├── codebuild ├── .gitignore ├── CanaryWrapper_MetricFunctions.py └── mqtt-canary-test.yml ├── NOTICE ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── documentation.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── clang-format.yml │ ├── handle-stale-discussions.yml │ ├── closed-issue-message.yml │ ├── codecov.yml │ ├── issue-regression-labeler.yml │ ├── stale_issue.yml │ └── ci.yml ├── CODE_OF_CONDUCT.md ├── include └── aws │ └── mqtt │ ├── private │ ├── shared.h │ ├── request-response │ │ ├── request_response_client.h │ │ ├── request_response_subscription_set.h │ │ ├── protocol_adapter.h │ │ └── subscription_manager.h │ ├── mqtt_client_test_helper.h │ ├── fixed_header.h │ ├── v5 │ │ ├── mqtt5_topic_alias.h │ │ ├── mqtt5_callbacks.h │ │ └── rate_limiters.h │ ├── mqtt311_decoder.h │ ├── client_impl_shared.h │ ├── topic_tree.h │ ├── mqtt311_listener.h │ └── mqtt_subscription_set.h │ ├── exports.h │ ├── v5 │ └── mqtt5_listener.h │ └── mqtt.h ├── cmake └── aws-c-mqtt-config.cmake ├── .clang-tidy ├── bin ├── mqtt5canary │ └── CMakeLists.txt ├── elastipubsub │ └── CMakeLists.txt ├── elastishadow │ └── CMakeLists.txt └── elastipubsub5 │ └── CMakeLists.txt ├── source ├── shared.c ├── v5 │ ├── mqtt5_listener.c │ ├── mqtt5_callbacks.c │ └── rate_limiters.c ├── fixed_header.c ├── client_impl_shared.c └── mqtt311_decoder.c ├── format-check.py ├── .clang-format ├── CONTRIBUTING.md ├── .gitignore ├── CMakeLists.txt ├── tests ├── v3 │ ├── mqtt311_testing_utils.h │ └── mqtt_mock_server_handler.h ├── shared_utils_tests.c └── v5 │ ├── mqtt5_utils_tests.c │ └── mqtt5_testing_utils.h └── README.md /codebuild/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS C Mqtt 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: Apache-2.0. 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 General Question 4 | url: https://github.com/awslabs/aws-c-mqtt/discussions/categories/q-a 5 | about: Please ask and answer questions as a discussion thread 6 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | clang-format: 7 | 8 | runs-on: ubuntu-24.04 # latest 9 | 10 | steps: 11 | - name: Checkout Sources 12 | uses: actions/checkout@v4 13 | 14 | - name: clang-format lint 15 | run: | 16 | ./format-check.py 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /.github/workflows/handle-stale-discussions.yml: -------------------------------------------------------------------------------- 1 | name: HandleStaleDiscussions 2 | on: 3 | schedule: 4 | - cron: "0 9 * * 1" 5 | discussion_comment: 6 | types: [created] 7 | 8 | jobs: 9 | handle-stale-discussions: 10 | name: Handle stale discussions 11 | runs-on: ubuntu-latest 12 | permissions: 13 | discussions: write 14 | steps: 15 | - name: Stale discussions action 16 | uses: aws-github-ops/handle-stale-discussions@v1 17 | env: 18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 19 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/shared.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_MQTT_SHARED_CONSTANTS_H 7 | #define AWS_MQTT_SHARED_CONSTANTS_H 8 | 9 | #include 10 | 11 | #include 12 | 13 | AWS_EXTERN_C_BEGIN 14 | 15 | AWS_MQTT_API extern const struct aws_byte_cursor *g_websocket_handshake_default_path; 16 | AWS_MQTT_API extern const struct aws_http_header *g_websocket_handshake_default_protocol_header; 17 | 18 | AWS_EXTERN_C_END 19 | 20 | #endif /* AWS_MQTT_SHARED_CONSTANTS_H */ 21 | -------------------------------------------------------------------------------- /cmake/aws-c-mqtt-config.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | find_dependency(aws-c-http) 4 | 5 | macro(aws_load_targets type) 6 | include(${CMAKE_CURRENT_LIST_DIR}/${type}/@PROJECT_NAME@-targets.cmake) 7 | endmacro() 8 | 9 | # try to load the lib follow BUILD_SHARED_LIBS. Fall back if not exist. 10 | if(BUILD_SHARED_LIBS) 11 | if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/shared") 12 | aws_load_targets(shared) 13 | else() 14 | aws_load_targets(static) 15 | endif() 16 | else() 17 | if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/static") 18 | aws_load_targets(static) 19 | else() 20 | aws_load_targets(shared) 21 | endif() 22 | endif() 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "📕 Documentation Issue" 3 | description: Report an issue in the API Reference documentation or Developer Guide 4 | title: "(short issue description)" 5 | labels: [documentation, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the issue 12 | description: A clear and concise description of the issue. 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: links 18 | attributes: 19 | label: Links 20 | description: | 21 | Include links to affected documentation page(s). 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,readability-*,modernize-*,bugprone-*,misc-*,google-runtime-int,llvm-header-guard,fuchsia-restrict-system-includes,-clang-analyzer-valist.Uninitialized,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-alpha.*,-readability-magic-numbers' 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: '.*\.[h|inl]$' 5 | FormatStyle: 'file' 6 | CheckOptions: 7 | - key: readability-braces-around-statements.ShortStatementLines 8 | value: '1' 9 | - key: google-runtime-int.TypeSufix 10 | value: '_t' 11 | - key: fuchsia-restrict-system-includes.Includes 12 | value: '*,-stdint.h,-stdbool.h,-assert.h' 13 | 14 | ... 15 | -------------------------------------------------------------------------------- /.github/workflows/closed-issue-message.yml: -------------------------------------------------------------------------------- 1 | name: Closed Issue Message 2 | on: 3 | issues: 4 | types: [closed] 5 | jobs: 6 | auto_comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: aws-actions/closed-issue-message@v1 10 | with: 11 | # These inputs are both required 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | message: | 14 | ### ⚠️COMMENT VISIBILITY WARNING⚠️ 15 | Comments on closed issues are hard for our team to see. 16 | If you need more assistance, please either tag a team member or open a new issue that references this one. 17 | If you wish to keep having a conversation with other community members under this issue feel free to do so. 18 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/request-response/request_response_client.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H 2 | #define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | 13 | AWS_EXTERN_C_BEGIN 14 | 15 | struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_acquire_internal( 16 | struct aws_mqtt_request_response_client *client); 17 | 18 | struct aws_mqtt_request_response_client *aws_mqtt_request_response_client_release_internal( 19 | struct aws_mqtt_request_response_client *client); 20 | 21 | AWS_EXTERN_C_END 22 | 23 | #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_RESPONSE_CLIENT_H */ 24 | -------------------------------------------------------------------------------- /bin/mqtt5canary/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(mqtt5canary C) 2 | 3 | file(GLOB MQTT5CANARY_SRC 4 | "*.c" 5 | ) 6 | 7 | set(MQTT5CANARY_PROJECT_NAME mqtt5canary) 8 | add_executable(${MQTT5CANARY_PROJECT_NAME} ${MQTT5CANARY_SRC}) 9 | aws_set_common_properties(${MQTT5CANARY_PROJECT_NAME}) 10 | 11 | 12 | target_include_directories(${MQTT5CANARY_PROJECT_NAME} PUBLIC 13 | $ 14 | $) 15 | 16 | target_link_libraries(${MQTT5CANARY_PROJECT_NAME} PRIVATE aws-c-mqtt) 17 | 18 | if (BUILD_SHARED_LIBS AND NOT WIN32) 19 | message(INFO " mqtt5canary will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application") 20 | endif() 21 | 22 | install(TARGETS ${MQTT5CANARY_PROJECT_NAME} 23 | EXPORT ${MQTT5CANARY_PROJECT_NAME}-targets 24 | COMPONENT Runtime 25 | RUNTIME 26 | DESTINATION ${CMAKE_INSTALL_BINDIR} 27 | COMPONENT Runtime) 28 | -------------------------------------------------------------------------------- /bin/elastipubsub/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(elastipubsub C) 2 | 3 | file(GLOB ELASTIPUBSUB_SRC 4 | "*.c" 5 | ) 6 | 7 | set(ELASTIPUBSUB_PROJECT_NAME elastipubsub) 8 | add_executable(${ELASTIPUBSUB_PROJECT_NAME} ${ELASTIPUBSUB_SRC}) 9 | aws_set_common_properties(${ELASTIPUBSUB_PROJECT_NAME}) 10 | 11 | 12 | target_include_directories(${ELASTIPUBSUB_PROJECT_NAME} PUBLIC 13 | $ 14 | $) 15 | 16 | target_link_libraries(${ELASTIPUBSUB_PROJECT_NAME} PRIVATE aws-c-mqtt) 17 | 18 | if (BUILD_SHARED_LIBS AND NOT WIN32) 19 | message(INFO " elastiPUBSUB will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application") 20 | endif() 21 | 22 | install(TARGETS ${ELASTIPUBSUB_PROJECT_NAME} 23 | EXPORT ${ELASTIPUBSUB_PROJECT_NAME}-targets 24 | COMPONENT Runtime 25 | RUNTIME 26 | DESTINATION ${CMAKE_INSTALL_BINDIR} 27 | COMPONENT Runtime) 28 | -------------------------------------------------------------------------------- /bin/elastishadow/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(elastishadow C) 2 | 3 | file(GLOB ELASTISHADOW_SRC 4 | "*.c" 5 | ) 6 | 7 | set(ELASTISHADOW_PROJECT_NAME elastishadow) 8 | add_executable(${ELASTISHADOW_PROJECT_NAME} ${ELASTISHADOW_SRC}) 9 | aws_set_common_properties(${ELASTISHADOW_PROJECT_NAME}) 10 | 11 | 12 | target_include_directories(${ELASTISHADOW_PROJECT_NAME} PUBLIC 13 | $ 14 | $) 15 | 16 | target_link_libraries(${ELASTISHADOW_PROJECT_NAME} PRIVATE aws-c-mqtt) 17 | 18 | if (BUILD_SHARED_LIBS AND NOT WIN32) 19 | message(INFO " elastishadow will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application") 20 | endif() 21 | 22 | install(TARGETS ${ELASTISHADOW_PROJECT_NAME} 23 | EXPORT ${ELASTISHADOW_PROJECT_NAME}-targets 24 | COMPONENT Runtime 25 | RUNTIME 26 | DESTINATION ${CMAKE_INSTALL_BINDIR} 27 | COMPONENT Runtime) 28 | -------------------------------------------------------------------------------- /source/shared.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | /* 11 | * These defaults were chosen because they're commmon in other MQTT libraries. 12 | * The user can modify the request in their transform callback if they need to. 13 | */ 14 | static const struct aws_byte_cursor s_websocket_handshake_default_path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/mqtt"); 15 | const struct aws_byte_cursor *g_websocket_handshake_default_path = &s_websocket_handshake_default_path; 16 | 17 | static const struct aws_http_header s_websocket_handshake_default_protocol_header = { 18 | .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Sec-WebSocket-Protocol"), 19 | .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("mqtt"), 20 | }; 21 | const struct aws_http_header *g_websocket_handshake_default_protocol_header = 22 | &s_websocket_handshake_default_protocol_header; 23 | -------------------------------------------------------------------------------- /include/aws/mqtt/exports.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_EXPORTS_H 2 | #define AWS_MQTT_EXPORTS_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #if defined(AWS_CRT_USE_WINDOWS_DLL_SEMANTICS) || defined(_WIN32) 8 | # ifdef AWS_MQTT_USE_IMPORT_EXPORT 9 | # ifdef AWS_MQTT_EXPORTS 10 | # define AWS_MQTT_API __declspec(dllexport) 11 | # else 12 | # define AWS_MQTT_API __declspec(dllimport) 13 | # endif /* AWS_MQTT_EXPORTS */ 14 | # else 15 | # define AWS_MQTT_API 16 | # endif /* USE_IMPORT_EXPORT */ 17 | 18 | #else /* defined (AWS_CRT_USE_WINDOWS_DLL_SEMANTICS) || defined (_WIN32) */ 19 | # if defined(AWS_MQTT_USE_IMPORT_EXPORT) && defined(AWS_MQTT_EXPORTS) 20 | # define AWS_MQTT_API __attribute__((visibility("default"))) 21 | # else 22 | # define AWS_MQTT_API 23 | # endif 24 | 25 | #endif /* defined (AWS_CRT_USE_WINDOWS_DLL_SEMANTICS) || defined (_WIN32) */ 26 | 27 | #endif /* AWS_MQTT_EXPORTS_H */ 28 | -------------------------------------------------------------------------------- /bin/elastipubsub5/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(elastipubsub5 C) 2 | 3 | file(GLOB ELASTIPUBSUB_SRC 4 | "*.c" 5 | ) 6 | 7 | set(ELASTIPUBSUB_MQTT5_PROJECT_NAME elastipubsub5) 8 | add_executable(${ELASTIPUBSUB_MQTT5_PROJECT_NAME} ${ELASTIPUBSUB_SRC}) 9 | aws_set_common_properties(${ELASTIPUBSUB_MQTT5_PROJECT_NAME}) 10 | 11 | 12 | target_include_directories(${ELASTIPUBSUB_MQTT5_PROJECT_NAME} PUBLIC 13 | $ 14 | $) 15 | 16 | target_link_libraries(${ELASTIPUBSUB_MQTT5_PROJECT_NAME} PRIVATE aws-c-mqtt) 17 | 18 | if (BUILD_SHARED_LIBS AND NOT WIN32) 19 | message(INFO " elastiPUBSUB will be built with shared libs, but you may need to set LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib to run the application") 20 | endif() 21 | 22 | install(TARGETS ${ELASTIPUBSUB_MQTT5_PROJECT_NAME} 23 | EXPORT ${ELASTIPUBSUB_MQTT5_PROJECT_NAME}-targets 24 | COMPONENT Runtime 25 | RUNTIME 26 | DESTINATION ${CMAKE_INSTALL_BINDIR} 27 | COMPONENT Runtime) 28 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Code coverage check 2 | 3 | on: 4 | push: 5 | 6 | env: 7 | BUILDER_VERSION: v0.9.74 8 | BUILDER_SOURCE: releases 9 | BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net 10 | PACKAGE_NAME: aws-c-mqtt 11 | CRT_CI_ROLE: ${{ secrets.CRT_CI_ROLE_ARN }} 12 | AWS_DEFAULT_REGION: us-east-1 13 | 14 | permissions: 15 | id-token: write # This is required for requesting the JWT 16 | 17 | jobs: 18 | codecov-linux: 19 | runs-on: ubuntu-24.04 20 | steps: 21 | - uses: aws-actions/configure-aws-credentials@v4 22 | with: 23 | role-to-assume: ${{ env.CRT_CI_ROLE }} 24 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 25 | - name: Checkout Sources 26 | uses: actions/checkout@v3 27 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 28 | run: | 29 | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" 30 | chmod a+x builder 31 | ./builder build -p ${{ env.PACKAGE_NAME }} --compiler=gcc --coverage 32 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/mqtt_client_test_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_CLIENT_TEST_HELPER_H 2 | #define AWS_MQTT_CLIENT_TEST_HELPER_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | struct aws_allocator; 12 | struct aws_byte_cursor; 13 | struct aws_mqtt_client_connection_311_impl; 14 | struct aws_string; 15 | 16 | AWS_EXTERN_C_BEGIN 17 | 18 | /** This is for testing applications sending MQTT payloads. Don't ever include this file outside of a unit test. */ 19 | 20 | /** result buffer will be initialized and payload will be written into it */ 21 | AWS_MQTT_API 22 | int aws_mqtt_client_get_payload_for_outstanding_publish_packet( 23 | struct aws_mqtt_client_connection *connection, 24 | uint16_t packet_id, 25 | struct aws_allocator *allocator, 26 | struct aws_byte_buf *result); 27 | 28 | AWS_MQTT_API 29 | int aws_mqtt_client_get_topic_for_outstanding_publish_packet( 30 | struct aws_mqtt_client_connection *connection, 31 | uint16_t packet_id, 32 | struct aws_allocator *allocator, 33 | struct aws_string **result); 34 | 35 | AWS_EXTERN_C_END 36 | 37 | #endif // AWS_C_IOT_MQTT_CLIENT_TEST_HELPER_H 38 | -------------------------------------------------------------------------------- /.github/workflows/issue-regression-labeler.yml: -------------------------------------------------------------------------------- 1 | # Apply potential regression label on issues 2 | name: issue-regression-label 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | jobs: 7 | add-regression-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | steps: 12 | - name: Fetch template body 13 | id: check_regression 14 | uses: actions/github-script@v7 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | TEMPLATE_BODY: ${{ github.event.issue.body }} 18 | with: 19 | script: | 20 | const regressionPattern = /\[x\] Select this option if this issue appears to be a regression\./i; 21 | const template = `${process.env.TEMPLATE_BODY}` 22 | const match = regressionPattern.test(template); 23 | core.setOutput('is_regression', match); 24 | - name: Manage regression label 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | run: | 28 | if [ "${{ steps.check_regression.outputs.is_regression }}" == "true" ]; then 29 | gh issue edit ${{ github.event.issue.number }} --add-label "potential-regression" -R ${{ github.repository }} 30 | else 31 | gh issue edit ${{ github.event.issue.number }} --remove-label "potential-regression" -R ${{ github.repository }} 32 | fi 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | description: Suggest an idea for this project 4 | title: "(short issue description)" 5 | labels: [feature-request, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the feature 12 | description: A clear and concise description of the feature you are proposing. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: use-case 17 | attributes: 18 | label: Use Case 19 | description: | 20 | Why do you need this feature? For example: "I'm always frustrated when..." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: Proposed Solution 27 | description: | 28 | Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: other 33 | attributes: 34 | label: Other Information 35 | description: | 36 | Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. 37 | validations: 38 | required: false 39 | - type: checkboxes 40 | id: ack 41 | attributes: 42 | label: Acknowledgements 43 | options: 44 | - label: I may be able to implement this feature request 45 | required: false 46 | - label: This feature might incur a breaking change 47 | required: false 48 | -------------------------------------------------------------------------------- /format-check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | from pathlib import Path 5 | import re 6 | from subprocess import list2cmdline, run 7 | from tempfile import NamedTemporaryFile 8 | 9 | CLANG_FORMAT_VERSION = '18.1.6' 10 | 11 | INCLUDE_REGEX = re.compile( 12 | r'^(include|source|tests|verification)/.*\.(c|h|inl)$') 13 | EXCLUDE_REGEX = re.compile(r'^$') 14 | 15 | arg_parser = argparse.ArgumentParser(description="Check with clang-format") 16 | arg_parser.add_argument('-i', '--inplace-edit', action='store_true', 17 | help="Edit files inplace") 18 | args = arg_parser.parse_args() 19 | 20 | os.chdir(Path(__file__).parent) 21 | 22 | # create file containing list of all files to format 23 | filepaths_file = NamedTemporaryFile(delete=False) 24 | for dirpath, dirnames, filenames in os.walk('.'): 25 | for filename in filenames: 26 | # our regexes expect filepath to use forward slash 27 | filepath = Path(dirpath, filename).as_posix() 28 | if not INCLUDE_REGEX.match(filepath): 29 | continue 30 | if EXCLUDE_REGEX.match(filepath): 31 | continue 32 | 33 | filepaths_file.write(f"{filepath}\n".encode()) 34 | filepaths_file.close() 35 | 36 | # use pipx to run clang-format from PyPI 37 | # this is a simple way to run the same clang-format version regardless of OS 38 | cmd = ['pipx', 'run', f'clang-format=={CLANG_FORMAT_VERSION}', 39 | f'--files={filepaths_file.name}'] 40 | if args.inplace_edit: 41 | cmd += ['-i'] 42 | else: 43 | cmd += ['--Werror', '--dry-run'] 44 | 45 | print(f"{Path.cwd()}$ {list2cmdline(cmd)}") 46 | if run(cmd).returncode: 47 | exit(1) 48 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Mozilla 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Right 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: Inline 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BreakBeforeBinaryOperators: None 21 | BreakBeforeBraces: Attach 22 | BreakBeforeTernaryOperators: true 23 | BreakStringLiterals: true 24 | ColumnLimit: 120 25 | ContinuationIndentWidth: 4 26 | DerivePointerAlignment: false 27 | IncludeBlocks: Preserve 28 | IndentCaseLabels: true 29 | IndentPPDirectives: AfterHash 30 | IndentWidth: 4 31 | IndentWrappedFunctionNames: true 32 | KeepEmptyLinesAtTheStartOfBlocks: true 33 | MacroBlockBegin: '' 34 | MacroBlockEnd: '' 35 | MaxEmptyLinesToKeep: 1 36 | PenaltyBreakAssignment: 2 37 | PenaltyBreakBeforeFirstCallParameter: 19 38 | PenaltyBreakComment: 300 39 | PenaltyBreakFirstLessLess: 120 40 | PenaltyBreakString: 1000 41 | PenaltyExcessCharacter: 1000000 42 | PenaltyReturnTypeOnItsOwnLine: 100000 43 | PointerAlignment: Right 44 | ReflowComments: true 45 | SortIncludes: true 46 | SpaceAfterCStyleCast: false 47 | SpaceBeforeAssignmentOperators: true 48 | SpaceBeforeParens: ControlStatements 49 | SpaceInEmptyParentheses: false 50 | SpacesInContainerLiterals: true 51 | SpacesInCStyleCastParentheses: false 52 | SpacesInParentheses: false 53 | SpacesInSquareBrackets: false 54 | Standard: Cpp11 55 | TabWidth: 4 56 | UseTab: Never 57 | ... 58 | 59 | -------------------------------------------------------------------------------- /codebuild/CanaryWrapper_MetricFunctions.py: -------------------------------------------------------------------------------- 1 | # Contains all of the metric reporting functions for the Canary Wrappers 2 | 3 | # Needs to be installed prior to running 4 | import psutil 5 | 6 | 7 | cache_cpu_psutil_process = None 8 | def get_metric_total_cpu_usage(psutil_process : psutil.Process): 9 | global cache_cpu_psutil_process 10 | 11 | try: 12 | if (psutil_process == None): 13 | print ("ERROR - No psutil.process passed! Cannot gather metric!", flush=True) 14 | return None 15 | # We always need to skip the first CPU poll 16 | if (cache_cpu_psutil_process != psutil_process): 17 | psutil.cpu_percent(interval=None) 18 | cache_cpu_psutil_process = psutil_process 19 | return None 20 | return psutil.cpu_percent(interval=None) 21 | except Exception as e: 22 | print ("ERROR - exception occurred gathering metrics!") 23 | print (f"Exception: {repr(e)}", flush=True) 24 | return None 25 | 26 | # Note: This value is in BYTES. 27 | def get_metric_total_memory_usage_value(psutil_process : psutil.Process): 28 | try: 29 | if (psutil_process == None): 30 | print ("ERROR - No psutil.process passed! Cannot gather metric!", flush=True) 31 | return None 32 | return psutil.virtual_memory()[3] 33 | except Exception as e: 34 | print ("ERROR - exception occurred gathering metrics!") 35 | print (f"Exception: {repr(e)}", flush=True) 36 | return None 37 | 38 | 39 | def get_metric_total_memory_usage_percent(psutil_process : psutil.Process): 40 | try: 41 | if (psutil_process == None): 42 | print ("ERROR - No psutil.process passed! Cannot gather metric!", flush=True) 43 | return None 44 | return psutil.virtual_memory()[2] 45 | except Exception as e: 46 | print ("ERROR - exception occurred gathering metrics!") 47 | print (f"Exception: {repr(e)}", flush=True) 48 | return None 49 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/fixed_header.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_FIXED_HEADER_H 2 | #define AWS_MQTT_PRIVATE_FIXED_HEADER_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | 13 | /* Represents the types of the MQTT control packets [MQTT-2.2.1]. */ 14 | enum aws_mqtt_packet_type { 15 | /* reserved = 0, */ 16 | AWS_MQTT_PACKET_CONNECT = 1, 17 | AWS_MQTT_PACKET_CONNACK, 18 | AWS_MQTT_PACKET_PUBLISH, 19 | AWS_MQTT_PACKET_PUBACK, 20 | AWS_MQTT_PACKET_PUBREC, 21 | AWS_MQTT_PACKET_PUBREL, 22 | AWS_MQTT_PACKET_PUBCOMP, 23 | AWS_MQTT_PACKET_SUBSCRIBE, 24 | AWS_MQTT_PACKET_SUBACK, 25 | AWS_MQTT_PACKET_UNSUBSCRIBE, 26 | AWS_MQTT_PACKET_UNSUBACK, 27 | AWS_MQTT_PACKET_PINGREQ, 28 | AWS_MQTT_PACKET_PINGRESP, 29 | AWS_MQTT_PACKET_DISCONNECT, 30 | /* reserved = 15, */ 31 | }; 32 | 33 | /** 34 | * Represents the fixed header [MQTT-2.2]. 35 | */ 36 | struct aws_mqtt_fixed_header { 37 | enum aws_mqtt_packet_type packet_type; 38 | size_t remaining_length; 39 | uint8_t flags; 40 | }; 41 | 42 | /** 43 | * Get the type of packet from the first byte of the buffer [MQTT-2.2.1]. 44 | */ 45 | AWS_MQTT_API enum aws_mqtt_packet_type aws_mqtt_get_packet_type(const uint8_t *buffer); 46 | 47 | /** 48 | * Get traits describing a packet described by header [MQTT-2.2.2]. 49 | */ 50 | AWS_MQTT_API bool aws_mqtt_packet_has_flags(const struct aws_mqtt_fixed_header *header); 51 | 52 | /** 53 | * Write a fixed header to a byte stream. 54 | */ 55 | AWS_MQTT_API int aws_mqtt_fixed_header_encode(struct aws_byte_buf *buf, const struct aws_mqtt_fixed_header *header); 56 | 57 | /** 58 | * Read a fixed header from a byte stream. 59 | */ 60 | AWS_MQTT_API int aws_mqtt_fixed_header_decode(struct aws_byte_cursor *cur, struct aws_mqtt_fixed_header *header); 61 | 62 | AWS_MQTT_API int aws_mqtt311_decode_remaining_length(struct aws_byte_cursor *cur, size_t *remaining_length_out); 63 | 64 | #endif /* AWS_MQTT_PRIVATE_FIXED_HEADER_H */ 65 | -------------------------------------------------------------------------------- /.github/workflows/stale_issue.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | - cron: "0 9 * * 1" 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-latest 11 | name: Stale issue job 12 | steps: 13 | - uses: aws-actions/stale-issue-cleanup@v3 14 | with: 15 | # Setting messages to an empty string will cause the automation to skip 16 | # that category 17 | ancient-issue-message: Greetings! Sorry to say but this is a very old issue that is probably not getting as much attention as it deservers. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to open a new one. 18 | stale-issue-message: Greetings! It looks like this issue hasn’t been active in longer than a week. We encourage you to check if this is still an issue in the latest release. Because it has been longer than a week since the last update on this, and in the absence of more information, we will be closing this issue soon. If you find that this is still a problem, please feel free to provide a comment or add an upvote to prevent automatic closure, or if the issue is already closed, please feel free to open a new one. 19 | stale-pr-message: Greetings! It looks like this PR hasn’t been active in longer than a week, add a comment or an upvote to prevent automatic closure, or if the issue is already closed, please feel free to open a new one. 20 | 21 | # These labels are required 22 | stale-issue-label: closing-soon 23 | exempt-issue-label: automation-exempt 24 | stale-pr-label: closing-soon 25 | exempt-pr-label: pr/needs-review 26 | response-requested-label: response-requested 27 | 28 | # Don't set closed-for-staleness label to skip closing very old issues 29 | # regardless of label 30 | closed-for-staleness-label: closed-for-staleness 31 | 32 | # Issue timing 33 | days-before-stale: 2 34 | days-before-close: 5 35 | days-before-ancient: 36500 36 | 37 | # If you don't want to mark a issue as being ancient based on a 38 | # threshold of "upvotes", you can set this here. An "upvote" is 39 | # the total number of +1, heart, hooray, and rocket reactions 40 | # on an issue. 41 | minimum-upvotes-to-exempt: 1 42 | 43 | repo-token: ${{ secrets.GITHUB_TOKEN }} 44 | loglevel: DEBUG 45 | # Set dry-run to true to not perform label or close actions. 46 | dry-run: false 47 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/v5/mqtt5_topic_alias.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_MQTT_MQTT5_TOPIC_ALIAS_H 7 | #define AWS_MQTT_MQTT5_TOPIC_ALIAS_H 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | /* outbound resolvers are polymorphic; implementations are completely internal */ 15 | struct aws_mqtt5_outbound_topic_alias_resolver; 16 | 17 | /* there are only two possibilities for inbound resolution: on or off */ 18 | struct aws_mqtt5_inbound_topic_alias_resolver { 19 | struct aws_allocator *allocator; 20 | 21 | struct aws_array_list topic_aliases; 22 | }; 23 | 24 | AWS_EXTERN_C_BEGIN 25 | 26 | AWS_MQTT_API int aws_mqtt5_inbound_topic_alias_resolver_init( 27 | struct aws_mqtt5_inbound_topic_alias_resolver *resolver, 28 | struct aws_allocator *allocator); 29 | 30 | AWS_MQTT_API void aws_mqtt5_inbound_topic_alias_resolver_clean_up( 31 | struct aws_mqtt5_inbound_topic_alias_resolver *resolver); 32 | 33 | AWS_MQTT_API int aws_mqtt5_inbound_topic_alias_resolver_reset( 34 | struct aws_mqtt5_inbound_topic_alias_resolver *resolver, 35 | uint16_t cache_size); 36 | 37 | AWS_MQTT_API int aws_mqtt5_inbound_topic_alias_resolver_resolve_alias( 38 | struct aws_mqtt5_inbound_topic_alias_resolver *resolver, 39 | uint16_t alias, 40 | struct aws_byte_cursor *topic_out); 41 | 42 | AWS_MQTT_API int aws_mqtt5_inbound_topic_alias_resolver_register_alias( 43 | struct aws_mqtt5_inbound_topic_alias_resolver *resolver, 44 | uint16_t alias, 45 | struct aws_byte_cursor topic); 46 | 47 | AWS_MQTT_API struct aws_mqtt5_outbound_topic_alias_resolver *aws_mqtt5_outbound_topic_alias_resolver_new( 48 | struct aws_allocator *allocator, 49 | enum aws_mqtt5_client_outbound_topic_alias_behavior_type outbound_alias_behavior); 50 | 51 | AWS_MQTT_API void aws_mqtt5_outbound_topic_alias_resolver_destroy( 52 | struct aws_mqtt5_outbound_topic_alias_resolver *resolver); 53 | 54 | AWS_MQTT_API int aws_mqtt5_outbound_topic_alias_resolver_reset( 55 | struct aws_mqtt5_outbound_topic_alias_resolver *resolver, 56 | uint16_t topic_alias_maximum); 57 | 58 | AWS_MQTT_API int aws_mqtt5_outbound_topic_alias_resolver_resolve_outbound_publish( 59 | struct aws_mqtt5_outbound_topic_alias_resolver *resolver, 60 | const struct aws_mqtt5_packet_publish_view *publish_view, 61 | uint16_t *topic_alias_out, 62 | struct aws_byte_cursor *topic_out); 63 | 64 | AWS_EXTERN_C_END 65 | 66 | #endif /* AWS_MQTT_MQTT5_TOPIC_ALIAS_H */ 67 | -------------------------------------------------------------------------------- /codebuild/mqtt-canary-test.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | env: 3 | shell: bash 4 | variables: 5 | CANARY_DURATION: 25200 6 | CANARY_THREADS: 3 7 | CANARY_TPS: 50 8 | CANARY_CLIENT_COUNT: 10 9 | CANARY_LOG_FILE: 'canary_log.txt' 10 | CANARY_LOG_LEVEL: 'ERROR' 11 | BUILDER_VERSION: v0.9.55 12 | BUILDER_SOURCE: releases 13 | BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net 14 | PACKAGE_NAME: 'aws-c-mqtt' 15 | CANARY_TEST_EXE_PATH: build/install/bin/mqtt5canary 16 | CANARY_SERVER_ARN: Mqtt5MosquittoSever 17 | CANARY_BUILD_S3_DST: mqtt5-canary/s3 18 | 19 | phases: 20 | install: 21 | commands: 22 | # install c++ dev libraries for codebuild environment. 23 | - sudo yum update -y 24 | - sudo yum groupinstall "Development Tools" -y 25 | # Install necessary lib for canary wrapper 26 | - sudo yum install gcc python3-dev -y 27 | - sudo yum install pip -y 28 | - python3 -m pip install psutil 29 | - python3 -m pip install boto3 30 | # Install Cmake3 31 | - wget https://cmake.org/files/v3.18/cmake-3.18.0.tar.gz 32 | - tar -xvzf cmake-3.18.0.tar.gz 33 | - cd cmake-3.18.0 34 | - ./bootstrap 35 | - make 36 | - sudo make install 37 | - cd .. 38 | build: 39 | commands: 40 | - export CANNARY_TEST_EXE=$CODEBUILD_SRC_DIR/$CANARY_TEST_EXE_PATH 41 | - echo $CANNARY_TEST_EXE 42 | - export ENDPOINT=$(aws secretsmanager get-secret-value --secret-id "$CANARY_SERVER_ARN" --query "SecretString" | cut -f2 -d":" | sed -e 's/[\\\"\}]//g') 43 | - export S3_DST=$(aws secretsmanager get-secret-value --secret-id "$CANARY_BUILD_S3_DST" --query "SecretString" | cut -f2,3 -d":" | sed -e 's/[\\\"\}]//g') 44 | - export GIT_HASH=$(git rev-parse HEAD) 45 | # Build library and test 46 | - python3 -c "from urllib.request import urlretrieve; urlretrieve('$BUILDER_HOST/$BUILDER_SOURCE/$BUILDER_VERSION/builder.pyz?run=$CODEBUILD_BUILD_ID', 'builder.pyz')" 47 | - python3 builder.pyz build -p aws-c-mqtt 48 | # Canary related: 49 | # ========== 50 | - echo run canary test through wrapper 51 | # Start canary 52 | - python3 codebuild/CanaryWrapper.py --canary_executable $CANNARY_TEST_EXE --canary_arguments "-s ${CANARY_DURATION} -t ${CANARY_THREADS} -T ${CANARY_TPS} -C ${CANARY_CLIENT_COUNT} -l ${CANARY_LOG_FILE} -v ${CANARY_LOG_LEVEL} endpoint ${ENDPOINT}" --git_hash ${GIT_HASH} --git_repo_name $PACKAGE_NAME --codebuild_log_path $CODEBUILD_LOG_PATH 53 | - aws s3 cp ./${CANARY_LOG_FILE} ${S3_DST}log/${GIT_HASH}/ 54 | # Upload built canary test build result to s3 bucket 55 | - zip -r latestBuild.zip build/install 56 | - aws s3 cp ./latestBuild.zip ${S3_DST}build/latest 57 | # Upload latest source to S3 bucket 58 | - find * -type f ! -perm +r -exec zip latestSnapshot.zip {} + 59 | - aws s3 cp ./latestSnapshot.zip ${S3_DST}source/latest 60 | # ========== 61 | 62 | post_build: 63 | commands: 64 | - echo Build completed on `date` 65 | -------------------------------------------------------------------------------- /include/aws/mqtt/v5/mqtt5_listener.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_MQTT_MQTT5_LISTENER_H 7 | #define AWS_MQTT_MQTT5_LISTENER_H 8 | 9 | #include 10 | 11 | #include 12 | 13 | AWS_PUSH_SANE_WARNING_LEVEL 14 | 15 | /* 16 | * Callback signature for when an mqtt5 listener has completely destroyed itself. 17 | */ 18 | typedef void(aws_mqtt5_listener_termination_completion_fn)(void *complete_ctx); 19 | 20 | /** 21 | * A record that tracks MQTT5 client callbacks which can be dynamically injected via a listener. 22 | */ 23 | struct aws_mqtt5_callback_set { 24 | aws_mqtt5_listener_publish_received_fn *listener_publish_received_handler; 25 | void *listener_publish_received_handler_user_data; 26 | 27 | aws_mqtt5_client_connection_event_callback_fn *lifecycle_event_handler; 28 | void *lifecycle_event_handler_user_data; 29 | }; 30 | 31 | /** 32 | * Configuration options for MQTT5 listener objects. 33 | */ 34 | struct aws_mqtt5_listener_config { 35 | 36 | /** 37 | * MQTT5 client to listen to events on 38 | */ 39 | struct aws_mqtt5_client *client; 40 | 41 | /** 42 | * Callbacks to invoke when events occur on the MQTT5 client 43 | */ 44 | struct aws_mqtt5_callback_set listener_callbacks; 45 | 46 | /** 47 | * Listener destruction is asynchronous and thus requires a termination callback and associated user data 48 | * to notify the user that the listener has been fully destroyed and no further events will be received. 49 | */ 50 | aws_mqtt5_listener_termination_completion_fn *termination_callback; 51 | void *termination_callback_user_data; 52 | }; 53 | 54 | AWS_EXTERN_C_BEGIN 55 | 56 | /** 57 | * Creates a new MQTT5 listener object. For as long as the listener lives, incoming publishes and lifecycle events 58 | * will be forwarded to the callbacks configured on the listener. 59 | * 60 | * @param allocator allocator to use 61 | * @param config listener configuration 62 | * @return a new aws_mqtt5_listener object 63 | */ 64 | AWS_MQTT_API struct aws_mqtt5_listener *aws_mqtt5_listener_new( 65 | struct aws_allocator *allocator, 66 | struct aws_mqtt5_listener_config *config); 67 | 68 | /** 69 | * Adds a reference to an mqtt5 listener. 70 | * 71 | * @param listener listener to add a reference to 72 | * @return the listener object 73 | */ 74 | AWS_MQTT_API struct aws_mqtt5_listener *aws_mqtt5_listener_acquire(struct aws_mqtt5_listener *listener); 75 | 76 | /** 77 | * Removes a reference to an mqtt5 listener. When the reference count drops to zero, the listener's asynchronous 78 | * destruction will be started. 79 | * 80 | * @param listener listener to remove a reference from 81 | * @return NULL 82 | */ 83 | AWS_MQTT_API struct aws_mqtt5_listener *aws_mqtt5_listener_release(struct aws_mqtt5_listener *listener); 84 | 85 | AWS_EXTERN_C_END 86 | AWS_POP_SANE_WARNING_LEVEL 87 | 88 | #endif /* AWS_MQTT_MQTT5_LISTENER_H */ 89 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/v5/mqtt5_callbacks.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_MQTT_MQTT5_CALLBACKS_H 7 | #define AWS_MQTT_MQTT5_CALLBACKS_H 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | struct aws_mqtt5_callback_set; 15 | 16 | /* 17 | * An internal type for managing chains of callbacks attached to an mqtt5 client. Supports chains for 18 | * lifecycle event handling and incoming publish packet handling. 19 | * 20 | * Assumed to be owned and used only by an MQTT5 client. 21 | */ 22 | struct aws_mqtt5_callback_set_manager { 23 | struct aws_mqtt5_client *client; 24 | 25 | struct aws_linked_list callback_set_entries; 26 | 27 | uint64_t next_callback_set_entry_id; 28 | }; 29 | 30 | AWS_EXTERN_C_BEGIN 31 | 32 | /* 33 | * Initializes a callback set manager 34 | */ 35 | AWS_MQTT_API 36 | void aws_mqtt5_callback_set_manager_init( 37 | struct aws_mqtt5_callback_set_manager *manager, 38 | struct aws_mqtt5_client *client); 39 | 40 | /* 41 | * Cleans up a callback set manager. 42 | * 43 | * aws_mqtt5_callback_set_manager_init must have been previously called or this will crash. 44 | */ 45 | AWS_MQTT_API 46 | void aws_mqtt5_callback_set_manager_clean_up(struct aws_mqtt5_callback_set_manager *manager); 47 | 48 | /* 49 | * Adds a callback set to the front of the handler chain. Returns an integer id that can be used to selectively 50 | * remove the callback set from the manager. 51 | * 52 | * May only be called on the client's event loop thread. 53 | */ 54 | AWS_MQTT_API 55 | uint64_t aws_mqtt5_callback_set_manager_push_front( 56 | struct aws_mqtt5_callback_set_manager *manager, 57 | struct aws_mqtt5_callback_set *callback_set); 58 | 59 | /* 60 | * Removes a callback set from the handler chain. 61 | * 62 | * May only be called on the client's event loop thread. 63 | */ 64 | AWS_MQTT_API 65 | void aws_mqtt5_callback_set_manager_remove(struct aws_mqtt5_callback_set_manager *manager, uint64_t callback_set_id); 66 | 67 | /* 68 | * Walks the handler chain for an MQTT5 client's incoming publish messages. The chain's callbacks will be invoked 69 | * until either the end is reached or one of the callbacks returns true. 70 | * 71 | * May only be called on the client's event loop thread. 72 | */ 73 | AWS_MQTT_API 74 | void aws_mqtt5_callback_set_manager_on_publish_received( 75 | struct aws_mqtt5_callback_set_manager *manager, 76 | const struct aws_mqtt5_packet_publish_view *publish_view); 77 | 78 | /* 79 | * Walks the handler chain for an MQTT5 client's lifecycle events. 80 | * 81 | * May only be called on the client's event loop thread. 82 | */ 83 | AWS_MQTT_API 84 | void aws_mqtt5_callback_set_manager_on_lifecycle_event( 85 | struct aws_mqtt5_callback_set_manager *manager, 86 | const struct aws_mqtt5_client_lifecycle_event *lifecycle_event); 87 | 88 | AWS_EXTERN_C_END 89 | 90 | #endif /* AWS_MQTT_MQTT5_CALLBACKS_H */ 91 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | description: Report a bug 4 | title: "(short issue description)" 5 | labels: [bug, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the bug 12 | description: What is the problem? A clear and concise description of the bug. 13 | validations: 14 | required: true 15 | - type: checkboxes 16 | id: regression 17 | attributes: 18 | label: Regression Issue 19 | description: What is a regression? If it worked in a previous version but doesn't in the latest version, it's considered a regression. In this case, please provide specific version number in the report. 20 | options: 21 | - label: Select this option if this issue appears to be a regression. 22 | required: false 23 | - type: textarea 24 | id: expected 25 | attributes: 26 | label: Expected Behavior 27 | description: | 28 | What did you expect to happen? 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: current 33 | attributes: 34 | label: Current Behavior 35 | description: | 36 | What actually happened? 37 | 38 | Please include full errors, uncaught exceptions, stack traces, and relevant logs. 39 | If service responses are relevant, please include wire logs. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: reproduction 44 | attributes: 45 | label: Reproduction Steps 46 | description: | 47 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 48 | For more complex issues provide a repo with the smallest sample that reproduces the bug. 49 | 50 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 51 | The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: solution 56 | attributes: 57 | label: Possible Solution 58 | description: | 59 | Suggest a fix/reason for the bug 60 | validations: 61 | required: false 62 | - type: textarea 63 | id: context 64 | attributes: 65 | label: Additional Information/Context 66 | description: | 67 | Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. 68 | validations: 69 | required: false 70 | 71 | - type: input 72 | id: aws-c-mqtt-version 73 | attributes: 74 | label: aws-c-mqtt version used 75 | validations: 76 | required: true 77 | 78 | - type: input 79 | id: compiler-version 80 | attributes: 81 | label: Compiler and version used 82 | validations: 83 | required: true 84 | 85 | - type: input 86 | id: operating-system 87 | attributes: 88 | label: Operating System and version 89 | validations: 90 | required: true 91 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/awslabs/aws-c-mqtt/issues), or [recently closed](https://github.com/awslabs/aws-c-mqtt/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 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. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | 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-c-mqtt/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/awslabs/aws-c-mqtt/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/macos,linux,clion,windows,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=macos,linux,clion,windows,visualstudiocode 4 | 5 | ### CLion ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | 40 | # CMake 41 | cmake-build-*/ 42 | 43 | # Mongo Explorer plugin 44 | .idea/**/mongoSettings.xml 45 | 46 | # File-based project format 47 | *.iws 48 | 49 | # IntelliJ 50 | out/ 51 | 52 | # mpeltonen/sbt-idea plugin 53 | .idea_modules/ 54 | 55 | # JIRA plugin 56 | atlassian-ide-plugin.xml 57 | 58 | # Cursive Clojure plugin 59 | .idea/replstate.xml 60 | 61 | # Crashlytics plugin (for Android Studio and IntelliJ) 62 | com_crashlytics_export_strings.xml 63 | crashlytics.properties 64 | crashlytics-build.properties 65 | fabric.properties 66 | 67 | # Editor-based Rest Client 68 | .idea/httpRequests 69 | 70 | # Android studio 3.1+ serialized cache file 71 | .idea/caches/build_file_checksums.ser 72 | 73 | ### CLion Patch ### 74 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 75 | 76 | # *.iml 77 | # modules.xml 78 | # .idea/misc.xml 79 | # *.ipr 80 | 81 | # Sonarlint plugin 82 | .idea/sonarlint 83 | 84 | ### Linux ### 85 | *~ 86 | 87 | # temporary files which can be created if a process still has a handle open of a deleted file 88 | .fuse_hidden* 89 | 90 | # KDE directory preferences 91 | .directory 92 | 93 | # Linux trash folder which might appear on any partition or disk 94 | .Trash-* 95 | 96 | # .nfs files are created when an open file is removed but is still being accessed 97 | .nfs* 98 | 99 | ### macOS ### 100 | # General 101 | .DS_Store 102 | .AppleDouble 103 | .LSOverride 104 | 105 | # Icon must end with two \r 106 | Icon 107 | 108 | # Thumbnails 109 | ._* 110 | 111 | # Files that might appear in the root of a volume 112 | .DocumentRevisions-V100 113 | .fseventsd 114 | .Spotlight-V100 115 | .TemporaryItems 116 | .Trashes 117 | .VolumeIcon.icns 118 | .com.apple.timemachine.donotpresent 119 | 120 | # Directories potentially created on remote AFP share 121 | .AppleDB 122 | .AppleDesktop 123 | Network Trash Folder 124 | Temporary Items 125 | .apdisk 126 | 127 | ### VisualStudioCode ### 128 | .vscode/* 129 | 130 | ### VisualStudioCode Patch ### 131 | # Ignore all local history of files 132 | .history 133 | 134 | ### Windows ### 135 | # Windows thumbnail cache files 136 | Thumbs.db 137 | ehthumbs.db 138 | ehthumbs_vista.db 139 | 140 | # Dump file 141 | *.stackdump 142 | 143 | # Folder config file 144 | [Dd]esktop.ini 145 | 146 | # Recycle Bin used on file shares 147 | $RECYCLE.BIN/ 148 | 149 | # Windows Installer files 150 | *.cab 151 | *.msi 152 | *.msix 153 | *.msm 154 | *.msp 155 | 156 | # Windows shortcuts 157 | *.lnk 158 | 159 | # End of https://www.gitignore.io/api/macos,linux,clion,windows,visualstudiocode 160 | .idea 161 | *.pem 162 | *.crt 163 | *.key 164 | build/ 165 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9...3.31) 2 | project(aws-c-mqtt C) 3 | 4 | option(ASSERT_LOCK_HELD "Enable ASSERT_SYNCED_DATA_LOCK_HELD for checking thread issue" OFF) 5 | if (ASSERT_LOCK_HELD) 6 | add_definitions(-DASSERT_LOCK_HELD) 7 | endif() 8 | 9 | if (NOT IN_SOURCE_BUILD) 10 | # this is required so we can use aws-c-common's CMake modules 11 | find_package(aws-c-common REQUIRED) 12 | endif() 13 | 14 | include(AwsCFlags) 15 | include(AwsCheckHeaders) 16 | include(AwsSharedLibSetup) 17 | include(AwsSanitizers) 18 | include(CheckCCompilerFlag) 19 | include(AwsFindPackage) 20 | include(GNUInstallDirs) 21 | 22 | file(GLOB AWS_MQTT_HEADERS 23 | "include/aws/mqtt/*.h" 24 | ) 25 | 26 | file(GLOB AWS_MQTT5_HEADERS 27 | "include/aws/mqtt/v5/*.h" 28 | ) 29 | 30 | file(GLOB AWS_MQTT_RR_HEADERS 31 | "include/aws/mqtt/request-response/*.h" 32 | ) 33 | 34 | file(GLOB AWS_MQTT_PRIV_HEADERS 35 | "include/aws/mqtt/private/*.h" 36 | "include/aws/mqtt/private/request-response/*.h" 37 | "include/aws/mqtt/private/v5/*.h" 38 | ) 39 | 40 | file(GLOB AWS_MQTT_PRIV_EXPOSED_HEADERS 41 | "include/aws/mqtt/private/mqtt_client_test_helper.h" 42 | ) 43 | 44 | file(GLOB AWS_MQTT_SRC 45 | "source/*.c" 46 | "source/v5/*.c" 47 | "source/request-response/*.c" 48 | ) 49 | 50 | file(GLOB MQTT_HEADERS 51 | ${AWS_MQTT_HEADERS} 52 | ${AWS_MQTT_PRIV_HEADERS} 53 | ) 54 | 55 | file(GLOB AWS_MQTT5_HEADERS 56 | ${AWS_MQTT5_HEADERS} 57 | ) 58 | 59 | file(GLOB MQTT_SRC 60 | ${AWS_MQTT_SRC} 61 | ) 62 | 63 | add_library(${PROJECT_NAME} ${MQTT_HEADERS} ${MQTT_SRC}) 64 | aws_set_common_properties(${PROJECT_NAME}) 65 | aws_prepare_symbol_visibility_args(${PROJECT_NAME} "AWS_MQTT") 66 | aws_check_headers(${PROJECT_NAME} ${AWS_MQTT_HEADERS} ${AWS_MQTT5_HEADERS}) 67 | 68 | aws_add_sanitizers(${PROJECT_NAME}) 69 | 70 | # We are not ABI stable yet 71 | set_target_properties(${PROJECT_NAME} PROPERTIES VERSION 1.0.0) 72 | 73 | target_include_directories(${PROJECT_NAME} PUBLIC 74 | $ 75 | $) 76 | 77 | aws_use_package(aws-c-http) 78 | 79 | target_link_libraries(${PROJECT_NAME} PUBLIC ${DEP_AWS_LIBS}) 80 | aws_prepare_shared_lib_exports(${PROJECT_NAME}) 81 | 82 | install(FILES ${AWS_MQTT_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aws/mqtt" COMPONENT Development) 83 | install(FILES ${AWS_MQTT5_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aws/mqtt/v5" COMPONENT Development) 84 | install(FILES ${AWS_MQTT_RR_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aws/mqtt/request-response" COMPONENT Development) 85 | install(FILES ${AWS_MQTT_TESTING_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aws/testing/mqtt" COMPONENT Development) 86 | install(FILES ${AWS_MQTT_PRIV_EXPOSED_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/aws/mqtt/private" COMPONENT Development) 87 | 88 | if (BUILD_SHARED_LIBS) 89 | set (TARGET_DIR "shared") 90 | else() 91 | set (TARGET_DIR "static") 92 | endif() 93 | 94 | install(EXPORT "${PROJECT_NAME}-targets" 95 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/${TARGET_DIR}" 96 | NAMESPACE AWS:: 97 | COMPONENT Development) 98 | 99 | configure_file("cmake/${PROJECT_NAME}-config.cmake" 100 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" 101 | @ONLY) 102 | 103 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" 104 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/" 105 | COMPONENT Development) 106 | 107 | include(CTest) 108 | if (BUILD_TESTING) 109 | add_subdirectory(tests) 110 | if (NOT CMAKE_CROSSCOMPILING ) 111 | add_subdirectory(bin/elastipubsub) 112 | add_subdirectory(bin/elastipubsub5) 113 | add_subdirectory(bin/elastishadow) 114 | add_subdirectory(bin/mqtt5canary) 115 | endif() 116 | endif () 117 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/v5/rate_limiters.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_RATE_LIMITERS_H 7 | #define AWS_RATE_LIMITERS_H 8 | 9 | #include 10 | 11 | #include 12 | 13 | struct aws_rate_limiter_token_bucket_options { 14 | /* Clock function override. If left null, the high resolution clock will be used */ 15 | aws_io_clock_fn *clock_fn; 16 | 17 | /* How many tokens regenerate per second? */ 18 | uint64_t tokens_per_second; 19 | 20 | /* Initial amount of tokens the limiter will start with */ 21 | uint64_t initial_token_count; 22 | 23 | /* 24 | * Maximum amount of tokens the limiter can hold. Regenerated tokens that exceed this maximum are 25 | * discarded 26 | */ 27 | uint64_t maximum_token_count; 28 | }; 29 | 30 | /** 31 | * A token-bucket based rate limiter. 32 | * 33 | * Has an unusually complex implementation due to implementer-desired constraints: 34 | * 35 | * (1) Model regeneration as an integral rate per second. This is for ease-of-use. A regeneration interval would 36 | * be a much simpler implementation, but not as intuitive (or accurate for non-integral rates). 37 | * (2) Integer math only. Not comfortable falling back on doubles and not having a good understanding of the 38 | * accuracy issues, over time, that doing so would create. 39 | * (3) Minimize as much as possible the dangers of multiplication saturation and integer division round-down. 40 | * (4) No integer division round-off "error" accumulation allowed. Arguments could be made that it might be small 41 | * enough to never make a difference but I'd rather not even have the argument at all. 42 | * (5) A perfectly accurate how-long-must-I-wait query. Not just a safe over-estimate. 43 | */ 44 | struct aws_rate_limiter_token_bucket { 45 | uint64_t last_service_time; 46 | uint64_t current_token_count; 47 | 48 | uint64_t fractional_nanos; 49 | uint64_t fractional_nano_tokens; 50 | 51 | struct aws_rate_limiter_token_bucket_options config; 52 | }; 53 | 54 | AWS_EXTERN_C_BEGIN 55 | 56 | /** 57 | * Initializes a token-bucket-based rate limiter 58 | * 59 | * @param limiter rate limiter to intiialize 60 | * @param options configuration values for the token bucket rate limiter 61 | * @return AWS_OP_SUCCESS/AWS_OP_ERR 62 | */ 63 | AWS_MQTT_API int aws_rate_limiter_token_bucket_init( 64 | struct aws_rate_limiter_token_bucket *limiter, 65 | const struct aws_rate_limiter_token_bucket_options *options); 66 | 67 | /** 68 | * Resets a token-bucket-based rate limiter 69 | * 70 | * @param limiter rate limiter to reset 71 | */ 72 | AWS_MQTT_API void aws_rate_limiter_token_bucket_reset(struct aws_rate_limiter_token_bucket *limiter); 73 | 74 | /** 75 | * Queries if the token bucket has a number of tokens currently available 76 | * 77 | * @param limiter token bucket rate limiter to query, non-const because token count is lazily updated 78 | * @param token_count how many tokens to check for 79 | * @return true if that many tokens are available, false otherwise 80 | */ 81 | AWS_MQTT_API bool aws_rate_limiter_token_bucket_can_take_tokens( 82 | struct aws_rate_limiter_token_bucket *limiter, 83 | uint64_t token_count); 84 | 85 | /** 86 | * Takes a number of tokens from the token bucket rate limiter 87 | * 88 | * @param limiter token bucket rate limiter to take from 89 | * @param token_count how many tokens to take 90 | * @return AWS_OP_SUCCESS if there were that many tokens available, AWS_OP_ERR otherwise 91 | */ 92 | AWS_MQTT_API int aws_rate_limiter_token_bucket_take_tokens( 93 | struct aws_rate_limiter_token_bucket *limiter, 94 | uint64_t token_count); 95 | 96 | /** 97 | * Queries a token-bucket-based rate limiter for how long, in nanoseconds, until a specified amount of tokens will 98 | * be available. 99 | * 100 | * @param limiter token-bucket-based rate limiter to query 101 | * @param token_count how many tokens need to be avilable 102 | * @return how long the caller must wait, in nanoseconds, before that many tokens are available 103 | */ 104 | AWS_MQTT_API uint64_t aws_rate_limiter_token_bucket_compute_wait_for_tokens( 105 | struct aws_rate_limiter_token_bucket *limiter, 106 | uint64_t token_count); 107 | 108 | AWS_EXTERN_C_END 109 | 110 | #endif /* AWS_RATE_LIMITERS_H */ 111 | -------------------------------------------------------------------------------- /source/v5/mqtt5_listener.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct aws_mqtt5_listener { 14 | struct aws_allocator *allocator; 15 | 16 | struct aws_ref_count ref_count; 17 | 18 | struct aws_mqtt5_listener_config config; 19 | 20 | uint64_t callback_set_id; 21 | 22 | struct aws_task initialize_task; 23 | struct aws_task terminate_task; 24 | }; 25 | 26 | static void s_mqtt5_listener_destroy(struct aws_mqtt5_listener *listener) { 27 | 28 | aws_mqtt5_client_release(listener->config.client); 29 | 30 | aws_mqtt5_listener_termination_completion_fn *termination_callback = listener->config.termination_callback; 31 | void *temination_callback_user_data = listener->config.termination_callback_user_data; 32 | 33 | aws_mem_release(listener->allocator, listener); 34 | 35 | if (termination_callback != NULL) { 36 | (*termination_callback)(temination_callback_user_data); 37 | } 38 | } 39 | 40 | static void s_mqtt5_listener_initialize_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { 41 | (void)task; 42 | 43 | struct aws_mqtt5_listener *listener = arg; 44 | 45 | if (task_status == AWS_TASK_STATUS_RUN_READY) { 46 | listener->callback_set_id = aws_mqtt5_callback_set_manager_push_front( 47 | &listener->config.client->callback_manager, &listener->config.listener_callbacks); 48 | AWS_LOGF_INFO( 49 | AWS_LS_MQTT5_GENERAL, 50 | "id=%p: Mqtt5 Listener initialized, listener id=%p", 51 | (void *)listener->config.client, 52 | (void *)listener); 53 | aws_mqtt5_listener_release(listener); 54 | } else { 55 | s_mqtt5_listener_destroy(listener); 56 | } 57 | } 58 | 59 | static void s_mqtt5_listener_terminate_task_fn(struct aws_task *task, void *arg, enum aws_task_status task_status) { 60 | (void)task; 61 | 62 | struct aws_mqtt5_listener *listener = arg; 63 | 64 | if (task_status == AWS_TASK_STATUS_RUN_READY) { 65 | aws_mqtt5_callback_set_manager_remove(&listener->config.client->callback_manager, listener->callback_set_id); 66 | } 67 | 68 | AWS_LOGF_INFO( 69 | AWS_LS_MQTT5_GENERAL, 70 | "id=%p: Mqtt5 Listener terminated, listener id=%p", 71 | (void *)listener->config.client, 72 | (void *)listener); 73 | 74 | s_mqtt5_listener_destroy(listener); 75 | } 76 | 77 | static void s_aws_mqtt5_listener_on_zero_ref_count(void *context) { 78 | struct aws_mqtt5_listener *listener = context; 79 | 80 | aws_event_loop_schedule_task_now(listener->config.client->loop, &listener->terminate_task); 81 | } 82 | 83 | struct aws_mqtt5_listener *aws_mqtt5_listener_new( 84 | struct aws_allocator *allocator, 85 | struct aws_mqtt5_listener_config *config) { 86 | if (config->client == NULL) { 87 | aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); 88 | return NULL; 89 | } 90 | 91 | struct aws_mqtt5_listener *listener = aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_listener)); 92 | 93 | listener->allocator = allocator; 94 | listener->config = *config; 95 | 96 | aws_mqtt5_client_acquire(config->client); 97 | aws_ref_count_init(&listener->ref_count, listener, s_aws_mqtt5_listener_on_zero_ref_count); 98 | 99 | aws_task_init(&listener->initialize_task, s_mqtt5_listener_initialize_task_fn, listener, "Mqtt5ListenerInitialize"); 100 | aws_task_init(&listener->terminate_task, s_mqtt5_listener_terminate_task_fn, listener, "Mqtt5ListenerTerminate"); 101 | 102 | aws_mqtt5_listener_acquire(listener); 103 | aws_event_loop_schedule_task_now(config->client->loop, &listener->initialize_task); 104 | 105 | return listener; 106 | } 107 | 108 | struct aws_mqtt5_listener *aws_mqtt5_listener_acquire(struct aws_mqtt5_listener *listener) { 109 | if (listener != NULL) { 110 | aws_ref_count_acquire(&listener->ref_count); 111 | } 112 | 113 | return listener; 114 | } 115 | 116 | struct aws_mqtt5_listener *aws_mqtt5_listener_release(struct aws_mqtt5_listener *listener) { 117 | if (listener != NULL) { 118 | aws_ref_count_release(&listener->ref_count); 119 | } 120 | 121 | return NULL; 122 | } 123 | -------------------------------------------------------------------------------- /source/fixed_header.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | 8 | /** 9 | * Implements encoding & decoding of the remaining_length field across 1-4 bytes [MQTT-2.2.3]. 10 | * 11 | * Any number less than or equal to 127 (7 bit max) can be written into a single byte, where any number larger than 128 12 | * may be written into multiple bytes, using the most significant bit (128) as a continuation flag. 13 | */ 14 | static int s_encode_remaining_length(struct aws_byte_buf *buf, size_t remaining_length) { 15 | 16 | AWS_PRECONDITION(buf); 17 | AWS_PRECONDITION(remaining_length < UINT32_MAX); 18 | 19 | do { 20 | uint8_t encoded_byte = remaining_length % 128; 21 | remaining_length /= 128; 22 | if (remaining_length) { 23 | encoded_byte |= 128; 24 | } 25 | if (!aws_byte_buf_write_u8(buf, encoded_byte)) { 26 | return aws_raise_error(AWS_ERROR_SHORT_BUFFER); 27 | } 28 | } while (remaining_length); 29 | 30 | return AWS_OP_SUCCESS; 31 | } 32 | 33 | int aws_mqtt311_decode_remaining_length(struct aws_byte_cursor *cur, size_t *remaining_length_out) { 34 | 35 | AWS_PRECONDITION(cur); 36 | 37 | /* Read remaining_length */ 38 | size_t multiplier = 1; 39 | size_t remaining_length = 0; 40 | while (true) { 41 | uint8_t encoded_byte; 42 | if (!aws_byte_cursor_read_u8(cur, &encoded_byte)) { 43 | return aws_raise_error(AWS_ERROR_SHORT_BUFFER); 44 | } 45 | 46 | remaining_length += (encoded_byte & 127) * multiplier; 47 | multiplier *= 128; 48 | 49 | if (!(encoded_byte & 128)) { 50 | break; 51 | } 52 | if (multiplier > 128 * 128 * 128) { 53 | /* If high order bit is set on last byte, value is malformed */ 54 | return aws_raise_error(AWS_ERROR_MQTT_INVALID_REMAINING_LENGTH); 55 | } 56 | } 57 | 58 | *remaining_length_out = remaining_length; 59 | return AWS_OP_SUCCESS; 60 | } 61 | 62 | enum aws_mqtt_packet_type aws_mqtt_get_packet_type(const uint8_t *buffer) { 63 | return *buffer >> 4; 64 | } 65 | 66 | bool aws_mqtt_packet_has_flags(const struct aws_mqtt_fixed_header *header) { 67 | 68 | /* Parse attributes based on packet type */ 69 | switch (header->packet_type) { 70 | case AWS_MQTT_PACKET_SUBSCRIBE: 71 | case AWS_MQTT_PACKET_UNSUBSCRIBE: 72 | case AWS_MQTT_PACKET_PUBLISH: 73 | case AWS_MQTT_PACKET_PUBREL: 74 | return true; 75 | break; 76 | 77 | case AWS_MQTT_PACKET_CONNECT: 78 | case AWS_MQTT_PACKET_CONNACK: 79 | case AWS_MQTT_PACKET_PUBACK: 80 | case AWS_MQTT_PACKET_PUBREC: 81 | case AWS_MQTT_PACKET_PUBCOMP: 82 | case AWS_MQTT_PACKET_SUBACK: 83 | case AWS_MQTT_PACKET_UNSUBACK: 84 | case AWS_MQTT_PACKET_PINGREQ: 85 | case AWS_MQTT_PACKET_PINGRESP: 86 | case AWS_MQTT_PACKET_DISCONNECT: 87 | return false; 88 | 89 | default: 90 | return false; 91 | } 92 | } 93 | 94 | int aws_mqtt_fixed_header_encode(struct aws_byte_buf *buf, const struct aws_mqtt_fixed_header *header) { 95 | 96 | AWS_PRECONDITION(buf); 97 | AWS_PRECONDITION(header); 98 | 99 | /* Check that flags are 0 if they must not be present */ 100 | if (!aws_mqtt_packet_has_flags(header) && header->flags != 0) { 101 | return aws_raise_error(AWS_ERROR_MQTT_INVALID_RESERVED_BITS); 102 | } 103 | 104 | /* Write packet type and flags */ 105 | uint8_t byte_1 = (uint8_t)((header->packet_type << 4) | (header->flags & 0xF)); 106 | if (!aws_byte_buf_write_u8(buf, byte_1)) { 107 | return aws_raise_error(AWS_ERROR_SHORT_BUFFER); 108 | } 109 | 110 | /* Write remaining length */ 111 | if (s_encode_remaining_length(buf, header->remaining_length)) { 112 | return AWS_OP_ERR; 113 | } 114 | 115 | return AWS_OP_SUCCESS; 116 | } 117 | 118 | int aws_mqtt_fixed_header_decode(struct aws_byte_cursor *cur, struct aws_mqtt_fixed_header *header) { 119 | 120 | AWS_PRECONDITION(cur); 121 | AWS_PRECONDITION(header); 122 | 123 | /* Read packet type and flags */ 124 | uint8_t byte_1 = 0; 125 | if (!aws_byte_cursor_read_u8(cur, &byte_1)) { 126 | return aws_raise_error(AWS_ERROR_SHORT_BUFFER); 127 | } 128 | header->packet_type = aws_mqtt_get_packet_type(&byte_1); 129 | header->flags = byte_1 & 0xF; 130 | 131 | /* Read remaining length */ 132 | if (aws_mqtt311_decode_remaining_length(cur, &header->remaining_length)) { 133 | return AWS_OP_ERR; 134 | } 135 | if (cur->len < header->remaining_length) { 136 | return aws_raise_error(AWS_ERROR_SHORT_BUFFER); 137 | } 138 | 139 | /* Check that flags are 0 if they must not be present */ 140 | if (!aws_mqtt_packet_has_flags(header) && header->flags != 0) { 141 | return aws_raise_error(AWS_ERROR_MQTT_INVALID_RESERVED_BITS); 142 | } 143 | 144 | return AWS_OP_SUCCESS; 145 | } 146 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/mqtt311_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_MQTT311_DECODER_H 2 | #define AWS_MQTT_PRIVATE_MQTT311_DECODER_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | 13 | /* 14 | * Per-packet-type callback signature. message_cursor contains the entire packet's data. 15 | */ 16 | typedef int(packet_handler_fn)(struct aws_byte_cursor message_cursor, void *user_data); 17 | 18 | /* 19 | * Wrapper for a set of packet handlers for each possible MQTT packet type. Some values are invalid in 311 (15), and 20 | * some values are only valid from the perspective of the server or client. 21 | */ 22 | struct aws_mqtt_client_connection_packet_handlers { 23 | packet_handler_fn *handlers_by_packet_type[16]; 24 | }; 25 | 26 | /* 27 | * Internal state of the 311 decoder/framing logic. 28 | * 29 | * When a packet is fragmented across multiple io buffers, state moves circularly: 30 | * first byte -> remaining length -> body -> first byte etc... 31 | * 32 | * When a packet is completely contained inside a single io buffer, the entire packet is processed within 33 | * the READ_FIRST_BYTE state. 34 | */ 35 | enum aws_mqtt_311_decoder_state_type { 36 | 37 | /* 38 | * The decoder is expecting the first byte of the fixed header of an MQTT control packet 39 | */ 40 | AWS_MDST_READ_FIRST_BYTE, 41 | 42 | /* 43 | * The decoder is expecting the vli-encoded total remaining length field of the fixed header on an MQTT control 44 | * packet. 45 | */ 46 | AWS_MDST_READ_REMAINING_LENGTH, 47 | 48 | /* 49 | * The decoder is expecting the "rest" of the MQTT packet's data based on the remaining length value that has 50 | * already been read. 51 | */ 52 | AWS_MDST_READ_BODY, 53 | 54 | /* 55 | * Terminal state for when a protocol error has been encountered by the decoder. The only way to leave this 56 | * state is to reset the decoder via the aws_mqtt311_decoder_reset_for_new_connection() API. 57 | */ 58 | AWS_MDST_PROTOCOL_ERROR, 59 | }; 60 | 61 | /* 62 | * Configuration options for the decoder. When used by the actual implementation, handler_user_data is the 63 | * connection object and the packet handlers are channel functions that hook into reactionary behavior and user 64 | * callbacks. 65 | */ 66 | struct aws_mqtt311_decoder_options { 67 | const struct aws_mqtt_client_connection_packet_handlers *packet_handlers; 68 | void *handler_user_data; 69 | }; 70 | 71 | /* 72 | * Simple MQTT311 decoder structure. Actual decoding is deferred to per-packet functions that expect the whole 73 | * packet in a buffer. The primary function of this sub-system is correctly framing a stream of bytes into the 74 | * constituent packets. 75 | */ 76 | struct aws_mqtt311_decoder { 77 | struct aws_mqtt311_decoder_options config; 78 | 79 | enum aws_mqtt_311_decoder_state_type state; 80 | 81 | /* 82 | * If zero, not valid. If non-zero, represents the number of bytes that need to be read to finish the packet. 83 | * This includes the total encoding size of the fixed header. 84 | */ 85 | size_t total_packet_length; 86 | 87 | /* scratch buffer to hold individual packets when they fragment across incoming data frame boundaries */ 88 | struct aws_byte_buf packet_buffer; 89 | }; 90 | 91 | AWS_EXTERN_C_BEGIN 92 | 93 | /** 94 | * Initialize function for the MQTT311 decoder 95 | * 96 | * @param decoder decoder to initialize 97 | * @param allocator memory allocator to use 98 | * @param options additional decoder configuration options 99 | */ 100 | AWS_MQTT_API void aws_mqtt311_decoder_init( 101 | struct aws_mqtt311_decoder *decoder, 102 | struct aws_allocator *allocator, 103 | const struct aws_mqtt311_decoder_options *options); 104 | 105 | /** 106 | * Clean up function for an MQTT311 decoder 107 | * 108 | * @param decoder decoder to release resources for 109 | */ 110 | AWS_MQTT_API void aws_mqtt311_decoder_clean_up(struct aws_mqtt311_decoder *decoder); 111 | 112 | /** 113 | * Callback function to decode the incoming data stream of an MQTT311 connection. Handles packet framing and 114 | * correct decoder/handler function dispatch. 115 | * 116 | * @param decoder decoder to decode with 117 | * @param data raw plaintext bytes of a connection operating on the MQTT311 protocol 118 | * @return success/failure, failure represents a protocol error and implies the connection must be shut down 119 | */ 120 | AWS_MQTT_API int aws_mqtt311_decoder_on_bytes_received( 121 | struct aws_mqtt311_decoder *decoder, 122 | struct aws_byte_cursor data); 123 | 124 | /** 125 | * Resets a decoder's state to its initial values. If using a decoder across multiple network connections (within 126 | * the same client), you must invoke this when setting up a new connection, before any MQTT protocol bytes are 127 | * processed. Breaks the decoder out of any previous protocol error terminal state. 128 | * 129 | * @param decoder decoder to reset 130 | */ 131 | AWS_MQTT_API void aws_mqtt311_decoder_reset_for_new_connection(struct aws_mqtt311_decoder *decoder); 132 | 133 | AWS_EXTERN_C_END 134 | 135 | #endif /* AWS_MQTT_PRIVATE_MQTT311_DECODER_H */ 136 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/request-response/request_response_subscription_set.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_SET_H 2 | #define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_SET_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | struct aws_mqtt_rr_incoming_publish_event; 15 | 16 | /* 17 | * Handles subscriptions for request-response client. 18 | * Lifetime of this struct is bound to request-response client. 19 | */ 20 | struct aws_request_response_subscriptions { 21 | struct aws_allocator *allocator; 22 | 23 | /* 24 | * Map from cursor (topic filter) -> list of streaming operations using that filter 25 | * 26 | * We don't garbage collect this table over the course of normal client operation. We only clean it up 27 | * when the client is shutting down. 28 | */ 29 | struct aws_hash_table streaming_operation_subscription_lists; 30 | 31 | /* 32 | * Map from cursor (topic filter with wildcards) -> list of streaming operations using that filter 33 | * 34 | * We don't garbage collect this table over the course of normal client operation. We only clean it up 35 | * when the client is shutting down. 36 | */ 37 | struct aws_hash_table streaming_operation_wildcards_subscription_lists; 38 | 39 | /* 40 | * Map from cursor (topic) -> request response path (topic, correlation token json path) 41 | */ 42 | struct aws_hash_table request_response_paths; 43 | }; 44 | 45 | /* 46 | * This is the (key and) value in stream subscriptions tables. 47 | */ 48 | struct aws_rr_operation_list_topic_filter_entry { 49 | struct aws_allocator *allocator; 50 | 51 | struct aws_byte_cursor topic_filter_cursor; 52 | struct aws_byte_buf topic_filter; 53 | 54 | struct aws_linked_list operations; 55 | }; 56 | 57 | /* 58 | * Value in request subscriptions table. 59 | */ 60 | struct aws_rr_response_path_entry { 61 | struct aws_allocator *allocator; 62 | 63 | size_t ref_count; 64 | 65 | struct aws_byte_cursor topic_cursor; 66 | struct aws_byte_buf topic; 67 | 68 | struct aws_byte_buf correlation_token_json_path; 69 | }; 70 | 71 | /* 72 | * Callback type for matched stream subscriptions. 73 | */ 74 | typedef void(aws_mqtt_stream_operation_subscription_match_fn)( 75 | const struct aws_linked_list *operations, 76 | const struct aws_byte_cursor *topic_filter, 77 | const struct aws_mqtt_rr_incoming_publish_event *publish_event, 78 | void *user_data); 79 | 80 | /* 81 | * Callback type for matched request subscriptions. 82 | */ 83 | typedef void(aws_mqtt_request_operation_subscription_match_fn)( 84 | struct aws_rr_response_path_entry *entry, 85 | const struct aws_mqtt_rr_incoming_publish_event *publish_event, 86 | void *user_data); 87 | 88 | AWS_EXTERN_C_BEGIN 89 | 90 | /* 91 | * Initialize internal state of a provided request-response subscriptions structure. 92 | */ 93 | AWS_MQTT_API int aws_mqtt_request_response_client_subscriptions_init( 94 | struct aws_request_response_subscriptions *subscriptions, 95 | struct aws_allocator *allocator); 96 | 97 | /* 98 | * Clean up internals of a provided request-response subscriptions structure. 99 | */ 100 | AWS_MQTT_API void aws_mqtt_request_response_client_subscriptions_clean_up( 101 | struct aws_request_response_subscriptions *subscriptions); 102 | 103 | /* 104 | * Add a subscription for stream operations. 105 | * If subscription with the same topic filter is already added, previously created 106 | * aws_rr_operation_list_topic_filter_entry instance is returned. 107 | */ 108 | AWS_MQTT_API struct aws_rr_operation_list_topic_filter_entry * 109 | aws_mqtt_request_response_client_subscriptions_add_stream_subscription( 110 | struct aws_request_response_subscriptions *subscriptions, 111 | const struct aws_byte_cursor *topic_filter); 112 | 113 | /* 114 | * Add a subscription for request operation. 115 | */ 116 | AWS_MQTT_API int aws_mqtt_request_response_client_subscriptions_add_request_subscription( 117 | struct aws_request_response_subscriptions *subscriptions, 118 | const struct aws_byte_cursor *topic_filter, 119 | const struct aws_byte_cursor *correlation_token_json_path); 120 | 121 | /* 122 | * Remove a subscription for a given request operation. 123 | */ 124 | AWS_MQTT_API int aws_mqtt_request_response_client_subscriptions_remove_request_subscription( 125 | struct aws_request_response_subscriptions *subscriptions, 126 | const struct aws_byte_cursor *topic_filter); 127 | 128 | /* 129 | * Call specified callbacks for all stream and request operations with subscriptions matching a provided publish event. 130 | */ 131 | AWS_MQTT_API void aws_mqtt_request_response_client_subscriptions_handle_incoming_publish( 132 | const struct aws_request_response_subscriptions *subscriptions, 133 | const struct aws_mqtt_rr_incoming_publish_event *publish_event, 134 | aws_mqtt_stream_operation_subscription_match_fn *on_stream_operation_subscription_match, 135 | aws_mqtt_request_operation_subscription_match_fn *on_request_operation_subscription_match, 136 | void *user_data); 137 | 138 | AWS_EXTERN_C_END 139 | 140 | #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_REQUEST_SET_H */ 141 | -------------------------------------------------------------------------------- /include/aws/mqtt/mqtt.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_MQTT_H 2 | #define AWS_MQTT_MQTT_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | AWS_PUSH_SANE_WARNING_LEVEL 15 | 16 | #define AWS_C_MQTT_PACKAGE_ID 5 17 | 18 | /* Quality of Service associated with a publish action or subscription [MQTT-4.3]. */ 19 | enum aws_mqtt_qos { 20 | AWS_MQTT_QOS_AT_MOST_ONCE = 0x0, 21 | AWS_MQTT_QOS_AT_LEAST_ONCE = 0x1, 22 | AWS_MQTT_QOS_EXACTLY_ONCE = 0x2, 23 | /* reserved = 3 */ 24 | AWS_MQTT_QOS_FAILURE = 0x80, /* Only used in SUBACK packets */ 25 | }; 26 | 27 | /* Result of a connect request [MQTT-3.2.2.3]. */ 28 | enum aws_mqtt_connect_return_code { 29 | AWS_MQTT_CONNECT_ACCEPTED, 30 | AWS_MQTT_CONNECT_UNACCEPTABLE_PROTOCOL_VERSION, 31 | AWS_MQTT_CONNECT_IDENTIFIER_REJECTED, 32 | AWS_MQTT_CONNECT_SERVER_UNAVAILABLE, 33 | AWS_MQTT_CONNECT_BAD_USERNAME_OR_PASSWORD, 34 | AWS_MQTT_CONNECT_NOT_AUTHORIZED, 35 | /* reserved = 6 - 255 */ 36 | }; 37 | 38 | enum aws_mqtt_error { 39 | AWS_ERROR_MQTT_INVALID_RESERVED_BITS = AWS_ERROR_ENUM_BEGIN_RANGE(AWS_C_MQTT_PACKAGE_ID), 40 | AWS_ERROR_MQTT_BUFFER_TOO_BIG, 41 | AWS_ERROR_MQTT_INVALID_REMAINING_LENGTH, 42 | AWS_ERROR_MQTT_UNSUPPORTED_PROTOCOL_NAME, 43 | AWS_ERROR_MQTT_UNSUPPORTED_PROTOCOL_LEVEL, 44 | AWS_ERROR_MQTT_INVALID_CREDENTIALS, 45 | AWS_ERROR_MQTT_INVALID_QOS, 46 | AWS_ERROR_MQTT_INVALID_PACKET_TYPE, 47 | AWS_ERROR_MQTT_INVALID_TOPIC, 48 | AWS_ERROR_MQTT_TIMEOUT, 49 | AWS_ERROR_MQTT_PROTOCOL_ERROR, 50 | AWS_ERROR_MQTT_NOT_CONNECTED, 51 | AWS_ERROR_MQTT_ALREADY_CONNECTED, 52 | AWS_ERROR_MQTT_BUILT_WITHOUT_WEBSOCKETS, 53 | AWS_ERROR_MQTT_UNEXPECTED_HANGUP, 54 | AWS_ERROR_MQTT_CONNECTION_SHUTDOWN, 55 | AWS_ERROR_MQTT_CONNECTION_DESTROYED, 56 | AWS_ERROR_MQTT_CONNECTION_DISCONNECTING, 57 | AWS_ERROR_MQTT_CANCELLED_FOR_CLEAN_SESSION, 58 | AWS_ERROR_MQTT_QUEUE_FULL, 59 | AWS_ERROR_MQTT5_CLIENT_OPTIONS_VALIDATION, 60 | AWS_ERROR_MQTT5_CONNECT_OPTIONS_VALIDATION, 61 | AWS_ERROR_MQTT5_DISCONNECT_OPTIONS_VALIDATION, 62 | AWS_ERROR_MQTT5_PUBLISH_OPTIONS_VALIDATION, 63 | AWS_ERROR_MQTT5_SUBSCRIBE_OPTIONS_VALIDATION, 64 | AWS_ERROR_MQTT5_UNSUBSCRIBE_OPTIONS_VALIDATION, 65 | AWS_ERROR_MQTT5_USER_PROPERTY_VALIDATION, 66 | AWS_ERROR_MQTT5_PACKET_VALIDATION, 67 | AWS_ERROR_MQTT5_ENCODE_FAILURE, 68 | AWS_ERROR_MQTT5_DECODE_PROTOCOL_ERROR, 69 | AWS_ERROR_MQTT5_CONNACK_CONNECTION_REFUSED, 70 | AWS_ERROR_MQTT5_CONNACK_TIMEOUT, 71 | AWS_ERROR_MQTT5_PING_RESPONSE_TIMEOUT, 72 | AWS_ERROR_MQTT5_USER_REQUESTED_STOP, 73 | AWS_ERROR_MQTT5_DISCONNECT_RECEIVED, 74 | AWS_ERROR_MQTT5_CLIENT_TERMINATED, 75 | AWS_ERROR_MQTT5_OPERATION_FAILED_DUE_TO_OFFLINE_QUEUE_POLICY, 76 | AWS_ERROR_MQTT5_ENCODE_SIZE_UNSUPPORTED_PACKET_TYPE, 77 | AWS_ERROR_MQTT5_OPERATION_PROCESSING_FAILURE, 78 | AWS_ERROR_MQTT5_INVALID_INBOUND_TOPIC_ALIAS, 79 | AWS_ERROR_MQTT5_INVALID_OUTBOUND_TOPIC_ALIAS, 80 | AWS_ERROR_MQTT5_INVALID_UTF8_STRING, 81 | AWS_ERROR_MQTT_CONNECTION_RESET_FOR_ADAPTER_CONNECT, 82 | AWS_ERROR_MQTT_CONNECTION_RESUBSCRIBE_NO_TOPICS, 83 | AWS_ERROR_MQTT_CONNECTION_SUBSCRIBE_FAILURE, 84 | AWS_ERROR_MQTT_ACK_REASON_CODE_FAILURE, 85 | AWS_ERROR_MQTT_PROTOCOL_ADAPTER_FAILING_REASON_CODE, 86 | AWS_ERROR_MQTT_REQUEST_RESPONSE_CLIENT_SHUT_DOWN, 87 | AWS_ERROR_MQTT_REQUEST_RESPONSE_TIMEOUT, 88 | AWS_ERROR_MQTT_REQUEST_RESPONSE_NO_SUBSCRIPTION_CAPACITY, 89 | AWS_ERROR_MQTT_REQUEST_RESPONSE_SUBSCRIBE_FAILURE, 90 | AWS_ERROR_MQTT_REQUEST_RESPONSE_INTERNAL_ERROR, 91 | AWS_ERROR_MQTT_REQUEST_RESPONSE_PUBLISH_FAILURE, 92 | AWS_ERROR_MQTT_REUQEST_RESPONSE_STREAM_ALREADY_ACTIVATED, 93 | AWS_ERROR_MQTT_REQUEST_RESPONSE_MODELED_SERVICE_ERROR, 94 | AWS_ERROR_MQTT_REQUEST_RESPONSE_PAYLOAD_PARSE_ERROR, 95 | AWS_ERROR_MQTT_REQUEST_RESPONSE_INVALID_RESPONSE_PATH, 96 | 97 | AWS_ERROR_END_MQTT_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_MQTT_PACKAGE_ID), 98 | }; 99 | 100 | enum aws_mqtt_log_subject { 101 | AWS_LS_MQTT_GENERAL = AWS_LOG_SUBJECT_BEGIN_RANGE(AWS_C_MQTT_PACKAGE_ID), 102 | AWS_LS_MQTT_CLIENT, 103 | AWS_LS_MQTT_TOPIC_TREE, 104 | AWS_LS_MQTT5_GENERAL, 105 | AWS_LS_MQTT5_CLIENT, 106 | AWS_LS_MQTT5_CANARY, 107 | AWS_LS_MQTT5_TO_MQTT3_ADAPTER, 108 | AWS_LS_MQTT_REQUEST_RESPONSE, 109 | }; 110 | 111 | /** Function called on cleanup of a userdata. */ 112 | typedef void(aws_mqtt_userdata_cleanup_fn)(void *userdata); 113 | 114 | AWS_EXTERN_C_BEGIN 115 | 116 | AWS_MQTT_API 117 | bool aws_mqtt_is_valid_topic(const struct aws_byte_cursor *topic); 118 | 119 | AWS_MQTT_API 120 | bool aws_mqtt_is_valid_topic_filter(const struct aws_byte_cursor *topic_filter); 121 | 122 | /** 123 | * Validate utf-8 string under mqtt specs 124 | * 125 | * @param text 126 | * @return AWS_OP_SUCCESS if the text is validate, otherwise AWS_OP_ERR 127 | */ 128 | AWS_MQTT_API int aws_mqtt_validate_utf8_text(struct aws_byte_cursor text); 129 | 130 | /** 131 | * Initializes internal datastructures used by aws-c-mqtt. 132 | * Must be called before using any functionality in aws-c-mqtt. 133 | */ 134 | AWS_MQTT_API 135 | void aws_mqtt_library_init(struct aws_allocator *allocator); 136 | 137 | /** 138 | * Shuts down the internal datastructures used by aws-c-mqtt. 139 | */ 140 | AWS_MQTT_API 141 | void aws_mqtt_library_clean_up(void); 142 | 143 | AWS_MQTT_API 144 | void aws_mqtt_fatal_assert_library_initialized(void); 145 | 146 | AWS_EXTERN_C_END 147 | AWS_POP_SANE_WARNING_LEVEL 148 | 149 | #endif /* AWS_MQTT_MQTT_H */ 150 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/client_impl_shared.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H 2 | #define AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | 11 | struct aws_mqtt_client_connection; 12 | 13 | /* 14 | * Internal enum that indicates what type of struct the underlying impl pointer actually is. We use this 15 | * to safely interact with private APIs on the implementation or extract the adapted 5 client directly, as 16 | * necessary. 17 | */ 18 | enum aws_mqtt311_impl_type { 19 | 20 | /* 311 connection impl can be cast to `struct aws_mqtt_client_connection_311_impl` */ 21 | AWS_MQTT311_IT_311_CONNECTION, 22 | 23 | /* 311 connection impl can be cast to `struct aws_mqtt_client_connection_5_impl`*/ 24 | AWS_MQTT311_IT_5_ADAPTER, 25 | }; 26 | 27 | struct aws_mqtt_client_connection_vtable { 28 | 29 | struct aws_mqtt_client_connection *(*acquire_fn)(void *impl); 30 | 31 | void (*release_fn)(void *impl); 32 | 33 | int (*set_will_fn)( 34 | void *impl, 35 | const struct aws_byte_cursor *topic, 36 | enum aws_mqtt_qos qos, 37 | bool retain, 38 | const struct aws_byte_cursor *payload); 39 | 40 | int (*set_login_fn)(void *impl, const struct aws_byte_cursor *username, const struct aws_byte_cursor *password); 41 | 42 | int (*use_websockets_fn)( 43 | void *impl, 44 | aws_mqtt_transform_websocket_handshake_fn *transformer, 45 | void *transformer_ud, 46 | aws_mqtt_validate_websocket_handshake_fn *validator, 47 | void *validator_ud); 48 | 49 | int (*set_http_proxy_options_fn)(void *impl, struct aws_http_proxy_options *proxy_options); 50 | 51 | int (*set_host_resolution_options_fn)(void *impl, const struct aws_host_resolution_config *host_resolution_config); 52 | 53 | int (*set_reconnect_timeout_fn)(void *impl, uint64_t min_timeout, uint64_t max_timeout); 54 | 55 | int (*set_connection_interruption_handlers_fn)( 56 | void *impl, 57 | aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, 58 | void *on_interrupted_ud, 59 | aws_mqtt_client_on_connection_resumed_fn *on_resumed, 60 | void *on_resumed_ud); 61 | 62 | int (*set_connection_result_handlers)( 63 | void *impl, 64 | aws_mqtt_client_on_connection_success_fn *on_connection_success, 65 | void *on_connection_success_ud, 66 | aws_mqtt_client_on_connection_failure_fn *on_connection_failure, 67 | void *on_connection_failure_ud); 68 | 69 | int (*set_connection_closed_handler_fn)( 70 | void *impl, 71 | aws_mqtt_client_on_connection_closed_fn *on_closed, 72 | void *on_closed_ud); 73 | 74 | int (*set_on_any_publish_handler_fn)( 75 | void *impl, 76 | aws_mqtt_client_publish_received_fn *on_any_publish, 77 | void *on_any_publish_ud); 78 | 79 | int (*set_connection_termination_handler_fn)( 80 | void *impl, 81 | aws_mqtt_client_on_connection_termination_fn *on_termination, 82 | void *on_termination_ud); 83 | 84 | int (*connect_fn)(void *impl, const struct aws_mqtt_connection_options *connection_options); 85 | 86 | int (*reconnect_fn)(void *impl, aws_mqtt_client_on_connection_complete_fn *on_connection_complete, void *userdata); 87 | 88 | int (*disconnect_fn)(void *impl, aws_mqtt_client_on_disconnect_fn *on_disconnect, void *userdata); 89 | 90 | uint16_t (*subscribe_multiple_fn)( 91 | void *impl, 92 | const struct aws_array_list *topic_filters, 93 | aws_mqtt_suback_multi_fn *on_suback, 94 | void *on_suback_ud); 95 | 96 | uint16_t (*subscribe_fn)( 97 | void *impl, 98 | const struct aws_byte_cursor *topic_filter, 99 | enum aws_mqtt_qos qos, 100 | aws_mqtt_client_publish_received_fn *on_publish, 101 | void *on_publish_ud, 102 | aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, 103 | aws_mqtt_suback_fn *on_suback, 104 | void *on_suback_ud); 105 | 106 | uint16_t (*resubscribe_existing_topics_fn)(void *impl, aws_mqtt_suback_multi_fn *on_suback, void *on_suback_ud); 107 | 108 | uint16_t (*unsubscribe_fn)( 109 | void *impl, 110 | const struct aws_byte_cursor *topic_filter, 111 | aws_mqtt_op_complete_fn *on_unsuback, 112 | void *on_unsuback_ud); 113 | 114 | uint16_t (*publish_fn)( 115 | void *impl, 116 | const struct aws_byte_cursor *topic, 117 | enum aws_mqtt_qos qos, 118 | bool retain, 119 | const struct aws_byte_cursor *payload, 120 | aws_mqtt_op_complete_fn *on_complete, 121 | void *userdata); 122 | 123 | int (*get_stats_fn)(void *impl, struct aws_mqtt_connection_operation_statistics *stats); 124 | 125 | enum aws_mqtt311_impl_type (*get_impl_type)(const void *impl); 126 | 127 | struct aws_event_loop *(*get_event_loop)(const void *impl); 128 | }; 129 | 130 | struct aws_mqtt_client_connection { 131 | struct aws_mqtt_client_connection_vtable *vtable; 132 | void *impl; 133 | }; 134 | 135 | AWS_MQTT_API enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( 136 | const struct aws_mqtt_client_connection *connection); 137 | 138 | AWS_MQTT_API uint64_t aws_mqtt_hash_uint16_t(const void *item); 139 | 140 | AWS_MQTT_API bool aws_mqtt_compare_uint16_t_eq(const void *a, const void *b); 141 | 142 | AWS_MQTT_API bool aws_mqtt_byte_cursor_hash_equality(const void *a, const void *b); 143 | 144 | AWS_MQTT_API struct aws_event_loop *aws_mqtt_client_connection_get_event_loop( 145 | const struct aws_mqtt_client_connection *connection); 146 | 147 | #endif /* AWS_MQTT_PRIVATE_CLIENT_IMPL_SHARED_H */ 148 | -------------------------------------------------------------------------------- /tests/v3/mqtt311_testing_utils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_C_MQTT_MQTT311_TESTING_UTILS_H 7 | #define AWS_C_MQTT_MQTT311_TESTING_UTILS_H 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #define TEST_LOG_SUBJECT 60000 20 | #define ONE_SEC 1000000000 21 | // The value is extract from aws-c-mqtt/source/client.c 22 | #define AWS_RESET_RECONNECT_BACKOFF_DELAY_SECONDS 10 23 | #define RECONNECT_BACKOFF_DELAY_ERROR_MARGIN_NANO_SECONDS 500000000 24 | #define DEFAULT_MIN_RECONNECT_DELAY_SECONDS 1 25 | 26 | #define DEFAULT_TEST_PING_TIMEOUT_MS 1000 27 | #define DEFAULT_TEST_KEEP_ALIVE_S 2 28 | 29 | struct received_publish_packet { 30 | struct aws_byte_buf topic; 31 | struct aws_byte_buf payload; 32 | bool dup; 33 | enum aws_mqtt_qos qos; 34 | bool retain; 35 | }; 36 | 37 | struct mqtt_connection_state_test { 38 | struct aws_allocator *allocator; 39 | struct aws_channel *server_channel; 40 | struct aws_channel_handler *mock_server; 41 | struct aws_client_bootstrap *client_bootstrap; 42 | struct aws_server_bootstrap *server_bootstrap; 43 | struct aws_event_loop_group *el_group; 44 | struct aws_host_resolver *host_resolver; 45 | struct aws_socket_endpoint endpoint; 46 | struct aws_socket *listener; 47 | struct aws_mqtt_client *mqtt_client; 48 | struct aws_mqtt_client_connection *mqtt_connection; 49 | struct aws_socket_options socket_options; 50 | 51 | bool session_present; 52 | bool connection_completed; 53 | bool connection_success; 54 | bool connection_failure; 55 | bool client_disconnect_completed; 56 | bool server_disconnect_completed; 57 | bool connection_interrupted; 58 | bool connection_resumed; 59 | bool subscribe_completed; 60 | bool listener_destroyed; 61 | bool connection_terminated; 62 | int interruption_error; 63 | int subscribe_complete_error; 64 | int op_complete_error; 65 | enum aws_mqtt_connect_return_code mqtt_return_code; 66 | int error; 67 | struct aws_condition_variable cvar; 68 | struct aws_mutex lock; 69 | /* any published messages from mock server, that you may not subscribe to. (Which should not happen in real life) */ 70 | struct aws_array_list any_published_messages; /* list of struct received_publish_packet */ 71 | size_t any_publishes_received; 72 | size_t expected_any_publishes; 73 | /* the published messages from mock server, that you did subscribe to. */ 74 | struct aws_array_list published_messages; /* list of struct received_publish_packet */ 75 | size_t publishes_received; 76 | size_t expected_publishes; 77 | /* The returned QoS from mock server */ 78 | struct aws_array_list qos_returned; /* list of uint_8 */ 79 | size_t ops_completed; 80 | size_t expected_ops_completed; 81 | size_t connection_close_calls; /* All of the times on_connection_closed has been called */ 82 | 83 | size_t connection_termination_calls; /* How many times on_connection_termination has been called, should be 1 */ 84 | }; 85 | 86 | AWS_EXTERN_C_BEGIN 87 | 88 | int aws_test311_setup_mqtt_server_fn(struct aws_allocator *allocator, void *ctx); 89 | 90 | int aws_test311_clean_up_mqtt_server_fn(struct aws_allocator *allocator, int setup_result, void *ctx); 91 | 92 | void aws_test311_wait_for_interrupt_to_complete(struct mqtt_connection_state_test *state_test_data); 93 | 94 | void aws_test311_wait_for_reconnect_to_complete(struct mqtt_connection_state_test *state_test_data); 95 | 96 | void aws_test311_wait_for_connection_to_succeed(struct mqtt_connection_state_test *state_test_data); 97 | 98 | void aws_test311_wait_for_connection_to_fail(struct mqtt_connection_state_test *state_test_data); 99 | 100 | void aws_test311_on_connection_complete_fn( 101 | struct aws_mqtt_client_connection *connection, 102 | int error_code, 103 | enum aws_mqtt_connect_return_code return_code, 104 | bool session_present, 105 | void *userdata); 106 | 107 | void aws_test311_wait_for_connection_to_complete(struct mqtt_connection_state_test *state_test_data); 108 | 109 | void aws_test311_on_disconnect_fn(struct aws_mqtt_client_connection *connection, void *userdata); 110 | 111 | void aws_test311_wait_for_disconnect_to_complete(struct mqtt_connection_state_test *state_test_data); 112 | 113 | void aws_test311_wait_for_any_publish(struct mqtt_connection_state_test *state_test_data); 114 | 115 | void aws_test311_on_publish_received( 116 | struct aws_mqtt_client_connection *connection, 117 | const struct aws_byte_cursor *topic, 118 | const struct aws_byte_cursor *payload, 119 | bool dup, 120 | enum aws_mqtt_qos qos, 121 | bool retain, 122 | void *userdata); 123 | 124 | void aws_test311_wait_for_publish(struct mqtt_connection_state_test *state_test_data); 125 | 126 | void aws_test311_on_suback( 127 | struct aws_mqtt_client_connection *connection, 128 | uint16_t packet_id, 129 | const struct aws_byte_cursor *topic, 130 | enum aws_mqtt_qos qos, 131 | int error_code, 132 | void *userdata); 133 | 134 | void aws_test311_wait_for_subscribe_to_complete(struct mqtt_connection_state_test *state_test_data); 135 | 136 | void aws_test311_on_multi_suback( 137 | struct aws_mqtt_client_connection *connection, 138 | uint16_t packet_id, 139 | const struct aws_array_list *topic_subacks, 140 | int error_code, 141 | void *userdata); 142 | 143 | void aws_test311_on_op_complete( 144 | struct aws_mqtt_client_connection *connection, 145 | uint16_t packet_id, 146 | int error_code, 147 | void *userdata); 148 | 149 | void aws_test311_wait_for_ops_completed(struct mqtt_connection_state_test *state_test_data); 150 | 151 | void aws_test311_on_connection_termination_fn(void *userdata); 152 | 153 | AWS_EXTERN_C_END 154 | 155 | #endif // AWS_C_MQTT_MQTT311_TESTING_UTILS_H 156 | -------------------------------------------------------------------------------- /tests/shared_utils_tests.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | struct utf8_example { 12 | const char *name; 13 | struct aws_byte_cursor text; 14 | }; 15 | 16 | static struct utf8_example s_valid_mqtt_utf8_examples[] = { 17 | { 18 | .name = "1 letter", 19 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("a"), 20 | }, 21 | { 22 | .name = "Several ascii letters", 23 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ascii word"), 24 | }, 25 | { 26 | .name = "empty string", 27 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(""), 28 | }, 29 | { 30 | .name = "2 byte codepoint", 31 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xC2\xA3"), 32 | }, 33 | { 34 | .name = "3 byte codepoint", 35 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xE2\x82\xAC"), 36 | }, 37 | { 38 | .name = "4 byte codepoint", 39 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xF0\x90\x8D\x88"), 40 | }, 41 | { 42 | .name = "A variety of different length codepoints", 43 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL( 44 | "\xF0\x90\x8D\x88\xE2\x82\xAC\xC2\xA3\x24\xC2\xA3\xE2\x82\xAC\xF0\x90\x8D\x88"), 45 | }, 46 | { 47 | .name = "UTF8 BOM", 48 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xBB\xBF"), 49 | }, 50 | { 51 | .name = "UTF8 BOM plus extra", 52 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xBB\xBF\x24\xC2\xA3"), 53 | }, 54 | { 55 | .name = "First possible 3 byte codepoint", 56 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xE0\xA0\x80"), 57 | }, 58 | { 59 | .name = "First possible 4 byte codepoint", 60 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xF0\x90\x80\x80"), 61 | }, 62 | { 63 | .name = "Last possible 2 byte codepoint", 64 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xDF\xBF"), 65 | }, 66 | { 67 | .name = "Last valid codepoint before prohibited range U+D800 - U+DFFF", 68 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xED\x9F\xBF"), 69 | }, 70 | { 71 | .name = "Next valid codepoint after prohibited range U+D800 - U+DFFF", 72 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEE\x80\x80"), 73 | }, 74 | { 75 | .name = "Boundary condition", 76 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xBF\xBD"), 77 | }, 78 | { 79 | .name = "Boundary condition", 80 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xF4\x90\x80\x80"), 81 | }, 82 | }; 83 | 84 | static struct utf8_example s_illegal_mqtt_utf8_examples[] = { 85 | { 86 | .name = "non character U+0000", 87 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\x00"), 88 | }, 89 | { 90 | .name = "Codepoint in prohibited range U+0001 - U+001F (in the middle)", 91 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\x04"), 92 | }, 93 | { 94 | .name = "Codepoint in prohibited range U+0001 - U+001F (boundary)", 95 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\x1F"), 96 | }, 97 | { 98 | .name = "Codepoint in prohibited range U+007F - U+009F (min: U+7F)", 99 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\x7F"), 100 | }, 101 | { 102 | .name = "Codepoint in prohibited range U+007F - U+009F (in the middle u+8F)", 103 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xC2\x8F"), 104 | }, 105 | { 106 | .name = "Codepoint in prohibited range U+007F - U+009F (boundary U+9F)", 107 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xC2\x9F"), 108 | }, 109 | { 110 | .name = "non character end with U+FFFF", 111 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xBF\xBF"), 112 | }, 113 | { 114 | .name = "non character end with U+FFFE", 115 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xF7\xBF\xBF\xBE"), 116 | }, 117 | { 118 | .name = "non character in U+FDD0 - U+FDEF (lower bound)", 119 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xB7\x90"), 120 | }, 121 | { 122 | .name = "non character in U+FDD0 - U+FDEF (in middle)", 123 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xB7\xA1"), 124 | }, 125 | { 126 | .name = "non character in U+FDD0 - U+FDEF (upper bound)", 127 | .text = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\xEF\xB7\xAF"), 128 | }}; 129 | 130 | static int s_mqtt_utf8_encoded_string_test(struct aws_allocator *allocator, void *ctx) { 131 | (void)ctx; 132 | /* Check the valid test cases */ 133 | for (size_t i = 0; i < AWS_ARRAY_SIZE(s_valid_mqtt_utf8_examples); ++i) { 134 | struct utf8_example example = s_valid_mqtt_utf8_examples[i]; 135 | printf("valid example [%zu]: %s\n", i, example.name); 136 | ASSERT_SUCCESS(aws_mqtt_validate_utf8_text(example.text)); 137 | } 138 | 139 | /* Glue all the valid test cases together, they ought to pass */ 140 | struct aws_byte_buf all_good_text; 141 | aws_byte_buf_init(&all_good_text, allocator, 1024); 142 | for (size_t i = 0; i < AWS_ARRAY_SIZE(s_valid_mqtt_utf8_examples); ++i) { 143 | aws_byte_buf_append_dynamic(&all_good_text, &s_valid_mqtt_utf8_examples[i].text); 144 | } 145 | ASSERT_SUCCESS(aws_mqtt_validate_utf8_text(aws_byte_cursor_from_buf(&all_good_text))); 146 | aws_byte_buf_clean_up(&all_good_text); 147 | 148 | /* Check the illegal test cases */ 149 | for (size_t i = 0; i < AWS_ARRAY_SIZE(s_illegal_mqtt_utf8_examples); ++i) { 150 | struct utf8_example example = s_illegal_mqtt_utf8_examples[i]; 151 | printf("illegal example [%zu]: %s\n", i, example.name); 152 | ASSERT_FAILS(aws_mqtt_validate_utf8_text(example.text)); 153 | } 154 | 155 | return AWS_OP_SUCCESS; 156 | } 157 | 158 | AWS_TEST_CASE(mqtt_utf8_encoded_string_test, s_mqtt_utf8_encoded_string_test) -------------------------------------------------------------------------------- /tests/v3/mqtt_mock_server_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_MOCK_SERVER_HANDLER_H 2 | #define MQTT_MOCK_SERVER_HANDLER_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include 8 | 9 | struct aws_channel_handler; 10 | struct aws_channel_slot; 11 | 12 | static const int MOCK_LOG_SUBJECT = 60000; 13 | 14 | struct mqtt_decoded_packet { 15 | enum aws_mqtt_packet_type type; 16 | struct aws_allocator *alloc; 17 | 18 | /* CONNECT */ 19 | bool clean_session; 20 | bool has_will; 21 | bool will_retain; 22 | bool has_password; 23 | bool has_username; 24 | uint16_t keep_alive_timeout; 25 | enum aws_mqtt_qos will_qos; 26 | struct aws_byte_cursor client_identifier; /* These cursors live with the received_message */ 27 | struct aws_byte_cursor will_topic; 28 | struct aws_byte_cursor will_message; 29 | struct aws_byte_cursor username; 30 | struct aws_byte_cursor password; 31 | 32 | /* PUBLISH SUBSCRIBE UNSUBSCRIBE */ 33 | uint16_t packet_identifier; 34 | struct aws_byte_cursor topic_name; /* PUBLISH topic */ 35 | struct aws_byte_cursor publish_payload; /* PUBLISH payload */ 36 | struct aws_array_list sub_topic_filters; /* list of aws_mqtt_subscription for SUBSCRIBE */ 37 | struct aws_array_list unsub_topic_filters; /* list of aws_byte_cursor for UNSUBSCRIBE */ 38 | bool duplicate; /* PUBLISH only */ 39 | 40 | /* index of the received packet, indicating when it's received by the server */ 41 | size_t index; 42 | }; 43 | 44 | struct aws_channel_handler *new_mqtt_mock_server(struct aws_allocator *allocator); 45 | void destroy_mqtt_mock_server(struct aws_channel_handler *handler); 46 | void mqtt_mock_server_handler_update_slot(struct aws_channel_handler *handler, struct aws_channel_slot *slot); 47 | 48 | /** 49 | * Mock server sends a publish packet back to client 50 | */ 51 | int mqtt_mock_server_send_publish( 52 | struct aws_channel_handler *handler, 53 | struct aws_byte_cursor *topic, 54 | struct aws_byte_cursor *payload, 55 | bool dup, 56 | enum aws_mqtt_qos qos, 57 | bool retain); 58 | 59 | /** 60 | * Mock server sends a publish packet back to client with user-controlled packet id 61 | */ 62 | int mqtt_mock_server_send_publish_by_id( 63 | struct aws_channel_handler *handler, 64 | uint16_t packet_id, 65 | struct aws_byte_cursor *topic, 66 | struct aws_byte_cursor *payload, 67 | bool dup, 68 | enum aws_mqtt_qos qos, 69 | bool retain); 70 | 71 | /** 72 | * Sets whether or not connacks return session present 73 | */ 74 | void mqtt_mock_server_set_session_present(struct aws_channel_handler *handler, bool session_present); 75 | 76 | /** 77 | * Sets whether or not server should reflect received PUBLISH packets. 78 | */ 79 | void mqtt_mock_server_set_publish_reflection(struct aws_channel_handler *handler, bool reflect_publishes); 80 | 81 | /** 82 | * Set max number of PINGRESP that mock server will send back to client 83 | */ 84 | void mqtt_mock_server_set_max_ping_resp(struct aws_channel_handler *handler, size_t max_ping); 85 | 86 | /** 87 | * Set max number of CONACK that mock server will send back to client 88 | */ 89 | void mqtt_mock_server_set_max_connack(struct aws_channel_handler *handler, size_t connack_avail); 90 | 91 | /** 92 | * Disable the automatically response (suback/unsuback/puback) to the client 93 | */ 94 | void mqtt_mock_server_disable_auto_ack(struct aws_channel_handler *handler); 95 | 96 | /** 97 | * Enable the automatically response (suback/unsuback/puback) to the client 98 | */ 99 | void mqtt_mock_server_enable_auto_ack(struct aws_channel_handler *handler); 100 | 101 | /** 102 | * Sets what reason code to return in subacks 103 | */ 104 | void mqtt_mock_server_suback_reason_code(struct aws_channel_handler *handler, uint8_t reason_code); 105 | 106 | /** 107 | * Send response back the client given the packet ID 108 | */ 109 | int mqtt_mock_server_send_unsuback(struct aws_channel_handler *handler, uint16_t packet_id); 110 | int mqtt_mock_server_send_puback(struct aws_channel_handler *handler, uint16_t packet_id); 111 | 112 | int mqtt_mock_server_send_single_suback( 113 | struct aws_channel_handler *handler, 114 | uint16_t packet_id, 115 | enum aws_mqtt_qos return_code); 116 | /** 117 | * Wait for puback_count PUBACK packages from client 118 | */ 119 | void mqtt_mock_server_wait_for_pubacks(struct aws_channel_handler *handler, size_t puback_count); 120 | 121 | /** 122 | * Getters for decoded packets, call mqtt_mock_server_decode_packets first. 123 | */ 124 | size_t mqtt_mock_server_decoded_packets_count(struct aws_channel_handler *handler); 125 | /** 126 | * Get the decoded packet by index 127 | */ 128 | struct mqtt_decoded_packet *mqtt_mock_server_get_decoded_packet_by_index(struct aws_channel_handler *handler, size_t i); 129 | /** 130 | * Get the latest received packet by index 131 | */ 132 | struct mqtt_decoded_packet *mqtt_mock_server_get_latest_decoded_packet(struct aws_channel_handler *handler); 133 | /** 134 | * Get the decoded packet by packet_id started from search_start_idx (included), Note: it may have multiple packets with 135 | * the same ID, this will return the earliest received on with the packet_id. If out_idx is not NULL, the index of found 136 | * packet will be stored at there, and if failed to find the packet, it will be set to SIZE_MAX, and the return value 137 | * will be NULL. 138 | */ 139 | struct mqtt_decoded_packet *mqtt_mock_server_find_decoded_packet_by_id( 140 | struct aws_channel_handler *handler, 141 | size_t search_start_idx, 142 | uint16_t packet_id, 143 | size_t *out_idx); 144 | /** 145 | * Get the decoded packet by type started from search_start_idx (included), Note: it may have multiple packets with 146 | * the same type, this will return the earliest received on with the packet_id. If out_idx is not NULL, the index of 147 | * found packet will be stored at there, and if failed to find the packet, it will be set to SIZE_MAX, and the return 148 | * value will be NULL. 149 | */ 150 | struct mqtt_decoded_packet *mqtt_mock_server_find_decoded_packet_by_type( 151 | struct aws_channel_handler *handler, 152 | size_t search_start_idx, 153 | enum aws_mqtt_packet_type type, 154 | size_t *out_idx); 155 | 156 | /** 157 | * Run all received messages through, and decode the messages. 158 | */ 159 | int mqtt_mock_server_decode_packets(struct aws_channel_handler *handler); 160 | 161 | /** 162 | * Returns the number of PINGs the server has gotten 163 | */ 164 | size_t mqtt_mock_server_get_ping_count(struct aws_channel_handler *handler); 165 | 166 | #endif /* MQTT_MOCK_SERVER_HANDLER_H */ 167 | -------------------------------------------------------------------------------- /source/v5/mqtt5_callbacks.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | struct aws_mqtt5_callback_set_entry { 15 | struct aws_allocator *allocator; 16 | 17 | struct aws_linked_list_node node; 18 | 19 | uint64_t id; 20 | 21 | struct aws_mqtt5_callback_set callbacks; 22 | }; 23 | 24 | void aws_mqtt5_callback_set_manager_init( 25 | struct aws_mqtt5_callback_set_manager *manager, 26 | struct aws_mqtt5_client *client) { 27 | 28 | manager->client = client; /* no need to ref count, it's assumed to be owned by the client */ 29 | manager->next_callback_set_entry_id = 1; 30 | 31 | aws_linked_list_init(&manager->callback_set_entries); 32 | } 33 | 34 | void aws_mqtt5_callback_set_manager_clean_up(struct aws_mqtt5_callback_set_manager *manager) { 35 | struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); 36 | while (node != aws_linked_list_end(&manager->callback_set_entries)) { 37 | struct aws_mqtt5_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt5_callback_set_entry, node); 38 | node = aws_linked_list_next(node); 39 | 40 | aws_linked_list_remove(&entry->node); 41 | aws_mem_release(entry->allocator, entry); 42 | } 43 | } 44 | 45 | static struct aws_mqtt5_callback_set_entry *s_new_callback_set_entry( 46 | struct aws_mqtt5_callback_set_manager *manager, 47 | struct aws_mqtt5_callback_set *callback_set) { 48 | struct aws_mqtt5_callback_set_entry *entry = 49 | aws_mem_calloc(manager->client->allocator, 1, sizeof(struct aws_mqtt5_callback_set_entry)); 50 | 51 | entry->allocator = manager->client->allocator; 52 | entry->id = manager->next_callback_set_entry_id++; 53 | entry->callbacks = *callback_set; 54 | 55 | AWS_LOGF_INFO( 56 | AWS_LS_MQTT5_GENERAL, 57 | "id=%p: callback manager created new entry :%" PRIu64, 58 | (void *)manager->client, 59 | entry->id); 60 | 61 | return entry; 62 | } 63 | 64 | uint64_t aws_mqtt5_callback_set_manager_push_front( 65 | struct aws_mqtt5_callback_set_manager *manager, 66 | struct aws_mqtt5_callback_set *callback_set) { 67 | 68 | AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); 69 | 70 | struct aws_mqtt5_callback_set_entry *entry = s_new_callback_set_entry(manager, callback_set); 71 | 72 | aws_linked_list_push_front(&manager->callback_set_entries, &entry->node); 73 | 74 | return entry->id; 75 | } 76 | 77 | void aws_mqtt5_callback_set_manager_remove(struct aws_mqtt5_callback_set_manager *manager, uint64_t callback_set_id) { 78 | 79 | AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); 80 | 81 | struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); 82 | while (node != aws_linked_list_end(&manager->callback_set_entries)) { 83 | struct aws_mqtt5_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt5_callback_set_entry, node); 84 | node = aws_linked_list_next(node); 85 | 86 | if (entry->id == callback_set_id) { 87 | aws_linked_list_remove(&entry->node); 88 | 89 | AWS_LOGF_INFO( 90 | AWS_LS_MQTT5_GENERAL, 91 | "id=%p: callback manager removed entry id=%" PRIu64, 92 | (void *)manager->client, 93 | entry->id); 94 | aws_mem_release(entry->allocator, entry); 95 | return; 96 | } 97 | } 98 | AWS_LOGF_INFO( 99 | AWS_LS_MQTT5_GENERAL, 100 | "id=%p: callback manager failed to remove entry id=%" PRIu64 ", callback set id not found.", 101 | (void *)manager->client, 102 | callback_set_id); 103 | } 104 | 105 | void aws_mqtt5_callback_set_manager_on_publish_received( 106 | struct aws_mqtt5_callback_set_manager *manager, 107 | const struct aws_mqtt5_packet_publish_view *publish_view) { 108 | 109 | AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); 110 | 111 | struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); 112 | while (node != aws_linked_list_end(&manager->callback_set_entries)) { 113 | struct aws_mqtt5_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt5_callback_set_entry, node); 114 | node = aws_linked_list_next(node); 115 | 116 | struct aws_mqtt5_callback_set *callback_set = &entry->callbacks; 117 | if (callback_set->listener_publish_received_handler != NULL) { 118 | bool handled = (*callback_set->listener_publish_received_handler)( 119 | publish_view, callback_set->listener_publish_received_handler_user_data); 120 | if (handled) { 121 | return; 122 | } 123 | } 124 | } 125 | 126 | if (manager->client->config->publish_received_handler != NULL) { 127 | (*manager->client->config->publish_received_handler)( 128 | publish_view, manager->client->config->publish_received_handler_user_data); 129 | } 130 | } 131 | 132 | void aws_mqtt5_callback_set_manager_on_lifecycle_event( 133 | struct aws_mqtt5_callback_set_manager *manager, 134 | const struct aws_mqtt5_client_lifecycle_event *lifecycle_event) { 135 | 136 | AWS_FATAL_ASSERT(aws_event_loop_thread_is_callers_thread(manager->client->loop)); 137 | 138 | struct aws_linked_list_node *node = aws_linked_list_begin(&manager->callback_set_entries); 139 | while (node != aws_linked_list_end(&manager->callback_set_entries)) { 140 | struct aws_mqtt5_callback_set_entry *entry = AWS_CONTAINER_OF(node, struct aws_mqtt5_callback_set_entry, node); 141 | node = aws_linked_list_next(node); 142 | 143 | struct aws_mqtt5_callback_set *callback_set = &entry->callbacks; 144 | 145 | if (callback_set->lifecycle_event_handler != NULL) { 146 | struct aws_mqtt5_client_lifecycle_event listener_copy = *lifecycle_event; 147 | listener_copy.user_data = callback_set->lifecycle_event_handler_user_data; 148 | 149 | (*callback_set->lifecycle_event_handler)(&listener_copy); 150 | } 151 | } 152 | 153 | struct aws_mqtt5_client_lifecycle_event client_copy = *lifecycle_event; 154 | client_copy.user_data = manager->client->config->lifecycle_event_handler_user_data; 155 | 156 | if (manager->client->config->lifecycle_event_handler != NULL) { 157 | (*manager->client->config->lifecycle_event_handler)(&client_copy); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/topic_tree.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_TOPIC_TREE_H 2 | #define AWS_MQTT_PRIVATE_TOPIC_TREE_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | 13 | /** Type of function called when a publish received matches a subscription */ 14 | typedef void(aws_mqtt_publish_received_fn)( 15 | const struct aws_byte_cursor *topic, 16 | const struct aws_byte_cursor *payload, 17 | bool dup, 18 | enum aws_mqtt_qos qos, 19 | bool retain, 20 | void *user_data); 21 | 22 | /** 23 | * Function called per subscription when iterating through subscriptions. 24 | * Return true to continue iteration, or false to stop. 25 | */ 26 | typedef bool( 27 | aws_mqtt_topic_tree_iterator_fn)(const struct aws_byte_cursor *topic, enum aws_mqtt_qos qos, void *user_data); 28 | 29 | struct aws_mqtt_topic_node { 30 | 31 | /* This node's part of the topic filter. If in another node's subtopics, this is the key. */ 32 | struct aws_byte_cursor topic; 33 | 34 | /** 35 | * aws_byte_cursor -> aws_mqtt_topic_node 36 | * '#' and '+' are special values in here 37 | */ 38 | struct aws_hash_table subtopics; 39 | 40 | /* The entire topic filter. If !owns_topic_filter, this topic_filter belongs to someone else. */ 41 | const struct aws_string *topic_filter; 42 | bool owns_topic_filter; 43 | 44 | /* The following will only be populated if the node IS a subscription */ 45 | /* Max QoS to deliver. */ 46 | enum aws_mqtt_qos qos; 47 | /* Callback to call on message received */ 48 | aws_mqtt_publish_received_fn *callback; 49 | aws_mqtt_userdata_cleanup_fn *cleanup; 50 | void *userdata; 51 | }; 52 | 53 | struct aws_mqtt_topic_tree { 54 | struct aws_mqtt_topic_node *root; 55 | struct aws_allocator *allocator; 56 | }; 57 | 58 | /** 59 | * The size of transaction instances. 60 | * When you initialize an aws_array_list for use as a transaction, pass this as the item size. 61 | */ 62 | extern AWS_MQTT_API size_t aws_mqtt_topic_tree_action_size; 63 | 64 | /** 65 | * Initialize a topic tree with an allocator to later use. 66 | * Note that calling init allocates root. 67 | */ 68 | AWS_MQTT_API int aws_mqtt_topic_tree_init(struct aws_mqtt_topic_tree *tree, struct aws_allocator *allocator); 69 | /** 70 | * Cleanup and deallocate an entire topic tree. 71 | */ 72 | AWS_MQTT_API void aws_mqtt_topic_tree_clean_up(struct aws_mqtt_topic_tree *tree); 73 | 74 | /** 75 | * Iterates through all registered subscriptions, and calls iterator. 76 | * 77 | * Iterator may return false to stop iterating, or true to continue. 78 | */ 79 | AWS_MQTT_API void aws_mqtt_topic_tree_iterate( 80 | const struct aws_mqtt_topic_tree *tree, 81 | aws_mqtt_topic_tree_iterator_fn *iterator, 82 | void *user_data); 83 | 84 | /** 85 | * Gets the total number of subscriptions in the tree. 86 | */ 87 | AWS_MQTT_API size_t aws_mqtt_topic_tree_get_sub_count(const struct aws_mqtt_topic_tree *tree); 88 | 89 | /** 90 | * Insert a new topic filter into the subscription tree (subscribe). 91 | * 92 | * \param[in] tree The tree to insert into. 93 | * \param[in] transaction The transaction to add the insert action to. 94 | * Must be initialized with aws_mqtt_topic_tree_action_size as item size. 95 | * \param[in] topic_filter The topic filter to subscribe on. May contain wildcards. 96 | * \param[in] callback The callback to call on a publish with a matching topic. 97 | * \param[in] connection The connection object to pass to the callback. This is a void* to support client and server 98 | * connections in the future. 99 | * \param[in] userdata The userdata to pass to callback. 100 | * 101 | * \returns AWS_OP_SUCCESS on successful insertion, AWS_OP_ERR with aws_last_error() populated on failure. 102 | * If AWS_OP_ERR is returned, aws_mqtt_topic_tree_transaction_rollback should be called to prevent leaks. 103 | */ 104 | AWS_MQTT_API int aws_mqtt_topic_tree_transaction_insert( 105 | struct aws_mqtt_topic_tree *tree, 106 | struct aws_array_list *transaction, 107 | const struct aws_string *topic_filter, 108 | enum aws_mqtt_qos qos, 109 | aws_mqtt_publish_received_fn *callback, 110 | aws_mqtt_userdata_cleanup_fn *cleanup, 111 | void *userdata); 112 | 113 | /** 114 | * Remove a topic filter from the subscription tree (unsubscribe). 115 | * 116 | * \param[in] tree The tree to remove from. 117 | * \param[in] transaction The transaction to add the insert action to. 118 | * Must be initialized with aws_mqtt_topic_tree_action_size as item size. 119 | * \param[in] topic_filter The filter to remove (must be exactly the same as the topic_filter passed to insert). 120 | * \param[out] old_userdata The userdata assigned to this subscription will be assigned if not NULL. 121 | * \NOTE once the transaction is committed, old_userdata may be destroyed, 122 | * if a cleanup callback was set on insert. 123 | * 124 | * \returns AWS_OP_SUCCESS on successful removal, AWS_OP_ERR with aws_last_error() populated on failure. 125 | * If AWS_OP_ERR is returned, aws_mqtt_topic_tree_transaction_rollback should be called to prevent leaks. 126 | */ 127 | AWS_MQTT_API int aws_mqtt_topic_tree_transaction_remove( 128 | struct aws_mqtt_topic_tree *tree, 129 | struct aws_array_list *transaction, 130 | const struct aws_byte_cursor *topic_filter, 131 | void **old_userdata); 132 | 133 | AWS_MQTT_API void aws_mqtt_topic_tree_transaction_commit( 134 | struct aws_mqtt_topic_tree *tree, 135 | struct aws_array_list *transaction); 136 | 137 | AWS_MQTT_API void aws_mqtt_topic_tree_transaction_roll_back( 138 | struct aws_mqtt_topic_tree *tree, 139 | struct aws_array_list *transaction); 140 | 141 | /** 142 | * Insert a new topic filter into the subscription tree (subscribe). 143 | * 144 | * \param[in] tree The tree to insert into. 145 | * \param[in] topic_filter The topic filter to subscribe on. May contain wildcards. 146 | * \param[in] callback The callback to call on a publish with a matching topic. 147 | * \param[in] connection The connection object to pass to the callback. This is a void* to support client and server 148 | * connections in the future. 149 | * \param[in] userdata The userdata to pass to callback. 150 | * 151 | * \returns AWS_OP_SUCCESS on successful insertion, AWS_OP_ERR with aws_last_error() populated on failure. 152 | */ 153 | AWS_MQTT_API 154 | int aws_mqtt_topic_tree_insert( 155 | struct aws_mqtt_topic_tree *tree, 156 | const struct aws_string *topic_filter, 157 | enum aws_mqtt_qos qos, 158 | aws_mqtt_publish_received_fn *callback, 159 | aws_mqtt_userdata_cleanup_fn *cleanup, 160 | void *userdata); 161 | 162 | AWS_MQTT_API 163 | int aws_mqtt_topic_tree_remove(struct aws_mqtt_topic_tree *tree, const struct aws_byte_cursor *topic_filter); 164 | 165 | /** 166 | * Dispatches a publish packet to all subscriptions matching the publish topic. 167 | * 168 | * \param[in] tree The tree to publish on. 169 | * \param[in] pub The publish packet to dispatch. The topic MUST NOT contain wildcards. 170 | */ 171 | void AWS_MQTT_API 172 | aws_mqtt_topic_tree_publish(const struct aws_mqtt_topic_tree *tree, struct aws_mqtt_packet_publish *pub); 173 | 174 | #endif /* AWS_MQTT_PRIVATE_TOPIC_TREE_H */ 175 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/mqtt311_listener.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_MQTT_MQTT311_LISTENER_H 7 | #define AWS_MQTT_MQTT311_LISTENER_H 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | AWS_PUSH_SANE_WARNING_LEVEL 15 | 16 | /** 17 | * Callback signature for when an mqtt311 listener has completely destroyed itself. 18 | */ 19 | typedef void(aws_mqtt311_listener_termination_completion_fn)(void *complete_ctx); 20 | 21 | /** 22 | * A record that tracks MQTT311 client connection callbacks which can be dynamically injected via a listener. 23 | * 24 | * All the callbacks that are supported here are invoked only on the 311 connection's event loop. With the 25 | * add/remove callback set also on the event loop, everything is correctly serialized without data races. 26 | * 27 | * If binding additional callbacks, they must only be invoked from the connection's event loop. 28 | * 29 | * We only listen to connection-success because the only connection-level event we care about is a failure 30 | * to rejoin a session (which invalidates all subscriptions that were considered valid) 31 | */ 32 | struct aws_mqtt311_callback_set { 33 | 34 | /* Called from s_packet_handler_publish which is event-loop invoked */ 35 | aws_mqtt_client_publish_received_fn *publish_received_handler; 36 | 37 | /* Called from s_packet_handler_connack which is event-loop invoked */ 38 | aws_mqtt_client_on_connection_success_fn *connection_success_handler; 39 | 40 | /* Called from s_mqtt_client_shutdown which is event-loop invoked */ 41 | aws_mqtt_client_on_connection_interrupted_fn *connection_interrupted_handler; 42 | 43 | /* Called from s_mqtt_client_shutdown which is event-loop invoked */ 44 | aws_mqtt_client_on_disconnect_fn *disconnect_handler; 45 | 46 | void *user_data; 47 | }; 48 | 49 | /** 50 | * An internal type for managing chains of callbacks attached to an mqtt311 client connection. Supports chains for 51 | * lifecycle events and incoming publish packet handling. 52 | * 53 | * Assumed to be owned and used only by an MQTT311 client connection. 54 | */ 55 | struct aws_mqtt311_callback_set_manager { 56 | struct aws_allocator *allocator; 57 | 58 | struct aws_mqtt_client_connection *connection; 59 | 60 | struct aws_linked_list callback_set_entries; 61 | 62 | uint64_t next_callback_set_entry_id; 63 | }; 64 | 65 | /** 66 | * Configuration options for MQTT311 listener objects. 67 | */ 68 | struct aws_mqtt311_listener_config { 69 | 70 | /** 71 | * MQTT311 client connection to listen to events on 72 | */ 73 | struct aws_mqtt_client_connection *connection; 74 | 75 | /** 76 | * Callbacks to invoke when events occur on the MQTT311 client connection 77 | */ 78 | struct aws_mqtt311_callback_set listener_callbacks; 79 | 80 | /** 81 | * Listener destruction is asynchronous and thus requires a termination callback and associated user data 82 | * to notify the user that the listener has been fully destroyed and no further events will be received. 83 | */ 84 | aws_mqtt311_listener_termination_completion_fn *termination_callback; 85 | void *termination_callback_user_data; 86 | }; 87 | 88 | AWS_EXTERN_C_BEGIN 89 | 90 | /** 91 | * Creates a new MQTT311 listener object. For as long as the listener lives, incoming publishes and lifecycle events 92 | * will be forwarded to the callbacks configured on the listener. 93 | * 94 | * @param allocator allocator to use 95 | * @param config listener configuration 96 | * @return a new aws_mqtt311_listener object 97 | */ 98 | AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_new( 99 | struct aws_allocator *allocator, 100 | struct aws_mqtt311_listener_config *config); 101 | 102 | /** 103 | * Adds a reference to an mqtt311 listener. 104 | * 105 | * @param listener listener to add a reference to 106 | * @return the listener object 107 | */ 108 | AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_acquire(struct aws_mqtt311_listener *listener); 109 | 110 | /** 111 | * Removes a reference to an mqtt311 listener. When the reference count drops to zero, the listener's asynchronous 112 | * destruction will be started. 113 | * 114 | * @param listener listener to remove a reference from 115 | * @return NULL 116 | */ 117 | AWS_MQTT_API struct aws_mqtt311_listener *aws_mqtt311_listener_release(struct aws_mqtt311_listener *listener); 118 | 119 | /** 120 | * Initializes a callback set manager 121 | */ 122 | AWS_MQTT_API 123 | void aws_mqtt311_callback_set_manager_init( 124 | struct aws_mqtt311_callback_set_manager *manager, 125 | struct aws_allocator *allocator, 126 | struct aws_mqtt_client_connection *connection); 127 | 128 | /** 129 | * Cleans up a callback set manager. 130 | * 131 | * aws_mqtt311_callback_set_manager_init must have been previously called or this will crash. 132 | */ 133 | AWS_MQTT_API 134 | void aws_mqtt311_callback_set_manager_clean_up(struct aws_mqtt311_callback_set_manager *manager); 135 | 136 | /** 137 | * Adds a callback set to the front of the handler chain. Returns an integer id that can be used to selectively 138 | * remove the callback set from the manager. 139 | * 140 | * May only be called on the client's event loop thread. 141 | */ 142 | AWS_MQTT_API 143 | uint64_t aws_mqtt311_callback_set_manager_push_front( 144 | struct aws_mqtt311_callback_set_manager *manager, 145 | struct aws_mqtt311_callback_set *callback_set); 146 | 147 | /** 148 | * Removes a callback set from the handler chain. 149 | * 150 | * May only be called on the client's event loop thread. 151 | */ 152 | AWS_MQTT_API 153 | void aws_mqtt311_callback_set_manager_remove( 154 | struct aws_mqtt311_callback_set_manager *manager, 155 | uint64_t callback_set_id); 156 | 157 | /** 158 | * Walks the incoming publish handler chain for an MQTT311 connection, invoking each in sequence. 159 | * 160 | * May only be called on the client's event loop thread. 161 | */ 162 | AWS_MQTT_API 163 | void aws_mqtt311_callback_set_manager_on_publish_received( 164 | struct aws_mqtt311_callback_set_manager *manager, 165 | const struct aws_byte_cursor *topic, 166 | const struct aws_byte_cursor *payload, 167 | bool dup, 168 | enum aws_mqtt_qos qos, 169 | bool retain); 170 | 171 | /** 172 | * Invokes a connection success event on each listener in the manager's collection of callback sets. 173 | * 174 | * May only be called on the client's event loop thread. 175 | */ 176 | AWS_MQTT_API 177 | void aws_mqtt311_callback_set_manager_on_connection_success( 178 | struct aws_mqtt311_callback_set_manager *manager, 179 | enum aws_mqtt_connect_return_code return_code, 180 | bool rejoined_session); 181 | 182 | /** 183 | * Invokes a connection interrupted event on each listener in the manager's collection of callback sets. 184 | * 185 | * May only be called on the client's event loop thread. 186 | */ 187 | AWS_MQTT_API 188 | void aws_mqtt311_callback_set_manager_on_connection_interrupted( 189 | struct aws_mqtt311_callback_set_manager *manager, 190 | int error_code); 191 | 192 | /** 193 | * Invokes a disconnection event on each listener in the manager's collection of callback sets. 194 | * 195 | * May only be called on the client's event loop thread. 196 | */ 197 | AWS_MQTT_API 198 | void aws_mqtt311_callback_set_manager_on_disconnect(struct aws_mqtt311_callback_set_manager *manager); 199 | 200 | AWS_EXTERN_C_END 201 | 202 | AWS_POP_SANE_WARNING_LEVEL 203 | 204 | #endif /* AWS_MQTT_MQTT311_LISTENER_H */ 205 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/request-response/protocol_adapter.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H 2 | #define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | struct aws_allocator; 15 | struct aws_event_loop; 16 | struct aws_mqtt_client_connection; 17 | struct aws_mqtt5_client; 18 | struct aws_mqtt_rr_incoming_publish_event; 19 | 20 | /* 21 | * The request-response protocol adapter is a translation layer that sits between the request-response native client 22 | * implementation and a protocol client capable of subscribing, unsubscribing, and publishing MQTT messages. 23 | * Valid protocol clients include the CRT MQTT5 client, the CRT MQTT311 client, and an eventstream RPC connection 24 | * that belongs to a Greengrass IPC client. Each of these protocol clients has a different (or even implicit) 25 | * contract for carrying out pub-sub operations. The protocol adapter abstracts these details with a simple, 26 | * minimal interface based on the requirements identified in the request-response design documents. 27 | */ 28 | 29 | /* 30 | * Minimal MQTT subscribe options 31 | */ 32 | struct aws_protocol_adapter_subscribe_options { 33 | struct aws_byte_cursor topic_filter; 34 | uint32_t ack_timeout_seconds; 35 | }; 36 | 37 | /* 38 | * Minimal MQTT unsubscribe options 39 | */ 40 | struct aws_protocol_adapter_unsubscribe_options { 41 | struct aws_byte_cursor topic_filter; 42 | uint32_t ack_timeout_seconds; 43 | }; 44 | 45 | /* 46 | * Minimal MQTT publish options 47 | */ 48 | struct aws_protocol_adapter_publish_options { 49 | struct aws_byte_cursor topic; 50 | struct aws_byte_cursor payload; 51 | uint32_t ack_timeout_seconds; 52 | 53 | /* 54 | * Invoked on success/failure of the publish itself. Our implementations use QoS1 which means that success 55 | * will be on puback receipt. 56 | */ 57 | void (*completion_callback_fn)(int, void *); 58 | 59 | /* 60 | * User data to pass in when invoking the completion callback 61 | */ 62 | void *user_data; 63 | }; 64 | 65 | /* 66 | * Describes the type of subscription event (relative to a topic filter) 67 | */ 68 | enum aws_protocol_adapter_subscription_event_type { 69 | AWS_PASET_SUBSCRIBE, 70 | AWS_PASET_UNSUBSCRIBE, 71 | }; 72 | 73 | /* 74 | * An event emitted by the protocol adapter when a subscribe or unsubscribe is completed by the adapted protocol 75 | * client. 76 | */ 77 | struct aws_protocol_adapter_subscription_event { 78 | struct aws_byte_cursor topic_filter; 79 | enum aws_protocol_adapter_subscription_event_type event_type; 80 | int error_code; 81 | bool retryable; 82 | }; 83 | 84 | enum aws_protocol_adapter_connection_event_type { 85 | AWS_PACET_CONNECTED, 86 | AWS_PACET_DISCONNECTED, 87 | }; 88 | 89 | /* 90 | * An event emitted by the protocol adapter whenever the protocol client's connection status changes 91 | */ 92 | struct aws_protocol_adapter_connection_event { 93 | enum aws_protocol_adapter_connection_event_type event_type; 94 | bool joined_session; 95 | }; 96 | 97 | typedef void(aws_protocol_adapter_subscription_event_fn)( 98 | const struct aws_protocol_adapter_subscription_event *event, 99 | void *user_data); 100 | 101 | typedef void(aws_protocol_adapter_incoming_publish_fn)( 102 | const struct aws_mqtt_rr_incoming_publish_event *publish, 103 | void *user_data); 104 | 105 | typedef void(aws_protocol_adapter_terminate_callback_fn)(void *user_data); 106 | 107 | typedef void(aws_protocol_adapter_connection_event_fn)( 108 | const struct aws_protocol_adapter_connection_event *event, 109 | void *user_data); 110 | 111 | /* 112 | * Set of callbacks invoked by the protocol adapter. These must all be set. 113 | */ 114 | struct aws_mqtt_protocol_adapter_options { 115 | aws_protocol_adapter_subscription_event_fn *subscription_event_callback; 116 | aws_protocol_adapter_incoming_publish_fn *incoming_publish_callback; 117 | aws_protocol_adapter_terminate_callback_fn *terminate_callback; 118 | aws_protocol_adapter_connection_event_fn *connection_event_callback; 119 | 120 | /* 121 | * User data to pass into all singleton protocol adapter callbacks. Likely either the request-response client 122 | * or the subscription manager component of the request-response client. 123 | */ 124 | void *user_data; 125 | }; 126 | 127 | struct aws_mqtt_protocol_adapter_vtable { 128 | 129 | void (*aws_mqtt_protocol_adapter_destroy_fn)(void *); 130 | 131 | int (*aws_mqtt_protocol_adapter_subscribe_fn)(void *, struct aws_protocol_adapter_subscribe_options *); 132 | 133 | int (*aws_mqtt_protocol_adapter_unsubscribe_fn)(void *, struct aws_protocol_adapter_unsubscribe_options *); 134 | 135 | int (*aws_mqtt_protocol_adapter_publish_fn)(void *, struct aws_protocol_adapter_publish_options *); 136 | 137 | bool (*aws_mqtt_protocol_adapter_is_connected_fn)(void *); 138 | 139 | struct aws_event_loop *(*aws_mqtt_protocol_adapter_get_event_loop_fn)(void *); 140 | }; 141 | 142 | struct aws_mqtt_protocol_adapter { 143 | const struct aws_mqtt_protocol_adapter_vtable *vtable; 144 | void *impl; 145 | }; 146 | 147 | AWS_EXTERN_C_BEGIN 148 | 149 | /* 150 | * Creates a new request-response protocol adapter from an MQTT311 client 151 | */ 152 | AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_311( 153 | struct aws_allocator *allocator, 154 | struct aws_mqtt_protocol_adapter_options *options, 155 | struct aws_mqtt_client_connection *connection); 156 | 157 | /* 158 | * Creates a new request-response protocol adapter from an MQTT5 client 159 | */ 160 | AWS_MQTT_API struct aws_mqtt_protocol_adapter *aws_mqtt_protocol_adapter_new_from_5( 161 | struct aws_allocator *allocator, 162 | struct aws_mqtt_protocol_adapter_options *options, 163 | struct aws_mqtt5_client *client); 164 | 165 | /* 166 | * Destroys a request-response protocol adapter. Destruction is an asynchronous process and the caller must 167 | * wait for the termination callback to be invoked before assuming that no further callbacks will be invoked. 168 | */ 169 | AWS_MQTT_API void aws_mqtt_protocol_adapter_destroy(struct aws_mqtt_protocol_adapter *adapter); 170 | 171 | /* 172 | * Asks the adapted protocol client to perform an MQTT subscribe operation 173 | */ 174 | AWS_MQTT_API int aws_mqtt_protocol_adapter_subscribe( 175 | struct aws_mqtt_protocol_adapter *adapter, 176 | struct aws_protocol_adapter_subscribe_options *options); 177 | 178 | /* 179 | * Asks the adapted protocol client to perform an MQTT unsubscribe operation 180 | */ 181 | AWS_MQTT_API int aws_mqtt_protocol_adapter_unsubscribe( 182 | struct aws_mqtt_protocol_adapter *adapter, 183 | struct aws_protocol_adapter_unsubscribe_options *options); 184 | 185 | /* 186 | * Asks the adapted protocol client to perform an MQTT publish operation 187 | */ 188 | AWS_MQTT_API int aws_mqtt_protocol_adapter_publish( 189 | struct aws_mqtt_protocol_adapter *adapter, 190 | struct aws_protocol_adapter_publish_options *options); 191 | 192 | /* 193 | * Synchronously checks the connection state of the adapted protocol client. May only be called from the 194 | * protocol client's event loop. 195 | */ 196 | AWS_MQTT_API bool aws_mqtt_protocol_adapter_is_connected(struct aws_mqtt_protocol_adapter *adapter); 197 | 198 | /* 199 | * Returns the event loop that the protocol client is bound to. 200 | */ 201 | AWS_MQTT_API struct aws_event_loop *aws_mqtt_protocol_adapter_get_event_loop(struct aws_mqtt_protocol_adapter *adapter); 202 | 203 | AWS_MQTT_API const char *aws_protocol_adapter_subscription_event_type_to_c_str( 204 | enum aws_protocol_adapter_subscription_event_type type); 205 | 206 | AWS_MQTT_API const char *aws_protocol_adapter_connection_event_type_to_c_str( 207 | enum aws_protocol_adapter_connection_event_type type); 208 | 209 | AWS_EXTERN_C_END 210 | 211 | #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_PROTOCOL_ADAPTER_H */ 212 | -------------------------------------------------------------------------------- /tests/v5/mqtt5_utils_tests.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | static int s_mqtt5_topic_skip_rules_prefix_fn(struct aws_allocator *allocator, void *ctx) { 13 | (void)ctx; 14 | (void)allocator; 15 | 16 | struct aws_byte_cursor skip_cursor; 17 | struct aws_byte_cursor expected_cursor; 18 | 19 | /* nothing should be skipped */ 20 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("dont/skip/anything")); 21 | expected_cursor = aws_byte_cursor_from_c_str("dont/skip/anything"); 22 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 23 | 24 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("")); 25 | expected_cursor = aws_byte_cursor_from_c_str(""); 26 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 27 | 28 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("/")); 29 | expected_cursor = aws_byte_cursor_from_c_str("/"); 30 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 31 | 32 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$aws")); 33 | expected_cursor = aws_byte_cursor_from_c_str("$aws"); 34 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 35 | 36 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$aws/rules")); 37 | expected_cursor = aws_byte_cursor_from_c_str("$aws/rules"); 38 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 39 | 40 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$aws/rules/")); 41 | expected_cursor = aws_byte_cursor_from_c_str("$aws/rules/"); 42 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 43 | 44 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$aws/rules/rulename")); 45 | expected_cursor = aws_byte_cursor_from_c_str("$aws/rules/rulename"); 46 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 47 | 48 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$share")); 49 | expected_cursor = aws_byte_cursor_from_c_str("$share"); 50 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 51 | 52 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$share/")); 53 | expected_cursor = aws_byte_cursor_from_c_str("$share/"); 54 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 55 | 56 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$share/share-name")); 57 | expected_cursor = aws_byte_cursor_from_c_str("$share/share-name"); 58 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 59 | 60 | /* prefix should be skipped */ 61 | skip_cursor = 62 | aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix(aws_byte_cursor_from_c_str("$aws/rules/rulename/")); 63 | expected_cursor = aws_byte_cursor_from_c_str(""); 64 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 65 | 66 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix( 67 | aws_byte_cursor_from_c_str("$aws/rules/some-rule/segment1/segment2")); 68 | expected_cursor = aws_byte_cursor_from_c_str("segment1/segment2"); 69 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 70 | 71 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix( 72 | aws_byte_cursor_from_c_str("$share/share-name/segment1/segment2")); 73 | expected_cursor = aws_byte_cursor_from_c_str("segment1/segment2"); 74 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 75 | 76 | skip_cursor = aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix( 77 | aws_byte_cursor_from_c_str("$share/share-name/$aws/rules/some-rule/segment1/segment2")); 78 | expected_cursor = aws_byte_cursor_from_c_str("segment1/segment2"); 79 | ASSERT_TRUE(aws_byte_cursor_eq(&skip_cursor, &expected_cursor)); 80 | 81 | return AWS_OP_SUCCESS; 82 | } 83 | 84 | AWS_TEST_CASE(mqtt5_topic_skip_rules_prefix, s_mqtt5_topic_skip_rules_prefix_fn) 85 | 86 | static int s_mqtt5_topic_get_segment_count_fn(struct aws_allocator *allocator, void *ctx) { 87 | (void)ctx; 88 | (void)allocator; 89 | 90 | ASSERT_INT_EQUALS(1, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str(""))); 91 | ASSERT_INT_EQUALS(1, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("hello"))); 92 | ASSERT_INT_EQUALS(2, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("hello/"))); 93 | ASSERT_INT_EQUALS(2, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("hello/world"))); 94 | ASSERT_INT_EQUALS(3, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("a/b/c"))); 95 | ASSERT_INT_EQUALS(3, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("//"))); 96 | ASSERT_INT_EQUALS(4, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("$SYS/bad/no/"))); 97 | ASSERT_INT_EQUALS(1, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("$aws"))); 98 | ASSERT_INT_EQUALS(8, aws_mqtt5_topic_get_segment_count(aws_byte_cursor_from_c_str("//a//b/c//"))); 99 | 100 | ASSERT_INT_EQUALS( 101 | 2, 102 | aws_mqtt5_topic_get_segment_count(aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix( 103 | aws_byte_cursor_from_c_str("$aws/rules/some-rule/segment1/segment2")))); 104 | ASSERT_INT_EQUALS( 105 | 1, 106 | aws_mqtt5_topic_get_segment_count(aws_mqtt5_topic_skip_aws_iot_core_uncounted_prefix( 107 | aws_byte_cursor_from_c_str("$aws/rules/some-rule/segment1")))); 108 | 109 | return AWS_OP_SUCCESS; 110 | } 111 | 112 | AWS_TEST_CASE(mqtt5_topic_get_segment_count, s_mqtt5_topic_get_segment_count_fn) 113 | 114 | static int s_mqtt5_shared_subscription_validation_fn(struct aws_allocator *allocator, void *ctx) { 115 | (void)ctx; 116 | (void)allocator; 117 | 118 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str(""))); 119 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("oof"))); 120 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$sha"))); 121 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share"))); 122 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/"))); 123 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share//"))); 124 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share//test"))); 125 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m+/"))); 126 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m#/"))); 127 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m"))); 128 | ASSERT_FALSE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m/"))); 129 | 130 | ASSERT_TRUE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m/#"))); 131 | ASSERT_TRUE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m/great"))); 132 | ASSERT_TRUE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m/test/+"))); 133 | ASSERT_TRUE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m/+/test"))); 134 | ASSERT_TRUE(aws_mqtt_is_topic_filter_shared_subscription(aws_byte_cursor_from_c_str("$share/m/test/#"))); 135 | 136 | return AWS_OP_SUCCESS; 137 | } 138 | 139 | AWS_TEST_CASE(mqtt5_shared_subscription_validation, s_mqtt5_shared_subscription_validation_fn) 140 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/mqtt_subscription_set.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #ifndef AWS_MQTT_MQTT3_TO_MQTT5_ADAPTER_SUBSCRIPTION_SET_H 7 | #define AWS_MQTT_MQTT3_TO_MQTT5_ADAPTER_SUBSCRIPTION_SET_H 8 | 9 | #include "aws/mqtt/mqtt.h" 10 | 11 | #include "aws/mqtt/client.h" 12 | #include "aws/mqtt/v5/mqtt5_types.h" 13 | #include 14 | 15 | /** 16 | * (Transient) configuration options about a single persistent MQTT topic filter subscription 17 | */ 18 | struct aws_mqtt_subscription_set_subscription_options { 19 | struct aws_byte_cursor topic_filter; 20 | 21 | enum aws_mqtt5_qos qos; 22 | 23 | bool no_local; 24 | bool retain_as_published; 25 | enum aws_mqtt5_retain_handling_type retain_handling_type; 26 | 27 | /* Callback invoked when this subscription matches an incoming publish */ 28 | aws_mqtt_client_publish_received_fn *on_publish_received; 29 | 30 | /* Callback invoked when this subscription is removed from the set */ 31 | aws_mqtt_userdata_cleanup_fn *on_cleanup; 32 | 33 | void *callback_user_data; 34 | }; 35 | 36 | /** 37 | * Persistent structure to track a single MQTT topic filter subscription 38 | */ 39 | struct aws_mqtt_subscription_set_subscription_record { 40 | struct aws_allocator *allocator; 41 | struct aws_byte_buf topic_filter; 42 | 43 | struct aws_mqtt_subscription_set_subscription_options subscription_view; 44 | }; 45 | 46 | /** 47 | * (Transient) configuration options about an incoming publish message 48 | */ 49 | struct aws_mqtt_subscription_set_publish_received_options { 50 | struct aws_mqtt_client_connection *connection; 51 | 52 | struct aws_byte_cursor topic; 53 | enum aws_mqtt_qos qos; 54 | bool retain; 55 | bool dup; 56 | 57 | struct aws_byte_cursor payload; 58 | }; 59 | 60 | /** 61 | * A node in the topic trie maintained by the subscription set. Each node represents a single "path segment" in a 62 | * topic filter "path." Segments can be empty. 63 | * 64 | * Some examples (topic filter -> path segments): 65 | * 66 | * "hello/world" -> [ "hello", "world" ] 67 | * "a/b/" -> [ "a", "b", "" ] 68 | * "/b/" -> [ "", "b", "" ] 69 | * "a/#/c" -> [ "a", "#", "c" ] 70 | * 71 | * On incoming publish, we walk the tree invoking callbacks based on topic vs. topic filter matching, segment by 72 | * segment. 73 | * 74 | */ 75 | struct aws_mqtt_subscription_set_topic_tree_node { 76 | struct aws_allocator *allocator; 77 | 78 | struct aws_byte_cursor topic_segment_cursor; /* segment can be empty */ 79 | struct aws_byte_buf topic_segment; 80 | 81 | struct aws_mqtt_subscription_set_topic_tree_node *parent; 82 | struct aws_hash_table children; /* (embedded topic_segment -> containing node) */ 83 | 84 | /* 85 | * A node starts with a ref count of one and is incremented every time a new, overlapping path is added 86 | * to the subscription set. When the ref count goes to zero, that means there are not subscriptions using the 87 | * segment (or path suffix) represented by this node and therefor it can be deleted without any additional 88 | * analysis. 89 | * 90 | * Replacing an existing path does not change the ref count. 91 | */ 92 | size_t ref_count; 93 | 94 | bool is_subscription; 95 | 96 | aws_mqtt_client_publish_received_fn *on_publish_received; 97 | aws_mqtt_userdata_cleanup_fn *on_cleanup; 98 | 99 | void *callback_user_data; 100 | }; 101 | 102 | /** 103 | * Utility type to track a client's subscriptions. 104 | * 105 | * The topic tree supports per-subscription callbacks as used by the MQTT311 implementation. 106 | * 107 | * The subscriptions table supports resubscribe APIs for both MQTT311 and MQTT5 by tracking the subscription 108 | * details on a per-topic-filter basis. 109 | */ 110 | struct aws_mqtt_subscription_set { 111 | struct aws_allocator *allocator; 112 | 113 | /* a permanent ref */ 114 | struct aws_mqtt_subscription_set_topic_tree_node *root; 115 | 116 | /* embedded topic_filter_cursor -> persistent subscription */ 117 | struct aws_hash_table subscriptions; 118 | }; 119 | 120 | AWS_EXTERN_C_BEGIN 121 | 122 | /** 123 | * Creates a new subscription set 124 | * 125 | * @param allocator allocator to use 126 | * @return a new subscription set or NULL 127 | */ 128 | AWS_MQTT_API struct aws_mqtt_subscription_set *aws_mqtt_subscription_set_new(struct aws_allocator *allocator); 129 | 130 | /** 131 | * Destroys a subscription set 132 | * 133 | * @param subscription_set subscription set to destroy 134 | */ 135 | AWS_MQTT_API void aws_mqtt_subscription_set_destroy(struct aws_mqtt_subscription_set *subscription_set); 136 | 137 | /** 138 | * Checks if a topic filter exists in the subscription set's hash table of subscriptions 139 | * 140 | * @param subscription_set subscription set to check 141 | * @param topic_filter topic filter to check for existence in the set 142 | * @return true if the topic filter exists in the table of subscriptions, false otherwise 143 | */ 144 | AWS_MQTT_API bool aws_mqtt_subscription_set_is_subscribed( 145 | const struct aws_mqtt_subscription_set *subscription_set, 146 | struct aws_byte_cursor topic_filter); 147 | 148 | /** 149 | * Checks if a topic filter exists as a subscription (has a publish received handler) in the set's topic tree 150 | * 151 | * @param subscription_set subscription set to check 152 | * @param topic_filter topic filter to check for existence in the set's topic tree 153 | * @return true if the set's topic tree contains a publish received callback for the topic filter, false otherwise 154 | */ 155 | AWS_MQTT_API bool aws_mqtt_subscription_set_is_in_topic_tree( 156 | const struct aws_mqtt_subscription_set *subscription_set, 157 | struct aws_byte_cursor topic_filter); 158 | 159 | /** 160 | * Adds a subscription to the subscription set. If a subscription already exists with a matching topic filter, it 161 | * will be overwritten. 162 | * 163 | * @param subscription_set subscription set to add a subscription to 164 | * @param subscription_options options for the new subscription 165 | */ 166 | AWS_MQTT_API void aws_mqtt_subscription_set_add_subscription( 167 | struct aws_mqtt_subscription_set *subscription_set, 168 | const struct aws_mqtt_subscription_set_subscription_options *subscription_options); 169 | 170 | /** 171 | * Removes a subscription from the subscription set 172 | * 173 | * @param subscription_set subscription set to remove a subscription from 174 | * @param topic_filter topic filter to remove subscription information for 175 | */ 176 | AWS_MQTT_API void aws_mqtt_subscription_set_remove_subscription( 177 | struct aws_mqtt_subscription_set *subscription_set, 178 | struct aws_byte_cursor topic_filter); 179 | 180 | /** 181 | * Given a publish message, invokes all publish received handlers for matching subscriptions in the subscription set 182 | * 183 | * @param subscription_set subscription set to invoke publish received callbacks for 184 | * @param publish_options received publish message properties 185 | */ 186 | AWS_MQTT_API void aws_mqtt_subscription_set_on_publish_received( 187 | const struct aws_mqtt_subscription_set *subscription_set, 188 | const struct aws_mqtt_subscription_set_publish_received_options *publish_options); 189 | 190 | /** 191 | * Queries the properties of all subscriptions tracked by this subscription set. Used to implement re-subscribe 192 | * behavior. 193 | * 194 | * @param subscription_set subscription set to query the subscriptions on 195 | * @param subscriptions uninitialized array list to hold the subscriptions. 196 | * 197 | * The caller must invoke the cleanup function for array lists on the result. The list elements are of type 198 | * 'struct aws_mqtt_subscription_set_subscription_options' and the topic filter cursor points to the subscription set's 199 | * internal record. This means that the result must be used and cleaned up in local scope. 200 | */ 201 | AWS_MQTT_API void aws_mqtt_subscription_set_get_subscriptions( 202 | struct aws_mqtt_subscription_set *subscription_set, 203 | struct aws_array_list *subscriptions); 204 | 205 | /** 206 | * Creates a new subscription record. A subscription record tracks all information about a single MQTT topic filter 207 | * subscription 208 | * 209 | * @param allocator memory allocator to use 210 | * @param subscription all relevant information about the subscription 211 | * @return a new persistent subscription record 212 | */ 213 | AWS_MQTT_API struct aws_mqtt_subscription_set_subscription_record *aws_mqtt_subscription_set_subscription_record_new( 214 | struct aws_allocator *allocator, 215 | const struct aws_mqtt_subscription_set_subscription_options *subscription); 216 | 217 | /** 218 | * Destroys a subscription record 219 | * 220 | * @param record subscription record to destroy 221 | */ 222 | AWS_MQTT_API void aws_mqtt_subscription_set_subscription_record_destroy( 223 | struct aws_mqtt_subscription_set_subscription_record *record); 224 | 225 | AWS_EXTERN_C_END 226 | 227 | #endif /* AWS_MQTT_MQTT3_TO_MQTT5_ADAPTER_SUBSCRIPTION_SET_H */ 228 | -------------------------------------------------------------------------------- /source/client_impl_shared.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | struct aws_mqtt_client_connection *aws_mqtt_client_connection_acquire(struct aws_mqtt_client_connection *connection) { 10 | if (connection != NULL) { 11 | return (*connection->vtable->acquire_fn)(connection->impl); 12 | } 13 | 14 | return NULL; 15 | } 16 | 17 | void aws_mqtt_client_connection_release(struct aws_mqtt_client_connection *connection) { 18 | if (connection != NULL) { 19 | (*connection->vtable->release_fn)(connection->impl); 20 | } 21 | } 22 | 23 | int aws_mqtt_client_connection_set_will( 24 | struct aws_mqtt_client_connection *connection, 25 | const struct aws_byte_cursor *topic, 26 | enum aws_mqtt_qos qos, 27 | bool retain, 28 | const struct aws_byte_cursor *payload) { 29 | 30 | return (*connection->vtable->set_will_fn)(connection->impl, topic, qos, retain, payload); 31 | } 32 | 33 | int aws_mqtt_client_connection_set_login( 34 | struct aws_mqtt_client_connection *connection, 35 | const struct aws_byte_cursor *username, 36 | const struct aws_byte_cursor *password) { 37 | 38 | return (*connection->vtable->set_login_fn)(connection->impl, username, password); 39 | } 40 | 41 | int aws_mqtt_client_connection_use_websockets( 42 | struct aws_mqtt_client_connection *connection, 43 | aws_mqtt_transform_websocket_handshake_fn *transformer, 44 | void *transformer_ud, 45 | aws_mqtt_validate_websocket_handshake_fn *validator, 46 | void *validator_ud) { 47 | 48 | return (*connection->vtable->use_websockets_fn)( 49 | connection->impl, transformer, transformer_ud, validator, validator_ud); 50 | } 51 | 52 | int aws_mqtt_client_connection_set_http_proxy_options( 53 | struct aws_mqtt_client_connection *connection, 54 | struct aws_http_proxy_options *proxy_options) { 55 | 56 | return (*connection->vtable->set_http_proxy_options_fn)(connection->impl, proxy_options); 57 | } 58 | 59 | int aws_mqtt_client_connection_set_host_resolution_options( 60 | struct aws_mqtt_client_connection *connection, 61 | const struct aws_host_resolution_config *host_resolution_config) { 62 | 63 | return (*connection->vtable->set_host_resolution_options_fn)(connection->impl, host_resolution_config); 64 | } 65 | 66 | int aws_mqtt_client_connection_set_reconnect_timeout( 67 | struct aws_mqtt_client_connection *connection, 68 | uint64_t min_timeout, 69 | uint64_t max_timeout) { 70 | 71 | return (*connection->vtable->set_reconnect_timeout_fn)(connection->impl, min_timeout, max_timeout); 72 | } 73 | 74 | int aws_mqtt_client_connection_set_connection_result_handlers( 75 | struct aws_mqtt_client_connection *connection, 76 | aws_mqtt_client_on_connection_success_fn *on_connection_success, 77 | void *on_connection_success_ud, 78 | aws_mqtt_client_on_connection_failure_fn *on_connection_failure, 79 | void *on_connection_failure_ud) { 80 | 81 | return (*connection->vtable->set_connection_result_handlers)( 82 | connection->impl, 83 | on_connection_success, 84 | on_connection_success_ud, 85 | on_connection_failure, 86 | on_connection_failure_ud); 87 | } 88 | 89 | int aws_mqtt_client_connection_set_connection_interruption_handlers( 90 | struct aws_mqtt_client_connection *connection, 91 | aws_mqtt_client_on_connection_interrupted_fn *on_interrupted, 92 | void *on_interrupted_ud, 93 | aws_mqtt_client_on_connection_resumed_fn *on_resumed, 94 | void *on_resumed_ud) { 95 | 96 | return (*connection->vtable->set_connection_interruption_handlers_fn)( 97 | connection->impl, on_interrupted, on_interrupted_ud, on_resumed, on_resumed_ud); 98 | } 99 | 100 | int aws_mqtt_client_connection_set_connection_closed_handler( 101 | struct aws_mqtt_client_connection *connection, 102 | aws_mqtt_client_on_connection_closed_fn *on_closed, 103 | void *on_closed_ud) { 104 | 105 | return (*connection->vtable->set_connection_closed_handler_fn)(connection->impl, on_closed, on_closed_ud); 106 | } 107 | 108 | int aws_mqtt_client_connection_set_on_any_publish_handler( 109 | struct aws_mqtt_client_connection *connection, 110 | aws_mqtt_client_publish_received_fn *on_any_publish, 111 | void *on_any_publish_ud) { 112 | 113 | return (*connection->vtable->set_on_any_publish_handler_fn)(connection->impl, on_any_publish, on_any_publish_ud); 114 | } 115 | 116 | int aws_mqtt_client_connection_set_connection_termination_handler( 117 | struct aws_mqtt_client_connection *connection, 118 | aws_mqtt_client_on_connection_termination_fn *on_termination, 119 | void *on_termination_ud) { 120 | 121 | return (*connection->vtable->set_connection_termination_handler_fn)( 122 | connection->impl, on_termination, on_termination_ud); 123 | } 124 | 125 | int aws_mqtt_client_connection_connect( 126 | struct aws_mqtt_client_connection *connection, 127 | const struct aws_mqtt_connection_options *connection_options) { 128 | 129 | return (*connection->vtable->connect_fn)(connection->impl, connection_options); 130 | } 131 | 132 | int aws_mqtt_client_connection_reconnect( 133 | struct aws_mqtt_client_connection *connection, 134 | aws_mqtt_client_on_connection_complete_fn *on_connection_complete, 135 | void *userdata) { 136 | 137 | return (*connection->vtable->reconnect_fn)(connection->impl, on_connection_complete, userdata); 138 | } 139 | 140 | int aws_mqtt_client_connection_disconnect( 141 | struct aws_mqtt_client_connection *connection, 142 | aws_mqtt_client_on_disconnect_fn *on_disconnect, 143 | void *userdata) { 144 | 145 | return (*connection->vtable->disconnect_fn)(connection->impl, on_disconnect, userdata); 146 | } 147 | 148 | uint16_t aws_mqtt_client_connection_subscribe_multiple( 149 | struct aws_mqtt_client_connection *connection, 150 | const struct aws_array_list *topic_filters, 151 | aws_mqtt_suback_multi_fn *on_suback, 152 | void *on_suback_ud) { 153 | 154 | return (*connection->vtable->subscribe_multiple_fn)(connection->impl, topic_filters, on_suback, on_suback_ud); 155 | } 156 | 157 | uint16_t aws_mqtt_client_connection_subscribe( 158 | struct aws_mqtt_client_connection *connection, 159 | const struct aws_byte_cursor *topic_filter, 160 | enum aws_mqtt_qos qos, 161 | aws_mqtt_client_publish_received_fn *on_publish, 162 | void *on_publish_ud, 163 | aws_mqtt_userdata_cleanup_fn *on_ud_cleanup, 164 | aws_mqtt_suback_fn *on_suback, 165 | void *on_suback_ud) { 166 | 167 | return (*connection->vtable->subscribe_fn)( 168 | connection->impl, topic_filter, qos, on_publish, on_publish_ud, on_ud_cleanup, on_suback, on_suback_ud); 169 | } 170 | 171 | uint16_t aws_mqtt_resubscribe_existing_topics( 172 | struct aws_mqtt_client_connection *connection, 173 | aws_mqtt_suback_multi_fn *on_suback, 174 | void *on_suback_ud) { 175 | 176 | return (*connection->vtable->resubscribe_existing_topics_fn)(connection->impl, on_suback, on_suback_ud); 177 | } 178 | 179 | uint16_t aws_mqtt_client_connection_unsubscribe( 180 | struct aws_mqtt_client_connection *connection, 181 | const struct aws_byte_cursor *topic_filter, 182 | aws_mqtt_op_complete_fn *on_unsuback, 183 | void *on_unsuback_ud) { 184 | 185 | return (*connection->vtable->unsubscribe_fn)(connection->impl, topic_filter, on_unsuback, on_unsuback_ud); 186 | } 187 | 188 | uint16_t aws_mqtt_client_connection_publish( 189 | struct aws_mqtt_client_connection *connection, 190 | const struct aws_byte_cursor *topic, 191 | enum aws_mqtt_qos qos, 192 | bool retain, 193 | const struct aws_byte_cursor *payload, 194 | aws_mqtt_op_complete_fn *on_complete, 195 | void *userdata) { 196 | 197 | return (*connection->vtable->publish_fn)(connection->impl, topic, qos, retain, payload, on_complete, userdata); 198 | } 199 | 200 | int aws_mqtt_client_connection_get_stats( 201 | struct aws_mqtt_client_connection *connection, 202 | struct aws_mqtt_connection_operation_statistics *stats) { 203 | 204 | return (*connection->vtable->get_stats_fn)(connection->impl, stats); 205 | } 206 | 207 | enum aws_mqtt311_impl_type aws_mqtt_client_connection_get_impl_type( 208 | const struct aws_mqtt_client_connection *connection) { 209 | return (*connection->vtable->get_impl_type)(connection->impl); 210 | } 211 | 212 | uint64_t aws_mqtt_hash_uint16_t(const void *item) { 213 | return *(uint16_t *)item; 214 | } 215 | 216 | bool aws_mqtt_compare_uint16_t_eq(const void *a, const void *b) { 217 | return *(uint16_t *)a == *(uint16_t *)b; 218 | } 219 | 220 | bool aws_mqtt_byte_cursor_hash_equality(const void *a, const void *b) { 221 | const struct aws_byte_cursor *a_cursor = a; 222 | const struct aws_byte_cursor *b_cursor = b; 223 | 224 | return aws_byte_cursor_eq(a_cursor, b_cursor); 225 | } 226 | 227 | struct aws_event_loop *aws_mqtt_client_connection_get_event_loop(const struct aws_mqtt_client_connection *connection) { 228 | return (*connection->vtable->get_event_loop)(connection->impl); 229 | } 230 | -------------------------------------------------------------------------------- /source/mqtt311_decoder.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | static void s_aws_mqtt311_decoder_reset(struct aws_mqtt311_decoder *decoder) { 11 | decoder->state = AWS_MDST_READ_FIRST_BYTE; 12 | decoder->total_packet_length = 0; 13 | aws_byte_buf_reset(&decoder->packet_buffer, false); 14 | } 15 | 16 | void aws_mqtt311_decoder_init( 17 | struct aws_mqtt311_decoder *decoder, 18 | struct aws_allocator *allocator, 19 | const struct aws_mqtt311_decoder_options *options) { 20 | 21 | aws_byte_buf_init(&decoder->packet_buffer, allocator, 5); 22 | decoder->config = *options; 23 | 24 | s_aws_mqtt311_decoder_reset(decoder); 25 | } 26 | 27 | void aws_mqtt311_decoder_clean_up(struct aws_mqtt311_decoder *decoder) { 28 | aws_byte_buf_clean_up(&decoder->packet_buffer); 29 | } 30 | 31 | static void s_aws_mqtt311_decoder_reset_for_new_packet(struct aws_mqtt311_decoder *decoder) { 32 | if (decoder->state != AWS_MDST_PROTOCOL_ERROR) { 33 | s_aws_mqtt311_decoder_reset(decoder); 34 | } 35 | } 36 | 37 | enum aws_mqtt311_decoding_directive { AWS_MDD_CONTINUE, AWS_MDD_OUT_OF_DATA, AWS_MDD_PROTOCOL_ERROR }; 38 | 39 | static enum aws_mqtt311_decoding_directive aws_result_to_mqtt311_decoding_directive(int result) { 40 | return (result == AWS_OP_SUCCESS) ? AWS_MDD_CONTINUE : AWS_MDD_PROTOCOL_ERROR; 41 | } 42 | 43 | static int s_aws_mqtt311_decoder_safe_packet_handle( 44 | struct aws_mqtt311_decoder *decoder, 45 | enum aws_mqtt_packet_type packet_type, 46 | struct aws_byte_cursor packet_cursor) { 47 | packet_handler_fn *handler = decoder->config.packet_handlers->handlers_by_packet_type[packet_type]; 48 | if (handler != NULL) { 49 | return handler(packet_cursor, decoder->config.handler_user_data); 50 | } else { 51 | return aws_raise_error(AWS_ERROR_MQTT_PROTOCOL_ERROR); 52 | } 53 | } 54 | 55 | static enum aws_mqtt311_decoding_directive s_handle_decoder_read_first_byte( 56 | struct aws_mqtt311_decoder *decoder, 57 | struct aws_byte_cursor *data) { 58 | AWS_FATAL_ASSERT(decoder->packet_buffer.len == 0); 59 | if (data->len == 0) { 60 | return AWS_MDD_OUT_OF_DATA; 61 | } 62 | 63 | /* 64 | * Do a greedy check to see if the whole MQTT packet is contained within the received data. If it is, decode it 65 | * directly from the incoming data cursor without buffering it first. Otherwise, the packet is fragmented 66 | * across multiple received data calls, and so we must use the packet buffer and copy everything first via the 67 | * full decoder state machine. 68 | * 69 | * A corollary of this is that the decoder is only ever in the AWS_MDST_READ_REMAINING_LENGTH or AWS_MDST_READ_BODY 70 | * states if the current MQTT packet was received in a fragmented manner. 71 | */ 72 | struct aws_byte_cursor temp_cursor = *data; 73 | struct aws_mqtt_fixed_header packet_header; 74 | AWS_ZERO_STRUCT(packet_header); 75 | if (!aws_mqtt_fixed_header_decode(&temp_cursor, &packet_header) && 76 | temp_cursor.len >= packet_header.remaining_length) { 77 | 78 | /* figure out the cursor that spans the full packet */ 79 | size_t fixed_header_length = temp_cursor.ptr - data->ptr; 80 | struct aws_byte_cursor whole_packet_cursor = *data; 81 | whole_packet_cursor.len = fixed_header_length + packet_header.remaining_length; 82 | 83 | /* advance the external, mutable data cursor to the start of the next packet */ 84 | aws_byte_cursor_advance(data, whole_packet_cursor.len); 85 | 86 | /* 87 | * if this fails, the decoder goes into an error state. If it succeeds we'll loop again into the same state 88 | * because we'll be back at the beginning of the next packet (if it exists). 89 | */ 90 | enum aws_mqtt_packet_type packet_type = aws_mqtt_get_packet_type(whole_packet_cursor.ptr); 91 | return aws_result_to_mqtt311_decoding_directive( 92 | s_aws_mqtt311_decoder_safe_packet_handle(decoder, packet_type, whole_packet_cursor)); 93 | } 94 | 95 | /* 96 | * The packet is fragmented, spanning more than this io message. So we buffer it and use the 97 | * simple state machine to decode. 98 | */ 99 | uint8_t byte = *data->ptr; 100 | aws_byte_cursor_advance(data, 1); 101 | aws_byte_buf_append_byte_dynamic(&decoder->packet_buffer, byte); 102 | 103 | decoder->state = AWS_MDST_READ_REMAINING_LENGTH; 104 | 105 | return AWS_MDD_CONTINUE; 106 | } 107 | 108 | static enum aws_mqtt311_decoding_directive s_handle_decoder_read_remaining_length( 109 | struct aws_mqtt311_decoder *decoder, 110 | struct aws_byte_cursor *data) { 111 | AWS_FATAL_ASSERT(decoder->total_packet_length == 0); 112 | if (data->len == 0) { 113 | return AWS_MDD_OUT_OF_DATA; 114 | } 115 | 116 | uint8_t byte = *data->ptr; 117 | aws_byte_cursor_advance(data, 1); 118 | aws_byte_buf_append_byte_dynamic(&decoder->packet_buffer, byte); 119 | 120 | struct aws_byte_cursor vli_cursor = aws_byte_cursor_from_buf(&decoder->packet_buffer); 121 | aws_byte_cursor_advance(&vli_cursor, 1); 122 | 123 | size_t remaining_length = 0; 124 | if (aws_mqtt311_decode_remaining_length(&vli_cursor, &remaining_length) == AWS_OP_ERR) { 125 | /* anything other than a short buffer error (not enough data yet) is a terminal error */ 126 | if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) { 127 | return AWS_MDD_CONTINUE; 128 | } else { 129 | return AWS_MDD_PROTOCOL_ERROR; 130 | } 131 | } 132 | 133 | /* 134 | * If we successfully decoded a variable-length integer, we now know exactly how many bytes we need to receive in 135 | * order to have the full packet. 136 | */ 137 | decoder->total_packet_length = remaining_length + decoder->packet_buffer.len; 138 | AWS_FATAL_ASSERT(decoder->total_packet_length > 0); 139 | decoder->state = AWS_MDST_READ_BODY; 140 | 141 | return AWS_MDD_CONTINUE; 142 | } 143 | 144 | static enum aws_mqtt311_decoding_directive s_handle_decoder_read_body( 145 | struct aws_mqtt311_decoder *decoder, 146 | struct aws_byte_cursor *data) { 147 | AWS_FATAL_ASSERT(decoder->total_packet_length > 0); 148 | 149 | size_t buffer_length = decoder->packet_buffer.len; 150 | size_t amount_to_read = aws_min_size(decoder->total_packet_length - buffer_length, data->len); 151 | 152 | struct aws_byte_cursor copy_cursor = aws_byte_cursor_advance(data, amount_to_read); 153 | aws_byte_buf_append_dynamic(&decoder->packet_buffer, ©_cursor); 154 | 155 | if (decoder->packet_buffer.len == decoder->total_packet_length) { 156 | 157 | /* We have the full packet in the scratch buffer, invoke the correct handler to decode and process it */ 158 | struct aws_byte_cursor packet_data = aws_byte_cursor_from_buf(&decoder->packet_buffer); 159 | enum aws_mqtt_packet_type packet_type = aws_mqtt_get_packet_type(packet_data.ptr); 160 | if (s_aws_mqtt311_decoder_safe_packet_handle(decoder, packet_type, packet_data) == AWS_OP_ERR) { 161 | return AWS_MDD_PROTOCOL_ERROR; 162 | } 163 | 164 | s_aws_mqtt311_decoder_reset_for_new_packet(decoder); 165 | return AWS_MDD_CONTINUE; 166 | } 167 | 168 | return AWS_MDD_OUT_OF_DATA; 169 | } 170 | 171 | int aws_mqtt311_decoder_on_bytes_received(struct aws_mqtt311_decoder *decoder, struct aws_byte_cursor data) { 172 | struct aws_byte_cursor data_cursor = data; 173 | 174 | enum aws_mqtt311_decoding_directive decode_directive = AWS_MDD_CONTINUE; 175 | while (decode_directive == AWS_MDD_CONTINUE) { 176 | switch (decoder->state) { 177 | case AWS_MDST_READ_FIRST_BYTE: 178 | decode_directive = s_handle_decoder_read_first_byte(decoder, &data_cursor); 179 | break; 180 | 181 | case AWS_MDST_READ_REMAINING_LENGTH: 182 | decode_directive = s_handle_decoder_read_remaining_length(decoder, &data_cursor); 183 | break; 184 | 185 | case AWS_MDST_READ_BODY: 186 | decode_directive = s_handle_decoder_read_body(decoder, &data_cursor); 187 | break; 188 | 189 | default: 190 | decode_directive = AWS_MDD_PROTOCOL_ERROR; 191 | break; 192 | } 193 | 194 | /* 195 | * Protocol error is a terminal failure state until aws_mqtt311_decoder_reset_for_new_connection() is called. 196 | */ 197 | if (decode_directive == AWS_MDD_PROTOCOL_ERROR) { 198 | decoder->state = AWS_MDST_PROTOCOL_ERROR; 199 | if (aws_last_error() == AWS_ERROR_SUCCESS) { 200 | aws_raise_error(AWS_ERROR_MQTT_PROTOCOL_ERROR); 201 | } 202 | return AWS_OP_ERR; 203 | } 204 | } 205 | 206 | return AWS_OP_SUCCESS; 207 | } 208 | 209 | void aws_mqtt311_decoder_reset_for_new_connection(struct aws_mqtt311_decoder *decoder) { 210 | s_aws_mqtt311_decoder_reset(decoder); 211 | } 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS C MQTT 2 | 3 | C99 implementation of the MQTT 3.1.1 and MQTT 5 specifications. 4 | 5 | ## License 6 | 7 | This library is licensed under the Apache 2.0 License. 8 | 9 | ## Usage 10 | 11 | ### Building 12 | 13 | CMake 3.9+ is required to build. 14 | 15 | `` must be an absolute path in the following instructions. 16 | 17 | #### Linux-Only Dependencies 18 | 19 | If you are building on Linux, you will need to build aws-lc and s2n-tls first. 20 | 21 | ``` 22 | git clone git@github.com:awslabs/aws-lc.git 23 | cmake -S aws-lc -B aws-lc/build -DCMAKE_INSTALL_PREFIX= 24 | cmake --build aws-lc/build --target install 25 | 26 | git clone git@github.com:aws/s2n-tls.git 27 | cmake -S s2n-tls -B s2n-tls/build -DCMAKE_INSTALL_PREFIX= -DCMAKE_PREFIX_PATH= 28 | cmake --build s2n-tls/build --target install 29 | ``` 30 | 31 | #### Building aws-c-mqtt and Remaining Dependencies 32 | 33 | ``` 34 | git clone git@github.com:awslabs/aws-c-common.git 35 | cmake -S aws-c-common -B aws-c-common/build -DCMAKE_INSTALL_PREFIX= 36 | cmake --build aws-c-common/build --target install 37 | 38 | git clone git@github.com:awslabs/aws-c-cal.git 39 | cmake -S aws-c-cal -B aws-c-cal/build -DCMAKE_INSTALL_PREFIX= -DCMAKE_PREFIX_PATH= 40 | cmake --build aws-c-cal/build --target install 41 | 42 | git clone git@github.com:awslabs/aws-c-io.git 43 | cmake -S aws-c-io -B aws-c-io/build -DCMAKE_INSTALL_PREFIX= -DCMAKE_PREFIX_PATH= 44 | cmake --build aws-c-io/build --target install 45 | 46 | git clone git@github.com:awslabs/aws-c-compression.git 47 | cmake -S aws-c-compression -B aws-c-compression/build -DCMAKE_INSTALL_PREFIX= -DCMAKE_PREFIX_PATH= 48 | cmake --build aws-c-compression/build --target install 49 | 50 | git clone git@github.com:awslabs/aws-c-http.git 51 | cmake -S aws-c-http -B aws-c-http/build -DCMAKE_INSTALL_PREFIX= -DCMAKE_PREFIX_PATH= 52 | cmake --build aws-c-http/build --target install 53 | 54 | git clone git@github.com:awslabs/aws-c-mqtt.git 55 | cmake -S aws-c-mqtt -B aws-c-mqtt/build -DCMAKE_INSTALL_PREFIX= -DCMAKE_PREFIX_PATH= 56 | cmake --build aws-c-mqtt/build --target install 57 | ``` 58 | 59 | ### Overview 60 | 61 | This library contains an MQTT implementation that is simple and easy to use, but also quite powerful and low on 62 | unnecessary copies. Here is a general overview of the API: 63 | 64 | ### `struct aws_mqtt_client;` 65 | 66 | `aws_mqtt_client` is meant to be created once per application to pool common resources required for opening MQTT 67 | connections. The instance does not need to be allocated, and may be managed by the user. 68 | 69 | ```c 70 | int aws_mqtt_client_init( 71 | struct aws_mqtt_client *client, 72 | struct aws_allocator *allocator, 73 | struct aws_event_loop_group *elg); 74 | ``` 75 | Initializes an instance of `aws_mqtt_client` with the required parameters. 76 | * `client` is effectively the `this` parameter. 77 | * `allocator` will be used to initialize the client (note that the client itself is NOT allocated). 78 | *This resource must outlive `client`*. 79 | * `bootstrap` will be used to initiate new socket connections MQTT. 80 | *This resource must outlive `client`*. 81 | See [aws-c-io][aws-c-io] for more information about `aws_client_bootstrap`. 82 | 83 | ```c 84 | void aws_mqtt_client_clean_up(struct aws_mqtt_client *client); 85 | ``` 86 | Cleans up a client and frees all owned resources. 87 | 88 | **NOTE**: DO NOT CALL THIS FUNCTION UNTIL ALL OUTSTANDING CONNECTIONS ARE CLOSED. 89 | 90 | ### `struct aws_mqtt_client_connection;` 91 | 92 | ```c 93 | struct aws_mqtt_client_connection *aws_mqtt_client_connection_new( 94 | struct aws_mqtt_client *client, 95 | struct aws_mqtt_client_connection_callbacks callbacks, 96 | const struct aws_byte_cursor *host_name, 97 | uint16_t port, 98 | struct aws_socket_options *socket_options, 99 | struct aws_tls_ctx_options *tls_options); 100 | ``` 101 | Allocates and initializes a new connection object (does NOT actually connect). You may use the returned object to 102 | configure connection parameters, and then call `aws_mqtt_client_connection_connect` to actually open the connection. 103 | * `client` is required in order to use an existing DNS resolver, event loop group, and allocator. 104 | * `callbacks` provides the connection-level (not operation level) callbacks and the userdata to be given back. 105 | * `host_name` lists the end point to connect to. This may be a DNS address or an IP address. 106 | *This resource may be freed immediately after return.* 107 | * `port` the port to connect to on `host_name`. 108 | * `socket_options` describes how to open the connection. 109 | See [aws-c-io][aws-c-io] for more information about `aws_socket_options`. 110 | * `tls_options` provides TLS credentials to connect with. Pass `NULL` to not use TLS (**NOT RECOMMENDED**). 111 | See [aws-c-io][aws-c-io] for more information about `aws_tls_ctx_options`. 112 | 113 | ```c 114 | void aws_mqtt_client_connection_destroy(struct aws_mqtt_client_connection *connection); 115 | ``` 116 | Destroys a connection and frees all outstanding resources. 117 | 118 | **NOTE**: DO NOT CALL THIS FUNCTION UNTIL THE CONNECTION IS CLOSED. 119 | 120 | ```c 121 | int aws_mqtt_client_connection_set_will( 122 | struct aws_mqtt_client_connection *connection, 123 | const struct aws_byte_cursor *topic, 124 | enum aws_mqtt_qos qos, 125 | bool retain, 126 | const struct aws_byte_cursor *payload); 127 | ``` 128 | Sets the last will and testament to be distributed by the server upon client disconnection. Must be called before 129 | `aws_mqtt_client_connection_connect`. See `aws_mqtt_client_connection_publish` for information on the parameters. 130 | `topic` and `payload` must persist past the call to `aws_mqtt_client_connection_connect`. 131 | 132 | ```c 133 | int aws_mqtt_client_connection_set_login( 134 | struct aws_mqtt_client_connection *connection, 135 | const struct aws_byte_cursor *username, 136 | const struct aws_byte_cursor *password); 137 | ``` 138 | Sets the username and password to be sent to the server on connection. Must be called before 139 | `aws_mqtt_client_connection_connect`. `username` and `password` must persist past the call to 140 | `aws_mqtt_client_connection_connect`. 141 | 142 | ```c 143 | int aws_mqtt_client_connection_set_reconnect_timeout( 144 | struct aws_mqtt_client_connection *connection, 145 | uint64_t min_timeout, 146 | uint64_t max_timeout); 147 | ``` 148 | Sets the minimum and maximum reconnect timeouts. The time between reconnect attempts will start at min and multipy by 2 149 | until max is reached. 150 | 151 | ```c 152 | int aws_mqtt_client_connection_connect( 153 | struct aws_mqtt_client_connection *connection, 154 | const struct aws_byte_cursor *client_id, 155 | bool clean_session, 156 | uint16_t keep_alive_time); 157 | ``` 158 | Connects to the remote endpoint. The parameters here are set in the MQTT CONNECT packet directly. `client_id` must persist until the `on_connack` connection callback is called. 159 | 160 | ```c 161 | int aws_mqtt_client_connection_disconnect(struct aws_mqtt_client_connection *connection); 162 | ``` 163 | Closes an open connection. Does not clean up any resources, that's to be done by `aws_mqtt_client_connection_destroy`, 164 | probably from the `on_disconnected` connection callback. 165 | 166 | ```c 167 | uint16_t aws_mqtt_client_connection_subscribe_single( 168 | struct aws_mqtt_client_connection *connection, 169 | const struct aws_byte_cursor *topic_filter, 170 | enum aws_mqtt_qos qos, 171 | aws_mqtt_client_publish_received_fn *on_publish, 172 | void *on_publish_ud, 173 | aws_mqtt_suback_single_fn *on_suback, 174 | void *on_suback_ud); 175 | ``` 176 | Subscribes to the topic filter given with the given QoS. `on_publish` will be called whenever a packet matching 177 | `topic_filter` arrives. `on_suback` will be called when the SUBACK packet has been received. `topic_filter` must persist until `on_suback` is called. The packet_id of the SUBSCRIBE packet will be returned, or 0 on error. 178 | 179 | ```c 180 | uint16_t aws_mqtt_client_connection_unsubscribe( 181 | struct aws_mqtt_client_connection *connection, 182 | const struct aws_byte_cursor *topic_filter, 183 | aws_mqtt_op_complete_fn *on_unsuback, 184 | void *on_unsuback_ud); 185 | ``` 186 | Unsubscribes from the topic filter given. `topic_filter` must persist until `on_unsuback` is called. The packet_id of 187 | the UNSUBSCRIBE packet will be returned, or 0 on error. 188 | 189 | ```c 190 | uint16_t aws_mqtt_client_connection_publish( 191 | struct aws_mqtt_client_connection *connection, 192 | const struct aws_byte_cursor *topic, 193 | enum aws_mqtt_qos qos, 194 | bool retain, 195 | const struct aws_byte_cursor *payload, 196 | aws_mqtt_op_complete_fn *on_complete, 197 | void *userdata); 198 | ``` 199 | Publish a payload to the topic specified. For QoS 0, `on_complete` will be called as soon as the packet is sent over 200 | the wire. For QoS 1, as soon as PUBACK comes back. For QoS 2, PUBCOMP. `topic` and `payload` must persist until 201 | `on_complete`. 202 | 203 | ```c 204 | int aws_mqtt_client_connection_ping(struct aws_mqtt_client_connection *connection); 205 | ``` 206 | Sends a PINGREQ packet to the server. 207 | 208 | [aws-c-io]: https://github.com/awslabs/aws-c-io 209 | -------------------------------------------------------------------------------- /tests/v5/mqtt5_testing_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_MQTT5_TESTING_UTILS_H 2 | #define MQTT_MQTT5_TESTING_UTILS_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | struct aws_event_loop_group; 20 | 21 | struct aws_mqtt5_mock_server_packet_record { 22 | struct aws_allocator *storage_allocator; 23 | 24 | uint64_t timestamp; 25 | 26 | void *packet_storage; 27 | enum aws_mqtt5_packet_type packet_type; 28 | }; 29 | 30 | struct aws_mqtt5_lifecycle_event_record { 31 | struct aws_allocator *allocator; 32 | 33 | uint64_t timestamp; 34 | 35 | struct aws_mqtt5_client_lifecycle_event event; 36 | 37 | struct aws_mqtt5_negotiated_settings settings_storage; 38 | struct aws_mqtt5_packet_disconnect_storage disconnect_storage; 39 | struct aws_mqtt5_packet_connack_storage connack_storage; 40 | }; 41 | 42 | struct aws_mqtt5_server_mock_connection_context { 43 | struct aws_allocator *allocator; 44 | 45 | struct aws_channel *channel; 46 | struct aws_channel_handler handler; 47 | struct aws_channel_slot *slot; 48 | 49 | struct aws_mqtt5_encoder_function_table encoding_table; 50 | struct aws_mqtt5_encoder encoder; 51 | 52 | struct aws_mqtt5_decoder_function_table decoding_table; 53 | struct aws_mqtt5_decoder decoder; 54 | struct aws_mqtt5_inbound_topic_alias_resolver inbound_alias_resolver; 55 | 56 | struct aws_mqtt5_client_mock_test_fixture *test_fixture; 57 | 58 | struct aws_task service_task; 59 | }; 60 | 61 | typedef int(aws_mqtt5_on_mock_server_packet_received_fn)( 62 | void *packet_view, 63 | struct aws_mqtt5_server_mock_connection_context *connection, 64 | void *packet_received_user_data); 65 | 66 | typedef void( 67 | aws_mqtt5_mock_server_service_fn)(struct aws_mqtt5_server_mock_connection_context *mock_server, void *user_data); 68 | 69 | struct aws_mqtt5_mock_server_vtable { 70 | aws_mqtt5_on_mock_server_packet_received_fn *packet_handlers[16]; 71 | aws_mqtt5_mock_server_service_fn *service_task_fn; 72 | }; 73 | 74 | struct aws_mqtt5_client_mqtt5_mock_test_fixture_options { 75 | struct aws_mqtt5_client_options *client_options; 76 | const struct aws_mqtt5_mock_server_vtable *server_function_table; 77 | 78 | void *mock_server_user_data; 79 | }; 80 | 81 | struct aws_mqtt5_client_mock_test_fixture { 82 | struct aws_allocator *allocator; 83 | 84 | struct aws_event_loop_group *client_elg; 85 | struct aws_event_loop_group *server_elg; 86 | struct aws_host_resolver *host_resolver; 87 | struct aws_client_bootstrap *client_bootstrap; 88 | struct aws_server_bootstrap *server_bootstrap; 89 | struct aws_socket_endpoint endpoint; 90 | struct aws_socket_options socket_options; 91 | struct aws_socket *listener; 92 | struct aws_channel *server_channel; 93 | 94 | const struct aws_mqtt5_mock_server_vtable *server_function_table; 95 | void *mock_server_user_data; 96 | 97 | struct aws_mqtt5_client_vtable client_vtable; 98 | struct aws_mqtt5_client *client; 99 | 100 | aws_mqtt5_client_connection_event_callback_fn *original_lifecycle_event_handler; 101 | void *original_lifecycle_event_handler_user_data; 102 | 103 | uint16_t maximum_inbound_topic_aliases; 104 | 105 | struct aws_mutex lock; 106 | struct aws_condition_variable signal; 107 | struct aws_array_list server_received_packets; 108 | struct aws_array_list lifecycle_events; 109 | struct aws_array_list client_states; 110 | struct aws_array_list client_statistics; 111 | bool listener_destroyed; 112 | bool subscribe_complete; 113 | bool disconnect_completion_callback_invoked; 114 | bool client_terminated; 115 | uint32_t total_pubacks_received; 116 | uint32_t publishes_received; 117 | uint32_t successful_pubacks_received; 118 | uint32_t timeouts_received; 119 | 120 | uint32_t server_maximum_inflight_publishes; 121 | uint32_t server_current_inflight_publishes; 122 | }; 123 | 124 | struct mqtt5_client_test_options { 125 | struct aws_mqtt5_client_topic_alias_options topic_aliasing_options; 126 | struct aws_mqtt5_packet_connect_view connect_options; 127 | struct aws_mqtt5_client_options client_options; 128 | struct aws_mqtt5_mock_server_vtable server_function_table; 129 | }; 130 | 131 | struct aws_mqtt5_mock_server_reconnect_state { 132 | size_t required_connection_count_threshold; 133 | 134 | size_t connection_attempts; 135 | uint64_t connect_timestamp; 136 | 137 | uint64_t successful_connection_disconnect_delay_ms; 138 | }; 139 | 140 | int aws_mqtt5_test_verify_user_properties_raw( 141 | size_t property_count, 142 | const struct aws_mqtt5_user_property *properties, 143 | size_t expected_count, 144 | const struct aws_mqtt5_user_property *expected_properties); 145 | 146 | void aws_mqtt5_encode_init_testing_function_table(struct aws_mqtt5_encoder_function_table *function_table); 147 | 148 | void aws_mqtt5_decode_init_testing_function_table(struct aws_mqtt5_decoder_function_table *function_table); 149 | 150 | int aws_mqtt5_client_mock_test_fixture_init( 151 | struct aws_mqtt5_client_mock_test_fixture *test_fixture, 152 | struct aws_allocator *allocator, 153 | struct aws_mqtt5_client_mqtt5_mock_test_fixture_options *options); 154 | 155 | void aws_mqtt5_client_mock_test_fixture_clean_up(struct aws_mqtt5_client_mock_test_fixture *test_fixture); 156 | 157 | bool aws_mqtt5_client_test_are_packets_equal( 158 | enum aws_mqtt5_packet_type packet_type, 159 | void *lhs_packet_storage, 160 | void *rhs_packet_storage); 161 | 162 | size_t aws_mqtt5_linked_list_length(struct aws_linked_list *list); 163 | 164 | void aws_mqtt5_client_test_init_default_options(struct mqtt5_client_test_options *test_options); 165 | 166 | void aws_wait_for_connected_lifecycle_event(struct aws_mqtt5_client_mock_test_fixture *test_context); 167 | void aws_wait_for_stopped_lifecycle_event(struct aws_mqtt5_client_mock_test_fixture *test_context); 168 | 169 | int aws_verify_received_packet_sequence( 170 | struct aws_mqtt5_client_mock_test_fixture *test_context, 171 | struct aws_mqtt5_mock_server_packet_record *expected_packets, 172 | size_t expected_packets_count); 173 | 174 | int aws_mqtt5_mock_server_handle_connect_always_fail( 175 | void *packet, 176 | struct aws_mqtt5_server_mock_connection_context *connection, 177 | void *user_data); 178 | 179 | void aws_mqtt5_wait_for_n_lifecycle_events( 180 | struct aws_mqtt5_client_mock_test_fixture *test_context, 181 | enum aws_mqtt5_client_lifecycle_event_type type, 182 | size_t count); 183 | 184 | int aws_verify_reconnection_exponential_backoff_timestamps(struct aws_mqtt5_client_mock_test_fixture *test_fixture); 185 | 186 | int aws_verify_client_state_sequence( 187 | struct aws_mqtt5_client_mock_test_fixture *test_context, 188 | enum aws_mqtt5_client_state *expected_states, 189 | size_t expected_states_count); 190 | 191 | int aws_mqtt5_mock_server_handle_connect_always_succeed( 192 | void *packet, 193 | struct aws_mqtt5_server_mock_connection_context *connection, 194 | void *user_data); 195 | 196 | int aws_mqtt5_mock_server_send_packet( 197 | struct aws_mqtt5_server_mock_connection_context *connection, 198 | enum aws_mqtt5_packet_type packet_type, 199 | void *packet); 200 | 201 | int aws_mqtt5_mock_server_handle_connect_succeed_on_nth( 202 | void *packet, 203 | struct aws_mqtt5_server_mock_connection_context *connection, 204 | void *user_data); 205 | 206 | int aws_mqtt5_mock_server_handle_publish_puback( 207 | void *packet, 208 | struct aws_mqtt5_server_mock_connection_context *connection, 209 | void *user_data); 210 | 211 | int aws_mqtt5_mock_server_handle_publish_puback_and_forward( 212 | void *packet, 213 | struct aws_mqtt5_server_mock_connection_context *connection, 214 | void *user_data); 215 | 216 | int aws_mqtt5_mock_server_handle_unsubscribe_unsuback_success( 217 | void *packet, 218 | struct aws_mqtt5_server_mock_connection_context *connection, 219 | void *user_data); 220 | 221 | int aws_mqtt5_mock_server_send_packet( 222 | struct aws_mqtt5_server_mock_connection_context *connection, 223 | enum aws_mqtt5_packet_type packet_type, 224 | void *packet); 225 | 226 | int aws_mqtt5_server_send_suback_on_subscribe( 227 | void *packet, 228 | struct aws_mqtt5_server_mock_connection_context *connection, 229 | void *user_data); 230 | 231 | int aws_mqtt5_mock_server_handle_connect_honor_session_unconditional( 232 | void *packet, 233 | struct aws_mqtt5_server_mock_connection_context *connection, 234 | void *user_data); 235 | 236 | void aws_wait_for_n_lifecycle_events( 237 | struct aws_mqtt5_client_mock_test_fixture *test_fixture, 238 | enum aws_mqtt5_client_lifecycle_event_type event_type, 239 | size_t expected_event_count); 240 | 241 | extern const struct aws_string *g_default_client_id; 242 | 243 | #define RECONNECT_TEST_MIN_BACKOFF 500 244 | #define RECONNECT_TEST_MAX_BACKOFF 5000 245 | #define RECONNECT_TEST_BACKOFF_RESET_DELAY 5000 246 | 247 | #endif /* MQTT_MQTT5_TESTING_UTILS_H */ 248 | -------------------------------------------------------------------------------- /source/v5/rate_limiters.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | 10 | static int s_rate_limit_time_fn(const struct aws_rate_limiter_token_bucket_options *options, uint64_t *current_time) { 11 | if (options->clock_fn != NULL) { 12 | return (*options->clock_fn)(current_time); 13 | } 14 | 15 | return aws_high_res_clock_get_ticks(current_time); 16 | } 17 | 18 | int aws_rate_limiter_token_bucket_init( 19 | struct aws_rate_limiter_token_bucket *limiter, 20 | const struct aws_rate_limiter_token_bucket_options *options) { 21 | AWS_ZERO_STRUCT(*limiter); 22 | 23 | if (options->tokens_per_second == 0 || options->maximum_token_count == 0) { 24 | return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); 25 | } 26 | 27 | limiter->config = *options; 28 | 29 | aws_rate_limiter_token_bucket_reset(limiter); 30 | 31 | return AWS_OP_SUCCESS; 32 | } 33 | 34 | void aws_rate_limiter_token_bucket_reset(struct aws_rate_limiter_token_bucket *limiter) { 35 | 36 | limiter->current_token_count = 37 | aws_min_u64(limiter->config.initial_token_count, limiter->config.maximum_token_count); 38 | limiter->fractional_nanos = 0; 39 | limiter->fractional_nano_tokens = 0; 40 | 41 | uint64_t now = 0; 42 | AWS_FATAL_ASSERT(s_rate_limit_time_fn(&limiter->config, &now) == AWS_OP_SUCCESS); 43 | 44 | limiter->last_service_time = now; 45 | } 46 | 47 | static void s_regenerate_tokens(struct aws_rate_limiter_token_bucket *limiter) { 48 | uint64_t now = 0; 49 | AWS_FATAL_ASSERT(s_rate_limit_time_fn(&limiter->config, &now) == AWS_OP_SUCCESS); 50 | 51 | if (now <= limiter->last_service_time) { 52 | return; 53 | } 54 | 55 | uint64_t nanos_elapsed = now - limiter->last_service_time; 56 | 57 | /* 58 | * We break the regeneration calculation into two distinct steps: 59 | * (1) Perform regeneration based on whole seconds elapsed (nice and easy just multiply times the regen rate) 60 | * (2) Perform regeneration based on the remaining fraction of a second elapsed 61 | * 62 | * We do this to minimize the chances of multiplication saturation before the divide necessary to normalize to 63 | * nanos. 64 | * 65 | * In particular, by doing this, we won't see saturation unless a regeneration rate in the multi-billions is used 66 | * or elapsed_seconds is in the billions. This is similar reasoning to what we do in aws_timestamp_convert_u64. 67 | * 68 | * Additionally, we use a (sub-second) fractional counter/accumulator (fractional_nanos, fractional_nano_tokens) 69 | * in order to prevent error accumulation due to integer division rounding. 70 | */ 71 | 72 | /* break elapsed time into seconds and remainder nanos */ 73 | uint64_t remainder_nanos = 0; 74 | uint64_t elapsed_seconds = 75 | aws_timestamp_convert(nanos_elapsed, AWS_TIMESTAMP_NANOS, AWS_TIMESTAMP_SECS, &remainder_nanos); 76 | 77 | /* apply seconds-based regeneration */ 78 | uint64_t tokens_regenerated = aws_mul_u64_saturating(elapsed_seconds, limiter->config.tokens_per_second); 79 | 80 | /* apply fractional remainder regeneration */ 81 | limiter->fractional_nanos += remainder_nanos; 82 | 83 | /* fractional overflow check */ 84 | if (limiter->fractional_nanos < AWS_TIMESTAMP_NANOS) { 85 | /* 86 | * no overflow, just do the division to figure out how many tokens are represented by the updated 87 | * fractional nanos 88 | */ 89 | uint64_t new_fractional_tokens = 90 | aws_mul_u64_saturating(limiter->fractional_nanos, limiter->config.tokens_per_second) / AWS_TIMESTAMP_NANOS; 91 | 92 | /* 93 | * update token count by how much fractional tokens changed 94 | */ 95 | tokens_regenerated += new_fractional_tokens - limiter->fractional_nano_tokens; 96 | limiter->fractional_nano_tokens = new_fractional_tokens; 97 | } else { 98 | /* 99 | * overflow. In this case, update token count by the remaining tokens left to regenerate to make the 100 | * original fractional nano amount equal to one second. This is the key part (a pseudo-reset) that lets us 101 | * avoid error accumulation due to integer division rounding over time. 102 | */ 103 | tokens_regenerated += limiter->config.tokens_per_second - limiter->fractional_nano_tokens; 104 | 105 | /* 106 | * subtract off a second from the fractional part. Guaranteed to be less than a second afterwards. 107 | */ 108 | limiter->fractional_nanos -= AWS_TIMESTAMP_NANOS; 109 | 110 | /* 111 | * Calculate the new fractional nano token amount, and add them in. 112 | */ 113 | limiter->fractional_nano_tokens = 114 | aws_mul_u64_saturating(limiter->fractional_nanos, limiter->config.tokens_per_second) / AWS_TIMESTAMP_NANOS; 115 | tokens_regenerated += limiter->fractional_nano_tokens; 116 | } 117 | 118 | limiter->current_token_count = aws_add_u64_saturating(tokens_regenerated, limiter->current_token_count); 119 | if (limiter->current_token_count > limiter->config.maximum_token_count) { 120 | limiter->current_token_count = limiter->config.maximum_token_count; 121 | } 122 | 123 | limiter->last_service_time = now; 124 | } 125 | 126 | bool aws_rate_limiter_token_bucket_can_take_tokens( 127 | struct aws_rate_limiter_token_bucket *limiter, 128 | uint64_t token_count) { 129 | s_regenerate_tokens(limiter); 130 | 131 | return limiter->current_token_count >= token_count; 132 | } 133 | 134 | int aws_rate_limiter_token_bucket_take_tokens(struct aws_rate_limiter_token_bucket *limiter, uint64_t token_count) { 135 | s_regenerate_tokens(limiter); 136 | 137 | if (limiter->current_token_count < token_count) { 138 | /* TODO: correct error once seated in aws-c-common */ 139 | return aws_raise_error(AWS_ERROR_INVALID_STATE); 140 | } 141 | 142 | limiter->current_token_count -= token_count; 143 | return AWS_OP_SUCCESS; 144 | } 145 | 146 | uint64_t aws_rate_limiter_token_bucket_compute_wait_for_tokens( 147 | struct aws_rate_limiter_token_bucket *limiter, 148 | uint64_t token_count) { 149 | s_regenerate_tokens(limiter); 150 | 151 | if (limiter->current_token_count >= token_count) { 152 | return 0; 153 | } 154 | 155 | uint64_t token_rate = limiter->config.tokens_per_second; 156 | AWS_FATAL_ASSERT(limiter->fractional_nanos < AWS_TIMESTAMP_NANOS); 157 | AWS_FATAL_ASSERT(limiter->fractional_nano_tokens <= token_rate); 158 | 159 | uint64_t expected_wait = 0; 160 | 161 | uint64_t deficit = token_count - limiter->current_token_count; 162 | uint64_t remaining_fractional_tokens = token_rate - limiter->fractional_nano_tokens; 163 | 164 | if (deficit < remaining_fractional_tokens) { 165 | /* 166 | * case 1: 167 | * The token deficit is less than what will be regenerated by waiting for the fractional nanos accumulator 168 | * to reach one second's worth of time. 169 | * 170 | * In this case, base the calculation off of just a wait from fractional nanos. 171 | */ 172 | uint64_t target_fractional_tokens = aws_add_u64_saturating(deficit, limiter->fractional_nano_tokens); 173 | uint64_t remainder_wait_unnormalized = aws_mul_u64_saturating(target_fractional_tokens, AWS_TIMESTAMP_NANOS); 174 | 175 | expected_wait = remainder_wait_unnormalized / token_rate - limiter->fractional_nanos; 176 | 177 | /* If the fractional wait is itself, fractional, then add one more nano second to push us over the edge */ 178 | if (remainder_wait_unnormalized % token_rate) { 179 | ++expected_wait; 180 | } 181 | } else { 182 | /* 183 | * case 2: 184 | * The token deficit requires regeneration for a time interval at least as large as what is needed 185 | * to overflow the fractional nanos accumulator. 186 | */ 187 | 188 | /* First account for making the fractional nano accumulator exactly one second */ 189 | expected_wait = AWS_TIMESTAMP_NANOS - limiter->fractional_nanos; 190 | deficit -= remaining_fractional_tokens; 191 | 192 | /* 193 | * Now, for the remaining tokens, split into tokens from whole seconds worth of regeneration as well 194 | * as a remainder requiring a fractional regeneration 195 | */ 196 | uint64_t expected_wait_seconds = deficit / token_rate; 197 | uint64_t deficit_remainder = deficit % token_rate; 198 | 199 | /* 200 | * Account for seconds worth of waiting 201 | */ 202 | expected_wait += aws_mul_u64_saturating(expected_wait_seconds, AWS_TIMESTAMP_NANOS); 203 | 204 | /* 205 | * And finally, calculate the fractional wait to give us the last few tokens 206 | */ 207 | uint64_t remainder_wait_unnormalized = aws_mul_u64_saturating(deficit_remainder, AWS_TIMESTAMP_NANOS); 208 | expected_wait += remainder_wait_unnormalized / token_rate; 209 | 210 | /* If the fractional wait is itself, fractional, then add one more nano second to push us over the edge */ 211 | if (remainder_wait_unnormalized % token_rate) { 212 | ++expected_wait; 213 | } 214 | } 215 | 216 | return expected_wait; 217 | } 218 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'main' 7 | 8 | env: 9 | BUILDER_VERSION: v0.9.73 10 | BUILDER_SOURCE: releases 11 | BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net 12 | PACKAGE_NAME: aws-c-mqtt 13 | LINUX_BASE_IMAGE: ubuntu-18-x64 14 | RUN: ${{ github.run_id }}-${{ github.run_number }} 15 | CRT_CI_ROLE: ${{ secrets.CRT_CI_ROLE_ARN }} 16 | AWS_DEFAULT_REGION: us-east-1 17 | 18 | permissions: 19 | id-token: write # This is required for requesting the JWT 20 | 21 | jobs: 22 | linux-compat: 23 | runs-on: ubuntu-24.04 # latest 24 | strategy: 25 | matrix: 26 | image: 27 | - manylinux1-x64 28 | - manylinux1-x86 29 | - manylinux2014-x64 30 | - manylinux2014-x86 31 | - al2-x64 32 | - fedora-34-x64 33 | - opensuse-leap 34 | - rhel8-x64 35 | steps: 36 | - uses: aws-actions/configure-aws-credentials@v4 37 | with: 38 | role-to-assume: ${{ env.CRT_CI_ROLE }} 39 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 40 | # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages 41 | - name: Build ${{ env.PACKAGE_NAME }} 42 | run: | 43 | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh 44 | ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON 45 | 46 | linux-compiler-compat: 47 | runs-on: ubuntu-24.04 # latest 48 | strategy: 49 | matrix: 50 | compiler: 51 | - clang-6 52 | - clang-8 53 | - clang-9 54 | - clang-10 55 | - clang-11 56 | - clang-15 57 | - clang-17 58 | - gcc-4.8 59 | - gcc-5 60 | - gcc-6 61 | - gcc-7 62 | - gcc-8 63 | - gcc-11 64 | steps: 65 | - uses: aws-actions/configure-aws-credentials@v4 66 | with: 67 | role-to-assume: ${{ env.CRT_CI_ROLE }} 68 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 69 | # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages 70 | - name: Build ${{ env.PACKAGE_NAME }} 71 | run: | 72 | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh 73 | ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --compiler=${{ matrix.compiler }} --cmake-extra=-DASSERT_LOCK_HELD=ON 74 | 75 | clang-sanitizers: 76 | runs-on: ubuntu-24.04 # latest 77 | strategy: 78 | matrix: 79 | sanitizers: [",thread", ",address,undefined"] 80 | steps: 81 | - uses: aws-actions/configure-aws-credentials@v4 82 | with: 83 | role-to-assume: ${{ env.CRT_CI_ROLE }} 84 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 85 | # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages 86 | - name: Build ${{ env.PACKAGE_NAME }} 87 | run: | 88 | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh 89 | ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --compiler=clang-11 --cmake-extra=-DENABLE_SANITIZERS=ON --cmake-extra=-DASSERT_LOCK_HELD=ON --cmake-extra=-DSANITIZERS="${{ matrix.sanitizers }}" 90 | 91 | linux-shared-libs: 92 | runs-on: ubuntu-24.04 # latest 93 | steps: 94 | - uses: aws-actions/configure-aws-credentials@v4 95 | with: 96 | role-to-assume: ${{ env.CRT_CI_ROLE }} 97 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 98 | # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages 99 | - name: Build ${{ env.PACKAGE_NAME }} 100 | run: | 101 | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh 102 | ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DBUILD_SHARED_LIBS=ON --cmake-extra=-DASSERT_LOCK_HELD=ON 103 | 104 | windows: 105 | runs-on: windows-2025 # latest 106 | steps: 107 | - uses: aws-actions/configure-aws-credentials@v4 108 | with: 109 | role-to-assume: ${{ env.CRT_CI_ROLE }} 110 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 111 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 112 | run: | 113 | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" 114 | python builder.pyz build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON 115 | 116 | windows-vc17: 117 | runs-on: windows-2025 # latest 118 | strategy: 119 | matrix: 120 | arch: [x86, x64] 121 | steps: 122 | - uses: aws-actions/configure-aws-credentials@v4 123 | with: 124 | role-to-assume: ${{ env.CRT_CI_ROLE }} 125 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 126 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 127 | run: | 128 | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" 129 | python builder.pyz build -p ${{ env.PACKAGE_NAME }} --target windows-${{ matrix.arch }} --compiler msvc-17 --cmake-extra=-DASSERT_LOCK_HELD=ON 130 | 131 | windows-shared-libs: 132 | runs-on: windows-2025 # latest 133 | steps: 134 | - uses: aws-actions/configure-aws-credentials@v4 135 | with: 136 | role-to-assume: ${{ env.CRT_CI_ROLE }} 137 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 138 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 139 | run: | 140 | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" 141 | python builder.pyz build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DBUILD_SHARED_LIBS=ON --cmake-extra=-DASSERT_LOCK_HELD=ON 142 | 143 | windows-app-verifier: 144 | runs-on: windows-2025 # latest 145 | steps: 146 | - uses: aws-actions/configure-aws-credentials@v4 147 | with: 148 | role-to-assume: ${{ env.CRT_CI_ROLE }} 149 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 150 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 151 | run: | 152 | python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder.pyz')" 153 | python builder.pyz build -p ${{ env.PACKAGE_NAME }} run_tests=false --cmake-extra=-DBUILD_TESTING=ON 154 | - name: Run and check AppVerifier 155 | run: | 156 | python .\aws-c-mqtt\build\deps\aws-c-common\scripts\appverifier_ctest.py --build_directory .\aws-c-mqtt\build\aws-c-mqtt 157 | 158 | macos: 159 | runs-on: macos-14 # latest 160 | strategy: 161 | fail-fast: false 162 | matrix: 163 | eventloop: ["kqueue", "dispatch_queue"] 164 | steps: 165 | - uses: aws-actions/configure-aws-credentials@v4 166 | with: 167 | role-to-assume: ${{ env.CRT_CI_ROLE }} 168 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 169 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 170 | run: | 171 | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" 172 | chmod a+x builder 173 | ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DAWS_USE_APPLE_NETWORK_FRAMEWORK=${{ matrix.eventloop == 'dispatch_queue' && 'ON' || 'OFF' }} 174 | 175 | macos-x64: 176 | runs-on: macos-14-large # latest 177 | steps: 178 | - uses: aws-actions/configure-aws-credentials@v4 179 | with: 180 | role-to-assume: ${{ env.CRT_CI_ROLE }} 181 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 182 | - name: Build ${{ env.PACKAGE_NAME }} + consumers 183 | run: | 184 | python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_SOURCE }}/${{ env.BUILDER_VERSION }}/builder.pyz?run=${{ env.RUN }}', 'builder')" 185 | chmod a+x builder 186 | ./builder build -p ${{ env.PACKAGE_NAME }} --cmake-extra=-DASSERT_LOCK_HELD=ON 187 | 188 | # Test downstream repos. 189 | # This should not be required because we can run into a chicken and egg problem if there is a change that needs some fix in a downstream repo. 190 | downstream: 191 | runs-on: ubuntu-24.04 # latest 192 | steps: 193 | - uses: aws-actions/configure-aws-credentials@v4 194 | with: 195 | role-to-assume: ${{ env.CRT_CI_ROLE }} 196 | aws-region: ${{ env.AWS_DEFAULT_REGION }} 197 | # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages 198 | - name: Build ${{ env.PACKAGE_NAME }} 199 | run: | 200 | aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh 201 | ./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-${{ env.LINUX_BASE_IMAGE }} build downstream -p ${{ env.PACKAGE_NAME }} 202 | -------------------------------------------------------------------------------- /include/aws/mqtt/private/request-response/subscription_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H 2 | #define AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H 3 | 4 | /** 5 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 6 | * SPDX-License-Identifier: Apache-2.0. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | 13 | struct aws_mqtt_protocol_adapter; 14 | struct aws_protocol_adapter_connection_event; 15 | struct aws_protocol_adapter_subscription_event; 16 | 17 | /* 18 | * Describes a change to the state of a request operation subscription 19 | */ 20 | enum aws_rr_subscription_event_type { 21 | 22 | /* 23 | * A request subscription subscribe succeeded 24 | */ 25 | ARRSET_REQUEST_SUBSCRIBE_SUCCESS, 26 | 27 | /* 28 | * A request subscription subscribe failed 29 | */ 30 | ARRSET_REQUEST_SUBSCRIBE_FAILURE, 31 | 32 | /* 33 | * A previously successful request subscription has ended. 34 | * 35 | * Under normal circumstances this can happen when 36 | * 37 | * (1) failure to rejoin a session 38 | */ 39 | ARRSET_REQUEST_SUBSCRIPTION_ENDED, 40 | 41 | /* 42 | * A streaming subscription subscribe succeeded 43 | */ 44 | ARRSET_STREAMING_SUBSCRIPTION_ESTABLISHED, 45 | 46 | /* 47 | * The protocol client failed to rejoin a session containing a previously-established streaming subscription 48 | */ 49 | ARRSET_STREAMING_SUBSCRIPTION_LOST, 50 | 51 | /* 52 | * A streaming subscription subscribe attempt resulted in an error or reason code that the client has determined 53 | * will result in indefinite failures to subscribe. In this case, we stop attempting to resubscribe. 54 | * 55 | * Situations that can lead to this: 56 | * (1) Permission failures 57 | * (2) Invalid topic filter 58 | */ 59 | ARRSET_STREAMING_SUBSCRIPTION_HALTED, 60 | 61 | /* 62 | * A subscription has lost its last listener and can be purged 63 | * 64 | * This event is global; operation_id will always be zero. 65 | */ 66 | ARRSET_SUBSCRIPTION_EMPTY, 67 | 68 | /* 69 | * A subscription has been unsubscribed from 70 | * 71 | * This event is global; operation_id will always be zero. 72 | */ 73 | ARRSET_UNSUBSCRIBE_COMPLETE, 74 | }; 75 | 76 | struct aws_rr_subscription_status_event { 77 | enum aws_rr_subscription_event_type type; 78 | struct aws_byte_cursor topic_filter; 79 | uint64_t operation_id; 80 | }; 81 | 82 | /* 83 | * Invariant: despite being on the same thread, these callbacks must be queued as cross-thread tasks on the native 84 | * request-response client. This allows us to iterate internal collections without worrying about external 85 | * callers disrupting things by invoking APIs back on us. 86 | */ 87 | typedef void( 88 | aws_rr_subscription_status_event_callback_fn)(const struct aws_rr_subscription_status_event *event, void *userdata); 89 | 90 | struct aws_rr_subscription_manager_options { 91 | 92 | /* 93 | * Maximum number of request-response subscriptions allowed. Must be at least two. 94 | */ 95 | size_t max_request_response_subscriptions; 96 | 97 | /* 98 | * Maximum number of streaming subscriptions allowed. 99 | */ 100 | size_t max_streaming_subscriptions; 101 | 102 | /* 103 | * Ack timeout to use for all subscribe and unsubscribe operations 104 | */ 105 | uint32_t operation_timeout_seconds; 106 | 107 | aws_rr_subscription_status_event_callback_fn *subscription_status_callback; 108 | void *userdata; 109 | }; 110 | 111 | /* 112 | * The subscription manager works with the request-response client to handle subscriptions in an eager manner. 113 | * Subscription purges are checked with every client service call. Unsubscribe failures don't trigger anything special, 114 | * we'll just try again next time we look for subscription space. Subscribes are attempted on idle subscriptions 115 | * that still need them, either in response to a new operation listener or a connection resumption event. 116 | * 117 | * We only allow one subscribe or unsubscribe to be outstanding at once for a given topic. If an operation requires a 118 | * subscription while an unsubscribe is in progress the operation is blocked until the unsubscribe resolves. 119 | * 120 | * These invariants are dropped during shutdown. In that case, we immediately send unsubscribes for everything 121 | * that is not already unsubscribing. 122 | */ 123 | struct aws_rr_subscription_manager { 124 | struct aws_allocator *allocator; 125 | 126 | struct aws_rr_subscription_manager_options config; 127 | 128 | /* non-owning reference; the client is responsible for destroying this asynchronously (listener detachment) */ 129 | struct aws_mqtt_protocol_adapter *protocol_adapter; 130 | 131 | /* &aws_rr_subscription_record.topic_filter_cursor -> aws_rr_subscription_record * */ 132 | struct aws_hash_table subscriptions; 133 | 134 | bool is_protocol_client_connected; 135 | }; 136 | 137 | enum aws_rr_subscription_type { 138 | ARRST_EVENT_STREAM, 139 | ARRST_REQUEST_RESPONSE, 140 | }; 141 | 142 | struct aws_rr_acquire_subscription_options { 143 | struct aws_byte_cursor *topic_filters; 144 | size_t topic_filter_count; 145 | 146 | uint64_t operation_id; 147 | enum aws_rr_subscription_type type; 148 | }; 149 | 150 | struct aws_rr_release_subscription_options { 151 | struct aws_byte_cursor *topic_filters; 152 | size_t topic_filter_count; 153 | 154 | uint64_t operation_id; 155 | }; 156 | 157 | enum aws_acquire_subscription_result_type { 158 | 159 | /* 160 | * All requested subscriptions already exist and are active. The operation can proceed to the next stage. 161 | */ 162 | AASRT_SUBSCRIBED, 163 | 164 | /* 165 | * The requested subscriptions now exist but at least one is not yet active. The operation must wait for subscribes 166 | * to complete as success or failure. 167 | */ 168 | AASRT_SUBSCRIBING, 169 | 170 | /* 171 | * At least one subscription does not exist and there is no room for it currently. Room may open up in the future, 172 | * so the operation should wait. 173 | */ 174 | AASRT_BLOCKED, 175 | 176 | /* 177 | * At least one subscription does not exist and there is no room for it. Unless an event stream subscription gets 178 | * closed, no room will be available in the future. The operation should be failed. 179 | */ 180 | AASRT_NO_CAPACITY, 181 | 182 | /* 183 | * An internal failure occurred while trying to establish subscriptions. The operation should be failed. 184 | */ 185 | AASRT_FAILURE 186 | }; 187 | 188 | AWS_EXTERN_C_BEGIN 189 | 190 | /* 191 | * Initializes a subscription manager. Every native request-response client owns a single subscription manager. 192 | */ 193 | AWS_MQTT_API void aws_rr_subscription_manager_init( 194 | struct aws_rr_subscription_manager *manager, 195 | struct aws_allocator *allocator, 196 | struct aws_mqtt_protocol_adapter *protocol_adapter, 197 | const struct aws_rr_subscription_manager_options *options); 198 | 199 | /* 200 | * Cleans up a subscription manager. This is done early in the native request-response client shutdown process. 201 | * After this API is called, no other subscription manager APIs will be called by the request-response client (during 202 | * the rest of the asynchronous shutdown process). 203 | */ 204 | AWS_MQTT_API void aws_rr_subscription_manager_clean_up(struct aws_rr_subscription_manager *manager); 205 | 206 | /* 207 | * Requests the the subscription manager unsubscribe from all currently-unused subscriptions 208 | */ 209 | AWS_MQTT_API void aws_rr_subscription_manager_purge_unused(struct aws_rr_subscription_manager *manager); 210 | 211 | /* 212 | * Signals to the subscription manager that the native request-response client is processing an operation that 213 | * needs a subscription to a particular topic. Return value indicates to the request-response client how it should 214 | * proceed with processing the operation. 215 | */ 216 | AWS_MQTT_API enum aws_acquire_subscription_result_type aws_rr_subscription_manager_acquire_subscription( 217 | struct aws_rr_subscription_manager *manager, 218 | const struct aws_rr_acquire_subscription_options *options); 219 | 220 | /* 221 | * Signals to the subscription manager that the native request-response client operation no longer 222 | * needs a subscription to a particular topic. 223 | */ 224 | AWS_MQTT_API void aws_rr_subscription_manager_release_subscription( 225 | struct aws_rr_subscription_manager *manager, 226 | const struct aws_rr_release_subscription_options *options); 227 | 228 | /* 229 | * Notifies the subscription manager of a subscription status event. Invoked by the native request-response client 230 | * that owns the subscription manager. The native request-response client also owns the protocol adapter that 231 | * the subscription event originates from, so the control flow looks like: 232 | * 233 | * [Subscribe] 234 | * subscription manager -> protocol adapter Subscribe -> protocol client Subscribe -> network... 235 | * 236 | * [Result] 237 | * protocol client Suback/Timeout/Error -> protocol adapter -> native request-response client -> 238 | * subscription manager (this API) 239 | */ 240 | AWS_MQTT_API void aws_rr_subscription_manager_on_protocol_adapter_subscription_event( 241 | struct aws_rr_subscription_manager *manager, 242 | const struct aws_protocol_adapter_subscription_event *event); 243 | 244 | /* 245 | * Notifies the subscription manager of a connection status event. Invoked by the native request-response client 246 | * that owns the subscription manager. The native request-response client also owns the protocol adapter that 247 | * the connection event originates from. The control flow looks like: 248 | * 249 | * protocol client connect/disconnect -> protocol adapter -> native request-response client -> 250 | * Subscription manager (this API) 251 | */ 252 | AWS_MQTT_API void aws_rr_subscription_manager_on_protocol_adapter_connection_event( 253 | struct aws_rr_subscription_manager *manager, 254 | const struct aws_protocol_adapter_connection_event *event); 255 | 256 | /* 257 | * Checks subscription manager options for validity. 258 | */ 259 | AWS_MQTT_API bool aws_rr_subscription_manager_are_options_valid( 260 | const struct aws_rr_subscription_manager_options *options); 261 | 262 | AWS_EXTERN_C_END 263 | 264 | #endif /* AWS_MQTT_PRIVATE_REQUEST_RESPONSE_SUBSCRIPTION_MANAGER_H */ 265 | --------------------------------------------------------------------------------