├── .github ├── .cSpellWords.txt ├── CONTRIBUTING.md ├── memory_statistics_config.json └── workflows │ ├── ci.yml │ ├── doxygen.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── MISRA.md ├── README.md ├── SECURITY.md ├── cspell.config.yaml ├── defenderFilePaths.cmake ├── docs ├── doxygen │ ├── config.doxyfile │ ├── include │ │ └── size_table.md │ ├── layout.xml │ ├── pages.dox │ ├── porting.dox │ └── style.css └── plantuml │ ├── defender_design_operations.pu │ └── images │ └── defender_design_operations.png ├── manifest.yml ├── source ├── defender.c └── include │ ├── defender.h │ └── defender_config_defaults.h ├── test ├── CMakeLists.txt ├── cbmc │ ├── .gitignore │ ├── include │ │ └── README.md │ ├── proofs │ │ ├── Defender_GetTopic │ │ │ ├── Defender_GetTopic_harness.c │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── cbmc-proof.txt │ │ │ └── cbmc-viewer.json │ │ ├── Defender_MatchTopic │ │ │ ├── Defender_MatchTopic_harness.c │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── cbmc-proof.txt │ │ │ └── cbmc-viewer.json │ │ ├── Makefile-project-defines │ │ ├── Makefile-project-targets │ │ ├── Makefile-project-testing │ │ ├── Makefile-template-defines │ │ ├── Makefile.common │ │ ├── README.md │ │ ├── lib │ │ │ ├── __init__.py │ │ │ ├── print_tool_versions.py │ │ │ └── summarize.py │ │ └── run-cbmc-proofs.py │ ├── sources │ │ └── README.md │ └── stubs │ │ └── README.md ├── include │ └── defender_config.h └── unit-test │ ├── CMakeLists.txt │ ├── defender_utest.c │ └── unity_build.cmake └── tools ├── coverity ├── README.md └── misra.config └── unity ├── coverage.cmake ├── create_test.cmake └── project.yml /.github/.cSpellWords.txt: -------------------------------------------------------------------------------- 1 | cbmc 2 | CBMC 3 | cbor 4 | CBOR 5 | cmet 6 | cmock 7 | Cmock 8 | CMock 9 | CMOCK 10 | coremqtt 11 | coverity 12 | Coverity 13 | CSDK 14 | ctest 15 | DCMOCK 16 | DCOV 17 | DDISABLE 18 | decihours 19 | Decihours 20 | DECIHOURS 21 | DNDEBUG 22 | DUNITY 23 | getpacketid 24 | isystem 25 | lcov 26 | misra 27 | Misra 28 | MISRA 29 | MQTT 30 | mypy 31 | nondet 32 | Nondet 33 | NONDET 34 | pylint 35 | pytest 36 | pyyaml 37 | sinclude 38 | UNACKED 39 | unpadded 40 | Unpadded 41 | UNPADDED 42 | UNSUB 43 | UNSUBACK 44 | unsubscriptions 45 | utest 46 | vect 47 | Vect 48 | VECT 49 | Wunused 50 | -------------------------------------------------------------------------------- /.github/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/aws/Device-Defender-for-AWS-IoT-embedded-sdk/issues), or [recently closed](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/issues?q=is%3Aissue+is%3Aclosed), 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 | 1. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 1. 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 | 1. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 1. Ensure that your contributions conform to the [style guide](https://docs.aws.amazon.com/embedded-csdk/202011.00/lib-ref/docs/doxygen/output/html/guide_developer_styleguide.html). 35 | 1. Format your code with uncrustify, using the config available in [FreeRTOS/CI-CD-Github-Actions](https://github.com/FreeRTOS/CI-CD-Github-Actions/blob/main/formatting/uncrustify.cfg). 36 | 1. Ensure local tests pass. 37 | 1. Commit to your fork using clear commit messages. 38 | 1. Send us a pull request, answering any default questions in the pull request interface. 39 | 1. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 40 | 41 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 42 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 43 | 44 | 45 | ## Finding contributions to work on 46 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/labels?q=help+wanted) issues is a great place to start. 47 | 48 | 49 | ## Code of Conduct 50 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 51 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 52 | opensource-codeofconduct@amazon.com with any additional questions or comments. 53 | 54 | 55 | ## Security issue notifications 56 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](https://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 57 | 58 | 59 | ## Licensing 60 | 61 | See the [LICENSE](../LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 62 | 63 | We may ask you to sign a [Contributor License Agreement (CLA)](https://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 64 | -------------------------------------------------------------------------------- /.github/memory_statistics_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib_name": "AWS IoT Device Defender", 3 | "src": [ 4 | "source/defender.c" 5 | ], 6 | "include": [ 7 | "source/include" 8 | ], 9 | "compiler_flags": [ 10 | "DEFENDER_DO_NOT_USE_CUSTOM_CONFIG" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | on: 3 | push: 4 | branches: ["**"] 5 | pull_request: 6 | branches: [main] 7 | workflow_dispatch: 8 | jobs: 9 | unittest: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Clone This Repo 13 | uses: actions/checkout@v3 14 | - name: Build 15 | run: | 16 | sudo apt-get install -y lcov 17 | cmake -S test -B build/ \ 18 | -G "Unix Makefiles" \ 19 | -DCMAKE_BUILD_TYPE=Debug \ 20 | -DBUILD_CLONE_SUBMODULES=ON \ 21 | -DUNITTEST=1 \ 22 | -DCMAKE_C_FLAGS='--coverage -Wall -Wextra -Werror -DNDEBUG' 23 | make -C build/ all 24 | - name: Test 25 | run: | 26 | cd build/ 27 | ctest -E system --output-on-failure 28 | cd .. 29 | - name: Run Coverage 30 | run: | 31 | make -C build/ coverage 32 | declare -a EXCLUDE=("\*test\*" "\*CMakeCCompilerId\*" "\*mocks\*") 33 | echo ${EXCLUDE[@]} | xargs lcov --rc lcov_branch_coverage=1 -r build/coverage.info -o build/coverage.info 34 | lcov --rc lcov_branch_coverage=1 --list build/coverage.info 35 | - name: Check Coverage 36 | uses: FreeRTOS/CI-CD-Github-Actions/coverage-cop@main 37 | with: 38 | coverage-file: ./build/coverage.info 39 | build-with-default-config: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Clone This Repo 43 | uses: actions/checkout@v3 44 | - name: Build 45 | run: | 46 | cmake -S test -B build/ \ 47 | -G "Unix Makefiles" \ 48 | -DCMAKE_BUILD_TYPE=Debug \ 49 | -DCMAKE_C_FLAGS='-Wall -Wextra -Werror -DDEFENDER_DO_NOT_USE_CUSTOM_CONFIG' 50 | make -C build/ all 51 | complexity: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v3 55 | - name: Check complexity 56 | uses: FreeRTOS/CI-CD-Github-Actions/complexity@main 57 | with: 58 | path: ./ 59 | doxygen: 60 | runs-on: ubuntu-latest 61 | steps: 62 | - uses: actions/checkout@v3 63 | - name: Run doxygen build 64 | uses: FreeRTOS/CI-CD-Github-Actions/doxygen@main 65 | with: 66 | path: ./ 67 | spell-check: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Clone This Repo 71 | uses: actions/checkout@v3 72 | - name: Run spellings check 73 | uses: FreeRTOS/CI-CD-Github-Actions/spellings@main 74 | with: 75 | path: ./ 76 | formatting: 77 | runs-on: ubuntu-20.04 78 | steps: 79 | - uses: actions/checkout@v3 80 | - name: Check formatting 81 | uses: FreeRTOS/CI-CD-Github-Actions/formatting@main 82 | with: 83 | path: ./ 84 | git-secrets: 85 | runs-on: ubuntu-latest 86 | steps: 87 | - uses: actions/checkout@v3 88 | - name: Checkout awslabs/git-secrets 89 | uses: actions/checkout@v3 90 | with: 91 | repository: awslabs/git-secrets 92 | ref: master 93 | path: git-secrets 94 | - name: Install git-secrets 95 | run: cd git-secrets && sudo make install && cd .. 96 | - name: Run git-secrets 97 | run: | 98 | git-secrets --register-aws 99 | git-secrets --scan 100 | memory_statistics: 101 | runs-on: ubuntu-latest 102 | steps: 103 | - uses: actions/checkout@v3 104 | with: 105 | submodules: "recursive" 106 | - name: Install Python3 107 | uses: actions/setup-python@v3 108 | with: 109 | python-version: "3.11.0" 110 | - name: Measure sizes 111 | uses: FreeRTOS/CI-CD-Github-Actions/memory_statistics@main 112 | with: 113 | config: .github/memory_statistics_config.json 114 | check_against: docs/doxygen/include/size_table.md 115 | link-verifier: 116 | runs-on: ubuntu-latest 117 | steps: 118 | - uses: actions/checkout@v3 119 | - name: Check Links 120 | env: 121 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 122 | uses: FreeRTOS/CI-CD-Github-Actions/link-verifier@main 123 | 124 | verify-manifest: 125 | runs-on: ubuntu-latest 126 | steps: 127 | - uses: actions/checkout@v3 128 | with: 129 | submodules: true 130 | fetch-depth: 0 131 | 132 | - name: Run manifest verifier 133 | uses: FreeRTOS/CI-CD-GitHub-Actions/manifest-verifier@main 134 | with: 135 | path: ./ 136 | fail-on-incorrect-version: true 137 | 138 | proof_ci: 139 | if: ${{ github.event.pull_request }} 140 | runs-on: cbmc_ubuntu-latest_16-core 141 | steps: 142 | - name: Set up CBMC runner 143 | uses: FreeRTOS/CI-CD-Github-Actions/set_up_cbmc_runner@main 144 | with: 145 | cbmc_version: "6.3.1" 146 | - name: Run CBMC 147 | uses: FreeRTOS/CI-CD-Github-Actions/run_cbmc@main 148 | with: 149 | proofs_dir: test/cbmc/proofs 150 | -------------------------------------------------------------------------------- /.github/workflows/doxygen.yml: -------------------------------------------------------------------------------- 1 | name: Doxygen Generation 2 | on: 3 | push: 4 | branches: [main] 5 | workflow_dispatch: 6 | jobs: 7 | doxygen-generation: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Doxygen generation 11 | uses: FreeRTOS/CI-CD-Github-Actions/doxygen-generation@main 12 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release automation 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | commit_id: 7 | description: 'Commit ID to tag and create a release for' 8 | required: true 9 | version_number: 10 | description: 'Release Version Number (Eg, v1.0.0)' 11 | required: true 12 | 13 | jobs: 14 | tag-commit: 15 | name: Tag commit 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | ref: ${{ github.event.inputs.commit_id }} 22 | - name: Configure git identity 23 | run: | 24 | git config --global user.name ${{ github.actor }} 25 | git config --global user.email ${{ github.actor }}@users.noreply.github.com 26 | - name: create a new branch that references commit id 27 | run: git checkout -b ${{ github.event.inputs.version_number }} ${{ github.event.inputs.commit_id }} 28 | - name: Generate SBOM 29 | uses: FreeRTOS/CI-CD-Github-Actions/sbom-generator@main 30 | with: 31 | repo_path: ./ 32 | source_path: ./source 33 | - name: commit SBOM file 34 | run: | 35 | git add . 36 | git commit -m 'Update SBOM' 37 | git push -u origin ${{ github.event.inputs.version_number }} 38 | - name: Tag Commit and Push to remote 39 | run: | 40 | git tag ${{ github.event.inputs.version_number }} -a -m "AWS IoT Device Defender ${{ github.event.inputs.version_number }}" 41 | git push origin --tags 42 | - name: Verify tag on remote 43 | run: | 44 | git tag -d ${{ github.event.inputs.version_number }} 45 | git remote update 46 | git checkout tags/${{ github.event.inputs.version_number }} 47 | git diff ${{ github.event.inputs.commit_id }} tags/${{ github.event.inputs.version_number }} 48 | create-zip: 49 | needs: tag-commit 50 | name: Create ZIP and verify package for release asset. 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Install ZIP tools 54 | run: sudo apt-get install zip unzip 55 | - name: Checkout code 56 | uses: actions/checkout@v4 57 | with: 58 | ref: ${{ github.event.inputs.commit_id }} 59 | path: Device-Defender-for-AWS-IoT-embedded-sdk 60 | submodules: recursive 61 | - name: Checkout disabled submodules 62 | run: | 63 | cd Device-Defender-for-AWS-IoT-embedded-sdk 64 | git submodule update --init --checkout --recursive 65 | - name: Create ZIP 66 | run: | 67 | zip -r Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip Device-Defender-for-AWS-IoT-embedded-sdk -x "*.git*" 68 | ls ./ 69 | - name: Validate created ZIP 70 | run: | 71 | mkdir zip-check 72 | mv Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip zip-check 73 | cd zip-check 74 | unzip Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip -d Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }} 75 | ls Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }} 76 | diff -r -x "*.git*" Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}/Device-Defender-for-AWS-IoT-embedded-sdk/ ../Device-Defender-for-AWS-IoT-embedded-sdk/ 77 | cd ../ 78 | - name: Build 79 | run: | 80 | cd zip-check/Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}/Device-Defender-for-AWS-IoT-embedded-sdk 81 | sudo apt-get install -y lcov 82 | cmake -S test -B build/ \ 83 | -G "Unix Makefiles" \ 84 | -DCMAKE_BUILD_TYPE=Debug \ 85 | -DBUILD_CLONE_SUBMODULES=ON \ 86 | -DCMAKE_C_FLAGS='--coverage -Wall -Wextra -Werror -DNDEBUG' 87 | make -C build/ all 88 | - name: Test 89 | run: | 90 | cd zip-check/Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}/Device-Defender-for-AWS-IoT-embedded-sdk/build/ 91 | ctest -E system --output-on-failure 92 | cd .. 93 | - name: Create artifact of ZIP 94 | uses: actions/upload-artifact@v2 95 | with: 96 | name: Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip 97 | path: zip-check/Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip 98 | deploy-doxygen: 99 | needs: tag-commit 100 | name: Deploy doxygen documentation 101 | runs-on: ubuntu-latest 102 | steps: 103 | - name: Doxygen generation 104 | uses: FreeRTOS/CI-CD-Github-Actions/doxygen-generation@main 105 | with: 106 | ref: ${{ github.event.inputs.version_number }} 107 | add_release: "true" 108 | create-release: 109 | needs: 110 | - create-zip 111 | - deploy-doxygen 112 | name: Create Release and Upload Release Asset 113 | runs-on: ubuntu-latest 114 | steps: 115 | - name: Create Release 116 | id: create_release 117 | uses: actions/create-release@v1 118 | env: 119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 120 | with: 121 | tag_name: ${{ github.event.inputs.version_number }} 122 | release_name: ${{ github.event.inputs.version_number }} 123 | body: Release ${{ github.event.inputs.version_number }} of AWS IoT Device Defender. 124 | draft: false 125 | prerelease: false 126 | - name: Download ZIP artifact 127 | uses: actions/download-artifact@v4.1.7 128 | with: 129 | name: Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip 130 | - name: Upload Release Asset 131 | id: upload-release-asset 132 | uses: actions/upload-release-asset@v1 133 | env: 134 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 135 | with: 136 | upload_url: ${{ steps.create_release.outputs.upload_url }} 137 | asset_path: ./Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip 138 | asset_name: Device-Defender-for-AWS-IoT-embedded-sdk-${{ github.event.inputs.version_number }}.zip 139 | asset_content_type: application/zip 140 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore documentation output. 2 | **/docs/**/output/* 3 | 4 | # Ignore CMake build directory. 5 | build/ 6 | 7 | # Ignore build artifacts. 8 | *.o 9 | 10 | # Ignore code coverage artifacts. 11 | *.gcda 12 | *.gcno 13 | *.gcov 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/unit-test/Unity"] 2 | path = test/unit-test/Unity 3 | url = https://github.com/ThrowTheSwitch/Unity 4 | update = none 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for AWS IoT Device Defender Library 2 | 3 | ## v1.4.0 (May 2024) 4 | 5 | ### Updates 6 | - [#80](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/pull/80) Fix MISRA violation 7 | 8 | 9 | ## v1.3.0 (October 2022) 10 | 11 | ### Updates 12 | - [#60](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/pull/60) MISRA Compliance Update 13 | - [#59](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/pull/59) Update CBMC starter kit 14 | - [#56](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/pull/56) Loop invariant update 15 | 16 | 17 | ## v1.2.0 (November 2021) 18 | 19 | ### Updates 20 | - [#50](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/pull/50) Update doxygen version for documentation 21 | 22 | ## v1.1.1 (July 2021) 23 | 24 | ### Updates 25 | - [#45](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/pull/45) Remove parentheses from key name macros to enable concatenating them with other string literals. 26 | 27 | ## v1.1.0 (March 2021) 28 | 29 | ### Updates 30 | - [#36](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/36) Add macros to API for [custom metrics](https://docs.aws.amazon.com/iot/latest/developerguide/dd-detect-custom-metrics.html) feature of AWS IoT Device Defender service. 31 | 32 | ## v1.0.1 (December 2020) 33 | 34 | ### Updates 35 | - [#28](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/28) Cast logging arguments to C types matching the format specifiers. 36 | 37 | ### Other 38 | - [#23](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/23) Formatting update. 39 | - [#25](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/25), [#30](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/30) Github actions updates. 40 | - [#26](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/26), [#29](https://github.com/aws/device-defender-for-aws-iot-embedded-sdk/pull/29) Github repository chores. 41 | 42 | ## v1.0.0 (November 2020) 43 | 44 | This is the first release of the AWS IoT Device Defender Client Library in this 45 | repository. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /MISRA.md: -------------------------------------------------------------------------------- 1 | # MISRA Compliance 2 | 3 | The Device Defender Client Library files conform to the [MISRA C:2012](https://www.misra.org.uk) 4 | guidelines, with some noted exceptions. Compliance is checked with Coverity static analysis 5 | version 2023.6.1. The specific deviations, suppressed inline, are listed below. 6 | 7 | Additionally, [MISRA configuration file](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/blob/main/tools/coverity/misra.config) contains the project wide deviations. 8 | 9 | ### Suppressed with Coverity Comments 10 | To find the violation references in the source files run grep on the source code 11 | with ( Assuming rule 11.4 violation; with justification in point 2 ): 12 | ``` 13 | grep 'MISRA Ref 11.4.2' . -rI 14 | ``` 15 | 16 | *None.* 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS IoT Device Defender Library 2 | 3 | **[API Documentation Pages for current and previous releases of this library can be found here](https://aws.github.io/Device-Defender-for-AWS-IoT-embedded-sdk/)** 4 | 5 | The Device Defender library enables you to send device metrics to the 6 | [AWS IoT Device Defender Service](https://aws.amazon.com/iot-device-defender/). 7 | This library also supports custom metrics, a feature that helps you monitor 8 | operational health metrics that are unique to your fleet or use case. For 9 | example, you can define a new metric to monitor the memory usage or CPU usage on 10 | your devices. This library has no dependencies on any additional libraries other 11 | than the standard C library, and therefore, can be used with any MQTT client 12 | library. This library is distributed under the 13 | [MIT Open Source License](LICENSE). 14 | 15 | This library has gone through code quality checks including verification that no 16 | function has a 17 | [GNU Complexity](https://www.gnu.org/software/complexity/manual/complexity.html) 18 | score over 8, and checks against deviations from mandatory rules in the 19 | [MISRA coding standard](https://www.misra.org.uk). Deviations from the MISRA 20 | C:2012 guidelines are documented under [MISRA Deviations](MISRA.md). This 21 | library has also undergone static code analysis using 22 | [Coverity static analysis](https://scan.coverity.com/), and validation of memory 23 | safety through the 24 | [CBMC automated reasoning tool](https://www.cprover.org/cbmc/). 25 | 26 | See memory requirements for this library 27 | [here](./docs/doxygen/include/size_table.md). 28 | 29 | **AWS IoT Device Defender v1.4.0 30 | [source code](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/tree/v1.4.0/source) 31 | is part of the 32 | [FreeRTOS 202406.00 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202406.00-LTS) 33 | release.** 34 | 35 | ## AWS IoT Device Defender Client Config File 36 | 37 | The AWS IoT Device Defender Client Library exposes build configuration macros 38 | that are required for building the library. A list of all the configurations and 39 | their default values are defined in 40 | [defender_config_defaults.h](source/include/defender_config_defaults.h). To 41 | provide custom values for the configuration macros, a config file named 42 | `defender_config.h` can be provided by the application to the library. 43 | 44 | By default, a `defender_config.h` config file is required to build the library. 45 | To disable this requirement and build the library with default configuration 46 | values, provide `DEFENDER_DO_NOT_USE_CUSTOM_CONFIG` as a compile time 47 | preprocessor macro. 48 | 49 | **Thus, the Device Defender client library can be built by either**: 50 | 51 | - Defining a `defender_config.h` file in the application, and adding it to the 52 | include directories list of the library. 53 | 54 | **OR** 55 | 56 | - Defining the `DEFENDER_DO_NOT_USE_CUSTOM_CONFIG` preprocessor macro for the 57 | library build. 58 | 59 | ## Building the Library 60 | 61 | The [defenderFilePaths.cmake](defenderFilePaths.cmake) file contains the 62 | information of all source files and the header include paths required to build 63 | the Device Defender client library. 64 | 65 | As mentioned in the previous section, either a custom config file (i.e. 66 | `defender_config.h`) or `DEFENDER_DO_NOT_USE_CUSTOM_CONFIG` macro needs to be 67 | provided to build the Device Defender client library. 68 | 69 | For a CMake example of building the Device Defender client library with the 70 | `defenderFilePaths.cmake` file, refer to the `coverity_analysis` library target 71 | in [test/CMakeLists.txt](test/CMakeLists.txt) file. 72 | 73 | ## Building Unit Tests 74 | 75 | ### Platform Prerequisites 76 | 77 | - For running unit tests: 78 | - **C90 compiler** like gcc. 79 | - **CMake 3.13.0 or later**. 80 | - **Ruby 2.0.0 or later** is additionally required for the CMock test 81 | framework (that we use). 82 | - For running the coverage target, **gcov** and **lcov** are additionally 83 | required. 84 | 85 | ### Steps to build **Unit Tests** 86 | 87 | 1. Go to the root directory of this repository. 88 | 89 | 1. Run the _cmake_ command: 90 | `cmake -S test -B build -DBUILD_CLONE_SUBMODULES=ON`. 91 | 92 | 1. Run this command to build the library and unit tests: `make -C build all`. 93 | 94 | 1. The generated test executables will be present in `build/bin/tests` folder. 95 | 96 | 1. Run `cd build && ctest` to execute all tests and view the test run summary. 97 | 98 | ## CBMC 99 | 100 | To learn more about CBMC and proofs specifically, review the training material 101 | [here](https://model-checking.github.io/cbmc-training). 102 | 103 | The `test/cbmc/proofs` directory contains CBMC proofs. 104 | 105 | In order to run these proofs you will need to install CBMC and other tools by 106 | following the instructions 107 | [here](https://model-checking.github.io/cbmc-training/installation.html). 108 | 109 | ## Reference examples 110 | 111 | The AWS IoT Embedded C-SDK repository contains a demo showing the use of AWS IoT 112 | Device Defender Client Library 113 | [here](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/main/demos/defender/defender_demo_json) 114 | on a POSIX platform. 115 | 116 | ## Documentation 117 | 118 | ### Existing documentation 119 | 120 | For pre-generated documentation, please see the documentation linked in the 121 | locations below: 122 | 123 | | Location | 124 | | :-------------------------------------------------------------------------------------------------------------------------------------: | 125 | | [AWS IoT Device SDK for Embedded C](https://github.com/aws/aws-iot-device-sdk-embedded-C#releases-and-documentation) | 126 | | [GitHub.io](https://aws.github.io/Device-Defender-for-AWS-IoT-embedded-sdk/v1.3.0/) | 127 | 128 | Note that the latest included version of the AWS IoT Device Defender library may 129 | differ across repositories. 130 | 131 | ### Generating documentation 132 | 133 | The Doxygen references were created using Doxygen version 1.9.2. To generate the 134 | Doxygen pages, please run the following command from the root of this 135 | repository: 136 | 137 | ```shell 138 | doxygen docs/doxygen/config.doxyfile 139 | ``` 140 | 141 | ## Contributing 142 | 143 | See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for information on 144 | contributing. 145 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a Vulnerability 2 | 3 | If you discover a potential security issue in this project, we ask that you notify AWS/Amazon Security 4 | via our [vulnerability reporting page](https://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. 5 | Please do **not** create a public github issue. 6 | -------------------------------------------------------------------------------- /cspell.config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | $schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json 3 | version: '0.2' 4 | # Allows things like stringLength 5 | allowCompoundWords: true 6 | 7 | # Read files not to spell check from the git ignore 8 | useGitignore: true 9 | 10 | # Language settings for C 11 | languageSettings: 12 | - caseSensitive: false 13 | enabled: true 14 | languageId: c 15 | locale: "*" 16 | 17 | # Add a dictionary, and the path to the word list 18 | dictionaryDefinitions: 19 | - name: freertos-words 20 | path: '.github/.cSpellWords.txt' 21 | addWords: true 22 | 23 | dictionaries: 24 | - freertos-words 25 | 26 | # Paths and files to ignore 27 | ignorePaths: 28 | - 'dependency' 29 | - 'docs' 30 | - 'ThirdParty' 31 | - 'History.txt' 32 | -------------------------------------------------------------------------------- /defenderFilePaths.cmake: -------------------------------------------------------------------------------- 1 | # This file is to add source files and include directories 2 | # into variables so that it can be reused from different repositories 3 | # in their Cmake based build system by including this file. 4 | # 5 | # Files specific to the repository such as test runner, platform tests 6 | # are not added to the variables. 7 | 8 | # Device Defender library source files. 9 | set( DEFENDER_SOURCES 10 | "${CMAKE_CURRENT_LIST_DIR}/source/defender.c" ) 11 | 12 | # Device Defender library public include directories. 13 | set( DEFENDER_INCLUDE_PUBLIC_DIRS 14 | "${CMAKE_CURRENT_LIST_DIR}/source/include" ) 15 | -------------------------------------------------------------------------------- /docs/doxygen/include/size_table.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Code Size of AWS IoT Device Defender (example generated with GCC for ARM Cortex-M)
File
With -O1 Optimization
With -Os Optimization
defender.c
1.1K
0.6K
Total estimates
1.1K
0.6K
21 | -------------------------------------------------------------------------------- /docs/doxygen/layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /docs/doxygen/pages.dox: -------------------------------------------------------------------------------- 1 | /** 2 | @mainpage Overview 3 | @anchor defender 4 | @brief AWS IoT Device Defender Client Library 5 | 6 | > AWS IoT Device Defender is a security service that allows you to audit the configuration of your devices, monitor connected devices to detect abnormal behavior, and mitigate security risks. It gives you the ability to enforce consistent security policies across your AWS IoT device fleet and respond quickly when devices are compromised. 7 | Description of Device Defender from AWS IoT documentation [https://docs.aws.amazon.com/iot/latest/developerguide/device-defender.html](https://docs.aws.amazon.com/iot/latest/developerguide/device-defender.html)
8 | 9 | AWS IoT Device Defender lets you continuously monitor security metrics from devices for deviations from what you have defined as appropriate behavior for each device. If something doesn’t look right, AWS IoT Device Defender sends out an alert so you can take action to remediate the issue. 10 | 11 | The AWS IoT Device Defender Client Library provides a convenience API for interacting with AWS IoT Device Defender service. This library is independent of the MQTT library. Applications can use this library to assemble and parse the Device Defender MQTT topics, then use any MQTT library to publish/subscribe to those topics. Features of this library include: 12 | - It is stateless. It does not use any global/static memory. 13 | - It depends on the standard C library only. 14 | 15 | @section defender_memory_requirements Memory Requirements 16 | @brief Memory requirements of the AWS IoT Device Defender Client Library. 17 | 18 | @include{doc} size_table.md 19 | */ 20 | 21 | /** 22 | @page defender_design Design 23 | AWS IoT Device Defender Client Library Design 24 | 25 | The AWS IoT Device Defender Client Library provides macros and functions to assemble and parse MQTT topic strings reserved for the Device Defender. Applications can use this library in conjunction with any MQTT library to interact with the AWS IoT Device Defender service. 26 | 27 | The diagram below demonstrates how an application uses the Device Defender Client Library, an MQTT library, and a JSON library to interact with the AWS IoT Device Defender service. 28 | 29 | \image html defender_design_operations.png "Device Defender Client Library Demo Operation Diagram" width=1000px 30 | */ 31 | 32 | /** 33 | @page defender_config Configurations 34 | @brief Configurations of the AWS IoT Device Defender Client Library. 35 | 36 | @par configpagestyle 37 | 38 | Configuration settings are C pre-processor constants. They can be set with a `\#define` in the config file (`defender_config.h`) or by using a compiler option such as -D in gcc. 39 | 40 | @section DEFENDER_DO_NOT_USE_CUSTOM_CONFIG 41 | @copydoc DEFENDER_DO_NOT_USE_CUSTOM_CONFIG 42 | 43 | @section DEFENDER_USE_LONG_KEYS 44 | @copydoc DEFENDER_USE_LONG_KEYS 45 | 46 | @section defender_logerror LogError 47 | @copydoc LogError 48 | 49 | @section defender_logwarn LogWarn 50 | @copydoc LogWarn 51 | 52 | @section defender_loginfo LogInfo 53 | @copydoc LogInfo 54 | 55 | @section defender_logdebug LogDebug 56 | @copydoc LogDebug 57 | */ 58 | 59 | /** 60 | @page defender_functions Functions 61 | @brief Primary functions of the AWS IoT Device Defender Client Library:

62 | @subpage defender_gettopic_function
63 | @subpage defender_matchtopic_function
64 | 65 | @page defender_gettopic_function Defender_GetTopic 66 | @snippet defender.h declare_defender_gettopic 67 | @copydoc Defender_GetTopic 68 | 69 | @page defender_matchtopic_function Defender_MatchTopic 70 | @snippet defender.h declare_defender_matchtopic 71 | @copydoc Defender_MatchTopic 72 | */ 73 | 74 | 75 | /** 76 | @defgroup defender_enum_types Enumerated Types 77 | @brief Enumerated types of the AWS IoT Device Defender Client Library 78 | */ 79 | 80 | /** 81 | @defgroup defender_constants Constants 82 | @brief Constants defined in the AWS IoT Device Defender Client Library 83 | */ 84 | -------------------------------------------------------------------------------- /docs/doxygen/porting.dox: -------------------------------------------------------------------------------- 1 | /** 2 | @page defender_porting Porting Guide 3 | @brief Guide for porting the AWS IoT Device Defender Client Library to a new platform. 4 | 5 | @section defender_porting_config Configuration Macros 6 | @brief Configuration macros that can be set the config header `defender_config.h`, or passed in as compiler options. 7 | 8 | The following optional logging macros are used throughout the library: 9 | - @ref LogError 10 | - @ref LogWarn 11 | - @ref LogInfo 12 | - @ref LogDebug 13 | 14 | @see [Configurations](@ref defender_config) for more information. 15 | 16 | @note Regardless of whether the above macros are defined in 17 | `defender_config.h` or passed as compiler options, by default the 18 | `defender_config.h` file is needed to build the AWS IoT Device Defender Client 19 | Library. To disable this requirement and build the library with default 20 | configuration values, provide `DEFENDER_DO_NOT_USE_CUSTOM_CONFIG` as a compile 21 | time preprocessor macro. 22 | */ 23 | -------------------------------------------------------------------------------- /docs/doxygen/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Stylesheet for Doxygen HTML output. 3 | * 4 | * This file defines styles for custom elements in the header/footer and 5 | * overrides some of the default Doxygen styles. 6 | * 7 | * Styles in this file do not affect the treeview sidebar. 8 | */ 9 | 10 | /* Set the margins to place a small amount of whitespace on the left and right 11 | * side of the page. */ 12 | div.contents { 13 | margin-left:4em; 14 | margin-right:4em; 15 | } 16 | 17 | /* Justify text in paragraphs. */ 18 | p { 19 | text-align: justify; 20 | } 21 | 22 | /* Style of section headings. */ 23 | h1 { 24 | border-bottom: 1px solid #879ECB; 25 | color: #354C7B; 26 | font-size: 160%; 27 | font-weight: normal; 28 | padding-bottom: 4px; 29 | padding-top: 8px; 30 | } 31 | 32 | /* Style of subsection headings. */ 33 | h2:not(.memtitle):not(.groupheader) { 34 | font-size: 125%; 35 | margin-bottom: 0px; 36 | margin-top: 16px; 37 | padding: 0px; 38 | } 39 | 40 | /* Style of paragraphs immediately after subsection headings. */ 41 | h2 + p { 42 | margin: 0px; 43 | padding: 0px; 44 | } 45 | 46 | /* Style of subsection headings. */ 47 | h3 { 48 | font-size: 100%; 49 | margin-bottom: 0px; 50 | margin-left: 2em; 51 | margin-right: 2em; 52 | } 53 | 54 | /* Style of paragraphs immediately after subsubsection headings. */ 55 | h3 + p { 56 | margin-top: 0px; 57 | margin-left: 2em; 58 | margin-right: 2em; 59 | } 60 | 61 | /* Style of the prefix "AWS IoT Device SDK C" that appears in the header. */ 62 | #csdkprefix { 63 | color: #757575; 64 | } 65 | 66 | /* Style of the "Return to main page" link that appears in the header. */ 67 | #returntomain { 68 | padding: 0.5em; 69 | } 70 | 71 | /* Style of the dividers on Configuration Settings pages. */ 72 | div.configpagedivider { 73 | margin-left: 0px !important; 74 | margin-right: 0px !important; 75 | margin-top: 20px !important; 76 | } 77 | 78 | /* Style of configuration setting names. */ 79 | dl.section.user ~ h1 { 80 | border-bottom: none; 81 | color: #000000; 82 | font-family: monospace, fixed; 83 | font-size: 16px; 84 | margin-bottom: 0px; 85 | margin-left: 2em; 86 | margin-top: 1.5em; 87 | } 88 | 89 | /* Style of paragraphs on a configuration settings page. */ 90 | dl.section.user ~ * { 91 | margin-bottom: 10px; 92 | margin-left: 4em; 93 | margin-right: 4em; 94 | margin-top: 0px; 95 | } 96 | 97 | /* Hide the configuration setting marker. */ 98 | dl.section.user { 99 | display: none; 100 | } 101 | 102 | /* Overrides for code fragments and lines. */ 103 | div.fragment { 104 | background: #ffffff; 105 | border: none; 106 | padding: 5px; 107 | } 108 | 109 | div.line { 110 | color: #3a3a3a; 111 | } 112 | 113 | /* Overrides for code syntax highlighting colors. */ 114 | span.comment { 115 | color: #008000; 116 | } 117 | 118 | span.keyword, span.keywordtype, span.keywordflow { 119 | color: #0000ff; 120 | } 121 | 122 | span.preprocessor { 123 | color: #50015a; 124 | } 125 | 126 | span.stringliteral, span.charliteral { 127 | color: #800c0c; 128 | } 129 | 130 | a.code, a.code:visited, a.line, a.line:visited { 131 | color: #496194; 132 | } 133 | -------------------------------------------------------------------------------- /docs/plantuml/defender_design_operations.pu: -------------------------------------------------------------------------------- 1 | @startuml 2 | skinparam dpi 300 3 | skinparam classFontSize 8 4 | skinparam classFontName Helvetica 5 | autonumber 6 | 7 | participant "User Application" as App 8 | participant "Defender Library" as Defender 9 | participant "MQTT Client" as MQTT 10 | participant "TCP Stack" as TCP 11 | participant "JSON Library" as JSON 12 | 13 | activate App 14 | App -> Defender : Generate Accepted and Rejected Topic Strings 15 | 16 | activate Defender 17 | Defender -> App : Accepted and Rejected Topic Strings 18 | deactivate Defender 19 | 20 | App -> MQTT : Subscribe to Accepted and Rejected Topics 21 | 22 | activate MQTT 23 | MQTT -> App : Subscription Successful 24 | deactivate MQTT 25 | 26 | loop Forever 27 | App -> TCP : Get Metrics 28 | 29 | activate TCP 30 | TCP -> App : Metrics 31 | deactivate TCP 32 | 33 | App -> JSON : Generate Report 34 | 35 | activate JSON 36 | JSON -> App : Report 37 | deactivate JSON 38 | 39 | App -> Defender : Generate Publish Topic String 40 | 41 | activate Defender 42 | Defender -> App : Publish Topic String 43 | deactivate Defender 44 | 45 | App -> MQTT : Publish Report 46 | 47 | activate MQTT 48 | MQTT -> App : Published 49 | MQTT -> App : Incoming Publish Message Received 50 | deactivate MQTT 51 | 52 | App -> Defender : Is this a Defender Message? 53 | 54 | activate Defender 55 | Defender -> App : Yes/No 56 | note right 57 | The Defender library also tells if the report was accepted by 58 | 59 | the service or not. 60 | endnote 61 | deactivate Defender 62 | end 63 | 64 | deactivate App 65 | 66 | @enduml -------------------------------------------------------------------------------- /docs/plantuml/images/defender_design_operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/33f9087d4b6db3c5c024c3254426bbba30a0f10a/docs/plantuml/images/defender_design_operations.png -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | name : "Device-Defender-for-AWS-IoT-embedded-sdk" 2 | version: "v1.4.0" 3 | description: | 4 | "Client library for using the AWS IoT Device Defender service on embedded devices.\n" 5 | license: "MIT" 6 | -------------------------------------------------------------------------------- /source/defender.c: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file defender.c 27 | * @brief Implementation of the AWS IoT Device Defender Client Library. 28 | */ 29 | 30 | /* Standard includes. */ 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | /* Defender API include. */ 37 | #include "defender.h" 38 | 39 | /** 40 | * @brief Get the topic length for a given defender API. 41 | * 42 | * @param[in] thingNameLength The length of the thing name as registered with AWS IoT. 43 | * @param[in] api The defender API value. 44 | * 45 | * @return The topic length for the given defender API. 46 | */ 47 | static uint16_t getTopicLength( uint16_t thingNameLength, 48 | DefenderTopic_t api ); 49 | 50 | /** 51 | * @brief Write the format and suffix part for the given defender API to the buffer. 52 | * 53 | * Format: json or cbor. 54 | * Suffix: /accepted or /rejected or empty. 55 | * 56 | * @param[in] pBuffer The buffer to write the format and suffix part into. 57 | * @param[in] api The defender API value. 58 | * 59 | * @note This function assumes that the buffer is large enough to hold the 60 | * value. 61 | */ 62 | static void writeFormatAndSuffix( char * pBuffer, 63 | DefenderTopic_t api ); 64 | 65 | /** 66 | * @brief Check if the unparsed topic so far starts with the defender prefix. 67 | * 68 | * The defender prefix is "$aws/things/". 69 | * 70 | * @param[in] pRemainingTopic Starting location of the unparsed topic. 71 | * @param[in] remainingTopicLength The length of the unparsed topic. 72 | * 73 | * @return #DefenderSuccess if the unparsed topic starts with the defender 74 | * prefix; #DefenderNoMatch otherwise. 75 | */ 76 | static DefenderStatus_t matchPrefix( const char * pRemainingTopic, 77 | uint16_t remainingTopicLength ); 78 | 79 | /** 80 | * @brief Extract the length of thing name in the unparsed topic so far. 81 | * 82 | * The end of thing name is marked by a forward slash. A zero length thing name 83 | * is not valid. 84 | * 85 | * This function extracts the same thing name from the following topic strings: 86 | * - $aws/things/THING_NAME/defender/metrics/json 87 | * - $aws/things/THING_NAME 88 | * The second topic is not a valid defender topic and the matching will fail 89 | * when we try to match the bridge part. 90 | * 91 | * @param[in] pRemainingTopic Starting location of the unparsed topic. 92 | * @param[in] remainingTopicLength The length of the unparsed topic. 93 | * @param[out] pOutThingNameLength The length of the thing name in the topic string. 94 | * 95 | * @return #DefenderSuccess if a valid thing name is found; #DefenderNoMatch 96 | * otherwise. 97 | */ 98 | static DefenderStatus_t extractThingNameLength( const char * pRemainingTopic, 99 | uint16_t remainingTopicLength, 100 | uint16_t * pOutThingNameLength ); 101 | 102 | /** 103 | * @brief Check if the unparsed topic so far starts with the defender bridge. 104 | * 105 | * The defender bridge is "/defender/metrics/". 106 | * 107 | * @param[in] pRemainingTopic Starting location of the unparsed topic. 108 | * @param[in] remainingTopicLength The length of the unparsed topic. 109 | * 110 | * @return #DefenderSuccess if the unparsed topic starts with the defender 111 | * bridge; #DefenderNoMatch otherwise. 112 | */ 113 | static DefenderStatus_t matchBridge( const char * pRemainingTopic, 114 | uint16_t remainingTopicLength ); 115 | 116 | /** 117 | * @brief Check if the unparsed topic so far exactly matches one of the defender 118 | * api topics. 119 | * 120 | * The defender api topics are the following: 121 | * - json 122 | * - json/accepted 123 | * - json/rejected 124 | * - cbor 125 | * - cbor/accepted 126 | * - cbor/rejected 127 | * 128 | * The function also outputs the defender API value if a match is found. 129 | * 130 | * @param[in] pRemainingTopic Starting location of the unparsed topic. 131 | * @param[in] remainingTopicLength The length of the unparsed topic. 132 | * @param[out] pOutApi The defender topic API value. 133 | * 134 | * @return #DefenderSuccess if the unparsed topic exactly matches one of the 135 | * defender api topics; #DefenderNoMatch otherwise. 136 | */ 137 | static DefenderStatus_t matchApi( const char * pRemainingTopic, 138 | uint16_t remainingTopicLength, 139 | DefenderTopic_t * pOutApi ); 140 | /*-----------------------------------------------------------*/ 141 | 142 | static uint16_t getTopicLength( uint16_t thingNameLength, 143 | DefenderTopic_t api ) 144 | { 145 | uint16_t topicLength = 0U; 146 | 147 | assert( ( thingNameLength != 0U ) && ( thingNameLength <= DEFENDER_THINGNAME_MAX_LENGTH ) ); 148 | assert( ( api > DefenderInvalidTopic ) && ( api < DefenderMaxTopic ) ); 149 | 150 | switch( api ) 151 | { 152 | case DefenderJsonReportPublish: 153 | topicLength = DEFENDER_API_LENGTH_JSON_PUBLISH( thingNameLength ); 154 | break; 155 | 156 | case DefenderJsonReportAccepted: 157 | topicLength = DEFENDER_API_LENGTH_JSON_ACCEPTED( thingNameLength ); 158 | break; 159 | 160 | case DefenderJsonReportRejected: 161 | topicLength = DEFENDER_API_LENGTH_JSON_REJECTED( thingNameLength ); 162 | break; 163 | 164 | case DefenderCborReportPublish: 165 | topicLength = DEFENDER_API_LENGTH_CBOR_PUBLISH( thingNameLength ); 166 | break; 167 | 168 | case DefenderCborReportAccepted: 169 | topicLength = DEFENDER_API_LENGTH_CBOR_ACCEPTED( thingNameLength ); 170 | break; 171 | 172 | /* The default is here just to silence compiler warnings in a way which 173 | * does not bring coverage down. The assert at the beginning of this 174 | * function ensures that the only api value hitting this case can be 175 | * DefenderCborReportRejected. */ 176 | case DefenderCborReportRejected: 177 | default: 178 | topicLength = DEFENDER_API_LENGTH_CBOR_REJECTED( thingNameLength ); 179 | break; 180 | } 181 | 182 | return topicLength; 183 | } 184 | /*-----------------------------------------------------------*/ 185 | 186 | static void writeFormatAndSuffix( char * pBuffer, 187 | DefenderTopic_t api ) 188 | { 189 | /* The following variables are to address MISRA Rule 7.4 violation of 190 | * passing const char * for const void * param of memcpy. */ 191 | const char * pDefenderApiJsonFormat = DEFENDER_API_JSON_FORMAT; 192 | const char * pDefenderApiCborFormat = DEFENDER_API_CBOR_FORMAT; 193 | const char * pDefenderApiAcceptedSuffix = DEFENDER_API_ACCEPTED_SUFFIX; 194 | const char * pDefenderApiRejectedSuffix = DEFENDER_API_REJECTED_SUFFIX; 195 | 196 | assert( pBuffer != NULL ); 197 | assert( ( api > DefenderInvalidTopic ) && ( api < DefenderMaxTopic ) ); 198 | 199 | switch( api ) 200 | { 201 | case DefenderJsonReportPublish: 202 | ( void ) memcpy( ( void * ) pBuffer, 203 | ( const void * ) pDefenderApiJsonFormat, 204 | ( size_t ) DEFENDER_API_LENGTH_JSON_FORMAT ); 205 | break; 206 | 207 | case DefenderJsonReportAccepted: 208 | ( void ) memcpy( ( void * ) pBuffer, 209 | ( const void * ) pDefenderApiJsonFormat, 210 | ( size_t ) DEFENDER_API_LENGTH_JSON_FORMAT ); 211 | ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_JSON_FORMAT ] ), 212 | ( const void * ) pDefenderApiAcceptedSuffix, 213 | ( size_t ) DEFENDER_API_LENGTH_ACCEPTED_SUFFIX ); 214 | break; 215 | 216 | case DefenderJsonReportRejected: 217 | ( void ) memcpy( ( void * ) pBuffer, 218 | ( const void * ) pDefenderApiJsonFormat, 219 | DEFENDER_API_LENGTH_JSON_FORMAT ); 220 | ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_JSON_FORMAT ] ), 221 | ( const void * ) pDefenderApiRejectedSuffix, 222 | ( size_t ) DEFENDER_API_LENGTH_REJECTED_SUFFIX ); 223 | break; 224 | 225 | case DefenderCborReportPublish: 226 | ( void ) memcpy( ( void * ) pBuffer, 227 | ( const void * ) pDefenderApiCborFormat, 228 | ( size_t ) DEFENDER_API_LENGTH_CBOR_FORMAT ); 229 | break; 230 | 231 | case DefenderCborReportAccepted: 232 | ( void ) memcpy( ( void * ) pBuffer, 233 | ( const void * ) pDefenderApiCborFormat, 234 | DEFENDER_API_LENGTH_CBOR_FORMAT ); 235 | ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_CBOR_FORMAT ] ), 236 | ( const void * ) pDefenderApiAcceptedSuffix, 237 | ( size_t ) DEFENDER_API_LENGTH_ACCEPTED_SUFFIX ); 238 | break; 239 | 240 | /* The default is here just to silence compiler warnings in a way which 241 | * does not bring coverage down. The assert at the beginning of this 242 | * function ensures that the only api value hitting this case can be 243 | * DefenderCborReportRejected. */ 244 | case DefenderCborReportRejected: 245 | default: 246 | ( void ) memcpy( ( void * ) pBuffer, 247 | ( const void * ) pDefenderApiCborFormat, 248 | ( size_t ) DEFENDER_API_LENGTH_CBOR_FORMAT ); 249 | ( void ) memcpy( ( void * ) &( pBuffer[ DEFENDER_API_LENGTH_CBOR_FORMAT ] ), 250 | ( const void * ) pDefenderApiRejectedSuffix, 251 | ( size_t ) DEFENDER_API_LENGTH_REJECTED_SUFFIX ); 252 | break; 253 | } 254 | } 255 | /*-----------------------------------------------------------*/ 256 | 257 | static DefenderStatus_t matchPrefix( const char * pRemainingTopic, 258 | uint16_t remainingTopicLength ) 259 | { 260 | DefenderStatus_t ret = DefenderNoMatch; 261 | 262 | assert( pRemainingTopic != NULL ); 263 | 264 | if( remainingTopicLength >= DEFENDER_API_LENGTH_PREFIX ) 265 | { 266 | if( strncmp( pRemainingTopic, 267 | DEFENDER_API_PREFIX, 268 | ( size_t ) DEFENDER_API_LENGTH_PREFIX ) == 0 ) 269 | { 270 | ret = DefenderSuccess; 271 | } 272 | } 273 | 274 | return ret; 275 | } 276 | /*-----------------------------------------------------------*/ 277 | 278 | static DefenderStatus_t extractThingNameLength( const char * pRemainingTopic, 279 | uint16_t remainingTopicLength, 280 | uint16_t * pOutThingNameLength ) 281 | { 282 | DefenderStatus_t ret = DefenderNoMatch; 283 | uint16_t i = 0U; 284 | 285 | assert( pRemainingTopic != NULL ); 286 | assert( pOutThingNameLength != NULL ); 287 | 288 | /* Find the first forward slash. It marks the end of the thing name. */ 289 | for( i = 0U; i < remainingTopicLength; i++ ) 290 | { 291 | if( pRemainingTopic[ i ] == '/' ) 292 | { 293 | break; 294 | } 295 | } 296 | 297 | /* Zero length thing name is not valid. */ 298 | if( i > 0U ) 299 | { 300 | *pOutThingNameLength = i; 301 | ret = DefenderSuccess; 302 | } 303 | 304 | return ret; 305 | } 306 | /*-----------------------------------------------------------*/ 307 | 308 | static DefenderStatus_t matchBridge( const char * pRemainingTopic, 309 | uint16_t remainingTopicLength ) 310 | { 311 | DefenderStatus_t ret = DefenderNoMatch; 312 | 313 | assert( pRemainingTopic != NULL ); 314 | 315 | if( remainingTopicLength >= DEFENDER_API_LENGTH_BRIDGE ) 316 | { 317 | if( strncmp( pRemainingTopic, 318 | DEFENDER_API_BRIDGE, 319 | ( size_t ) DEFENDER_API_LENGTH_BRIDGE ) == 0 ) 320 | { 321 | ret = DefenderSuccess; 322 | } 323 | } 324 | 325 | return ret; 326 | } 327 | /*-----------------------------------------------------------*/ 328 | 329 | static DefenderStatus_t matchApi( const char * pRemainingTopic, 330 | uint16_t remainingTopicLength, 331 | DefenderTopic_t * pOutApi ) 332 | { 333 | DefenderStatus_t ret = DefenderNoMatch; 334 | uint16_t i = 0U; 335 | /* Table of defender APIs. */ 336 | static const DefenderTopic_t defenderApi[] = 337 | { 338 | DefenderJsonReportPublish, 339 | DefenderJsonReportAccepted, 340 | DefenderJsonReportRejected, 341 | DefenderCborReportPublish, 342 | DefenderCborReportAccepted, 343 | DefenderCborReportRejected, 344 | }; 345 | /* Table of topic API strings in the same order as the above defenderApi table. */ 346 | static const char * const defenderApiTopic[] = 347 | { 348 | DEFENDER_API_JSON_FORMAT, 349 | DEFENDER_API_JSON_FORMAT DEFENDER_API_ACCEPTED_SUFFIX, 350 | DEFENDER_API_JSON_FORMAT DEFENDER_API_REJECTED_SUFFIX, 351 | DEFENDER_API_CBOR_FORMAT, 352 | DEFENDER_API_CBOR_FORMAT DEFENDER_API_ACCEPTED_SUFFIX, 353 | DEFENDER_API_CBOR_FORMAT DEFENDER_API_REJECTED_SUFFIX, 354 | }; 355 | /* Table of topic API string lengths in the same order as the above defenderApi table. */ 356 | static const uint16_t defenderApiTopicLength[] = 357 | { 358 | DEFENDER_API_LENGTH_JSON_FORMAT, 359 | DEFENDER_API_LENGTH_JSON_FORMAT + DEFENDER_API_LENGTH_ACCEPTED_SUFFIX, 360 | DEFENDER_API_LENGTH_JSON_FORMAT + DEFENDER_API_LENGTH_REJECTED_SUFFIX, 361 | DEFENDER_API_LENGTH_CBOR_FORMAT, 362 | DEFENDER_API_LENGTH_CBOR_FORMAT + DEFENDER_API_LENGTH_ACCEPTED_SUFFIX, 363 | DEFENDER_API_LENGTH_CBOR_FORMAT + DEFENDER_API_LENGTH_REJECTED_SUFFIX, 364 | }; 365 | 366 | for( i = 0U; i < ( sizeof( defenderApi ) / sizeof( defenderApi[ 0 ] ) ); i++ ) 367 | { 368 | if( ( remainingTopicLength == defenderApiTopicLength[ i ] ) && 369 | ( strncmp( pRemainingTopic, 370 | defenderApiTopic[ i ], 371 | ( size_t ) defenderApiTopicLength[ i ] ) == 0 ) ) 372 | { 373 | *pOutApi = defenderApi[ i ]; 374 | ret = DefenderSuccess; 375 | 376 | break; 377 | } 378 | } 379 | 380 | return ret; 381 | } 382 | /*-----------------------------------------------------------*/ 383 | 384 | DefenderStatus_t Defender_GetTopic( char * pBuffer, 385 | uint16_t bufferLength, 386 | const char * pThingName, 387 | uint16_t thingNameLength, 388 | DefenderTopic_t api, 389 | uint16_t * pOutLength ) 390 | { 391 | DefenderStatus_t ret = DefenderSuccess; 392 | uint16_t topicLength = 0U, offset = 0U; 393 | 394 | /* The following variables are to address MISRA Rule 7.4 violation of 395 | * passing const char * for const void * param of memcpy. */ 396 | const char * pDefenderApiPrefix = DEFENDER_API_PREFIX; 397 | const char * pDefenderApiBridge = DEFENDER_API_BRIDGE; 398 | 399 | if( ( pBuffer == NULL ) || 400 | ( pThingName == NULL ) || 401 | ( thingNameLength == 0U ) || ( thingNameLength > DEFENDER_THINGNAME_MAX_LENGTH ) || 402 | ( api <= DefenderInvalidTopic ) || ( api >= DefenderMaxTopic ) || 403 | ( pOutLength == NULL ) ) 404 | { 405 | ret = DefenderBadParameter; 406 | 407 | LogError( ( "Invalid input parameter. pBuffer: %p, bufferLength: %u, " 408 | "pThingName: %p, thingNameLength: %u, api: %d, pOutLength: %p.", 409 | ( const void * ) pBuffer, 410 | ( unsigned int ) bufferLength, 411 | ( const void * ) pThingName, 412 | ( unsigned int ) thingNameLength, 413 | api, 414 | ( void * ) pOutLength ) ); 415 | } 416 | 417 | if( ret == DefenderSuccess ) 418 | { 419 | topicLength = getTopicLength( thingNameLength, api ); 420 | 421 | if( bufferLength < topicLength ) 422 | { 423 | ret = DefenderBufferTooSmall; 424 | 425 | LogError( ( "The buffer is too small to hold the topic string. " 426 | "Provided buffer size: %u, Required buffer size: %u.", 427 | ( unsigned int ) bufferLength, 428 | ( unsigned int ) topicLength ) ); 429 | } 430 | } 431 | 432 | if( ret == DefenderSuccess ) 433 | { 434 | /* At this point, it is certain that we have a large enough buffer to 435 | * write the topic string into. */ 436 | 437 | /* Write prefix first. */ 438 | ( void ) memcpy( ( void * ) &( pBuffer[ offset ] ), 439 | ( const void * ) pDefenderApiPrefix, 440 | ( size_t ) DEFENDER_API_LENGTH_PREFIX ); 441 | offset += DEFENDER_API_LENGTH_PREFIX; 442 | 443 | /* Write thing name next. */ 444 | ( void ) memcpy( ( void * ) &( pBuffer[ offset ] ), 445 | ( const void * ) pThingName, 446 | ( size_t ) thingNameLength ); 447 | offset += thingNameLength; 448 | 449 | /* Write bridge next. */ 450 | ( void ) memcpy( ( void * ) &( pBuffer[ offset ] ), 451 | ( const void * ) pDefenderApiBridge, 452 | ( size_t ) DEFENDER_API_LENGTH_BRIDGE ); 453 | offset += DEFENDER_API_LENGTH_BRIDGE; 454 | 455 | /* Write report format and suffix. */ 456 | writeFormatAndSuffix( &( pBuffer[ offset ] ), api ); 457 | 458 | *pOutLength = topicLength; 459 | } 460 | 461 | return ret; 462 | } 463 | /*-----------------------------------------------------------*/ 464 | 465 | DefenderStatus_t Defender_MatchTopic( const char * pTopic, 466 | uint16_t topicLength, 467 | DefenderTopic_t * pOutApi, 468 | const char ** ppOutThingName, 469 | uint16_t * pOutThingNameLength ) 470 | { 471 | DefenderStatus_t ret = DefenderSuccess; 472 | uint16_t remainingTopicLength = 0U, consumedTopicLength = 0U, thingNameLength = 0U; 473 | 474 | if( ( pTopic == NULL ) || ( pOutApi == NULL ) ) 475 | { 476 | ret = DefenderBadParameter; 477 | LogError( ( "Invalid input parameter. pTopic: %p, pOutApi: %p.", 478 | ( const void * ) pTopic, 479 | ( void * ) pOutApi ) ); 480 | } 481 | 482 | /* Nothing is consumed yet. */ 483 | remainingTopicLength = topicLength; 484 | consumedTopicLength = 0; 485 | 486 | if( ret == DefenderSuccess ) 487 | { 488 | ret = matchPrefix( &( pTopic[ consumedTopicLength ] ), 489 | remainingTopicLength ); 490 | 491 | if( ret == DefenderSuccess ) 492 | { 493 | remainingTopicLength -= DEFENDER_API_LENGTH_PREFIX; 494 | consumedTopicLength += DEFENDER_API_LENGTH_PREFIX; 495 | } 496 | else 497 | { 498 | LogDebug( ( "The topic does not contain defender prefix $aws/things/." ) ); 499 | } 500 | } 501 | 502 | if( ret == DefenderSuccess ) 503 | { 504 | ret = extractThingNameLength( &( pTopic[ consumedTopicLength ] ), 505 | remainingTopicLength, 506 | &( thingNameLength ) ); 507 | 508 | if( ret == DefenderSuccess ) 509 | { 510 | remainingTopicLength -= thingNameLength; 511 | consumedTopicLength += thingNameLength; 512 | } 513 | else 514 | { 515 | LogDebug( ( "The topic does not contain a valid thing name." ) ); 516 | } 517 | } 518 | 519 | if( ret == DefenderSuccess ) 520 | { 521 | ret = matchBridge( &( pTopic[ consumedTopicLength ] ), 522 | remainingTopicLength ); 523 | 524 | if( ret == DefenderSuccess ) 525 | { 526 | remainingTopicLength -= DEFENDER_API_LENGTH_BRIDGE; 527 | consumedTopicLength += DEFENDER_API_LENGTH_BRIDGE; 528 | } 529 | else 530 | { 531 | LogDebug( ( "The topic does not contain the defender bridge /defender/metrics/." ) ); 532 | } 533 | } 534 | 535 | if( ret == DefenderSuccess ) 536 | { 537 | ret = matchApi( &( pTopic[ consumedTopicLength ] ), 538 | remainingTopicLength, 539 | pOutApi ); 540 | 541 | if( ret != DefenderSuccess ) 542 | { 543 | LogDebug( ( "The topic does not contain valid report format or suffix " 544 | " needed to be a valid defender topic." ) ); 545 | } 546 | } 547 | 548 | /* Update the out parameters for thing name and thing length location, if we 549 | * successfully matched the topic. */ 550 | if( ret == DefenderSuccess ) 551 | { 552 | if( ppOutThingName != NULL ) 553 | { 554 | /* Thing name comes after the defender prefix. */ 555 | *ppOutThingName = &( pTopic[ DEFENDER_API_LENGTH_PREFIX ] ); 556 | } 557 | 558 | if( pOutThingNameLength != NULL ) 559 | { 560 | *pOutThingNameLength = thingNameLength; 561 | } 562 | } 563 | 564 | /* Update the output parameter for API if the topic did not match. In case 565 | * of a match, it is updated in the matchApi function. */ 566 | if( ret == DefenderNoMatch ) 567 | { 568 | *pOutApi = DefenderInvalidTopic; 569 | } 570 | 571 | return ret; 572 | } 573 | /*-----------------------------------------------------------*/ 574 | -------------------------------------------------------------------------------- /source/include/defender.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file defender.h 27 | * @brief Interface for the AWS IoT Device Defender Client Library. 28 | */ 29 | 30 | #ifndef DEFENDER_H_ 31 | #define DEFENDER_H_ 32 | 33 | /* Standard includes. */ 34 | #include 35 | 36 | /* *INDENT-OFF* */ 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | /* *INDENT-ON* */ 41 | 42 | /* DEFENDER_DO_NOT_USE_CUSTOM_CONFIG allows building the Device Defender library 43 | * without a config file. If a config file is provided, DEFENDER_DO_NOT_USE_CUSTOM_CONFIG 44 | * macro must not be defined. 45 | */ 46 | #ifndef DEFENDER_DO_NOT_USE_CUSTOM_CONFIG 47 | #include "defender_config.h" 48 | #endif 49 | 50 | /* Default config values. */ 51 | #include "defender_config_defaults.h" 52 | 53 | /** 54 | * @ingroup defender_enum_types 55 | * @brief Return codes from defender APIs. 56 | */ 57 | typedef enum 58 | { 59 | DefenderError = 0, /**< Generic Error. */ 60 | DefenderSuccess, /**< Success. */ 61 | DefenderNoMatch, /**< The provided topic does not match any defender topic. */ 62 | DefenderBadParameter, /**< Invalid parameters were passed. */ 63 | DefenderBufferTooSmall /**< The output buffer is too small. */ 64 | } DefenderStatus_t; 65 | 66 | /** 67 | * @ingroup defender_enum_types 68 | * @brief Topic values for subscription requests. 69 | */ 70 | typedef enum 71 | { 72 | DefenderInvalidTopic = -1, /**< Invalid topic. */ 73 | DefenderJsonReportPublish, /**< Topic for publishing a JSON report. */ 74 | DefenderJsonReportAccepted, /**< Topic for getting a JSON report accepted response. */ 75 | DefenderJsonReportRejected, /**< Topic for getting a JSON report rejected response. */ 76 | DefenderCborReportPublish, /**< Topic for publishing a CBOR report. */ 77 | DefenderCborReportAccepted, /**< Topic for getting a CBOR report accepted response. */ 78 | DefenderCborReportRejected, /**< Topic for getting a CBOR report rejected response. */ 79 | DefenderMaxTopic 80 | } DefenderTopic_t; 81 | 82 | /*-----------------------------------------------------------*/ 83 | 84 | /** 85 | * @brief Helper macro to calculate the length of a string literal. 86 | */ 87 | #define STRING_LITERAL_LENGTH( literal ) ( ( uint16_t ) ( sizeof( literal ) - 1U ) ) 88 | 89 | /*-----------------------------------------------------------*/ 90 | 91 | /** 92 | * @ingroup defender_constants 93 | * @brief Maximum length of a thing's name as permitted by AWS IoT Core. 94 | */ 95 | #define DEFENDER_THINGNAME_MAX_LENGTH 128U 96 | 97 | /** 98 | * @ingroup defender_constants 99 | * @brief Minimum period between 2 consecutive defender reports sent by the 100 | * device. 101 | * 102 | * This is as per AWS IoT Device Defender Service reference. 103 | */ 104 | #define DEFENDER_REPORT_MIN_PERIOD_SECONDS 300 105 | 106 | /*-----------------------------------------------------------*/ 107 | 108 | /* 109 | * A Defender topic has the following format: 110 | * 111 | * 112 | * 113 | * Where: 114 | * = $aws/things/ 115 | * = Name of the thing. 116 | * = /defender/metrics/ 117 | * = json or cbor 118 | * = /accepted or /rejected or empty 119 | * 120 | * Examples: 121 | * $aws/things/THING_NAME/defender/metrics/json 122 | * $aws/things/THING_NAME/defender/metrics/json/accepted 123 | * $aws/things/THING_NAME/defender/metrics/json/rejected 124 | * $aws/things/THING_NAME/defender/metrics/cbor 125 | * $aws/things/THING_NAME/defender/metrics/cbor/accepted 126 | * $aws/things/THING_NAME/defender/metrics/json/rejected 127 | */ 128 | 129 | /** 130 | * @cond DOXYGEN_IGNORE 131 | * Doxygen should ignore these macros as they are private. 132 | */ 133 | 134 | #define DEFENDER_API_PREFIX "$aws/things/" 135 | #define DEFENDER_API_LENGTH_PREFIX STRING_LITERAL_LENGTH( DEFENDER_API_PREFIX ) 136 | 137 | #define DEFENDER_API_BRIDGE "/defender/metrics/" 138 | #define DEFENDER_API_LENGTH_BRIDGE STRING_LITERAL_LENGTH( DEFENDER_API_BRIDGE ) 139 | 140 | #define DEFENDER_API_JSON_FORMAT "json" 141 | #define DEFENDER_API_LENGTH_JSON_FORMAT STRING_LITERAL_LENGTH( DEFENDER_API_JSON_FORMAT ) 142 | 143 | #define DEFENDER_API_CBOR_FORMAT "cbor" 144 | #define DEFENDER_API_LENGTH_CBOR_FORMAT STRING_LITERAL_LENGTH( DEFENDER_API_CBOR_FORMAT ) 145 | 146 | #define DEFENDER_API_ACCEPTED_SUFFIX "/accepted" 147 | #define DEFENDER_API_LENGTH_ACCEPTED_SUFFIX STRING_LITERAL_LENGTH( DEFENDER_API_ACCEPTED_SUFFIX ) 148 | 149 | #define DEFENDER_API_REJECTED_SUFFIX "/rejected" 150 | #define DEFENDER_API_LENGTH_REJECTED_SUFFIX STRING_LITERAL_LENGTH( DEFENDER_API_REJECTED_SUFFIX ) 151 | 152 | #define DEFENDER_API_NULL_SUFFIX "" 153 | #define DEFENDER_API_LENGTH_NULL_SUFFIX ( 0U ) 154 | 155 | /** @endcond */ 156 | 157 | /*-----------------------------------------------------------*/ 158 | 159 | /** 160 | * @cond DOXYGEN_IGNORE 161 | * Doxygen should ignore this macro as it is private. 162 | */ 163 | 164 | /* Defender API topic lengths. */ 165 | #define DEFENDER_API_COMMON_LENGTH( thingNameLength, reportFormatLength, suffixLength ) \ 166 | ( DEFENDER_API_LENGTH_PREFIX + \ 167 | ( thingNameLength ) + \ 168 | DEFENDER_API_LENGTH_BRIDGE + \ 169 | ( reportFormatLength ) + \ 170 | ( suffixLength ) ) 171 | 172 | /** @endcond */ 173 | 174 | /** 175 | * @brief Length of the topic string for publishing a JSON report. 176 | * 177 | * @param[in] thingNameLength Length of the thing name as registered with AWS IoT Core. 178 | */ 179 | #define DEFENDER_API_LENGTH_JSON_PUBLISH( thingNameLength ) \ 180 | DEFENDER_API_COMMON_LENGTH( thingNameLength, \ 181 | DEFENDER_API_LENGTH_JSON_FORMAT, \ 182 | DEFENDER_API_LENGTH_NULL_SUFFIX ) 183 | 184 | /** 185 | * @brief Length of the topic string for getting a JSON report accepted response. 186 | * 187 | * @param[in] thingNameLength Length of the thing name as registered with AWS IoT Core. 188 | */ 189 | #define DEFENDER_API_LENGTH_JSON_ACCEPTED( thingNameLength ) \ 190 | DEFENDER_API_COMMON_LENGTH( thingNameLength, \ 191 | DEFENDER_API_LENGTH_JSON_FORMAT, \ 192 | DEFENDER_API_LENGTH_ACCEPTED_SUFFIX ) 193 | 194 | /** 195 | * @brief Length of the topic string for getting a JSON report rejected response. 196 | * 197 | * @param[in] thingNameLength Length of the thing name as registered with AWS IoT Core. 198 | */ 199 | #define DEFENDER_API_LENGTH_JSON_REJECTED( thingNameLength ) \ 200 | DEFENDER_API_COMMON_LENGTH( thingNameLength, \ 201 | DEFENDER_API_LENGTH_JSON_FORMAT, \ 202 | DEFENDER_API_LENGTH_REJECTED_SUFFIX ) 203 | 204 | /** 205 | * @brief Length of the topic string for publishing a CBOR report. 206 | * 207 | * @param[in] thingNameLength Length of the thing name as registered with AWS IoT Core. 208 | */ 209 | #define DEFENDER_API_LENGTH_CBOR_PUBLISH( thingNameLength ) \ 210 | DEFENDER_API_COMMON_LENGTH( thingNameLength, \ 211 | DEFENDER_API_LENGTH_CBOR_FORMAT, \ 212 | DEFENDER_API_LENGTH_NULL_SUFFIX ) 213 | 214 | /** 215 | * @brief Length of the topic string for getting a CBOR report accepted response. 216 | * 217 | * @param[in] thingNameLength Length of the thing name. as registered with AWS IoT Core. 218 | */ 219 | #define DEFENDER_API_LENGTH_CBOR_ACCEPTED( thingNameLength ) \ 220 | DEFENDER_API_COMMON_LENGTH( thingNameLength, \ 221 | DEFENDER_API_LENGTH_CBOR_FORMAT, \ 222 | DEFENDER_API_LENGTH_ACCEPTED_SUFFIX ) 223 | 224 | /** 225 | * @brief Length of the topic string for getting a CBOR report rejected response. 226 | * 227 | * @param[in] thingNameLength Length of the thing name as registered with AWS IoT Core. 228 | */ 229 | #define DEFENDER_API_LENGTH_CBOR_REJECTED( thingNameLength ) \ 230 | DEFENDER_API_COMMON_LENGTH( thingNameLength, \ 231 | DEFENDER_API_LENGTH_CBOR_FORMAT, \ 232 | DEFENDER_API_LENGTH_REJECTED_SUFFIX ) 233 | 234 | /** 235 | * @brief Maximum length of the topic string for any defender operation. 236 | * 237 | * @param[in] thingNameLength Length of the thing name as registered with AWS IoT Core. 238 | */ 239 | #define DEFENDER_API_MAX_LENGTH( thingNameLength ) \ 240 | DEFENDER_API_LENGTH_CBOR_ACCEPTED( thingNameLength ) 241 | 242 | /*-----------------------------------------------------------*/ 243 | 244 | /** 245 | * @cond DOXYGEN_IGNORE 246 | * Doxygen should ignore this macro as it is private. 247 | */ 248 | 249 | /* Defender API topics. */ 250 | #define DEFENDER_API_COMMON( thingName, reportFormat, suffix ) \ 251 | ( DEFENDER_API_PREFIX \ 252 | thingName \ 253 | DEFENDER_API_BRIDGE \ 254 | reportFormat \ 255 | suffix ) 256 | 257 | /** @endcond */ 258 | 259 | /** 260 | * @brief Topic string for publishing a JSON report. 261 | * 262 | * This macro should be used when the thing name is known at the compile time. 263 | * If the thing name is not known at compile time, the #Defender_GetTopic API 264 | * should be used instead. 265 | * 266 | * @param thingName The thing name as registered with AWS IoT Core. 267 | */ 268 | #define DEFENDER_API_JSON_PUBLISH( thingName ) \ 269 | DEFENDER_API_COMMON( thingName, \ 270 | DEFENDER_API_JSON_FORMAT, \ 271 | DEFENDER_API_NULL_SUFFIX ) 272 | 273 | /** 274 | * @brief Topic string for getting a JSON report accepted response. 275 | * 276 | * This macro should be used when the thing name is known at the compile time. 277 | * If the thing name is not known at compile time, the #Defender_GetTopic API 278 | * should be used instead. 279 | * 280 | * @param thingName The thing name as registered with AWS IoT Core. 281 | */ 282 | #define DEFENDER_API_JSON_ACCEPTED( thingName ) \ 283 | DEFENDER_API_COMMON( thingName, \ 284 | DEFENDER_API_JSON_FORMAT, \ 285 | DEFENDER_API_ACCEPTED_SUFFIX ) 286 | 287 | /** 288 | * @brief Topic string for getting a JSON report rejected response. 289 | * 290 | * This macro should be used when the thing name is known at the compile time. 291 | * If the thing name is not known at compile time, the #Defender_GetTopic API 292 | * should be used instead. 293 | * 294 | * @param thingName The thing name as registered with AWS IoT Core. 295 | */ 296 | #define DEFENDER_API_JSON_REJECTED( thingName ) \ 297 | DEFENDER_API_COMMON( thingName, \ 298 | DEFENDER_API_JSON_FORMAT, \ 299 | DEFENDER_API_REJECTED_SUFFIX ) 300 | 301 | /** 302 | * @brief Topic string for publishing a CBOR report. 303 | * 304 | * This macro should be used when the thing name is known at the compile time. 305 | * If the thing name is not known at compile time, the #Defender_GetTopic API 306 | * should be used instead. 307 | * 308 | * @param thingName The thing name as registered with AWS IoT Core. 309 | */ 310 | #define DEFENDER_API_CBOR_PUBLISH( thingName ) \ 311 | DEFENDER_API_COMMON( thingName, \ 312 | DEFENDER_API_CBOR_FORMAT, \ 313 | DEFENDER_API_NULL_SUFFIX ) 314 | 315 | /** 316 | * @brief Topic string for getting a CBOR report accepted response. 317 | * 318 | * This macro should be used when the thing name is known at the compile time. 319 | * If the thing name is not known at compile time, the #Defender_GetTopic API 320 | * should be used instead. 321 | * 322 | * @param thingName The thing name as registered with AWS IoT Core. 323 | */ 324 | #define DEFENDER_API_CBOR_ACCEPTED( thingName ) \ 325 | DEFENDER_API_COMMON( thingName, \ 326 | DEFENDER_API_CBOR_FORMAT, \ 327 | DEFENDER_API_ACCEPTED_SUFFIX ) 328 | 329 | /** 330 | * @brief Topic string for getting a CBOR report rejected response. 331 | * 332 | * This macro should be used when the thing name is known at the compile time. 333 | * If the thing name is not known at compile time, the #Defender_GetTopic API 334 | * should be used instead. 335 | * 336 | * @param thingName The thing name as registered with AWS IoT Core. 337 | */ 338 | #define DEFENDER_API_CBOR_REJECTED( thingName ) \ 339 | DEFENDER_API_COMMON( thingName, \ 340 | DEFENDER_API_CBOR_FORMAT, \ 341 | DEFENDER_API_REJECTED_SUFFIX ) 342 | 343 | /*-----------------------------------------------------------*/ 344 | 345 | /** 346 | * @cond DOXYGEN_IGNORE 347 | * Doxygen should ignore this macro as it is private. 348 | */ 349 | 350 | /* Keys used in defender report. */ 351 | #if ( defined( DEFENDER_USE_LONG_KEYS ) && ( DEFENDER_USE_LONG_KEYS == 1 ) ) 352 | #define DEFENDER_REPORT_SELECT_KEY( longKey, shortKey ) longKey 353 | #else 354 | #define DEFENDER_REPORT_SELECT_KEY( longKey, shortKey ) shortKey 355 | #endif 356 | 357 | /** @endcond */ 358 | 359 | /** 360 | * @ingroup defender_constants 361 | * @brief "header" key in the defender report. 362 | */ 363 | #define DEFENDER_REPORT_HEADER_KEY DEFENDER_REPORT_SELECT_KEY( "header", "hed" ) 364 | 365 | /** 366 | * @ingroup defender_constants 367 | * @brief Length of the "header" key in the defender report. 368 | */ 369 | #define DEFENDER_REPORT_LENGTH_HEADER_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_HEADER_KEY ) 370 | 371 | /** 372 | * @ingroup defender_constants 373 | * @brief "metrics" key in the defender report. 374 | */ 375 | #define DEFENDER_REPORT_METRICS_KEY DEFENDER_REPORT_SELECT_KEY( "metrics", "met" ) 376 | 377 | /** 378 | * @ingroup defender_constants 379 | * @brief Length of the "metrics" key in the defender report. 380 | */ 381 | #define DEFENDER_REPORT_LENGTH_METRICS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_METRICS_KEY ) 382 | 383 | /** 384 | * @ingroup defender_constants 385 | * @brief "report_id" key in the defender report. 386 | */ 387 | #define DEFENDER_REPORT_ID_KEY DEFENDER_REPORT_SELECT_KEY( "report_id", "rid" ) 388 | 389 | /** 390 | * @ingroup defender_constants 391 | * @brief Length of the "report_id" key in the defender report. 392 | */ 393 | #define DEFENDER_REPORT_LENGTH_ID_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_ID_KEY ) 394 | 395 | /** 396 | * @ingroup defender_constants 397 | * @brief "version" key in the defender report. 398 | */ 399 | #define DEFENDER_REPORT_VERSION_KEY DEFENDER_REPORT_SELECT_KEY( "version", "v" ) 400 | 401 | /** 402 | * @ingroup defender_constants 403 | * @brief Length of the "version" key in the defender report. 404 | */ 405 | #define DEFENDER_REPORT_LENGTH_VERSION_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_VERSION_KEY ) 406 | 407 | /** 408 | * @ingroup defender_constants 409 | * @brief "tcp_connections" key in the defender report. 410 | */ 411 | #define DEFENDER_REPORT_TCP_CONNECTIONS_KEY DEFENDER_REPORT_SELECT_KEY( "tcp_connections", "tc" ) 412 | 413 | /** 414 | * @ingroup defender_constants 415 | * @brief Length of the "tcp_connections" key in the defender report. 416 | */ 417 | #define DEFENDER_REPORT_LENGTH_TCP_CONNECTIONS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_TCP_CONNECTIONS_KEY ) 418 | 419 | /** 420 | * @ingroup defender_constants 421 | * @brief "established_connections" key in the defender report. 422 | */ 423 | #define DEFENDER_REPORT_ESTABLISHED_CONNECTIONS_KEY DEFENDER_REPORT_SELECT_KEY( "established_connections", "ec" ) 424 | 425 | /** 426 | * @ingroup defender_constants 427 | * @brief Length of the "established_connections" key in the defender report. 428 | */ 429 | #define DEFENDER_REPORT_LENGTH_ESTABLISHED_CONNECTIONS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_ESTABLISHED_CONNECTIONS_KEY ) 430 | 431 | /** 432 | * @ingroup defender_constants 433 | * @brief "connections" key in the defender report. 434 | */ 435 | #define DEFENDER_REPORT_CONNECTIONS_KEY DEFENDER_REPORT_SELECT_KEY( "connections", "cs" ) 436 | 437 | /** 438 | * @ingroup defender_constants 439 | * @brief Length of the "connections" key in the defender report. 440 | */ 441 | #define DEFENDER_REPORT_LENGTH_CONNECTIONS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_CONNECTIONS_KEY ) 442 | 443 | /** 444 | * @ingroup defender_constants 445 | * @brief "remote_addr" key in the defender report. 446 | */ 447 | #define DEFENDER_REPORT_REMOTE_ADDR_KEY DEFENDER_REPORT_SELECT_KEY( "remote_addr", "rad" ) 448 | 449 | /** 450 | * @ingroup defender_constants 451 | * @brief Length of the "remote_addr" key in the defender report. 452 | */ 453 | #define DEFENDER_REPORT_LENGTH_REMOTE_ADDR_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_REMOTE_ADDR_KEY ) 454 | 455 | /** 456 | * @ingroup defender_constants 457 | * @brief "local_port" key in the defender report. 458 | */ 459 | #define DEFENDER_REPORT_LOCAL_PORT_KEY DEFENDER_REPORT_SELECT_KEY( "local_port", "lp" ) 460 | 461 | /** 462 | * @ingroup defender_constants 463 | * @brief Length of the "local_port" key in the defender report. 464 | */ 465 | #define DEFENDER_REPORT_LENGTH_LOCAL_PORT_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LOCAL_PORT_KEY ) 466 | 467 | /** 468 | * @ingroup defender_constants 469 | * @brief "local_interface" key in the defender report. 470 | */ 471 | #define DEFENDER_REPORT_LOCAL_INTERFACE_KEY DEFENDER_REPORT_SELECT_KEY( "local_interface", "li" ) 472 | 473 | /** 474 | * @ingroup defender_constants 475 | * @brief Length of the "local_interface" key in the defender report. 476 | */ 477 | #define DEFENDER_REPORT_LENGTH_LOCAL_INTERFACE_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LOCAL_INTERFACE_KEY ) 478 | 479 | /** 480 | * @ingroup defender_constants 481 | * @brief "total" key in the defender report. 482 | */ 483 | #define DEFENDER_REPORT_TOTAL_KEY DEFENDER_REPORT_SELECT_KEY( "total", "t" ) 484 | 485 | /** 486 | * @ingroup defender_constants 487 | * @brief Length of the "total" key in the defender report. 488 | */ 489 | #define DEFENDER_REPORT_LENGTH_TOTAL_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_TOTAL_KEY ) 490 | 491 | /** 492 | * @ingroup defender_constants 493 | * @brief "listening_tcp_ports" key in the defender report. 494 | */ 495 | #define DEFENDER_REPORT_TCP_LISTENING_PORTS_KEY DEFENDER_REPORT_SELECT_KEY( "listening_tcp_ports", "tp" ) 496 | 497 | /** 498 | * @ingroup defender_constants 499 | * @brief Length of the "listening_tcp_ports" key in the defender report. 500 | */ 501 | #define DEFENDER_REPORT_LENGTH_TCP_LISTENING_PORTS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_TCP_LISTENING_PORTS_KEY ) 502 | 503 | /** 504 | * @ingroup defender_constants 505 | * @brief "ports" key in the defender report. 506 | */ 507 | #define DEFENDER_REPORT_PORTS_KEY DEFENDER_REPORT_SELECT_KEY( "ports", "pts" ) 508 | 509 | /** 510 | * @ingroup defender_constants 511 | * @brief Length of the "ports" key in the defender report. 512 | */ 513 | #define DEFENDER_REPORT_LENGTH_PORTS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_PORTS_KEY ) 514 | 515 | /** 516 | * @ingroup defender_constants 517 | * @brief "port" key in the defender report. 518 | */ 519 | #define DEFENDER_REPORT_PORT_KEY DEFENDER_REPORT_SELECT_KEY( "port", "pt" ) 520 | 521 | /** 522 | * @ingroup defender_constants 523 | * @brief Length of the "port" key in the defender report. 524 | */ 525 | #define DEFENDER_REPORT_LENGTH_PORT_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_PORT_KEY ) 526 | 527 | /** 528 | * @ingroup defender_constants 529 | * @brief "interface" key in the defender report. 530 | */ 531 | #define DEFENDER_REPORT_INTERFACE_KEY DEFENDER_REPORT_SELECT_KEY( "interface", "if" ) 532 | 533 | /** 534 | * @ingroup defender_constants 535 | * @brief Length of the "interface" key in the defender report. 536 | */ 537 | #define DEFENDER_REPORT_LENGTH_INTERFACE_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_INTERFACE_KEY ) 538 | 539 | /** 540 | * @ingroup defender_constants 541 | * @brief "listening_udp_ports" key in the defender report. 542 | */ 543 | #define DEFENDER_REPORT_UDP_LISTENING_PORTS_KEY DEFENDER_REPORT_SELECT_KEY( "listening_udp_ports", "up" ) 544 | 545 | /** 546 | * @ingroup defender_constants 547 | * @brief Length of the "listening_udp_ports" key in the defender report. 548 | */ 549 | #define DEFENDER_REPORT_LENGTH_UDP_LISTENING_PORTS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_UDP_LISTENING_PORTS_KEY ) 550 | 551 | /** 552 | * @ingroup defender_constants 553 | * @brief "network_stats" key in the defender report. 554 | */ 555 | #define DEFENDER_REPORT_NETWORK_STATS_KEY DEFENDER_REPORT_SELECT_KEY( "network_stats", "ns" ) 556 | 557 | /** 558 | * @ingroup defender_constants 559 | * @brief Length of the "network_stats" key in the defender report. 560 | */ 561 | #define DEFENDER_REPORT_LENGTH_NETWORK_STATS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_NETWORK_STATS_KEY ) 562 | 563 | /** 564 | * @ingroup defender_constants 565 | * @brief "bytes_in" key in the defender report. 566 | */ 567 | #define DEFENDER_REPORT_BYTES_IN_KEY DEFENDER_REPORT_SELECT_KEY( "bytes_in", "bi" ) 568 | 569 | /** 570 | * @ingroup defender_constants 571 | * @brief Length of the "bytes_in" key in the defender report. 572 | */ 573 | #define DEFENDER_REPORT_LENGTH_BYTES_IN_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_BYTES_IN_KEY ) 574 | 575 | /** 576 | * @ingroup defender_constants 577 | * @brief "bytes_out" key in the defender report. 578 | */ 579 | #define DEFENDER_REPORT_BYTES_OUT_KEY DEFENDER_REPORT_SELECT_KEY( "bytes_out", "bo" ) 580 | 581 | /** 582 | * @ingroup defender_constants 583 | * @brief Length of the "bytes_out" key in the defender report. 584 | */ 585 | #define DEFENDER_REPORT_LENGTH_BYTES_OUT_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_BYTES_OUT_KEY ) 586 | 587 | /** 588 | * @ingroup defender_constants 589 | * @brief "packets_in" key in the defender report. 590 | */ 591 | #define DEFENDER_REPORT_PKTS_IN_KEY DEFENDER_REPORT_SELECT_KEY( "packets_in", "pi" ) 592 | 593 | /** 594 | * @ingroup defender_constants 595 | * @brief Length of the "packets_in" key in the defender report. 596 | */ 597 | #define DEFENDER_REPORT_LENGTH_PKTS_IN_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_PKTS_IN_KEY ) 598 | 599 | /** 600 | * @ingroup defender_constants 601 | * @brief "packets_out" key in the defender report. 602 | */ 603 | #define DEFENDER_REPORT_PKTS_OUT_KEY DEFENDER_REPORT_SELECT_KEY( "packets_out", "po" ) 604 | 605 | /** 606 | * @ingroup defender_constants 607 | * @brief Length of the "packets_out" key in the defender report. 608 | */ 609 | #define DEFENDER_REPORT_LENGTH_PKS_OUT_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LENGTH_PKS_OUT_KEY ) 610 | 611 | /** 612 | * @ingroup defender_constants 613 | * @brief "custom_metrics" key in the defender report. 614 | */ 615 | #define DEFENDER_REPORT_CUSTOM_METRICS_KEY DEFENDER_REPORT_SELECT_KEY( "custom_metrics", "cmet" ) 616 | 617 | /** 618 | * @ingroup defender_constants 619 | * @brief Length of the "custom_metrics" key in the defender report. 620 | */ 621 | #define DEFENDER_REPORT_LENGTH_CUSTOM_METRICS_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LENGTH_CUSTOM_METRICS_KEY ) 622 | 623 | /** 624 | * @ingroup defender_constants 625 | * @brief "number" key in the defender report. 626 | */ 627 | #define DEFENDER_REPORT_NUMBER_KEY "number" 628 | 629 | /** 630 | * @ingroup defender_constants 631 | * @brief Length of the "number" key in the defender report. 632 | */ 633 | #define DEFENDER_REPORT_LENGTH_NUMBER_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LENGTH_NUMBER_KEY ) 634 | 635 | /** 636 | * @ingroup defender_constants 637 | * @brief "number_list" key in the defender report. 638 | */ 639 | #define DEFENDER_REPORT_NUMBER_LIST_KEY "number_list" 640 | 641 | /** 642 | * @ingroup defender_constants 643 | * @brief Length of the "number_list" key in the defender report. 644 | */ 645 | #define DEFENDER_REPORT_LENGTH_NUMBER_LIST_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LENGTH_NUMBER_LIST_KEY ) 646 | 647 | /** 648 | * @ingroup defender_constants 649 | * @brief "string_list" key in the defender report. 650 | */ 651 | #define DEFENDER_REPORT_STRING_LIST_KEY "string_list" 652 | 653 | /** 654 | * @ingroup defender_constants 655 | * @brief Length of the "string_list" key in the defender report. 656 | */ 657 | #define DEFENDER_REPORT_LENGTH_STRING_LIST_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LENGTH_STRING_LIST_KEY ) 658 | 659 | /** 660 | * @ingroup defender_constants 661 | * @brief "ip_list" key in the defender report. 662 | */ 663 | #define DEFENDER_REPORT_IP_LIST_KEY "ip_list" 664 | 665 | /** 666 | * @ingroup defender_constants 667 | * @brief Length of the "ip_list" key in the defender report. 668 | */ 669 | #define DEFENDER_REPORT_LENGTH_IP_LIST_KEY STRING_LITERAL_LENGTH( DEFENDER_REPORT_LENGTH_IP_LIST_KEY ) 670 | 671 | /*-----------------------------------------------------------*/ 672 | 673 | /** 674 | * @brief Populate the topic string for a Device Defender operation. 675 | * 676 | * @param[in] pBuffer The buffer to write the topic string into. 677 | * @param[in] bufferLength The length of the buffer. 678 | * @param[in] pThingName The device's thingName as registered with AWS IoT. 679 | * @param[in] thingNameLength The length of the thing name. 680 | * @param[in] api The desired Device Defender API. 681 | * @param[out] pOutLength The length of the topic string written to the buffer. 682 | * 683 | * @return #DefenderSuccess if the topic string is written to the buffer; 684 | * #DefenderBadParameter if invalid parameters are passed; 685 | * #DefenderBufferTooSmall if the buffer cannot hold the full topic string. 686 | * 687 | * Example 688 | * @code{c} 689 | * 690 | * // The following example shows how to use the Defender_GetTopic API to 691 | * // generate a topic string for getting a JSON report accepted response. 692 | * 693 | * #define TOPIC_BUFFER_LENGTH ( 256U ) 694 | * 695 | * // Every device should have a unique thing name registered with AWS IoT Core. 696 | * // This example assumes that the device has a unique serial number which is 697 | * // registered as the thing name with AWS IoT Core. 698 | * const char * pThingName = GetDeviceSerialNumber(); 699 | * uint16_t thingNameLength = ( uint16_t )strlen( pThingname ); 700 | * char topicBuffer[ TOPIC_BUFFER_LENGTH ] = { 0 }; 701 | * uint16_t topicLength = 0; 702 | * DefenderStatus_t status = DefenderSuccess; 703 | * 704 | * status = Defender_GetTopic( &( topicBuffer[ 0 ] ), 705 | * TOPIC_BUFFER_LENGTH, 706 | * pThingName, 707 | * thingNameLength, 708 | * DefenderJsonReportAccepted, 709 | * &( topicLength ) ); 710 | * 711 | * if( status == DefenderSuccess ) 712 | * { 713 | * // The buffer topicBuffer contains the topic string of length 714 | * // topicLength for getting a JSON report accepted response. Subscribe 715 | * // to this topic using an MQTT client of your choice. 716 | * } 717 | * @endcode 718 | */ 719 | /* @[declare_defender_gettopic] */ 720 | DefenderStatus_t Defender_GetTopic( char * pBuffer, 721 | uint16_t bufferLength, 722 | const char * pThingName, 723 | uint16_t thingNameLength, 724 | DefenderTopic_t api, 725 | uint16_t * pOutLength ); 726 | /* @[declare_defender_gettopic] */ 727 | 728 | /*-----------------------------------------------------------*/ 729 | 730 | /** 731 | * @brief Check if the given topic is one of the Device Defender topics. 732 | * 733 | * The function outputs which API the topic is for. It also optionally outputs 734 | * the starting location and length of the thing name in the given topic. 735 | * 736 | * @param[in] pTopic The topic string to check. 737 | * @param[in] topicLength The length of the topic string. 738 | * @param[out] pOutApi The defender topic API value. 739 | * @param[out] ppOutThingName Optional parameter to output the beginning of the 740 | * thing name in the topic string. Pass NULL if not needed. 741 | * @param[out] pOutThingNameLength Optional parameter to output the length of 742 | * the thing name in the topic string. Pass NULL if not needed. 743 | * 744 | * @return #DefenderSuccess if the topic is one of the defender topics; 745 | * #DefenderBadParameter if invalid parameters are passed; 746 | * #DefenderNoMatch if the topic is NOT one of the defender topics (parameter 747 | * pOutApi gets #DefenderInvalidTopic). 748 | * 749 | * Example 750 | * @code{c} 751 | * 752 | * // The following example shows how to use the Defender_MatchTopic API to 753 | * // check if an incoming MQTT publish message is a Device Defender message. 754 | * 755 | * DefenderTopic_t api; 756 | * DefenderStatus_t status = DefenderSuccess; 757 | * 758 | * // pTopic and topicLength are the topic string and length of the topic on 759 | * // which the publish message is received. These are usually provided by the 760 | * // MQTT client used. We pass the last two argument as NULL as we are not 761 | * // interested in the thing name in the topic string. 762 | * status = Defender_MatchTopic( pTopic, 763 | * topicLength, 764 | * &( api ), 765 | * NULL, 766 | * NULL ); 767 | * 768 | * if( status == DefenderSuccess ) 769 | * { 770 | * if( api == DefenderJsonReportAccepted ) 771 | * { 772 | * // The published JSON report was accepted by the AWS IoT Device 773 | * // Defender service. You can parse the response using your choice 774 | * // of JSON parser and ensure that the report Id is same as was sent 775 | * // in the defender report. 776 | * } 777 | * else if( api == DefenderJsonReportRejected ) 778 | * { 779 | * // The published JSON report was rejected by the AWS IoT Device 780 | * // Defender service. 781 | * } 782 | * else 783 | * { 784 | * // Unexpected response. 785 | * } 786 | * } 787 | * @endcode 788 | */ 789 | /* @[declare_defender_matchtopic] */ 790 | DefenderStatus_t Defender_MatchTopic( const char * pTopic, 791 | uint16_t topicLength, 792 | DefenderTopic_t * pOutApi, 793 | const char ** ppOutThingName, 794 | uint16_t * pOutThingNameLength ); 795 | /* @[declare_defender_matchtopic] */ 796 | 797 | /*-----------------------------------------------------------*/ 798 | 799 | /* *INDENT-OFF* */ 800 | #ifdef __cplusplus 801 | } 802 | #endif 803 | /* *INDENT-ON* */ 804 | 805 | #endif /* DEFENDER_H_ */ 806 | -------------------------------------------------------------------------------- /source/include/defender_config_defaults.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file defender_config_defaults.h 27 | * @brief Default config values for the AWS IoT Device Defender Client Library. 28 | */ 29 | 30 | #ifndef DEFENDER_CONFIG_DEFAULTS_H_ 31 | #define DEFENDER_CONFIG_DEFAULTS_H_ 32 | 33 | /* The macro definition for DEFENDER_DO_NOT_USE_CUSTOM_CONFIG is for Doxygen 34 | * documentation only. */ 35 | 36 | /** 37 | * @brief Define this macro to build the AWS IoT Device Defender Client Library 38 | * without the custom config file defender_config.h. 39 | * 40 | * Without the custom config, the the AWS IoT Device Defender Client Library 41 | * builds with default values of config macros defined in the 42 | * defender_config_defaults.h file. 43 | * 44 | * If a custom config file is provided, then DEFENDER_DO_NOT_USE_CUSTOM_CONFIG 45 | * must not be defined. 46 | * 47 | * Default value: DEFENDER_DO_NOT_USE_CUSTOM_CONFIG is not defined 48 | * by default and the library expects a defender_config.h file. 49 | */ 50 | #ifdef DOXYGEN 51 | #define DEFENDER_DO_NOT_USE_CUSTOM_CONFIG 52 | #endif 53 | 54 | /** 55 | * @brief Set it to 1 to enable use of long key names in the defender report. 56 | * 57 | * AWS IoT Device Defender Service supports both long and short names for keys 58 | * in the report sent by a device. For example, 59 | * 60 | * A device defender report using long key names: 61 | * @code{c} 62 | * { 63 | * "header": { 64 | * "report_id": 1530304554, 65 | * "version": "1.0" 66 | * }, 67 | * "metrics": { 68 | * "network_stats": { 69 | * "bytes_in": 29358693495, 70 | * "bytes_out": 26485035, 71 | * "packets_in": 10013573555, 72 | * "packets_out": 11382615 73 | * } 74 | * } 75 | * } 76 | * @endcode 77 | * 78 | * An equivalent report using short key names: 79 | * @code{c} 80 | * { 81 | * "hed": { 82 | * "rid": 1530304554, 83 | * "v": "1.0" 84 | * }, 85 | * "met": { 86 | * "ns": { 87 | * "bi": 29358693495, 88 | * "bo": 26485035, 89 | * "pi": 10013573555, 90 | * "po": 11382615 91 | * } 92 | * } 93 | * } 94 | * @endcode 95 | * 96 | * Default value: 0 as short key names are preferred option for resource 97 | * constrained devices because they result in smaller report size. If you want 98 | * to use long key names instead, set DEFENDER_USE_LONG_KEYS to 1 in the 99 | * defender_config.h file. 100 | */ 101 | #ifndef DEFENDER_USE_LONG_KEYS 102 | #define DEFENDER_USE_LONG_KEYS 0 103 | #endif 104 | 105 | /** 106 | * @brief Macro used in the Device Defender client library to log error messages. 107 | * 108 | * To enable error logging, this macro should be mapped to an 109 | * application-specific logging implementation. 110 | * 111 | * @note This logging macro is called in the Device Defender client library with 112 | * parameters wrapped in double parentheses to be ISO C89/C90 standard 113 | * compliant. For a reference POSIX implementation of the logging macros, refer 114 | * to the defender_config.h file, and the logging-stack in demos folder of the 115 | * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). 116 | * 117 | * Default value: Error logs are turned off, and no code is generated 118 | * for calls to the macro in the Device Defender client library on compilation. 119 | */ 120 | #ifndef LogError 121 | #define LogError( message ) 122 | #endif 123 | 124 | /** 125 | * @brief Macro used in the Device Defender client library to log warning messages. 126 | * 127 | * To enable warning logging, this macro should be mapped to an 128 | * application-specific logging implementation. 129 | * 130 | * @note This logging macro is called in the Device Defender client library with 131 | * parameters wrapped in double parentheses to be ISO C89/C90 standard 132 | * compliant. For a reference POSIX implementation of the logging macros, refer 133 | * to the defender_config.h file, and the logging-stack in demos folder of the 134 | * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). 135 | * 136 | * Default value: Warning logs are turned off, and no code is generated 137 | * for calls to the macro in the Device Defender client library on compilation. 138 | */ 139 | #ifndef LogWarn 140 | #define LogWarn( message ) 141 | #endif 142 | 143 | /** 144 | * @brief Macro used in the Device Defender client library to log info messages. 145 | * 146 | * To enable info logging, this macro should be mapped to an 147 | * application-specific logging implementation. 148 | * 149 | * @note This logging macro is called in the Device Defender client library with 150 | * parameters wrapped in double parentheses to be ISO C89/C90 standard 151 | * compliant. For a reference POSIX implementation of the logging macros, refer 152 | * to the defender_config.h file, and the logging-stack in demos folder of the 153 | * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). 154 | * 155 | * Default value: Info logs are turned off, and no code is generated for 156 | * calls to the macro in the Device Defender client library on compilation. 157 | */ 158 | #ifndef LogInfo 159 | #define LogInfo( message ) 160 | #endif 161 | 162 | /** 163 | * @brief Macro used in the Device Defender client library to log debug messages. 164 | * 165 | * To enable debug logging, this macro should be mapped to an 166 | * application-specific logging implementation. 167 | * 168 | * @note This logging macro is called in the Device Defender client library with 169 | * parameters wrapped in double parentheses to be ISO C89/C90 standard 170 | * compliant. For a reference POSIX implementation of the logging macros, refer 171 | * to the defender_config.h file, and the logging-stack in demos folder of the 172 | * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). 173 | * 174 | * Default value: Debug logs are turned off, and no code is generated for 175 | * calls to the macro in the Device Defender client library on compilation. 176 | */ 177 | #ifndef LogDebug 178 | #define LogDebug( message ) 179 | #endif 180 | 181 | #endif /* DEFENDER_CONFIG_DEFAULTS_H_ */ 182 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required ( VERSION 3.13.0 ) 2 | project ( "Defender unit test" 3 | VERSION 1.4.0 4 | LANGUAGES C ) 5 | 6 | # Allow the project to be organized into folders. 7 | set_property( GLOBAL PROPERTY USE_FOLDERS ON ) 8 | 9 | # Use C90. 10 | set( CMAKE_C_STANDARD 90 ) 11 | set( CMAKE_C_STANDARD_REQUIRED ON ) 12 | 13 | # If no configuration is defined, turn everything on. 14 | if( NOT DEFINED COV_ANALYSIS AND NOT DEFINED UNITTEST ) 15 | set( COV_ANALYSIS TRUE ) 16 | set( UNITTEST TRUE ) 17 | endif() 18 | 19 | # Do not allow in-source build. 20 | if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) 21 | message( FATAL_ERROR "In-source build is not allowed. Please build in a separate directory, such as ${PROJECT_SOURCE_DIR}/build." ) 22 | endif() 23 | 24 | # Set global path variable. 25 | get_filename_component( __MODULE_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE ) 26 | set( MODULE_ROOT_DIR ${__MODULE_ROOT_DIR} CACHE INTERNAL "Device Defender repository root." ) 27 | set( UNIT_TEST_DIR ${MODULE_ROOT_DIR}/test/unit-test CACHE INTERNAL "Device Defender unit test directory." ) 28 | set( UNITY_DIR ${UNIT_TEST_DIR}/Unity CACHE INTERNAL "Unity library source directory." ) 29 | 30 | # Configure options to always show in CMake GUI. 31 | option( BUILD_CLONE_SUBMODULES 32 | "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." 33 | ON ) 34 | 35 | # Set output directories. 36 | set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) 37 | set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) 38 | set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) 39 | 40 | # ====================== Coverity Analysis Configuration ====================== 41 | if( COV_ANALYSIS ) 42 | # Include filepaths for source and include. 43 | include( ${MODULE_ROOT_DIR}/defenderFilePaths.cmake ) 44 | 45 | # Target for Coverity analysis that builds the library. 46 | add_library( coverity_analysis 47 | ${DEFENDER_SOURCES} ) 48 | 49 | # Device Defender public include path. 50 | target_include_directories( coverity_analysis 51 | PUBLIC 52 | ${DEFENDER_INCLUDE_PUBLIC_DIRS} 53 | "${CMAKE_CURRENT_LIST_DIR}/include" ) 54 | 55 | # Disable logging/assert() calls when building the Coverity analysis target 56 | target_compile_options(coverity_analysis PUBLIC -DNDEBUG -DDISABLE_LOGGING ) 57 | endif() 58 | 59 | # ============================ Test Configuration ============================ 60 | if( UNITTEST ) 61 | # Include Unity build configuration. 62 | include( unit-test/unity_build.cmake ) 63 | 64 | # Check if the Unity source directory exists. If it does not exist and the 65 | # BUILD_CLONE_SUBMODULES configuration is enabled, clone the Unity submodule. 66 | if( NOT EXISTS ${UNITY_DIR}/src ) 67 | # Attempt to clone Unity. 68 | if( ${BUILD_CLONE_SUBMODULES} ) 69 | clone_unity() 70 | else() 71 | message( FATAL_ERROR "The required submodule Unity does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) 72 | endif() 73 | endif() 74 | 75 | # Use CTest utility for managing test runs. 76 | enable_testing() 77 | 78 | # Add build target for Unity, required for unit testing. 79 | add_unity_target() 80 | 81 | # Add functions to enable Unity based tests and coverage. 82 | include( ${MODULE_ROOT_DIR}/tools/unity/create_test.cmake ) 83 | 84 | # Include build configuration for unit tests. 85 | add_subdirectory( unit-test ) 86 | 87 | # ====================== Coverage Analysis configuration ====================== 88 | 89 | # Add a target for running coverage on tests. 90 | add_custom_target( coverage 91 | COMMAND ${CMAKE_COMMAND} -DUNITY_DIR=${UNITY_DIR} 92 | -P ${MODULE_ROOT_DIR}/tools/unity/coverage.cmake 93 | DEPENDS unity defender_utest 94 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) 95 | endif() 96 | -------------------------------------------------------------------------------- /test/cbmc/.gitignore: -------------------------------------------------------------------------------- 1 | # Emitted when running CBMC proofs 2 | proofs/**/logs 3 | proofs/**/gotos 4 | proofs/**/report 5 | proofs/**/html 6 | proofs/output 7 | 8 | # Emitted by CBMC Viewer 9 | TAGS-* 10 | 11 | # Emitted by Arpa 12 | arpa_cmake/ 13 | arpa-validation-logs/ 14 | Makefile.arpa 15 | 16 | # Emitted by litani 17 | .ninja_deps 18 | .ninja_log 19 | .litani_cache_dir 20 | 21 | # These files should be overwritten whenever prepare.py runs 22 | cbmc-batch.yaml 23 | 24 | __pycache__/ 25 | -------------------------------------------------------------------------------- /test/cbmc/include/README.md: -------------------------------------------------------------------------------- 1 | CBMC proof include files 2 | ======================== 3 | 4 | This directory contains include files written for CBMC proof. It is 5 | common to write some code to model aspects of the system under test, 6 | and the header files for this code go here. 7 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_GetTopic/Defender_GetTopic_harness.c: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file Defender_GetTopic_harness.c 27 | * @brief Implements the proof harness for Defender_GetTopic function. 28 | */ 29 | 30 | #include "defender.h" 31 | 32 | void harness() 33 | { 34 | char * pTopicBuffer; 35 | uint16_t topicBufferLength; 36 | char * pThingName; 37 | uint16_t thingNameLength; 38 | DefenderTopic_t api; 39 | uint16_t * pOutLength; 40 | 41 | __CPROVER_assume( topicBufferLength < CBMC_MAX_OBJECT_SIZE ); 42 | 43 | /* +1 is to ensure that we run the function for invalid thing name length as 44 | * well. */ 45 | __CPROVER_assume( thingNameLength <= ( DEFENDER_THINGNAME_MAX_LENGTH + 1 ) ); 46 | 47 | pTopicBuffer = malloc( topicBufferLength ); 48 | pThingName = malloc( thingNameLength ); 49 | pOutLength = malloc( sizeof( *pOutLength ) ); 50 | 51 | Defender_GetTopic( pTopicBuffer, 52 | topicBufferLength, 53 | pThingName, 54 | thingNameLength, 55 | api, 56 | pOutLength ); 57 | } 58 | /*-----------------------------------------------------------*/ 59 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_GetTopic/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | HARNESS_ENTRY=harness 5 | HARNESS_FILE=Defender_GetTopic_harness 6 | 7 | # This should be a unique identifier for this proof, and will appear on the 8 | # Litani dashboard. It can be human-readable and contain spaces if you wish. 9 | PROOF_UID = Defender_GetTopic 10 | 11 | DEFINES += 12 | INCLUDES += 13 | 14 | REMOVE_FUNCTION_BODY += 15 | UNWINDSET += 16 | 17 | PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c 18 | PROJECT_SOURCES += $(SRCDIR)/source/defender.c 19 | 20 | include ../Makefile.common 21 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_GetTopic/README.md: -------------------------------------------------------------------------------- 1 | Defender_GetTopic proof 2 | ============== 3 | 4 | This directory contains a memory safety proof for Defender_GetTopic. 5 | 6 | To run the proof. 7 | ------------- 8 | 9 | * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer 10 | to your path. 11 | * Run "make". 12 | * Open html/index.html in a web browser. 13 | 14 | To use [`arpa`](https://awslabs.github.io/aws-proof-build-assistant) to simplify writing Makefiles. 15 | ------------- 16 | 17 | * Run "make arpa" to generate a Makefile.arpa that contains relevant build information for the proof. 18 | * Use Makefile.arpa as the starting point for your proof Makefile by: 19 | 1. Modifying Makefile.arpa (if required). 20 | 2. Including Makefile.arpa into the existing proof Makefile (add `sinclude Makefile.arpa` at the bottom of the Makefile, right before `include ../Makefile.common`). -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_GetTopic/cbmc-proof.txt: -------------------------------------------------------------------------------- 1 | # This file marks this directory as containing a CBMC proof. 2 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_GetTopic/cbmc-viewer.json: -------------------------------------------------------------------------------- 1 | { "expected-missing-functions": 2 | [ 3 | 4 | ], 5 | "proof-name": "Defender_GetTopic", 6 | "proof-root": "test/cbmc/proofs" 7 | } -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_MatchTopic/Defender_MatchTopic_harness.c: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file Defender_MatchTopic_harness.c 27 | * @brief Implements the proof harness for Defender_MatchTopic function. 28 | */ 29 | 30 | #include "defender.h" 31 | 32 | void harness() 33 | { 34 | const char * pTopic; 35 | uint16_t topicLength; 36 | DefenderTopic_t * pOutApi; 37 | const char ** ppOutThingName; 38 | uint16_t * pOutThingNameLength; 39 | 40 | __CPROVER_assume( topicLength < TOPIC_STRING_LENGTH_MAX ); 41 | 42 | pTopic = malloc( topicLength ); 43 | pOutApi = malloc( sizeof( *pOutApi ) ); 44 | ppOutThingName = malloc( sizeof( *ppOutThingName ) ); 45 | pOutThingNameLength = malloc( sizeof( *pOutThingNameLength ) ); 46 | 47 | Defender_MatchTopic( pTopic, 48 | topicLength, 49 | pOutApi, 50 | ppOutThingName, 51 | pOutThingNameLength ); 52 | } 53 | /*-----------------------------------------------------------*/ 54 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_MatchTopic/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | HARNESS_ENTRY=harness 5 | HARNESS_FILE=Defender_MatchTopic_harness 6 | 7 | # This should be a unique identifier for this proof, and will appear on the 8 | # Litani dashboard. It can be human-readable and contain spaces if you wish. 9 | PROOF_UID = Defender_MatchTopic 10 | 11 | # The topic length is bounded to reduce the proof run time. Memory safety on the 12 | # buffer holding the topic string can be proven within a reasonable bound. It 13 | # adds no value to the proof to input the largest possible topic string accepted 14 | # by AWS (64KB). 15 | TOPIC_STRING_LENGTH_MAX=200 16 | 17 | DEFINES += -DTOPIC_STRING_LENGTH_MAX=$(TOPIC_STRING_LENGTH_MAX) 18 | INCLUDES += 19 | 20 | REMOVE_FUNCTION_BODY += 21 | 22 | # The longest strncmp is in matchBridge function which matches DEFENDER_API_BRIDGE 23 | # length of which is 19. We unwind one more time than the bridge length. 24 | DEFENDER_API_BRIDGE_LENGTH=20 25 | UNWINDSET += strncmp.0:$(DEFENDER_API_BRIDGE_LENGTH) 26 | 27 | # Enough to unwind the extractThingNameLength loop TOPIC_STRING_LENGTH_MAX times 28 | # as thingname in the topic string can not be longer than the topic string 29 | # length. 30 | UNWINDSET += __CPROVER_file_local_defender_c_extractThingNameLength.0:$(TOPIC_STRING_LENGTH_MAX) 31 | 32 | # Total defender APIs are 6: 33 | # DefenderJsonReportPublish 34 | # DefenderJsonReportAccepted 35 | # DefenderJsonReportRejected 36 | # DefenderCborReportPublish 37 | # DefenderCborReportAccepted 38 | # DefenderCborReportRejected 39 | # 40 | # We unwind one more than the total API count. 41 | DEFENDER_API_COUNT=7 42 | UNWINDSET += __CPROVER_file_local_defender_c_matchApi.0:$(DEFENDER_API_COUNT) 43 | 44 | PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c 45 | PROJECT_SOURCES += $(SRCDIR)/source/defender.c 46 | 47 | include ../Makefile.common 48 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_MatchTopic/README.md: -------------------------------------------------------------------------------- 1 | Defender_MatchTopic proof 2 | ============== 3 | 4 | This directory contains a memory safety proof for Defender_MatchTopic. 5 | 6 | To run the proof. 7 | ------------- 8 | 9 | * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer 10 | to your path. 11 | * Run "make". 12 | * Open html/index.html in a web browser. 13 | 14 | To use [`arpa`](https://awslabs.github.io/aws-proof-build-assistant) to simplify writing Makefiles. 15 | ------------- 16 | 17 | * Run "make arpa" to generate a Makefile.arpa that contains relevant build information for the proof. 18 | * Use Makefile.arpa as the starting point for your proof Makefile by: 19 | 1. Modifying Makefile.arpa (if required). 20 | 2. Including Makefile.arpa into the existing proof Makefile (add `sinclude Makefile.arpa` at the bottom of the Makefile, right before `include ../Makefile.common`). -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_MatchTopic/cbmc-proof.txt: -------------------------------------------------------------------------------- 1 | # This file marks this directory as containing a CBMC proof. 2 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Defender_MatchTopic/cbmc-viewer.json: -------------------------------------------------------------------------------- 1 | { "expected-missing-functions": 2 | [ 3 | 4 | ], 5 | "proof-name": "Defender_MatchTopic", 6 | "proof-root": "test/cbmc/proofs" 7 | } -------------------------------------------------------------------------------- /test/cbmc/proofs/Makefile-project-defines: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # The first line sets the emacs major mode to Makefile 3 | 4 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # SPDX-License-Identifier: MIT-0 6 | 7 | ################################################################ 8 | # Use this file to give project-specific definitions of the command 9 | # line arguments to pass to CBMC tools like goto-cc to build the goto 10 | # binaries and cbmc to do the property and coverage checking. 11 | # 12 | # Use this file to override most default definitions of variables in 13 | # Makefile.common. 14 | ################################################################ 15 | 16 | # Name of this proof project, displayed in proof reports. For example, 17 | # "s2n" or "Amazon FreeRTOS". For projects with multiple proof roots, 18 | # this may be overridden on the command-line to Make, for example 19 | # 20 | # make PROJECT_NAME="FreeRTOS MQTT" report 21 | # 22 | PROJECT_NAME = "FreeRTOS Device Defender" 23 | 24 | # Path to litani executable, relative to the root of the repository. 25 | # This applies even for projects with multiple proof roots. 26 | LITANI ?= litani 27 | 28 | # Flags to pass to goto-cc for compilation (typically those passed to gcc -c) 29 | COMPILE_FLAGS += -fPIC 30 | COMPILE_FLAGS += -std=gnu90 31 | 32 | # Flags to pass to goto-cc for linking (typically those passed to gcc) 33 | # LINK_FLAGS = 34 | 35 | # Preprocessor include paths -I... 36 | # Consider adding 37 | # INCLUDES += -I$(CBMC_ROOT)/include 38 | # You will want to decide what order that comes in relative to the other 39 | # include directories in your project. 40 | # 41 | INCLUDES += -I$(SRCDIR)/test/cbmc/include 42 | INCLUDES += -I$(SRCDIR)/source/include 43 | INCLUDES += -I$(SRCDIR)/source 44 | INCLUDES += -I$(SRCDIR)/test/include 45 | 46 | # Preprocessor definitions -D... 47 | DEFINES += -Ddefender_EXPORTS 48 | 49 | # Path to arpa executable 50 | # ARPA = 51 | 52 | # Flags to pass to cmake for building the project 53 | # ARPA_CMAKE_FLAGS = 54 | 55 | # Additional CBMC flags used for property checking 56 | # TODO - Remove these once they are in the starter kit. 57 | CHECKFLAGS += --pointer-primitive-check 58 | CHECKFLAGS += --malloc-may-fail 59 | CHECKFLAGS += --malloc-fail-null 60 | 61 | COVERFLAGS += --malloc-may-fail 62 | COVERFLAGS += --malloc-fail-null 63 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Makefile-project-targets: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # The first line sets the emacs major mode to Makefile 3 | 4 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # SPDX-License-Identifier: MIT-0 6 | 7 | ################################################################ 8 | # Use this file to give project-specific targets, including targets 9 | # that may depend on targets defined in Makefile.common. 10 | ################################################################ 11 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Makefile-project-testing: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # The first line sets the emacs major mode to Makefile 3 | 4 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # SPDX-License-Identifier: MIT-0 6 | 7 | ################################################################ 8 | # Use this file to define project-specific targets and definitions for 9 | # unit testing or continuous integration that may depend on targets 10 | # defined in Makefile.common 11 | ################################################################ 12 | -------------------------------------------------------------------------------- /test/cbmc/proofs/Makefile-template-defines: -------------------------------------------------------------------------------- 1 | SRCDIR ?= $(abspath $(PROOF_ROOT)/../../..) 2 | -------------------------------------------------------------------------------- /test/cbmc/proofs/README.md: -------------------------------------------------------------------------------- 1 | CBMC proofs 2 | =========== 3 | 4 | This directory contains the CBMC proofs. Each proof is in its own 5 | directory. 6 | 7 | This directory includes four Makefiles. 8 | 9 | One Makefile describes the basic workflow for building and running proofs: 10 | 11 | * Makefile.common: 12 | * make: builds the goto binary, does the cbmc property checking 13 | and coverage checking, and builds the final report. 14 | * make goto: builds the goto binary 15 | * make result: does cbmc property checking 16 | * make coverage: does cbmc coverage checking 17 | * make report: builds the final report 18 | 19 | Three included Makefiles describe project-specific settings and can override 20 | definitions in Makefile.common: 21 | 22 | * Makefile-project-defines: definitions like compiler flags 23 | required to build the goto binaries, and definitions to override 24 | definitions in Makefile.common. 25 | * Makefile-project-targets: other make targets needed for the project 26 | * Makefile-project-testing: other definitions and targets needed for 27 | unit testing or continuous integration. 28 | -------------------------------------------------------------------------------- /test/cbmc/proofs/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/33f9087d4b6db3c5c024c3254426bbba30a0f10a/test/cbmc/proofs/lib/__init__.py -------------------------------------------------------------------------------- /test/cbmc/proofs/lib/print_tool_versions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | 7 | import logging 8 | import pathlib 9 | import shutil 10 | import subprocess 11 | 12 | 13 | _TOOLS = [ 14 | "cadical", 15 | "cbmc", 16 | "cbmc-viewer", 17 | "cbmc-starter-kit-update", 18 | "kissat", 19 | "litani", 20 | ] 21 | 22 | 23 | def _format_versions(table): 24 | lines = [ 25 | "", 26 | '', 27 | ] 28 | for tool, version in table.items(): 29 | if version: 30 | v_str = f'
{version}
' 31 | else: 32 | v_str = 'not found' 33 | lines.append( 34 | f'' 36 | f'') 37 | lines.append("
Tool Versions
{tool}:{v_str}
") 38 | return "\n".join(lines) 39 | 40 | 41 | def _get_tool_versions(): 42 | ret = {} 43 | for tool in _TOOLS: 44 | err = f"Could not determine version of {tool}: " 45 | ret[tool] = None 46 | if not shutil.which(tool): 47 | logging.error("%s'%s' not found on $PATH", err, tool) 48 | continue 49 | cmd = [tool, "--version"] 50 | proc = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE) 51 | try: 52 | out, _ = proc.communicate(timeout=10) 53 | except subprocess.TimeoutExpired: 54 | logging.error("%s'%s --version' timed out", err, tool) 55 | continue 56 | if proc.returncode: 57 | logging.error( 58 | "%s'%s --version' returned %s", err, tool, str(proc.returncode)) 59 | continue 60 | ret[tool] = out.strip() 61 | return ret 62 | 63 | 64 | def main(): 65 | exe_name = pathlib.Path(__file__).name 66 | logging.basicConfig(format=f"{exe_name}: %(message)s") 67 | 68 | table = _get_tool_versions() 69 | out = _format_versions(table) 70 | print(out) 71 | 72 | 73 | if __name__ == "__main__": 74 | main() 75 | -------------------------------------------------------------------------------- /test/cbmc/proofs/lib/summarize.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import argparse 5 | import json 6 | import logging 7 | import os 8 | import sys 9 | 10 | 11 | DESCRIPTION = """Print 2 tables in GitHub-flavored Markdown that summarize 12 | an execution of CBMC proofs.""" 13 | 14 | 15 | def get_args(): 16 | """Parse arguments for summarize script.""" 17 | parser = argparse.ArgumentParser(description=DESCRIPTION) 18 | for arg in [{ 19 | "flags": ["--run-file"], 20 | "help": "path to the Litani run.json file", 21 | "required": True, 22 | }]: 23 | flags = arg.pop("flags") 24 | parser.add_argument(*flags, **arg) 25 | return parser.parse_args() 26 | 27 | 28 | def _get_max_length_per_column_list(data): 29 | ret = [len(item) + 1 for item in data[0]] 30 | for row in data[1:]: 31 | for idx, item in enumerate(row): 32 | ret[idx] = max(ret[idx], len(item) + 1) 33 | return ret 34 | 35 | 36 | def _get_table_header_separator(max_length_per_column_list): 37 | line_sep = "" 38 | for max_length_of_word_in_col in max_length_per_column_list: 39 | line_sep += "|" + "-" * (max_length_of_word_in_col + 1) 40 | line_sep += "|\n" 41 | return line_sep 42 | 43 | 44 | def _get_entries(max_length_per_column_list, row_data): 45 | entries = [] 46 | for row in row_data: 47 | entry = "" 48 | for idx, word in enumerate(row): 49 | max_length_of_word_in_col = max_length_per_column_list[idx] 50 | space_formatted_word = (max_length_of_word_in_col - len(word)) * " " 51 | entry += "| " + word + space_formatted_word 52 | entry += "|\n" 53 | entries.append(entry) 54 | return entries 55 | 56 | 57 | def _get_rendered_table(data): 58 | table = [] 59 | max_length_per_column_list = _get_max_length_per_column_list(data) 60 | entries = _get_entries(max_length_per_column_list, data) 61 | for idx, entry in enumerate(entries): 62 | if idx == 1: 63 | line_sep = _get_table_header_separator(max_length_per_column_list) 64 | table.append(line_sep) 65 | table.append(entry) 66 | table.append("\n") 67 | return "".join(table) 68 | 69 | 70 | def _get_status_and_proof_summaries(run_dict): 71 | """Parse a dict representing a Litani run and create lists summarizing the 72 | proof results. 73 | 74 | Parameters 75 | ---------- 76 | run_dict 77 | A dictionary representing a Litani run. 78 | 79 | 80 | Returns 81 | ------- 82 | A list of 2 lists. 83 | The first sub-list maps a status to the number of proofs with that status. 84 | The second sub-list maps each proof to its status. 85 | """ 86 | count_statuses = {} 87 | proofs = [["Proof", "Status"]] 88 | for proof_pipeline in run_dict["pipelines"]: 89 | status_pretty_name = proof_pipeline["status"].title().replace("_", " ") 90 | try: 91 | count_statuses[status_pretty_name] += 1 92 | except KeyError: 93 | count_statuses[status_pretty_name] = 1 94 | if proof_pipeline["name"] == "print_tool_versions": 95 | continue 96 | proofs.append([proof_pipeline["name"], status_pretty_name]) 97 | statuses = [["Status", "Count"]] 98 | for status, count in count_statuses.items(): 99 | statuses.append([status, str(count)]) 100 | return [statuses, proofs] 101 | 102 | 103 | def print_proof_results(out_file): 104 | """ 105 | Print 2 strings that summarize the proof results. 106 | When printing, each string will render as a GitHub flavored Markdown table. 107 | """ 108 | output = "## Summary of CBMC proof results\n\n" 109 | with open(out_file, encoding='utf-8') as run_json: 110 | run_dict = json.load(run_json) 111 | status_table, proof_table = _get_status_and_proof_summaries(run_dict) 112 | for summary in (status_table, proof_table): 113 | output += _get_rendered_table(summary) 114 | 115 | print(output) 116 | sys.stdout.flush() 117 | 118 | github_summary_file = os.getenv("GITHUB_STEP_SUMMARY") 119 | if github_summary_file: 120 | with open(github_summary_file, "a") as handle: 121 | print(output, file=handle) 122 | handle.flush() 123 | else: 124 | logging.warning( 125 | "$GITHUB_STEP_SUMMARY not set, not writing summary file") 126 | 127 | msg = ( 128 | "Click the 'Summary' button to view a Markdown table " 129 | "summarizing all proof results") 130 | if run_dict["status"] != "success": 131 | logging.error("Not all proofs passed.") 132 | logging.error(msg) 133 | sys.exit(1) 134 | logging.info(msg) 135 | 136 | 137 | if __name__ == '__main__': 138 | args = get_args() 139 | logging.basicConfig(format="%(levelname)s: %(message)s") 140 | try: 141 | print_proof_results(args.run_file) 142 | except Exception as ex: # pylint: disable=broad-except 143 | logging.critical("Could not print results. Exception: %s", str(ex)) 144 | -------------------------------------------------------------------------------- /test/cbmc/proofs/run-cbmc-proofs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | 6 | 7 | import argparse 8 | import asyncio 9 | import json 10 | import logging 11 | import math 12 | import os 13 | import pathlib 14 | import re 15 | import subprocess 16 | import sys 17 | import tempfile 18 | 19 | from lib.summarize import print_proof_results 20 | 21 | 22 | DESCRIPTION = "Configure and run all CBMC proofs in parallel" 23 | 24 | # Keep the epilog hard-wrapped at 70 characters, as it gets printed 25 | # verbatim in the terminal. 70 characters stops here --------------> | 26 | EPILOG = """ 27 | This tool automates the process of running `make report` in each of 28 | the CBMC proof directories. The tool calculates the dependency graph 29 | of all tasks needed to build, run, and report on all the proofs, and 30 | executes these tasks in parallel. 31 | 32 | The tool is roughly equivalent to doing this: 33 | 34 | litani init --project "my-cool-project"; 35 | 36 | find . -name cbmc-proof.txt | while read -r proof; do 37 | pushd $(dirname ${proof}); 38 | 39 | # The `make _report` rule adds a single proof to litani 40 | # without running it 41 | make _report; 42 | 43 | popd; 44 | done 45 | 46 | litani run-build; 47 | 48 | except that it is much faster and provides some convenience options. 49 | The CBMC CI runs this script with no arguments to build and run all 50 | proofs in parallel. The value of "my-cool-project" is taken from the 51 | PROJECT_NAME variable in Makefile-project-defines. 52 | 53 | The --no-standalone argument omits the `litani init` and `litani 54 | run-build`; use it when you want to add additional proof jobs, not 55 | just the CBMC ones. In that case, you would run `litani init` 56 | yourself; then run `run-cbmc-proofs --no-standalone`; add any 57 | additional jobs that you want to execute with `litani add-job`; and 58 | finally run `litani run-build`. 59 | 60 | The litani dashboard will be written under the `output` directory; the 61 | cbmc-viewer reports remain in the `$PROOF_DIR/report` directory. The 62 | HTML dashboard from the latest Litani run will always be symlinked to 63 | `output/latest/html/index.html`, so you can keep that page open in 64 | your browser and reload the page whenever you re-run this script. 65 | """ 66 | # 70 characters stops here ----------------------------------------> | 67 | 68 | 69 | def get_project_name(): 70 | cmd = [ 71 | "make", 72 | "--no-print-directory", 73 | "-f", "Makefile.common", 74 | "echo-project-name", 75 | ] 76 | logging.debug(" ".join(cmd)) 77 | proc = subprocess.run(cmd, universal_newlines=True, stdout=subprocess.PIPE, check=False) 78 | if proc.returncode: 79 | logging.critical("could not run make to determine project name") 80 | sys.exit(1) 81 | if not proc.stdout.strip(): 82 | logging.warning( 83 | "project name has not been set; using generic name instead. " 84 | "Set the PROJECT_NAME value in Makefile-project-defines to " 85 | "remove this warning") 86 | return "" 87 | return proc.stdout.strip() 88 | 89 | 90 | def get_args(): 91 | pars = argparse.ArgumentParser( 92 | description=DESCRIPTION, epilog=EPILOG, 93 | formatter_class=argparse.RawDescriptionHelpFormatter) 94 | for arg in [{ 95 | "flags": ["-j", "--parallel-jobs"], 96 | "type": int, 97 | "metavar": "N", 98 | "help": "run at most N proof jobs in parallel", 99 | }, { 100 | "flags": ["--fail-on-proof-failure"], 101 | "action": "store_true", 102 | "help": "exit with return code `10' if any proof failed" 103 | " (default: exit 0)", 104 | }, { 105 | "flags": ["--no-standalone"], 106 | "action": "store_true", 107 | "help": "only configure proofs: do not initialize nor run", 108 | }, { 109 | "flags": ["-p", "--proofs"], 110 | "nargs": "+", 111 | "metavar": "DIR", 112 | "help": "only run proof in directory DIR (can pass more than one)", 113 | }, { 114 | "flags": ["--project-name"], 115 | "metavar": "NAME", 116 | "default": get_project_name(), 117 | "help": "project name for report. Default: %(default)s", 118 | }, { 119 | "flags": ["--marker-file"], 120 | "metavar": "FILE", 121 | "default": "cbmc-proof.txt", 122 | "help": ( 123 | "name of file that marks proof directories. Default: " 124 | "%(default)s"), 125 | }, { 126 | "flags": ["--no-memory-profile"], 127 | "action": "store_true", 128 | "help": "disable memory profiling, even if Litani supports it" 129 | }, { 130 | "flags": ["--no-expensive-limit"], 131 | "action": "store_true", 132 | "help": "do not limit parallelism of 'EXPENSIVE' jobs", 133 | }, { 134 | "flags": ["--expensive-jobs-parallelism"], 135 | "metavar": "N", 136 | "default": 1, 137 | "type": int, 138 | "help": ( 139 | "how many proof jobs marked 'EXPENSIVE' to run in parallel. " 140 | "Default: %(default)s"), 141 | }, { 142 | "flags": ["--verbose"], 143 | "action": "store_true", 144 | "help": "verbose output", 145 | }, { 146 | "flags": ["--debug"], 147 | "action": "store_true", 148 | "help": "debug output", 149 | }, { 150 | "flags": ["--summarize"], 151 | "action": "store_true", 152 | "help": "summarize proof results with two tables on stdout", 153 | }, { 154 | "flags": ["--version"], 155 | "action": "version", 156 | "version": "CBMC starter kit 2.5", 157 | "help": "display version and exit" 158 | }]: 159 | flags = arg.pop("flags") 160 | pars.add_argument(*flags, **arg) 161 | return pars.parse_args() 162 | 163 | 164 | def set_up_logging(verbose): 165 | if verbose: 166 | level = logging.DEBUG 167 | else: 168 | level = logging.WARNING 169 | logging.basicConfig( 170 | format="run-cbmc-proofs: %(message)s", level=level) 171 | 172 | 173 | def task_pool_size(): 174 | ret = os.cpu_count() 175 | if ret is None or ret < 3: 176 | return 1 177 | return ret - 2 178 | 179 | 180 | def print_counter(counter): 181 | # pylint: disable=consider-using-f-string 182 | print("\rConfiguring CBMC proofs: " 183 | "{complete:{width}} / {total:{width}}".format(**counter), end="", file=sys.stderr) 184 | 185 | 186 | def get_proof_dirs(proof_root, proof_list, marker_file): 187 | if proof_list is not None: 188 | proofs_remaining = list(proof_list) 189 | else: 190 | proofs_remaining = [] 191 | 192 | for root, _, fyles in os.walk(proof_root): 193 | proof_name = str(pathlib.Path(root).name) 194 | if root != str(proof_root) and ".litani_cache_dir" in fyles: 195 | pathlib.Path(f"{root}/.litani_cache_dir").unlink() 196 | if proof_list and proof_name not in proof_list: 197 | continue 198 | if proof_list and proof_name in proofs_remaining: 199 | proofs_remaining.remove(proof_name) 200 | if marker_file in fyles: 201 | yield root 202 | 203 | if proofs_remaining: 204 | logging.critical( 205 | "The following proofs were not found: %s", 206 | ", ".join(proofs_remaining)) 207 | sys.exit(1) 208 | 209 | 210 | def run_build(litani, jobs, fail_on_proof_failure, summarize): 211 | cmd = [str(litani), "run-build"] 212 | if jobs: 213 | cmd.extend(["-j", str(jobs)]) 214 | if fail_on_proof_failure: 215 | cmd.append("--fail-on-pipeline-failure") 216 | if summarize: 217 | out_file = pathlib.Path(tempfile.gettempdir(), "run.json").resolve() 218 | cmd.extend(["--out-file", str(out_file)]) 219 | 220 | logging.debug(" ".join(cmd)) 221 | proc = subprocess.run(cmd, check=False) 222 | 223 | if proc.returncode and not fail_on_proof_failure: 224 | logging.critical("Failed to run litani run-build") 225 | sys.exit(1) 226 | 227 | if summarize: 228 | print_proof_results(out_file) 229 | out_file.unlink() 230 | 231 | if proc.returncode: 232 | logging.error("One or more proofs failed") 233 | sys.exit(10) 234 | 235 | def get_litani_path(proof_root): 236 | cmd = [ 237 | "make", 238 | "--no-print-directory", 239 | f"PROOF_ROOT={proof_root}", 240 | "-f", "Makefile.common", 241 | "litani-path", 242 | ] 243 | logging.debug(" ".join(cmd)) 244 | proc = subprocess.run(cmd, universal_newlines=True, stdout=subprocess.PIPE, check=False) 245 | if proc.returncode: 246 | logging.critical("Could not determine path to litani") 247 | sys.exit(1) 248 | return proc.stdout.strip() 249 | 250 | 251 | def get_litani_capabilities(litani_path): 252 | cmd = [litani_path, "print-capabilities"] 253 | proc = subprocess.run( 254 | cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False) 255 | if proc.returncode: 256 | return [] 257 | try: 258 | return json.loads(proc.stdout) 259 | except RuntimeError: 260 | logging.warning("Could not load litani capabilities: '%s'", proc.stdout) 261 | return [] 262 | 263 | 264 | def check_uid_uniqueness(proof_dir, proof_uids): 265 | with (pathlib.Path(proof_dir) / "Makefile").open() as handle: 266 | for line in handle: 267 | match = re.match(r"^PROOF_UID\s*=\s*(?P\w+)", line) 268 | if not match: 269 | continue 270 | if match["uid"] not in proof_uids: 271 | proof_uids[match["uid"]] = proof_dir 272 | return 273 | 274 | logging.critical( 275 | "The Makefile in directory '%s' should have a different " 276 | "PROOF_UID than the Makefile in directory '%s'", 277 | proof_dir, proof_uids[match["uid"]]) 278 | sys.exit(1) 279 | 280 | logging.critical( 281 | "The Makefile in directory '%s' should contain a line like", proof_dir) 282 | logging.critical("PROOF_UID = ...") 283 | logging.critical("with a unique identifier for the proof.") 284 | sys.exit(1) 285 | 286 | 287 | def should_enable_memory_profiling(litani_caps, args): 288 | if args.no_memory_profile: 289 | return False 290 | return "memory_profile" in litani_caps 291 | 292 | 293 | def should_enable_pools(litani_caps, args): 294 | if args.no_expensive_limit: 295 | return False 296 | return "pools" in litani_caps 297 | 298 | 299 | async def configure_proof_dirs( # pylint: disable=too-many-arguments 300 | queue, counter, proof_uids, enable_pools, enable_memory_profiling, debug): 301 | while True: 302 | print_counter(counter) 303 | path = str(await queue.get()) 304 | 305 | check_uid_uniqueness(path, proof_uids) 306 | 307 | pools = ["ENABLE_POOLS=true"] if enable_pools else [] 308 | profiling = [ 309 | "ENABLE_MEMORY_PROFILING=true"] if enable_memory_profiling else [] 310 | 311 | # Allow interactive tasks to preempt proof configuration 312 | proc = await asyncio.create_subprocess_exec( 313 | "nice", "-n", "15", "make", *pools, 314 | *profiling, "-B", "_report", "" if debug else "--quiet", cwd=path, 315 | stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) 316 | stdout, stderr = await proc.communicate() 317 | logging.debug("returncode: %s", str(proc.returncode)) 318 | logging.debug("stdout:") 319 | for line in stdout.decode().splitlines(): 320 | logging.debug(line) 321 | logging.debug("stderr:") 322 | for line in stderr.decode().splitlines(): 323 | logging.debug(line) 324 | 325 | counter["fail" if proc.returncode else "pass"].append(path) 326 | counter["complete"] += 1 327 | 328 | print_counter(counter) 329 | queue.task_done() 330 | 331 | 332 | async def main(): # pylint: disable=too-many-locals 333 | args = get_args() 334 | set_up_logging(args.verbose) 335 | 336 | proof_root = pathlib.Path(os.getcwd()) 337 | litani = get_litani_path(proof_root) 338 | 339 | litani_caps = get_litani_capabilities(litani) 340 | enable_pools = should_enable_pools(litani_caps, args) 341 | init_pools = [ 342 | "--pools", f"expensive:{args.expensive_jobs_parallelism}" 343 | ] if enable_pools else [] 344 | 345 | if not args.no_standalone: 346 | cmd = [ 347 | str(litani), "init", *init_pools, "--project", args.project_name, 348 | "--no-print-out-dir", 349 | ] 350 | 351 | if "output_directory_flags" in litani_caps: 352 | out_prefix = proof_root / "output" 353 | out_symlink = out_prefix / "latest" 354 | out_index = out_symlink / "html" / "index.html" 355 | cmd.extend([ 356 | "--output-prefix", str(out_prefix), 357 | "--output-symlink", str(out_symlink), 358 | ]) 359 | print( 360 | "\nFor your convenience, the output of this run will be symbolically linked to ", 361 | out_index, "\n") 362 | 363 | logging.debug(" ".join(cmd)) 364 | proc = subprocess.run(cmd, check=False) 365 | if proc.returncode: 366 | logging.critical("Failed to run litani init") 367 | sys.exit(1) 368 | 369 | proof_dirs = list(get_proof_dirs( 370 | proof_root, args.proofs, args.marker_file)) 371 | if not proof_dirs: 372 | logging.critical("No proof directories found") 373 | sys.exit(1) 374 | 375 | proof_queue = asyncio.Queue() 376 | for proof_dir in proof_dirs: 377 | proof_queue.put_nowait(proof_dir) 378 | 379 | counter = { 380 | "pass": [], 381 | "fail": [], 382 | "complete": 0, 383 | "total": len(proof_dirs), 384 | "width": int(math.log10(len(proof_dirs))) + 1 385 | } 386 | 387 | proof_uids = {} 388 | tasks = [] 389 | 390 | enable_memory_profiling = should_enable_memory_profiling(litani_caps, args) 391 | 392 | for _ in range(task_pool_size()): 393 | task = asyncio.create_task(configure_proof_dirs( 394 | proof_queue, counter, proof_uids, enable_pools, 395 | enable_memory_profiling, args.debug)) 396 | tasks.append(task) 397 | 398 | await proof_queue.join() 399 | 400 | print_counter(counter) 401 | print("", file=sys.stderr) 402 | 403 | if counter["fail"]: 404 | logging.critical( 405 | "Failed to configure the following proofs:\n%s", "\n".join( 406 | [str(f) for f in counter["fail"]])) 407 | sys.exit(1) 408 | 409 | if not args.no_standalone: 410 | run_build(litani, args.parallel_jobs, args.fail_on_proof_failure, args.summarize) 411 | 412 | 413 | if __name__ == "__main__": 414 | asyncio.run(main()) 415 | -------------------------------------------------------------------------------- /test/cbmc/sources/README.md: -------------------------------------------------------------------------------- 1 | CBMC proof source code 2 | ====================== 3 | 4 | This directory contains source code written for CBMC proofs. It is 5 | common to write some code to model aspects of the system under test, 6 | and this code goes here. 7 | -------------------------------------------------------------------------------- /test/cbmc/stubs/README.md: -------------------------------------------------------------------------------- 1 | CBMC proof stubs 2 | ====================== 3 | 4 | This directory contains the stubs written for CBMC proofs. It is 5 | common to stub out functionality like network send and receive methods 6 | when writing a CBMC proof, and the code for these stubs goes here. 7 | -------------------------------------------------------------------------------- /test/include/defender_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file defender_config.h 27 | * @brief Config values for testing the AWS IoT Device Defender Client Library. 28 | */ 29 | 30 | #ifndef DEFENDER_CONFIG_H_ 31 | #define DEFENDER_CONFIG_H_ 32 | 33 | #include 34 | 35 | #ifdef DISABLE_LOGGING 36 | #ifndef LogError 37 | #define LogError( message ) 38 | #endif 39 | #ifndef LogWarn 40 | #define LogWarn( message ) 41 | #endif 42 | 43 | #ifndef LogInfo 44 | #define LogInfo( message ) 45 | #endif 46 | 47 | #ifndef LogDebug 48 | #define LogDebug( message ) 49 | #endif 50 | 51 | #else /* ! DISABLE_LOGGING */ 52 | #define LogError( message ) printf( "Error: " ); printf message; printf( "\n" ) 53 | 54 | #define LogWarn( message ) printf( "Warn: " ); printf message; printf( "\n" ) 55 | 56 | #define LogInfo( message ) printf( "Info: " ); printf message; printf( "\n" ) 57 | 58 | #define LogDebug( message ) printf( "Debug: " ); printf message; printf( "\n" ) 59 | #endif /* DISABLE_LOGGING */ 60 | 61 | #endif /* DEFENDER_CONFIG_H_ */ 62 | -------------------------------------------------------------------------------- /test/unit-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Include filepaths for Device Defender library. 2 | include( ${MODULE_ROOT_DIR}/defenderFilePaths.cmake ) 3 | 4 | set( library_name "defender" ) 5 | set( library_target_name "${library_name}_target" ) 6 | set( utest_binary_name "${library_name}_utest" ) 7 | 8 | # =========================== Library ============================== 9 | 10 | # List of library source files. 11 | list( APPEND library_source_files 12 | ${DEFENDER_SOURCES} ) 13 | 14 | # List of library include directories. 15 | list( APPEND library_include_directories 16 | ${DEFENDER_INCLUDE_PUBLIC_DIRS} 17 | "${CMAKE_CURRENT_LIST_DIR}/../include" ) 18 | 19 | # Create a target for building library. 20 | create_library_target( ${library_target_name} 21 | "${library_source_files}" 22 | "${library_include_directories}" ) 23 | 24 | # =========================== Test Binary ============================== 25 | 26 | # The source file containing the unit tests. 27 | set( utest_source_file "defender_utest.c" ) 28 | 29 | # The list of include directories for the test binary target. 30 | list( APPEND utest_include_directories 31 | ${DEFENDER_INCLUDE_PUBLIC_DIRS} ) 32 | 33 | # Libraries to be linked while building the test binary. 34 | list( APPEND utest_link_list 35 | lib${library_target_name}.a ) 36 | 37 | # The targets on which the test binary target depends. 38 | list( APPEND utest_dep_list 39 | ${library_target_name} ) 40 | 41 | # Create a target for the test binary. 42 | create_test_binary_target( ${utest_binary_name} 43 | ${utest_source_file} 44 | "${utest_link_list}" 45 | "${utest_dep_list}" 46 | "${test_include_directories}" ) 47 | -------------------------------------------------------------------------------- /test/unit-test/defender_utest.c: -------------------------------------------------------------------------------- 1 | /* 2 | * AWS IoT Device Defender Client v1.4.0 3 | * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * 5 | * SPDX-License-Identifier: MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @file defender_utest.c 27 | * @brief Unit tests for the Device Defender library. 28 | */ 29 | 30 | /* Standard includes. */ 31 | #include 32 | 33 | /* Test framework include. */ 34 | #include "unity.h" 35 | 36 | /* Defender API include. */ 37 | #include "defender.h" 38 | 39 | /* Thing name used in the tests. */ 40 | #define TEST_THING_NAME "TestThing" 41 | #define TEST_THING_NAME_LENGTH STRING_LITERAL_LENGTH( TEST_THING_NAME ) 42 | 43 | /* Topics used in the tests. */ 44 | #define TEST_JSON_PUBLISH_TOPIC "$aws/things/" TEST_THING_NAME "/defender/metrics/json" 45 | #define TEST_JSON_PUBLISH_TOPIC_LENGTH STRING_LITERAL_LENGTH( TEST_JSON_PUBLISH_TOPIC ) 46 | 47 | #define TEST_JSON_ACCEPTED_TOPIC "$aws/things/" TEST_THING_NAME "/defender/metrics/json/accepted" 48 | #define TEST_JSON_ACCEPTED_TOPIC_LENGTH STRING_LITERAL_LENGTH( TEST_JSON_ACCEPTED_TOPIC ) 49 | 50 | #define TEST_JSON_REJECTED_TOPIC "$aws/things/" TEST_THING_NAME "/defender/metrics/json/rejected" 51 | #define TEST_JSON_REJECTED_TOPIC_LENGTH STRING_LITERAL_LENGTH( TEST_JSON_REJECTED_TOPIC ) 52 | 53 | #define TEST_CBOR_PUBLISH_TOPIC "$aws/things/" TEST_THING_NAME "/defender/metrics/cbor" 54 | #define TEST_CBOR_PUBLISH_TOPIC_LENGTH STRING_LITERAL_LENGTH( TEST_CBOR_PUBLISH_TOPIC ) 55 | 56 | #define TEST_CBOR_ACCEPTED_TOPIC "$aws/things/" TEST_THING_NAME "/defender/metrics/cbor/accepted" 57 | #define TEST_CBOR_ACCEPTED_TOPIC_LENGTH STRING_LITERAL_LENGTH( TEST_CBOR_ACCEPTED_TOPIC ) 58 | 59 | #define TEST_CBOR_REJECTED_TOPIC "$aws/things/" TEST_THING_NAME "/defender/metrics/cbor/rejected" 60 | #define TEST_CBOR_REJECTED_TOPIC_LENGTH STRING_LITERAL_LENGTH( TEST_CBOR_REJECTED_TOPIC ) 61 | 62 | /* Length of the topic buffer used in tests. Guard buffers are placed before and 63 | * after the topic buffer to verify that defender APIs do not write out of 64 | * bounds. The memory layout is: 65 | * 66 | * +--------------+-------------------------------+------------+ 67 | * | Guard | Writable Topic Buffer | Guard | 68 | * +--------------+-------------------------------+------------+ 69 | * 70 | * Both guard buffers are filled with a known pattern before each test and are 71 | * verified to remain unchanged after each test. 72 | */ 73 | #define TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH 32 74 | #define TEST_TOPIC_BUFFER_WRITABLE_LENGTH 256 75 | #define TEST_TOPIC_BUFFER_SUFFIX_GUARD_LENGTH 32 76 | #define TEST_TOPIC_BUFFER_TOTAL_LENGTH \ 77 | ( TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + \ 78 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH + \ 79 | TEST_TOPIC_BUFFER_SUFFIX_GUARD_LENGTH ) 80 | /*-----------------------------------------------------------*/ 81 | 82 | /** 83 | * @brief Topic buffer used in tests. 84 | */ 85 | static char testTopicBuffer[ TEST_TOPIC_BUFFER_TOTAL_LENGTH ]; 86 | /*-----------------------------------------------------------*/ 87 | 88 | /* ============================ UNITY FIXTURES ============================ */ 89 | 90 | /* Called before each test method. */ 91 | void setUp() 92 | { 93 | /* Initialize the topic buffer with 0xA5. */ 94 | memset( &( testTopicBuffer[ 0 ] ), 0xA5, TEST_TOPIC_BUFFER_TOTAL_LENGTH ); 95 | } 96 | 97 | /* Called after each test method. */ 98 | void tearDown() 99 | { 100 | /* Prefix and Suffix guard buffers must never change. */ 101 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 102 | &( testTopicBuffer[ 0 ] ), 103 | TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ); 104 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 105 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + 106 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ] ), 107 | TEST_TOPIC_BUFFER_SUFFIX_GUARD_LENGTH ); 108 | } 109 | 110 | /* Called at the beginning of the whole suite. */ 111 | void suiteSetUp() 112 | { 113 | } 114 | 115 | /* Called at the end of the whole suite. */ 116 | int suiteTearDown( int numFailures ) 117 | { 118 | return numFailures; 119 | } 120 | /*-----------------------------------------------------------*/ 121 | 122 | /** 123 | * @brief Test that macros generates expected strings. 124 | */ 125 | void test_Defender_MacrosString( void ) 126 | { 127 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_JSON_PUBLISH_TOPIC, 128 | DEFENDER_API_JSON_PUBLISH( TEST_THING_NAME ), 129 | TEST_JSON_PUBLISH_TOPIC_LENGTH ); 130 | 131 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_JSON_ACCEPTED_TOPIC, 132 | DEFENDER_API_JSON_ACCEPTED( TEST_THING_NAME ), 133 | TEST_JSON_ACCEPTED_TOPIC_LENGTH ); 134 | 135 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_JSON_REJECTED_TOPIC, 136 | DEFENDER_API_JSON_REJECTED( TEST_THING_NAME ), 137 | TEST_JSON_REJECTED_TOPIC_LENGTH ); 138 | 139 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_CBOR_PUBLISH_TOPIC, 140 | DEFENDER_API_CBOR_PUBLISH( TEST_THING_NAME ), 141 | TEST_CBOR_PUBLISH_TOPIC_LENGTH ); 142 | 143 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_CBOR_ACCEPTED_TOPIC, 144 | DEFENDER_API_CBOR_ACCEPTED( TEST_THING_NAME ), 145 | TEST_CBOR_ACCEPTED_TOPIC_LENGTH ); 146 | 147 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_CBOR_REJECTED_TOPIC, 148 | DEFENDER_API_CBOR_REJECTED( TEST_THING_NAME ), 149 | TEST_CBOR_REJECTED_TOPIC_LENGTH ); 150 | } 151 | /*-----------------------------------------------------------*/ 152 | 153 | /** 154 | * @brief Test that macros generates expected string lengths. 155 | */ 156 | void test_Defender_MacrosLength( void ) 157 | { 158 | TEST_ASSERT_EQUAL( TEST_JSON_PUBLISH_TOPIC_LENGTH, 159 | DEFENDER_API_LENGTH_JSON_PUBLISH( TEST_THING_NAME_LENGTH ) ); 160 | 161 | TEST_ASSERT_EQUAL( TEST_JSON_ACCEPTED_TOPIC_LENGTH, 162 | DEFENDER_API_LENGTH_JSON_ACCEPTED( TEST_THING_NAME_LENGTH ) ); 163 | 164 | TEST_ASSERT_EQUAL( TEST_JSON_REJECTED_TOPIC_LENGTH, 165 | DEFENDER_API_LENGTH_JSON_REJECTED( TEST_THING_NAME_LENGTH ) ); 166 | 167 | TEST_ASSERT_EQUAL( TEST_CBOR_PUBLISH_TOPIC_LENGTH, 168 | DEFENDER_API_LENGTH_CBOR_PUBLISH( TEST_THING_NAME_LENGTH ) ); 169 | 170 | TEST_ASSERT_EQUAL( TEST_CBOR_ACCEPTED_TOPIC_LENGTH, 171 | DEFENDER_API_LENGTH_CBOR_ACCEPTED( TEST_THING_NAME_LENGTH ) ); 172 | 173 | TEST_ASSERT_EQUAL( TEST_CBOR_REJECTED_TOPIC_LENGTH, 174 | DEFENDER_API_LENGTH_CBOR_REJECTED( TEST_THING_NAME_LENGTH ) ); 175 | } 176 | /*-----------------------------------------------------------*/ 177 | 178 | void test_Defender_GetTopic_BadParams( void ) 179 | { 180 | DefenderStatus_t ret; 181 | uint16_t topicLength; 182 | 183 | /* Null buffer. */ 184 | ret = Defender_GetTopic( NULL, 185 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 186 | TEST_THING_NAME, 187 | TEST_THING_NAME_LENGTH, 188 | DefenderJsonReportPublish, 189 | &( topicLength ) ); 190 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 191 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 192 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 193 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 194 | 195 | /* NULL thing name. */ 196 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 197 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 198 | NULL, 199 | TEST_THING_NAME_LENGTH, 200 | DefenderJsonReportPublish, 201 | &( topicLength ) ); 202 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 203 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 204 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 205 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 206 | 207 | /* Zero length thing name. */ 208 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 209 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 210 | TEST_THING_NAME, 211 | 0, 212 | DefenderJsonReportPublish, 213 | &( topicLength ) ); 214 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 215 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 216 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 217 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 218 | 219 | /* Thing name length more than the maximum allowed. */ 220 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 221 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 222 | TEST_THING_NAME, 223 | DEFENDER_THINGNAME_MAX_LENGTH + 1, 224 | DefenderJsonReportPublish, 225 | &( topicLength ) ); 226 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 227 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 228 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 229 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 230 | 231 | /* Invalid api. */ 232 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 233 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 234 | TEST_THING_NAME, 235 | TEST_THING_NAME_LENGTH, 236 | DefenderInvalidTopic, 237 | &( topicLength ) ); 238 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 239 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 240 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 241 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 242 | 243 | /* Invalid api. */ 244 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 245 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 246 | TEST_THING_NAME, 247 | TEST_THING_NAME_LENGTH, 248 | DefenderMaxTopic, 249 | &( topicLength ) ); 250 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 251 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 252 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 253 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 254 | 255 | /* NULL output parameter. */ 256 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 257 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 258 | TEST_THING_NAME, 259 | TEST_THING_NAME_LENGTH, 260 | DefenderJsonReportPublish, 261 | NULL ); 262 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 263 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 264 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 265 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 266 | } 267 | /*-----------------------------------------------------------*/ 268 | 269 | void test_Defender_GetTopic_BufferTooSmall( void ) 270 | { 271 | DefenderStatus_t ret; 272 | uint16_t topicLength; 273 | 274 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 275 | 5, /* Length too small to fit the entire topic. */ 276 | TEST_THING_NAME, 277 | TEST_THING_NAME_LENGTH, 278 | DefenderJsonReportPublish, 279 | &( topicLength ) ); 280 | TEST_ASSERT_EQUAL( DefenderBufferTooSmall, ret ); 281 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 282 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 283 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH ); 284 | } 285 | /*-----------------------------------------------------------*/ 286 | 287 | void test_Defender_GetTopic_JsonPublishHappyPath( void ) 288 | { 289 | DefenderStatus_t ret; 290 | uint16_t topicLength; 291 | 292 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 293 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 294 | TEST_THING_NAME, 295 | TEST_THING_NAME_LENGTH, 296 | DefenderJsonReportPublish, 297 | &( topicLength ) ); 298 | 299 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 300 | TEST_ASSERT_EQUAL( TEST_JSON_PUBLISH_TOPIC_LENGTH, topicLength ); 301 | 302 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_JSON_PUBLISH_TOPIC, 303 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 304 | topicLength ); 305 | 306 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 307 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + topicLength ] ), 308 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH - topicLength ); 309 | } 310 | /*-----------------------------------------------------------*/ 311 | 312 | void test_Defender_GetTopic_JsonAcceptedHappyPath( void ) 313 | { 314 | DefenderStatus_t ret; 315 | uint16_t topicLength; 316 | 317 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 318 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 319 | TEST_THING_NAME, 320 | TEST_THING_NAME_LENGTH, 321 | DefenderJsonReportAccepted, 322 | &( topicLength ) ); 323 | 324 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 325 | TEST_ASSERT_EQUAL( TEST_JSON_ACCEPTED_TOPIC_LENGTH, topicLength ); 326 | 327 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_JSON_ACCEPTED_TOPIC, 328 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 329 | topicLength ); 330 | 331 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 332 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + topicLength ] ), 333 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH - topicLength ); 334 | } 335 | /*-----------------------------------------------------------*/ 336 | 337 | void test_Defender_GetTopic_JsonRejectedHappyPath( void ) 338 | { 339 | DefenderStatus_t ret; 340 | uint16_t topicLength; 341 | 342 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 343 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 344 | TEST_THING_NAME, 345 | TEST_THING_NAME_LENGTH, 346 | DefenderJsonReportRejected, 347 | &( topicLength ) ); 348 | 349 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 350 | TEST_ASSERT_EQUAL( TEST_JSON_REJECTED_TOPIC_LENGTH, topicLength ); 351 | 352 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_JSON_REJECTED_TOPIC, 353 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 354 | topicLength ); 355 | 356 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 357 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + topicLength ] ), 358 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH - topicLength ); 359 | } 360 | /*-----------------------------------------------------------*/ 361 | 362 | void test_Defender_GetTopic_CborPublishHappyPath( void ) 363 | { 364 | DefenderStatus_t ret; 365 | uint16_t topicLength; 366 | 367 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 368 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 369 | TEST_THING_NAME, 370 | TEST_THING_NAME_LENGTH, 371 | DefenderCborReportPublish, 372 | &( topicLength ) ); 373 | 374 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 375 | TEST_ASSERT_EQUAL( TEST_CBOR_PUBLISH_TOPIC_LENGTH, topicLength ); 376 | 377 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_CBOR_PUBLISH_TOPIC, 378 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 379 | topicLength ); 380 | 381 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 382 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + topicLength ] ), 383 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH - topicLength ); 384 | } 385 | /*-----------------------------------------------------------*/ 386 | 387 | void test_Defender_GetTopic_CborAcceptedHappyPath( void ) 388 | { 389 | DefenderStatus_t ret; 390 | uint16_t topicLength; 391 | 392 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 393 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 394 | TEST_THING_NAME, 395 | TEST_THING_NAME_LENGTH, 396 | DefenderCborReportAccepted, 397 | &( topicLength ) ); 398 | 399 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 400 | TEST_ASSERT_EQUAL( TEST_CBOR_ACCEPTED_TOPIC_LENGTH, topicLength ); 401 | 402 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_CBOR_ACCEPTED_TOPIC, 403 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 404 | topicLength ); 405 | 406 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 407 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + topicLength ] ), 408 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH - topicLength ); 409 | } 410 | /*-----------------------------------------------------------*/ 411 | 412 | void test_Defender_GetTopic_CborRejectedHappyPath( void ) 413 | { 414 | DefenderStatus_t ret; 415 | uint16_t topicLength; 416 | 417 | ret = Defender_GetTopic( &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 418 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH, 419 | TEST_THING_NAME, 420 | TEST_THING_NAME_LENGTH, 421 | DefenderCborReportRejected, 422 | &( topicLength ) ); 423 | 424 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 425 | TEST_ASSERT_EQUAL( TEST_CBOR_REJECTED_TOPIC_LENGTH, topicLength ); 426 | 427 | TEST_ASSERT_EQUAL_STRING_LEN( TEST_CBOR_REJECTED_TOPIC, 428 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH ] ), 429 | topicLength ); 430 | 431 | TEST_ASSERT_EACH_EQUAL_HEX8( 0xA5, 432 | &( testTopicBuffer[ TEST_TOPIC_BUFFER_PREFIX_GUARD_LENGTH + topicLength ] ), 433 | TEST_TOPIC_BUFFER_WRITABLE_LENGTH - topicLength ); 434 | } 435 | /*-----------------------------------------------------------*/ 436 | 437 | void test_Defender_MatchTopic_BadParams( void ) 438 | { 439 | DefenderStatus_t ret; 440 | const char * pThingName; 441 | uint16_t thingNameLength; 442 | DefenderTopic_t api; 443 | 444 | /* NULL topic. */ 445 | ret = Defender_MatchTopic( NULL, 446 | TEST_JSON_PUBLISH_TOPIC_LENGTH, 447 | &( api ), 448 | &( pThingName ), 449 | &( thingNameLength ) ); 450 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 451 | 452 | /* NULL output parameter. */ 453 | ret = Defender_MatchTopic( TEST_JSON_PUBLISH_TOPIC, 454 | TEST_JSON_PUBLISH_TOPIC_LENGTH, 455 | NULL, 456 | &( pThingName ), 457 | &( thingNameLength ) ); 458 | TEST_ASSERT_EQUAL( DefenderBadParameter, ret ); 459 | } 460 | /*-----------------------------------------------------------*/ 461 | 462 | void test_Defender_MatchTopic_IncompletePrefix( void ) 463 | { 464 | DefenderStatus_t ret; 465 | const char * pThingName; 466 | uint16_t thingNameLength; 467 | DefenderTopic_t api; 468 | 469 | ret = Defender_MatchTopic( "$aws/things", 470 | STRING_LITERAL_LENGTH( "$aws/things" ), 471 | &( api ), 472 | &( pThingName ), 473 | &( thingNameLength ) ); 474 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 475 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 476 | } 477 | /*-----------------------------------------------------------*/ 478 | 479 | void test_Defender_MatchTopic_InvalidPrefix( void ) 480 | { 481 | DefenderStatus_t ret; 482 | const char * pThingName; 483 | uint16_t thingNameLength; 484 | DefenderTopic_t api; 485 | 486 | ret = Defender_MatchTopic( "$aws/jobs/things/TestThing/defender/metrics/json", 487 | STRING_LITERAL_LENGTH( "$aws/jobs/things/TestThing/defender/metrics/json" ), 488 | &( api ), 489 | &( pThingName ), 490 | &( thingNameLength ) ); 491 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 492 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 493 | 494 | ret = Defender_MatchTopic( "$aws/things/jobs/TestThing/defender/metrics/json", 495 | STRING_LITERAL_LENGTH( "$aws/jobs/TestThing/defender/metrics/json" ), 496 | &( api ), 497 | &( pThingName ), 498 | &( thingNameLength ) ); 499 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 500 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 501 | } 502 | /*-----------------------------------------------------------*/ 503 | 504 | void test_Defender_MatchTopic_MissingThingName( void ) 505 | { 506 | DefenderStatus_t ret; 507 | const char * pThingName; 508 | uint16_t thingNameLength; 509 | DefenderTopic_t api; 510 | 511 | ret = Defender_MatchTopic( "$aws/things/", 512 | STRING_LITERAL_LENGTH( "$aws/things/" ), 513 | &( api ), 514 | &( pThingName ), 515 | &( thingNameLength ) ); 516 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 517 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 518 | } 519 | /*-----------------------------------------------------------*/ 520 | 521 | void test_Defender_MatchTopic_IncompleteBridge( void ) 522 | { 523 | DefenderStatus_t ret; 524 | const char * pThingName; 525 | uint16_t thingNameLength; 526 | DefenderTopic_t api; 527 | 528 | ret = Defender_MatchTopic( "$aws/things/TestThing", 529 | STRING_LITERAL_LENGTH( "$aws/things/TestThing" ), 530 | &( api ), 531 | &( pThingName ), 532 | &( thingNameLength ) ); 533 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 534 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 535 | 536 | ret = Defender_MatchTopic( "$aws/things/TestThing/", 537 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/" ), 538 | &( api ), 539 | &( pThingName ), 540 | &( thingNameLength ) ); 541 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 542 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 543 | 544 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender", 545 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender" ), 546 | &( api ), 547 | &( pThingName ), 548 | &( thingNameLength ) ); 549 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 550 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 551 | } 552 | /*-----------------------------------------------------------*/ 553 | 554 | void test_Defender_MatchTopic_InvalidBridge( void ) 555 | { 556 | DefenderStatus_t ret; 557 | const char * pThingName; 558 | uint16_t thingNameLength; 559 | DefenderTopic_t api; 560 | 561 | ret = Defender_MatchTopic( "$aws/things/TestThing/shadow/metrics/json", 562 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/shadow/metrics/json" ), 563 | &( api ), 564 | &( pThingName ), 565 | &( thingNameLength ) ); 566 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 567 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 568 | 569 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/defender/metrics/json", 570 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/defender/metrics/json" ), 571 | &( api ), 572 | &( pThingName ), 573 | &( thingNameLength ) ); 574 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 575 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 576 | } 577 | /*-----------------------------------------------------------*/ 578 | 579 | void test_Defender_MatchTopic_IncompleteFormat( void ) 580 | { 581 | DefenderStatus_t ret; 582 | const char * pThingName; 583 | uint16_t thingNameLength; 584 | DefenderTopic_t api; 585 | 586 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/metrics", 587 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/metrics" ), 588 | &( api ), 589 | &( pThingName ), 590 | &( thingNameLength ) ); 591 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 592 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 593 | 594 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/metrics/", 595 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/metrics/" ), 596 | &( api ), 597 | &( pThingName ), 598 | &( thingNameLength ) ); 599 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 600 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 601 | } 602 | /*-----------------------------------------------------------*/ 603 | 604 | void test_Defender_MatchTopic_InvalidFormat( void ) 605 | { 606 | DefenderStatus_t ret; 607 | const char * pThingName; 608 | uint16_t thingNameLength; 609 | DefenderTopic_t api; 610 | 611 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/metrics/xml", 612 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/metrics/xml" ), 613 | &( api ), 614 | &( pThingName ), 615 | &( thingNameLength ) ); 616 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 617 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 618 | } 619 | /*-----------------------------------------------------------*/ 620 | 621 | void test_Defender_MatchTopic_InvalidSuffix( void ) 622 | { 623 | DefenderStatus_t ret; 624 | const char * pThingName; 625 | uint16_t thingNameLength; 626 | DefenderTopic_t api; 627 | 628 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/metrics/json/delta", 629 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/metrics/json/delta" ), 630 | &( api ), 631 | &( pThingName ), 632 | &( thingNameLength ) ); 633 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 634 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 635 | } 636 | /*-----------------------------------------------------------*/ 637 | 638 | void test_Defender_MatchTopic_ExtraData( void ) 639 | { 640 | DefenderStatus_t ret; 641 | const char * pThingName; 642 | uint16_t thingNameLength; 643 | DefenderTopic_t api; 644 | 645 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/metrics/json/gibberish", 646 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/metrics/json/gibberish" ), 647 | &( api ), 648 | &( pThingName ), 649 | &( thingNameLength ) ); 650 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 651 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 652 | 653 | ret = Defender_MatchTopic( "$aws/things/TestThing/defender/metrics/json/accepted/gibberish", 654 | STRING_LITERAL_LENGTH( "$aws/things/TestThing/defender/metrics/json/accepted/gibberish" ), 655 | &( api ), 656 | &( pThingName ), 657 | &( thingNameLength ) ); 658 | TEST_ASSERT_EQUAL( DefenderNoMatch, ret ); 659 | TEST_ASSERT_EQUAL( DefenderInvalidTopic, api ); 660 | } 661 | /*-----------------------------------------------------------*/ 662 | 663 | void test_Defender_MatchTopic_JsonPublishHappyPath( void ) 664 | { 665 | DefenderStatus_t ret; 666 | const char * pThingName; 667 | uint16_t thingNameLength; 668 | DefenderTopic_t api; 669 | 670 | ret = Defender_MatchTopic( TEST_JSON_PUBLISH_TOPIC, 671 | TEST_JSON_PUBLISH_TOPIC_LENGTH, 672 | &( api ), 673 | &( pThingName ), 674 | &( thingNameLength ) ); 675 | 676 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 677 | TEST_ASSERT_EQUAL( DefenderJsonReportPublish, api ); 678 | TEST_ASSERT_EQUAL_PTR( TEST_JSON_PUBLISH_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 679 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 680 | } 681 | /*-----------------------------------------------------------*/ 682 | 683 | void test_Defender_MatchTopic_JsonAcceptedHappyPath( void ) 684 | { 685 | DefenderStatus_t ret; 686 | const char * pThingName; 687 | uint16_t thingNameLength; 688 | DefenderTopic_t api; 689 | 690 | ret = Defender_MatchTopic( TEST_JSON_ACCEPTED_TOPIC, 691 | TEST_JSON_ACCEPTED_TOPIC_LENGTH, 692 | &( api ), 693 | &( pThingName ), 694 | &( thingNameLength ) ); 695 | 696 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 697 | TEST_ASSERT_EQUAL( DefenderJsonReportAccepted, api ); 698 | TEST_ASSERT_EQUAL_PTR( TEST_JSON_ACCEPTED_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 699 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 700 | } 701 | /*-----------------------------------------------------------*/ 702 | 703 | void test_Defender_MatchTopic_JsonRejectedHappyPath( void ) 704 | { 705 | DefenderStatus_t ret; 706 | const char * pThingName; 707 | uint16_t thingNameLength; 708 | DefenderTopic_t api; 709 | 710 | ret = Defender_MatchTopic( TEST_JSON_REJECTED_TOPIC, 711 | TEST_JSON_REJECTED_TOPIC_LENGTH, 712 | &( api ), 713 | &( pThingName ), 714 | &( thingNameLength ) ); 715 | 716 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 717 | TEST_ASSERT_EQUAL( DefenderJsonReportRejected, api ); 718 | TEST_ASSERT_EQUAL_PTR( TEST_JSON_REJECTED_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 719 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 720 | } 721 | /*-----------------------------------------------------------*/ 722 | 723 | void test_Defender_MatchTopic_CborPublishHappyPath( void ) 724 | { 725 | DefenderStatus_t ret; 726 | const char * pThingName; 727 | uint16_t thingNameLength; 728 | DefenderTopic_t api; 729 | 730 | ret = Defender_MatchTopic( TEST_CBOR_PUBLISH_TOPIC, 731 | TEST_CBOR_PUBLISH_TOPIC_LENGTH, 732 | &( api ), 733 | &( pThingName ), 734 | &( thingNameLength ) ); 735 | 736 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 737 | TEST_ASSERT_EQUAL( DefenderCborReportPublish, api ); 738 | TEST_ASSERT_EQUAL_PTR( TEST_CBOR_PUBLISH_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 739 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 740 | } 741 | /*-----------------------------------------------------------*/ 742 | 743 | void test_Defender_MatchTopic_CborAcceptedHappyPath( void ) 744 | { 745 | DefenderStatus_t ret; 746 | const char * pThingName; 747 | uint16_t thingNameLength; 748 | DefenderTopic_t api; 749 | 750 | ret = Defender_MatchTopic( TEST_CBOR_ACCEPTED_TOPIC, 751 | TEST_CBOR_ACCEPTED_TOPIC_LENGTH, 752 | &( api ), 753 | &( pThingName ), 754 | &( thingNameLength ) ); 755 | 756 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 757 | TEST_ASSERT_EQUAL( DefenderCborReportAccepted, api ); 758 | TEST_ASSERT_EQUAL_PTR( TEST_CBOR_ACCEPTED_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 759 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 760 | } 761 | /*-----------------------------------------------------------*/ 762 | 763 | void test_Defender_MatchTopic_CborRejectedHappyPath( void ) 764 | { 765 | DefenderStatus_t ret; 766 | const char * pThingName; 767 | uint16_t thingNameLength; 768 | DefenderTopic_t api; 769 | 770 | ret = Defender_MatchTopic( TEST_CBOR_REJECTED_TOPIC, 771 | TEST_CBOR_REJECTED_TOPIC_LENGTH, 772 | &( api ), 773 | &( pThingName ), 774 | &( thingNameLength ) ); 775 | 776 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 777 | TEST_ASSERT_EQUAL( DefenderCborReportRejected, api ); 778 | TEST_ASSERT_EQUAL_PTR( TEST_CBOR_REJECTED_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 779 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 780 | } 781 | /*-----------------------------------------------------------*/ 782 | 783 | void test_Defender_MatchTopic_MissingOptionalParams( void ) 784 | { 785 | DefenderStatus_t ret; 786 | const char * pThingName; 787 | uint16_t thingNameLength; 788 | DefenderTopic_t api; 789 | 790 | /* Missing output param for returning thing name. */ 791 | ret = Defender_MatchTopic( TEST_JSON_PUBLISH_TOPIC, 792 | TEST_JSON_PUBLISH_TOPIC_LENGTH, 793 | &( api ), 794 | NULL, 795 | &( thingNameLength ) ); 796 | 797 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 798 | TEST_ASSERT_EQUAL( DefenderJsonReportPublish, api ); 799 | TEST_ASSERT_EQUAL( TEST_THING_NAME_LENGTH, thingNameLength ); 800 | 801 | /* Missing output param for returning thing name length. */ 802 | ret = Defender_MatchTopic( TEST_JSON_PUBLISH_TOPIC, 803 | TEST_JSON_PUBLISH_TOPIC_LENGTH, 804 | &( api ), 805 | &( pThingName ), 806 | NULL ); 807 | 808 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 809 | TEST_ASSERT_EQUAL( DefenderJsonReportPublish, api ); 810 | TEST_ASSERT_EQUAL_PTR( TEST_JSON_PUBLISH_TOPIC + DEFENDER_API_LENGTH_PREFIX, pThingName ); 811 | 812 | /* Missing output param for returning both thing name and thing name 813 | * length. */ 814 | ret = Defender_MatchTopic( TEST_JSON_PUBLISH_TOPIC, 815 | TEST_JSON_PUBLISH_TOPIC_LENGTH, 816 | &( api ), 817 | NULL, 818 | NULL ); 819 | 820 | TEST_ASSERT_EQUAL( DefenderSuccess, ret ); 821 | TEST_ASSERT_EQUAL( DefenderJsonReportPublish, api ); 822 | } 823 | /*-----------------------------------------------------------*/ 824 | -------------------------------------------------------------------------------- /test/unit-test/unity_build.cmake: -------------------------------------------------------------------------------- 1 | # Macro to clone the Unity submodule. 2 | macro( clone_unity ) 3 | find_package( Git REQUIRED ) 4 | message( "Cloning Unity submodule." ) 5 | execute_process( COMMAND rm -rf ${UNITY_DIR} 6 | COMMAND ${GIT_EXECUTABLE} submodule update --checkout --init --recursive ${UNITY_DIR} 7 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 8 | RESULT_VARIABLE UNITY_CLONE_RESULT ) 9 | 10 | if( NOT ${UNITY_CLONE_RESULT} STREQUAL "0" ) 11 | message( FATAL_ERROR "Failed to clone Unity submodule." ) 12 | endif() 13 | endmacro() 14 | 15 | # Macro to add library target for Unity to build configuration. 16 | macro( add_unity_target ) 17 | add_library( unity STATIC 18 | "${UNITY_DIR}/src/unity.c" 19 | "${UNITY_DIR}/extras/fixture/src/unity_fixture.c" 20 | "${UNITY_DIR}/extras/memory/src/unity_memory.c" ) 21 | 22 | target_include_directories( unity PUBLIC 23 | "${UNITY_DIR}/src/" 24 | "${UNITY_DIR}/extras/fixture/src" 25 | "${UNITY_DIR}/extras/memory/src" ) 26 | 27 | set_target_properties( unity PROPERTIES 28 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 29 | POSITION_INDEPENDENT_CODE ON ) 30 | 31 | target_link_libraries( unity ) 32 | endmacro() 33 | -------------------------------------------------------------------------------- /tools/coverity/README.md: -------------------------------------------------------------------------------- 1 | # Static code analysis for Device-Defender-for-AWS-IoT-embedded-sdk library 2 | This directory is made for the purpose of statically testing the MISRA C:2012 compliance of Device-Defender-for-AWS-IoT-embedded-sdk using 3 | [Synopsys Coverity](https://www.synopsys.com/software-integrity/security-testing/static-analysis-sast.html) static analysis tool. 4 | To that end, this directory provides a [configuration file](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/blob/main/tools/coverity/misra.config) to use when 5 | building a binary for the tool to analyze. 6 | 7 | > **Note** 8 | For generating the report as outlined below, we have used Coverity version 2023.6.1. 9 | 10 | For details regarding the suppressed violations in the report (which can be generated using the instructions described below), please 11 | see the [MISRA.md](https://github.com/aws/Device-Defender-for-AWS-IoT-embedded-sdk/blob/main/MISRA.md) file. 12 | 13 | ## Getting Started 14 | ### Prerequisites 15 | You can run this on a platform supported by Coverity. The list and other details can be found [here](https://documentation.blackduck.com/bundle/coverity-docs-2024.9/page/deploy-install-guide/topics/supported_platforms_for_coverity_analysis.html). 16 | To compile and run the Coverity target successfully, you must have the following: 17 | 18 | 1. CMake version > 3.13.0 (You can check whether you have this by typing `cmake --version`) 19 | 2. GCC compiler 20 | - You can see the downloading and installation instructions [here](https://gcc.gnu.org/install/). 21 | 3. Download the repo and include the submodules using the following commands. 22 | - `git clone --recurse-submodules git@github.com:aws/Device-Defender-for-AWS-IoT-embedded-sdk.git ./Device-Defender-for-AWS-IoT-embedded-sdk` 23 | - `cd ./Device-Defender-for-AWS-IoT-embedded-sdk` 24 | - `git submodule update --checkout --init --recursive` 25 | 26 | ### To build and run coverity: 27 | Go to the root directory of the library and run the following commands in terminal: 28 | 1. Update the compiler configuration in Coverity 29 | ~~~ 30 | cov-configure --force --compiler cc --comptype gcc 31 | ~~~ 32 | 2. Create the build files using CMake in a `build` directory 33 | ~~~ 34 | cmake -B build -S test -DCOV_ANALYSIS 35 | ~~~ 36 | 3. Go to the build directory and copy the coverity configuration file 37 | ~~~ 38 | cd build/ 39 | ~~~ 40 | 4. Build the static analysis target 41 | ~~~ 42 | cov-build --emit-complementary-info --dir cov-out make coverity_analysis 43 | ~~~ 44 | 5. Go to the Coverity output directory (`cov-out`) and begin Coverity static analysis 45 | ~~~ 46 | cd cov-out/ 47 | cov-analyze --dir . --coding-standard-config ../../tools/coverity/misra.config --tu-pattern "file('.*/source/.*')" 48 | ~~~ 49 | 6. Format the errors in HTML format so that it is more readable while removing the test and build directory from the report 50 | ~~~ 51 | cov-format-errors --dir . --file "source" --exclude-files '(/build/|/test/)' --html-output html-out; 52 | ~~~ 53 | 7. Format the errors in JSON format to perform a jq query to get a simplified list of any exceptions. 54 | NOTE: A blank output means there are no defects that aren't being suppressed by the config or inline comments. 55 | ~~~ 56 | cov-format-errors --dir . --file "source" --exclude-files '(/build/|/test/)' --json-output-v2 defects.json; 57 | echo -e "\n-------------------------Non-Suppresed Deviations, if any, Listed Below-------------------------\n"; 58 | jq '.issues[] | .events[] | .eventTag ' defects.json | sort | uniq -c | sort -nr; 59 | echo -e "\n-------------------------Non-Suppresed Deviations, if any, Listed Above-------------------------\n"; 60 | ~~~ 61 | 62 | For your convenience the commands above are below to be copy/pasted into a UNIX command friendly terminal. 63 | ~~~ 64 | cov-configure --force --compiler cc --comptype gcc; 65 | cmake -B build -S test -DCOV_ANALYSIS; 66 | cd build/; 67 | cov-build --emit-complementary-info --dir cov-out make coverity_analysis; 68 | cd cov-out/ 69 | cov-analyze --dir . --coding-standard-config ../../tools/coverity/misra.config; 70 | cov-format-errors --dir . --file "source" --exclude-files '(/build/|/test/)' --html-output html-out; 71 | cov-format-errors --dir . --file "source" --exclude-files '(/build/|/test/)' --json-output-v2 defects.json; 72 | echo -e "\n-------------------------Non-Suppresed Deviations, if any, Listed Below-------------------------\n"; 73 | jq '.issues[] | .events[] | .eventTag ' defects.json | sort | uniq -c | sort -nr; 74 | echo -e "\n-------------------------Non-Suppresed Deviations, if any, Listed Above-------------------------\n"; 75 | cd ../../; 76 | ~~~ 77 | 78 | You should now have the HTML formatted violations list in a directory named `build/cov-out/html-output`. 79 | With the current configuration and the provided project, you should not see any deviations. -------------------------------------------------------------------------------- /tools/coverity/misra.config: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "2.0", 3 | "standard" : "c2012", 4 | "title": "Coverity MISRA Configuration", 5 | "deviations" : [ 6 | { 7 | "deviation": "Directive 4.9", 8 | "category": "Advisory", 9 | "reason": "Allow inclusion of function like macros. The `assert` macro is used throughout the library for parameter validation, and logging is done using function like macros." 10 | }, 11 | { 12 | "deviation": "Rule 2.5", 13 | "reason": "Allow unused macros. Library headers may define macros intended for the application's use, but not used by a specific file." 14 | }, 15 | { 16 | "deviation": "Rule 3.1", 17 | "category": "Required", 18 | "reason": "Allow nested comments. Documentation blocks contain comments for example code." 19 | }, 20 | { 21 | "deviation": "Rule 8.7", 22 | "reason": "API functions are not used by library. They must be externally visible in order to be used by the application." 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tools/unity/coverage.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 3.13 ) 2 | set( BINARY_DIR ${CMAKE_BINARY_DIR} ) 3 | 4 | # Reset coverage counters. 5 | execute_process( COMMAND lcov --directory ${CMAKE_BINARY_DIR} 6 | --base-directory ${CMAKE_BINARY_DIR} 7 | --zerocounters ) 8 | 9 | # Create directory for results. 10 | execute_process( COMMAND mkdir -p ${CMAKE_BINARY_DIR}/coverage ) 11 | 12 | # Generate "baseline" coverage data with zero coverage for every instrumented 13 | # line. This is later combined with the coverage data from test run to ensure 14 | # that the percentage of total lines covered is correct even when not all source 15 | # code files were loaded during the test. 16 | execute_process( COMMAND lcov --directory ${CMAKE_BINARY_DIR} 17 | --base-directory ${CMAKE_BINARY_DIR} 18 | --initial 19 | --capture 20 | --rc lcov_branch_coverage=1 21 | --rc genhtml_branch_coverage=1 22 | --output-file=${CMAKE_BINARY_DIR}/base_coverage.info ) 23 | 24 | # Capture all the test binaries. 25 | file( GLOB files "${CMAKE_BINARY_DIR}/bin/tests/*" ) 26 | 27 | # Create an empty report file. 28 | set( REPORT_FILE ${CMAKE_BINARY_DIR}/utest_report.txt ) 29 | file( WRITE ${REPORT_FILE} "" ) 30 | 31 | # Execute all test binaries and capture all the output in the report file. 32 | foreach( testname ${files} ) 33 | get_filename_component( test ${testname} NAME_WLE ) 34 | message( "Running ${testname}" ) 35 | execute_process( COMMAND ${testname} OUTPUT_FILE ${CMAKE_BINARY_DIR}/${test}_out.txt ) 36 | 37 | # Append the test run output to the report file. 38 | file( READ ${CMAKE_BINARY_DIR}/${test}_out.txt CONTENTS ) 39 | file( APPEND ${REPORT_FILE} "${CONTENTS}" ) 40 | endforeach() 41 | 42 | # Generate Junit style xml output. 43 | execute_process( COMMAND ruby 44 | ${UNITY_DIR}/auto/parse_output.rb 45 | -xml ${REPORT_FILE} 46 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) 47 | 48 | # Capture coverage data after test run. 49 | execute_process( COMMAND lcov --capture 50 | --rc lcov_branch_coverage=1 51 | --rc genhtml_branch_coverage=1 52 | --base-directory ${CMAKE_BINARY_DIR} 53 | --directory ${CMAKE_BINARY_DIR} 54 | --output-file ${CMAKE_BINARY_DIR}/second_coverage.info ) 55 | 56 | # Combine baseline coverage data (zeros) with the coverage data from test run. 57 | execute_process( COMMAND lcov --base-directory ${CMAKE_BINARY_DIR} 58 | --directory ${CMAKE_BINARY_DIR} 59 | --add-tracefile ${CMAKE_BINARY_DIR}/base_coverage.info 60 | --add-tracefile ${CMAKE_BINARY_DIR}/second_coverage.info 61 | --output-file ${CMAKE_BINARY_DIR}/coverage.info 62 | --no-external 63 | --rc lcov_branch_coverage=1 ) 64 | 65 | # Generate HTML Report. 66 | execute_process( COMMAND genhtml --rc lcov_branch_coverage=1 67 | --branch-coverage 68 | --output-directory ${CMAKE_BINARY_DIR}/coverage 69 | ${CMAKE_BINARY_DIR}/coverage.info ) 70 | -------------------------------------------------------------------------------- /tools/unity/create_test.cmake: -------------------------------------------------------------------------------- 1 | # Function to create the test executable. 2 | function( create_test_binary_target test_name 3 | test_src 4 | link_list 5 | dep_list 6 | include_list ) 7 | include ( CTest ) 8 | get_filename_component( test_src_absolute ${test_src} ABSOLUTE ) 9 | 10 | # Generate test runner file. 11 | add_custom_command( OUTPUT ${test_name}_runner.c 12 | COMMAND ruby ${UNITY_DIR}/auto/generate_test_runner.rb 13 | ${MODULE_ROOT_DIR}/tools/unity/project.yml 14 | ${test_src_absolute} 15 | ${test_name}_runner.c 16 | DEPENDS ${test_src} ) 17 | 18 | add_executable( ${test_name} ${test_src} ${test_name}_runner.c ) 19 | 20 | set_target_properties( ${test_name} PROPERTIES 21 | COMPILE_FLAG "-O0 -ggdb" 22 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests" 23 | INSTALL_RPATH_USE_LINK_PATH TRUE 24 | LINK_FLAGS "-Wl,-rpath,${CMAKE_BINARY_DIR}/lib \ 25 | -Wl,-rpath,${CMAKE_CURRENT_BINARY_DIR}/lib" ) 26 | 27 | target_include_directories( ${test_name} PUBLIC ${mocks_dir} ${include_list} ) 28 | 29 | target_link_directories( ${test_name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ) 30 | 31 | # Link all libraries sent through parameters. 32 | foreach( link IN LISTS link_list ) 33 | target_link_libraries( ${test_name} ${link} ) 34 | endforeach() 35 | 36 | # Add dependency to all the dep_list parameter. 37 | foreach( dependency IN LISTS dep_list ) 38 | add_dependencies( ${test_name} ${dependency} ) 39 | target_link_libraries( ${test_name} ${dependency} ) 40 | endforeach() 41 | 42 | target_link_libraries( ${test_name} -lgcov unity ) 43 | target_link_directories( ${test_name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/lib ) 44 | 45 | add_test( NAME ${test_name} 46 | COMMAND ${CMAKE_BINARY_DIR}/bin/tests/${test_name} 47 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) 48 | endfunction() 49 | 50 | # Function to create target for library under test. 51 | function( create_library_target target_name 52 | src_file 53 | include_list ) 54 | add_library( ${target_name} STATIC ${src_file} ) 55 | target_include_directories( ${target_name} PUBLIC ${include_list} ) 56 | 57 | set_target_properties( ${target_name} PROPERTIES 58 | COMPILE_FLAGS "-Wextra -Wpedantic \ 59 | -fprofile-arcs -ftest-coverage -fprofile-generate \ 60 | -Wno-unused-but-set-variable" 61 | LINK_FLAGS "-fprofile-arcs -ftest-coverage \ 62 | -fprofile-generate " 63 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib ) 64 | endfunction() 65 | -------------------------------------------------------------------------------- /tools/unity/project.yml: -------------------------------------------------------------------------------- 1 | :unity: 2 | :when_no_prototypes: :warn 3 | :enforce_strict_ordering: TRUE 4 | :treat_as: 5 | uint8: HEX8 6 | uint16: HEX16 7 | uint32: UINT32 8 | int8: INT8 9 | bool: UINT8 10 | :treat_externs: :exclude 11 | :weak: __attribute__((weak)) 12 | :treat_externs: :include 13 | --------------------------------------------------------------------------------