pax_global_header00006660000000000000000000000064151423243360014515gustar00rootroot0000000000000052 comment=99fa50f30e325609394c324c8ff71cfbbe95d8ab eclipse-mosquitto-mosquitto-691eab3/000077500000000000000000000000001514232433600176615ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/.editorconfig000066400000000000000000000004451514232433600223410ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{c,h,cpp,hpp}] indent_style = tab indent_size = 4 [*.py] indent_style = space indent_size = 4 [Makefile] indent_style = tab [*.{yml,yaml}] indent_style = space indent_size = 2 eclipse-mosquitto-mosquitto-691eab3/.github/000077500000000000000000000000001514232433600212215ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/.github/issue_template.md000066400000000000000000000015541514232433600245730ustar00rootroot00000000000000 eclipse-mosquitto-mosquitto-691eab3/.github/labeler.yml000066400000000000000000000000401514232433600233440ustar00rootroot00000000000000'Status: Available': - '/.*/' eclipse-mosquitto-mosquitto-691eab3/.github/pull_request_template.md000066400000000000000000000022421514232433600261620ustar00rootroot00000000000000Thank you for contributing your time to the Mosquitto project! Before you go any further, please note that we cannot accept contributions if you haven't signed the [Eclipse Contributor Agreement](https://www.eclipse.org/legal/ECA.php). If you aren't able to do that, or just don't want to, please describe your bug fix/feature change in an issue. For simple bug fixes it is can be just as easy for us to be told about the problem and then go fix it directly. Then please check the following list of things we ask for in your pull request: - [ ] Have you signed the [Eclipse Contributor Agreement](https://www.eclipse.org/legal/ECA.php), using the same email address as you used in your commits? - [ ] Do each of your commits have a "Signed-off-by" line, with the correct email address? Use "git commit -s" to generate this line for you. - [ ] If you are contributing a new feature, is your work based off the develop branch? - [ ] If you are contributing a bugfix, is your work based off the fixes branch? - [ ] Have you added an explanation of what your changes do and why you'd like us to include them? - [ ] Have you successfully run `make test` with your changes locally? ----- eclipse-mosquitto-mosquitto-691eab3/.github/workflows/000077500000000000000000000000001514232433600232565ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/.github/workflows/build-variants.yml000066400000000000000000000015251514232433600267300ustar00rootroot00000000000000name: Mosquitto - Build variants on: push: branches: - master - develop - fixes - release/* pull_request: branches: - master - develop - fixes - release/* jobs: build: runs-on: ubuntu-latest steps: - name: Install third party dependencies run: | sudo apt-get update sudo apt-get install -y \ docbook-xsl \ libargon2-dev \ libc-ares-dev \ libcjson-dev \ libcunit1-dev \ libedit-dev \ libgmock-dev \ libmicrohttpd-dev \ libssl-dev \ libsystemd-dev \ libwrap0-dev \ python3-all \ uthash-dev \ xsltproc - uses: actions/checkout@v6 with: submodules: 'true' - name: build run: ./buildtest.py eclipse-mosquitto-mosquitto-691eab3/.github/workflows/cifuzz.yml000066400000000000000000000021371514232433600253160ustar00rootroot00000000000000name: CIFuzz on: workflow_dispatch: pull_request: branches: - master - develop paths: - '**.c' - '**.cpp' - '**.h' permissions: {} jobs: Fuzzing: runs-on: ubuntu-latest permissions: security-events: write steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'mosquitto' - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'mosquitto' fuzz-seconds: 600 output-sarif: true - name: Upload Crash uses: actions/upload-artifact@v6 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' uses: github/codeql-action/upload-sarif@v3 with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif checkout_path: cifuzz-sarif eclipse-mosquitto-mosquitto-691eab3/.github/workflows/codeql-analysis.yml000066400000000000000000000050231514232433600270710ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: workflow_dispatch: push: branches: [ master, fixes, develop ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '44 18 * * 1' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: [ 'cpp', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl - run: sudo apt-get update && sudo apt-get install -y gcc g++ git libssl-dev libargon2-dev libedit-dev libmicrohttpd-dev # Install cJSON - run: git clone https://github.com/DaveGamble/cJSON /tmp/cJSON - run: wget https://github.com/DaveGamble/cJSON/archive/v1.7.19.tar.gz -O /tmp/cjson.tar.gz - run: mkdir -p /tmp/build/cjson - run: tar --strip=1 -xf /tmp/cjson.tar.gz -C /tmp/build/cjson - run: rm /tmp/cjson.tar.gz - run: cd /tmp/build/cjson && cmake . -DCMAKE_BUILD_TYPE=MinSizeRel -DCJSON_BUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/usr - run: sudo make -C /tmp/build/cjson install # Now build Mosquitto - run: make binary - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 eclipse-mosquitto-mosquitto-691eab3/.github/workflows/coverage.yml000066400000000000000000000026731514232433600256040ustar00rootroot00000000000000name: Coverage on: workflow_dispatch: push: branches: - master - develop - fixes - release/* pull_request: branches: - master - develop - fixes - release/* jobs: coverage: runs-on: ubuntu-22.04 steps: - run: | sudo apt-get update sudo apt-get install -y \ docbook-xsl \ lcov \ libargon2-dev \ libc-ares-dev \ libcjson-dev \ libcjson1 \ libcunit1-dev \ libedit-dev \ libgmock-dev \ libmicrohttpd-dev \ libssl-dev \ libwrap0-dev \ microsocks \ python3-all \ python3-paho-mqtt \ python3-psutil \ uthash-dev \ xsltproc - uses: actions/checkout@v6 - run: | make \ WITH_COVERAGE=yes \ CFLAGS="-O0 -Wall -ggdb -fprofile-arcs" \ -j $(nproc) \ binary make \ WITH_COVERAGE=yes \ CFLAGS="-O0 -Wall -ggdb -fprofile-arcs" \ -j $(nproc) \ test-compile - run: | make -C test test - run: | lcov --capture --directory . --output-file coverage.info --no-external - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true files: ./coverage.info verbose: true eclipse-mosquitto-mosquitto-691eab3/.github/workflows/coverity-scan-develop.yml000066400000000000000000000021061514232433600302220ustar00rootroot00000000000000name: Coverity Scan develop branch on a weekly basis on: workflow_dispatch: schedule: - cron: "7 3 * * 0" jobs: coverity: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: ref: develop - name: Dependencies run: | sudo apt-get update sudo apt-get install -y \ docbook-xsl \ google-mock \ googletest \ lcov \ libc-ares-dev \ libcjson-dev \ libcjson1 \ libcunit1-dev \ libedit-dev \ libgmock-dev \ libmicrohttpd-dev \ libssl-dev \ libwrap0-dev \ microsocks \ python3-all \ python3-paho-mqtt \ python3-psutil \ uthash-dev \ xsltproc - uses: vapier/coverity-scan-action@v1 with: build_language: 'cxx' project: "eclipse/mosquitto" token: ${{ secrets.COVERITY_SCAN_TOKEN }} email: ${{ secrets.COVERITY_SCAN_EMAIL }} command: "make binary-all" eclipse-mosquitto-mosquitto-691eab3/.github/workflows/coverity-scan-fixes.yml000066400000000000000000000017641514232433600277130ustar00rootroot00000000000000name: Coverity Scan fixes branch on a weekly basis on: workflow_dispatch: schedule: - cron: "7 3 * * 3" jobs: coverity: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: ref: fixes - name: Dependencies run: | sudo apt-get update sudo apt-get install -y \ docbook-xsl \ lcov \ libc-ares-dev \ libcjson-dev \ libcjson1 \ libcunit1-dev \ libedit-dev \ libmicrohttpd-dev \ libssl-dev \ libwrap0-dev \ microsocks \ python3-all \ python3-paho-mqtt \ python3-psutil \ uthash-dev \ xsltproc - uses: vapier/coverity-scan-action@v1 with: build_language: 'cxx' project: "eclipse/mosquitto" token: ${{ secrets.COVERITY_SCAN_TOKEN }} email: ${{ secrets.COVERITY_SCAN_EMAIL }} command: "make binary-all" eclipse-mosquitto-mosquitto-691eab3/.github/workflows/delete-old-workflow-runs.yml000066400000000000000000000006631514232433600306610ustar00rootroot00000000000000name: Delete old workflow runs on: workflow_dispatch: schedule: - cron: '0 0 1 * *' # Run monthly, at 00:00 on the 1st day of month. jobs: del_runs: runs-on: ubuntu-latest steps: - name: Delete workflow runs uses: Mattraks/delete-workflow-runs@v2 with: token: ${{ github.token }} repository: ${{ github.repository }} retain_days: 30 keep_minimum_runs: 6 eclipse-mosquitto-mosquitto-691eab3/.github/workflows/issue-labler.yml000066400000000000000000000005631514232433600263740ustar00rootroot00000000000000name: "Issue Labeler" on: issues: types: [opened] permissions: issues: write contents: read jobs: triage: runs-on: ubuntu-latest steps: - uses: github/issue-labeler@v3.4 with: configuration-path: .github/labeler.yml not-before: 2024-11-03T00:00:00Z enable-versioned-regex: 0 repo-token: ${{ github.token }} eclipse-mosquitto-mosquitto-691eab3/.github/workflows/lock.yml000066400000000000000000000004731514232433600247350ustar00rootroot00000000000000name: 'Lock Threads' on: schedule: - cron: '0 0 * * 0' workflow_dispatch: permissions: issues: write pull-requests: write concurrency: group: lock-threads jobs: action: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v6 with: issue-inactive-days: '90' eclipse-mosquitto-mosquitto-691eab3/.github/workflows/macos.yml000066400000000000000000000036401514232433600251060ustar00rootroot00000000000000name: Mac OS build on: workflow_dispatch: push: branches: - master - fixes - develop - release/* tags: - 'v[0-9]+.*' pull_request: branches: - master - fixes - develop - release/* env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: mosquitto: runs-on: macos-latest steps: - uses: actions/checkout@v6 - name: pin cmake to 3.x series uses: jwlawson/actions-setup-cmake@09fd9b0fb3b239b4b68d9256cd65adf8d6b91da0 with: cmake-version: '3.31.6' - name: Python test dependencies uses: actions/setup-python@v6 with: cache: 'pip' - name: Install Homebrew dependencies run: | brew update brew list cmake || brew install cmake brew install \ argon2 \ cjson \ cunit \ docbook-xsl \ gcc \ googletest \ libedit \ libmicrohttpd \ make \ openssl \ uthash - name: Configure CMake run: | EDITLINE_DIR=$(brew --prefix libedit) HOMEBREW_PREFIX=$(brew --prefix) cmake -B ${{github.workspace}}/build64 \ -G Ninja \ -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ -DCMAKE_PREFIX_PATH="$HOMEBREW_PREFIX" \ -DWITH_DOCS=OFF \ -DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3) - name: Build run: | cmake --build ${{github.workspace}}/build64 \ --config ${{env.BUILD_TYPE}} - name: Test working-directory: build64/ run: | python3 -m venv venv source venv/bin/activate python3 -m pip install --upgrade pip python3 -m pip install psutil ctest --output-on-failure --repeat until-pass:5 eclipse-mosquitto-mosquitto-691eab3/.github/workflows/mosquitto-cmake.yml000066400000000000000000000022521514232433600271240ustar00rootroot00000000000000name: Mosquitto - CMake on: workflow_dispatch: push: branches: - master - fixes - develop - release/* tags: - 'v[0-9]+.*' pull_request: branches: - master - fixes - develop - release/* jobs: build: runs-on: ubuntu-latest steps: - name: Install third party dependencies run: | sudo apt-get update sudo apt-get install -y \ docbook-xsl \ lcov \ libargon2-dev \ libc-ares-dev \ libcjson-dev \ libcjson1 \ libcunit1-dev \ libedit-dev \ libgmock-dev \ libmicrohttpd-dev \ libssl-dev \ libwrap0-dev \ microsocks \ python3-all \ python3-paho-mqtt \ python3-psutil \ uthash-dev \ xsltproc - uses: actions/checkout@v6 - run: cmake -E make_directory build - run: | cmake \ -DCMAKE_BUILD_TYPE=Debug \ -S . \ -B build - run: cmake --build build --parallel $(nproc) - working-directory: build/ run: ctest --output-on-failure --repeat until-pass:5 eclipse-mosquitto-mosquitto-691eab3/.github/workflows/mosquitto-make-asan.yml000066400000000000000000000020271514232433600277010ustar00rootroot00000000000000name: Mosquitto - Make ASAN on: push: branches: - master - develop - fixes - release/* pull_request: branches: - master - develop - fixes - release/* jobs: build: runs-on: ubuntu-latest steps: - name: Install third party dependencies run: | sudo apt-get update sudo apt-get install -y \ clang \ docbook-xsl \ lcov \ libargon2-dev \ libc-ares-dev \ libcjson-dev \ libcjson1 \ libcunit1-dev \ libedit-dev \ libgmock-dev \ libmicrohttpd-dev \ libssl-dev \ libwrap0-dev \ microsocks \ python3-all \ python3-paho-mqtt \ python3-psutil \ uthash-dev \ xsltproc - uses: actions/checkout@v6 with: submodules: 'true' - name: make run: make WITH_ASAN=yes - name: make test run: | make WITH_ASAN=yes ptest eclipse-mosquitto-mosquitto-691eab3/.github/workflows/mosquitto-make.yml000066400000000000000000000020271514232433600267610ustar00rootroot00000000000000name: Mosquitto - Make on: workflow_dispatch: push: branches: - master - fixes - develop - release/* tags: - 'v[0-9]+.*' pull_request: branches: - master - fixes - develop - release/* jobs: build: runs-on: ubuntu-latest steps: - name: Install third party dependencies run: | sudo apt-get update sudo apt-get install -y \ docbook-xsl \ lcov \ libargon2-dev \ libc-ares-dev \ libcjson-dev \ libcjson1 \ libcunit1-dev \ libedit-dev \ libgmock-dev \ libmicrohttpd-dev \ libssl-dev \ libwrap0-dev \ microsocks \ python3-all \ python3-paho-mqtt \ python3-psutil \ uthash-dev \ xsltproc - uses: actions/checkout@v6 with: submodules: 'true' - name: make run: make - name: make test run: | make ptest eclipse-mosquitto-mosquitto-691eab3/.github/workflows/windows-x86.yml000066400000000000000000000033671514232433600261270ustar00rootroot00000000000000name: Windows x86 build on: workflow_dispatch: push: branches: - master - fixes - develop - release/* tags: - 'v[0-9]+.*' pull_request: branches: - master - fixes - develop - release/* env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: mosquitto: runs-on: windows-2022 steps: - uses: actions/checkout@v6 - name: vcpkg build uses: johnwason/vcpkg-action@v7 id: vcpkg with: manifest-dir: ${{ github.workspace }} triplet: x86-windows token: ${{ github.token }} - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DWITH_WEBSOCKETS=ON -DWITH_TESTS=OFF -DCMAKE_GENERATOR_PLATFORM=WIN32 -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x86-windows -DVCPKG_MANIFEST_MODE=ON -DWITH_HTTP_API=ON -DHTTP_API_DIR="C:\\Program Files (x86)\\Mosquitto\\dashboard" - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - uses: suisei-cn/actions-download-file@v1.6.1 id: vcredist name: Download VC redistributable with: url: https://aka.ms/vs/17/release/vc_redist.x86.exe target: ${{github.workspace}}/installer/ - name: Installer uses: joncloud/makensis-action@v5.0 with: script-file: ${{github.workspace}}/installer/mosquitto.nsi - name: Upload installer to artifacts uses: actions/upload-artifact@v6 with: name: installer path: ${{ github.workspace }}/installer/mosquitto*.exe eclipse-mosquitto-mosquitto-691eab3/.github/workflows/windows.yml000066400000000000000000000036531514232433600255020ustar00rootroot00000000000000name: Windows build on: workflow_dispatch: push: branches: - master - fixes - develop - release/* tags: - 'v[0-9]+.*' pull_request: branches: - master - fixes - develop - release/* env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: mosquitto: runs-on: windows-2022 steps: - uses: actions/checkout@v6 - name: pin cmake to 3.x series uses: jwlawson/actions-setup-cmake@09fd9b0fb3b239b4b68d9256cd65adf8d6b91da0 with: cmake-version: '3.31.6' - name: vcpkg build uses: johnwason/vcpkg-action@v7 id: vcpkg with: manifest-dir: ${{ github.workspace }} triplet: x64-windows-release token: ${{ github.token }} - name: Configure CMake run: cmake -B ${{github.workspace}}/build64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DWITH_WEBSOCKETS=ON -DWITH_TESTS=OFF -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-release -DVCPKG_MANIFEST_MODE=ON -DWITH_HTTP_API=ON -DHTTP_API_DIR="C:\\Program Files\\Mosquitto\\dashboard" - name: Build run: cmake --build ${{github.workspace}}/build64 --config ${{env.BUILD_TYPE}} - uses: suisei-cn/actions-download-file@v1.6.1 id: vcredist name: Download VC redistributable with: url: https://aka.ms/vs/17/release/vc_redist.x64.exe target: ${{github.workspace}}/installer/ - name: Installer uses: joncloud/makensis-action@v5.0 with: script-file: ${{github.workspace}}/installer/mosquitto64.nsi - name: Upload installer to artifacts uses: actions/upload-artifact@v6 with: name: installer path: ${{ github.workspace }}/installer/mosquitto*.exe eclipse-mosquitto-mosquitto-691eab3/.gitignore000066400000000000000000000076631514232433600216650ustar00rootroot00000000000000# .gitignore *.a *.db *.gcda *.gcno *.exe *.o *.old *.pyc *.so *.vglog callgrind.out.* coverage.info dhat.out.* massif.out.* vglog* c/*.test cpp/*.test apps/db_dump/mosquitto_db_dump apps/db_dump/mosquitto_db_dump.a apps/mosquitto_ctrl/mosquitto_ctrl apps/mosquitto_passwd/mosquitto_passwd apps/mosquitto_passwd/mosquitto_passwd.a apps/mosquitto_signal/mosquitto_signal build/ build64/ client/mosquitto_pub client/mosquitto_pub.a client/mosquitto_rr client/mosquitto_rr.a client/mosquitto_sub client/mosquitto_sub.a client/testing client/testing.c cov-int/ dist/ docker/local/mosq.tar.gz examples/mysql_log/mosquitto_mysql_log examples/temperature_conversion/mqtt_temperature_conversion examples/publish/basic-1 examples/publish/basic-websockets-1 fuzzing/apps/db_dump/db_dump_fuzz_load fuzzing/apps/db_dump/db_dump_fuzz_load_client_stats fuzzing/apps/db_dump/db_dump_fuzz_load_stats fuzzing/apps/mosquitto_passwd/mosquitto_passwd_fuzz_load fuzzing/broker/broker_fuzz_acl_file fuzzing/broker/broker_fuzz_handle_auth fuzzing/broker/broker_fuzz_handle_connect fuzzing/broker/broker_fuzz_handle_publish fuzzing/broker/broker_fuzz_handle_subscribe fuzzing/broker/broker_fuzz_handle_unsubscribe fuzzing/broker/broker_fuzz_initial_packet fuzzing/broker/broker_fuzz_initial_packet_with_init fuzzing/broker/broker_fuzz_password_file fuzzing/broker/broker_fuzz_proxy_v1 fuzzing/broker/broker_fuzz_proxy_v2 fuzzing/broker/broker_fuzz_psk_file fuzzing/broker/broker_fuzz_queue_msg fuzzing/broker/broker_fuzz_read_handle fuzzing/broker/broker_fuzz_second_packet fuzzing/broker/broker_fuzz_second_packet_with_init fuzzing/broker/broker_fuzz_test_config fuzzing/corpora/broker/* fuzzing/corpora/broker_packet_seed_corpus.zip fuzzing/corpora/client/* fuzzing/corpora/client_packet_seed_corpus.zip fuzzing/corpora/db_dump_seed_corpus.zip fuzzing/lib/lib_fuzz_pub_topic_check2 fuzzing/lib/lib_fuzz_sub_topic_check2 fuzzing/lib/lib_fuzz_utf8 fuzzing/libcommon/libcommon_fuzz_property fuzzing/libcommon/libcommon_fuzz_property.pb.cc fuzzing/libcommon/libcommon_fuzz_property.pb.h fuzzing/libcommon/libcommon_fuzz_pub_topic_check2 fuzzing/libcommon/libcommon_fuzz_sub_topic_check2 fuzzing/libcommon/libcommon_fuzz_topic_matching fuzzing/libcommon/libcommon_fuzz_topic_matching.pb.cc fuzzing/libcommon/libcommon_fuzz_topic_matching.pb.h fuzzing/libcommon/libcommon_fuzz_topic_tokenise fuzzing/libcommon/libcommon_fuzz_utf8 fuzzing/plugins/dynamic-security/dynsec_fuzz_load lib/cpp/libmosquittopp.so* lib/cpp/libmosquittopp.a lib/libmosquitto.so* lib/libmosquitto.a man/libmosquitto.3 man/mosquitto-tls.7 man/mosquitto.7 man/mosquitto.8 man/mosquitto.conf.5 man/mosquitto_ctrl.1 man/mosquitto_ctrl_dynsec.1 man/mosquitto_ctrl_shell.1 man/mosquitto_passwd.1 man/mosquitto_pub.1 man/mosquitto_rr.1 man/mosquitto_signal.1 man/mosquitto_sub.1 man/mqtt.7 out/ src/mosquitto src/mosquitto_broker.a test/apps/ctrl/ctrl_shell_broker_test test/apps/ctrl/ctrl_shell_completion_test test/apps/ctrl/ctrl_shell_dynsec_test test/apps/ctrl/ctrl_shell_help_test test/apps/ctrl/ctrl_shell_options_test test/apps/ctrl/ctrl_shell_pre_connect_test test/apps/ctrl/ctrl_shell_test test/broker/broker.pid test/test_client test/fake_user test/msgsps_pub test/msgsps_sub test/msgsps_pub.dat test/msgsps_sub.dat test/broker/c/auth_plugin.so test/broker/c/*.test test/ssl/*.csr test/ssl/rootCA/ test/ssl/signingCA/ test/lib/c/*.test test/lib/cpp/*.test test/unit/broker/bridge_topic_test test/unit/broker/keepalive_test test/unit/broker/persist_read_test test/unit/broker/persist_write_test test/unit/broker/subs_test test/unit/coverage.info test/unit/lib/lib_test test/unit/libcommon/libcommon_test test/unit/tls_test www/cache/ __pycache__ *.sync-conflict-* # Debian generated files debian/.debhelper/ debian/debhelper-build-stamp debian/files debian/*.log debian/*.substvars debian/*mosquitto*/ debian/*.debhelper debian/tmp/ obj-*/ # Emacs generated files *~ # clangd .cache/ # VSCode generated files .vscode/ # Others tmp/ failed-tests.json eclipse-mosquitto-mosquitto-691eab3/.uncrustify.cfg000066400000000000000000004443571514232433600226540ustar00rootroot00000000000000# Uncrustify_d-0.80.1-109-229eb1c05 # # General options # set FOR HASH_ITER set FOR DL_FOREACH set FOR DL_FOREACH_SAFE set FOR DL_FOREACH_SAFE2 set FOR LL_FOREACH set FOR LL_FOREACH_SAFE set FOR cJSON_ArrayForEach # The type of line endings. # # Default: auto newlines = auto # lf/crlf/cr/auto # The original size of tabs in the input. # # Default: 8 input_tab_size = 4 # unsigned number # The size of tabs in the output (only used if align_with_tabs=true). # # Default: 8 output_tab_size = 4 # unsigned number # The ASCII value of the string escape char, usually 92 (\) or (Pawn) 94 (^). # # Default: 92 string_escape_char = 92 # unsigned number # Alternate string escape char (usually only used for Pawn). # Only works right before the quote char. string_escape_char2 = 0 # unsigned number # Replace tab characters found in string literals with the escape sequence \t # instead. string_replace_tab_chars = false # true/false # Allow interpreting '>=' and '>>=' as part of a template in code like # 'void f(list>=val);'. If true, 'assert(x<0 && y>=3)' will be broken. # Improvements to template detection may make this option obsolete. tok_split_gte = false # true/false # Disable formatting of NL_CONT ('\\n') ended lines (e.g. multi-line macros). disable_processing_nl_cont = false # true/false # Specify the marker used in comments to disable processing of part of the # file. # # Default: *INDENT-OFF* disable_processing_cmt = " *INDENT-OFF*" # string # Specify the marker used in comments to (re)enable processing in a file. # # Default: *INDENT-ON* enable_processing_cmt = " *INDENT-ON*" # string # Enable parsing of digraphs. enable_digraphs = false # true/false # Option to allow both disable_processing_cmt and enable_processing_cmt # strings, if specified, to be interpreted as ECMAScript regular expressions. # If true, a regex search will be performed within comments according to the # specified patterns in order to disable/enable processing. processing_cmt_as_regex = false # true/false # Add or remove the UTF-8 BOM (recommend 'remove'). utf8_bom = remove # ignore/add/remove/force # If the file contains bytes with values between 128 and 255, but is not # UTF-8, then output as UTF-8. utf8_byte = false # true/false # Force the output encoding to UTF-8. utf8_force = true # true/false # # Spacing options # # Add or remove space around non-assignment symbolic operators ('+', '/', '%', # '<<', and so forth). sp_arith = ignore # ignore/add/remove/force # Add or remove space around arithmetic operators '+' and '-'. # # Overrides sp_arith. sp_arith_additive = ignore # ignore/add/remove/force # Add or remove space around assignment operator '=', '+=', etc. sp_assign = ignore # ignore/add/remove/force # Add or remove space around '=' in C++11 lambda capture specifications. # # Overrides sp_assign. sp_cpp_lambda_assign = ignore # ignore/add/remove/force # Add or remove space after the capture specification of a C++11 lambda when # an argument list is present, as in '[] (int x){ ... }'. sp_cpp_lambda_square_paren = ignore # ignore/add/remove/force # Add or remove space after the capture specification of a C++11 lambda with # no argument list is present, as in '[] { ... }'. sp_cpp_lambda_square_brace = ignore # ignore/add/remove/force # Add or remove space after the opening parenthesis and before the closing # parenthesis of a argument list of a C++11 lambda, as in # '[]( ){ ... }' # with an empty list. sp_cpp_lambda_argument_list_empty = ignore # ignore/add/remove/force # Add or remove space after the opening parenthesis and before the closing # parenthesis of a argument list of a C++11 lambda, as in # '[]( int x ){ ... }'. sp_cpp_lambda_argument_list = ignore # ignore/add/remove/force # Add or remove space after the argument list of a C++11 lambda, as in # '[](int x) { ... }'. sp_cpp_lambda_paren_brace = ignore # ignore/add/remove/force # Add or remove space between a lambda body and its call operator of an # immediately invoked lambda, as in '[]( ... ){ ... } ( ... )'. sp_cpp_lambda_fparen = ignore # ignore/add/remove/force # Add or remove space around assignment operator '=' in a prototype. # # If set to ignore, use sp_assign. sp_assign_default = ignore # ignore/add/remove/force # Add or remove space before assignment operator '=', '+=', etc. # # Overrides sp_assign. sp_before_assign = ignore # ignore/add/remove/force # Add or remove space after assignment operator '=', '+=', etc. # # Overrides sp_assign. sp_after_assign = ignore # ignore/add/remove/force # Add or remove space in 'enum {'. # # Default: add sp_enum_brace = remove # ignore/add/remove/force # Add or remove space in 'NS_ENUM ('. sp_enum_paren = ignore # ignore/add/remove/force # Add or remove space around assignment '=' in enum. sp_enum_assign = ignore # ignore/add/remove/force # Add or remove space before assignment '=' in enum. # # Overrides sp_enum_assign. sp_enum_before_assign = ignore # ignore/add/remove/force # Add or remove space after assignment '=' in enum. # # Overrides sp_enum_assign. sp_enum_after_assign = ignore # ignore/add/remove/force # Add or remove space around assignment ':' in enum. sp_enum_colon = ignore # ignore/add/remove/force # Add or remove space around preprocessor '##' concatenation operator. # # Default: add sp_pp_concat = add # ignore/add/remove/force # Add or remove space after preprocessor '#' stringify operator. # Also affects the '#@' charizing operator. sp_pp_stringify = ignore # ignore/add/remove/force # Add or remove space before preprocessor '#' stringify operator # as in '#define x(y) L#y'. sp_before_pp_stringify = ignore # ignore/add/remove/force # Add or remove space around boolean operators '&&' and '||'. sp_bool = ignore # ignore/add/remove/force # Add or remove space around compare operator '<', '>', '==', etc. sp_compare = ignore # ignore/add/remove/force # Add or remove space inside '(' and ')'. sp_inside_paren = remove # ignore/add/remove/force # Add or remove space between nested parentheses, i.e. '((' vs. ') )'. sp_paren_paren = remove # ignore/add/remove/force # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('. sp_cparen_oparen = ignore # ignore/add/remove/force # Add or remove space between ')' and '{'. sp_paren_brace = remove # ignore/add/remove/force # Add or remove space between nested braces, i.e. '{{' vs. '{ {'. sp_brace_brace = remove # ignore/add/remove/force # Add or remove space before pointer star '*'. sp_before_ptr_star = add # ignore/add/remove/force # Add or remove space before pointer star '*' that isn't followed by a # variable name. If set to ignore, sp_before_ptr_star is used instead. sp_before_unnamed_ptr_star = add # ignore/add/remove/force # Add or remove space before pointer star '*' that is followed by a qualifier. # If set to ignore, sp_before_unnamed_ptr_star is used instead. sp_before_qualifier_ptr_star = add # ignore/add/remove/force # Add or remove space before pointer star '*' that is followed by 'operator' keyword. # If set to ignore, sp_before_unnamed_ptr_star is used instead. sp_before_operator_ptr_star = ignore # ignore/add/remove/force # Add or remove space before pointer star '*' that is followed by # a class scope (as in 'int *MyClass::method()') or namespace scope # (as in 'int *my_ns::func()'). # If set to ignore, sp_before_unnamed_ptr_star is used instead. sp_before_scope_ptr_star = ignore # ignore/add/remove/force # Add or remove space before pointer star '*' that is followed by '::', # as in 'int *::func()'. # If set to ignore, sp_before_unnamed_ptr_star is used instead. sp_before_global_scope_ptr_star = ignore # ignore/add/remove/force # Add or remove space between a qualifier and a pointer star '*' that isn't # followed by a variable name, as in '(char const *)'. If set to ignore, # sp_before_ptr_star is used instead. sp_qualifier_unnamed_ptr_star = ignore # ignore/add/remove/force # Add or remove space between pointer stars '*', as in 'int ***a;'. sp_between_ptr_star = remove # ignore/add/remove/force # Add or remove space between pointer star '*' and reference '&', as in 'int *& a;'. sp_between_ptr_ref = ignore # ignore/add/remove/force # Add or remove space after pointer star '*', if followed by a word. # # Overrides sp_type_func. sp_after_ptr_star = remove # ignore/add/remove/force # Add or remove space after pointer caret '^', if followed by a word. sp_after_ptr_block_caret = ignore # ignore/add/remove/force # Add or remove space after pointer star '*', if followed by a qualifier. sp_after_ptr_star_qualifier = ignore # ignore/add/remove/force # Add or remove space after a pointer star '*', if followed by a function # prototype or function definition. # # Overrides sp_after_ptr_star and sp_type_func. sp_after_ptr_star_func = remove # ignore/add/remove/force # Add or remove space after a pointer star '*' in the trailing return of a # function prototype or function definition. sp_after_ptr_star_trailing = ignore # ignore/add/remove/force # Add or remove space between the pointer star '*' and the name of the variable # in a function pointer definition. sp_ptr_star_func_var = remove # ignore/add/remove/force # Add or remove space between the pointer star '*' and the name of the type # in a function pointer type definition. sp_ptr_star_func_type = ignore # ignore/add/remove/force # Add or remove space after a pointer star '*', if followed by an open # parenthesis, as in 'void* (*)()'. sp_ptr_star_paren = ignore # ignore/add/remove/force # Add or remove space before a pointer star '*', if followed by a function # prototype or function definition. If set to ignore, sp_before_ptr_star is # used instead. sp_before_ptr_star_func = ignore # ignore/add/remove/force # Add or remove space between a qualifier and a pointer star '*' followed by # the name of the function in a function prototype or definition, as in # 'char const *foo()`. If set to ignore, sp_before_ptr_star is used instead. sp_qualifier_ptr_star_func = ignore # ignore/add/remove/force # Add or remove space before a pointer star '*' in the trailing return of a # function prototype or function definition. sp_before_ptr_star_trailing = ignore # ignore/add/remove/force # Add or remove space between a qualifier and a pointer star '*' in the # trailing return of a function prototype or function definition, as in # 'auto foo() -> char const *'. sp_qualifier_ptr_star_trailing = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&'. sp_before_byref = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&' that isn't followed by a # variable name. If set to ignore, sp_before_byref is used instead. sp_before_unnamed_byref = ignore # ignore/add/remove/force # Add or remove space after reference sign '&', if followed by a word. # # Overrides sp_type_func. sp_after_byref = ignore # ignore/add/remove/force # Add or remove space after a reference sign '&', if followed by a function # prototype or function definition. # # Overrides sp_after_byref and sp_type_func. sp_after_byref_func = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&', if followed by a function # prototype or function definition. sp_before_byref_func = ignore # ignore/add/remove/force # Add or remove space after a reference sign '&', if followed by an open # parenthesis, as in 'char& (*)()'. sp_byref_paren = ignore # ignore/add/remove/force # Add or remove space between type and word. In cases where total removal of # whitespace would be a syntax error, a value of 'remove' is treated the same # as 'force'. # # This also affects some other instances of space following a type that are # not covered by other options; for example, between the return type and # parenthesis of a function type template argument, between the type and # parenthesis of an array parameter, or between 'decltype(...)' and the # following word. # # Default: force sp_after_type = force # ignore/add/remove/force # Add or remove space between 'decltype(...)' and word, # brace or function call. sp_after_decltype = ignore # ignore/add/remove/force # (D) Add or remove space before the parenthesis in the D constructs # 'template Foo(' and 'class Foo('. sp_before_template_paren = ignore # ignore/add/remove/force # Add or remove space between 'template' and '<'. # If set to ignore, sp_before_angle is used. sp_template_angle = ignore # ignore/add/remove/force # Add or remove space before '<'. sp_before_angle = ignore # ignore/add/remove/force # Add or remove space inside '<' and '>'. sp_inside_angle = ignore # ignore/add/remove/force # Add or remove space inside '<>'. # if empty. sp_inside_angle_empty = ignore # ignore/add/remove/force # Add or remove space between '>' and ':'. sp_angle_colon = ignore # ignore/add/remove/force # Add or remove space after '>'. sp_after_angle = ignore # ignore/add/remove/force # Add or remove space between '>' and '(' as found in 'new List(foo);'. sp_angle_paren = ignore # ignore/add/remove/force # Add or remove space between '>' and '()' as found in 'new List();'. sp_angle_paren_empty = ignore # ignore/add/remove/force # Add or remove space between '>' and a word as in 'List m;' or # 'template static ...'. sp_angle_word = ignore # ignore/add/remove/force # Add or remove space between '>' and '>' in '>>' (template stuff). # # Default: add sp_angle_shift = add # ignore/add/remove/force # (C++11) Permit removal of the space between '>>' in 'foo >'. Note # that sp_angle_shift cannot remove the space without this option. sp_permit_cpp11_shift = false # true/false # Add or remove space before '(' of control statements ('if', 'for', 'switch', # 'while', etc.). sp_before_sparen = remove # ignore/add/remove/force # Add or remove space inside '(' and ')' of control statements other than # 'for'. sp_inside_sparen = remove # ignore/add/remove/force # Add or remove space after '(' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_open = ignore # ignore/add/remove/force # Add or remove space before ')' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_close = ignore # ignore/add/remove/force # Add or remove space inside '(' and ')' of 'for' statements. sp_inside_for = ignore # ignore/add/remove/force # Add or remove space after '(' of 'for' statements. # # Overrides sp_inside_for. sp_inside_for_open = ignore # ignore/add/remove/force # Add or remove space before ')' of 'for' statements. # # Overrides sp_inside_for. sp_inside_for_close = ignore # ignore/add/remove/force # Add or remove space between '((' or '))' of control statements. sp_sparen_paren = remove # ignore/add/remove/force # Add or remove space after ')' of control statements. sp_after_sparen = ignore # ignore/add/remove/force # Add or remove space between ')' and '{' of control statements. sp_sparen_brace = remove # ignore/add/remove/force # Add or remove space between 'do' and '{'. sp_do_brace_open = remove # ignore/add/remove/force # Add or remove space between '}' and 'while'. sp_brace_close_while = remove # ignore/add/remove/force # Add or remove space between 'while' and '('. Overrides sp_before_sparen. sp_while_paren_open = remove # ignore/add/remove/force # (D) Add or remove space between 'invariant' and '('. sp_invariant_paren = ignore # ignore/add/remove/force # (D) Add or remove space after the ')' in 'invariant (C) c'. sp_after_invariant_paren = ignore # ignore/add/remove/force # Add or remove space before empty statement ';' on 'if', 'for' and 'while'. # examples: # if (b) ; # for (a=1; a<10; a++) ; # while (*p++ = ' ') ; sp_special_semi = ignore # ignore/add/remove/force # Add or remove space before ';'. # # Default: remove sp_before_semi = remove # ignore/add/remove/force # Add or remove space before ';' in non-empty 'for' statements. sp_before_semi_for = ignore # ignore/add/remove/force # Add or remove space before a semicolon of an empty left part of a for # statement, as in 'for ( ; ; )'. sp_before_semi_for_empty = ignore # ignore/add/remove/force # Add or remove space between the semicolons of an empty middle part of a for # statement, as in 'for ( ; ; )'. sp_between_semi_for_empty = ignore # ignore/add/remove/force # Add or remove space after ';', except when followed by a comment. # # Default: add sp_after_semi = add # ignore/add/remove/force # Add or remove space after ';' in non-empty 'for' statements. # # Default: force sp_after_semi_for = force # ignore/add/remove/force # Add or remove space after the final semicolon of an empty part of a for # statement, as in 'for ( ; ; )'. sp_after_semi_for_empty = ignore # ignore/add/remove/force # Add or remove space before '[' (except '[]'). sp_before_square = ignore # ignore/add/remove/force # Add or remove space before '[' for a variable definition. # # Default: remove sp_before_vardef_square = remove # ignore/add/remove/force # Add or remove space before '[' for asm block. sp_before_square_asm_block = ignore # ignore/add/remove/force # Add or remove space before '[]'. sp_before_squares = ignore # ignore/add/remove/force # Add or remove space before C++17 structured bindings # after byref. sp_cpp_before_struct_binding_after_byref = ignore # ignore/add/remove/force # Add or remove space before C++17 structured bindings. sp_cpp_before_struct_binding = ignore # ignore/add/remove/force # Add or remove space inside a non-empty '[' and ']'. sp_inside_square = ignore # ignore/add/remove/force # Add or remove space inside '[]'. # if empty. sp_inside_square_empty = ignore # ignore/add/remove/force # (OC) Add or remove space inside a non-empty Objective-C boxed array '@[' and # ']'. If set to ignore, sp_inside_square is used. sp_inside_square_oc_array = ignore # ignore/add/remove/force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'. sp_after_comma = add # ignore/add/remove/force # Add or remove space before ',', i.e. 'a,b' vs. 'a ,b'. # # Default: remove sp_before_comma = remove # ignore/add/remove/force # (C#, Vala) Add or remove space between ',' and ']' in multidimensional array type # like 'int[,,]'. sp_after_mdatype_commas = ignore # ignore/add/remove/force # (C#, Vala) Add or remove space between '[' and ',' in multidimensional array type # like 'int[,,]'. sp_before_mdatype_commas = ignore # ignore/add/remove/force # (C#, Vala) Add or remove space between ',' in multidimensional array type # like 'int[,,]'. sp_between_mdatype_commas = ignore # ignore/add/remove/force # Add or remove space between an open parenthesis and comma, # i.e. '(,' vs. '( ,'. # # Default: force sp_paren_comma = force # ignore/add/remove/force # Add or remove space between a type and ':'. sp_type_colon = ignore # ignore/add/remove/force # Add or remove space after the variadic '...' when preceded by a # non-punctuator. # The value REMOVE will be overridden with FORCE sp_after_ellipsis = ignore # ignore/add/remove/force # Add or remove space before the variadic '...' when preceded by a # non-punctuator. # The value REMOVE will be overridden with FORCE sp_before_ellipsis = ignore # ignore/add/remove/force # Add or remove space between a type and '...'. sp_type_ellipsis = ignore # ignore/add/remove/force # Add or remove space between a '*' and '...'. sp_ptr_type_ellipsis = ignore # ignore/add/remove/force # Add or remove space between ')' and '...'. sp_paren_ellipsis = ignore # ignore/add/remove/force # Add or remove space between '&&' and '...'. sp_byref_ellipsis = ignore # ignore/add/remove/force # Add or remove space between ')' and a qualifier such as 'const'. sp_paren_qualifier = ignore # ignore/add/remove/force # Add or remove space between ')' and 'noexcept'. sp_paren_noexcept = ignore # ignore/add/remove/force # Add or remove space after class ':'. sp_after_class_colon = ignore # ignore/add/remove/force # Add or remove space before class ':'. sp_before_class_colon = ignore # ignore/add/remove/force # Add or remove space after class constructor ':'. # # Default: add sp_after_constr_colon = add # ignore/add/remove/force # Add or remove space before class constructor ':'. # # Default: add sp_before_constr_colon = add # ignore/add/remove/force # Add or remove space before case ':'. # # Default: remove sp_before_case_colon = remove # ignore/add/remove/force # Add or remove space between 'operator' and operator sign. sp_after_operator = ignore # ignore/add/remove/force # Add or remove space between the operator symbol and the open parenthesis, as # in 'operator ++('. sp_after_operator_sym = ignore # ignore/add/remove/force # Overrides sp_after_operator_sym when the operator has no arguments, as in # 'operator *()'. sp_after_operator_sym_empty = ignore # ignore/add/remove/force # Add or remove space after C/D cast, i.e. 'cast(int)a' vs. 'cast(int) a' or # '(int)a' vs. '(int) a'. sp_after_cast = remove # ignore/add/remove/force # Add or remove spaces inside cast parentheses. sp_inside_paren_cast = ignore # ignore/add/remove/force # Add or remove space between the type and open parenthesis in a C++ cast, # i.e. 'int(exp)' vs. 'int (exp)'. sp_cpp_cast_paren = ignore # ignore/add/remove/force # Add or remove space between 'sizeof' and '('. sp_sizeof_paren = ignore # ignore/add/remove/force # Add or remove space between 'sizeof' and '...'. sp_sizeof_ellipsis = ignore # ignore/add/remove/force # Add or remove space between 'sizeof...' and '('. sp_sizeof_ellipsis_paren = ignore # ignore/add/remove/force # Add or remove space between '...' and a parameter pack. sp_ellipsis_parameter_pack = ignore # ignore/add/remove/force # Add or remove space between a parameter pack and '...'. sp_parameter_pack_ellipsis = ignore # ignore/add/remove/force # Add or remove space between 'decltype' and '('. sp_decltype_paren = ignore # ignore/add/remove/force # (Pawn) Add or remove space after the tag keyword. sp_after_tag = ignore # ignore/add/remove/force # Add or remove space inside enum '{' and '}'. sp_inside_braces_enum = ignore # ignore/add/remove/force # Add or remove space inside struct/union '{' and '}'. sp_inside_braces_struct = ignore # ignore/add/remove/force # (OC) Add or remove space inside Objective-C boxed dictionary '{' and '}' sp_inside_braces_oc_dict = ignore # ignore/add/remove/force # Add or remove space after open brace in an unnamed temporary # direct-list-initialization # if statement is a brace_init_lst # works only if sp_brace_brace is set to ignore. sp_after_type_brace_init_lst_open = ignore # ignore/add/remove/force # Add or remove space before close brace in an unnamed temporary # direct-list-initialization # if statement is a brace_init_lst # works only if sp_brace_brace is set to ignore. sp_before_type_brace_init_lst_close = ignore # ignore/add/remove/force # Add or remove space inside an unnamed temporary direct-list-initialization # if statement is a brace_init_lst # works only if sp_brace_brace is set to ignore # works only if sp_before_type_brace_init_lst_close is set to ignore. sp_inside_type_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space inside '{' and '}'. sp_inside_braces = ignore # ignore/add/remove/force # Add or remove space inside '{}'. # if empty. sp_inside_braces_empty = remove # ignore/add/remove/force # Add or remove space around trailing return operator '->'. sp_trailing_return = ignore # ignore/add/remove/force # Add or remove space between return type and function name. A minimum of 1 # is forced except for pointer return types. sp_type_func = ignore # ignore/add/remove/force # Add or remove space between type and open brace of an unnamed temporary # direct-list-initialization. sp_type_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function declaration. sp_func_proto_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function declaration # if empty. sp_func_proto_paren_empty = remove # ignore/add/remove/force # Add or remove space between function name and '(' with a typedef specifier. sp_func_type_paren = ignore # ignore/add/remove/force # Add or remove space between alias name and '(' of a non-pointer function type typedef. sp_func_def_paren = ignore # ignore/add/remove/force # Add or remove space between function name and '()' on function definition # if empty. sp_func_def_paren_empty = ignore # ignore/add/remove/force # Add or remove space inside empty function '()'. # Overrides sp_after_angle unless use_sp_after_angle_always is set to true. sp_inside_fparens = remove # ignore/add/remove/force # Add or remove space inside function '(' and ')'. sp_inside_fparen = remove # ignore/add/remove/force # Add or remove space inside user functor '(' and ')'. sp_func_call_user_inside_rparen = ignore # ignore/add/remove/force # Add or remove space inside empty functor '()'. # Overrides sp_after_angle unless use_sp_after_angle_always is set to true. sp_inside_rparens = ignore # ignore/add/remove/force # Add or remove space inside functor '(' and ')'. sp_inside_rparen = ignore # ignore/add/remove/force # Add or remove space inside the first parentheses in a function type, as in # 'void (*x)(...)'. sp_inside_tparen = ignore # ignore/add/remove/force # Add or remove space between the ')' and '(' in a function type, as in # 'void (*x)(...)'. sp_after_tparen_close = ignore # ignore/add/remove/force # Add or remove space between ']' and '(' when part of a function call. sp_square_fparen = ignore # ignore/add/remove/force # Add or remove space between ')' and '{' of function. sp_fparen_brace = remove # ignore/add/remove/force # Add or remove space between ')' and '{' of a function call in object # initialization. # # Overrides sp_fparen_brace. sp_fparen_brace_initializer = remove # ignore/add/remove/force # (Java) Add or remove space between ')' and '{{' of double brace initializer. sp_fparen_dbrace = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function calls. sp_func_call_paren = remove # ignore/add/remove/force # Add or remove space between function name and '()' on function calls without # parameters. If set to ignore (the default), sp_func_call_paren is used. sp_func_call_paren_empty = ignore # ignore/add/remove/force # Add or remove space between the user function name and '(' on function # calls. You need to set a keyword to be a user function in the config file, # like: # set func_call_user tr _ i18n sp_func_call_user_paren = ignore # ignore/add/remove/force # Add or remove space inside user function '(' and ')'. sp_func_call_user_inside_fparen = ignore # ignore/add/remove/force # Add or remove space between nested parentheses with user functions, # i.e. '((' vs. '( ('. sp_func_call_user_paren_paren = ignore # ignore/add/remove/force # Add or remove space between a constructor/destructor and the open # parenthesis. sp_func_class_paren = ignore # ignore/add/remove/force # Add or remove space between a constructor without parameters or destructor # and '()'. sp_func_class_paren_empty = ignore # ignore/add/remove/force # Add or remove space after 'return'. # # Default: force sp_return = force # ignore/add/remove/force # Add or remove space between 'return' and '('. sp_return_paren = ignore # ignore/add/remove/force # Add or remove space between 'return' and '{'. sp_return_brace = ignore # ignore/add/remove/force # Add or remove space between '__attribute__' and '('. sp_attribute_paren = ignore # ignore/add/remove/force # Add or remove space between 'defined' and '(' in '#if defined (FOO)'. sp_defined_paren = ignore # ignore/add/remove/force # Add or remove space between 'throw' and '(' in 'throw (something)'. sp_throw_paren = ignore # ignore/add/remove/force # Add or remove space between 'throw' and anything other than '(' as in # '@throw [...];'. sp_after_throw = ignore # ignore/add/remove/force # Add or remove space between 'catch' and '(' in 'catch (something) { }'. # If set to ignore, sp_before_sparen is used. sp_catch_paren = ignore # ignore/add/remove/force # (OC) Add or remove space between '@catch' and '(' # in '@catch (something) { }'. If set to ignore, sp_catch_paren is used. sp_oc_catch_paren = ignore # ignore/add/remove/force # (OC) Add or remove space before Objective-C protocol list # as in '@protocol Protocol' or '@interface MyClass : NSObject'. sp_before_oc_proto_list = ignore # ignore/add/remove/force # (OC) Add or remove space between class name and '(' # in '@interface className(categoryName):BaseClass' sp_oc_classname_paren = ignore # ignore/add/remove/force # (D) Add or remove space between 'version' and '(' # in 'version (something) { }'. If set to ignore, sp_before_sparen is used. sp_version_paren = ignore # ignore/add/remove/force # (D) Add or remove space between 'scope' and '(' # in 'scope (something) { }'. If set to ignore, sp_before_sparen is used. sp_scope_paren = ignore # ignore/add/remove/force # Add or remove space between 'super' and '(' in 'super (something)'. # # Default: remove sp_super_paren = remove # ignore/add/remove/force # Add or remove space between 'this' and '(' in 'this (something)'. # # Default: remove sp_this_paren = remove # ignore/add/remove/force # Add or remove space between a macro name and its definition. sp_macro = ignore # ignore/add/remove/force # Add or remove space between a macro function ')' and its definition. sp_macro_func = ignore # ignore/add/remove/force # Add or remove space between 'else' and '{' if on the same line. sp_else_brace = remove # ignore/add/remove/force # Add or remove space between '}' and 'else' if on the same line. sp_brace_else = remove # ignore/add/remove/force # Add or remove space between '}' and the name of a typedef on the same line. sp_brace_typedef = ignore # ignore/add/remove/force # Add or remove space before the '{' of a 'catch' statement, if the '{' and # 'catch' are on the same line, as in 'catch (decl) {'. sp_catch_brace = ignore # ignore/add/remove/force # (OC) Add or remove space before the '{' of a '@catch' statement, if the '{' # and '@catch' are on the same line, as in '@catch (decl) {'. # If set to ignore, sp_catch_brace is used. sp_oc_catch_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'catch' if on the same line. sp_brace_catch = ignore # ignore/add/remove/force # (OC) Add or remove space between '}' and '@catch' if on the same line. # If set to ignore, sp_brace_catch is used. sp_oc_brace_catch = ignore # ignore/add/remove/force # Add or remove space between 'finally' and '{' if on the same line. sp_finally_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'finally' if on the same line. sp_brace_finally = ignore # ignore/add/remove/force # Add or remove space between 'try' and '{' if on the same line. sp_try_brace = ignore # ignore/add/remove/force # Add or remove space between get/set and '{' if on the same line. sp_getset_brace = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for C++ uniform # initialization. sp_word_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for a namespace. # # Default: add sp_word_brace_ns = remove # ignore/add/remove/force # Add or remove space before the '::' operator. sp_before_dc = ignore # ignore/add/remove/force # Add or remove space after the '::' operator. sp_after_dc = ignore # ignore/add/remove/force # (D) Add or remove around the D named array initializer ':' operator. sp_d_array_colon = ignore # ignore/add/remove/force # Add or remove space after the '!' (not) unary operator. # # Default: remove sp_not = remove # ignore/add/remove/force # Add or remove space between two '!' (not) unary operators. # If set to ignore, sp_not will be used. sp_not_not = ignore # ignore/add/remove/force # Add or remove space after the '~' (invert) unary operator. # # Default: remove sp_inv = remove # ignore/add/remove/force # Add or remove space after the '&' (address-of) unary operator. This does not # affect the spacing after a '&' that is part of a type. # # Default: remove sp_addr = remove # ignore/add/remove/force # Add or remove space around the '.' or '->' operators. # also the c-sharp null-conditional operator '?.' # # Default: remove sp_member = remove # ignore/add/remove/force # Add or remove space after the '*' (dereference) unary operator. This does # not affect the spacing after a '*' that is part of a type. # # Default: remove sp_deref = remove # ignore/add/remove/force # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. # # Default: remove sp_sign = remove # ignore/add/remove/force # Add or remove space between '++' and '--' the word to which it is being # applied, as in '(--x)' or 'y++;'. # # Default: remove sp_incdec = remove # ignore/add/remove/force # Add or remove space before a backslash-newline at the end of a line. # # Default: add sp_before_nl_cont = add # ignore/add/remove/force # (OC) Add or remove space after the scope '+' or '-', as in '-(void) foo;' # or '+(int) bar;'. sp_after_oc_scope = ignore # ignore/add/remove/force # (OC) Add or remove space after the colon in message specs, # i.e. '-(int) f:(int) x;' vs. '-(int) f: (int) x;'. sp_after_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space before the colon in message specs, # i.e. '-(int) f: (int) x;' vs. '-(int) f : (int) x;'. sp_before_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space after the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};'. sp_after_oc_dict_colon = ignore # ignore/add/remove/force # (OC) Add or remove space before the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};'. sp_before_oc_dict_colon = ignore # ignore/add/remove/force # (OC) Add or remove space after the colon in message specs, # i.e. '[object setValue:1];' vs. '[object setValue: 1];'. sp_after_send_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space before the colon in message specs, # i.e. '[object setValue:1];' vs. '[object setValue :1];'. sp_before_send_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space after the (type) in message specs, # i.e. '-(int)f: (int) x;' vs. '-(int)f: (int)x;'. sp_after_oc_type = ignore # ignore/add/remove/force # (OC) Add or remove space after the first (type) in message specs, # i.e. '-(int) f:(int)x;' vs. '-(int)f:(int)x;'. sp_after_oc_return_type = ignore # ignore/add/remove/force # (OC) Add or remove space between '@selector' and '(', # i.e. '@selector(msgName)' vs. '@selector (msgName)'. # Also applies to '@protocol()' constructs. sp_after_oc_at_sel = ignore # ignore/add/remove/force # (OC) Add or remove space between '@selector(x)' and the following word, # i.e. '@selector(foo) a:' vs. '@selector(foo)a:'. sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force # (OC) Add or remove space inside '@selector' parentheses, # i.e. '@selector(foo)' vs. '@selector( foo )'. # Also applies to '@protocol()' constructs. sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force # (OC) Add or remove space before a block pointer caret, # i.e. '^int (int arg){...}' vs. ' ^int (int arg){...}'. sp_before_oc_block_caret = ignore # ignore/add/remove/force # (OC) Add or remove space after a block pointer caret, # i.e. '^int (int arg){...}' vs. '^ int (int arg){...}'. sp_after_oc_block_caret = ignore # ignore/add/remove/force # (OC) Add or remove space between the receiver and selector in a message, # as in '[receiver selector ...]'. sp_after_oc_msg_receiver = ignore # ignore/add/remove/force # (OC) Add or remove space after '@property'. sp_after_oc_property = ignore # ignore/add/remove/force # (OC) Add or remove space between '@synchronized' and the open parenthesis, # i.e. '@synchronized(foo)' vs. '@synchronized (foo)'. sp_after_oc_synchronized = ignore # ignore/add/remove/force # Add or remove space around the ':' in 'b ? t : f'. sp_cond_colon = ignore # ignore/add/remove/force # Add or remove space before the ':' in 'b ? t : f'. # # Overrides sp_cond_colon. sp_cond_colon_before = ignore # ignore/add/remove/force # Add or remove space after the ':' in 'b ? t : f'. # # Overrides sp_cond_colon. sp_cond_colon_after = ignore # ignore/add/remove/force # Add or remove space around the '?' in 'b ? t : f'. sp_cond_question = ignore # ignore/add/remove/force # Add or remove space before the '?' in 'b ? t : f'. # # Overrides sp_cond_question. sp_cond_question_before = ignore # ignore/add/remove/force # Add or remove space after the '?' in 'b ? t : f'. # # Overrides sp_cond_question. sp_cond_question_after = ignore # ignore/add/remove/force # In the abbreviated ternary form '(a ?: b)', add or remove space between '?' # and ':'. # # Overrides all other sp_cond_* options. sp_cond_ternary_short = ignore # ignore/add/remove/force # Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make # sense here. sp_case_label = ignore # ignore/add/remove/force # (D) Add or remove space around the D '..' operator. sp_range = ignore # ignore/add/remove/force # Add or remove space after ':' in a Java/C++11 range-based 'for', # as in 'for (Type var : expr)'. sp_after_for_colon = ignore # ignore/add/remove/force # Add or remove space before ':' in a Java/C++11 range-based 'for', # as in 'for (Type var : expr)'. sp_before_for_colon = ignore # ignore/add/remove/force # (D) Add or remove space between 'extern' and '(' as in 'extern (C)'. sp_extern_paren = ignore # ignore/add/remove/force # Add or remove space after the opening of a C++ comment, as in '// A'. sp_cmt_cpp_start = ignore # ignore/add/remove/force # remove space after the '//' and the pvs command '-V1234', # only works with sp_cmt_cpp_start set to add or force. sp_cmt_cpp_pvs = false # true/false # remove space after the '//' and the command 'lint', # only works with sp_cmt_cpp_start set to add or force. sp_cmt_cpp_lint = false # true/false # Add or remove space in a C++ region marker comment, as in '// BEGIN'. # A region marker is defined as a comment which is not preceded by other text # (i.e. the comment is the first non-whitespace on the line), and which starts # with either 'BEGIN' or 'END'. # # Overrides sp_cmt_cpp_start. sp_cmt_cpp_region = ignore # ignore/add/remove/force # If true, space added with sp_cmt_cpp_start will be added after Doxygen # sequences like '///', '///<', '//!' and '//!<'. sp_cmt_cpp_doxygen = false # true/false # If true, space added with sp_cmt_cpp_start will be added after Qt translator # or meta-data comments like '//:', '//=', and '//~'. sp_cmt_cpp_qttr = false # true/false # Add or remove space between #else or #endif and a trailing comment. sp_endif_cmt = ignore # ignore/add/remove/force # Add or remove space after 'new', 'delete' and 'delete[]'. sp_after_new = ignore # ignore/add/remove/force # Add or remove space between 'new' and '(' in 'new()'. sp_between_new_paren = ignore # ignore/add/remove/force # Add or remove space between ')' and type in 'new(foo) BAR'. sp_after_newop_paren = ignore # ignore/add/remove/force # Add or remove space inside parentheses of the new operator # as in 'new(foo) BAR'. sp_inside_newop_paren = ignore # ignore/add/remove/force # Add or remove space after the open parenthesis of the new operator, # as in 'new(foo) BAR'. # # Overrides sp_inside_newop_paren. sp_inside_newop_paren_open = ignore # ignore/add/remove/force # Add or remove space before the close parenthesis of the new operator, # as in 'new(foo) BAR'. # # Overrides sp_inside_newop_paren. sp_inside_newop_paren_close = ignore # ignore/add/remove/force # Add or remove space before a trailing comment. sp_before_tr_cmt = ignore # ignore/add/remove/force # Number of spaces before a trailing comment. sp_num_before_tr_cmt = 0 # unsigned number # Add or remove space before an embedded comment. # # Default: force sp_before_emb_cmt = force # ignore/add/remove/force # Number of spaces before an embedded comment. # # Default: 1 sp_num_before_emb_cmt = 1 # unsigned number # Add or remove space after an embedded comment. # # Default: force sp_after_emb_cmt = force # ignore/add/remove/force # Number of spaces after an embedded comment. # # Default: 1 sp_num_after_emb_cmt = 1 # unsigned number # Embedded comment spacing options have higher priority (== override) # than other spacing options (comma, parenthesis, braces, ...) sp_emb_cmt_priority = false # true/false # (Java) Add or remove space between an annotation and the open parenthesis. sp_annotation_paren = ignore # ignore/add/remove/force # If true, vbrace tokens are dropped to the previous token and skipped. sp_skip_vbrace_tokens = false # true/false # Add or remove space after 'noexcept'. sp_after_noexcept = ignore # ignore/add/remove/force # Add or remove space after '_'. sp_vala_after_translation = ignore # ignore/add/remove/force # Add or remove space before a bit colon ':'. sp_before_bit_colon = ignore # ignore/add/remove/force # Add or remove space after a bit colon ':'. sp_after_bit_colon = ignore # ignore/add/remove/force # If true, a is inserted after #define. force_tab_after_define = false # true/false # Add or remove space between two strings. sp_string_string = ignore # ignore/add/remove/force # Add or remove space 'struct' and a type. sp_struct_type = ignore # ignore/add/remove/force # # Indenting options # # The number of columns to indent per level. Usually 2, 3, 4, or 8. # # Default: 8 indent_columns = 4 # unsigned number # Whether to ignore indent for the first continuation line. Subsequent # continuation lines will still be indented to match the first. indent_ignore_first_continue = false # true/false # The continuation indent. If non-zero, this overrides the indent of '(', '[' # and '=' continuation indents. Negative values are OK; negative value is # absolute and not increased for each '(' or '[' level. # # For FreeBSD, this is set to 4. # Requires indent_ignore_first_continue=false. indent_continue = 8 # number # The continuation indent, only for class header line(s). If non-zero, this # overrides the indent of 'class' continuation indents. # Requires indent_ignore_first_continue=false. indent_continue_class_head = 0 # unsigned number # Whether to indent empty lines (i.e. lines which contain only spaces before # the newline character). indent_single_newlines = false # true/false # The continuation indent for func_*_param if they are true. If non-zero, this # overrides the indent. indent_param = 0 # unsigned number # How to use tabs when indenting code. # # 0: Spaces only # 1: Indent with tabs to brace level, align with spaces (default) # 2: Indent and align with tabs, using spaces when not on a tabstop # # Default: 1 indent_with_tabs = 2 # unsigned number # Whether to indent comments that are not at a brace level with tabs on a # tabstop. Requires indent_with_tabs=2. If false, will use spaces. indent_cmt_with_tabs = false # true/false # Whether to indent strings broken by '\' so that they line up. indent_align_string = false # true/false # The number of spaces to indent multi-line XML strings. # Requires indent_align_string=true. indent_xml_string = 0 # unsigned number # Spaces to indent '{' from level. indent_brace = 0 # unsigned number # Whether braces are indented to the body level. indent_braces = false # true/false # Whether to disable indenting function braces if indent_braces=true. indent_braces_no_func = false # true/false # Whether to disable indenting class braces if indent_braces=true. indent_braces_no_class = false # true/false # Whether to disable indenting struct braces if indent_braces=true. indent_braces_no_struct = false # true/false # Whether to indent based on the size of the brace parent, # i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. indent_brace_parent = false # true/false # Whether to indent based on the open parenthesis instead of the open brace # in '({\n'. indent_paren_open_brace = false # true/false # (C#) Whether to indent the brace of a C# delegate by another level. indent_cs_delegate_brace = false # true/false # (C#) Whether to indent a C# delegate (to handle delegates with no brace) by # another level. indent_cs_delegate_body = false # true/false # Whether to indent the body of a 'namespace'. indent_namespace = false # true/false # Whether to indent only the first namespace, and not any nested namespaces. # Requires indent_namespace=true. indent_namespace_single_indent = false # true/false # The number of spaces to indent a namespace block. # If set to zero, use the value indent_columns indent_namespace_level = 0 # unsigned number # If the body of the namespace is longer than this number, it won't be # indented. Requires indent_namespace=true. 0 means no limit. indent_namespace_limit = 0 # unsigned number # Whether to indent only in inner namespaces (nested in other namespaces). # Requires indent_namespace=true. indent_namespace_inner_only = false # true/false # Whether the 'extern "C"' body is indented. indent_extern = false # true/false # Whether the 'class' body is indented. indent_class = true # true/false # Whether to ignore indent for the leading base class colon. indent_ignore_before_class_colon = false # true/false # Additional indent before the leading base class colon. # Negative values decrease indent down to the first column. # Requires indent_ignore_before_class_colon=false and a newline break before # the colon (see pos_class_colon and nl_class_colon) indent_before_class_colon = 0 # number # Whether to indent the stuff after a leading base class colon. indent_class_colon = false # true/false # Whether to indent based on a class colon instead of the stuff after the # colon. Requires indent_class_colon=true. indent_class_on_colon = false # true/false # Whether to ignore indent for a leading class initializer colon. indent_ignore_before_constr_colon = false # true/false # Whether to indent the stuff after a leading class initializer colon. indent_constr_colon = false # true/false # Virtual indent from the ':' for leading member initializers. # # Default: 2 indent_ctor_init_leading = 2 # unsigned number # Virtual indent from the ':' for following member initializers. # # Default: 2 indent_ctor_init_following = 2 # unsigned number # Additional indent for constructor initializer list. # Negative values decrease indent down to the first column. indent_ctor_init = 0 # number # Whether to indent 'if' following 'else' as a new block under the 'else'. # If false, 'else\nif' is treated as 'else if' for indenting purposes. indent_else_if = false # true/false # Amount to indent variable declarations after a open brace. # # <0: Relative # >=0: Absolute indent_var_def_blk = 0 # number # Whether to indent continued variable declarations instead of aligning. indent_var_def_cont = true # true/false # How to indent continued shift expressions ('<<' and '>>'). # Set align_left_shift=false when using this. # 0: Align shift operators instead of indenting them (default) # 1: Indent by one level # -1: Preserve original indentation indent_shift = 0 # number # Whether to force indentation of function definitions to start in column 1. indent_func_def_force_col1 = false # true/false # Whether to indent continued function call parameters one indent level, # rather than aligning parameters under the open parenthesis. indent_func_call_param = true # true/false # Whether to indent continued function definition parameters one indent level, # rather than aligning parameters under the open parenthesis. indent_func_def_param = true # true/false # for function definitions, only if indent_func_def_param is false # Allows to align params when appropriate and indent them when not # behave as if it was true if paren position is more than this value # if paren position is more than the option value indent_func_def_param_paren_pos_threshold = 0 # unsigned number # Whether to indent continued function call prototype one indent level, # rather than aligning parameters under the open parenthesis. indent_func_proto_param = true # true/false # Whether to indent continued function call declaration one indent level, # rather than aligning parameters under the open parenthesis. indent_func_class_param = true # true/false # Whether to indent continued class variable constructors one indent level, # rather than aligning parameters under the open parenthesis. indent_func_ctor_var_param = true # true/false # Whether to indent continued template parameter list one indent level, # rather than aligning parameters under the open parenthesis. indent_template_param = true # true/false # Double the indent for indent_func_xxx_param options. # Use both values of the options indent_columns and indent_param. indent_func_param_double = true # true/false # Indentation column for standalone 'const' qualifier on a function # prototype. indent_func_const = 0 # unsigned number # Indentation column for standalone 'throw' qualifier on a function # prototype. indent_func_throw = 0 # unsigned number # How to indent within a macro followed by a brace on the same line # This allows reducing the indent in macros that have (for example) # `do { ... } while (0)` blocks bracketing them. # # true: add an indent for the brace on the same line as the macro # false: do not add an indent for the brace on the same line as the macro # # Default: true indent_macro_brace = true # true/false # The number of spaces to indent a continued '->' or '.'. # Usually set to 0, 1, or indent_columns. indent_member = indent_columns # unsigned number # Whether lines broken at '.' or '->' should be indented by a single indent. # The indent_member option will not be effective if this is set to true. indent_member_single = false # true/false # Spaces to indent single line ('//') comments on lines before code. indent_single_line_comments_before = 0 # unsigned number # Spaces to indent single line ('//') comments on lines after code. indent_single_line_comments_after = 0 # unsigned number # When opening a paren for a control statement (if, for, while, etc), increase # the indent level by this value. Negative values decrease the indent level. indent_sparen_extra = 0 # number # Whether to indent trailing single line ('//') comments relative to the code # instead of trying to keep the same absolute column. indent_relative_single_line_comments = false # true/false # Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. # It might be wise to choose the same value for the option indent_case_brace. indent_switch_case = indent_columns # unsigned number # Spaces to indent the body of a 'switch' before any 'case'. # Usually the same as indent_columns or indent_switch_case. indent_switch_body = 0 # unsigned number # Whether to ignore indent for '{' following 'case'. indent_ignore_case_brace = false # true/false # Spaces to indent '{' from 'case'. By default, the brace will appear under # the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. # It might be wise to choose the same value for the option indent_switch_case. indent_case_brace = indent_columns # number # indent 'break' with 'case' from 'switch'. indent_switch_break_with_case = false # true/false # Whether to indent preprocessor statements inside of switch statements. # # Default: true indent_switch_pp = true # true/false # Spaces to shift the 'case' line, without affecting any other lines. # Usually 0. indent_case_shift = 0 # unsigned number # Whether to align comments before 'case' with the 'case'. # # Default: true indent_case_comment = true # true/false # Whether to indent comments not found in first column. # # Default: true indent_comment = true # true/false # Whether to indent comments found in first column. indent_col1_comment = false # true/false # Whether to indent multi string literal in first column. indent_col1_multi_string_literal = false # true/false # Align comments on adjacent lines that are this many columns apart or less. # # Default: 3 indent_comment_align_thresh = 3 # unsigned number # Whether to ignore indent for goto labels. indent_ignore_label = false # true/false # How to indent goto labels. Requires indent_ignore_label=false. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent # # Default: 1 indent_label = 1 # number # How to indent access specifiers that are followed by a # colon. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent # # Default: 1 indent_access_spec = 1 # number # Whether to indent the code after an access specifier by one level. # If true, this option forces 'indent_access_spec=0'. indent_access_spec_body = false # true/false # If an open parenthesis is followed by a newline, whether to indent the next # line so that it lines up after the open parenthesis (not recommended). indent_paren_nl = false # true/false # How to indent a close parenthesis after a newline. # # 0: Indent to body level (default) # 1: Align under the open parenthesis # 2: Indent to the brace level # -1: Preserve original indentation indent_paren_close = 0 # number # Whether to indent the open parenthesis of a function definition, # if the parenthesis is on its own line. indent_paren_after_func_def = false # true/false # Whether to indent the open parenthesis of a function declaration, # if the parenthesis is on its own line. indent_paren_after_func_decl = false # true/false # Whether to indent the open parenthesis of a function call, # if the parenthesis is on its own line. indent_paren_after_func_call = false # true/false # How to indent a comma when inside braces. # 0: Indent by one level (default) # 1: Align under the open brace # -1: Preserve original indentation indent_comma_brace = 0 # number # How to indent a comma when inside parentheses. # 0: Indent by one level (default) # 1: Align under the open parenthesis # -1: Preserve original indentation indent_comma_paren = 0 # number # How to indent a Boolean operator when inside parentheses. # 0: Indent by one level (default) # 1: Align under the open parenthesis # -1: Preserve original indentation indent_bool_paren = 0 # number # Whether to ignore the indentation of a Boolean operator when outside # parentheses. indent_ignore_bool = false # true/false # Whether to indent lines that are nested in boolean expression one more level for each nesting indent_bool_nested_all = false # true/false # Whether to ignore the indentation of an arithmetic operator. indent_ignore_arith = false # true/false # Whether to indent a semicolon when inside a for parenthesis. # If true, aligns under the open for parenthesis. indent_semicolon_for_paren = false # true/false # Whether to ignore the indentation of a semicolon outside of a 'for' # statement. indent_ignore_semicolon = false # true/false # Whether to align the first expression to following ones # if indent_bool_paren=1. indent_first_bool_expr = false # true/false # Whether to align the first expression to following ones # if indent_semicolon_for_paren=true. indent_first_for_expr = false # true/false # If an open square is followed by a newline, whether to indent the next line # so that it lines up after the open square (not recommended). indent_square_nl = false # true/false # (ESQL/C) Whether to preserve the relative indent of 'EXEC SQL' bodies. indent_preserve_sql = false # true/false # Whether to ignore the indentation of an assignment operator. indent_ignore_assign = false # true/false # Whether to align continued statements at the '='. If false or if the '=' is # followed by a newline, the next line is indent one tab. # # Default: true indent_align_assign = true # true/false # If true, the indentation of the chunks after a '=' sequence will be set at # LHS token indentation column before '='. indent_off_after_assign = false # true/false # Whether to align continued statements at the '('. If false or the '(' is # followed by a newline, the next line indent is one tab. # # Default: true indent_align_paren = true # true/false # (OC) Whether to indent Objective-C code inside message selectors. indent_oc_inside_msg_sel = false # true/false # (OC) Whether to indent Objective-C blocks at brace level instead of usual # rules. indent_oc_block = false # true/false # (OC) Indent for Objective-C blocks in a message relative to the parameter # name. # # =0: Use indent_oc_block rules # >0: Use specified number of spaces to indent indent_oc_block_msg = 0 # unsigned number # (OC) Minimum indent for subsequent parameters indent_oc_msg_colon = 0 # unsigned number # (OC) Whether to prioritize aligning with initial colon (and stripping spaces # from lines, if necessary). # # Default: true indent_oc_msg_prioritize_first_colon = true # true/false # (OC) Whether to indent blocks the way that Xcode does by default # (from the keyword if the parameter is on its own line; otherwise, from the # previous indentation level). Requires indent_oc_block_msg=true. indent_oc_block_msg_xcode_style = false # true/false # (OC) Whether to indent blocks from where the brace is, relative to a # message keyword. Requires indent_oc_block_msg=true. indent_oc_block_msg_from_keyword = false # true/false # (OC) Whether to indent blocks from where the brace is, relative to a message # colon. Requires indent_oc_block_msg=true. indent_oc_block_msg_from_colon = false # true/false # (OC) Whether to indent blocks from where the block caret is. # Requires indent_oc_block_msg=true. indent_oc_block_msg_from_caret = false # true/false # (OC) Whether to indent blocks from where the brace caret is. # Requires indent_oc_block_msg=true. indent_oc_block_msg_from_brace = false # true/false # When indenting after virtual brace open and newline add further spaces to # reach this minimum indent. indent_min_vbrace_open = 0 # unsigned number # Whether to add further spaces after regular indent to reach next tabstop # when indenting after virtual brace open and newline. indent_vbrace_open_on_tabstop = false # true/false # How to indent after a brace followed by another token (not a newline). # true: indent all contained lines to match the token # false: indent all contained lines to match the brace # # Default: true indent_token_after_brace = true # true/false # Whether to indent the body of a C++11 lambda. indent_cpp_lambda_body = false # true/false # How to indent compound literals that are being returned. # true: add both the indent from return & the compound literal open brace # (i.e. 2 indent levels) # false: only indent 1 level, don't add the indent for the open brace, only # add the indent for the return. # # Default: true indent_compound_literal_return = true # true/false # (C#) Whether to indent a 'using' block if no braces are used. # # Default: true indent_using_block = true # true/false # How to indent the continuation of ternary operator. # # 0: Off (default) # 1: When the `if_false` is a continuation, indent it under the `if_true` branch # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number # Whether to indent the statements inside ternary operator. indent_inside_ternary_operator = false # true/false # If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. indent_off_after_return = false # true/false # If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. indent_off_after_return_new = false # true/false # If true, the tokens after return are indented with regular single indentation. By default (false) the indentation is after the return token. indent_single_after_return = false # true/false # Whether to ignore indent and alignment for 'asm' blocks (i.e. assume they # have their own indentation). indent_ignore_asm_block = false # true/false # Don't indent the close parenthesis of a function definition, # if the parenthesis is on its own line. donot_indent_func_def_close_paren = false # true/false # # Newline adding and removing options # # Whether to collapse empty blocks between '{' and '}' except for functions. # Use nl_collapse_empty_body_functions to specify how empty function braces # should be formatted. nl_collapse_empty_body = false # true/false # Whether to collapse empty blocks between '{' and '}' for functions only. # If true, overrides nl_inside_empty_func. nl_collapse_empty_body_functions = false # true/false # Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'. nl_assign_leave_one_liners = true # true/false # Don't split one-line braced statements inside a 'class xx { }' body. nl_class_leave_one_liners = true # true/false # Don't split one-line enums, as in 'enum foo { BAR = 15 };' nl_enum_leave_one_liners = true # true/false # Don't split one-line get or set functions. nl_getset_leave_one_liners = true # true/false # (C#) Don't split one-line property get or set functions. nl_cs_property_leave_one_liners = false # true/false # Don't split one-line function definitions, as in 'int foo() { return 0; }'. # might modify nl_func_type_name nl_func_leave_one_liners = false # true/false # Don't split one-line C++11 lambdas, as in '[]() { return 0; }'. nl_cpp_lambda_leave_one_liners = false # true/false # Don't split one-line if/else statements, as in 'if(...) b++;'. nl_if_leave_one_liners = false # true/false # Don't split one-line while statements, as in 'while(...) b++;'. nl_while_leave_one_liners = false # true/false # Don't split one-line do statements, as in 'do { b++; } while(...);'. nl_do_leave_one_liners = false # true/false # Don't split one-line for statements, as in 'for(...) b++;'. nl_for_leave_one_liners = false # true/false # (OC) Don't split one-line Objective-C messages. nl_oc_msg_leave_one_liner = false # true/false # (OC) Add or remove newline between method declaration and '{'. nl_oc_mdef_brace = ignore # ignore/add/remove/force # (OC) Add or remove newline between Objective-C block signature and '{'. nl_oc_block_brace = ignore # ignore/add/remove/force # (OC) Add or remove blank line before '@interface' statement. nl_oc_before_interface = ignore # ignore/add/remove/force # (OC) Add or remove blank line before '@implementation' statement. nl_oc_before_implementation = ignore # ignore/add/remove/force # (OC) Add or remove blank line before '@end' statement. nl_oc_before_end = ignore # ignore/add/remove/force # (OC) Add or remove newline between '@interface' and '{'. nl_oc_interface_brace = ignore # ignore/add/remove/force # (OC) Add or remove newline between '@implementation' and '{'. nl_oc_implementation_brace = ignore # ignore/add/remove/force # Add or remove newlines at the start of the file. nl_start_of_file = ignore # ignore/add/remove/force # The minimum number of newlines at the start of the file (only used if # nl_start_of_file is 'add' or 'force'). nl_start_of_file_min = 0 # unsigned number # Add or remove newline at the end of the file. nl_end_of_file = ignore # ignore/add/remove/force # The minimum number of newlines at the end of the file (only used if # nl_end_of_file is 'add' or 'force'). nl_end_of_file_min = 0 # unsigned number # Add or remove newline between '=' and '{'. nl_assign_brace = remove # ignore/add/remove/force # (D) Add or remove newline between '=' and '['. nl_assign_square = ignore # ignore/add/remove/force # Add or remove newline between '[]' and '{'. nl_tsquare_brace = ignore # ignore/add/remove/force # (D) Add or remove newline after '= ['. Will also affect the newline before # the ']'. nl_after_square_assign = ignore # ignore/add/remove/force # Add or remove newline between a function call's ')' and '{', as in # 'list_for_each(item, &list) { }'. nl_fcall_brace = add # ignore/add/remove/force # Add or remove newline between 'enum' and '{'. nl_enum_brace = remove # ignore/add/remove/force # Add or remove newline between 'enum' and 'class'. nl_enum_class = ignore # ignore/add/remove/force # Add or remove newline between 'enum class' and the identifier. nl_enum_class_identifier = ignore # ignore/add/remove/force # Add or remove newline between 'enum class' type and ':'. nl_enum_identifier_colon = ignore # ignore/add/remove/force # Add or remove newline between 'enum class identifier :' and type. nl_enum_colon_type = ignore # ignore/add/remove/force # Add or remove newline between 'struct and '{'. nl_struct_brace = remove # ignore/add/remove/force # Add or remove newline between 'union' and '{'. nl_union_brace = remove # ignore/add/remove/force # Add or remove newline between 'if' and '{'. nl_if_brace = remove # ignore/add/remove/force # Add or remove newline between '}' and 'else'. nl_brace_else = remove # ignore/add/remove/force # Add or remove newline between 'else if' and '{'. If set to ignore, # nl_if_brace is used instead. nl_elseif_brace = remove # ignore/add/remove/force # Add or remove newline between 'else' and '{'. nl_else_brace = remove # ignore/add/remove/force # Add or remove newline between 'else' and 'if'. nl_else_if = remove # ignore/add/remove/force # Add or remove newline before '{' opening brace nl_before_opening_brace_func_class_def = ignore # ignore/add/remove/force # Add or remove newline before 'if'/'else if' closing parenthesis. nl_before_if_closing_paren = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'finally'. nl_brace_finally = ignore # ignore/add/remove/force # Add or remove newline between 'finally' and '{'. nl_finally_brace = ignore # ignore/add/remove/force # Add or remove newline between 'try' and '{'. nl_try_brace = ignore # ignore/add/remove/force # Add or remove newline between get/set and '{'. nl_getset_brace = ignore # ignore/add/remove/force # Add or remove newline between 'for' and '{'. nl_for_brace = remove # ignore/add/remove/force # Add or remove newline before the '{' of a 'catch' statement, as in # 'catch (decl) {'. nl_catch_brace = ignore # ignore/add/remove/force # (OC) Add or remove newline before the '{' of a '@catch' statement, as in # '@catch (decl) {'. If set to ignore, nl_catch_brace is used. nl_oc_catch_brace = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'catch'. nl_brace_catch = ignore # ignore/add/remove/force # (OC) Add or remove newline between '}' and '@catch'. If set to ignore, # nl_brace_catch is used. nl_oc_brace_catch = ignore # ignore/add/remove/force # Add or remove newline between '}' and ']'. nl_brace_square = ignore # ignore/add/remove/force # Add or remove newline between '}' and ')' in a function invocation. nl_brace_fparen = ignore # ignore/add/remove/force # Add or remove newline between 'while' and '{'. nl_while_brace = remove # ignore/add/remove/force # (D) Add or remove newline between 'scope (x)' and '{'. nl_scope_brace = ignore # ignore/add/remove/force # (D) Add or remove newline between 'unittest' and '{'. nl_unittest_brace = ignore # ignore/add/remove/force # (D) Add or remove newline between 'version (x)' and '{'. nl_version_brace = ignore # ignore/add/remove/force # (C#) Add or remove newline between 'using' and '{'. nl_using_brace = ignore # ignore/add/remove/force # Add or remove newline between two open or close braces. Due to general # newline/brace handling, REMOVE may not work. nl_brace_brace = ignore # ignore/add/remove/force # Add or remove newline between 'do' and '{'. nl_do_brace = remove # ignore/add/remove/force # Add or remove newline between '}' and 'while' of 'do' statement. nl_brace_while = remove # ignore/add/remove/force # Add or remove newline between 'switch' and '{'. nl_switch_brace = remove # ignore/add/remove/force # Add or remove newline between 'synchronized' and '{'. nl_synchronized_brace = remove # ignore/add/remove/force # Add a newline between ')' and '{' if the ')' is on a different line than the # if/for/etc. # # Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and # nl_catch_brace. nl_multi_line_cond = false # true/false # Add a newline after '(' if an if/for/while/switch condition spans multiple # lines nl_multi_line_sparen_open = ignore # ignore/add/remove/force # Add a newline before ')' if an if/for/while/switch condition spans multiple # lines. Overrides nl_before_if_closing_paren if both are specified. nl_multi_line_sparen_close = ignore # ignore/add/remove/force # Force a newline in a define after the macro name for multi-line defines. nl_multi_line_define = false # true/false # Whether to add a newline before 'case', and a blank line before a 'case' # statement that follows a ';' or '}'. nl_before_case = false # true/false # Whether to add a newline after a 'case' statement. nl_after_case = false # true/false # Add or remove newline between a case ':' and '{'. # # Overrides nl_after_case. nl_case_colon_brace = ignore # ignore/add/remove/force # Add or remove newline between ')' and 'throw'. nl_before_throw = ignore # ignore/add/remove/force # Add or remove newline between 'namespace' and '{'. nl_namespace_brace = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template class. nl_template_class = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template class declaration. # # Overrides nl_template_class. nl_template_class_decl = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized class declaration. # # Overrides nl_template_class_decl. nl_template_class_decl_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template class definition. # # Overrides nl_template_class. nl_template_class_def = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized class definition. # # Overrides nl_template_class_def. nl_template_class_def_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template function. nl_template_func = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template function # declaration. # # Overrides nl_template_func. nl_template_func_decl = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized function # declaration. # # Overrides nl_template_func_decl. nl_template_func_decl_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template function # definition. # # Overrides nl_template_func. nl_template_func_def = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized function # definition. # # Overrides nl_template_func_def. nl_template_func_def_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template variable. nl_template_var = ignore # ignore/add/remove/force # Add or remove newline between 'template<...>' and 'using' of a templated # type alias. nl_template_using = ignore # ignore/add/remove/force # Add or remove newline between 'class' and '{'. nl_class_brace = ignore # ignore/add/remove/force # Add or remove newline before or after (depending on pos_class_comma, # may not be IGNORE) each',' in the base class list. nl_class_init_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in the constructor member # initialization. Related to nl_constr_colon, pos_constr_colon and # pos_constr_comma. nl_constr_init_args = ignore # ignore/add/remove/force # Add or remove newline before first element, after comma, and after last # element, in 'enum'. nl_enum_own_lines = ignore # ignore/add/remove/force # Add or remove newline between return type and function name in a function # definition. # might be modified by nl_func_leave_one_liners nl_func_type_name = ignore # ignore/add/remove/force # Add or remove newline between return type and function name inside a class # definition. If set to ignore, nl_func_type_name or nl_func_proto_type_name # is used instead. nl_func_type_name_class = ignore # ignore/add/remove/force # Add or remove newline between class specification and '::' # in 'void A::f() { }'. Only appears in separate member implementation (does # not appear with in-line implementation). nl_func_class_scope = ignore # ignore/add/remove/force # Add or remove newline between function scope and name, as in # 'void A :: f() { }'. nl_func_scope_name = ignore # ignore/add/remove/force # Add or remove newline between return type and function name in a prototype. nl_func_proto_type_name = ignore # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the # declaration. nl_func_paren = remove # ignore/add/remove/force # Overrides nl_func_paren for functions with no parameters. nl_func_paren_empty = remove # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the # definition. nl_func_def_paren = remove # ignore/add/remove/force # Overrides nl_func_def_paren for functions with no parameters. nl_func_def_paren_empty = remove # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the # call. nl_func_call_paren = remove # ignore/add/remove/force # Overrides nl_func_call_paren for functions with no parameters. nl_func_call_paren_empty = remove # ignore/add/remove/force # Add or remove newline after '(' in a function declaration. nl_func_decl_start = ignore # ignore/add/remove/force # Add or remove newline after '(' in a function definition. nl_func_def_start = ignore # ignore/add/remove/force # Overrides nl_func_decl_start when there is only one parameter. nl_func_decl_start_single = ignore # ignore/add/remove/force # Overrides nl_func_def_start when there is only one parameter. nl_func_def_start_single = ignore # ignore/add/remove/force # Whether to add a newline after '(' in a function declaration if '(' and ')' # are in different lines. If false, nl_func_decl_start is used instead. nl_func_decl_start_multi_line = false # true/false # Whether to add a newline after '(' in a function definition if '(' and ')' # are in different lines. If false, nl_func_def_start is used instead. nl_func_def_start_multi_line = false # true/false # Add or remove newline after each ',' in a function declaration. nl_func_decl_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function definition. nl_func_def_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function call. nl_func_call_args = ignore # ignore/add/remove/force # Whether to add a newline after each ',' in a function declaration if '(' # and ')' are in different lines. If false, nl_func_decl_args is used instead. nl_func_decl_args_multi_line = false # true/false # Whether to add a newline after each ',' in a function definition if '(' # and ')' are in different lines. If false, nl_func_def_args is used instead. nl_func_def_args_multi_line = false # true/false # Add or remove newline before the ')' in a function declaration. nl_func_decl_end = ignore # ignore/add/remove/force # Add or remove newline before the ')' in a function definition. nl_func_def_end = ignore # ignore/add/remove/force # Overrides nl_func_decl_end when there is only one parameter. nl_func_decl_end_single = ignore # ignore/add/remove/force # Overrides nl_func_def_end when there is only one parameter. nl_func_def_end_single = ignore # ignore/add/remove/force # Whether to add a newline before ')' in a function declaration if '(' and ')' # are in different lines. If false, nl_func_decl_end is used instead. nl_func_decl_end_multi_line = false # true/false # Whether to add a newline before ')' in a function definition if '(' and ')' # are in different lines. If false, nl_func_def_end is used instead. nl_func_def_end_multi_line = false # true/false # Add or remove newline between '()' in a function declaration. nl_func_decl_empty = ignore # ignore/add/remove/force # Add or remove newline between '()' in a function definition. nl_func_def_empty = ignore # ignore/add/remove/force # Add or remove newline between '()' in a function call. nl_func_call_empty = ignore # ignore/add/remove/force # Whether to add a newline after '(' in a function call, # has preference over nl_func_call_start_multi_line. nl_func_call_start = ignore # ignore/add/remove/force # Whether to add a newline before ')' in a function call. nl_func_call_end = ignore # ignore/add/remove/force # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. nl_func_call_start_multi_line = false # true/false # Whether to add a newline after each ',' in a function call if '(' and ')' # are in different lines. nl_func_call_args_multi_line = false # true/false # Whether to add a newline before ')' in a function call if '(' and ')' are in # different lines. nl_func_call_end_multi_line = false # true/false # Whether to respect nl_func_call_XXX option in case of closure args. nl_func_call_args_multi_line_ignore_closures = false # true/false # Whether to add a newline after '<' of a template parameter list. nl_template_start = false # true/false # Whether to add a newline after each ',' in a template parameter list. nl_template_args = false # true/false # Whether to add a newline before '>' of a template parameter list. nl_template_end = false # true/false # (OC) Whether to put each Objective-C message parameter on a separate line. # See nl_oc_msg_leave_one_liner. nl_oc_msg_args = false # true/false # (OC) Minimum number of Objective-C message parameters before applying nl_oc_msg_args. nl_oc_msg_args_min_params = 0 # unsigned number # (OC) Max code width of Objective-C message before applying nl_oc_msg_args. nl_oc_msg_args_max_code_width = 0 # unsigned number # (OC) Whether to apply nl_oc_msg_args if some of the parameters are already # on new lines. Overrides nl_oc_msg_args_min_params and nl_oc_msg_args_max_code_width. nl_oc_msg_args_finish_multi_line = false # true/false # Add or remove newline between function signature and '{'. nl_fdef_brace = add # ignore/add/remove/force # Add or remove newline between function signature and '{', # if signature ends with ')'. Overrides nl_fdef_brace. nl_fdef_brace_cond = ignore # ignore/add/remove/force # Add or remove newline between C++11 lambda signature and '{'. nl_cpp_ldef_brace = ignore # ignore/add/remove/force # Add or remove newline between 'return' and the return expression. nl_return_expr = ignore # ignore/add/remove/force # Add or remove newline between 'throw' and the throw expression. nl_throw_expr = ignore # ignore/add/remove/force # Whether to add a newline after semicolons, except in 'for' statements. nl_after_semicolon = false # true/false # (Java) Add or remove newline between the ')' and '{{' of the double brace # initializer. nl_paren_dbrace_open = ignore # ignore/add/remove/force # Whether to add a newline after the type in an unnamed temporary # direct-list-initialization, better: # before a direct-list-initialization. nl_type_brace_init_lst = ignore # ignore/add/remove/force # Whether to add a newline after the open brace in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst_open = ignore # ignore/add/remove/force # Whether to add a newline before the close brace in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst_close = ignore # ignore/add/remove/force # Whether to add a newline before '{'. nl_before_brace_open = false # true/false # Whether to add a newline after '{'. nl_after_brace_open = false # true/false # Whether to add a newline between the open brace and a trailing single-line # comment. Requires nl_after_brace_open=true. nl_after_brace_open_cmt = false # true/false # Whether to add a newline after a virtual brace open with a non-empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open = false # true/false # Whether to add a newline after a virtual brace open with an empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open_empty = false # true/false # Whether to add a newline after '}'. Does not apply if followed by a # necessary ';'. nl_after_brace_close = false # true/false # Whether to add a newline after a virtual brace close, # as in 'if (foo) a++; return;'. nl_after_vbrace_close = false # true/false # Add or remove newline between the close brace and identifier, # as in 'struct { int a; } b;'. Affects enumerations, unions and # structures. If set to ignore, uses nl_after_brace_close. nl_brace_struct_var = ignore # ignore/add/remove/force # Whether to add a newline before/after each '&&' or `||` on the same # nesting level in a boolean expression if boolean expression will not # fit on a line. code_width needs to be positive and pos_bool needs # to be 'lead' or 'trail' for this option to have any effect. nl_bool_expr_hierarchical = false # true/false # Whether to alter newlines in '#define' macros. nl_define_macro = false # true/false # Whether to alter newlines between consecutive parenthesis closes. The number # of closing parentheses in a line will depend on respective open parenthesis # lines. nl_squeeze_paren_close = false # true/false # Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and # '#endif'. Does not affect top-level #ifdefs. nl_squeeze_ifdef = false # true/false # Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well. nl_squeeze_ifdef_top_level = false # true/false # Add or remove blank line before 'if'. nl_before_if = ignore # ignore/add/remove/force # Add or remove blank line after 'if' statement. Add/Force work only if the # next token is not a closing brace. nl_after_if = ignore # ignore/add/remove/force # Add or remove blank line before 'for'. nl_before_for = ignore # ignore/add/remove/force # Add or remove blank line after 'for' statement. nl_after_for = ignore # ignore/add/remove/force # Add or remove blank line before 'while'. nl_before_while = ignore # ignore/add/remove/force # Add or remove blank line after 'while' statement. nl_after_while = ignore # ignore/add/remove/force # Add or remove blank line before 'switch'. nl_before_switch = ignore # ignore/add/remove/force # Add or remove blank line after 'switch' statement. nl_after_switch = ignore # ignore/add/remove/force # Add or remove blank line before 'synchronized'. nl_before_synchronized = ignore # ignore/add/remove/force # Add or remove blank line after 'synchronized' statement. nl_after_synchronized = ignore # ignore/add/remove/force # Add or remove blank line before 'do'. nl_before_do = ignore # ignore/add/remove/force # Add or remove blank line after 'do/while' statement. nl_after_do = ignore # ignore/add/remove/force # Ignore nl_before_{if,for,switch,do,synchronized} if the control # statement is immediately after a case statement. # if nl_before_{if,for,switch,do} is set to remove, this option # does nothing. nl_before_ignore_after_case = false # true/false # Whether to put a blank line before 'return' statements, unless after an open # brace. nl_before_return = false # true/false # Whether to put a blank line after 'return' statements, unless followed by a # close brace. nl_after_return = false # true/false # Whether to put a blank line before a member '.' or '->' operators. nl_before_member = ignore # ignore/add/remove/force # (Java) Whether to put a blank line after a member '.' or '->' operators. nl_after_member = ignore # ignore/add/remove/force # Whether to double-space commented-entries in 'struct'/'union'/'enum'. nl_ds_struct_enum_cmt = false # true/false # Whether to force a newline before '}' of a 'struct'/'union'/'enum'. # (Lower priority than eat_blanks_before_close_brace.) nl_ds_struct_enum_close_brace = false # true/false # Add or remove newline before or after (depending on pos_class_colon) a class # colon, as in 'class Foo : public Bar'. nl_class_colon = ignore # ignore/add/remove/force # Add or remove newline around a class constructor colon. The exact position # depends on nl_constr_init_args, pos_constr_colon and pos_constr_comma. nl_constr_colon = ignore # ignore/add/remove/force # Whether to collapse a two-line namespace, like 'namespace foo\n{ decl; }' # into a single line. If true, prevents other brace newline rules from turning # such code into four lines. If true, it also preserves one-liner namespaces. nl_namespace_two_to_one_liner = false # true/false # Whether to remove a newline in simple unbraced if statements, turning them # into one-liners, as in 'if(b)\n i++;' => 'if(b) i++;'. nl_create_if_one_liner = false # true/false # Whether to remove a newline in simple unbraced for statements, turning them # into one-liners, as in 'for (...)\n stmt;' => 'for (...) stmt;'. nl_create_for_one_liner = false # true/false # Whether to remove a newline in simple unbraced while statements, turning # them into one-liners, as in 'while (expr)\n stmt;' => 'while (expr) stmt;'. nl_create_while_one_liner = false # true/false # Whether to collapse a function definition whose body (not counting braces) # is only one line so that the entire definition (prototype, braces, body) is # a single line. nl_create_func_def_one_liner = false # true/false # Whether to split one-line simple list definitions into three lines by # adding newlines, as in 'int a[12] = { 0 };'. nl_create_list_one_liner = false # true/false # Whether to split one-line simple unbraced if statements into two lines by # adding a newline, as in 'if(b) i++;'. nl_split_if_one_liner = true # true/false # Whether to split one-line simple unbraced for statements into two lines by # adding a newline, as in 'for (...) stmt;'. nl_split_for_one_liner = true # true/false # Whether to split one-line simple unbraced while statements into two lines by # adding a newline, as in 'while (expr) stmt;'. nl_split_while_one_liner = true # true/false # Don't add a newline before a cpp-comment in a parameter list of a function # call. donot_add_nl_before_cpp_comment = false # true/false # # Blank line options # # The maximum number of consecutive newlines (3 = 2 blank lines). nl_max = 0 # unsigned number # The maximum number of consecutive newlines in a function. nl_max_blank_in_func = 0 # unsigned number # The number of newlines inside an empty function body. # This option overrides eat_blanks_after_open_brace and # eat_blanks_before_close_brace, but is ignored when # nl_collapse_empty_body_functions=true nl_inside_empty_func = 0 # unsigned number # The number of newlines before a function prototype. nl_before_func_body_proto = 0 # unsigned number # The number of newlines before a multi-line function definition. Where # applicable, this option is overridden with eat_blanks_after_open_brace=true nl_before_func_body_def = 3 # unsigned number # The number of newlines before a class constructor/destructor prototype. nl_before_func_class_proto = 0 # unsigned number # The number of newlines before a class constructor/destructor definition. nl_before_func_class_def = 0 # unsigned number # The number of newlines after a function prototype. nl_after_func_proto = 0 # unsigned number # The number of newlines after a function prototype, if not followed by # another function prototype. nl_after_func_proto_group = 0 # unsigned number # The number of newlines after a class constructor/destructor prototype. nl_after_func_class_proto = 0 # unsigned number # The number of newlines after a class constructor/destructor prototype, # if not followed by another constructor/destructor prototype. nl_after_func_class_proto_group = 0 # unsigned number # Whether one-line method definitions inside a class body should be treated # as if they were prototypes for the purposes of adding newlines. # # Requires nl_class_leave_one_liners=true. Overrides nl_before_func_body_def # and nl_before_func_class_def for one-liners. nl_class_leave_one_liner_groups = false # true/false # The number of newlines after '}' of a multi-line function body. # # Overrides nl_min_after_func_body and nl_max_after_func_body. nl_after_func_body = 0 # unsigned number # The minimum number of newlines after '}' of a multi-line function body. # # Only works when nl_after_func_body is 0. nl_min_after_func_body = 0 # unsigned number # The maximum number of newlines after '}' of a multi-line function body. # # Only works when nl_after_func_body is 0. # Takes precedence over nl_min_after_func_body. nl_max_after_func_body = 0 # unsigned number # The number of newlines after '}' of a multi-line function body in a class # declaration. Also affects class constructors/destructors. # # Overrides nl_after_func_body. nl_after_func_body_class = 0 # unsigned number # The number of newlines after '}' of a single line function body. Also # affects class constructors/destructors. # # Overrides nl_after_func_body and nl_after_func_body_class. nl_after_func_body_one_liner = 0 # unsigned number # The number of newlines before a block of typedefs. If nl_after_access_spec # is non-zero, that option takes precedence. # # 0: No change (default). nl_typedef_blk_start = 0 # unsigned number # The number of newlines after a block of typedefs. # # 0: No change (default). nl_typedef_blk_end = 0 # unsigned number # The maximum number of consecutive newlines within a block of typedefs. # # 0: No change (default). nl_typedef_blk_in = 0 # unsigned number # The minimum number of blank lines after a block of variable definitions # at the top of a function body. If any preprocessor directives appear # between the opening brace of the function and the variable block, then # it is considered as not at the top of the function.Newlines are added # before trailing preprocessor directives, if any exist. # # 0: No change (default). nl_var_def_blk_end_func_top = 0 # unsigned number # The minimum number of empty newlines before a block of variable definitions # not at the top of a function body. If nl_after_access_spec is non-zero, # that option takes precedence. Newlines are not added at the top of the # file or just after an opening brace. Newlines are added above any # preprocessor directives before the block. # # 0: No change (default). nl_var_def_blk_start = 0 # unsigned number # The minimum number of empty newlines after a block of variable definitions # not at the top of a function body. Newlines are not added if the block # is at the bottom of the file or just before a preprocessor directive. # # 0: No change (default). nl_var_def_blk_end = 0 # unsigned number # The maximum number of consecutive newlines within a block of variable # definitions. # # 0: No change (default). nl_var_def_blk_in = 0 # unsigned number # The minimum number of newlines before a multi-line comment. # Doesn't apply if after a brace open or another multi-line comment. nl_before_block_comment = 0 # unsigned number # The minimum number of newlines before a single-line C comment. # Doesn't apply if after a brace open or other single-line C comments. nl_before_c_comment = 0 # unsigned number # The minimum number of newlines before a CPP comment. # Doesn't apply if after a brace open or other CPP comments. nl_before_cpp_comment = 0 # unsigned number # Whether to force a newline after a multi-line comment. nl_after_multiline_comment = false # true/false # Whether to force a newline after a label's colon. nl_after_label_colon = false # true/false # The number of newlines before a struct definition. nl_before_struct = 0 # unsigned number # The number of newlines after '}' or ';' of a struct/enum/union definition. nl_after_struct = 0 # unsigned number # The number of newlines before a class definition. nl_before_class = 0 # unsigned number # The number of newlines after '}' or ';' of a class definition. nl_after_class = 0 # unsigned number # The number of newlines before a namespace. nl_before_namespace = 0 # unsigned number # The number of newlines after '{' of a namespace. This also adds newlines # before the matching '}'. # # 0: Apply eat_blanks_after_open_brace or eat_blanks_before_close_brace if # applicable, otherwise no change. # # Overrides eat_blanks_after_open_brace and eat_blanks_before_close_brace. nl_inside_namespace = 0 # unsigned number # The number of newlines after '}' of a namespace. nl_after_namespace = 0 # unsigned number # The number of newlines before an access specifier label. This also includes # the Qt-specific 'signals:' and 'slots:'. Will not change the newline count # if after a brace open. # # 0: No change (default). nl_before_access_spec = 0 # unsigned number # The number of newlines after an access specifier label. This also includes # the Qt-specific 'signals:' and 'slots:'. Will not change the newline count # if after a brace open. # # 0: No change (default). # # Overrides nl_typedef_blk_start and nl_var_def_blk_start. nl_after_access_spec = 0 # unsigned number # The number of newlines between a function definition and the function # comment, as in '// comment\n void foo() {...}'. # # 0: No change (default). nl_comment_func_def = 0 # unsigned number # The number of newlines after a try-catch-finally block that isn't followed # by a brace close. # # 0: No change (default). nl_after_try_catch_finally = 0 # unsigned number # (C#) The number of newlines before and after a property, indexer or event # declaration. # # 0: No change (default). nl_around_cs_property = 0 # unsigned number # (C#) The number of newlines between the get/set/add/remove handlers. # # 0: No change (default). nl_between_get_set = 0 # unsigned number # (C#) Add or remove newline between property and the '{'. nl_property_brace = ignore # ignore/add/remove/force # Whether to remove blank lines after '{'. eat_blanks_after_open_brace = false # true/false # Whether to remove blank lines before '}'. eat_blanks_before_close_brace = false # true/false # How aggressively to remove extra newlines not in preprocessor. # # 0: No change (default) # 1: Remove most newlines not handled by other config # 2: Remove all newlines and reformat completely by config nl_remove_extra_newlines = 0 # unsigned number # (Java) Add or remove newline after an annotation statement. Only affects # annotations that are after a newline. nl_after_annotation = ignore # ignore/add/remove/force # (Java) Add or remove newline between two annotations. nl_between_annotation = ignore # ignore/add/remove/force # The number of newlines before a whole-file #ifdef. # # 0: No change (default). nl_before_whole_file_ifdef = 0 # unsigned number # The number of newlines after a whole-file #ifdef. # # 0: No change (default). nl_after_whole_file_ifdef = 0 # unsigned number # The number of newlines before a whole-file #endif. # # 0: No change (default). nl_before_whole_file_endif = 0 # unsigned number # The number of newlines after a whole-file #endif. # # 0: No change (default). nl_after_whole_file_endif = 0 # unsigned number # # Positioning options # # The position of arithmetic operators in wrapped expressions. pos_arith = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of assignment in wrapped expressions. Do not affect '=' # followed by '{'. pos_assign = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of Boolean operators in wrapped expressions. pos_bool = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of comparison operators in wrapped expressions. pos_compare = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of conditional operators, as in the '?' and ':' of # 'expr ? stmt : stmt', in wrapped expressions. pos_conditional = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in wrapped expressions. pos_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in enum entries. pos_enum_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in the base class list if there is more than one # line. Affects nl_class_init_args. pos_class_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in the constructor initialization list. # Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon. pos_constr_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of trailing/leading class colon, between class and base class # list. Affects nl_class_colon. pos_class_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of colons between constructor and member initialization. # Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma. pos_constr_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of shift operators in wrapped expressions. pos_shift = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # # Line splitting options # # Try to limit code width to N columns. code_width = 0 # unsigned number # Whether to fully split long 'for' statements at semi-colons. ls_for_split_full = false # true/false # Whether to fully split long function prototypes/calls at commas. # The option ls_code_width has priority over the option ls_func_split_full. ls_func_split_full = false # true/false # Whether to split lines as close to code_width as possible and ignore some # groupings. # The option ls_code_width has priority over the option ls_func_split_full. ls_code_width = false # true/false # # Code alignment options (not left column spaces/tabs) # # Whether to keep non-indenting tabs. align_keep_tabs = false # true/false # Whether to use tabs for aligning. align_with_tabs = false # true/false # Whether to bump out to the next tab when aligning. align_on_tabstop = false # true/false # Whether to right-align numbers. align_number_right = false # true/false # Whether to keep whitespace not required for alignment. align_keep_extra_space = false # true/false # Whether to align variable definitions in prototypes and functions. align_func_params = false # true/false # The span for aligning parameter definitions in function on parameter name. # # 0: Don't align (default). align_func_params_span = 0 # unsigned number # The threshold for aligning function parameter definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_func_params_thresh = 0 # number # The gap for aligning function parameter definitions. align_func_params_gap = 0 # unsigned number # The span for aligning constructor value. # # 0: Don't align (default). align_constr_value_span = 0 # unsigned number # The threshold for aligning constructor value. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_constr_value_thresh = 0 # number # The gap for aligning constructor value. align_constr_value_gap = 0 # unsigned number # Whether to align parameters in single-line functions that have the same # name. The function names must already be aligned with each other. align_same_func_call_params = false # true/false # The span for aligning function-call parameters for single line functions. # # 0: Don't align (default). align_same_func_call_params_span = 0 # unsigned number # The threshold for aligning function-call parameters for single line # functions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_same_func_call_params_thresh = 0 # number # The span for aligning variable definitions. # # 0: Don't align (default). align_var_def_span = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of variable definitions. # # 0: Part of the type 'void * foo;' (default) # 1: Part of the variable 'void *foo;' # 2: Dangling 'void *foo;' # Dangling: the '*' will not be taken into account when aligning. align_var_def_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of variable definitions. # # 0: Part of the type 'long & foo;' (default) # 1: Part of the variable 'long &foo;' # 2: Dangling 'long &foo;' # Dangling: the '&' will not be taken into account when aligning. align_var_def_amp_style = 0 # unsigned number # The threshold for aligning variable definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_def_thresh = 0 # number # The gap for aligning variable definitions. align_var_def_gap = 0 # unsigned number # Whether to align the colon in struct bit fields. align_var_def_colon = false # true/false # The gap for aligning the colon in struct bit fields. align_var_def_colon_gap = 0 # unsigned number # Whether to align any attribute after the variable name. align_var_def_attribute = false # true/false # Whether to align inline struct/enum/union variable definitions. align_var_def_inline = false # true/false # The span for aligning on '=' in assignments. # # 0: Don't align (default). align_assign_span = 0 # unsigned number # The span for aligning on '=' in function prototype modifier. # # 0: Don't align (default). align_assign_func_proto_span = 0 # unsigned number # The threshold for aligning on '=' in assignments. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_assign_thresh = 0 # number # Whether to align on the left most assignment when multiple # definitions are found on the same line. # Depends on 'align_assign_span' and 'align_assign_thresh' settings. align_assign_on_multi_var_defs = false # true/false # The span for aligning on '{' in braced init list. # # 0: Don't align (default). align_braced_init_list_span = 0 # unsigned number # The threshold for aligning on '{' in braced init list. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_braced_init_list_thresh = 0 # number # How to apply align_assign_span to function declaration "assignments", i.e. # 'virtual void foo() = 0' or '~foo() = {default|delete}'. # # 0: Align with other assignments (default) # 1: Align with each other, ignoring regular assignments # 2: Don't align align_assign_decl_func = 0 # unsigned number # The span for aligning on '=' in enums. # # 0: Don't align (default). align_enum_equ_span = 0 # unsigned number # The threshold for aligning on '=' in enums. # Use a negative number for absolute thresholds. # # 0: no limit (default). align_enum_equ_thresh = 0 # number # The span for aligning class member definitions. # # 0: Don't align (default). align_var_class_span = 0 # unsigned number # The threshold for aligning class member definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_class_thresh = 0 # number # The gap for aligning class member definitions. align_var_class_gap = 0 # unsigned number # The span for aligning struct/union member definitions. # # 0: Don't align (default). align_var_struct_span = 0 # unsigned number # The threshold for aligning struct/union member definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_struct_thresh = 0 # number # The gap for aligning struct/union member definitions. align_var_struct_gap = 0 # unsigned number # The span for aligning struct initializer values. # # 0: Don't align (default). align_struct_init_span = 0 # unsigned number # The span for aligning single-line typedefs. # # 0: Don't align (default). align_typedef_span = 0 # unsigned number # The minimum space between the type and the synonym of a typedef. align_typedef_gap = 0 # unsigned number # How to align typedef'd functions with other typedefs. # # 0: Don't mix them at all (default) # 1: Align the open parenthesis with the types # 2: Align the function type name with the other type names align_typedef_func = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of typedefs. # # 0: Part of the typedef type, 'typedef int * pint;' (default) # 1: Part of type name: 'typedef int *pint;' # 2: Dangling: 'typedef int *pint;' # Dangling: the '*' will not be taken into account when aligning. align_typedef_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of typedefs. # # 0: Part of the typedef type, 'typedef int & intref;' (default) # 1: Part of type name: 'typedef int &intref;' # 2: Dangling: 'typedef int &intref;' # Dangling: the '&' will not be taken into account when aligning. align_typedef_amp_style = 0 # unsigned number # The span for aligning comments that end lines. # # 0: Don't align (default). align_right_cmt_span = 0 # unsigned number # Minimum number of columns between preceding text and a trailing comment in # order for the comment to qualify for being aligned. Must be non-zero to have # an effect. align_right_cmt_gap = 0 # unsigned number # If aligning comments, whether to mix with comments after '}' and #endif with # less than three spaces before the comment. align_right_cmt_mix = false # true/false # Whether to only align trailing comments that are at the same brace level. align_right_cmt_same_level = false # true/false # Minimum column at which to align trailing comments. Comments which are # aligned beyond this column, but which can be aligned in a lesser column, # may be "pulled in". # # 0: Ignore (default). align_right_cmt_at_col = 0 # unsigned number # The span for aligning function prototypes. # # 0: Don't align (default). align_func_proto_span = 0 # unsigned number # Whether to ignore continuation lines when evaluating the number of # new lines for the function prototype alignment's span. # # false: continuation lines are part of the newlines count # true: continuation lines are not counted align_func_proto_span_ignore_cont_lines = false # true/false # How to consider (or treat) the '*' in the alignment of function prototypes. # # 0: Part of the type 'void * foo();' (default) # 1: Part of the function 'void *foo();' # 2: Dangling 'void *foo();' # Dangling: the '*' will not be taken into account when aligning. align_func_proto_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of function prototypes. # # 0: Part of the type 'long & foo();' (default) # 1: Part of the function 'long &foo();' # 2: Dangling 'long &foo();' # Dangling: the '&' will not be taken into account when aligning. align_func_proto_amp_style = 0 # unsigned number # The threshold for aligning function prototypes. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_func_proto_thresh = 0 # number # Minimum gap between the return type and the function name. align_func_proto_gap = 0 # unsigned number # Whether to align function prototypes on the 'operator' keyword instead of # what follows. align_on_operator = false # true/false # Whether to mix aligning prototype and variable declarations. If true, # align_var_def_XXX options are used instead of align_func_proto_XXX options. align_mix_var_proto = false # true/false # Whether to align single-line functions with function prototypes. # Uses align_func_proto_span. align_single_line_func = false # true/false # Whether to align the open brace of single-line functions. # Requires align_single_line_func=true. Uses align_func_proto_span. align_single_line_brace = false # true/false # Gap for align_single_line_brace. align_single_line_brace_gap = 0 # unsigned number # (OC) The span for aligning Objective-C message specifications. # # 0: Don't align (default). align_oc_msg_spec_span = 0 # unsigned number # Whether and how to align backslashes that split a macro onto multiple lines. # This will not work right if the macro contains a multi-line comment. # # 0: Do nothing (default) # 1: Align the backslashes in the column at the end of the longest line # 2: Align with the backslash that is farthest to the left, or, if that # backslash is farther left than the end of the longest line, at the end of # the longest line # 3: Align with the backslash that is farthest to the right align_nl_cont = 0 # unsigned number # The minimum number of spaces between the end of a line and its continuation # backslash. Requires align_nl_cont. # # Default: 1 align_nl_cont_spaces = 1 # unsigned number # Whether to align macro functions and variables together. align_pp_define_together = false # true/false # The span for aligning on '#define' bodies. # # =0: Don't align (default) # >0: Number of lines (including comments) between blocks align_pp_define_span = 0 # unsigned number # The minimum space between label and value of a preprocessor define. align_pp_define_gap = 0 # unsigned number # Whether to align lines that start with '<<' with previous '<<'. # # Default: true align_left_shift = true # true/false # Whether to align comma-separated statements following '<<' (as used to # initialize Eigen matrices). align_eigen_comma_init = false # true/false # Whether to align text after 'asm volatile ()' colons. align_asm_colon = false # true/false # (OC) Span for aligning parameters in an Objective-C message call # on the ':'. # # 0: Don't align. align_oc_msg_colon_span = 0 # unsigned number # (OC) Whether to always align with the first parameter, even if it is too # short. align_oc_msg_colon_first = false # true/false # (OC) Whether to align parameters in an Objective-C '+' or '-' declaration # on the ':'. align_oc_decl_colon = false # true/false # (OC) Whether to not align parameters in an Objectve-C message call if first # colon is not on next line of the message call (the same way Xcode does # alignment) align_oc_msg_colon_xcode_like = false # true/false # # Comment modification options # # Try to wrap comments at N columns. cmt_width = 0 # unsigned number # How to reflow comments. # # 0: No reflowing (apart from the line wrapping due to cmt_width) (default) # 1: No touching at all # 2: Full reflow (enable cmt_indent_multi for indent with line wrapping due to cmt_width) cmt_reflow_mode = 0 # unsigned number # Path to a file that contains regular expressions describing patterns for # which the end of one line and the beginning of the next will be folded into # the same sentence or paragraph during full comment reflow. The regular # expressions are described using ECMAScript syntax. The syntax for this # specification is as follows, where "..." indicates the custom regular # expression and "n" indicates the nth end_of_prev_line_regex and # beg_of_next_line_regex regular expression pair: # # end_of_prev_line_regex[1] = "...$" # beg_of_next_line_regex[1] = "^..." # end_of_prev_line_regex[2] = "...$" # beg_of_next_line_regex[2] = "^..." # . # . # . # end_of_prev_line_regex[n] = "...$" # beg_of_next_line_regex[n] = "^..." # # Note that use of this option overrides the default reflow fold regular # expressions, which are internally defined as follows: # # end_of_prev_line_regex[1] = "[\w,\]\)]$" # beg_of_next_line_regex[1] = "^[\w,\[\(]" # end_of_prev_line_regex[2] = "\.$" # beg_of_next_line_regex[2] = "^[A-Z]" cmt_reflow_fold_regex_file = "" # string # Whether to indent wrapped lines to the start of the encompassing paragraph # during full comment reflow (cmt_reflow_mode = 2). Overrides the value # specified by cmt_sp_after_star_cont. # # Note that cmt_align_doxygen_javadoc_tags overrides this option for # paragraphs associated with javadoc tags cmt_reflow_indent_to_paragraph_start = false # true/false # Whether to convert all tabs to spaces in comments. If false, tabs in # comments are left alone, unless used for indenting. cmt_convert_tab_to_spaces = false # true/false # Whether to apply changes to multi-line comments, including cmt_width, # keyword substitution and leading chars. # # Default: true cmt_indent_multi = false # true/false # Whether to align doxygen javadoc-style tags ('@param', '@return', etc.) # and corresponding fields such that groups of consecutive block tags, # parameter names, and descriptions align with one another. Overrides that # which is specified by the cmt_sp_after_star_cont. If cmt_width > 0, it may # be necessary to enable cmt_indent_multi and set cmt_reflow_mode = 2 # in order to achieve the desired alignment for line-wrapping. cmt_align_doxygen_javadoc_tags = false # true/false # The number of spaces to insert after the star and before doxygen # javadoc-style tags (@param, @return, etc). Requires enabling # cmt_align_doxygen_javadoc_tags. Overrides that which is specified by the # cmt_sp_after_star_cont. # # Default: 1 cmt_sp_before_doxygen_javadoc_tags = 1 # unsigned number # Whether to change trailing, single-line c-comments into cpp-comments. cmt_trailing_single_line_c_to_cpp = false # true/false # Whether to group c-comments that look like they are in a block. cmt_c_group = false # true/false # Whether to put an empty '/*' on the first line of the combined c-comment. cmt_c_nl_start = false # true/false # Whether to add a newline before the closing '*/' of the combined c-comment. cmt_c_nl_end = false # true/false # Whether to change cpp-comments into c-comments. cmt_cpp_to_c = false # true/false # Whether to group cpp-comments that look like they are in a block. Only # meaningful if cmt_cpp_to_c=true. cmt_cpp_group = false # true/false # Whether to put an empty '/*' on the first line of the combined cpp-comment # when converting to a c-comment. # # Requires cmt_cpp_to_c=true and cmt_cpp_group=true. cmt_cpp_nl_start = false # true/false # Whether to add a newline before the closing '*/' of the combined cpp-comment # when converting to a c-comment. # # Requires cmt_cpp_to_c=true and cmt_cpp_group=true. cmt_cpp_nl_end = false # true/false # Whether to put a star on subsequent comment lines. cmt_star_cont = false # true/false # The number of spaces to insert at the start of subsequent comment lines. cmt_sp_before_star_cont = 0 # unsigned number # The number of spaces to insert after the star on subsequent comment lines. cmt_sp_after_star_cont = 0 # unsigned number # For multi-line comments with a '*' lead, remove leading spaces if the first # and last lines of the comment are the same length. # # Default: true cmt_multi_check_last = true # true/false # For multi-line comments with a '*' lead, remove leading spaces if the first # and last lines of the comment are the same length AND if the length is # bigger as the first_len minimum. # # Default: 4 cmt_multi_first_len_minimum = 4 # unsigned number # Path to a file that contains text to insert at the beginning of a file if # the file doesn't start with a C/C++ comment. If the inserted text contains # '$(filename)', that will be replaced with the current file's name. cmt_insert_file_header = "" # string # Path to a file that contains text to insert at the end of a file if the # file doesn't end with a C/C++ comment. If the inserted text contains # '$(filename)', that will be replaced with the current file's name. cmt_insert_file_footer = "" # string # Path to a file that contains text to insert before a function definition if # the function isn't preceded by a C/C++ comment. If the inserted text # contains '$(function)', '$(javaparam)' or '$(fclass)', these will be # replaced with, respectively, the name of the function, the javadoc '@param' # and '@return' stuff, or the name of the class to which the member function # belongs. cmt_insert_func_header = "" # string # Path to a file that contains text to insert before a class if the class # isn't preceded by a C/C++ comment. If the inserted text contains '$(class)', # that will be replaced with the class name. cmt_insert_class_header = "" # string # Path to a file that contains text to insert before an Objective-C message # specification, if the method isn't preceded by a C/C++ comment. If the # inserted text contains '$(message)' or '$(javaparam)', these will be # replaced with, respectively, the name of the function, or the javadoc # '@param' and '@return' stuff. cmt_insert_oc_msg_header = "" # string # Whether a comment should be inserted if a preprocessor is encountered when # stepping backwards from a function name. # # Applies to cmt_insert_oc_msg_header, cmt_insert_func_header and # cmt_insert_class_header. cmt_insert_before_preproc = false # true/false # Whether a comment should be inserted if a function is declared inline to a # class definition. # # Applies to cmt_insert_func_header. # # Default: true cmt_insert_before_inlines = true # true/false # Whether a comment should be inserted if the function is a class constructor # or destructor. # # Applies to cmt_insert_func_header. cmt_insert_before_ctor_dtor = false # true/false # # Code modifying options (non-whitespace) # # Add or remove braces on a single-line 'do' statement. mod_full_brace_do = add # ignore/add/remove/force # Add or remove braces on a single-line 'for' statement. mod_full_brace_for = add # ignore/add/remove/force # (Pawn) Add or remove braces on a single-line function definition. mod_full_brace_function = ignore # ignore/add/remove/force # Add or remove braces on a single-line 'if' statement. Braces will not be # removed if the braced statement contains an 'else'. mod_full_brace_if = add # ignore/add/remove/force # Whether to enforce that all blocks of an 'if'/'else if'/'else' chain either # have, or do not have, braces. Overrides mod_full_brace_if. # # 0: Don't override mod_full_brace_if # 1: Add braces to all blocks if any block needs braces and remove braces if # they can be removed from all blocks # 2: Add braces to all blocks if any block already has braces, regardless of # whether it needs them # 3: Add braces to all blocks if any block needs braces and remove braces if # they can be removed from all blocks, except if all blocks have braces # despite none needing them mod_full_brace_if_chain = 0 # unsigned number # Whether to add braces to all blocks of an 'if'/'else if'/'else' chain. # If true, mod_full_brace_if_chain will only remove braces from an 'if' that # does not have an 'else if' or 'else'. mod_full_brace_if_chain_only = false # true/false # Add or remove braces on single-line 'while' statement. mod_full_brace_while = add # ignore/add/remove/force # Add or remove braces on single-line 'using ()' statement. mod_full_brace_using = ignore # ignore/add/remove/force # Don't remove braces around statements that span N newlines mod_full_brace_nl = 0 # unsigned number # Whether to prevent removal of braces from 'if'/'for'/'while'/etc. blocks # which span multiple lines. # # Affects: # mod_full_brace_for # mod_full_brace_if # mod_full_brace_if_chain # mod_full_brace_if_chain_only # mod_full_brace_while # mod_full_brace_using # # Does not affect: # mod_full_brace_do # mod_full_brace_function mod_full_brace_nl_block_rem_mlcond = false # true/false # Add or remove unnecessary parentheses on 'return' statement. mod_paren_on_return = ignore # ignore/add/remove/force # Add or remove unnecessary parentheses on 'throw' statement. mod_paren_on_throw = ignore # ignore/add/remove/force # (Pawn) Whether to change optional semicolons to real semicolons. mod_pawn_semicolon = false # true/false # Whether to fully parenthesize Boolean expressions in 'while' and 'if' # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'. mod_full_paren_if_bool = false # true/false # Whether to fully parenthesize Boolean expressions after '=' # statement, as in 'x = a && b > c;' => 'x = (a && (b > c));'. mod_full_paren_assign_bool = false # true/false # Whether to fully parenthesize Boolean expressions after '=' # statement, as in 'return a && b > c;' => 'return (a && (b > c));'. mod_full_paren_return_bool = false # true/false # Whether to remove superfluous semicolons. mod_remove_extra_semicolon = false # true/false # Whether to remove duplicate include. mod_remove_duplicate_include = false # true/false # the following options (mod_XX_closebrace_comment) use different comment, # depending of the setting of the next option. # false: Use the c comment (default) # true : Use the cpp comment mod_add_force_c_closebrace_comment = false # true/false # If a function body exceeds the specified number of newlines and doesn't have # a comment after the close brace, a comment will be added. mod_add_long_function_closebrace_comment = 0 # unsigned number # If a namespace body exceeds the specified number of newlines and doesn't # have a comment after the close brace, a comment will be added. mod_add_long_namespace_closebrace_comment = 0 # unsigned number # If a class body exceeds the specified number of newlines and doesn't have a # comment after the close brace, a comment will be added. mod_add_long_class_closebrace_comment = 0 # unsigned number # If a switch body exceeds the specified number of newlines and doesn't have a # comment after the close brace, a comment will be added. mod_add_long_switch_closebrace_comment = 0 # unsigned number # If an #ifdef body exceeds the specified number of newlines and doesn't have # a comment after the #endif, a comment will be added. mod_add_long_ifdef_endif_comment = 0 # unsigned number # If an #ifdef or #else body exceeds the specified number of newlines and # doesn't have a comment after the #else, a comment will be added. mod_add_long_ifdef_else_comment = 0 # unsigned number # Whether to take care of the case by the mod_sort_xx options. mod_sort_case_sensitive = false # true/false # Whether to sort consecutive single-line 'import' statements. mod_sort_import = false # true/false # (C#) Whether to sort consecutive single-line 'using' statements. mod_sort_using = false # true/false # Whether to sort consecutive single-line '#include' statements (C/C++) and # '#import' statements (Objective-C). Be aware that this has the potential to # break your code if your includes/imports have ordering dependencies. mod_sort_include = false # true/false # Whether to prioritize '#include' and '#import' statements that contain # filename without extension when sorting is enabled. mod_sort_incl_import_prioritize_filename = false # true/false # Whether to prioritize '#include' and '#import' statements that does not # contain extensions when sorting is enabled. mod_sort_incl_import_prioritize_extensionless = false # true/false # Whether to prioritize '#include' and '#import' statements that contain # angle over quotes when sorting is enabled. mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false # Whether to ignore file extension in '#include' and '#import' statements # for sorting comparison. mod_sort_incl_import_ignore_extension = false # true/false # Whether to group '#include' and '#import' statements when sorting is enabled. mod_sort_incl_import_grouping_enabled = false # true/false # Whether to move a 'break' that appears after a fully braced 'case' before # the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. mod_move_case_break = false # true/false # Whether to move a 'return' that appears after a fully braced 'case' before # the close brace, as in 'case X: { ... } return;' => 'case X: { ... return; }'. mod_move_case_return = false # true/false # Add or remove braces around a fully braced case statement. Will only remove # braces if there are no variable declarations in the block. mod_case_brace = ignore # ignore/add/remove/force # Whether to remove a void 'return;' that appears as the last statement in a # function. mod_remove_empty_return = false # true/false # Add or remove the comma after the last value of an enumeration. mod_enum_last_comma = add # ignore/add/remove/force # Syntax to use for infinite loops. # # 0: Leave syntax alone (default) # 1: Rewrite as `for(;;)` # 2: Rewrite as `while(true)` # 3: Rewrite as `do`...`while(true);` # 4: Rewrite as `while(1)` # 5: Rewrite as `do`...`while(1);` # # Infinite loops that do not already match one of these syntaxes are ignored. # Other options that affect loop formatting will be applied after transforming # the syntax. mod_infinite_loop = 0 # unsigned number # Add or remove the 'int' keyword in 'int short'. mod_int_short = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'short int'. mod_short_int = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'int long'. mod_int_long = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'long int'. mod_long_int = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'int signed'. mod_int_signed = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'signed int'. mod_signed_int = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'int unsigned'. mod_int_unsigned = ignore # ignore/add/remove/force # Add or remove the 'int' keyword in 'unsigned int'. mod_unsigned_int = ignore # ignore/add/remove/force # If there is a situation where mod_int_* and mod_*_int would result in # multiple int keywords, whether to keep the rightmost int (the default) or the # leftmost int. mod_int_prefer_int_on_left = false # true/false # (OC) Whether to organize the properties. If true, properties will be # rearranged according to the mod_sort_oc_property_*_weight factors. mod_sort_oc_properties = false # true/false # (OC) Weight of a class property modifier. mod_sort_oc_property_class_weight = 0 # number # (OC) Weight of 'atomic' and 'nonatomic'. mod_sort_oc_property_thread_safe_weight = 0 # number # (OC) Weight of 'readwrite' when organizing properties. mod_sort_oc_property_readwrite_weight = 0 # number # (OC) Weight of a reference type specifier ('retain', 'copy', 'assign', # 'weak', 'strong') when organizing properties. mod_sort_oc_property_reference_weight = 0 # number # (OC) Weight of getter type ('getter=') when organizing properties. mod_sort_oc_property_getter_weight = 0 # number # (OC) Weight of setter type ('setter=') when organizing properties. mod_sort_oc_property_setter_weight = 0 # number # (OC) Weight of nullability type ('nullable', 'nonnull', 'null_unspecified', # 'null_resettable') when organizing properties. mod_sort_oc_property_nullability_weight = 0 # number # # Preprocessor options # # How to use tabs when indenting preprocessor code. # # -1: Use 'indent_with_tabs' setting (default) # 0: Spaces only # 1: Indent with tabs to brace level, align with spaces # 2: Indent and align with tabs, using spaces when not on a tabstop # # Default: -1 pp_indent_with_tabs = -1 # number # Add or remove indentation of preprocessor directives inside #if blocks # at brace level 0 (file-level). pp_indent = remove # ignore/add/remove/force # Whether to indent #if/#else/#endif at the brace level. If false, these are # indented from column 1. pp_indent_at_level = false # true/false # Whether to indent #if/#else/#endif at the parenthesis level if the brace # level is 0. If false, these are indented from column 1. pp_indent_at_level0 = false # true/false # Specifies the number of columns to indent preprocessors per level # at brace level 0 (file-level). If pp_indent_at_level=false, also specifies # the number of columns to indent preprocessors per level # at brace level > 0 (function-level). # # Default: 1 pp_indent_count = 1 # unsigned number # Add or remove space after # based on pp level of #if blocks. pp_space_after = ignore # ignore/add/remove/force # Sets the number of spaces per level added with pp_space_after. pp_space_count = 0 # unsigned number # The indent for '#region' and '#endregion' in C# and '#pragma region' in # C/C++. Negative values decrease indent down to the first column. pp_indent_region = 0 # number # Whether to indent the code between #region and #endregion. pp_region_indent_code = false # true/false # If pp_indent_at_level=true, sets the indent for #if, #else and #endif when # not at file-level. Negative values decrease indent down to the first column. # # =0: Indent preprocessors using output_tab_size # >0: Column at which all preprocessors will be indented pp_indent_if = 0 # number # Whether to indent the code between #if, #else and #endif. pp_if_indent_code = false # true/false # Whether to indent the body of an #if that encompasses all the code in the file. pp_indent_in_guard = false # true/false # Whether to indent '#define' at the brace level. If false, these are # indented from column 1. pp_define_at_level = false # true/false # Whether to indent '#include' at the brace level. pp_include_at_level = false # true/false # Whether to ignore the '#define' body while formatting. pp_ignore_define_body = false # true/false # An offset value that controls the indentation of the body of a multiline #define. # 'body' refers to all the lines of a multiline #define except the first line. # Requires 'pp_ignore_define_body = false'. # # <0: Absolute column: the body indentation starts off at the specified column # (ex. -3 ==> the body is indented starting from column 3) # >=0: Relative to the column of the '#' of '#define' # (ex. 3 ==> the body is indented starting 3 columns at the right of '#') # # Default: 8 pp_multiline_define_body_indent = 8 # number # Whether to indent case statements between #if, #else, and #endif. # Only applies to the indent of the preprocessor that the case statements # directly inside of. # # Default: true pp_indent_case = true # true/false # Whether to indent whole function definitions between #if, #else, and #endif. # Only applies to the indent of the preprocessor that the function definition # is directly inside of. # # Default: true pp_indent_func_def = true # true/false # Whether to indent extern C blocks between #if, #else, and #endif. # Only applies to the indent of the preprocessor that the extern block is # directly inside of. # # Default: true pp_indent_extern = true # true/false # How to indent braces directly inside #if, #else, and #endif. # Requires pp_if_indent_code=true and only applies to the indent of the # preprocessor that the braces are directly inside of. # 0: No extra indent # 1: Indent by one level # -1: Preserve original indentation # # Default: 1 pp_indent_brace = 1 # number # Action to perform when unbalanced #if and #else blocks are found. # 0: do nothing # 1: print a warning message # 2: terminate the program with an error (EX_SOFTWARE) # # The action will be triggered in the following cases: # - if an #ifdef block ends on a different indent level than # where it started from. Example: # # #ifdef TEST # int i; # { # int j; # #endif # # - an #elif/#else block ends on a different indent level than # the corresponding #ifdef block. Example: # # #ifdef TEST # int i; # #else # } # int j; # #endif pp_unbalanced_if_action = 0 # unsigned number # # Sort includes options # # The regex for include category with priority 0. include_category_0 = "" # string # The regex for include category with priority 1. include_category_1 = "" # string # The regex for include category with priority 2. include_category_2 = "" # string # # Use or Do not Use options # # true: indent_func_call_param will be used (default) # false: indent_func_call_param will NOT be used # # Default: true use_indent_func_call_param = true # true/false # The value of the indentation for a continuation line is calculated # differently if the statement is: # - a declaration: your case with QString fileName ... # - an assignment: your case with pSettings = new QSettings( ... # # At the second case the indentation value might be used twice: # - at the assignment # - at the function call (if present) # # To prevent the double use of the indentation value, use this option with the # value 'true'. # # true: indent_continue will be used only once # false: indent_continue will be used every time (default) # # Requires indent_ignore_first_continue=false. use_indent_continue_only_once = false # true/false # The indentation can be: # - after the assignment, at the '[' character # - at the beginning of the lambda body # # true: indentation will be at the beginning of the lambda body # false: indentation will be after the assignment (default) indent_cpp_lambda_only_once = false # true/false # Whether sp_after_angle takes precedence over sp_inside_fparen. This was the # historic behavior, but is probably not the desired behavior, so this is off # by default. use_sp_after_angle_always = false # true/false # Whether to apply special formatting for Qt SIGNAL/SLOT macros. Essentially, # this tries to format these so that they match Qt's normalized form (i.e. the # result of QMetaObject::normalizedSignature), which can slightly improve the # performance of the QObject::connect call, rather than how they would # otherwise be formatted. # # See options_for_QT.cpp for details. # # Default: true use_options_overriding_for_qt_macros = true # true/false # If true: the form feed character is removed from the list of whitespace # characters. See https://en.cppreference.com/w/cpp/string/byte/isspace. use_form_feed_no_more_as_whitespace_character = false # true/false # # Warn levels - 1: error, 2: warning (default), 3: note # # (C#) Warning is given if doing tab-to-\t replacement and we have found one # in a C# verbatim string literal. # # Default: 2 warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number # Limit the number of loops. # Used by uncrustify.cpp to exit from infinite loop. # 0: no limit. debug_max_number_of_loops = 0 # number # Set the number of the line to protocol; # Used in the function prot_the_line if the 2. parameter is zero. # 0: nothing protocol. debug_line_number_to_protocol = 0 # number # Set the number of second(s) before terminating formatting the current file, # 0: no timeout. # only for linux debug_timeout = 0 # number # Set the number of characters to be printed if the text is too long, # 0: do not truncate. debug_truncate = 0 # unsigned number # sort (or not) the tracking info. # # Default: true debug_sort_the_tracks = true # true/false # decode (or not) the flags as a new line. # only if the -p option is set. debug_decode_the_flags = false # true/false # use (or not) the exit(EX_SOFTWARE) function. # # Default: true debug_use_the_exit_function_pop = true # true/false # print (or not) the version in the file defined at the command option -o. debug_print_version = false # true/false # insert the number of the line at the beginning of each line set_numbering_for_html_output = false # true/false # Meaning of the settings: # Ignore - do not do any changes # Add - makes sure there is 1 or more space/brace/newline/etc # Force - makes sure there is exactly 1 space/brace/newline/etc, # behaves like Add in some contexts # Remove - removes space/brace/newline/etc # # # - Token(s) can be treated as specific type(s) with the 'set' option: # `set tokenType tokenString [tokenString...]` # # Example: # `set BOOL __AND__ __OR__` # # tokenTypes are defined in src/token_enum.h, use them without the # 'CT_' prefix: 'CT_BOOL' => 'BOOL' # # # - Token(s) can be treated as type(s) with the 'type' option. # `type tokenString [tokenString...]` # # Example: # `type int c_uint_8 Rectangle` # # This can also be achieved with `set TYPE int c_uint_8 Rectangle` # # # To embed whitespace in tokenStrings use the '\' escape character, or quote # the tokenStrings. These quotes are supported: "'` # # # - Support for the auto detection of languages through the file ending can be # added using the 'file_ext' command. # `file_ext langType langString [langString..]` # # Example: # `file_ext CPP .ch .cxx .cpp.in` # # langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use # them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP' # # # - Custom macro-based indentation can be set up using 'macro-open', # 'macro-else' and 'macro-close'. # `(macro-open | macro-else | macro-close) tokenString` # # Example: # `macro-open BEGIN_TEMPLATE_MESSAGE_MAP` # `macro-open BEGIN_MESSAGE_MAP` # `macro-close END_MESSAGE_MAP` # # # option(s) with 'not default' value: 0 # eclipse-mosquitto-mosquitto-691eab3/CITATION.cff000066400000000000000000000012311514232433600215500ustar00rootroot00000000000000cff-version: 1.2.0 message: "If you use this software, please cite it as below." authors: - family-names: "Light" given-names: "Roger A." orcid: "https://orcid.org/0000-0001-9218-7797" title: "My Research Software" version: 2.0.21 doi: 10.21105/joss.00265 url: "https://github.com/eclise-mosquitto/mosquitto" preferred-citation: type: article authors: - family-names: "Light" given-names: "Roger A." orcid: "https://orcid.org/0000-0001-9218-7797" doi: "10.21105/joss.00265" journal: "Journal of Open Source Software" start: 265 title: "Mosquitto: server and client implementation of the MQTT protocol" issue: 13 volume: 2 year: 2017 eclipse-mosquitto-mosquitto-691eab3/CMakeLists.txt000066400000000000000000000203141514232433600224210ustar00rootroot00000000000000# This is a cmake script. Process it with the CMake gui or command line utility # to produce makefiles / Visual Studio project files on Mac OS X and Windows. # # To configure the build options either use the CMake gui, or run the command # line utility including the "-i" option. cmake_minimum_required(VERSION 3.18) set (VERSION 2.1.2) project(mosquitto VERSION ${VERSION} DESCRIPTION "Eclipse Mosquitto" LANGUAGES C CXX ) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") add_definitions (-DCMAKE -DVERSION=\"${VERSION}\") if(WIN32) add_definitions("-D_CRT_SECURE_NO_WARNINGS") add_definitions("-D_CRT_NONSTDC_NO_DEPRECATE") endif() if(APPLE) set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -undefined dynamic_lookup") endif() add_library(common-options INTERFACE) if(NOT WIN32) target_compile_options(common-options INTERFACE -Wall -Wextra -Wconversion) endif() include(CMakePushCheckState) include(CheckIncludeFiles) include(CheckLibraryExists) include(CheckSourceCompiles) include(GNUInstallDirs) include(CMakePushCheckState) include(CheckSourceCompiles) option(WITH_BUNDLED_DEPS "Build with bundled dependencies?" ON) option(WITH_TLS "Include SSL/TLS support?" ON) option(WITH_TLS_PSK "Include TLS-PSK support (requires WITH_TLS)?" ON) option(WITH_TESTS "Enable tests" ON) option(INC_MEMTRACK "Include memory tracking support?" ON) if (WITH_TLS) find_package(OpenSSL REQUIRED) add_definitions("-DWITH_TLS") # mosquitto uses OpenSSL 1.1 API, so set OPENSSL_API_COMPAT accordingly: # https://www.openssl.org/docs/manmaster/man7/OPENSSL_API_COMPAT.html # TODO: migrate off ENGINE API (deprecated since OpenSSL 3.0), see: # https://www.openssl.org/docs/manmaster/man7/migration_guide.html#Engines-and-METHOD-APIs add_definitions("-DOPENSSL_API_COMPAT=0x10100000L") if (WITH_TLS_PSK) add_definitions("-DWITH_TLS_PSK") endif (WITH_TLS_PSK) else() set (OPENSSL_INCLUDE_DIR "") endif() option(WITH_UNIX_SOCKETS "Include Unix Domain Socket support?" ON) if(WITH_UNIX_SOCKETS AND NOT WIN32) add_definitions("-DWITH_UNIX_SOCKETS") endif() option(WITH_SOCKS "Include SOCKS5 support?" ON) if(WITH_SOCKS) add_definitions("-DWITH_SOCKS") endif() option(WITH_WEBSOCKETS "Include websockets support?" ON) option(WITH_WEBSOCKETS_BUILTIN "Websockets support uses builtin library? Set OFF to use libwebsockets" ON) if(WITH_WEBSOCKETS AND NOT WITH_TLS) message(FATAL_ERROR "WITH_WEBSOCKETS support requires WITH_TLS.") endif() option(WITH_SRV "Include SRV lookup support?" OFF) option(WITH_STATIC_LIBRARIES "Build static versions of the libmosquitto/pp libraries?" OFF) option(WITH_PIC "Build the static library with PIC (Position Independent Code) enabled archives?" OFF) option(WITH_THREADING "Include threading support?" ON) if(WITH_THREADING) add_definitions("-DWITH_THREADING") if(WIN32) find_package(PThreads4W REQUIRED) endif() endif() option(WITH_DLT "Include DLT support?" OFF) message(STATUS "WITH_DLT = ${WITH_DLT}") if(WITH_DLT) find_package(PkgConfig) pkg_check_modules(DLT "automotive-dlt >= 2.11" REQUIRED) endif() find_package(cJSON REQUIRED) find_package(argon2) option(WITH_CTRL_SHELL "Include mosquitto_ctrl interactive shell support?" ON) if(WITH_CTRL_SHELL) find_package(LineEditing) endif() if(ARGON2_FOUND) # Disable until separate password handling thread is implemented #add_definitions("-DWITH_ARGON2") endif() option(WITH_LTO "Build with link time optimizations (IPO) / interprocedural optimization (IPO) enabled." ON) if(WITH_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT is_ipo_supported OUTPUT output) if(is_ipo_supported) set_property(TARGET common-options PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) else() message(WARNING "LTO/IPO is not supported: ${output}") endif() endif() # ======================================== # Include projects # ======================================== option(WITH_CLIENTS "Build clients?" ON) option(WITH_BROKER "Build broker?" ON) option(WITH_APPS "Build apps?" ON) option(WITH_PLUGINS "Build plugins?" ON) option(WITH_DOCS "Build documentation?" ON) add_library(config-header INTERFACE) target_sources(config-header INTERFACE config.h) target_include_directories(config-header INTERFACE ${mosquitto_SOURCE_DIR} ) if(WITH_TLS) target_include_directories(config-header INTERFACE "${OPENSSL_INCLUDE_DIR}" ) endif() add_subdirectory(libcommon) add_subdirectory(lib) if(WITH_CLIENTS) add_subdirectory(client) endif() if(WITH_BROKER) add_subdirectory(src) endif() if(WITH_APPS) add_subdirectory(apps) endif() if(WITH_PLUGINS) add_subdirectory(plugins) endif() if(WITH_DOCS) add_subdirectory(man) endif() # ======================================== # Install config file # ======================================== if(WITH_BROKER) install(FILES mosquitto.conf RENAME mosquitto.conf.example DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/mosquitto") install(FILES aclfile.example pskfile.example pwfile.example DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/mosquitto") endif() # ======================================== # Install pkg-config files # ======================================== configure_file(libmosquitto.pc.in libmosquitto.pc @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libmosquitto.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") configure_file(libmosquittopp.pc.in libmosquittopp.pc @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libmosquittopp.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") # ======================================== # Install headers # ======================================== install( FILES ${mosquitto_SOURCE_DIR}/include/mosquitto.h ${mosquitto_SOURCE_DIR}/include/mosquitto_broker.h ${mosquitto_SOURCE_DIR}/include/mosquitto_plugin.h ${mosquitto_SOURCE_DIR}/include/mosquittopp.h ${mosquitto_SOURCE_DIR}/include/mqtt_protocol.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" ) install( FILES ${mosquitto_SOURCE_DIR}/include/mosquitto/broker.h ${mosquitto_SOURCE_DIR}/include/mosquitto/broker_control.h ${mosquitto_SOURCE_DIR}/include/mosquitto/broker_plugin.h ${mosquitto_SOURCE_DIR}/include/mosquitto/defs.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_base64.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_cjson.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_file.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_memory.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_password.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_properties.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_random.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_string.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_time.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_topic.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libcommon_utf8.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_auth.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_callbacks.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_connect.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_create_delete.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_helpers.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_loop.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_message.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_options.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_publish.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_socks.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_subscribe.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_tls.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_unsubscribe.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquitto_will.h ${mosquitto_SOURCE_DIR}/include/mosquitto/libmosquittopp.h ${mosquitto_SOURCE_DIR}/include/mosquitto/mqtt_protocol.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/mosquitto" ) # ======================================== # Testing # ======================================== if(WITH_TESTS) find_package(GTest REQUIRED) include(GoogleTest) enable_testing() add_subdirectory(test) endif() eclipse-mosquitto-mosquitto-691eab3/CONTRIBUTING.md000066400000000000000000000075121514232433600221170ustar00rootroot00000000000000Contributing to Mosquitto ========================= Thank you for your interest in this project. Project description: -------------------- The Mosquitto project has been created to provide a light weight, open-source implementation, of an MQTT broker to allow new, existing, and emerging applications for Machine-to-Machine (M2M) and Internet of Things (IoT). - - Source ------ The Mosquitto code is stored in a git repository. - https://github.com/eclipse/mosquitto You can contribute bugfixes and new features by sending pull requests through GitHub. ## Legal In order for your contribution to be accepted, it must comply with the Eclipse Foundation IP policy. Please read the [Eclipse Foundation policy on accepting contributions via Git](http://wiki.eclipse.org/Development_Resources/Contributing_via_Git). 1. Sign the [Eclipse ECA](http://www.eclipse.org/legal/ECA.php) 1. Register for an Eclipse Foundation User ID. You can register [here](https://accounts.eclipse.org/user/register). 2. Log into the [Accounts Portal](https://accounts.eclipse.org/), and click on the '[Eclipse Contributor Agreement](https://accounts.eclipse.org/user/eca)' link. 2. Go to your [account settings](https://accounts.eclipse.org/user/edit) and add your GitHub username to your account. 3. Make sure that you _sign-off_ your Git commits in the following format: ``` Signed-off-by: John Smith ``` This is usually at the bottom of the commit message. You can automate this by adding the '-s' flag when you make the commits. e.g. ```git commit -s -m "Adding a cool feature"``` 4. Ensure that the email address that you make your commits with is the same one you used to sign up to the Eclipse Foundation website with. ## Contributing a change 1. [Fork the repository on GitHub](https://github.com/eclipse/mosquitto/fork) 2. Clone the forked repository onto your computer: ``` git clone https://github.com//mosquitto.git ``` 3. If you are adding a new feature, then create a new branch from the latest ```develop``` branch with ```git checkout -b YOUR_BRANCH_NAME origin/develop``` 4. If you are fixing a bug, then create a new branch from the latest ```fixes``` branch with ```git checkout -b YOUR_BRANCH_NAME origin/fixes``` 5. Make your changes 6. Ensure that all new and existing tests pass. 7. Commit the changes into the branch: ``` git commit -s ``` Make sure that your commit message is meaningful and describes your changes correctly. 8. If you have a lot of commits for the change, squash them into a single / few commits. 9. Push the changes in your branch to your forked repository. 10. Finally, go to [https://github.com/eclipse/mosquitto](https://github.com/eclipse/mosquitto) and create a pull request from your "YOUR_BRANCH_NAME" branch to the ```develop``` or ```fixes``` branch as appropriate to request review and merge of the commits in your pushed branch. What happens next depends on the content of the patch. If it is 100% authored by the contributor and is less than 1000 lines (and meets the needs of the project), then it can be pulled into the main repository. If not, more steps are required. These are detailed in the [legal process poster](http://www.eclipse.org/legal/EclipseLegalProcessPoster.pdf). Contact: -------- Contact the project developers via the project's development [mailing list](https://dev.eclipse.org/mailman/listinfo/mosquitto-dev). Search for bugs: ---------------- This project uses [Github](https://github.com/eclipse/mosquitto/issues) to track ongoing development and issues. Create a new bug: ----------------- Be sure to search for existing bugs before you create another one. Remember that contributions are always welcome! - [Create new Mosquitto bug](https://github.com/eclipse/mosquitto/issues) eclipse-mosquitto-mosquitto-691eab3/ChangeLog.txt000066400000000000000000004701021514232433600222550ustar00rootroot000000000000002.1.2 - 2026-02-09 ================== Broker: - Forbid running with `persistence true` and with a persistence plugin at the same time. Build: - Build fixes for OpenBSD. Closes #3474. - Add missing libedit to docker builds. Closes #3476. - Fix static/shared linking of libwebsockets under cmake. 2.1.1 - 2026-02-04 ================== # Broker - Fix PUID/PGID checking for docker - Add MOSQUITTO_UNSAFE_ALLOW_SYMLINKS environment variable to allow the restrictions on reading files through symlinks to be lifted in safe environments like kubernetes. Closes #3461. - Fix inconsistent disconnect log message format, and add address:port. - Fix `plugin`/`global_plugin` option not allowing space characters. - Fix $SYS load values not being published initially. Closes #3459. - Fix max_connections not being honoured on libwebsockets listeners. This does not affect the built-in websockets support. Closes #3455. - Don't enforce receive-maximum, just log a warning. This allows badly behaving clients to be fixed. Closes #3471. # Plugins - Fix incorrect linking of libmosquitto_common.so for the acl and password file plugins. Closes #3460. # Build - Fix building with WITH_TLS=no 2.1.0 - 2026-01-29 ================== # Broker ## Deprecations - The acl_file option is deprecated in favour of the acl-file plugin, which is the same code but moved into a plugin. The acl_file option will be removed in 3.0. - The password_file option is deprecated in favour of the password-file plugin, which is the same code but moved into a plugin. The password_file option will be removed in 3.0. - The per_listener_settings option is deprecated in favour of the new listener specific options. The per_listener_settings option will be removed in 3.0. ## Behaviour changes - max_packet_size now defaults to 2,000,000 bytes instead of the 256MB MQTT limit. If you are using payloads that will result in a packet larger than this, you need to manually set the option to a value that suits your application. - acl_file and password_file will produce an error on invalid input when reloading the config, causing the broker to quit. ## Protocol related - Add support for broker created topic aliases. Topics are allocated on a first come first serve basis. - Add support for bridges to allow remote brokers to create topic aliases when running in MQTT v5 mode. - Enforce receive maximum on MQTT v5. - Return protocol error if a client attemps to subscribe to a shared subscription and also sets no-local. - Protocol version numbers reported in the log when a client connects now match the MQTT protocol version numbers, not internal Mosquitto values. - Send DISCONNECT With session-takeover return code to MQTT v5 clients when a client connects with the same client id. Closes #2340. - The `allow_duplicate_messages` now defaults to `true`. - Add `accept_protocol_versions` option to allow limiting which MQTT protocol versions are allowed for a particular listener. ## TLS related - Add `--tls-keylog` option which can be used to generate a file that can be used by wireshark to decrypt TLS traffic for debugging purposes. Closes #1818. - Add `disable_client_cert_date_checks` option to allow expired client certificate to be considered valid. - Add `bridge_tls_use_os_certs` option to allow bridges to be easily configured to trust default CA certificates. Closes #2473. - Remove support for TLS v1.1 (clients only - it remains available in the broker but is now undocumented) - Use openssl provided function for x509 certificate hostname verification, rather than own function. ## Bridge related - Add `bridge_receive_maximum` option for MQTT v5.0 bridges. - Add `bridge_session_expiry_interval` option for MQTT v5.0 bridges. - Bridge reconnection backoff improvements. ## Transport related - Add the `websockets_origin` option to allow optional enforcement of origin when a connection attempts an upgrade to WebSockets. - Add built-in websockets support that doesn't use libwebsockets. This is the preferred websockets implementation. - Add support for X-Forwarded-For header for built in websockets. - Add suport for PROXY protocol v1 and v2. ## Platform specific - Increase maximum connection count on Windows from 2048 to 8192 where supported. Closes #2122. - Allow multiple instances of mosquitto to run as services on Windows. See README-windows.txt. - Add kqueue support. - Add support for systemd watchdog. ## General - Report on what compile time options are enabled. Closes #2193. - Performance: reduce memory allocations when sending packets. - Log protocol version and ciphers that a client negotiates when connecting. - Password salts are now 64 bytes long. - Add the `global_plugin` option, which gives global plugin loaded regardless of `per_listener_settings`. - Add `global_max_clients` option to allow limiting client sessions globally on the broker. - Add `global_max_connections` option to allow limiting client connections globally on the broker. - Improve idle performance. The broker now calculates when the next event of interest is, and uses that as the timeout for e.g. `epoll_wait()`. This can reduce the number of process wakeups by 100x on an idle broker. - Add more efficient keepalive check. - Add support for sending the SIGRTMIN signal to trigger log rotation. Closes #2337. - Add `--test-config` option which can be used to test a configuration file before trying to use it in a live broker. Closes #2521. - Add support for PUID/PGID environment variables for setting the user/group to drop privileges to. Closes #2441. - Report persistence stats when starting. - $SYS updates are now aligned to `sys_interval` seconds, meaning that if set to 10, for example, updates will be sent at times matching x0 seconds. Previously update intervals were aligned to the time the broker was started. - Add `log_dest android` for logging to the Android logd daemon. - Fix some retained topic memory not being cleared immediately after used. - Add -q option to allow logging to be disabled at the command line. - Log message if a client attempts to connect with TLS to a non-TLS listener. - Add `listener_allow_anonymous` option. - Add `listener_auto_id_prefix` option. - Allow seconds when defining `persistent_client_expiration`. ## Plugin interface - Add `mosquitto_topic_matches_sub_with_pattern()`, which can match against subscriptions with `%c` and `%u` patterns for client id / username substitution. - Add support for modifying outgoing messages using `MOSQ_EVT_MESSAGE_OUT`. - Add `mosquitto_client()` function for retrieving a client struct if that client is connected. - Add `MOSQ_ERR_PLUGIN_IGNORE` to allow plugins to register basic auth or acl check callbacks, but still act as though they are not registered. A plugin that wanted to act as a blocklist for certain usernames, but wasn't carrying out authentication could return `MOSQ_ERR_PLUGIN_IGNORE` for usernames not on its blocklist. If no other plugins were configured, the client would be authenticated. Using `MOSQ_ERR_PLUGIN_DEFER` instead would mean the clients would be denied if no other plugins were configured. - Add `mosquitto_client_port()` function for plugins. - Add `MOSQ_EVT_CONNECT`, to allow plugins to know when a client has successfully authenticated to the broker. - Add connection-state example plugin to demonstrate `MOSQ_EVT_CONNECT`. - Add `MOSQ_EVT_CLIENT_OFFLINE`, to allow plugins to know when a client with a non-zero session expiry interval has gone offline. - Plugins on non-Windows platforms now no longer make their symbols globally available, which means they are self contained. - Add support for delayed basic authentication in plugins. - Plugins using the `MOSQ_EVT_MESSAGE_WRITE` callback can now return `MOSQ_ERR_QUOTA_EXCEEDED` to have the message be rejected. MQTT v5 clients using QoS 1 or 2 will receive the quota-exceeded reason code in the corresponding PUBACK/PUBREC. - `MOSQ_EVT_TICK` is now passed to plugins when `per_listener_settings` is true. - Add `mosquitto_sub_matches_acl()`, which can match one topic filter (a subscription) against another topic filter (an ACL). - Registration of the `MOSQ_EVT_CONTROL` plugin event is now handled globally across the broker, so only a single plugin can register for a given $CONTROL topic. - Add `mosquitto_plugin_set_info()` to allow plugins to tell the broker their name and version. - Add builtin $CONTROL/broker/v1 control topic with the `listPlugins` command. This is disabled by default, but can be enabled with the `enable_control_api` option. - Plugins no longer need to define `mosquitto_plugin_cleanup()` if they do not need to do any of their own cleanup. Callbacks will be unregistered automatically. - Add `mosquitto_set_clientid()` to allow plugins to force a client id for a client. - Add `MOSQ_EVT_SUBSCRIBE` and `MOSQ_EVT_UNSUBSCRIBE` events that are called when subscribe/unsubscribes actually succeed. Allow modifying topic and qos. - Add `mosquitto_persistence_location()` for plugins to use to find a valid location for storing persistent data. - Plugins can now use the `next_s` and `next_ms` members of the tick event data struct to set a minimum interval that the broker will wait before calling the tick callback again. - MOSQ_EVT_ACL_CHECK event is now passed message properties where possible. # Plugins - Add acl-file plugin. - Add password-file plugin. - Add persist-sqlite plugin. - Add sparkplug-aware plugin. # Dynamic security plugin - Add ability to deny wildcard subscriptions for a role to the dynsec plugin. - The dynamic security plugin now only kicks clients at the start of the next network loop, to give chance for PUBACK/PUBREC to be sent. Closes #2474. - The dynamic security plugin now reports client connections in getClient and listClients. - The dynamic security plugin now generates an initial configuration if none is present, including a set of default roles. - The dynamic security plugin now supports `%c` and `%u` patterns for substituting client id and username respectively, in all ACLs except for subscribeLiteral and unsubscribeLiteral. - The dynamic security plugin now supports multiple ways to initialise the first configuration file. # Client library - Add `MOSQ_OPT_DISABLE_SOCKETPAIR` to allow the disabling of the socketpair feature that allows the network thread to be woken from select() by another thread when e.g. `mosquitto_publish()` is called. This reduces the number of sockets used by each client by two. - Add `on_pre_connect()` callback to allow clients to update username/password/TLS parameters before an automatic reconnection. - Callbacks no longer block other callbacks, and can be set from within a callback. Closes #2127. - Add support for MQTT v5 broker to client topic aliases. - Add `mosquitto_topic_matches_sub_with_pattern()`, which can match against subscriptions with `%c` and `%u` patterns for client id / username substitution. - Add `mosquitto_sub_matches_acl()`, which can match one topic filter (a subscription) against another topic filter (an ACL). - Add `mosquitto_sub_matches_acl_with_pattern()`, which can match one topic filter (a subscription) against another topic filter (an ACL), with `%c` and `%u` patterns for client id / username substitution. - Performance: reduce memory allocations when sending packets. - Reintroduce threading support for Windows. Closes #1509. - `mosquitto_subscribe*()` now returns `MOSQ_ERR_INVAL` if an empty string is passed as a topic filter. - `mosquitto_unsubscribe*()` now returns `MOSQ_ERR_INVAL` if an empty string is passed as a topic filter. - Add websockets support. - `mosquitto_property_read_binary/string/string_pair` will now set the name/value parameter to NULL if the binary/string is empty. This aligns the behaviour with other property functions. Closes #2648. - Add `mosquitto_unsubscribe2_v5_callback_set`, which provides a callback that gives access to reason codes for each of the unsubscription requests. - Add `mosquitto_property_remove`, for removing properties from property lists. - Add `on_ext_auth()` callback to allow handling MQTT v5 extended authentication. - Add `mosquitto_ext_auth_continue()` function to continue an MQTT v5 extended authentication. - Remove support for TLS v1.1. - Use openssl provided function for x509 certificate hostname verification, rather than own function. # Clients ## General - Add `-W` timeout support to Windows. - The `--insecure` option now disables all server certificate verification. - Add websockets support. - Using `-x` now sets the clients to use MQTT v5.0. - Fix parsing of IPv6 addresses in socks proxy urls. - Add `--tls-keylog` option which can be used to generate a file that can be used by wireshark to decrypt TLS traffic for debugging purposes. - Remove support for TLS v1.1. - Add `-o` option for all clients loading options from a specific file. - Add `--no-tls` option for all clients which disables all TLS options for that instance. This is useful for negating TLS options provided in a config file, or to disable the automatic use of TLS when using port 8883. Closes #2180. ## mosquitto_rr - Fix `-f` and `-s` options in mosquitto_rr. - Add `--latency` option to mosquitto_rr, for printing the request/response latency. - Add `--retain-handling` option. ## mosquitto_sub - Fix incorrect output formatting in mosquitto_sub when using field widths with `%x` and `%X` for printing the payload in hex. - Add float printing option to mosquitto_sub. - mosquitto_sub payload hex output can now be split by fixed field length. - Add `--message-rate` option to mosquitto_sub, for printing the count of messages received each second. - Add `--retain-handling` option. - Add `-w`/`--watch` to mosquitto_sub which means messages will be printed on a fixed line number based on the topic and order in which messages were received. Requires ANSI escape code support in the terminal. - mosquitto_sub now only needs `-t` or `-U` to run - this means that `-t` is not required in all situations. # Apps ## mosquitto_signal - Add `mosquitto_signal` for helping send signals to mosquitto on Windows. ## mosquitto_ctrl - Add interactive shell mode to mosquitto_ctrl. - Add support for `listPlugins` to mosquitto_ctrl. - Allow mosquitto_ctrl dynsec module to update passwords in files rather than having to connect to a broker. ## mosquitto_passwd - Print messages in mosquitto_passwd when adding/updating passwords. Closes #2544. - When creating a new file with `-c`, setting the output filename to a dash `-` will output the result to stdout. ## mosquitto_db_dump - Add `--json` output mode do mosquitto_db_dump. # Build - Increased CMake minimal required version to 3.14, which is required for the preinstalled SQLite3 find module. - Add an CMake option `WITH_LTO` to enable/disable link time optimization. - Set C99 as the explicit, rather than implicit, build standard. - cJSON is now a required dependency. - Refactored headers for easier discovery. - Support for openssl < 3.0 removed. 2.0.23 - 2026-01-14 =================== Broker: - Fix handling of disconnected sessions for `per_listener_settings true` - Check return values of openssl *_get_ex_data() and *_set_ex_data() to prevent possible crash. This could occur only in extremely unlikely situations. See https://github.com/eclipse-mosquitto/mosquitto/issues/3389 Closes #3389. - Check return value of openssl ASN1_string_[get0_]data() functions for NULL. This prevents a crash in case of incorrect certificate handling in openssl. Closes #3390. - Fix potential crash on startup if a malicious/corrupt persistence file from mosquitto 1.5 or earlier is loaded. Closes #3439. - Limit auto_id_prefix to 50 characters. Closes #3440. 2.0.22 - 2025-07-11 =================== # Broker - Windows: Fix broker crash on startup if using `log_dest stdout` - Bridge: Fix idle_timeout never occurring for lazy bridges. - Fix case where max_queued_messages = 0 was not treated as unlimited. Closes #3244. - Fix `--version` exit code and output. Closes #3267. - Fix crash on receiving a $CONTROL message over a bridge, if per_listener_settings is set true and the bridge is carrying out topic remapping. Closes #3261. - Fix incorrect reference clock being selected on startup on Linux. Closes #3238. - Fix reporting of client disconnections being incorrectly attributed to "out of memory". Closes #3253. - Fix compilation when using `WITH_OLD_KEEPALIVE`. Closes #3250. - Add Windows linker file for the broker to the installer. Closes #3269. - Fix Websockets PING not being sent on Windows. Closes #3272. - Fix problems with secure websockets. Closes #1211. - Fix crash on exit when using WITH_EPOLL=no. Closes #3302. - Fix clients being incorrectly expired when they have keepalive == max_keepalive. Closes #3226, #3286. # Dynamic security plugin - Fix mismatch memory free when saving config which caused memory tracking to be incorrect. # Client library - Fix C++ symbols being removed when compiled with link time optimisation. Closes #3259. - TLS error handling was incorrectly setting a protocol error for non-TLS errors. This would cause the mosquitto_loop_start() thread to exit if no broker was available on the first connection attempt. This has been fixed. Closes #3258. - Fix linker errors on some architectures using cmake. Closes #3167. # Tests - Fix 08-ssl-connect-cert-auth-expired and 08-ssl-connect-cert-auth-revoked tests when running on a single CPU system. Closes #3230. 2.0.21 - 2025-03-06 =================== # Security - Fix leak on malicious SUBSCRIBE by authenticated client. Closes eclipse #248. - Further fix for CVE-2023-28366. # Broker - Fix clients sending a RESERVED packet not being quickly disconnected. Closes #2325. - Fix `bind_interface` producing an error when used with an interface that has an IPv6 link-local address and no other IPv6 addresses. Closes #2696. - Fix mismatched wrapped/unwrapped memory alloc/free in properties. Closes #3192. - Fix `allow_anonymous false` not being applied in local only mode. Closes #3198. - Add `retain_expiry_interval` option to fix expired retained message not being removed from memory if they are not subscribed to. Closes #3221. - Produce an error if invalid combinations of cafile/capath/certfile/keyfile are used. Closes #1836. Closes #3130. - Backport keepalive checking from develop to fix problems in current implementation. Closes #3138. # Client library - Fix potential deadlock in mosquitto_sub if `-W` is used. Closes #3175. # Apps - mosquitto_ctrl dynsec now also allows `-i` to specify a clientid as well as `-c`. This matches the documentation which states `-i`. Closes #3219. # Client library - Fix threads linking on Windows for static libmosquitto library Closes #3143 # Build - Fix Windows builds not having websockets enabled. - Add tzdata to docker images # Tests - Fix 08-ssl-connect-cert-auth-expired and 08-ssl-connect-cert-auth-revoked tests when under load. Closes #3208. 2.0.20 - 2024-10-16 =================== # Broker - Fix QoS 1 / QoS 2 publish incorrectly returning "no subscribers". Closes #3128. - Open files with appropriate access on Windows. Closes #3119. - Don't allow invalid response topic values. - Fix some strict protocol compliance issues. Closes #3052. # Client library - Fix cmake build on OS X. Closes #3125. # Build - Fix build on NetBSD 2.0.19 - 2024-10-02 =================== # Security - Fix mismatched subscribe/unsubscribe with normal/shared topics. - Fix crash on bridge using remapped topic being sent a crafted packet. # Broker - Fix assert failure when loading a persistence file that contains subscriptions with no client id. - Fix local bridges being incorrectly expired when persistent_client_expiration is in use. - Fix use of CLOCK_BOOTTIME for getting time. Closes #3089. - Fix mismatched subscribe/unsubscribe with normal/shared topics. - Fix crash on bridge using remapped topic being sent a crafted packet. # Client library - Fix some error codes being converted to string as "unknown". Closes #2579. - Clear SSL error state to avoid spurious error reporting. Closes #3054. - Fix "payload format invalid" not being allowed as a PUBREC reason code. - Don't allow SUBACK with missing reason codes. # Build - Thread support is re-enabled on Windows. 2.0.18 - 2023-09-18 =================== # Broker - Fix crash on subscribe under certain unlikely conditions. Closes #2885. Closes #2881. # Clients - Fix mosquitto_rr not honouring `-R`. Closes #2893. # Windows - Installer will start/stop the mosquitto service when installing and uninstalling, to prevent problems with not being able to overwrite or remove mosquitto.exe. 2.0.17 - 2023-08-22 =================== # Broker - Fix `max_queued_messages 0` stopping clients from receiving messages. Closes #2879. - Fix `max_inflight_messages` not being set correctly. Closes #2876. # Apps - Fix `mosquitto_passwd -U` backup file creation. Closes #2873. 2.0.16 - 2023-08-16 =================== # Security - CVE-2023-28366: Fix memory leak in broker when clients send multiple QoS 2 messages with the same message ID, but then never respond to the PUBREC commands. - CVE-2023-0809: Fix excessive memory being allocated based on malicious initial packets that are not CONNECT packets. - CVE-2023-3592: Fix memory leak when clients send v5 CONNECT packets with a will message that contains invalid property types. - Broker will now reject Will messages that attempt to publish to $CONTROL/. - Broker now validates usernames provided in a TLS certificate or TLS-PSK identity are valid UTF-8. - Fix potential crash when loading invalid persistence file. - Library will no longer allow single level wildcard certificates, e.g. *.com # Broker - Fix $SYS messages being expired after 60 seconds and hence unchanged values disappearing. - Fix some retained topic memory not being cleared immediately after used. - Fix error handling related to the `bind_interface` option. - Fix std* files not being redirected when daemonising, when built with assertions removed. Closes #2708. - Fix default settings incorrectly allowing TLS v1.1. Closes #2722. - Use line buffered mode for stdout. Closes #2354. Closes #2749. - Fix bridges with non-matching cleansession/local_cleansession being expired on start after restoring from persistence. Closes #2634. - Fix connections being limited to 2048 on Windows. The limit is now 8192, where supported. Closes #2732. - Broker will log warnings if sensitive files are world readable/writable, or if the owner/group is not the same as the user/group the broker is running as. In future versions the broker will refuse to open these files. - mosquitto_memcmp_const is now more constant time. - Only register with DLT if DLT logging is enabled. - Fix any possible case where a json string might be incorrectly loaded. This could have caused a crash if a textname or textdescription field of a role was not a string, when loading the dynsec config from file only. - Dynsec plugin will not allow duplicate clients/groups/roles when loading config from file, which matches the behaviour for when creating them. - Fix heap overflow when reading corrupt config with "log_dest file". # Client library - Use CLOCK_BOOTTIME when available, to keep track of time. This solves the problem of the client OS sleeping and the client hence not being able to calculate the actual time for keepalive purposes. Closes #2760. - Fix default settings incorrectly allowing TLS v1.1. Closes #2722. - Fix high CPU use on slow TLS connect. Closes #2794. # Clients - Fix incorrect topic-alias property value in mosquitto_sub json output. - Fix confusing message on TLS certificate verification. Closes #2746. # Apps - mosquitto_passwd uses mkstemp() for backup files. - `mosquitto_ctrl dynsec init` will refuse to overwrite an existing file, without a race-condition. 2.0.15 - 2022-08-16 =================== # Security - Deleting the group configured as the anonymous group in the Dynamic Security plugin, would leave a dangling pointer that could lead to a single crash. This is considered a minor issue - only administrative users should have access to dynsec, the impact on availability is one-off, and there is no associated loss of data. It is now forbidden to delete the group configured as the anonymous group. # Broker - Fix memory leak when a plugin modifies the topic of a message in MOSQ_EVT_MESSAGE. - Fix bridge `restart_timeout` not being honoured. - Fix potential memory leaks if a plugin modifies the message in the MOSQ_EVT_MESSAGE event. - Fix unused flags in CONNECT command being forced to be 0, which is not required for MQTT v3.1. Closes #2522. - Improve documentation of `persistent_client_expiration` option. Closes #2404. - Add clients to session expiry check list when restarting and reloading from persistence. Closes #2546. - Fix bridges not sending failure notification messages to the local broker if the remote bridge connection fails. Closes #2467. Closes #1488. - Fix some PUBLISH messages not being counted in $SYS stats. Closes #2448. - Fix incorrect return code being sent in DISCONNECT when a client session is taken over. Closes #2607. - Fix confusing "out of memory" error when a client is kicked in the dynamic security plugin. Closes #2525. - Fix confusing error message when dynamic security config file was a directory. Closes #2520. - Fix bridge queued messages not being persisted when local_cleansession is set to false and cleansession is set to true. Closes #2604. - Dynamic security: Fix modifyClient and modifyGroup commands to not modify the client/group if a new group/client being added is not valid. Closes #2598. - Dynamic security: Fix the plugin being able to be loaded twice. Currently only a single plugin can interact with a unique $CONTROL topic. Using multiple instances of the plugin would produce duplicate entries in the config file. Closes #2601. Closes #2470. - Fix case where expired messages were causing queued messages not to be delivered. Closes #2609. - Fix websockets not passing on the X-Forwarded-For header. # Client library - Fix threads library detection on Windows under cmake. Bumps the minimum cmake version to 3.1, which is still ancient. - Fix use of `MOSQ_OPT_TLS_ENGINE` being unable to be used due to the openssl ctx not being initialised until starting to connect. Closes #2537. - Fix incorrect use of SSL_connect. Closes #2594. - Don't set SIGPIPE to ignore, use MSG_NOSIGNAL instead. Closes #2564. - Add documentation of struct mosquitto_message to header. Closes #2561. - Fix documentation omission around mosquitto_reinitialise. Closes #2489. - Fix use of MOSQ_OPT_SSL_CTX when used in conjunction with MOSQ_OPT_SSL_CTX_DEFAULTS. Closes #2463. - Fix failure to close thread in some situations. Closes #2545. # Clients - Fix mosquitto_pub incorrectly reusing topic aliases when reconnecting. Closes #2494. # Apps - Fix `-o` not working in `mosquitto_ctrl`, and typo in related documentation. Closes #2471. 2.0.14 - 2021-11-17 =================== # Broker - Fix bridge not respecting receive-maximum when reconnecting with MQTT v5. # Client library - Fix mosquitto_topic_matches_sub2() not using the length parameters. Closes #2364. - Fix incorrect subscribe_callback in mosquittopp.h. Closes #2367. 2.0.13 - 2021-10-27 =================== # Broker - Fix `max_keepalive` option not being able to be set to 0. - Fix LWT messages not being delivered if `per_listener_settings` was set to true. Closes #2314. - Various fixes around inflight quota management. Closes #2306. - Fix problem parsing config files with Windows line endings. Closes #2297. - Don't send retained messages when a shared subscription is made. - Fix log being truncated in Windows. - Fix client id not showing in log on failed connections, where possible. - Fix broker sending duplicate CONNACK on failed MQTT v5 reauthentication. Closes #2339. - Fix mosquitto_plugin.h not including mosquitto_broker.h. Closes #2350. - Fix unlimited message quota not being properly checked for incoming messages. Closes #2593. - Fixed build for openssl compiled with OPENSSL_NO_ENGINE. Closes #2589. # Client library - Initialise sockpairR/W to invalid in `mosquitto_reinitialise()` to avoid closing invalid sockets in `mosquitto_destroy()` on error. Closes #2326. # Clients - Fix date format in mosquitto_sub output. Closes #2353. 2.0.12 - 2021-08-31 =================== # Security - An MQTT v5 client connecting with a large number of user-property properties could cause excessive CPU usage, leading to a loss of performance and possible denial of service. This has been fixed. - Fix `max_keepalive` not applying to MQTT v3.1.1 and v3.1 connections. These clients are now rejected if their keepalive value exceeds max_keepalive. This option allows CVE-2020-13849, which is for the MQTT v3.1.1 protocol itself rather than an implementation, to be addressed. - Using certain listener related configuration options e.g. `cafile`, that apply to the default listener without defining any listener would cause a remotely accessible listener to be opened that was not confined to the local machine but did have anonymous access enabled, contrary to the documentation. This has been fixed. Closes #2283. - CVE-2021-34434: If a plugin had granted ACL subscription access to a durable/non-clean-session client, then removed that access, the client would keep its existing subscription. This has been fixed. - Incoming QoS 2 messages that had not completed the QoS flow were not being checked for ACL access when a clean session=False client was reconnecting. This has been fixed. # Broker - Fix possible out of bounds memory reads when reading a corrupt/crafted configuration file. Unless your configuration file is writable by untrusted users this is not a risk. Closes #567213. - Fix `max_connections` option not being correctly counted. - Fix TLS certificates and TLS-PSK not being able to be configured at the same time. - Disable TLS v1.3 when using TLS-PSK, because it isn't correctly configured. - Fix `max_keepalive` not applying to MQTT v3.1.1 and v3.1 connections. These clients are now rejected if their keepalive value exceeds max_keepalive. This option allows CVE-2020-13849, which is for the MQTT v3.1.1 protocol itself rather than an implementation, to be addressed. - Fix broker not quitting if e.g. the `password_file` is specified as a directory. Closes #2241. - Fix listener mount_point not being removed on outgoing messages. Closes #2244. - Strict protocol compliance fixes, plus test suite. - Fix $share subscriptions not being recovered for durable clients that reconnect. - Update plugin configuration documentation. Closes #2286. # Client library - If a client uses TLS-PSK then force the default cipher list to use "PSK" ciphers only. This means that a client connecting to a broker configured with x509 certificates only will now fail. Prior to this, the client would connect successfully without verifying certificates, because they were not configured. - Disable TLS v1.3 when using TLS-PSK, because it isn't correctly configured. - Threaded mode is deconfigured when the mosquitto_loop_start() thread ends, which allows mosquitto_loop_start() to be called again. Closes #2242. - Fix MOSQ_OPT_SSL_CTX not being able to be set to NULL. Closes #2289. - Fix reconnecting failing when MOSQ_OPT_TLS_USE_OS_CERTS was in use, but none of capath, cafile, psk, nor MOSQ_OPT_SSL_CTX were set, and MOSQ_OPT_SSL_CTX_WITH_DEFAULTS was set to the default value of true. Closes #2288. # Apps - Fix `mosquitto_ctrl dynsec setDefaultACLAccess` command not working. # Clients - mosquitto_sub and mosquitto_rr now open stdout in binary mode on Windows so binary payloads are not modified when printing. - Document TLS certificate behaviour when using `-p 8883`. # Build - Fix installation using WITH_TLS=no. Closes #2281. - Fix builds with libressl 3.4.0. Closes #2198. - Remove some unnecessary code guards related to libressl. - Fix printf format build warning on MIPS. Closes #2271. 2.0.11 - 2021-06-08 =================== # Security - If a MQTT v5 client connects with a crafted CONNECT packet a memory leak will occur. This has been fixed. # Broker - Fix possible crash having just upgraded from 1.6 if `per_listener_settings true` is set, and a SIGHUP is sent to the broker before a client has reconnected to the broker. Closes #2167. - Fix bridge not reconnectng if the first reconnection attempt fails. Closes #2207. - Improve QoS 0 outgoing packet queueing. - Fix non-reachable bridge blocking the broker on Windows. Closes #2172. - Fix possible corruption of pollfd array on Windows when bridges were reconnecting. Closes #2173. - Fix QoS 0 messages not being queued when `queue_qos0_messages` was enabled. Closes #2224. - Fix openssl not being linked to dynamic security plugin. Closes #2277. # Clients - If sending mosquitto_sub output to a pipe, mosquitto_sub will now detect that the pipe has closed and disconnect. Closes #2164. - Fix `mosquitto_pub -l` quitting if a message publication is attempted when the broker is temporarily unavailable. Closes #2187. 2.0.10 - 2021-04-03 ================== # Security - CVE-2021-28166: If an authenticated client connected with MQTT v5 sent a malformed CONNACK message to the broker a NULL pointer dereference occurred, most likely resulting in a segfault. Affects versions 2.0.0 to 2.0.9 inclusive. # Broker - Don't over write new receive-maximum if a v5 client connects and takes over an old session. Closes #2134. - Fix CVE-2021-28166. Closes #2163. # Clients - Set `receive-maximum` to not exceed the `-C` message count in mosquitto_sub and mosquitto_rr, to avoid potentially lost messages. Closes #2134. - Fix TLS-PSK mode not working with port 8883. Closes #2152. # Client library - Fix possible socket leak. This would occur if a client was using `mosquitto_loop_start()`, then if the connection failed due to the remote server being inaccessible they called `mosquitto_loop_stop(, true)` and recreated the mosquitto object. # Build - A variety of minor build related fixes, like functions not having previous declarations. - Fix CMake cross compile builds not finding opensslconf.h. Closes #2160. - Fix build on Solaris non-sparc. Closes #2136. 2.0.9 - 2021-03-11 ================== # Security - If an empty or invalid CA file was provided to the client library for verifying the remote broker, then the initial connection would fail but subsequent connections would succeed without verifying the remote broker certificate. Closes #2130. - If an empty or invalid CA file was provided to the broker for verifying the remote broker for an outgoing bridge connection then the initial connection would fail but subsequent connections would succeed without verifying the remote broker certificate. Closes #2130. # Broker - Fix encrypted bridge connections incorrectly connecting when `bridge_cafile` is empty or invalid. Closes #2130. - Fix `tls_version` behaviour not matching documentation. It was setting the exact TLS version to use, not the minimum TLS version to use. Closes #2110. - Fix messages to `$` prefixed topics being rejected. Closes #2111. - Fix QoS 0 messages not being delivered when max_queued_bytes was configured. Closes #2123. - Fix bridge increasing backoff calculation. - Improve handling of invalid combinations of listener address and bind interface configurations. Closes #2081. - Fix `max_keepalive` option not applying to clients connecting with keepalive set to 0. Closes #2117. # Client library - Fix encrypted connections incorrectly connecting when the CA file passed to `mosquitto_tls_set()` is empty or invalid. Closes #2130. - Fix connections retrying very rapidly in some situations. # Build - Fix cmake epoll detection. 2.0.8 - 2021-02-25 ================== # Broker - Fix incorrect datatypes in `struct mosquitto_evt_tick`. This changes the size and offset of two of the members of this struct, and changes the size of the struct. This is an ABI break, but is considered to be acceptable because plugins should never be allocating their own instance of this struct, and currently none of the struct members are used for anything, so a plugin should not be accessing them. It would also be safe to read/write from the existing struct parameters. - Give compile time warning if libwebsockets compiled without external poll support. Closes #2060. - Fix memory tracking not being available on FreeBSD or macOS. Closes #2096. # Client library - Fix mosquitto_{pub|sub}_topic_check() functions not returning MOSQ_ERR_INVAL on topic == NULL. # Clients - Fix possible loss of data in `mosquitto_pub -l` when sending multiple long lines. Closes #2078. # Build - Provide a mechanism for Docker users to run a broker that doesn't use authentication, without having to provide their own configuration file. Closes #2040. 2.0.7 - 2021-02-04 ================== # Broker - Fix exporting of executable symbols on BSD when building via makefile. - Fix some minor memory leaks on exit only. - Fix possible memory leak on connect. Closes #2057. - Fix openssl engine not being able to load private key. Closes #2066. # Clients - Fix config files truncating options after the first space. Closes #2059. # Build - Fix man page building to not absolutely require xsltproc when using CMake. This now handles the case where we are building from the released tar, or building from git if xsltproc is available, or building from git if xsltproc is not available. 1.6.13 - 2021-02-04 =================== # Broker - Fix crash on Windows if loading a plugin fails. Closes #1866. - Fix DH group not being set for TLS connections, which meant ciphers using DHE couldn't be used. Closes #1925. Closes #1476. - Fix local bridges being disconnected on SIGHUP. Closes #1942. - Fix $SYS/broker/publish/messages/+ counters not being updated for QoS 1, 2 messages. Closes #1968. - Fix listener not being reassociated with client when reloading a persistence file and `per_listener_settings true` is set and the client did not set a username. Closes #1891. - Fix file logging on Windows. Closes #1880. - Fix bridge sock not being removed from sock hash on error. Closes #1897. # Client library - Fix build on Mac Big Sur. Closes #1905. - Fix DH group not being set for TLS connections, which meant ciphers using DHE couldn't be used. Closes #1925. Closes #1476. # Clients - mosquitto_sub will now quit with an error if the %U option is used on Windows, rather than just quitting. Closes #1908. - Fix config files truncating options after the first space. Closes #2059. # Apps - Perform stricter parsing of input username in mosquitto_passwd. Closes #570126 (Eclipse bugzilla). # Build - Enable epoll support in CMake builds. 2.0.6 - 2021-01-28 # Broker - Fix calculation of remaining length parameter for websockets clients that send fragmented packets. Closes #1974. # Broker - Fix potential duplicate Will messages being sent when a will delay interval has been set. - Fix message expiry interval property not being honoured in `mosquitto_broker_publish` and `mosquitto_broker_publish_copy`. - Fix websockets listeners with TLS not responding. Closes #2020. - Add notes that libsystemd-dev or similar is needed if building with systemd support. Closes #2019. - Improve logging in obscure cases when a client disconnects. Closes #2017. - Fix reloading of listeners where multiple listeners have been defined with the same port but different bind addresses. Closes #2029. - Fix `message_size_limit` not applying to the Will payload. Closes #2022. - The error topic-alias-invalid was being sent if an MQTT v5 client published a message with empty topic and topic alias set, but the topic alias hadn't already been configured on the broker. This has been fixed to send a protocol error, as per section 3.3.4 of the specification. - Note in the man pages that SIGHUP reloads TLS certificates. Closes #2037. - Fix bridges not always connecting on Windows. Closes #2043. # Apps - Allow command line arguments to override config file options in mosquitto_ctrl. Closes #2010. - mosquitto_ctrl: produce an error when requesting a new password if both attempts do not match. Closes #2011. # Build - Fix cmake builds using `WITH_CJSON=no` not working if cJSON not found. Closes #2026. # Other - The SPDX identifiers for EDL-1.0 have been changed to BSD-3-Clause as per The Eclipse legal documentation generator. The licenses are identical. 2.0.5 - 2021-01-11 ================== # Broker - Fix `auth_method` not being provided to the extended auth plugin event. Closes #1975. - Fix large packets not being completely published to slow clients. Closes #1977. - Fix bridge connection not relinquishing POLLOUT after messages are sent. Closes #1979. - Fix apparmor incorrectly denying access to /var/lib/mosquitto/mosquitto.db.new. Closes #1978. - Fix potential intermittent initial bridge connections when using poll(). - Fix `bind_interface` option. Closes #1999. - Fix invalid behaviour in dynsec plugin if a group or client is deleted before a role that was attached to the group or client is deleted. Closes #1998. - Improve logging in dynsec addGroupRole command. Closes #2005. - Improve logging in dynsec addGroupClient command. Closes #2008. # Client library - Improve documentation around the `_v5()` and non-v5 functions, e.g. `mosquitto_publish()` and `mosquitto_publish_v5(). # Build - `install` Makefile target should depend on `all`, not `mosquitto`, to ensure that man pages are always built. Closes #1989. - Fixes for lots of minor build warnings highlighted by Visual Studio. # Apps - Disallow control characters in mosquitto_passwd usernames. - Fix incorrect description in mosquitto_ctrl man page. Closes #1995. - Fix `mosquitto_ctrl dynsec getGroup` not showing roles. Closes #1997. 2.0.4 - 2020-12-22 ================== # Broker - Fix $SYS/broker/publish/messages/+ counters not being updated for QoS 1, 2 messages. Closes #1968. - mosquitto_connect_bind_async() and mosquitto_connect_bind_v5() should not reset the bind address option if called with bind_address == NULL. - Fix dynamic security configuration possibly not being reloaded on Windows only. Closes #1962. - Add more log messages for dynsec load/save error conditions. - Fix websockets connections blocking non-websockets connections on Windows. Closes #1934. # Build - Fix man pages not being built when using CMake. Closes #1969. 2.0.3 - 2020-12-17 ================== # Security - Running mosquitto_passwd with the following arguments only `mosquitto_passwd -b password_file username password` would cause the username to be used as the password. # Broker - Fix excessive CPU use on non-Linux systems when the open file limit is set high. Closes #1947. - Fix LWT not being sent on client takeover when the existing session wasn't being continued. Closes #1946. - Fix bridges possibly not completing connections when WITH_ADNS is in use. Closes #1960. - Fix QoS 0 messages not being delivered if max_queued_messages was set to 0. Closes #1956. - Fix local bridges being disconnected on SIGHUP. Closes #1942. - Fix slow initial bridge connections for WITH_ADNS=no. - Fix persistence_location not appending a '/'. # Clients - Fix mosquitto_sub being unable to terminate with Ctrl-C if a successful connection is not made. Closes #1957. # Apps - Fix `mosquitto_passwd -b` using username as password (not if `-c` is also used). Closes #1949. # Build - Fix `install` target when using WITH_CJSON=no. Closes #1938. - Fix `generic` docker build. Closes #1945. 2.0.2 - 2020-12-10 ================== # Broker - Fix build regression for WITH_WEBSOCKETS=yes on non-Linux systems. 2.0.1 - 2020-12-10 ================== # Broker - Fix websockets connections on Windows blocking subsequent connections. Closes #1934. - Fix DH group not being set for TLS connections, which meant ciphers using DHE couldn't be used. Closes #1925. Closes #1476. - Fix websockets listeners not causing the main loop not to wake up. Closes #1936. # Client library - Fix DH group not being set for TLS connections, which meant ciphers using DHE couldn't be used. Closes #1925. Closes #1476. # Apps - Fix `mosquitto_passwd -U` # Build - Fix cjson include paths. - Fix build using WITH_TLS=no when the openssl headers aren't available. - Distribute cmake/ and snap/ directories in tar. 2.0.0 - 2020-12-03 ================== # Breaking changes - When the Mosquitto broker is run without configuring any listeners it will now bind to the loopback interfaces 127.0.0.1 and/or ::1. This means that only connections from the local host will be possible. Running the broker as `mosquitto` or `mosquitto -p 1883` will bind to the loopback interface. Running the broker with a configuration file with no listeners configured will bind to the loopback interface with port 1883. Running the broker with a listener defined will bind by default to `0.0.0.0` / `::` and so will be accessible from any interface. It is still possible to bind to a specific address/interface. If the broker is run as `mosquitto -c mosquitto.conf -p 1884`, and a listener is defined in the configuration file, then the port defined on the command line will be IGNORED, and no listener configured for it. - All listeners now default to `allow_anonymous false` unless explicitly set to true in the configuration file. This means that when configuring a listener the user must either configure an authentication and access control method, or set `allow_anonymous true`. When the broker is run without a configured listener, and so binds to the loopback interface, anonymous connections are allowed. - If Mosquitto is run on as root on a unix like system, it will attempt to drop privileges as soon as the configuration file has been read. This is in contrast to the previous behaviour where elevated privileges were only dropped after listeners had been started (and hence TLS certificates loaded) and logging had been started. The change means that clients will never be able to connect to the broker when it is running as root, unless the user explicitly sets it to run as root, which is not advised. It also means that all locations that the broker needs to access must be available to the unprivileged user. In particular those people using TLS certificates from Lets Encrypt will need to do something to allow Mosquitto to access those certificates. An example deploy renewal hook script to help with this is at `misc/letsencrypt/mosquitto-copy.sh`. The user that Mosquitto will change to are the one provided in the configuration, `mosquitto`, or `nobody`, in order of availability. - The `pid_file` option will now always attempt to write a pid file, regardless of whether the `-d` argument is used when running the broker. - The `tls_version` option now defines the *minimum* TLS protocol version to be used, rather than the exact version. Closes #1258. - The `max_queued_messages` option has been increased from 100 to 1000 by default, and now also applies to QoS 0 messages, when a client is connected. - The mosquitto_sub, mosquitto_pub, and mosquitto_rr clients will now load OS provided CA certificates by default if `-L mqtts://...` is used, or if the port is set to 8883 and no other CA certificates are loaded. - Minimum support libwebsockets version is now 2.4.0 - The license has changed from "EPL-1.0 OR EDL-1.0" to "EPL-2.0 OR EDL-1.0". # Broker features - New plugin interface which is more flexible, easier to develop for and easier to extend. - New dynamic security plugin, which allows clients, groups, and roles to be defined and updated as the broker is running. - Performance improvements, particularly for higher numbers of clients. - When running as root, if dropping privileges to the "mosquitto" user fails, then try "nobody" instead. This reduces the burden on users installing Mosquitto themselves. - Add support for Unix domain socket listeners. - Add `bridge_outgoing_retain` option, to allow outgoing messages from a bridge to have the retain bit completely disabled, which is useful when bridging to e.g. Amazon or Google. - Add support for MQTT v5 bridges to handle the "retain-available" property being false. - Allow MQTT v5.0 outgoing bridges to fall back to MQTT v3.1.1 if connecting to a v3.x only broker. - DLT logging is now configurable at runtime with `log_dest dlt`. Closes #1735. - Add `mosquitto_broker_publish()` and `mosquitto_broker_publish_copy()` functions, which can be used by plugins to publish messages. - Add `mosquitto_client_protocol_version()` function which can be used by plugins to determine which version of MQTT a client has connected with. - Add `mosquitto_kick_client_by_clientid()` and `mosquitto_kick_client_by_username()` functions, which can be used by plugins to disconnect clients. - Add support for handling $CONTROL/ topics in plugins. - Add support for PBKDF2-SHA512 password hashing. - Enabling certificate based TLS encryption is now through certfile and keyfile, not capath or cafile. - Added support for controlling UNSUBSCRIBE calls in v5 plugin ACL checks. - Add "deny" acl type. Closes #1611. - The broker now sends the receive-maximum property for MQTT v5 CONNACKs. - Add the `bridge_max_packet_size` option. Closes #265. - Add the `bridge_bind_address` option. Closes #1311. - TLS certificates for the server are now reloaded on SIGHUP. - Default for max_queued_messages has been changed to 1000. - Add `ciphers_tls1.3` option, to allow setting TLS v1.3 ciphersuites. Closes #1825. - Bridges now obey MQTT v5 server-keepalive. - Add bridge support for the MQTT v5 maximum-qos property. - Log client port on new connections. Closes #1911. # Broker fixes - Send DISCONNECT with `malformed-packet` reason code on invalid PUBLISH, SUBSCRIBE, and UNSUBSCRIBE packets. - Document that X509_free() must be called after using mosquitto_client_certificate(). Closes #1842. - Fix listener not being reassociated with client when reloading a persistence file and `per_listener_settings true` is set and the client did not set a username. Closes #1891. - Fix bridge sock not being removed from sock hash on error. Closes #1897. - mosquitto_password now forbids the : character. Closes #1833. - Fix `log_timestamp_format` not applying to `log_dest topic`. Closes #1862. - Fix crash on Windows if loading a plugin fails. Closes #1866. - Fix file logging on Windows. Closes #1880. - Report an error if the config file is set to a directory. Closes #1814. - Fix bridges incorrectly setting Wills to manage remote notifications when `notifications_local_only` was set true. Closes #1902. # Client library features - Client no longer generates random client ids for v3.1.1 clients, these are now expected to be generated on the broker. This matches the behaviour for v5 clients. Closes #291. - Add support for connecting to brokers through Unix domain sockets. - Add `mosquitto_property_identifier()`, for retrieving the identifier integer for a property. - Add `mosquitto_property_identifier_to_string()` for converting a property identifier integer to the corresponding property name string. - Add `mosquitto_property_next()` to retrieve the next property in a list, for iterating over property lists. - mosquitto_pub now handles the MQTT v5 retain-available property by never setting the retain bit. - Added MOSQ_OPT_TCP_NODELAY, to allow disabling Nagle's algorithm on client sockets. Closes #1526. - Add `mosquitto_ssl_get()` to allow clients to access their SSL structure and perform additional verification. - Add MOSQ_OPT_BIND_ADDRESS to allow setting of a bind address independently of the `mosquitto_connect*()` call. - Add `MOSQ_OPT_TLS_USE_OS_CERTS` option, to instruct the client to load and trust OS provided CA certificates for use with TLS connections. # Client library fixes - Fix send quota being incorrectly reset on reconnect. Closes #1822. - Don't use logging until log mutex is initialised. Closes #1819. - Fix missing mach/mach_time.h header on OS X. Closes #1831. - Fix connect properties not being sent when the client automatically reconnects. Closes #1846. # Client features - Add timeout return code (27) for `mosquitto_sub -W ` and `mosquitto_rr -W `. Closes #275. - Add support for connecting to brokers through Unix domain sockets with the `--unix` argument. - Use cJSON library for producing JSON output, where available. Closes #1222. - Add support for outputting MQTT v5 property information to mosquitto_sub/rr JSON output. Closes #1416. - Add `--pretty` option to mosquitto_sub/rr for formatted/unformatted JSON output. - Add support for v5 property printing to mosquitto_sub/rr in non-JSON mode. Closes #1416. - Add `--nodelay` to all clients to allow them to use the MOSQ_OPT_TCP_NODELAY option. - Add `-x` to all clients to all the session-expiry-interval property to be easily set for MQTT v5 clients. - Add `--random-filter` to mosquitto_sub, to allow only a certain proportion of received messages to be printed. - mosquitto_sub %j and %J timestamps are now in a ISO 8601 compatible format. - mosquitto_sub now supports extra format specifiers for field width and precision for some parameters. - Add `--version` for all clients. - All clients now load OS provided CA certificates if used with `-L mqtts://...`, or if port is set to 8883 and no other CA certificates are used. Closes #1824. - Add the `--tls-use-os-certs` option to all clients. # Client fixes - mosquitto_sub will now exit if all subscriptions were denied. - mosquitto_pub now sends 0 length files without an error when using `-f`. - Fix description of `-e` and `-t` arguments in mosquitto_rr. Closes #1881. - mosquitto_sub will now quit with an error if the %U option is used on Windows, rather than just quitting. Closes #1908. 1.6.12 - 2020-08-19 =================== # Security - In some circumstances, Mosquitto could leak memory when handling PUBLISH messages. This is limited to incoming QoS 2 messages, and is related to the combination of the broker having persistence enabled, a clean session=false client, which was connected prior to the broker restarting, then has reconnected and has now sent messages at a sufficiently high rate that the incoming queue at the broker has filled up and hence messages are being dropped. This is more likely to have an effect where max_queued_messages is a small value. This has now been fixed. Closes #1793. # Broker - Build warning fixes when building with WITH_BRIDGE=no and WITH_TLS=no. # Clients - All clients exit with an error exit code on CONNACK failure. Closes #1778. - Don't busy loop with `mosquitto_pub -l` on a slow connection. 1.5.10 - 2020-08-19 =================== # Security - In some circumstances, Mosquitto could leak memory when handling PUBLISH messages. This is limited to incoming QoS 2 messages, and is related to the combination of the broker having persistence enabled, a clean session=false client, which was connected prior to the broker restarting, then has reconnected and has now sent messages at a sufficiently high rate that the incoming queue at the broker has filled up and hence messages are being dropped. This is more likely to have an effect where max_queued_messages is a small value. This has now been fixed. Closes #1793. 1.6.11 - 2020-08-11 =================== # Security - On Windows the Mosquitto service was being installed without appropriate path quoting, this has been fixed. # Broker - Fix usage message only mentioning v3.1.1. Closes #1713. - Fix broker refusing to start if only websockets listeners were defined. Closes #1740. - Change systemd unit files to create /var/log/mosquitto before starting. Closes #821. - Don't quit with an error if opening the log file isn't possible. Closes #821. - Fix bridge topic remapping when using "" as the topic. Closes #1749. - Fix messages being queued for disconnected bridges when clean start was set to true. Closes #1729. - Fix `autosave_interval` not being triggered by messages being delivered. Closes #1726. - Fix websockets clients sometimes not being disconnected promptly. Closes #1718. - Fix "slow" file based logging by switching to line based buffering. Closes #1689. Closes #1741. - Log protocol error message where appropriate from a bad UNSUBSCRIBE, rather than the generic "socket error". - Don't try to start DLT logging if DLT unavailable, to avoid a long delay when shutting down the broker. Closes #1735. - Fix potential memory leaks. Closes #1773. Closes #1774. - Fix clients not receiving messages after a previous client with the same client ID and positive will delay interval quit. Closes #1752. - Fix overly broad HAVE_PTHREAD_CANCEL compile guard. Closes #1547. # Client library - Improved documentation around connect callback return codes. Close #1730. - Fix `mosquitto_publish*()` no longer returning `MOSQ_ERR_NO_CONN` when not connected. Closes #1725. - `mosquitto_loop_start()` now sets a thread name on Linux, FreeBSD, NetBSD, and OpenBSD. Closes #1777. - Fix `mosquitto_loop_stop()` not stopping on Windows. Closes #1748. Closes #117. 1.6.10 - 2020-05-25 =================== # Broker - Report invalid bridge prefix+pattern combinations at config parsing time rather than letting the bridge fail later. Issue #1635. - Fix `mosquitto_passwd -b` not updating passwords for existing users correctly. Creating a new user with `-b` worked without problem. Closes #1664. - Fix memory leak when connecting clients rejected. - Don't disconnect clients that are already disconnected. This prevents the session expiry being extended on SIGHUP. Closes #1521. - Fix support for openssl 3.0. - Fix check when loading persistence file of a different version than the native version. Closes #1684. - Fix possible assert crash associated with bridge reconnecting when compiled without epoll support. Closes #1700. # Client library - Don't treat an unexpected PUBACK, PUBREL, or PUBCOMP as a fatal error. Issue #1629. - Fix support for openssl 3.0. - Fix memory leaks from multiple calls to `mosquitto_lib_init()`/`mosquitto_lib_cleanup()`. Closes #1691. - Fix documentation on return code of `mosquitto_lib_init()` for Windows. Closes #1690. # Clients - Fix mosquitto_sub %j or %J not working on Windows. Closes #1674. # Build - Various fixes for building with user not being freed on exit. Closes #1564. - Fix trailing whitespace not being trimmed on acl users. Closes #1539. - Fix `bind_interface` not working for the default listener. Closes #1533. - Improve password file parsing in the broker and mosqitto_passwd. Closes #1584. - Print OpenSSL errors in more situations, like when loading certificates fails. Closes #1552. - Fix `mosquitto_client_protocol() returning incorrect values. # Client library - Set minimum keepalive argument to `mosquitto_connect*()` to be 5 seconds. Closes #1550. - Fix `mosquitto_topic_matches_sub()` not returning MOSQ_ERR_INVAL if the topic contains a wildcard. Closes #1589. # Clients - Fix `--remove-retained` not obeying the `-T` option for filtering out topics. Closes #1585. - Default behaviour for v5 clients using `-c` is now to use infinite length sessions, as with v3 clients. Closes #1546. 1.6.8 - 20191128 ================ # Broker - Various fixes for `allow_zero_length_clientid` config, where this option was not being set correctly. Closes #1429. - Fix incorrect memory tracking causing problems with memory_limit option. Closes #1437. - Fix subscription topics being limited to 200 characters instead of 200 hierarchy levels. Closes #1441. - Only a single CRL could be loaded at once. This has been fixed. Closes #1442. - Fix problems with reloading config when `per_listener_settings` was true. Closes #1459. - Fix retained messages with an expiry interval not being expired after being restored from persistence. Closes #1464. - Fix messages with an expiry interval being sent without an expiry interval property just before they were expired. Closes #1464. - Fix TLS Websockets clients not receiving messages after taking over a previous connection. Closes #1489. - Fix MQTT 3.1.1 clients using clean session false, or MQTT 5.0 clients using session-expiry-interval set to infinity never expiring, even when the global `persistent_client_expiration` option was set. Closes #1494. # Client library - Fix publish properties not being passed to on_message_v5 callback for QoS 2 messages. Closes #1432. - Fix documentation issues in mosquitto.h. Closes #1478. - Document `mosquitto_connect_srv()`. Closes #1499. # Clients - Fix duplicate cfg definition in rr_client. Closes #1453. - Fix `mosquitto_pub -l` hang when stdin stream ends. Closes #1448. - Fix `mosquitto_pub -l` not sending the final line of stdin if it does not end with a new line. Closes #1473. - Make documentation for `mosquitto_pub -l` match reality - blank lines are sent as empty messages. Closes #1474. - Free memory in `mosquitto_sub` when quitting without having made a successful connection. Closes #1513. # Build - Added `CLIENT_STATIC_LDADD` to makefile builds to allow more libraries to be linked when compiling the clients with a static libmosquitto, as required for e.g. openssl on some systems. # Installer - Fix mosquitto_rr.exe not being included in Windows installers. Closes #1463. 1.6.7 - 20190925 ================ # Broker - Add workaround for working with libwebsockets 3.2.0. - Fix potential crash when reloading config. Closes #1424, #1425. # Client library - Don't use `/` in autogenerated client ids, to avoid confusing with topics. - Fix `mosquitto_max_inflight_messages_set()` and `mosquitto_int_option(..., MOSQ_OPT_*_MAX, ...)` behaviour. Closes #1417. - Fix regression on use of `mosquitto_connect_async()` not working. Closes #1415 and #1422. # Clients - mosquitto_sub: Fix `-E` incorrectly not working unless `-d` was also specified. Closes #1418. - Updated documentation around automatic client ids. 1.6.6 - 20190917 ================ # Security - Restrict topic hierarchy to 200 levels to prevent possible stack overflow. Closes #1412. # Broker - Restrict topic hierarchy to 200 levels to prevent possible stack overflow. Closes #1412. - mosquitto_passwd now returns 1 when attempting to update a user that does not exist. Closes #1414. 1.6.5 - 20190912 ================ # Broker - Fix v5 DISCONNECT packets with remaining length == 2 being treated as a protocol error. Closes #1367. - Fix support for libwebsockets 3.x. - Fix slow websockets performance when sending large messages. Closes #1390. - Fix bridges potentially not connecting on Windows. Closes #478. - Fix clients authorised using `use_identity_as_username` or `use_subject_as_username` being disconnected on SIGHUP. Closes #1402. - Improve error messages in some situations when clients disconnect. Reduces the number of "Socket error on client X, disconnecting" messages. - Fix Will for v5 clients not being sent if will delay interval was greater than the session expiry interval. Closes #1401. - Fix CRL file not being reloaded on HUP. Closes #35. - Fix repeated "Error in poll" messages on Windows when only websockets listeners are defined. Closes #1391. # Client library - Fix reconnect backoff for the situation where connections are dropped rather than refused. Closes #737. - Fix missing locks on `mosq->state`. Closes #1374. # Documentation - Improve details on global/per listener options in the mosquitto.conf man page. Closes #274. - Clarify behaviour when clients exceed the `message_size_limit`. Closes #448. - Improve documentation for `max_inflight_bytes`, `max_inflight_messages`, and `max_queued_messages`. # Build - Fix missing function warnings on NetBSD. - Fix WITH_STATIC_LIBRARIES using CMake on Windows. Closes #1369. - Guard ssize_t definition on Windows. Closes #522. 1.6.4 - 20190801 ================ # Broker - Fix persistent clients being incorrectly expired on Raspberry Pis. Closes #1272. - Windows: Allow other applications access to the log file when running. Closes #515. - Fix incoming QoS 2 messages being blocked when `max_inflight_messages` was set to 1. Closes #1332. - Fix incoming messages not being removed for a client if the topic being published to does not have any subscribers. Closes #1322. # Client library - Fix MQTT v5 subscription options being incorrectly set for MQTT v3 subscriptions. Closes #1353. - Make behaviour of `mosquitto_connect_async()` consistent with `mosquitto_connect()` when connecting to a non-existent server. Closes #1345. - `mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, ...)` was incorrectly returning `MOSQ_ERR_INVAL` with valid input. This has been fixed. Closes #1360. - on_connect callback is now called with the correct v5 reason code if a v5 client connects to a v3.x broker and is sent a CONNACK with the "unacceptable protocol version" connack reason code. - Fix memory leak when setting v5 properties in mosquitto_connect_v5(). - Fix properties not being sent on QoS>0 PUBLISH messages. # Clients - mosquitto_pub: fix error codes not being returned when mosquitto_pub exits. Closes #1354. - All clients: improve error messages when connecting to a v3.x broker when in v5 mode. Closes #1344. # Other - Various documentation fixes. 1.6.3 - 20190618 ================ # Broker - Fix detection of incoming v3.1/v3.1.1 bridges. Closes #1263. - Fix default max_topic_alias listener config not being copied to the in-use listener when compiled without TLS support. - Fix random number generation if compiling using `WITH_TLS=no` and on Linux with glibc >= 2.25. Without this fix, no random numbers would be generated for e.g. on broker client id generation, and so clients connecting expecting this feature would be unable to connect. - Fix compilation problem related to `getrandom()` on non-glibc systems. - Fix Will message for a persistent client incorrectly being sent when the client reconnects after a clean disconnect. Closes #1273. - Fix Will message for a persistent client not being sent on disconnect. Closes #1273. - Improve documentation around the upgrading of persistence files. Closes #1276. - Add 'extern "C"' on mosquitto_broker.h and mosquitto_plugin.h for C++ plugin writing. Closes #1290. - Fix persistent Websockets clients not receiving messages after they reconnect, having sent DISCONNECT on a previous session. Closes #1227. - Disable TLS renegotiation. Client initiated renegotiation is considered to be a potential attack vector against servers. Closes #1257. - Fix incorrect shared subscription topic '$shared'. - Fix zero length client ids being rejected for MQTT v5 clients with clean start set to true. - Fix MQTT v5 overlapping subscription behaviour. Clients now receive message from all matching subscriptions rather than the first one encountered, which ensures the maximum QoS requirement is met. - Fix incoming/outgoing quota problems for QoS>0. - Remove obsolete `store_clean_interval` from documentation. - Fix v4 authentication plugin never calling psk_key_get. # Client library - Fix typo causing build error on Windows when building without TLS support. Closes #1264. # Clients - Fix -L url parsing when `/topic` part is missing. - Stop some error messages being printed even when `--quiet` was used. Closes #1284. - Fix mosquitto_pub exiting with error code 0 when an error occurred. Closes #1285. - Fix mosquitto_pub not using the `-c` option. Closes #1273. - Fix MQTT v5 clients not being able to specify a password without a username. Closes #1274. - Fix `mosquitto_pub -l` not handling network failures. Closes #1152. - Fix `mosquitto_pub -l` not handling zero length input. Closes #1302. - Fix double free on exit in mosquitto_pub. Closes #1280. # Documentation - Remove references to Python binding and C++ wrapper in libmosquitto man page. Closes #1266. # Build - CLIENT_LDFLAGS now uses LDFLAGS. Closes #1294. 1.6.2 - 20190430 ================ # Broker - Fix memory access after free, leading to possible crash, when v5 client with Will message disconnects, where the Will message has as its first property one of `content-type`, `correlation-data`, `payload-format-indicator`, or `response-topic`. Closes #1244. - Fix build for WITH_TLS=no. Closes #1250. - Fix Will message not allowing user-property properties. - Fix broker originated messages (e.g. $SYS/broker/version) not being published when `check_retain_source` set to true. Closes #1245. - Fix $SYS/broker/version being incorrectly expired after 60 seconds. Closes #1245. # Library - Fix crash after client has been unable to connect to a broker. This occurs when the client is exiting and is part of the final library cleanup routine. Closes #1246. # Clients - Fix -L url parsing. Closes #1248. 1.6.1 - 20190426 ================ # Broker - Document `memory_limit` option. # Clients - Fix compilation on non glibc systems due to missing sys/time.h header. # Build - Add `make check` target and document testing procedure. Closes #1230. - Document bundled dependencies and how to disable. Closes #1231. - Split CFLAGS and CPPFLAGS, and LDFLAGS and LDADD/LIBADD. - test/unit now respects CPPFLAGS and LDFLAGS. Closes #1232. - Don't call ldconfig in CMake scripts. Closes #1048. - Use CMAKE_INSTALL_* variables when installing in CMake. Closes #1049. 1.6 - 20190417 ============== # Broker features - Add support for MQTT v5 - Add support for OCSP stapling. - Add support for ALPN on bridge TLS connections. Closes #924. - Add support for Automotive DLT logging. - Add TLS Engine support. - Persistence file read/write performance improvements. - General performance improvements. - Add max_keepalive option, to allow a maximum keepalive value to be set for MQTT v5 clients only. - Add `bind_interface` option which allows a listener to be bound to a specific network interface, in a similar fashion to the `bind_address` option. Linux only. - Add improved bridge restart interval based on Decorrelated Jitter. - Add `dhparamfile` option, to allow DH parameters to be loaded for Ephemeral DH support - Disallow writing to $ topics where appropriate. - Fix mosquitto_passwd crashing on corrupt password file. Closes #1207. - Add explicit support for TLS v1.3. - Drop support for TLS v1.0. - Improved general support for broker generated client ids. Removed libuuid dependency. - auto_id_prefix now defaults to 'auto-'. - QoS 1 and 2 flow control improvements. # Client library features - Add support for MQTT v5 - Add mosquitto_subscribe_multiple() for sending subscriptions to multiple topics in one command. - Add TLS Engine support. - Add explicit support for TLS v1.3. - Drop support for TLS v1.0. - QoS 1 and 2 flow control improvements. # Client features - Add support for MQTT v5 - Add mosquitto_rr client, which can be used for "request-response" messaging, by sending a request message and awaiting a response. - Add TLS Engine support. - Add support for ALPN on TLS connections. Closes #924. - Add -D option for all clients to specify MQTT v5 properties. - Add -E to mosquitto_sub, which causes it to exit immediately after having its subscriptions acknowledged. Use with -c to create a durable client session without requiring a message to be received. - Add --remove-retained to mosquitto_sub, which can be used to clear retained messages on a broker. - Add --repeat and --repeat-delay to mosquitto_pub, which can be used to repeat single message publishes at a regular interval. - -V now accepts `5, `311`, `31`, as well as `mqttv5` etc. - Add explicit support for TLS v1.3. - Drop support for TLS v1.0. # Broker fixes - Improve error reporting when creating listeners. - Fix build on SmartOS due to missing IPV6_V6ONLY. Closes #1212. Client library fixes - Add missing `mosquitto_userdata()` function. # Client fixes - mosquitto_pub wouldn't always publish all messages when using `-l` and QoS>0. This has been fixed. - mosquitto_sub was incorrectly encoding special characters when using %j output format. Closes #1220. 1.5.8 - 20190228 ================ # Broker - Fix clients being disconnected when ACLs are in use. This only affects the case where a client connects using a username, and the anonymous ACL list is defined but specific user ACLs are not defined. Closes #1162. - Make error messages for missing config file clearer. - Fix some Coverity Scan reported errors that could occur when the broker was already failing to start. - Fix broken mosquitto_passwd on FreeBSD. Closes #1032. - Fix delayed bridge local subscriptions causing missing messages. Closes #1174. # Library - Use higher resolution timer for random initialisation of client id generation. Closes #1177. - Fix some Coverity Scan reported errors that could occur when the library was already quitting. 1.5.7 - 20190213 ================ # Broker - Fix build failure when using WITH_ADNS=yes - Ensure that an error occurs if `per_listener_settings true` is given after other security options. Closes #1149. - Fix include_dir not sorting config files before loading. This was partially fixed in 1.5 previously. - Improve documentation around the `include_dir` option. Closes #1154. - Fix case where old unreferenced msg_store messages were being saved to the persistence file, bloating its size unnecessarily. Closes #389. # Library - Fix `mosquitto_topic_matches_sub()` not returning MOSQ_ERR_INVAL for invalid subscriptions like `topic/#abc`. This only affects the return value, not the match/no match result, which was already correct. # Build - Don't require C99 compiler. - Add rewritten build test script and remove some build warnings. 1.5.6 - 20190206 ================ # Security - CVE-2018-12551: If Mosquitto is configured to use a password file for authentication, any malformed data in the password file will be treated as valid. This typically means that the malformed data becomes a username and no password. If this occurs, clients can circumvent authentication and get access to the broker by using the malformed username. In particular, a blank line will be treated as a valid empty username. Other security measures are unaffected. Users who have only used the mosquitto_passwd utility to create and modify their password files are unaffected by this vulnerability. Affects version 1.0 to 1.5.5 inclusive. - CVE-2018-12550: If an ACL file is empty, or has only blank lines or comments, then mosquitto treats the ACL file as not being defined, which means that no topic access is denied. Although denying access to all topics is not a useful configuration, this behaviour is unexpected and could lead to access being incorrectly granted in some circumstances. This is now fixed. Affects versions 1.0 to 1.5.5 inclusive. - CVE-2018-12546. If a client publishes a retained message to a topic that they have access to, and then their access to that topic is revoked, the retained message will still be delivered to future subscribers. This behaviour may be undesirable in some applications, so a configuration option `check_retain_source` has been introduced to enforce checking of the retained message source on publish. # Broker - Fixed comment handling for config options that have optional arguments. - Improved documentation around bridge topic remapping. - Handle mismatched handshakes (e.g. QoS1 PUBLISH with QoS2 reply) properly. - Fix spaces not being allowed in the bridge remote_username option. Closes #1131. - Allow broker to always restart on Windows when using `log_dest file`. Closes #1080. - Fix Will not being sent for Websockets clients. Closes #1143. - Windows: Fix possible crash when client disconnects. Closes #1137. - Fixed durable clients being unable to receive messages when offline, when per_listener_settings was set to true. Closes #1081. - Add log message for the case where a client is disconnected for sending a topic with invalid UTF-8. Closes #1144. # Library - Fix TLS connections not working over SOCKS. - Don't clear SSL context when TLS connection is closed, meaning if a user provided an external SSL_CTX they have less chance of leaking references. # Build - Fix comparison of boolean values in CMake build. Closes #1101. - Fix compilation when openssl deprecated APIs are not available. Closes #1094. - Man pages can now be built on any system. Closes #1139. 1.5.5 - 20181211 ================ # Security - If `per_listener_settings` is set to true, then the `acl_file` setting was ignored for the "default listener" only. This has been fixed. This does not affect any listeners defined with the `listener` option. Closes #1073. This is now tracked as CVE-2018-20145. # Broker - Add `socket_domain` option to allow listeners to disable IPv6 support. This is required to work around a problem in libwebsockets that means sockets only listen on IPv6 by default if IPv6 support is compiled in. Closes #1004. - When using ADNS, don't ask for all network protocols when connecting, because this can lead to confusing "Protocol not supported" errors if the network is down. Closes #1062. - Fix outgoing retained messages not being sent by bridges on initial connection. Closes #1040. - Don't reload auth_opt_ options on reload, to match the behaviour of the other plugin options. Closes #1068. - Print message on error when installing/uninstalling as a Windows service. - All non-error connect/disconnect messages are controlled by the `connection_messages` option. Closes #772. Closes #613. Closes #537. # Library - Fix reconnect delay backoff behaviour. Closes #1027. - Don't call on_disconnect() twice if keepalive tests fail. Closes #1067. # Client - Always print leading zeros in mosquitto_sub when output format is hex. Closes #1066. # Build - Fix building where TLS-PSK is not available. Closes #68. 1.5.4 - 20181108 ================ # Security - When using a TLS enabled websockets listener with "require_certificate" enabled, the mosquitto broker does not correctly verify client certificates. This is now fixed. All other security measures operate as expected, and in particular non-websockets listeners are not affected by this. Closes #996. # Broker - Process all pending messages even when a client has disconnected. This means a client that send a PUBLISH then DISCONNECT quickly, then disconnects will have its DISCONNECT message processed properly and so no Will will be sent. Closes #7. - $SYS/broker/clients/disconnected should never be negative. Closes #287. - Give better error message if a client sends a password without a username. Closes #1015. - Fix bridge not honoring restart_timeout. Closes #1019. - Don't disconnect a client if an auth plugin denies access to SUBSCRIBE. Closes #1016. # Library - Fix memory leak that occurred if mosquitto_reconnect() was used when TLS errors were present. Closes #592. - Fix TLS connections when using an external event loop with mosquitto_loop_read() and mosquitto_write(). Closes #990. # Build - Fix clients not being compiled with threading support when using CMake. Closes #983. - Header fixes for FreeBSD. Closes #977. - Use _GNU_SOURCE to fix build errors in websockets and getaddrinfo usage. Closes #862 and #933. - Fix builds on QNX 7.0.0. Closes #1018. 1.5.3 - 20180925 ================ # Security - Fix CVE-2018-12543. If a message is sent to Mosquitto with a topic that begins with $, but is not $SYS, then an assert that should be unreachable is triggered and Mosquitto will exit. # Broker - Elevate log level to warning for situation when socket limit is hit. - Remove requirement to use `user root` in snap package config files. - Fix retained messages not sent by bridges on outgoing topics at the first connection. Closes #701. - Documentation fixes. Closes #520, #600. - Fix duplicate clients being added to by_id hash before the old client was removed. Closes #645. - Fix Windows version not starting if include_dir did not contain any files. Closes #566. - When an authentication plugin denied access to a SUBSCRIBE, the client would be disconnected incorrectly. This has been fixed. Closes #1016. # Build - Various fixes to ease building. 1.5.2 - 20180919 ================ # Broker - Fix build when using WITH_ADNS=yes. - Fix incorrect call to setsockopt() for TCP_NODELAY. Closes #941. - Fix excessive CPU usage when the number of sockets exceeds the system limit. Closes #948. - Fix for bridge connections when using WITH_ADNS=yes. - Fix round_robin false behaviour. Closes #481. - Fix segfault on HUP when bridges and security options are configured. Closes #965. # Library - Fix situation where username and password is used with SOCKS5 proxy. Closes #927. - Fix SOCKS5 behaviour when passing IP addresses. Closes #927. # Build - Make it easier to build without bundled uthash.h using "WITH_BUNDLED_DEPS=no". - Fix build with OPENSSL_NO_ENGINE. Closes #932. 1.5.1 - 20180816 ================ # Broker - Fix plugin cleanup function not being called on exit of the broker. Closes #900. - Print more OpenSSL errors when loading certificates/keys fail. - Use AF_UNSPEC etc. instead of PF_UNSPEC to comply with POSIX. Closes #863. - Remove use of AI_ADDRCONFIG, which means the broker can be used on systems where only the loopback interface is defined. Closes #869, Closes #901. - Fix IPv6 addresses not being able to be used as bridge addresses. Closes #886. - All clients now time out if they exceed their keepalive*1.5, rather than just reach it. This was inconsistent in two places. - Fix segfault on startup if bridge CA certificates could not be read. Closes #851. - Fix problem opening listeners on Pi caused by unsigned char being default. Found via #849. - ACL patterns that do not contain either %c or %u now produce a warning in the log. Closes #209. - Fix bridge publishing failing when per_listener_settings was true. Closes #860. - Fix `use_identity_as_username true` not working. Closes #833. - Fix UNSUBACK messages not being logged. Closes #903. - Fix possible endian issue when reading the `memory_limit` option. - Fix building for libwebsockets < 1.6. - Fix accessor functions for username and client id when used in plugin auth check. # Library - Fix some places where return codes were incorrect, including to the on_disconnect() callback. This has resulted in two new error codes, MOSQ_ERR_KEEPALIVE and MOSQ_ERR_LOOKUP. - Fix connection problems when mosquitto_loop_start() was called before mosquitto_connect_async(). Closes #848. # Clients - When compiled using WITH_TLS=no, the default port was incorrectly being set to -1. This has been fixed. - Fix compiling on Mac OS X <10.12. Closes #813 and #240. # Build - Fixes for building on NetBSD. Closes #258. - Fixes for building on FreeBSD. - Add support for compiling with static libwebsockets library. 1.5 - 20180502 ============== # Security - Fix memory leak that could be caused by a malicious CONNECT packet. CVE-2017-7654. Closes #533493 (on Eclipse bugtracker) # Broker features - Add per_listener_settings to allow authentication and access control to be per listener. - Add limited support for reloading listener settings. This allows settings for an already defined listener to be reloaded, but port numbers must not be changed. - Add ability to deny access to SUBSCRIBE messages as well as the current read/write accesses. Currently for auth plugins only. - Reduce calls to malloc through the use of UHPA. - Outgoing messages with QoS>1 are no longer retried after a timeout period. Messages will be retried when a client reconnects. This change in behaviour can be justified by considering when the timeout may have occurred. * If a connection is unreliable and has dropped, but without one end noticing, the messages will be retried on reconnection. Sending additional PUBLISH or PUBREL would not have changed anything. * If a client is overloaded/unable to respond/has a slow connection then sending additional PUBLISH or PUBREL would not help the client catch up. Once the backlog has cleared the client will respond. If it is not able to catch up, sending additional duplicates would not help either. - Add use_subject_as_username option for certificate based client authentication to use the entire certificate subject as a username, rather than just the CN. Closes #469467. - Change sys tree printing output. This format shouldn't be relied upon and may change at any time. Closes #470246. - Minimum supported libwebsockets version is now 1.3. - Add systemd startup notification and services. Closes #471053. - Reduce unnecessary malloc and memcpy when receiving a message and storing it. Closes #470258. - Support for Windows XP has been dropped. - Bridge connections now default to using MQTT v3.1.1. - mosquitto_db_dump tool can now output some stats on clients. - Perform utf-8 validation on incoming will, subscription and unsubscription topics. - new $SYS/broker/store/messages/count (deprecates $SYS/broker/messages/stored) - new $SYS/broker/store/messages/bytes - max_queued_bytes feature to limit queues by real size rather than than just message count. Closes Eclipse #452919 or Github #100 - Add support for bridges to be configured to only send notifications to the local broker. - Add set_tcp_nodelay option to allow Nagle's algorithm to be disabled on client sockets. Closes #433. - The behaviour of allow_anonymous has changed. In the old behaviour, the default if not set was to allow anonymous access. The new behaviour is to default is to allow anonymous access unless another security option is set. For example, if password_file is set and allow_anonymous is not set, then anonymous access will be denied. It is still possible to allow anonymous access by setting it explicitly. # Broker fixes - Fix UNSUBSCRIBE with no topic is accepted on MQTT 3.1.1. Closes #665. - Produce an error if two bridges share the same local_clientid. - Miscellaneous fixes on Windows. - queue_qos0_messages was not observing max_queued_** limits - When using the include_dir configuration option sort the files alphabetically before loading them. Closes #17. - IPv6 is no longer disabled for websockets listeners. - Remove all build timestamp information including $SYS/broker/timestamp. Close #651. - Correctly handle incoming strings that contain a NULL byte. Closes #693. - Use constant time memcmp for password comparisons. - Fix incorrect PSK key being used if it had leading zeroes. - Fix memory leak if a client provided a username/password for a listener with use_identity_as_username configured. - Fix use_identity_as_username not working on websockets clients. - Don't crash if an auth plugin returns MOSQ_ERR_AUTH for a username check on a websockets client. Closes #490. - Fix 08-ssl-bridge.py test when using async dns lookups. Closes #507. - Lines in the config file are no longer limited to 1024 characters long. Closes #652. - Fix $SYS counters of messages and bytes sent when message is sent over a Websockets. Closes #250. - Fix upgrade_outgoing_qos for retained message. Closes #534. - Fix CONNACK message not being sent for unauthorised connect on websockets. Closes #8. - Maximum connections on Windows increased to 2048. - When a client with an in-use client-id connects, if the old client has a will, send the will message. Closes #26. - Fix parsing of configuration options that end with a space. Closes #804. # Client library features - Outgoing messages with QoS>1 are no longer retried after a timeout period. Messages will be retried when a client reconnects. - DNS-SRV support is now disabled by default. - Add mosquitto_subscribe_simple() This is a helper function to make retrieving messages from a broker very straightforward. Examples of its use are in examples/subscribe_simple. - Add mosquitto_subscribe_callback() This is a helper function to make processing messages from a broker very straightforward. An example of its use is in examples/subscribe_simple. - Connections now default to using MQTT v3.1.1. - Add mosquitto_validate_utf8() to check whether a string is valid UTF-8 according to the UTF-8 spec and to the additional restrictions imposed by the MQTT spec. - Topic inputs are checked for UTF-8 validity. - Add mosquitto_userdata function to allow retrieving the client userdata member variable. Closes #111. - Add mosquitto_pub_topic_check2(), mosquitto_sub_topic_check2(), and mosquitto_topic_matches_sub2() which are identical to the similarly named functions but also take length arguments. - Add mosquitto_connect_with_flags_callback_set(), which allows a second connect callback to be used which also exposes the connect flags parameter. Closes #738 and #128. - Add MOSQ_OPT_SSL_CTX option to allow a user specified SSL_CTX to be used instead of the one generated by libmosquitto. This allows greater control over what options can be set. Closes #715. - Add MOSQ_OPT_SSL_CTX_WITH_DEFAULTS to work with MOSQ_OPT_SSL_CTX and have the default libmosquitto SSL_CTX configuration applied to the user provided SSL_CTX. Closes #567. # Client library fixes - Fix incorrect PSK key being used if it had leading zeroes. - Initialise "result" variable as soon as possible in mosquitto_topic_matches_sub. Closes #654. - No need to close socket again if setting non-blocking failed. Closes #649. - Fix mosquitto_topic_matches_sub() not correctly matching foo/bar against foo/+/#. Closes #670. - SNI host support added. # Client features - Add -F to mosquitto_sub to allow the user to choose the output format. - Add -U to mosquitto_sub for unsubscribing from topics. - Add -c (clean session) to mosquitto_pub. - Add --retained-only to mosquitto_sub to exit after receiving all retained messages. - Add -W to allow mosquitto_sub to stop processing incoming messages after a timeout. - Connections now default to using MQTT v3.1.1. - Default to using port 8883 when using TLS. - mosquitto_sub doesn't continue to keep connecting if CONNACK tells it the connection was refused. # Client fixes - Correctly handle empty files with "mosquitto_pub -l". Closes #676. # Build - Add WITH_STRIP option (defaulting to "no") that when set to "yes" will strip executables and shared libraries when installing. - Add WITH_STATIC_LIBRARIES (defaulting to "no") that when set to "yes" will build and install static versions of the client libraries. - Don't run TLS-PSK tests if TLS-PSK disabled at compile time. Closes #636. - Support for openssl versions 1.0.0 and 1.0.1 has been removed as these are no longer supported by openssl. # Documentation - Replace mentions of deprecated 'c_rehash' with 'openssl rehash'. 1.4.15 - 20180228 ================= # Security - Fix CVE-2017-7652. If a SIGHUP is sent to the broker when there are no more file descriptors, then opening the configuration file will fail and security settings will be set back to their default values. - Fix CVE-2017-7651. Unauthenticated clients can cause excessive memory use by setting "remaining length" to be a large value. This is now mitigated by limiting the size of remaining length to valid values. A "memory_limit" configuration option has also been added to allow the overall memory used by the broker to be limited. # Broker - Use constant time memcmp for password comparisons. - Fix incorrect PSK key being used if it had leading zeroes. - Fix memory leak if a client provided a username/password for a listener with use_identity_as_username configured. - Fix use_identity_as_username not working on websockets clients. - Don't crash if an auth plugin returns MOSQ_ERR_AUTH for a username check on a websockets client. Closes #490. - Fix 08-ssl-bridge.py test when using async dns lookups. Closes #507. - Lines in the config file are no longer limited to 1024 characters long. Closes #652. - Fix $SYS counters of messages and bytes sent when message is sent over a Websockets. Closes #250. - Fix upgrade_outgoing_qos for retained message. Closes #534. - Fix CONNACK message not being sent for unauthorised connect on websockets. Closes #8. # Client library - Fix incorrect PSK key being used if it had leading zeroes. - Initialise "result" variable as soon as possible in mosquitto_topic_matches_sub. Closes #654. - No need to close socket again if setting non-blocking failed. Closes #649. - Fix mosquitto_topic_matches_sub() not correctly matching foo/bar against foo/+/#. Closes #670. # Clients - Correctly handle empty files with "mosquitto_pub -l". Closes #676. # Build - Don't run TLS-PSK tests if TLS-PSK disabled at compile time. Closes #636. 1.4.14 - 20170710 ================= # Broker - Fix regression from 1.4.13 where persistence data was not being saved. 1.4.13 - 20170627 ================= # Security - Fix CVE-2017-9868. The persistence file was readable by all local users, potentially allowing sensitive information to be leaked. This can also be fixed administratively, by restricting access to the directory in which the persistence file is stored. # Broker - Fix for poor websockets performance. - Fix lazy bridges not timing out for idle_timeout. Closes #417. - Fix problems with large retained messages over websockets. Closes #427. - Set persistence file to only be readable by owner, except on Windows. Closes #468. - Fix CONNECT check for reserved=0, as per MQTT v3.1.1 check MQTT-3.1.2-3. - When the broker stop, wills for any connected clients are now "sent". Closes #477. - Auth plugins can be configured to disable the check for +# in usernames/client ids with the auth_plugin_deny_special_chars option. Partially closes #462. - Restrictions for CVE-2017-7650 have been relaxed - '/' is allowed in usernames/client ids. Remainder of fix for #462. # Clients - Don't use / in auto-generated client ids. 1.4.12 - 20170528 ================= # Security - Fix CVE-2017-7650, which allows clients with username or client id set to '#' or '+' to bypass pattern based ACLs or third party plugins. The fix denies message sending or receiving of messages for clients with a '#' or '+' in their username or client id and if the message is subject to a pattern ACL check or plugin check. Patches for other versions are available at https://mosquitto.org/files/cve/2017-7650/ # Broker - Fix mosquitto.db from becoming corrupted due to client messages being persisted with no stored message. Closes #424. - Fix bridge not restarting properly. Closes #428. - Fix uninitialised memory in gets_quiet on Windows. Closes #426. - Fix building with WITH_ADNS=no for systems that don't use glibc. Closes #415. - Fixes to readme.md. - Fix deprecation warning for OpenSSL 1.1. PR #416. - Don't segfault on duplicate bridge names. Closes #446. - Fix CVE-2017-7650. 1.4.11 - 20170220 ================= # Broker - Fix crash when "lazy" type bridge attempts to reconnect. Closes #259. - maximum_connections now applies to websockets listeners. Closes #271. - Allow bridges to use TLS with IPv6. - Don't error on zero length persistence files. Closes #316. - For http only websockets clients, close files served over http in all cases when the client disconnects. Closes #354. - Fix error message when websockets http_dir directory does not exist. - Improve password utility error message. Closes #379. # Clients - Use of --ciphers no longer requires you to also pass --tls-version. Closes #380. # Client library - Clients can now use TLS with IPv6. - Fix potential socket leakage when reconnecting. Closes #304. - Fix potential negative timeout being passed to pselect. Closes #329. 1.4.10 - 20160816 ================= # Broker - Fix TLS operation with websockets listeners and libwebsockts 2.x. Closes #186. - Don't disconnect client on HUP before reading the pending data. Closes #7. - Fix some $SYS messages being incorrectly persisted. Closes #191. - Support OpenSSL 1.1.0. - Call fsync after persisting data to ensure it is correctly written. Closes #189. - Fix persistence saving of subscription QoS on big-endian machines. - Fix will retained flag handling on Windows. Closes #222. - Broker now displays an error if it is unable to open the log file. Closes #234. # Client library - Support OpenSSL 1.1.0. - Fixed the C++ library not allowing SOCKS support to be used. Closes #198. - Fix memory leak when verifying a server certificate with a subjectAltName section. Closes #237. # Build - Don't attempt to install docs when WITH_DOCS=no. Closes #184. 1.4.9 - 20160603 ================ # Broker - Ensure websockets clients that previously connected with clean session set to false have their queued messages delivered immediately on reconnecting. Closes #476314. - Reconnecting client with clean session set to false doesn't start with mid=1 again. - Will topic isn't truncated by one byte when using a mount_point any more. - Network errors are printed correctly on Windows. - Fix incorrect $SYS heap memory reporting when using ACLs. - Bridge config parameters couldn't contain a space, this has been fixed. Closes #150. - Fix saving of persistence messages that start with a '/'. Closes #151. - Fix reconnecting for bridges that use TLS on Windows. Closes #154. - Broker and bridges can now cope with unknown incoming PUBACK, PUBREC, PUBREL, PUBCOMP without disconnecting. Closes #57. - Fix websockets listeners not being able to bind to an IP address. Closes #170. - mosquitto_passwd utility now correctly deals with unknown command line arguments in all cases. Closes #169. - Fix publishing of $SYS/broker/clients/maximum - Fix order of #includes in lib/send_mosq.c to ensure struct mosquitto doesn't differ between source files when websockets is being used. Closes #180. - Fix possible rare crash when writing out persistence file and a client has incomplete messages inflight that it has been denied the right to publish. # Client library - Fix the case where a message received just before the keepalive timer expired would cause the client to miss the keepalive timer. - Return value of pthread_create is now checked. - _mosquitto_destroy should not cancel threads that weren't created by libmosquitto. Closes #166. - Clients can now cope with unknown incoming PUBACK, PUBREC, PUBREL, PUBCOMP without disconnecting. Closes #57. - Fix mosquitto_topic_matches_sub() reporting matches on some invalid subscriptions. # Clients - Handle some unchecked malloc() calls. Closes #1. # Build - Fix string quoting in CMakeLists.txt. Closes #4. - Fix building on Visual Studio 2015. Closes #136. 1.4.8 - 20160214 ================ # Broker - Wills published by clients connected to a listener with mount_point defined now correctly obey the mount point. This was a potential security risk because it allowed clients to publish messages outside of their restricted mount point. This is only affects brokers where the mount_point option is in use. Closes #487178. - Fix detection of broken connections on Windows. Closes #485143. - Close stdin etc. when daemonised. Closes #485589. - Fix incorrect detection of FreeBSD and OpenBSD. Closes #485131. # Client library - mosq->want_write should be cleared immediately before a call to SSL_write, to allow clients using mosquitto_want_write() to get accurate results. 1.4.7 - 20151221 ================ # Broker - Fix support for libwebsockets 1.22. 1.4.6 - 20151220 ================ # Broker - Add support for libwebsockets 1.6. # Client library - Fix _mosquitto_socketpair() on Windows, reducing the chance of delays when publishing. Closes #483979. # Clients - Fix "mosquitto_pub -l" stripping the final character on a line. Closes #483981. 1.4.5 - 20151108 ================ # Broker - Fix possible memory leak if bridge using SSL attempts to connect to a host that is not up. - Free unused topic tree elements (fix in 1.4.3 was incomplete). Closes #468987. # Clients - "mosquitto_pub -l" now no longer limited to 1024 byte lines. Closes #478917. 1.4.4 - 20150916 ================ # Broker - Don't leak sockets when outgoing bridge with multiple addresses cannot connect. Closes #477571. - Fix cross compiling of websockets. Closes #475807. - Fix memory free related crashes on openwrt. Closes #475707. - Fix excessive calls to message retry check. 1.4.3 - 20150818 ================ # Broker - Fix incorrect bridge notification on initial connection. Closes #467096. - Build fixes for OpenBSD. - Fix incorrect behaviour for autosave_interval, most noticeable for autosave_interval=1. Closes #465438. - Fix handling of outgoing QoS>0 messages for bridges that could not be sent because the bridge connection was down. - Free unused topic tree elements. Closes #468987. - Fix some potential memory leaks. Closes #470253. - Fix potential crash on libwebsockets error. # Client library - Add missing error strings to mosquitto_strerror. - Handle fragmented TLS packets without a delay. Closes #470660. - Fix incorrect loop timeout being chosen when using threaded interface and keepalive = 0. Closes #471334. - Increment inflight messages count correctly. Closes #474935. # Clients - Report error string on connection failure rather than error code. 1.4.2 - 20150507 ================ # Broker - Fix bridge prefixes only working for the first outgoing message. Closes #464437. - Fix incorrect bridge connection notifications on local broker. - Fix persistent db writing on Windows. Closes #464779. - ACLs are now checked before sending a will message. - Fix possible crash when using bridges on Windows. Closes #465384. - Fix parsing of auth_opt_ arguments with extra spaces/tabs. - Broker will return CONNACK rc=5 when a username/password is not authorised. This was being incorrectly set as rc=4. - Fix handling of payload lengths>4096 with websockets. # Client library - Inflight message count wasn't being decreased for outgoing messages using QoS 2, meaning that only up to 20 QoS 2 messages could be sent. This has been fixed. Closes #464436. - Fix CMake dependencies for C++ wrapper building. Closes #463884. - Fix possibility of select() being called with a socket that is >FD_SETSIZE. This is a fix for #464632 that will be followed up by removing the select() call in a future version. - Fix calls to mosquitto_connect*_async() not completing. 1.4.1 - 20150403 ================ # Broker - Fix possible crash under heavy network load. Closes #463241. - Fix possible crash when using pattern ACLs. - Fix problems parsing config strings with multiple leading spaces. Closes #462154. - Websockets clients are now periodically disconnected if they have not maintained their keepalive timer. Closes #461619. - Fix possible minor memory leak on acl parsing. # Client library - Inflight limits should only apply to outgoing messages. Closes #461620. - Fix reconnect bug on Windows. Closes #463000. - Return -1 on error from mosquitto_socket(). Closes #461705. - Fix crash on multiple calls to mosquitto_lib_init/mosquitto_lib_cleanup. Closes #462780. - Allow longer paths on Windows. Closes #462781. - Make _mosquitto_mid_generate() thread safe. Closes #463479. 1.4 - 20150218 ============== # Important changes - Websockets support in the broker. - Bridge behaviour on the local broker has changed due to the introduction of the local_* options. This may affect you if you are using authentication and/or ACLs with bridges. - The default TLS behaviour has changed to accept all of TLS v1.2, v1.1 and v1.0, rather than only only one version of the protocol. It is still possible to restrict a listener to a single version of TLS. - The Python client has been removed now that the Eclipse Paho Python client has had a release. - When a durable client reconnects, its queued messages are now checked against ACLs in case of a change in username/ACL state since it last connected. - New use_username_as_clientid option on the broker, for preventing hijacking of a client id. - The client library and clients now have experimental SOCKS5 support. - Wildcard TLS certificates are now supported for bridges and clients. - The clients have support for config files with default options. - Client and client libraries have support for MQTT v3.1.1. - Bridge support for MQTT v3.1.1. # Broker - Websockets support in the broker. - Add local_clientid, local_username, local_password for bridge connections to authenticate to the local broker. - Default TLS mode now accepts TLS v1.2, v1.1 and v1.0. - Support for ECDHE-ECDSA family ciphers. - Fix bug #1324411, which could have had unexpected consequences for delayed messages in rare circumstances. - Add support for "session present" in CONNACK messages for MQTT v3.1.1. - Remove strict protocol #ifdefs. - Change $SYS/broker/clients/active -> $SYS/broker/clients/connected - Change $SYS/broker/clients/inactive -> $SYS/broker/clients/disconnected - When a durable client reconnects, its queued messages are now checked against ACLs in case of a change in username/ACL state since it last connected. - libuuid is used to generate client ids, where it is available, when an MQTT v3.1.1 client connects with a zero length client id. - Anonymous clients are no longer accidently disconnected from the broker after a SIGHUP. - mosquitto_passwd now supports -b (batch mode) to allow the password to be provided at the command line. - Removed $SYS/broker/changeset. This was intended for use with debugging, but in practice is of no use. - Add support for use_username_as_clientid which can be used with authentication to restrict ownership of client ids and hence prevent one client disconnecting another by using the same client id. - When "require_certificate" was false, the broker was incorrectly asking for a certificate (but not checking it). This caused problems with some clients and has been fixed so the broker no longer asks. - When using syslog logging on non-Windows OSs, it is now possible to specify the logging facility to one of local0-7 instead of the default "daemon". - The bridge_attempt_unsubscribe option has been added, to allow the sending of UNSUBSCRIBE requests to be disabled for topics with "out" direction. Closes bug #456899. - Wildcard TLS certificates are now supported for bridges. - Support for "hour" client expiration lengths for the persistent_client_expiration option. Closes bug #425835. - Bridge support for MQTT v3.1.1. - Root privileges are now dropped after starting listeners and loading certificates/private keys, to allow private keys to have their permissions restricted to the root user only. Closes bug #452914. - Usernames and topics given in ACL files can now include a space. Closes bug #431780. - Fix hang if pattern acl contains a %u but an anonymous client connect. Closes bug #455402. - Fix man page installation with cmake. Closes bug #458843. - When using "log_dest file" the output file is now flushed periodically. # Clients - Both clients can now load default configuration options from a file. - Add -C option to mosquitto_sub to allow the client to quit after receiving a certain count of messages. Closes bug #453850. - Add --proxy SOCKS5 support for both clients. - Pub client supports setting its keepalive. Closes bug #454852. - Add support for config files with default options. - Add support for MQTT v3.1.1. # Client library - Add experimental SOCKS5 support. - mosquitto_loop_forever now quits after a fatal error, rather than blindly retrying. - SRV support is now not compiled in by default. - Wildcard TLS certificates are now supported. - mosquittopp now has a virtual destructor. Closes bug #452915. - Add support for MQTT v3.1.1. - Don't quit mosquitto_loop_forever() if broker not available on first connect. Closes bug #453293, but requires more work. - Don't reset queued messages state on CONNACK. Fixes bug with duplicate messages on connection. 1.3.5 - 20141008 ================ # Broker - Fix possible memory leak when using a topic that has a leading slash. Fixes bug #1360985. - Fix saving persistent database on Windows. - Temporarily disable ACL checks on subscriptions when using MQTT v3.1.1. This is due to the complexity of checking wildcard ACLs against wildcard subscriptions. This does not have a negative impact on security because checks are still made before a message is sent to a client. Fixes bug #1374291. - When using -v and the broker receives a SIGHUP, verbose logging was being disabled. This has been fixed. # Client library - Fix mutex being incorrectly passed by value. Fixes bug #1373785. 1.3.4 - 20140806 ================ # Broker - Don't ask client for certificate when require_certificate is false. - Backout incomplete functionality that was incorrectly included in 1.3.2. 1.3.3 - 20140801 ================ # Broker - Fix incorrect handling of anonymous bridges on the local broker. 1.3.2 - 20140713 ================ # Broker - Don't allow access to clients when authenticating if a security plugin returns an application error. Fixes bug #1340782. - Ensure that bridges verify certificates by default when using TLS. - Fix possible crash when using pattern ACLs that do not include a %u and clients that connect without a username. - Fix subscriptions being deleted when clients subscribed to a topic beginning with a $ but that is not $SYS. - When a durable client reconnects, its queued messages are now checked against ACLs in case of a change in username/ACL state since it last connected. - Fix bug #1324411, which could have had unexpected consequences for delayed messages in rare circumstances. - Anonymous clients are no longer accidently disconnected from the broker after a SIGHUP. # Client library - Fix topic matching edge case. - Fix callback deadlocks after calling mosquitto_disconnect(), when using the threaded interfaces. Closes bug #1313725. - Fix SRV support when building with CMake. - Remove strict protocol #ifdefs. # General - Use $(STRIP) for stripping binaries when installing, to allow easier cross compilation. 1.3.1 - 20140324 ================ # Broker - Prevent possible crash on client reconnect. Closes bug #1294108. - Don't accept zero length unsubscription strings (MQTT v3.1.1 fix) - Don't accept QoS 3 (MQTT v3.1.1 fix) - Don't disconnect clients immediately on HUP to give chance for all data to be read. - Reject invalid un/subscriptions e.g. foo/+bar #/bar. - Take more care not to disconnect clients that are sending large messages. # Client library - Fix socketpair code on the Mac. - Fix compilation for WITH_THREADING=no. - Break out of select() when calling mosquitto_loop_stop(). - Reject invalid un/subscriptions e.g. foo/+bar #/bar. - Add mosquitto_threaded_set(). # Clients - Fix keepalive value on mosquitto_pub. - Fix possibility of mosquitto_pub not exiting after sending messages when using -l. 1.3 - 20140316 ============== # Broker - The broker no longer ignores the auth_plugin_init() return value. - Accept SSLv2/SSLv3 HELLOs when using TLSv1, whilst keeping SSLv2 and SSLv3 disabled. This increases client compatibility without sacrificing security. - The $SYS tree can now be disabled at runtime as well as at compile time. - When remapping bridged topics, only check for matches when the message direction is correct. This allows two identical topics to be remapped differently for both in and out. - Change "$SYS/broker/heap/current size" to "$SYS/broker/heap/current" for easier parsing. - Change "$SYS/broker/heap/maximum size" to "$SYS/broker/heap/maximum" for easier parsing. - Topics are no longer normalised from e.g a///topic to a/topic. This matches the behaviour as clarified by the Oasis MQTT spec. This will lead to unexpected behaviour if you were using topics of this form. - Log when outgoing messages for a client begin to drop off the end of the queue. - Bridge clients are recognised as bridges even after reloading from persistence. - Basic support for MQTT v3.1.1. This does not include being able to bridge to an MQTT v3.1.1 broker. - Username is displayed in log if present when a client connects. - Support for 0 length client ids (v3.1.1 only) that result in automatically generated client ids on the broker (see option allow_zero_length_clientid). - Ability to set the prefix of automatically generated client ids (see option auto_id_prefix). - Add support for TLS session resumption. - When using TLS, the server now chooses the cipher to use when negotiating with the client. - Weak TLS ciphers are now disabled by default. # Client library - Fix support for Python 2.6, 3.0, 3.1. - Add support for un/subscribing to multiple topics at once in un/subscribe(). - Clients now close their socket after sending DISCONNECT. - Python client now contains its version number. - C library mosquitto_want_write() now supports TLS clients. - Fix possible memory leak in C/C++ library when communicating with a broker that doesn't follow the spec. - Return strerror() through mosquitto_strerror() to make error printing easier. - Topics are no longer normalised from e.g a///topic to a/topic. This matches the behaviour as clarified by the Oasis MQTT spec. This will lead to unexpected behaviour if you were using topics of this form. - Add support for SRV lookups. - Break out of select() on publish(), subscribe() etc. when using the threaded interface. Fixes bug #1270062. - Handle incoming and outgoing messages separately. Fixes bug #1263172. - Don't terminate threads on mosquitto_destroy() when a client is not using the threaded interface but does use their own thread. Fixes bug #1291473. # Clients - Add --ciphers to allow specifying which TLS ciphers to support. - Add support for SRV lookups. - Add -N to sub client to suppress printing of EOL after the payload. - Add -T to sub client to suppress printing of a topic hierarchy. 1.2.3 - 20131202 ================ # Broker - Don't always attempt to call read() for SSL clients, irrespective of whether they were ready to read or not. Reduces syscalls significantly. - Possible memory leak fixes. - Further fix for bug #1226040: multiple retained messages being delivered for subscriptions ending in #. - Fix bridge reconnections when using multiple bridge addresses. # Client library - Fix possible memory leak in C/C++ library when communicating with a broker that doesn't follow the spec. - Block in Python loop_stop() until all messages are sent, as the documentation states should happen. - Fix for asynchronous connections on Windows. Closes bug #1249202. - Module version is now available in mosquitto.py. # Clients - mosquitto_sub now uses fwrite() instead of printf() to output messages, so messages with NULL characters aren't truncated. 1.2.2 - 20131021 ================ # Broker - Fix compliance with max_inflight_messages when a non-clean session client reconnects. Closes one of the issues on bug #1237389. # Client library - Fix incorrect inflight message accounting, which caused messages to go unsent. Partial fix for bug #1237351. - Fix potential memory corruption when sending QoS>0 messages at a high rate using the threaded interface. Further fix for #1237351. - Fix incorrect delay scaling when exponential_backoff=true in mosquitto_reconnect_delay_set(). - Some pep8 fixes for Python. 1.2.1 - 20130918 ================ # Broker - The broker no longer ignores the auth_plugin_init() return value. Closes bug #1215084. - Use RTLD_GLOBAL when opening authentication plugins on posix systems. Fixes resolving of symbols in libraries used by authentication plugins. - Add/fix some config documentation. - Fix ACLs for topics with $SYS. - Clients loaded from the persistence file on startup were not being added to the client hash, causing subtle problems when the client reconnected, including ACLs failing. This has been fixed. - Add note to mosquitto-tls man page stating that certificates need to be unique. Closes bug #1221285. - Fix incorrect retained message delivery when using wildcard subs in some circumstances. Fixes bug #1226040. # Client library - Fix support for Python 2.6, 3.0, 3.1. - Fix TLS subjectAltName verification and segfaults. - Handle EAGAIN in Python on Windows. Closes bug #1220004. - Fix compilation when using WITH_TLS=no. - Don't fail reconnecting in Python when broker is temporarily unavailable. 1.2 - 20130708 ============== # Broker - Replace O(n) username lookup on CONNECT with a roughly O(1) hashtable version. - It is now possible to disable $SYS at compile time. - Add dropped publish messages to load tree in $SYS. Closes bug #1183318. - Add support for logging SUBSCRIBE/UNSUBSCRIBE events. - Add "log_dest file" logging support. - Auth plugin ACL check function now passes the client id as well as username and password. - The queue_qos0_messages option wasn't working correctly, this has now been fixed. Closes bug #1125200. - Don't drop all messages for disconnected durable clients when max_queued_messages=0. - Add support for "log_type all". - Add support for "-v" option on the command line to provide the equivalent of "log_type all" without needing a config file. - Add the "upgrade_outgoing_qos" option, a non-standard feature. - Persistence data is now written to a temporary file which is atomically renamed on completion, so a crash during writing will not produce a corrupt file. - mosquitto.conf is now installed as mosquitto.conf.example - Configuration file errors are now reported with filename and line number. - The broker now uses a monotonic clock if available, to avoid changes in time causing client disconnections or message retries. - Clean session and keepalive status are now display the log when a client connects. - Add support for TLSv1.2 and TLSv1.1. - Clients that connect with zero length will topics are now rejected. - Add the ability to set a maximum allowed PUBLISH payload size. - Fix an ACL with topic "#" incorrectly granting access to $SYS. - Fix retained messages incorrectly being set on wildcard topics, leading to duplicate retained messages being sent on subscription. Closes bug #1116233. - Don't discard listener values when no "port" option given. Closes bug #1131406. - Client password check was always failing when security was being reapplied after a config reload. This meant that all clients were being disconnected. This has been fixed. - Fix build when WITH_TLS=no. Closes bug #1174971. - Fix single outgoing packets not being sent in a timely fashion if they were not sent in one call to write(). Closes bug #1176796. - Fix remapping of messages for clients connected to a listener with mount_point set. Closes bug #1180765. - Fix duplicate retained messages being sent for some wildcard patterns. - If a client connects with a will topic to which they do not have write access, they are now disconnected with CONNACK "not authorised". - Fix retained messages on topic foo being incorrectly delivered to subscriptions of /# - Fix handling of SSL errors on SSL_accept(). - Fix handling of QoS 2 messages on client reconnect. - Drop privileges now sets supplementary groups correctly. - Fix load reporting interval (is now 60s). - Be strict with malformed PUBLISH packets - clients are now disconnected rather than the packet discarded. This goes inline with future OASIS spec changes and makes other changes more straightforward. - Process incoming messages denied by ACL properly so that clients don't keep resending them. - Add support for round_robin bridge option. - Add bridge support for verifying remote server certificate subject against the remote hostname. - Fix problem with out of order calls to free() when restarting a lazy bridge. - The broker now attempts to resolve bind_address and bridge addresses immediately when parsing the config file in order to detect invalid hosts. - Bridges now set their notification state before attempting to connect, so if they fail to connect the state can still be seen. - Fix bridge notification payload length - no need to send a null byte. - mosquitto_passwd utility now reports errors more clearly. - Fix "mosquitto_passwd -U". # Client library - Add support for TLSv1.2 and TLSv1.1, except for on the Python module. - Add support for verifying remote server certificate subject against the remote hostname. - Add mosquitto_reconnect_async() support and make asynchronous connections truly asynchronous rather than simply deferred. DNS lookups are still blocking, so asynchronous connections require an IP address instead of hostname. - Allow control of reconnection timeouts in mosquitto_loop_forever() and after mosquitto_loop_start() by using mosquitto_reconnect_delay_set(). - Fix building on Android NDK. - Re-raise unhandled errors in Python so as not to provide confusing error messages later on. - Python module supports IPv6 connections. - mosquitto_sub_topic_tokenise() was behaving incorrectly if the last topic hierarchy had only a single character. This has been fixed. Closes bug #1163348. - Fix possible crash after disconnects when using the threaded interface with TLS. - Allow build/install without Python. Closes bug #1174972. - Add support for binding connection to a local interface. - Implement maximum inflight messages handling. - Fix Python client not handling will_payload==None. - Fix potential memory leak when setting username/password. - Fix handling of QoS 2 messages on reconnect. - Improve handling of mosquitto_disconnect() with threaded mode. # Clients - Add support for TLSv1.2 and TLSv1.1. - Sub client can now suppress printing of messages with the retain bit set. - Add support for binding connection to a local interface. - Implement maximum inflight messages handling for the pub client. 1.1.3 - 20130211 ================ # Broker - mosquitto_passwd utility now uses tmpfile() to generate its temporary data storage file. It also creates a backup file that can be used to recover data if an errors occur. # Other - Build script fixes to help packaging on Debian. 1.1.2 - 20130130 ================ # Client library - Fix tls_cert_reqs not being set to SSL_VERIFY_PEER by default. This meant that clients were not verifying the server certificate when connecting over TLS. This affects the C, C++ and Python libraries. 1.1.1 - 20130116 ================ # Broker - Fix crash on reload if using acl patterns. # Client library - Fix static C++ functions not being exported on Windows. Fixes bug #1098256. 1.1 - 20121219 ============== # Broker - Add $SYS/broker/messages/dropped - Add $SYS/broker/clients/expired - Replace $SYS/broker/+/per second/+ with moving average versions published at $SYS/broker/load/# - Add $SYS/broker/load/sockets/+ and $SYS/broker/load/connections/+ - Documentation on password file format has been fixed. - Disable SSL compression. This reduces memory usage significantly and removes the possibility of CRIME type attacks. - Enable SSL_MODE_RELEASE_BUFFERS mode to reduce SSL memory usage further. - Add allow_duplicate_messages option. - ACL files can now have comment lines with # as the first character. - Display message on startup about which config is being loaded. - Fix max_inflight_messages and max_queued_messages not being applied. - Fix documentation error in mosquitto.conf. - Ensure that QoS 2 queued messages are sent out in a timely manner. - Local bridges now act on clean_session correctly. - Local bridges with clean_session==false now remove unused subscriptions on broker restart. - The $SYS/broker/heap/# messages now no longer include "bytes" as part of the string for ease of use. # Client library - Free memory used by OpenSSL in mosquitto_lib_cleanup() where possible. - Change WebSocket subprotocol name to mqttv3.1 to make future changes easier and for compatibility with other implementations. - mosquitto_loop_read() and mosquitto_loop_write() now handle errors themselves rather than having mosquitto_loop() handle their errors. This makes using them in a separate event loop more straightforward. - Add mosquitto_loop_forever() / loop_forever() function call to make simple clients easier. - Disable SSL compression. This reduces memory usage significantly and removes the possibility of CRIME type attacks. - Enable SSL_MODE_RELEASE_BUFFERS mode to reduce SSL memory usage further. - mosquitto_tls_set() will now return an error or raise an exception immediately if the CA certificate or client certificate/key cannot be accessed. - Fix potential memory leaks on connection failures. - Don't produce return error from mosquitto_loop() if a system call is interrupted. This prevents disconnects/reconnects in threaded mode and simplifies non-threaded client handling. - Ignore SIGPIPE to prevent unnecessary client quits in threaded mode. - Fix document error for mosquitto_message_retry_set(). - Fix mosquitto_topic_matches_sub() for subscriptions with + as the final character. Fixes bug #1085797. - Rename all "obj" parameters to "userdata" for consistency with other libraries. - Reset errno before network read/write to ensure EAGAIN isn't mistakenly returned. - The message queue length is now tracked and used to determine the maximum number of packets to process at once. This removes the need for the max_packets parameter which is now unused. - Fix incorrect error value in Python error_string() function. Fixes bug #1086777. - Reset last message in/out timer in Python module when we send a PINGREQ. Fixes too-early disconnects. # Clients - Clients now display their own version number and library version number in their help messages. - Fix "mosquitto_pub -l -q 2" disconnecting before all messages were transmitted. - Fix potential out-of-bounds array access with client ids. Fixes bug #1083182. # Other - mosquitto_passwd can now convert password files with plain text files to hashed versions. 1.0.5 - 20121103 ================ # Broker - Fix crash when the broker has use_identity_as_username set to true but a client connects without a certificate. - mosquitto_passwd should only be installed if WITH_TLS=yes. # Library - Use symbolic errno values rather than numbers in Python module to avoid cross platform issues (incorrect errno on Mac OS). # Other - Build script fixes for FreeBSD. 1.0.4 - 20121017 ================ # Broker - Deal with poll() POLLIN/POLLOUT before POLL[RD]HUP to correctly handle the case where a client sends data and immediately closes its socket. # Library - Fix memory leak with messages of QoS=2. Fixes bug #1064981. - Fix potential thread synchronisation problem with outgoing packets in the Python module. Fixes bug #1064977. # Clients - Fix "mosquitto_sub -l" incorrectly only sending one message per second. 1.0.3 - 20120927 ================ # Broker - Fix loading of psk files. - Don't return an error when reloading config if an ACL file isn't defined. This was preventing psk files being reloaded. - Clarify meaning of $SYS/broker/clients/total in mosquitto(8) man page. - Clarify meaning of $SYS/broker/messages/stored in mosquitto(8) man page. - Fix non-retained message delivery when subscribing to #. - Fix retained message delivery for subs to foo/# with retained messages at foo. - Include the filename in password/acl file loading errors. # Library - Fix possible AttributeError when self._sock == None in Python module. - Fix reconnecting after a timeout in Python module. - Fix reconnecting when there were outgoing packets in the queue in the Python module. - Fix problem with mutex initialisation causing crashes on some Windows installations. 1.0.2 - 20120919 ================ # Broker - If the broker was configured for persistence, a durable client had a subscription to topics in $SYS/# and had messages in its queue when the broker restarted, then the persistent database would have messages missing and so the broker would not restart properly. This has been fixed. # Library - Fix threading problem on some systems. # Tests - Close socket after 08-ssl-connect-no-auth-wrong-ca.py test to prevent subsequent tests having problems. # Build scripts - Install pskfile.example in CMake. Fixes bug #1037504. # Other - Fix db_dump parameter printing message store and sub chunks. 1.0.1 - 20120815 ================ # Broker - Fix default log_dest when running as a Windows service. # Client library - Fix incorrect parameters in Python on_log() callback call. Fixes bug #1036818. # Clients - Clients now don't display TLS/TLS-PSK usage help if they don't support it. # Build scripts - Fix TLS-PSK support in the CMake build files. - Fix man page installation in the CMake build files. - Fix SYSCONFDIR in cmake on *nix when installing to /usr. Fixes bug #1036908. # Documentation - Fix mqtt/MQTT capitalisation in man pages. - Update compiling.txt. - Fix incorrect callback docs in mosquitto.py. Fixes bug #1036607. - Fix various doc typos and remove obsolete script. Fixes bug #1037088. 1.0 - 20120814 ============== # Broker - Add SSL/TLS support. - Add TLS-PSK support, providing a simpler encryption method for constrained devices. - Passwords are now salted+hashed if compiled with WITH_TLS (recommended). - Add mosquitto_passwd for handling password files. - Add $SYS/broker/publish/messages/{sent|received} to show the number of PUBLISH messages sent/received. - Add $SYS/broker/publish/bytes/{sent|received} to show the number of PUBLISH bytes sent/received. - Add reload parameter for security init/cleanup functions. - Add option for expiring disconnected persistent clients. - Add option for queueing of QoS 0 messages when persistent clients are disconnected. - Enforce client id limits in the broker (only when WITH_STRICT_PROTOCOL is defined). - Fix reloading of log configuration. - Add support for try_private config option for bridge connections. - Add support for autosave_on_changes config option. - Add support for include_dir config option. - Add support for topic remapping. - Usernames were being lost when a non clean-session client reconnected, potentially causing problems with ACLs. This has been fixed. - Significant improvement to memory handling on Windows. - Bridges with outgoing topics will now set the retain flag correctly so that messages will be retained on the remote broker. - Incoming bridge connections are now detected by checking if bit 8 of the protocol version number is set. This requires support from the remote broker. - Add support for notification_topic option. - Add $SYS/broker/subscriptions/count and $SYS/broker/retained messages/count. - Add restart_timeout to control the amount of time an automatic bridge will wait before reconnecting. - Overlapping subscriptions are now handled properly. Fixes bug #928538. - Fix reloading of persistence_file and persistence_location. - Fix broker crash on incorrect protocol number. - Fix missing COMPAT_ECONNRESET define on Windows. - Clients that had disconnected were not always being detected immediately on Linux. This has been fixed. - Don't save $SYS messages to the on-disk persistent db. All $SYS messages should be reconstructed on a restart. This means bridge connection notifications will now be correct on a restart. - Fix reloading of bridge clients from the persistent db. This means that outgoing bridged topics should always work. - Local bridges are now no longer restricted by local ACLs. - Discard publish messages with zero length topics. - Drop to "mosquitto" user even if no config file specified. - Don't incorrectly allow topic access if ACL patterns but no normal ACL rules are defined. # Client library - Add SSL/TLS support. - Add TLS-PSK support, providing a simpler encryption method for constrained devices. - Add javascript/websockets client library. - Add "struct mosquitto *mosq" parameter for all callbacks in the client library. This is a binary incompatible change so the soversion of the libraries has been incremented. The new parameter should make it easier to use callbacks in practice. - Add mosquitto_want_write() for use when using own select() loop with mosquitto_socket(). - Add mosquitto_connect_async() to provide a non-blocking connect client call. - Add mosquitto_user_data_set() to allow user data pointer to be updated. - Add "int rc" parameter to disconnect callback to indicate whether disconnect was unexpected or the result of calling mosquitto_disconnect(). - Add mosquitto_strerror() for obtaining a string description of error numbers. - Add mosquitto_connack_string() for obtaining a string description of MQTT connection results. - Add mosquitto_will_clear() and change mosquitto_will_set() to only set the will. - Add mosquitto_sub_topic_tokenise() and mosquitto_sub_topic_tokens_free() utility functions to tokenise a subscription/topic string into a string array. - Add mosquitto_topic_matches_sub() to check whether a topic matches a subscription. - Replaced mosquitto_log_init() with mosquitto_log_callback_set() to allow clients to decide what to do with log messages. - Client will now disconnect itself from the broker if it doesn't receive a PINGRESP in the keepalive period after sending a PINGREQ. - Client will now send a PINGREQ if it has not received a message from the broker in keepalive seconds. - mosquitto_new() will now generate a random client id if the id parameter is NULL. - Added max_packets to mosquitto_loop(), mosquitto_loop_read() and mosquitto_loop_write() to control the maximum number of packets that are handled per call. - Payload parameters are now void * instead of uint8_t *. - The clean_session parameter has been moved from mosquitto_connect() to mosquitto_new() because it is a client parameter rather than a connection parameter. - Functions now use int instead of uint*_t where possible. - mosquitto_new() now sets errno to indicate failure type. - Return MOSQ_ERR_INVAL on zero length topic. - Fix automatic client id generation on Windows. - mosquitto_loop_misq() can now return MOSQ_ERR_NO_CONN. - Compile static library as well as dynamic library with default makefiles. - Rename C++ namespace from mosquittopp to mosqpp to remove ambiguity. - C++ lib_init(), lib_version() and lib_cleanup() are now in the mosqpp namespace directly, not mosquittopp class members. - The Python library is now written in pure Python and so no longer depends on libmosquitto. - The Python library includes SSL/TLS support. - The Python library should now be compatible with Python 3. # Other - Fix db_dump reading of retained messages. - Add example of logging all messages to mysql. - Add C++ client example. - Fix potential buffer overflow in pub/sub clients. - Add "make binary" target that doesn't make documents. - Add "--help" arguments to pub/sub clients. - Fix building on Solaris. 0.15 - 20120205 =============== - Add support for $SYS/broker/clients/maximum and $SYS/broker/clients/active topics. - Add support for $SYS messages/byte per second received/sent topics. - Updated mosquitto man page - $SYS hierarchy and signal support were out of date. - Auto generated pub/sub client ids now include the hostname. - Tool for dumping persistent DB contents is available in src/db_dump. It isn't installed by default. - Enforce topic length checks in client library. - Implement "once" and "lazy" bridge start types. - Add new return type MOSQ_ERR_ERRNO to indicate that the errno variable should be checked for the real error code. - Add support for connection_messages config option. - mosquitto_sub will now refuse to run if the -c option (disable clean session) is given and no client id is provided. - mosquitto_pub now gives more useful error messages on invalid input or other error conditions. - Fix Python will_set() true/True typo. - Fix messages to topic "a/b" incorrectly matching on a subscription "a" if another subscription "a/#" exists. 0.14.4 - 20120106 ================= - Fix local bridge notification messages. - Fix return values for more internal library calls. - Fix incorrect out of memory checks in library and broker. - Never time out local bridge connections. 0.14.3 - 20111210 ================= - Fix potential crash when client connects with an invalid CONNECT packet. - Fix incorrect invalid socket comparison on Windows. - Server shouldn't crash when a message is published to foo/ when a subscription to foo/# exists (bug #901697). - SO_REUSEADDR doesn't work the same on Windows, so don't use it. - Cygwin builds now support Windows service features. - Fix $SYS/broker/bytes/sent reporting. 0.14.2 - 20111123 ================= - Add uninstall target for libs. - Don't try to write packet whilst in a callback. 0.14.1 - 20111117 ================= - Fix Python syntax errors (bug #891673). 0.14 - 20111116 =============== - Add support for matching ACLs based on client id and username. - Add a Windows installer file (NSIS based). - Add native support for running the broker as a Windows service. This is the default when installed using the new installer. - Fix client count for listeners. When clients disconnect, decrement the count. Allow max_connections to work again. - Attempt to send all packets immediately upon being queued. This will result in more immediate network communication in many cases. - Log IP address when reporting CONNACK packets if the client id isn't yet known. - Fix payload length calculation in python will_set function. - Fix Python publish and will_set functions for payload=None. - Fix keepalive value being lost when reconnecting a client (bug #880863). - Persistence file writing now uses portable file functions, so the Cygwin broker build should no longer be necessary. - Duplicate code between the client and broker side has been reduced. - Queued messages for clients reconnecting with clean_session=false set were not being sent until the next message for that client was received. This has been fixed (bug #890724). - Fix subscriptions to # incorrectly matching against topics beginning with / 0.13 - 20110920 =============== - Implement bridge state notification messages. - Save client last used mid in persistent database (DB version number bumped). - Expose message id in Python MosquittoMessage. - It is now possible to set the topic QoS level for bridges. - Python MosquittoMessage payload parameter is now a Python string, not a ctypes object which makes it much easier to use. - Fix queueing of messages for disconnected clients. The max_queued_messages option is now obeyed. - C++ library is now in its own namespace, mosquittopp. - Add support for adding log message timestamps in the broker. - Fix missing mosquitto_username_pw_set() python binding. - Fix keepalive timeout for reconnecting non clean-session clients. Prevents immediate disconnection on reconnection. - Fix subscription wildcard matching - a subscription of +/+ will now match against /foo - Fix subscription wildcard matching - a subscription of foo/# will now match against foo - When restoring persistent database, clients should be set to non clean-session or their subscriptions will be immediately removed. - Fix SUBACK payload for multiple topic subscriptions. - Don't send retained messages when a client subscribes to a topic it is already subscribed to. 0.12 - 20110725 =============== - Reload (most) configuration on SIGHUP. - Memory tracking is no longer compiled in the client library. - Add --help option to mosquitto to display usage. - Add --id-prefix option to clients to allow easier use with brokers that are using the clientid_prefix option. - Fix compilation on QNX. - Add -P as a synonym argument for --pw in the clients. - Fix python MosquittoMessage payload parameter. This is now returned as a pointer to an array of c_uint8 values so binary data is handled correctly. If a string is needed, use msg.payload_str - Fix memory leaks on client authentication. - If password_file is not defined then clients can now connect even if they use a username/password. - Add mosquitto_reconnect() to the client library. - Add option for compiling with liberal protocol compliance support (enabled by default). - Fix problems with clients reconnecting and old messages remaining in the message store. - Display both ip and client id in the log message when a client connects. Change the socket connection message to make it more obvious that it is just a socket connection being made (bug #801135). - Fix retained message delivery where a subscription contains a +. - Be more lenient when reloading persistent database to reduce errors with empty retained messages. 0.11.3 - 20110707 ================= - Don't complain and quit if persistence_file option is given (bug #802423). - Initialise listeners correctly when clients with duplicate client ids connect. Bug #801678. - Memory tracking is now disabled for Symbian builds due to lack of malloc.h. - Fix memory tracking compilation for kFreeBSD. - Python callbacks can now be used with class member functions. - Fix persistent database writing of client message chunks which caused errors when restoring (bug #798164). 0.11.2 - 20110626 ================= - Don't free contexts in mqtt3_context_disconnect() (bug #799688 / #801678). - Only free will if present when freeing a client context. 0.11.1 - 20110620 ================= - Fix buffer overrun when checking for + and # in topics (bug #799688). - Pub client now quits if publish fails. 0.11 - 20110619 =============== - Removed all old sqlite code. - Remove client id limit in clients. - Implemented $SYS/broker/heap/maximum size - Implemented $SYS/broker/clients/inactive to show the number of disconnected non-clean session clients. - $SYS/broker/heap/current size and maximum size messages now include "bytes" to match rsmb message format. - Implemented the retained_persistence config file option - a synonym of the "persistence" option. - Added security_external.c to broker source to make it easier for third parties to add support for their existing username/password and ACL database for security checks. See external_security_checks.txt. - $SYS messages are now only republished when their value changes. - Windows native broker now responds to command line arguments. - Simplify client disconnecting so wills gets sent in all cases (bug #792468). - Clients now have a --quiet option. - The on_disconnect() callback will always be called now, even if the client has disconnected unexpectedly. - Always close persistent DB file after restoring. - Return error code when exiting the clients. - mosquitto_publish() now returns MOSQ_ERR_INVAL if the topic contains + or # - mosquitto now silently rejects published messages with + or # in the topic. - max_connections is now a per-listener setting instead of global. - Connection count is now reduced when clients disconnect (bug #797983). 0.10.2 - 20110106 ================= - Don't abort when connecting if the first connection fails. This is important on e.g. Windows 7, where IPV6 is offered as the first choice but may not be available. - Deal with long logging messages properly (bug #785882). - Fix library compilation on Symbian - no pselect() available. - Don't stop processing subscriptions on received messages after a subscription with # matches. (bug #791206). 0.10.1 - 20110512 ================= - Fix Windows compilation. - Fix mosquitto.py on Windows - call lib init/cleanup. - Don't abort when connecting if given an unknown address type (assuming an IPv4 or IPv6 address is given). 0.10 - 20110429 =============== - Implement support for the password_file option and accompanying authentication requirements in the broker. - Implement topic Access Control Lists. - mosquitto_will_set() and mosquitto_publish() now return MOSQ_ERR_PAYLOAD_SIZE if the payload is too large (>268,435,455 bytes). - Bridge support can now be disabled at compile time. - Group together network writes for outgoing packets - don't send single byte writes! - Add support for clientid_prefixes variable. - Add support for the clientid config variable for controlling bridge client ids. - Remove 32-bit database ID support because htobe64() no longer used. - Multiple client subscriptions to the same topic result in only a single subscription. Bug #744077. 0.9.3 - 20110310 ================ - Set retained message status for QoS 2 messages (bug #726535). - Only abort with an error when opening listening sockets if no address family is available, rather than aborting when any address family is not available. - Don't clean queued messages when a non clean session client reconnects. - Make mosquitto.py compatible with Python <2.6. - Fix mosquitto.h header includes for Windows. 0.9.2 - 20110208 ================ - Only send a single DISCONNECT command when using -l in the pub client. - Set QoS=1 on PUBREL commands to meet protocol spec. - Don't leak sockets on connection failure in the library. - Install man pages when building under cmake. - Fix crash bug on malformed CONNECT message. - Clients are now rejected if their socket peer name cannot be obtained on connection. - Fix a number of potential problems caused when a client with a duplicate id connects. - Install mosquitto.conf under cmake. 0.9.1 - 20101203 ================ - Add missing code for parsing the "bind_address" configuration option. - Fix missing include when compiling with tcp-wrappers support. - Add linker version script for C library to control exported functions. 0.9 - 20101114 ============== - Client and message data is now stored in memory with custom routines rather than a sqlite database. This removes the dependencies on sqlite, pcre and sqlite3-pcre. It also means that the persistent database format has had to be reimplemented in a custom format. Optional support for importing old sqlite databases is provided. - Added IPv6 support for mosquitto and the clients. - Provide username and password support for the clients and client libraries. This is part of the new MQTT v3.1 spec. - The broker supports the username and password connection flags, but will not do anything with the username and password. - Python callback functions now optionally take an extra argument which will return the user object passed to the Mosquitto() constructor, or the calling python object itself if nothing was given to Mosquitto(). - Remove the mosquitto command line option "-i interface". - Remove the mosquitto.conf "interface" variable. - Add support for the listener config variable (replaces the interface variable) - Add support for the bind_address config variable. - Change the port config variable behaviour to match that of rsmb (applies to the default listener only, can be given just once). - Fix QoS 2 protocol compliance - stop sending duplicate messages and handle timeouts correctly. Fixes bug #598290. - Set retain flag correctly for outgoing messages. It should only be set for messages sent in response to a subscribe command (ie. stale data). - Fix bug in returning correct CONNACK result to on_connect client callback. - Don't send client will if it is disconnected for exceeding its keepalive timer. - Fix client library unsubscribe function incorrectly sending a SUBSCRIBE command when it should be UNSUBSCRIBE. - Fix max_inflight_messages and max_queued_messages operation. These parameters now apply only to QoS 1 and 2 messages and are used regardless of the client connection state. - mosquitto.conf now installed to /etc/mosquitto/mosquitto.conf instead of /etc/mosquitto.conf. The /etc/mosquitto/ directory will be used for password and access control files in the future. - Give the compile time option of using 32-bit integers for the database IDs instead of 64-bit integers. This is useful where htobe64()/be64toh() are not available or for embedded systems for example. - The DUP bit is now set correctly when resending PUBREL messages. - A port to Windows native has been partially completed. This currently drops a number of features, including the ability to change configuration parameters and persistent storage. 0.8.3 - 20101004 ================ - Fix QoS 2 protocol compliance - stop sending duplicate messages and handle timeouts correctly. Fixes bug #598290. (backported from future 0.9 code) 0.8.2 - 20100815 ================ - Fix default loop() timeout value in mosquitto.py. Previous value was 0, causing high cpu load. - Fix message handling problem in client library when more than one message was in the client queue. - Fix the logic used to determine whether a QoS>0 message needs to be retried. - Fix the Python sub.py example so that it quits on error. 0.8.1 - 20100812 ================ - Improve python interface - Fix incorrect return value from message delete function - Use logging function to print error messages in clients. - Fix python installation script DESTDIR. - Fix library destination path for 64-bit machines. 0.8 - 20100807 ============== - Topics starting with a / are treated as distinct to those not starting with a /. For example, /topic/path is different to topic/path. This matches the behaviour of rsmb. - Correctly calculate the will QoS on a new client connection (bug #597451). - Add "addresses" configuration file variable as an alias of "address", for better rsmb compatibility. - Bridge clean_session setting is now false, to give more sensible behaviour and be more compatible with rsmb. - Add cleansession variable for configuring bridges. - Add keepalive_interval variable for bridges. - Remove default topic subscription for mosquitto_sub because the old behaviour was too confusing. - Added a C client library, which the pub and sub clients now use. - Added a C++ client library (bound to the C library). - Added a Python client library (bound to the C library). - Added CMake build scripts to allow the library and clients (not the broker) to be compiled natively on Windows. 0.7 - 20100615 ============== - mosquitto_pub can now send null (zero length) messages. - Don't store QoS=0 messages for disconnected clients with subscriptions of QoS>0. - accept() all available sockets when new clients are connecting, rather than just one. - Add option to print debug messages in pub and sub clients. - hg revision is now exported via $SYS/broker/changeset - Send Will when client exceeds keepalive timer and is disconnected. - Check to see if a client has a will before sending it. - Correctly deal with clients connecting with the same id multiple times. - Add compile time option to disable heap memory tracking. - Use poll() instead of select() to allow >1024 clients. - Implement max_connections. - Run VACUUM on in-memory database on receiving SIGUSR2. - Fix bridge keepalive timeouts and reconnects. - Don't attempt to drop root privileges when running on Windows as this isn't well supported (bug #586231). 0.6.1 - 20100506 ================ - Fix DB auto upgrade for messages table. 0.6 - 20100505 ============== - Basic support for connecting multiple MQTT brokers together (bridging). - mosquitto_sub can now subscribe to multiple topics (limited to a global QoS). - mosquitto_pub can now send a file as a message. - mosquitto_pub can now read all of stdin and send it as a message. - mosquitto_pub can now read stdin and send each line as a message. - mosquitto will now correctly run VACUUM on the persistent database on exit. - Implement a more efficient database design, so that only one copy of each message is held in the database, rather than one per subscribed client. - Add the store_cleanup_interval config option for dealing with the internal message store. - Add support for disabling "clean session" for the sub client. - Add support for automatic upgrading of the mosquitto DB from v1 to v2. - Add persistence_file config option to allow changing the filename of the persistence database. This allows multiple mosquitto DBs to be stored in the same location whilst keeping persistence_location compatible with rsmb. - Don't store QoS=0 messages for disconnected clients. Fixes bug #572608. This wasn't correctly fixed in version 0.5. - Don't disconnect clients if they send a PUBLISH with zero length payload (bug #573610). - If a retained message is received with a zero length payload, the retained message for that topic is deleted. - Send through zero length messages. - Produce a warning on unsupported rsmb options instead of quitting. - Describe clean session flag in the mqtt man page. - Implement the max_inflight_messages and max_queued_messages features in the broker. 0.5.4 - 20100311 ================ - Fix memory allocation in mqtt3_fix_sub_topic() (bug #531861). - Remove accidental limit of 100 client connections. - Fix mosquitto_pub handling of messages with QoS>0 (bug #537061). 0.5.3 - 20100303 ================ - Will messages are now only sent when a client disconnects unexpectedly. - Fix all incoming topics/subscriptions that start with a / or contain multiple / in a row (//). - Do actually disconnect client when it sends an empty subscription/topic string. - Add missing $SYS/broker/clients/total to man page. 0.5.2 - 20100302 ================ - Always update last backup time, so that the backup doesn't run every time through the main loop once autosave_interval has been reached. - Report $SYS/broker/uptime in the same format as rsmb. - Make mandatory options obvious in usage output and man page of mosquitto_pub. Fixes bug #529990. - Treat subscriptions with a trailing slash correctly. This should fix bugs #530369 and #530099. 0.5.1 - 20100227 ================ - Must daemonise before pid file is written. 0.5 - 20100227 ============== - No longer store QoS=0 messages for disconnected clients that do not have clean start set. - Rename msg_timeout option to retry_interval for better rsmb compatibility. - Change persistence behaviour. The database is now stored in memory even if persistence is enabled. It is written to disk when mosquitto exits and also at periodic intervals as defined by the new autosave_interval option. - The writing of the persistence database may be forced by sending mosquitto the SIGUSR1 signal. - Clients that do not send CONNECT as their first command are now disconnected. - Boolean configuration values may now be specified with true/false as well as 1/0. - Log message on CONNECT with invalid protocol or protocol version. - Default sqlite3-pcre path on Linux is now /usr/lib/sqlite3/pcre.so to match future sqlite3-pcre packages. - Add mosquitto_sub and mosquitto_pub, simple clients for subscribe/publish. - Add man pages for clients. - Add general man page on mqtt. - Root privileges are now dropped only after attempting to write a pid file (if configured). This means that the pid file can be written to /var/run/ directly and should fix bug #523183. 0.4.2 - 20100203 ================ - Fix segfault on client connect with invalid protocol name/version. 0.4.1 - 20100112 =============== - Fix regex used for finding retained messages to send on new subscription. 0.4 - 20100105 ============== - Added support for wildcard subscriptions using + and #. - All network operations are now non-blocking and can cope with partial packets, meaning that networking should be a lot more reliable. - Total messsages/bytes sent/received are now available in $SYS. - Improved logging information - use client ip address and id instead of socket number. - Broker build timestamp is available in $SYS. - Keepalive==0 is now correctly treated as "never disconnect". - Fixed manpage installation. - Fixed incorrect $SYS hierarchy locations in documentation and code. - Debug type log messages are no longer sent to "topics". - Default logging destination no longer includes "topics" to prevent possible error logging to the db before it is initialised. - Periodic $SYS messages can now be disabled. - stdout and stderr are flushed when logging to them to give more timely updates. - dup is now set correctly when resending messages. - Database format bumped due to topic column naming fix. 0.3 - 20091217 ============== - The port option in the configuration file and --port command line argument may now be given any number of times to make mosquitto listen on multiple sockets. - Add new config file and command line option "interface" to specify an interface to listen on, rather than all interfaces. - Added host access control through tcp-wrappers support. - Set SO_REUSEADDR on the listening socket so restart is much quicker. - Added support for tracking current heap memory usage - this is published on the topic "$SYS/broker/heap/current size" - Added code for logging to stderr, stdout, syslog and topics. - Added logging to numerous places - still plenty of scope for more. 0.2 - 20091204 ============== - Replaced the command line option --foreground with --daemon, swapping the default behaviour. - Added the command line option --config-file, to specify a config file to load. If this is not given, no config file is load and the default options are used. - Added the command line option --port for specifying the port to listen on. This overrides values in the config file. - Don't use persistence by default. - Default behaviour is now more sane when run by a normal user with no command line options (combination of above changes). - Added option user to config file, defaulting to a value of mosquitto. If this value isn't blank and mosquitto is started by root, then it will drop privileges by changing to the user and its primary group. This replaces the current behaviour of refusing to start if run by root. - Fix non-persistent mode, which would never work in the previous release. - Added information on default values of msg_timeout and sys_interval to the mosquitto.conf man page. (closes bug #492045). eclipse-mosquitto-mosquitto-691eab3/LICENSE.txt000066400000000000000000000003141514232433600215020ustar00rootroot00000000000000This project is dual licensed under the Eclipse Public License 2.0 OR the Eclipse Distribution License 1.0 as described in the epl-v20 and edl-v10 files. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause eclipse-mosquitto-mosquitto-691eab3/Makefile000066400000000000000000000120271514232433600213230ustar00rootroot00000000000000include config.mk DIRS=libcommon lib apps client plugins src DOCDIRS=man DISTDIRS=man DISTFILES= \ apps/ \ client/ \ cmake/ \ common/ \ dashboard/ \ deps/ \ doc/ \ docker/ \ examples/ \ fuzzing/ \ include/ \ installer/ \ libcommon/ \ lib/ \ logo/ \ make/ \ man/ \ misc/ \ plugins/ \ security/ \ service/ \ snap/ \ src/ \ test/ \ www/ \ .github \ \ .editorconfig \ .gitignore \ .uncrustify.cfg \ buildtest.py \ codecov.yml \ CMakeLists.txt \ CITATION.cff \ CONTRIBUTING.md \ ChangeLog.txt \ format.sh \ LICENSE.txt \ Makefile \ about.html \ aclfile.example \ config.h \ config.mk \ edl-v10 \ epl-v20 \ libmosquitto.pc.in \ libmosquittopp.pc.in \ mosquitto.conf \ NOTICE.md \ pskfile.example \ pwfile.example \ README-compiling.md \ README-letsencrypt.md \ README-tests.md \ README-windows.txt \ README.md \ run_tests.py \ set-version.sh \ SECURITY.md \ THANKS.txt \ vcpkg.json .PHONY : all mosquitto api docs binary check clean reallyclean test test-compile install uninstall dist sign copy localdocker all : $(MAKE_ALL) api : mkdir -p api p naturaldocs -o HTML api -i include -p p rm -rf p docs : set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d}; done binary : mosquitto binary-all : mosquitto test-compile mosquitto : ifeq ($(UNAME),Darwin) $(error Please compile using CMake on Mac OS X) endif set -e; for d in ${DIRS}; do $(MAKE) -C $${d}; done fuzzing : mosquitto $(MAKE) -C fuzzing clean : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} clean; done set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d} clean; done $(MAKE) -C test clean $(MAKE) -C fuzzing clean reallyclean : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} reallyclean; done set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d} reallyclean; done $(MAKE) -C test reallyclean -rm -f *.orig check : test test-compile: mosquitto lib $(MAKE) -C test test-compile $(MAKE) -C plugins test-compile test : mosquitto lib apps test-compile $(MAKE) -C test test $(MAKE) -C plugins test ptest : mosquitto $(MAKE) -C test ptest utest : mosquitto $(MAKE) -C test utest install : all set -e; for d in ${DIRS}; do $(MAKE) -C $${d} install; done ifeq ($(WITH_DOCS),yes) set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d} install; done endif $(INSTALL) -d "${DESTDIR}/etc/mosquitto" $(INSTALL) -m 644 mosquitto.conf "${DESTDIR}/etc/mosquitto/mosquitto.conf.example" $(INSTALL) -m 644 aclfile.example "${DESTDIR}/etc/mosquitto/aclfile.example" $(INSTALL) -m 644 pwfile.example "${DESTDIR}/etc/mosquitto/pwfile.example" $(INSTALL) -m 644 pskfile.example "${DESTDIR}/etc/mosquitto/pskfile.example" $(INSTALL) -d "${DESTDIR}$(prefix)/include/mosquitto" $(INSTALL) include/mosquitto/*.h "${DESTDIR}${prefix}/include/mosquitto/" $(INSTALL) include/mosquitto.h "${DESTDIR}${prefix}/include/mosquitto.h" $(INSTALL) include/mosquitto_broker.h "${DESTDIR}${prefix}/include/mosquitto_broker.h" $(INSTALL) include/mosquitto_plugin.h "${DESTDIR}${prefix}/include/mosquitto_plugin.h" $(INSTALL) include/mosquittopp.h "${DESTDIR}${prefix}/include/mosquittopp.h" $(INSTALL) include/mqtt_protocol.h "${DESTDIR}${prefix}/include/mqtt_protocol.h" uninstall : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} uninstall; done rm -f "${DESTDIR}/etc/mosquitto/mosquitto.conf.example" rm -f "${DESTDIR}/etc/mosquitto/aclfile.example" rm -f "${DESTDIR}/etc/mosquitto/pwfile.example" rm -f "${DESTDIR}/etc/mosquitto/pskfile.example" rm -f "${DESTDIR}${prefix}/include/mosquitto.h" rm -f "${DESTDIR}${prefix}/include/mosquitto/broker.h" rm -f "${DESTDIR}${prefix}/include/mosquitto/broker_control.h" rm -f "${DESTDIR}${prefix}/include/mosquitto/broker_plugin.h" rm -f "${DESTDIR}${prefix}/include/mosquitto/libmosquittopp.h" rm -f "${DESTDIR}${prefix}/include/mosquitto/mqtt_protocol.h" rm -f "${DESTDIR}${prefix}/include/mosquitto_broker.h" rm -f "${DESTDIR}${prefix}/include/mosquitto_plugin.h" rm -f "${DESTDIR}${prefix}/include/mosquittopp.h" rm -f "${DESTDIR}${prefix}/include/mqtt_protocol.h" dist : reallyclean set -e; for d in ${DISTDIRS}; do $(MAKE) -C $${d} dist; done mkdir -p dist/mosquitto-${VERSION} cp -r ${DISTFILES} dist/mosquitto-${VERSION}/ cd dist; tar -zcf mosquitto-${VERSION}.tar.gz mosquitto-${VERSION}/ sign : dist cd dist; gpg --detach-sign -a mosquitto-${VERSION}.tar.gz copy : sign cd dist; scp mosquitto-${VERSION}.tar.gz mosquitto-${VERSION}.tar.gz.asc mosquitto:site/mosquitto.org/files/source/ scp ChangeLog.txt mosquitto:site/mosquitto.org/ coverage : lcov --capture -d apps -d client -d lib -d plugins -d src --output-file coverage.info --no-external --ignore-errors empty genhtml --ignore-errors inconsistent coverage.info --output-directory out localdocker : reallyclean set -e; for d in ${DISTDIRS}; do $(MAKE) -C $${d} dist; done rm -rf dockertmp/ mkdir -p dockertmp/mosquitto-${VERSION} cp -r ${DISTFILES} dockertmp/mosquitto-${VERSION}/ cd dockertmp/; tar -zcf mosq.tar.gz mosquitto-${VERSION}/ cp dockertmp/mosq.tar.gz docker/local rm -rf dockertmp/ cd docker/local && docker build . -t eclipse-mosquitto:local --build-arg VERSION=${VERSION} eclipse-mosquitto-mosquitto-691eab3/NOTICE.md000066400000000000000000000045531514232433600211730ustar00rootroot00000000000000# Notices for Mosquitto This content is produced and maintained by the Eclipse Mosquitto project. * Project home: https://projects.eclipse.org/projects/iot.mosquitto ## Trademarks Eclipse Mosquitto trademarks of the Eclipse Foundation. Eclipse, and the Eclipse Logo are registered trademarks of the Eclipse Foundation. ## Copyright All content is the property of the respective authors or their employers. For more information regarding authorship of content, please consult the listed source code repository logs. ## Declared Project Licenses This program and the accompanying materials are made available under the terms of the Eclipse Public License v2.0 which is available at http://www.eclipse.org/legal/epl-v10.html, or the BSD 3 Clause license. SPDX-License-Identifier: EPL-2.0 or BSD-3-Clause ## Source Code The project maintains the following source code repositories: * https://github.com/eclipse-mosquitto/mosquitto ## Third-party Content This project makes use of the follow third party projects. c-ares * License: MIT * Project: https://c-ares.org/ * Source: https://github.com/c-ares/c-ares cJSON (1.7.x) * License: MIT * Project: https://github.com/DaveGamble/cJSON googletest * License: BSD-3-Clause * Project: https://github.com/google/googletest libedit (3.1) * License: BSD-3-Clause * Project: https://thrysoee.dk/editline/ * Source: https://thrysoee.dk/editline/libedit-20250104-3.1.tar.gz libwebsockets (4.x) * License: MIT * Project: https://github.com/warmcat/libwebsockets microhttpd * License: GNU LGPL v2.1+ * Project: https://www.gnu.org/software/libmicrohttpd/ * Source: git://git.gnunet.org/libmicrohttpd2.git openssl (3.x.x) * License: Apache 2.0 * Project: https://openssl.org * Source: https://github.com/openssl/openssl picohttpparser * License: MIT * Project: github.com/h2o/picohttpparser uthash (2.3.0) * License: BSD revised (https://troydhanson.github.io/uthash/license.html) * Project: https://github.com/troydhanson/uthash ## Cryptography Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. eclipse-mosquitto-mosquitto-691eab3/README-compiling.md000066400000000000000000000031631514232433600231220ustar00rootroot00000000000000The following packages can be used to add features to mosquitto. * cJSON - required * c-ares (libc-ares-dev on Debian based systems) - optional, enable with `WITH_SRV=yes` * libedit - for mosquitto_ctrl interactive shell - optional, disable with `WITH_EDITLINE=no` * libmicrohttpd - for broker http api support - optional, disable with `WITH_HTTP_API=no` * openssl (libssl-dev on Debian based systems) - optional, disable with `WITH_TLS=no` * pthreads - for client library thread support. This is required to support the `mosquitto_loop_start()` and `mosquitto_loop_stop()` functions. If compiled without pthread support, the library isn't guaranteed to be thread safe. * sqlite3 - for persistence support in the broker - optional, disable with `WITH_SQLITE=no` * uthash / utlist - bundled versions of these headers are provided, disable their use with `WITH_BUNDLED_DEPS=no` * xsltproc (xsltproc and docbook-xsl on Debian based systems) - only needed when building from git sources - disable with `WITH_DOCS=no` For testing, the following packages are required: * cunit * googletest / gmock * microsocks * python To compile you may either use CMake, or on Linux look in the file `config.mk` for compile options and use plain `make`. Up to version 2.1, the recommendation was to use CMake for Windows and Mac, and to use make everywhere else. The recommendation now is to use cmake in all cases, and that the plain makefiles will be removed in version 3.0. If you have any questions, problems or suggestions (particularly related to installing on a more unusual device) then please get in touch using the details in README.md. eclipse-mosquitto-mosquitto-691eab3/README-letsencrypt.md000066400000000000000000000016531514232433600235170ustar00rootroot00000000000000# Using Lets Encrypt with Mosquitto On Unix like operating systems, Mosquitto will attempt to drop root access as soon as it has loaded its configuration file, but before it has activated any of that configuration. This means that if you are using Lets Encrypt TLS certificates, it will be unable to access the certificates and private keys typically located in /etc/letsencrypt/live/ To help with this problem there is an example `deploy` renewal hook script in `misc/letsencrypt/mosquitto-copy.sh` which shows how the certificate and private key for a mosquitto broker can be copied to /etc/mosquitto/certs/ and given the correct ownership and permissions so the broker can access them, but no other user can. It then signals Mosquitto to reload the certificates. Use of this script allows you to happily use Lets Encrypt certificates with Mosquitto without needing root access for Mosquitto, and without having to restart Mosquitto. eclipse-mosquitto-mosquitto-691eab3/README-tests.md000066400000000000000000000012001514232433600222710ustar00rootroot00000000000000# Tests ## Running The Mosquitto test suite can be invoked using either of ``` make test make check ``` The tests run in series and due to the nature of some of the tests being made can take a while. ## Parallel Tests To run the tests with some parallelism, use ``` make ptest ``` This runs up to 20 tests in parallel at once, yielding much faster overall run time at the expense of having up to 20 instances of Python running at once. This is not a particularly CPU intensive option, but does require more memory and may be unsuitable on e.g. a Raspberry Pi. ## Dependencies The tests require Python 3 and CUnit to be installed. eclipse-mosquitto-mosquitto-691eab3/README-windows.txt000066400000000000000000000056611514232433600230570ustar00rootroot00000000000000Mosquitto for Windows ===================== Mosquitto for Windows comes in 64-bit and 32-bit flavours. All dependencies are provided in the installer. Installing ---------- Running the installer will present the normal type of graphical installer. If you want to install without starting the graphical part of the installer, you can do so by running it from a cmd prompt with the `/S` switch: ``` mosquitto-2.0.12-install-windows-x64.exe /S ``` You can override the installation directory with the `/D` switch: ``` mosquitto-2.0.12-install-windows-x64.exe /S /D=:\mosquitto ``` Capabilities ------------ Some versions of Windows have limitations on the number of concurrent connections due to the Windows API being used. In modern versions of Windows, e.g. Windows 10 or Windows Server 2019, this is approximately 8192 connections. In earlier versions of Windows, this limit is 2048 connections. Windows Service --------------- If you wish, mosquitto can be installed as a Windows service so you can start/stop it from the control panel as well as running it as a normal executable. When running as a service, the configuration file used is mosquitto.conf in the directory defined by the %MOSQUITTO_DIR% environment variable. This will be set to the directory that you installed to by default. If you want to install/uninstall mosquitto as a Windows service run from the command line as follows: C:\Program Files\mosquitto\mosquitto install C:\Program Files\mosquitto\mosquitto uninstall It is possible to install and run multiple instances of a Mosquitto service, as of version 2.1. To do this, copy the mosquitto executable to a new *name* and run the service install as above. The service will load a configuration file mosquitto.conf from the directory defined by the environment variable "_DIR". For this reason it is suggested to keep the executable name consisting of alphanumeric and '_' characters. Any other character will be replaced with '_'. For example, if you copy mosquitto.exe to eclipse_mosquitto.exe, you would run these commands to install/uninstall: C:\Program Files\mosquitto\eclipse_mosquitto install C:\Program Files\mosquitto\eclipse_mosquitto uninstall And the service would try to load the config file at %ECLIPSE_MOSQUITTO_DIR%/mosquitto.conf The new service will appear in the service list as "Mosquitto Broker (eclipse_mosquitto.exe)". Logging ------- If you use `log_dest file ...` in your configuration, the log file will be created with security permissions for the current user only. If running as a service, this means the SYSTEM user. You will only be able to view the log file if you add permissions for yourself or whatever user you wish to view the logs. Signals ------- Starting with version 2.1, it is possible to use the mosquitto_signal command to send signals to the broker, in a similar way to sending signals on a posix based system. See https://mosquitto.org/man/mosquitto_signal-1.html for more details. eclipse-mosquitto-mosquitto-691eab3/README.md000066400000000000000000000123071514232433600211430ustar00rootroot00000000000000Eclipse Mosquitto ================= Mosquitto is an open source implementation of a server for version 5.0, 3.1.1, and 3.1 of the MQTT protocol. It also includes a C and C++ client library, the `mosquitto_pub` `mosquitto_rr`, and `mosquitto_sub` utilities for publishing and subscribing, and the `mosquitto_ctrl`, `mosquitto_signal`, and `mosquitto_passwd` applications for helping administer the broker. ## Links See the following links for more information on MQTT: - Community page: - MQTT v3.1.1 standard: - MQTT v5.0 standard: Mosquitto project information is available at the following locations: - Main homepage: - Find existing bugs or submit a new bug: - Source code repository: There is also a public test server available at ## Installing See for details on installing binaries for various platforms. ## Quick start If you have installed a binary package the broker should have been started automatically. If not, it can be started with a very basic configuration: mosquitto Then use `mosquitto_sub` to subscribe to a topic: mosquitto_sub -t 'test/topic' -v And to publish a message: mosquitto_pub -t 'test/topic' -m 'hello world' Note that starting the broker like this allows anonymous/unauthenticated access but only from the local computer, so it's only really useful for initial testing. If you want to have clients from another computer connect, you will need to provide a configuration file. If you have installed from a binary package, you will probably already have a configuration file at somewhere like `/etc/mosquitto/mosquitto.conf`. If you've compiled from source, you can write your config file then run as `mosquitto -c /path/to/mosquitto.conf`. To start your config file you define a listener and you will need to think about what authentication you require. It is not advised to run your broker with anonymous access when it is publicly available. For details on how to do this, look at the [authentication methods](https://mosquitto.org/documentation/authentication-methods/) available and the [dynamic security plugin](https://mosquitto.org/documentation/dynamic-security/). ## Documentation Documentation for the broker, clients and client library API can be found in the man pages, which are available online at . There are also pages with an introduction to the features of MQTT, the `mosquitto_passwd` utility for dealing with username/passwords, and a description of the configuration file options available for the broker. Detailed client library API documentation can be found at ## Building from source To build from source the recommended route for end users is to download the archive from . On Windows and Mac, use `cmake` to build. On other platforms, just run `make` to build. For Windows, see also `README-windows.md`. If you are building from the git repository then the documentation will not already be built. Use `make binary` to skip building the man pages, or install `docbook-xsl` on Debian/Ubuntu systems. ### Build Dependencies * cJSON - required * c-ares (libc-ares-dev on Debian based systems) - optional, enable with `WITH_SRV=yes` * libedit - for mosquitto_ctrl interactive shell - optional, disable with `WITH_EDITLINE=no` * libmicrohttpd - for broker http api support - optional, disable with `WITH_HTTP_API=no` * openssl (libssl-dev on Debian based systems) - optional, disable with `WITH_TLS=no` * pthreads - for client library thread support. This is required to support the `mosquitto_loop_start()` and `mosquitto_loop_stop()` functions. If compiled without pthread support, the library isn't guaranteed to be thread safe. * sqlite3 - for persistence support in the broker - optional, disable with `WITH_SQLITE=no` * uthash / utlist - bundled versions of these headers are provided, disable their use with `WITH_BUNDLED_DEPS=no` * xsltproc (xsltproc and docbook-xsl on Debian based systems) - only needed when building from git sources - disable with `WITH_DOCS=no` Equivalent options for enabling/disabling features are available when using the CMake build. It is also possible to enable/disable building of specific plugins in the CMake build. ### Building mosquitto - Using vcpkg You can download and install mosquitto using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install ./vcpkg install mosquitto The mosquitto port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. ## Credits Mosquitto was written by Roger Light . There have been substantial contributions by other people in the community both in terms of code and other help. eclipse-mosquitto-mosquitto-691eab3/SECURITY.md000066400000000000000000000003301514232433600214460ustar00rootroot00000000000000# Security Policy ## Reporting a Vulnerability If you think you have found a security vulnerability in Mosquitto, please follow the steps on [Eclipse Security](https://www.eclipse.org/security/) page to report it. eclipse-mosquitto-mosquitto-691eab3/THANKS.txt000066400000000000000000000027251514232433600214200ustar00rootroot00000000000000These people have reported bugs / provided patches / done something else to aid the mosquitto project. Thanks to you all! If you think I've missed you off the list, please rest assured that it wasn't intentional and get in touch and I'll fix it. Adam Rudd Alexandre Zia Andrew Elwell Andy Piper Andy Stanford-Clark Anton Prokofiev Axel Busch Bart Van Der Meerssche BD Walker Ben Tobin Bob Blackrock Brad Stancel Brett Francis Chris Willing Christian Sampaio Christoph Krey Craig Hollabaugh Cristian Manuel Vertiz Fernandez Dan Anderson Daniel Deadwyler Daniel O'Conner Dariusz Suchojad Darren Oliver David Huang David Monro Dirk O. Kaar Dominik Obermaier Dominik Zajac Ed Morris Fabian Ruff Fang Chong Frank Hansen Gary Koh Gianfranco Costamagna Guido Hinderberger Hiram van Paassen Jan-Piet Mens Joan Zapata Joe McIlvain Karl Palsson Larry Lendo Martin Assarsson Martin Rauscher Martin van Wingerden Marty Lee Matt Daubney Michael C Michael Frisch Michael Hekel Michael Laing Michael Rushton Mike Bush Milan Tucic Neil Bothwick Nicholas Humfrey Nicholas O'Leary Nithin Kumar Patrick Geary Paul Diston Peter Magnusson Pryce Jones Peter George Richard Eagland Rob Pridham Robin Gingras Roland de Boo Sebastian Kroll Sergio Rubio Sharon Ben-Asher sskaje Stefan Hudelmaier Stefano Costa Stephen Owens Stephen Woods Steven Lougheed Surendra Reddy Szymon Kochanski Tammo Besemann Thomas Hilbig Tobias Assarsson Toby Jaffey Tomas Novotny Vicente Ruiz Wayne Ingram Yun Wu Yuvraaj Kelkar 賴 冠廷 eclipse-mosquitto-mosquitto-691eab3/about.html000066400000000000000000000047001514232433600216620ustar00rootroot00000000000000 About

About This Content

May 8, 2014

License

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Eclipse Public License Version 2.0 ("EPL") and Eclipse Distribution License Version 1.0 ("EDL"). A copy of the EPL is available at https://www.eclipse.org/legal/epl-2.0/ and a copy of the EDL is available at http://www.eclipse.org/org/documents/edl-v10.php. For purposes of the EPL, "Program" will mean the Content.

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

Third Party Content

The Content includes items that have been sourced from third parties as set out below. If you did not receive this Content directly from the Eclipse Foundation, the following is provided for informational purposes only, and you should look to the Redistributor's license for terms and conditions of use.

libwebsockets 2.4.2

This project makes use of the libwebsockets library.

The use of libwebsockets is based on the terms and conditions of the LGPL 2.1 with some specific exceptions. https://github.com/warmcat/libwebsockets/blob/v2.4.2/LICENSE

When libwebsockets is distributed with the project, it is being used subject to the Static Linking Exception (Section 2) of the License. As a result, the content is not subject to the LGPL 2.1.

eclipse-mosquitto-mosquitto-691eab3/aclfile.example000066400000000000000000000005531514232433600226400ustar00rootroot00000000000000# This affects access control for clients with no username. topic read $SYS/# # This only affects clients with username "roger". user roger topic foo/bar # This affects all clients. # Note that this is the only topic it is possible to grant access to writing to # the $SYS tree. All other topics are always denied. pattern write $SYS/broker/connection/%c/state eclipse-mosquitto-mosquitto-691eab3/apps/000077500000000000000000000000001514232433600206245ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/apps/CMakeLists.txt000066400000000000000000000002011514232433600233550ustar00rootroot00000000000000add_subdirectory(db_dump) add_subdirectory(mosquitto_ctrl) add_subdirectory(mosquitto_passwd) add_subdirectory(mosquitto_signal) eclipse-mosquitto-mosquitto-691eab3/apps/Makefile000066400000000000000000000012631514232433600222660ustar00rootroot00000000000000R=.. include ${R}/config.mk DIRS= \ mosquitto_ctrl \ mosquitto_passwd \ mosquitto_signal ifeq ($(WITH_PERSISTENCE),yes) DIRS+=db_dump endif .PHONY : all binary check clean reallyclean test install uninstall all : set -e; for d in ${DIRS}; do $(MAKE) -C $${d}; done binary : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} $@; done clean : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} $@; done reallyclean : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} $@; done check : test test : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} $@; done install : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} $@; done uninstall : set -e; for d in ${DIRS}; do $(MAKE) -C $${d} $@; done eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/000077500000000000000000000000001514232433600222365ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/CMakeLists.txt000066400000000000000000000021661514232433600250030ustar00rootroot00000000000000add_executable(mosquitto_db_dump db_dump.c db_dump.h json.c print.c stubs.c ../../common/json_help.c ../../common/json_help.h ../../lib/packet_datatypes.c ../../lib/property_mosq.c ../../src/persist_read.c ../../src/persist_read_v234.c ../../src/persist_read_v5.c ../../src/topic_tok.c ) target_compile_definitions(mosquitto_db_dump PRIVATE WITH_BROKER WITH_PERSISTENCE ) target_include_directories(mosquitto_db_dump PRIVATE "${mosquitto_SOURCE_DIR}" "${mosquitto_SOURCE_DIR}/common" "${mosquitto_SOURCE_DIR}/include" "${mosquitto_SOURCE_DIR}/lib" "${mosquitto_SOURCE_DIR}/libcommon" "${mosquitto_SOURCE_DIR}/src" "${OPENSSL_INCLUDE_DIR}" "${CJSON_INCLUDE_DIRS}" ) if(WITH_BUNDLED_DEPS) target_include_directories(mosquitto_db_dump PRIVATE "${mosquitto_SOURCE_DIR}/deps" ) endif() if(WITH_TLS) target_link_libraries(mosquitto_db_dump PRIVATE config-header OpenSSL::SSL) endif() if(WIN32) target_link_libraries(mosquitto_db_dump PRIVATE ws2_32) endif() target_link_libraries(mosquitto_db_dump PRIVATE libmosquitto_common) install(TARGETS mosquitto_db_dump RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/Makefile000066400000000000000000000031631514232433600237010ustar00rootroot00000000000000R=../.. include ${R}/config.mk LOCAL_CFLAGS+= LOCAL_CPPFLAGS+=-I${R} -I${R}/lib -I${R}/src -I${R}/common -DWITH_BROKER LOCAL_LDFLAGS+= LOCAL_LDADD+=-lcjson -lm ${LIBMOSQ_COMMON} # ------------------------------------------ # Compile time options # ------------------------------------------ include ${R}/make/broker.mk # ------------------------------------------ # Targets # ------------------------------------------ OBJS = \ db_dump.o \ json.o \ print.o \ stubs.o BROKER_OBJS = \ ${R}/src/packet_datatypes.o \ ${R}/src/persist_read.o \ ${R}/src/persist_read_v234.o \ ${R}/src/persist_read_v5.o \ ${R}/src/property_mosq.o \ ${R}/src/topic_tok.o OBJS_EXTERNAL = \ json_help.o ifeq ($(UNAME),Linux) LIBS:=$(LIBS) -lrt endif .PHONY: all clean reallyclean ifeq ($(WITH_FUZZING),yes) all : mosquitto_db_dump.a else all : mosquitto_db_dump endif mosquitto_db_dump : ${OBJS} ${BROKER_OBJS} ${OBJS_EXTERNAL} ${CROSS_COMPILE}${CC} $^ -o $@ ${LOCAL_LDFLAGS} ${LOCAL_LDADD} mosquitto_db_dump.a : ${OBJS} ${BROKER_OBJS} ${OBJS_EXTERNAL} ${CROSS_COMPILE}$(AR) cr $@ $^ ${OBJS} : %.o:%.c db_dump.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ ${BROKER_OBJS} : $(MAKE) -C ${R}/src $(notdir $@) json_help.o : ${R}/common/json_help.c ${R}/common/json_help.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ reallyclean: clean clean : -rm -f $(OBJS) $(OBJS_EXTERNAL) $(BROKER_OBJS) mosquitto_db_dump mosquitto_db_dump.a *.gcda *.gcno install: $(INSTALL) -d "${DESTDIR}$(prefix)/bin" $(INSTALL) ${STRIP_OPTS} mosquitto_db_dump "${DESTDIR}${prefix}/bin/mosquitto_db_dump" uninstall: eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/db_dump.c000066400000000000000000000310511514232433600240140ustar00rootroot00000000000000/* Copyright (c) 2010-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include #include #include #include #include #include "db_dump.h" #include #include #define mosquitto_malloc(A) malloc((A)) #define mosquitto_free(A) free((A)) #define _mosquitto_malloc(A) malloc((A)) #define _mosquitto_free(A) free((A)) #include #include "db_dump.h" #ifdef __ANDROID__ #include #endif struct client_data { UT_hash_handle hh_id; char *id; uint32_t subscriptions; uint32_t subscription_size; int messages; long message_size; }; struct base_msg_chunk { UT_hash_handle hh; dbid_t store_id; uint32_t length; }; struct mosquitto_db db; extern uint32_t db_version; static int stats = 0; static int client_stats = 0; static int do_print = 1; static int do_json = 0; /* Counts */ static long cfg_count = 0; static long client_count = 0; static long client_msg_count = 0; static long base_msg_count = 0; static long retain_count = 0; static long sub_count = 0; /* ====== */ struct client_data *clients_by_id = NULL; struct base_msg_chunk *msgs_by_id = NULL; static void free__sub(struct P_sub *chunk) { free(chunk->clientid); free(chunk->topic); } static void free__client(struct P_client *chunk) { free(chunk->username); free(chunk->clientid); } static void free__client_msg(struct P_client_msg *chunk) { free(chunk->clientid); } static void free__base_msg(struct P_base_msg *chunk) { free(chunk->topic); free(chunk->payload); mosquitto_property_free_all(&chunk->properties); } static int dump__cfg_chunk_process(FILE *db_fd, uint32_t length) { struct PF_cfg chunk; int rc; cfg_count++; memset(&chunk, 0, sizeof(struct PF_cfg)); if(db_version == 6 || db_version == 5){ rc = persist__chunk_cfg_read_v56(db_fd, &chunk); }else{ rc = persist__chunk_cfg_read_v234(db_fd, &chunk); } if(rc){ fprintf(stderr, "Error: Corrupt persistent database.\n"); return rc; } if(do_print){ printf("DB_CHUNK_CFG:\n"); } if(do_print){ printf("\tLength: %d\n", length); } if(do_print){ printf("\tShutdown: %d\n", chunk.shutdown); } if(do_print){ printf("\tDB ID size: %d\n", chunk.dbid_size); } if(chunk.dbid_size != sizeof(dbid_t)){ fprintf(stderr, "Error: Incompatible database configuration (dbid size is %d bytes, expected %zu)", chunk.dbid_size, sizeof(dbid_t)); return MOSQ_ERR_INVAL; } if(do_print){ printf("\tLast DB ID: %" PRIu64 "\n", chunk.last_db_id); } return 0; } static int dump__client_chunk_process(FILE *db_fd, uint32_t length) { struct P_client chunk; int rc = 0; struct client_data *cc = NULL; client_count++; memset(&chunk, 0, sizeof(struct P_client)); if(db_version == 6 || db_version == 5){ rc = persist__chunk_client_read_v56(db_fd, &chunk, db_version); }else{ rc = persist__chunk_client_read_v234(db_fd, &chunk, db_version); } if(rc){ fprintf(stderr, "Error: Corrupt persistent database.\n"); return rc; } if(client_stats && chunk.clientid){ cc = calloc(1, sizeof(struct client_data)); if(!cc){ fprintf(stderr, "Error: Out of memory.\n"); free(chunk.clientid); return MOSQ_ERR_NOMEM; } cc->id = strdup(chunk.clientid); HASH_ADD_KEYPTR(hh_id, clients_by_id, cc->id, strlen(cc->id), cc); } if(do_json){ json_add_client(&chunk); } if(do_print){ print__client(&chunk, length); } free__client(&chunk); return 0; } static int dump__client_msg_chunk_process(FILE *db_fd, uint32_t length) { struct P_client_msg chunk; struct client_data *cc; struct base_msg_chunk *msc; int rc; client_msg_count++; memset(&chunk, 0, sizeof(struct P_client_msg)); if(db_version == 6 || db_version == 5){ rc = persist__chunk_client_msg_read_v56(db_fd, &chunk, length); }else{ rc = persist__chunk_client_msg_read_v234(db_fd, &chunk); } if(rc){ fprintf(stderr, "Error: Corrupt persistent database.\n"); return rc; } if(client_stats && chunk.clientid){ HASH_FIND(hh_id, clients_by_id, chunk.clientid, strlen(chunk.clientid), cc); if(cc){ cc->messages++; cc->message_size += length; HASH_FIND(hh, msgs_by_id, &chunk.F.store_id, sizeof(dbid_t), msc); if(msc){ cc->message_size += msc->length; } } } if(do_json){ json_add_client_msg(&chunk); } if(do_print){ print__client_msg(&chunk, length); } free__client_msg(&chunk); return 0; } static int dump__base_msg_chunk_process(FILE *db_fptr, uint32_t length) { struct P_base_msg chunk; struct mosquitto__base_msg *stored = NULL; int64_t message_expiry_interval64; uint32_t message_expiry_interval; int rc = 0; struct base_msg_chunk *mcs; base_msg_count++; memset(&chunk, 0, sizeof(struct P_base_msg)); if(db_version == 6 || db_version == 5){ rc = persist__chunk_base_msg_read_v56(db_fptr, &chunk, length); }else{ rc = persist__chunk_base_msg_read_v234(db_fptr, &chunk, db_version); } if(rc){ fprintf(stderr, "Error: Corrupt persistent database.\n"); return rc; } if(chunk.F.expiry_time > 0){ message_expiry_interval64 = chunk.F.expiry_time - time(NULL); if(message_expiry_interval64 < 0 || message_expiry_interval64 > UINT32_MAX){ message_expiry_interval = 0; }else{ message_expiry_interval = (uint32_t)message_expiry_interval64; } }else{ message_expiry_interval = 0; } stored = mosquitto_calloc(1, sizeof(struct mosquitto__base_msg)); if(stored == NULL){ fprintf(stderr, "Error: Out of memory.\n"); mosquitto_free(chunk.source.id); mosquitto_free(chunk.source.username); mosquitto_free(chunk.topic); mosquitto_free(chunk.payload); return MOSQ_ERR_NOMEM; } stored->data.store_id = chunk.F.store_id; stored->data.source_mid = chunk.F.source_mid; stored->data.topic = chunk.topic; stored->data.qos = chunk.F.qos; stored->data.retain = chunk.F.retain; stored->data.payloadlen = chunk.F.payloadlen; stored->data.payload = chunk.payload; stored->data.properties = chunk.properties; rc = db__message_store(&chunk.source, stored, &message_expiry_interval, mosq_mo_client); if(do_json){ json_add_base_msg(&chunk); } mosquitto_free(chunk.source.id); mosquitto_free(chunk.source.username); chunk.source.id = NULL; chunk.source.username = NULL; if(rc == MOSQ_ERR_SUCCESS){ stored->source_listener = chunk.source.listener; stored->data.store_id = chunk.F.store_id; HASH_ADD(hh, db.msg_store, data.store_id, sizeof(dbid_t), stored); }else{ fprintf(stderr, "Error: Out of memory.\n"); return rc; } if(client_stats){ mcs = calloc(1, sizeof(struct base_msg_chunk)); if(!mcs){ fprintf(stderr, "Error: Out of memory.\n"); return MOSQ_ERR_NOMEM; } mcs->store_id = chunk.F.store_id; mcs->length = length; HASH_ADD(hh, msgs_by_id, store_id, sizeof(dbid_t), mcs); } if(do_print){ print__base_msg(&chunk, length); } free__base_msg(&chunk); return 0; } static int dump__retain_chunk_process(FILE *db_fd, uint32_t length) { struct P_retain chunk; int rc; retain_count++; if(do_print){ printf("DB_CHUNK_RETAIN:\n"); } if(do_print){ printf("\tLength: %d\n", length); } if(db_version == 6 || db_version == 5){ rc = persist__chunk_retain_read_v56(db_fd, &chunk); }else{ rc = persist__chunk_retain_read_v234(db_fd, &chunk); } if(rc){ fprintf(stderr, "Error: Corrupt persistent database.\n"); return rc; } if(do_json){ json_add_retained_msg(&chunk); } if(do_print){ printf("\tStore ID: %" PRIu64 "\n", chunk.F.store_id); } return 0; } static int dump__sub_chunk_process(FILE *db_fd, uint32_t length) { int rc = 0; struct P_sub chunk; struct client_data *cc; sub_count++; memset(&chunk, 0, sizeof(struct P_sub)); if(db_version == 6 || db_version == 5){ rc = persist__chunk_sub_read_v56(db_fd, &chunk); }else{ rc = persist__chunk_sub_read_v234(db_fd, &chunk); } if(rc){ fprintf(stderr, "Error: Corrupt persistent database.\n"); return rc; } if(client_stats && chunk.clientid){ HASH_FIND(hh_id, clients_by_id, chunk.clientid, strlen(chunk.clientid), cc); if(cc){ cc->subscriptions++; cc->subscription_size += length; } } if(do_json){ json_add_subscription(&chunk); } if(do_print){ print__sub(&chunk, length); } free__sub(&chunk); return 0; } static void report_client_stats(void) { if(client_stats){ struct client_data *cc, *cc_tmp; HASH_ITER(hh_id, clients_by_id, cc, cc_tmp){ printf("SC: %d SS: %d MC: %d MS: %ld ", cc->subscriptions, cc->subscription_size, cc->messages, cc->message_size); printf("%s\n", cc->id); } } } static void cleanup_client_stats() { struct base_msg_chunk *msg, *msg_tmp; struct client_data *cc, *cc_tmp; HASH_ITER(hh, msgs_by_id, msg, msg_tmp){ HASH_DELETE(hh, msgs_by_id, msg); free(msg); } HASH_ITER(hh_id, clients_by_id, cc, cc_tmp){ HASH_DELETE(hh_id, clients_by_id, cc); free(cc->id); free(cc); } } static void cleanup_msg_store() { struct mosquitto__base_msg *msg, *msg_tmp; HASH_ITER(hh, db.msg_store, msg, msg_tmp){ HASH_DELETE(hh, db.msg_store, msg); free(msg); } } #ifdef WITH_FUZZING int db_dump_fuzz_main(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { FILE *fd; char header[15]; int rc = 0; uint32_t crc; uint32_t i32temp; uint32_t length; uint32_t chunk; char *filename; if(argc == 2){ filename = argv[1]; }else if(argc == 3 && !strcmp(argv[1], "--stats")){ stats = 1; do_print = 0; filename = argv[2]; }else if(argc == 3 && !strcmp(argv[1], "--client-stats")){ client_stats = 1; do_print = 0; filename = argv[2]; }else if(argc == 3 && !strcmp(argv[1], "--json")){ do_print = 0; do_json = 1; filename = argv[2]; }else{ fprintf(stderr, "Usage: db_dump [--stats | --client-stats | --json] \n"); return 1; } if(do_json){ json_init(); } memset(&db, 0, sizeof(struct mosquitto_db)); fd = fopen(filename, "rb"); if(!fd){ fprintf(stderr, "Error: Unable to open %s\n", filename); return 0; } read_e(fd, &header, 15); if(!memcmp(header, magic, 15)){ if(do_print){ printf("Mosquitto DB dump\n"); } /* Restore DB as normal */ read_e(fd, &crc, sizeof(uint32_t)); if(do_print){ printf("CRC: %d\n", crc); } read_e(fd, &i32temp, sizeof(uint32_t)); db_version = ntohl(i32temp); if(do_print){ printf("DB version: %d\n", db_version); } if(db_version > MOSQ_DB_VERSION){ if(do_print){ printf("Warning: mosquitto_db_dump does not support this DB version, continuing but expecting errors.\n"); } } while(persist__chunk_header_read(fd, &chunk, &length) == MOSQ_ERR_SUCCESS){ switch(chunk){ case DB_CHUNK_CFG: if(dump__cfg_chunk_process(fd, length)){ goto error; } break; case DB_CHUNK_BASE_MSG: if(dump__base_msg_chunk_process(fd, length)){ goto error; } break; case DB_CHUNK_CLIENT_MSG: if(dump__client_msg_chunk_process(fd, length)){ goto error; } break; case DB_CHUNK_RETAIN: if(dump__retain_chunk_process(fd, length)){ goto error; } break; case DB_CHUNK_SUB: if(dump__sub_chunk_process(fd, length)){ goto error; } break; case DB_CHUNK_CLIENT: if(dump__client_chunk_process(fd, length)){ goto error; } break; default: fprintf(stderr, "Warning: Unsupported chunk \"%d\" of length %d in persistent database file at position %ld. Ignoring.\n", chunk, length, ftell(fd)); if(fseek(fd, length, SEEK_CUR)){ fprintf(stderr, "Error: %s\n", strerror(errno)); goto error; } break; } } }else{ fprintf(stderr, "Error: Unrecognised file format.\n"); rc = 1; } fclose(fd); if(do_json){ json_print(); json_cleanup(); } if(stats){ printf("DB_CHUNK_CFG: %ld\n", cfg_count); printf("DB_CHUNK_BASE_MSG: %ld\n", base_msg_count); printf("DB_CHUNK_CLIENT_MSG: %ld\n", client_msg_count); printf("DB_CHUNK_RETAIN: %ld\n", retain_count); printf("DB_CHUNK_SUB: %ld\n", sub_count); printf("DB_CHUNK_CLIENT: %ld\n", client_count); } report_client_stats(); cleanup_client_stats(); cleanup_msg_store(); return rc; error: cleanup_msg_store(); if(fd){ fclose(fd); } return 1; } eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/db_dump.h000066400000000000000000000023031514232433600240170ustar00rootroot00000000000000#ifndef DB_DUMP_H #define DB_DUMP_H /* Copyright (c) 2010-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include void print__client(struct P_client *chunk, uint32_t length); void print__client_msg(struct P_client_msg *chunk, uint32_t length); void print__base_msg(struct P_base_msg *chunk, uint32_t length); void print__sub(struct P_sub *chunk, uint32_t length); void json_init(void); void json_print(void); void json_cleanup(void); void json_add_base_msg(struct P_base_msg *msg); void json_add_client(struct P_client *chunk); void json_add_client_msg(struct P_client_msg *chunk); void json_add_retained_msg(struct P_retain *msg); void json_add_subscription(struct P_sub *chunk); #endif eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/json.c000066400000000000000000000126411514232433600233570ustar00rootroot00000000000000/* Copyright (c) 2010-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include #include #include #include #include #include #include "db_dump.h" #include "json_help.h" #include #include #define mosquitto_malloc(A) malloc((A)) #define mosquitto_free(A) free((A)) #define _mosquitto_malloc(A) malloc((A)) #define _mosquitto_free(A) free((A)) #include #include "db_dump.h" cJSON *j_tree = NULL; cJSON *j_base_msgs = NULL; cJSON *j_clients = NULL; cJSON *j_client_msgs = NULL; cJSON *j_retained_msgs = NULL; cJSON *j_subscriptions = NULL; void json_init(void) { j_tree = cJSON_CreateObject(); if(!j_tree){ fprintf(stderr, "Error: Out of memory.\n"); exit(1); } if((j_base_msgs = cJSON_AddArrayToObject(j_tree, "base-messages")) == NULL || (j_clients = cJSON_AddArrayToObject(j_tree, "clients")) == NULL || (j_client_msgs = cJSON_AddArrayToObject(j_tree, "client-messages")) == NULL || (j_retained_msgs = cJSON_AddArrayToObject(j_tree, "retained-messages")) == NULL || (j_subscriptions = cJSON_AddArrayToObject(j_tree, "subscriptions")) == NULL ){ fprintf(stderr, "Error: Out of memory.\n"); exit(1); } } void json_print(void) { char *jstr = cJSON_Print(j_tree); printf("%s\n", jstr); free(jstr); } void json_cleanup(void) { cJSON_Delete(j_tree); } void json_add_base_msg(struct P_base_msg *chunk) { cJSON *j_base_msg = NULL; j_base_msg = cJSON_CreateObject(); cJSON_AddItemToArray(j_base_msgs, j_base_msg); cJSON_AddUIntToObject(j_base_msg, "storeid", chunk->F.store_id); cJSON_AddIntToObject(j_base_msg, "expiry-time", chunk->F.expiry_time); cJSON_AddNumberToObject(j_base_msg, "source-mid", chunk->F.source_mid); cJSON_AddNumberToObject(j_base_msg, "source-port", chunk->F.source_port); cJSON_AddNumberToObject(j_base_msg, "qos", chunk->F.qos); cJSON_AddNumberToObject(j_base_msg, "retain", chunk->F.retain); cJSON_AddStringToObject(j_base_msg, "topic", chunk->topic); if(chunk->source.id){ cJSON_AddStringToObject(j_base_msg, "clientid", chunk->source.id); } if(chunk->source.username){ cJSON_AddStringToObject(j_base_msg, "username", chunk->source.username); } if(chunk->F.payloadlen > 0){ #ifdef WITH_TLS char *payload; if(mosquitto_base64_encode(chunk->payload, chunk->F.payloadlen, &payload) == MOSQ_ERR_SUCCESS){ cJSON_AddStringToObject(j_base_msg, "payload", payload); mosquitto_free(payload); } #else fprintf(stderr, "Warning: payload not output due to missing base64 support.\n"); #endif } if(chunk->properties){ cJSON *j_props = mosquitto_properties_to_json(chunk->properties); if(j_props){ cJSON_AddItemToObject(j_base_msg, "properties", j_props); } } } void json_add_client(struct P_client *chunk) { cJSON *j_client; j_client = cJSON_CreateObject(); cJSON_AddItemToArray(j_clients, j_client); cJSON_AddStringToObject(j_client, "clientid", chunk->clientid); if(chunk->username){ cJSON_AddStringToObject(j_client, "username", chunk->username); } cJSON_AddIntToObject(j_client, "session-expiry-time", chunk->F.session_expiry_time); cJSON_AddNumberToObject(j_client, "session-expiry-interval", chunk->F.session_expiry_interval); cJSON_AddNumberToObject(j_client, "last-mid", chunk->F.last_mid); cJSON_AddNumberToObject(j_client, "listener-port", chunk->F.listener_port); } void json_add_client_msg(struct P_client_msg *chunk) { cJSON *j_client_msg; j_client_msg = cJSON_CreateObject(); cJSON_AddItemToArray(j_client_msgs, j_client_msg); cJSON_AddStringToObject(j_client_msg, "clientid", chunk->clientid); cJSON_AddNumberToObject(j_client_msg, "storeid", chunk->subscription_identifier); cJSON_AddNumberToObject(j_client_msg, "mid", chunk->F.mid); cJSON_AddNumberToObject(j_client_msg, "qos", chunk->F.qos); cJSON_AddNumberToObject(j_client_msg, "state", chunk->F.state); cJSON_AddNumberToObject(j_client_msg, "retain-dup", chunk->F.retain_dup); cJSON_AddNumberToObject(j_client_msg, "direction", chunk->F.direction); cJSON_AddNumberToObject(j_client_msg, "subscription-identifier", chunk->subscription_identifier); } void json_add_subscription(struct P_sub *chunk) { cJSON *j_subscription; j_subscription = cJSON_CreateObject(); cJSON_AddItemToArray(j_subscriptions, j_subscription); cJSON_AddStringToObject(j_subscription, "clientid", chunk->clientid); cJSON_AddStringToObject(j_subscription, "topic", chunk->topic); cJSON_AddNumberToObject(j_subscription, "qos", chunk->F.qos); cJSON_AddNumberToObject(j_subscription, "options", chunk->F.options); cJSON_AddNumberToObject(j_subscription, "identifier", chunk->F.identifier); } void json_add_retained_msg(struct P_retain *chunk) { cJSON *j_retained_msg; j_retained_msg = cJSON_CreateObject(); cJSON_AddItemToArray(j_retained_msgs, j_retained_msg); cJSON_AddUIntToObject(j_retained_msg, "storeid", chunk->F.store_id); } eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/print.c000066400000000000000000000110461514232433600235400ustar00rootroot00000000000000/* Copyright (c) 2010-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include "db_dump.h" #include #include #include #include static void print__properties(mosquitto_property *properties) { int i; if(properties == NULL){ return; } printf("\tProperties:\n"); while(properties){ switch(mosquitto_property_identifier(properties)){ /* Only properties for base messages are valid for saving */ case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR: printf("\t\tPayload format indicator: %d\n", mosquitto_property_byte_value(properties)); break; case MQTT_PROP_CONTENT_TYPE: printf("\t\tContent type: %s\n", mosquitto_property_string_value(properties)); break; case MQTT_PROP_RESPONSE_TOPIC: printf("\t\tResponse topic: %s\n", mosquitto_property_string_value(properties)); break; case MQTT_PROP_CORRELATION_DATA: printf("\t\tCorrelation data: "); const uint8_t *bin = mosquitto_property_binary_value(properties); for(i=0; iclientid); if(chunk->username){ printf("\tUsername: %s\n", chunk->username); } if(chunk->F.listener_port > 0){ printf("\tListener port: %u\n", chunk->F.listener_port); } printf("\tLast MID: %d\n", chunk->F.last_mid); printf("\tSession expiry time: %" PRIu64 "\n", chunk->F.session_expiry_time); printf("\tSession expiry interval: %u\n", chunk->F.session_expiry_interval); } void print__client_msg(struct P_client_msg *chunk, uint32_t length) { printf("DB_CHUNK_CLIENT_MSG:\n"); printf("\tLength: %d\n", length); printf("\tClient ID: %s\n", chunk->clientid); printf("\tStore ID: %" PRIu64 "\n", chunk->F.store_id); printf("\tMID: %d\n", chunk->F.mid); printf("\tQoS: %d\n", chunk->F.qos); printf("\tRetain: %d\n", (chunk->F.retain_dup&0xF0)>>4); printf("\tDirection: %d\n", chunk->F.direction); printf("\tState: %d\n", chunk->F.state); printf("\tDup: %d\n", chunk->F.retain_dup&0x0F); if(chunk->subscription_identifier){ printf("\tSubscription identifier: %d\n", chunk->subscription_identifier); } } void print__base_msg(struct P_base_msg *chunk, uint32_t length) { uint8_t *payload; printf("DB_CHUNK_BASE_MSG:\n"); printf("\tLength: %d\n", length); printf("\tStore ID: %" PRIu64 "\n", chunk->F.store_id); /* printf("\tSource ID: %s\n", chunk->source_id); */ /* printf("\tSource Username: %s\n", chunk->source_username); */ printf("\tSource Port: %d\n", chunk->F.source_port); printf("\tSource MID: %d\n", chunk->F.source_mid); printf("\tTopic: %s\n", chunk->topic); printf("\tQoS: %d\n", chunk->F.qos); printf("\tRetain: %d\n", chunk->F.retain); printf("\tPayload Length: %d\n", chunk->F.payloadlen); printf("\tExpiry Time: %" PRIu64 "\n", chunk->F.expiry_time); payload = chunk->payload; if(chunk->F.payloadlen < 256){ /* Print payloads with UTF-8 data below an arbitrary limit of 256 bytes */ if(mosquitto_validate_utf8((char *)payload, (uint16_t)chunk->F.payloadlen) == MOSQ_ERR_SUCCESS){ printf("\tPayload: %s\n", payload); } } print__properties(chunk->properties); } void print__sub(struct P_sub *chunk, uint32_t length) { printf("DB_CHUNK_SUB:\n"); printf("\tLength: %u\n", length); printf("\tClient ID: %s\n", chunk->clientid); printf("\tTopic: %s\n", chunk->topic); printf("\tQoS: %d\n", chunk->F.qos); printf("\tSubscription ID: %d\n", chunk->F.identifier); printf("\tOptions: 0x%02X\n", chunk->F.options); } eclipse-mosquitto-mosquitto-691eab3/apps/db_dump/stubs.c000066400000000000000000000030461514232433600235450ustar00rootroot00000000000000#include #include #include "mosquitto_broker_internal.h" #include "mosquitto_internal.h" #include "util_mosq.h" #ifndef UNUSED # define UNUSED(A) (void)(A) #endif struct mosquitto *context__init(void) { return NULL; } void context__add_to_by_id(struct mosquitto *context) { UNUSED(context); } int db__message_store(const struct mosquitto *source, struct mosquitto__base_msg *base_msg, uint32_t *message_expiry_interval, enum mosquitto_msg_origin origin) { UNUSED(source); UNUSED(base_msg); UNUSED(message_expiry_interval); UNUSED(origin); return 0; } void db__msg_store_ref_inc(struct mosquitto__base_msg *base_msg) { UNUSED(base_msg); } int log__printf(struct mosquitto *mosq, unsigned int level, const char *fmt, ...) { UNUSED(mosq); UNUSED(level); UNUSED(fmt); return 0; } int retain__store(const char *topic, struct mosquitto__base_msg *base_msg, char **split_topics, bool persist) { UNUSED(topic); UNUSED(base_msg); UNUSED(split_topics); UNUSED(persist); return 0; } int sub__add(struct mosquitto *context, const struct mosquitto_subscription *sub) { UNUSED(context); UNUSED(sub); return 0; } void db__msg_add_to_inflight_stats(struct mosquitto_msg_data *msg_data, struct mosquitto__client_msg *msg) { UNUSED(msg_data); UNUSED(msg); } void db__msg_add_to_queued_stats(struct mosquitto_msg_data *msg_data, struct mosquitto__client_msg *msg) { UNUSED(msg_data); UNUSED(msg); } int session_expiry__add_from_persistence(struct mosquitto *context, time_t expiry_time) { UNUSED(context); UNUSED(expiry_time); return 0; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/000077500000000000000000000000001514232433600237145ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/CMakeLists.txt000066400000000000000000000056341514232433600264640ustar00rootroot00000000000000if(WITH_TLS) set(SRC mosquitto_ctrl.c mosquitto_ctrl.h broker.c client.c dynsec.c dynsec_client.c dynsec_group.c dynsec_role.c ../mosquitto_passwd/get_password.c ../mosquitto_passwd/get_password.h options.c ../../common/json_help.c ../../common/json_help.h ) if(WITH_CTRL_SHELL AND LINEEDITING_FOUND) add_library(ctrl_shell OBJECT ctrl_shell.c ctrl_shell.h ctrl_shell_broker.c ctrl_shell_client.c ctrl_shell_completion_tree.c ctrl_shell_dynsec.c ctrl_shell_post_connect.c ctrl_shell_pre_connect.c ctrl_shell_printf.c ) target_compile_definitions(ctrl_shell PRIVATE WITH_CTRL_SHELL) target_include_directories(ctrl_shell PRIVATE "${mosquitto_SOURCE_DIR}" "${mosquitto_SOURCE_DIR}/common" "${mosquitto_SOURCE_DIR}/include" "${CJSON_INCLUDE_DIRS}" ) target_link_libraries(ctrl_shell PRIVATE LineEditing::LineEditing OpenSSL::SSL ) add_library(ctrl_shell_io OBJECT ctrl_shell_io.c ) target_compile_definitions(ctrl_shell_io PRIVATE WITH_CTRL_SHELL) target_include_directories(ctrl_shell_io PRIVATE "${mosquitto_SOURCE_DIR}" "${mosquitto_SOURCE_DIR}/common" "${mosquitto_SOURCE_DIR}/include" "${CJSON_INCLUDE_DIRS}" ) target_link_libraries(ctrl_shell_io PRIVATE LineEditing::LineEditing OpenSSL::SSL ) endif() add_executable(mosquitto_ctrl ${SRC}) target_include_directories(mosquitto_ctrl PRIVATE "${mosquitto_SOURCE_DIR}" "${mosquitto_SOURCE_DIR}/apps/mosquitto_passwd" "${mosquitto_SOURCE_DIR}/common" "${mosquitto_SOURCE_DIR}/plugins/common" "${mosquitto_SOURCE_DIR}/plugins/dynamic-security" "${ARGON2_INCLUDE_DIRS}" "${CJSON_INCLUDE_DIRS}" ) if(WITH_BUNDLED_DEPS) target_include_directories(mosquitto_ctrl PRIVATE "${mosquitto_SOURCE_DIR}/deps" ) endif() if(WITH_CTRL_SHELL AND LINEEDITING_FOUND) target_compile_definitions(mosquitto_ctrl PRIVATE WITH_CTRL_SHELL) target_link_libraries(mosquitto_ctrl PRIVATE LineEditing::LineEditing ctrl_shell ctrl_shell_io ) endif() if(WITH_STATIC_LIBRARIES) target_link_libraries(mosquitto_ctrl PRIVATE libmosquitto_static) else() target_link_libraries(mosquitto_ctrl PRIVATE libmosquitto) endif() if(UNIX) if(APPLE) target_link_libraries(mosquitto_ctrl PRIVATE dl) elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") # elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") # elseif(QNX) # else() target_link_libraries(mosquitto_ctrl PRIVATE dl) endif() endif() target_link_libraries(mosquitto_ctrl PRIVATE common-options libmosquitto_common OpenSSL::SSL cJSON ) if(WITH_THREADING) if(WIN32) target_link_libraries(mosquitto_ctrl PRIVATE PThreads4W::PThreads4W) else() set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(mosquitto_ctrl PRIVATE Threads::Threads) endif() endif() install(TARGETS mosquitto_ctrl RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) endif() eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/Makefile000066400000000000000000000045141514232433600253600ustar00rootroot00000000000000R=../.. include ${R}/config.mk .PHONY: all install uninstall clean reallyclean LOCAL_CFLAGS+= LOCAL_CPPFLAGS+=-I${R}/lib -I${R}/apps/mosquitto_passwd -I${R}/plugins/dynamic-security -I${R}/common LOCAL_LDFLAGS+= LOCAL_LDADD+=-lcjson -ldl ${LIBMOSQ} ${LIBMOSQ_COMMON} -lcrypto -lssl # ------------------------------------------ # Compile time options # ------------------------------------------ ifeq ($(WITH_SOCKS),yes) LOCAL_CPPFLAGS+=-DWITH_SOCKS endif ifeq ($(WITH_THREADING),yes) LOCAL_LDFLAGS+=-pthread endif ifeq ($(WITH_EDITLINE),yes) LOCAL_LDADD+=-ledit LOCAL_CPPFLAGS+=-DWITH_CTRL_SHELL -DWITH_EDITLINE endif OBJS= \ mosquitto_ctrl.o \ broker.o \ client.o \ dynsec.o \ dynsec_client.o \ dynsec_group.o \ dynsec_role.o \ options.o ifeq ($(WITH_EDITLINE),yes) OBJS+= \ ctrl_shell.o \ ctrl_shell_broker.o \ ctrl_shell_client.o \ ctrl_shell_completion_tree.o \ ctrl_shell_dynsec.o \ ctrl_shell_io.o \ ctrl_shell_post_connect.o \ ctrl_shell_pre_connect.o \ ctrl_shell_printf.o endif OBJS_EXTERNAL= \ get_password.o \ json_help.o EXAMPLE_OBJS= example.o TARGET:=mosquitto_ctrl mosquitto_ctrl_example.so all : ${TARGET} mosquitto_ctrl : ${OBJS} ${OBJS_EXTERNAL} ${CROSS_COMPILE}${CC} $^ -o $@ $(LOCAL_LDFLAGS) $(LOCAL_LDADD) mosquitto_ctrl_example.so : ${EXAMPLE_OBJS} $(CROSS_COMPILE)$(CC) $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) ${LOCAL_LDFLAGS} -fPIC -shared $< -o $@ ${OBJS} : %.o: %.c mosquitto_ctrl.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ example.o : example.c mosquitto_ctrl.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -fPIC -c $< -o $@ get_password.o : ${R}/apps/mosquitto_passwd/get_password.c ${R}/apps/mosquitto_passwd/get_password.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ json_help.o : ${R}/common/json_help.c ${R}/common/json_help.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ ${R}/lib/libmosquitto.so.${SOVERSION} : $(MAKE) -C ${R}/lib ${R}/lib/libmosquitto.a : $(MAKE) -C ${R}/lib libmosquitto.a install : all ifeq ($(WITH_TLS),yes) $(INSTALL) -d "${DESTDIR}$(prefix)/bin" $(INSTALL) ${STRIP_OPTS} mosquitto_ctrl "${DESTDIR}${prefix}/bin/mosquitto_ctrl" endif uninstall : -rm -f "${DESTDIR}${prefix}/bin/mosquitto_ctrl" clean : -rm -f *.o mosquitto_ctrl *.gcda *.gcno *.so reallyclean : clean -rm -rf *.orig *.db eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/broker.c000066400000000000000000000164141514232433600253520ustar00rootroot00000000000000/* Copyright (c) 2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #define CJSON_VERSION_FULL (CJSON_VERSION_MAJOR*1000000+CJSON_VERSION_MINOR*1000+CJSON_VERSION_PATCH) #include #include #include #include "json_help.h" #include "mosquitto_ctrl.h" #include "mosquitto.h" void broker__print_usage(void) { printf("\nBroker Control module\n"); printf("=======================\n"); printf("List plugins : listPlugins\n"); printf("List listeners : listListeners\n"); } /* ################################################################ * # * # Payload callback * # * ################################################################ */ static void print_listeners(cJSON *j_response) { cJSON *j_data, *j_listeners, *j_listener, *jtmp; const char *stmp; int i=1; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_listeners = cJSON_GetObjectItem(j_data, "listeners"); if(j_listeners == NULL || !cJSON_IsArray(j_listeners)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } cJSON_ArrayForEach(j_listener, j_listeners){ printf("Listener %d:\n", i); jtmp = cJSON_GetObjectItem(j_listener, "port"); if(jtmp && cJSON_IsNumber(jtmp)){ printf(" Port: %d\n", jtmp->valueint); } if(json_get_string(j_listener, "protocol", &stmp, false) == MOSQ_ERR_SUCCESS){ printf(" Protocol: %s\n", stmp); } if(json_get_string(j_listener, "socket-path", &stmp, false) == MOSQ_ERR_SUCCESS){ printf(" Socket path: %s\n", stmp); } if(json_get_string(j_listener, "bind-address", &stmp, false) == MOSQ_ERR_SUCCESS){ printf(" Bind address: %s\n", stmp); } jtmp = cJSON_GetObjectItem(j_listener, "tls"); printf(" TLS: %s\n", jtmp && cJSON_IsBool(jtmp) && cJSON_IsTrue(jtmp)?"true":"false"); printf("\n"); i++; } } static void print_plugin_info(cJSON *j_response) { cJSON *j_data, *j_plugins, *j_plugin, *jtmp, *j_eps; const char *stmp; bool first; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_plugins = cJSON_GetObjectItem(j_data, "plugins"); if(j_plugins == NULL || !cJSON_IsArray(j_plugins)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } cJSON_ArrayForEach(j_plugin, j_plugins){ if(json_get_string(j_plugin, "name", &stmp, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("Plugin: %s\n", stmp); if(json_get_string(j_plugin, "version", &stmp, false) == MOSQ_ERR_SUCCESS){ printf("Version: %s\n", stmp); } j_eps = cJSON_GetObjectItem(j_plugin, "control-endpoints"); if(j_eps && cJSON_IsArray(j_eps)){ first = true; cJSON_ArrayForEach(jtmp, j_eps){ if(jtmp && cJSON_IsString(jtmp) && jtmp->valuestring){ if(first){ first = false; printf("Control endpoints: %s\n", jtmp->valuestring); }else{ printf(" %s\n", jtmp->valuestring); } } } } } } static void broker__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, const void *payload) { cJSON *tree, *j_responses, *j_response, *j_command; UNUSED(ctrl); #if CJSON_VERSION_FULL < 1007013 UNUSED(payloadlen); tree = cJSON_Parse(payload); #else tree = cJSON_ParseWithLength(payload, (size_t)payloadlen); #endif if(tree == NULL){ fprintf(stderr, "Error: Payload not JSON.\n"); return; } j_responses = cJSON_GetObjectItem(tree, "responses"); if(j_responses == NULL || !cJSON_IsArray(j_responses)){ fprintf(stderr, "Error: Payload missing data.\n"); cJSON_Delete(tree); return; } j_response = cJSON_GetArrayItem(j_responses, 0); if(j_response == NULL){ fprintf(stderr, "Error: Payload missing data.\n"); cJSON_Delete(tree); return; } j_command = cJSON_GetObjectItem(j_response, "command"); if(j_command == NULL){ fprintf(stderr, "Error: Payload missing data.\n"); cJSON_Delete(tree); return; } const char *error; if(json_get_string(j_response, "error", &error, false) == MOSQ_ERR_SUCCESS){ fprintf(stderr, "%s: Error: %s.\n", j_command->valuestring, error); }else{ if(!strcasecmp(j_command->valuestring, "listPlugins")){ print_plugin_info(j_response); }else if(!strcasecmp(j_command->valuestring, "listListeners")){ print_listeners(j_response); }else{ /* fprintf(stderr, "%s: Success\n", j_command->valuestring); */ } } cJSON_Delete(tree); } static int broker__list_plugins(int argc, char *argv[], cJSON *j_command) { UNUSED(argc); UNUSED(argv); if(cJSON_AddStringToObject(j_command, "command", "listPlugins") == NULL ){ return MOSQ_ERR_NOMEM; } return MOSQ_ERR_SUCCESS; } static int broker__list_listeners(int argc, char *argv[], cJSON *j_command) { UNUSED(argc); UNUSED(argv); if(cJSON_AddStringToObject(j_command, "command", "listListeners") == NULL ){ return MOSQ_ERR_NOMEM; } return MOSQ_ERR_SUCCESS; } /* ################################################################ * # * # Main * # * ################################################################ */ int broker__main(int argc, char *argv[], struct mosq_ctrl *ctrl) { int rc = -1; cJSON *j_tree; cJSON *j_commands, *j_command; if(!strcasecmp(argv[0], "help")){ broker__print_usage(); return -1; } /* The remaining commands need a network connection and JSON command. */ ctrl->payload_callback = broker__payload_callback; ctrl->request_topic = strdup("$CONTROL/broker/v1"); ctrl->response_topic = strdup("$CONTROL/broker/v1/response"); if(ctrl->request_topic == NULL || ctrl->response_topic == NULL){ return MOSQ_ERR_NOMEM; } j_tree = cJSON_CreateObject(); if(j_tree == NULL){ return MOSQ_ERR_NOMEM; } j_commands = cJSON_AddArrayToObject(j_tree, "commands"); if(j_commands == NULL){ cJSON_Delete(j_tree); j_tree = NULL; return MOSQ_ERR_NOMEM; } j_command = cJSON_CreateObject(); if(j_command == NULL){ cJSON_Delete(j_tree); j_tree = NULL; return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_commands, j_command); if(!strcasecmp(argv[0], "listPlugins")){ rc = broker__list_plugins(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "listListeners")){ rc = broker__list_listeners(argc-1, &argv[1], j_command); }else{ fprintf(stderr, "Command '%s' not recognised.\n", argv[0]); cJSON_Delete(j_tree); j_tree = NULL; return MOSQ_ERR_UNKNOWN; } if(rc == MOSQ_ERR_SUCCESS){ ctrl->payload = cJSON_PrintUnformatted(j_tree); cJSON_Delete(j_tree); if(ctrl->payload == NULL){ fprintf(stderr, "Error: Out of memory.\n"); return MOSQ_ERR_NOMEM; } } return rc; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/client.c000066400000000000000000000104511514232433600253370ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "mosquitto_ctrl.h" static int run = 1; static void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg, const mosquitto_property *properties) { struct mosq_ctrl *ctrl = obj; UNUSED(properties); if(ctrl->payload_callback){ ctrl->payload_callback(ctrl, msg->payloadlen, msg->payload); } mosquitto_disconnect_v5(mosq, 0, NULL); run = 0; } static void on_publish(struct mosquitto *mosq, void *obj, int mid, int reason_code, const mosquitto_property *properties) { UNUSED(obj); UNUSED(mid); UNUSED(properties); if(reason_code > 127){ fprintf(stderr, "Publish error: %s\n", mosquitto_reason_string(reason_code)); run = 0; mosquitto_disconnect_v5(mosq, 0, NULL); } } static void on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos, const mosquitto_property *properties) { struct mosq_ctrl *ctrl = obj; UNUSED(mid); UNUSED(properties); if(qos_count == 1){ if(granted_qos[0] < 128){ /* Success */ mosquitto_publish(mosq, NULL, ctrl->request_topic, (int)strlen(ctrl->payload), ctrl->payload, ctrl->cfg.qos, 0); free(ctrl->request_topic); ctrl->request_topic = NULL; free(ctrl->payload); ctrl->payload = NULL; }else{ if(ctrl->cfg.protocol_version == MQTT_PROTOCOL_V5){ fprintf(stderr, "Subscribe error: %s\n", mosquitto_reason_string(granted_qos[0])); }else{ fprintf(stderr, "Subscribe error: Subscription refused.\n"); } run = 0; mosquitto_disconnect_v5(mosq, 0, NULL); } }else{ run = 0; mosquitto_disconnect_v5(mosq, 0, NULL); } } static void on_connect(struct mosquitto *mosq, void *obj, int reason_code, int flags, const mosquitto_property *properties) { struct mosq_ctrl *ctrl = obj; UNUSED(flags); UNUSED(properties); if(reason_code == 0){ if(ctrl->response_topic){ mosquitto_subscribe(mosq, NULL, ctrl->response_topic, ctrl->cfg.qos); free(ctrl->response_topic); ctrl->response_topic = NULL; } }else{ if(ctrl->cfg.protocol_version == MQTT_PROTOCOL_V5){ if(reason_code == MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION){ fprintf(stderr, "Connection error: %s. Try connecting to an MQTT v5 broker, or use MQTT v3.x mode.\n", mosquitto_reason_string(reason_code)); }else{ fprintf(stderr, "Connection error: %s\n", mosquitto_reason_string(reason_code)); } }else{ fprintf(stderr, "Connection error: %s\n", mosquitto_connack_string(reason_code)); } run = 0; mosquitto_disconnect_v5(mosq, 0, NULL); } } int client_request_response(struct mosq_ctrl *ctrl) { struct mosquitto *mosq; int rc; time_t start; if(ctrl->cfg.cafile == NULL && ctrl->cfg.capath == NULL && !ctrl->cfg.tls_use_os_certs && ctrl->cfg.port != 8883 # ifdef FINAL_WITH_TLS_PSK && !ctrl->cfg.psk # endif ){ fprintf(stderr, "Warning: You are running mosquitto_ctrl without encryption.\nThis means all of the configuration changes you are making are visible on the network, including passwords.\n\n"); } mosquitto_lib_init(); mosq = mosquitto_new(ctrl->cfg.id, true, ctrl); rc = client_opts_set(mosq, &ctrl->cfg); if(rc){ goto cleanup; } mosquitto_connect_v5_callback_set(mosq, on_connect); mosquitto_subscribe_v5_callback_set(mosq, on_subscribe); mosquitto_publish_v5_callback_set(mosq, on_publish); mosquitto_message_v5_callback_set(mosq, on_message); rc = client_connect(mosq, &ctrl->cfg); if(rc){ goto cleanup; } start = time(NULL); while(run && start+10 > time(NULL)){ mosquitto_loop(mosq, -1, 1); } cleanup: mosquitto_destroy(mosq); mosquitto_lib_cleanup(); return rc; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell.c000066400000000000000000000500401514232433600262120ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef WIN32 # include # include #endif #include "ctrl_shell.h" #include "ctrl_shell_internal.h" #define UNUSED(A) (void)(A) #ifdef WITH_CTRL_SHELL #define FREE(A) do{free(A); A = NULL;}while(0) const char *ANSI_URL = NULL; const char *ANSI_MODULE = NULL; const char *ANSI_INPUT = NULL; const char *ANSI_ERROR = NULL; const char **ANSI_LABEL = NULL; const char *ANSI_RESET = "\001\e[0m\002"; const char *ANSI_TOPIC = NULL; const char *ANSI_POSITIVE = NULL; const char *ANSI_NEGATIVE = NULL; const char *ANSI_LABEL_none[] = {"", ""}; const char ANSI_URL_dark[] = "\001\e[38;5;155m\002"; const char ANSI_MODULE_dark[] = "\001\e[38;5;214m\002"; const char ANSI_INPUT_dark[] = "\001\e[38;5;80m\002"; const char ANSI_ERROR_dark[] = "\001\e[38;5;198m\002"; const char *ANSI_LABEL_dark[] = { "\001\e[38;5;207m\002", "\001\e[38;5;219m\002", }; const char ANSI_TOPIC_dark[] = "\001\e[93m\002"; const char ANSI_POSITIVE_dark[] = "\001\e[92m\002"; const char ANSI_NEGATIVE_dark[] = "\001\e[95m\002"; const char ANSI_URL_light[] = "\001\e[38;5;23m\002"; const char ANSI_MODULE_light[] = "\001\e[38;5;130m\002"; const char ANSI_INPUT_light[] = "\001\e[38;5;27m\002"; const char ANSI_ERROR_light[] = "\001\e[38;5;196m\002"; const char *ANSI_LABEL_light[] = { "\001\e[38;5;165m\002", "\001\e[38;5;171m\002", }; const char ANSI_TOPIC_light[] = "\001\e[33m\002"; const char ANSI_POSITIVE_light[] = "\001\e[32m\002"; const char ANSI_NEGATIVE_light[] = "\001\e[35m\002"; char prompt[200]; struct ctrl_shell data; static int generator_arg = -1; struct completion_tree_cmd *current_cmd_match = NULL; static void signal_winch(int signal) { UNUSED(signal); rl_resize_terminal(); } static void signal_term(int signal) { UNUSED(signal); data.run = 0; } static void term_set_flag(bool set, unsigned int flag) { struct termios ts; tcgetattr(0, &ts); if(set){ ts.c_lflag |= (flag); }else{ ts.c_lflag &= (unsigned int)(~flag); } tcsetattr(0, TCSANOW, &ts); } static void term_set_echo(bool echo) { term_set_flag(echo, ECHO); } static void term_set_canon(bool canon) { term_set_flag(canon, ICANON); } void ctrl_shell_rtrim(char *buf) { size_t slen = strlen(buf); while(slen > 0 && isspace((unsigned char)buf[slen-1])){ buf[slen-1] = '\0'; slen = strlen(buf); } } bool ctrl_shell_get_password(char *buf, size_t len) { ctrl_shell_printf("%spassword:%s", ANSI_INPUT, ANSI_RESET); term_set_echo(false); if(ctrl_shell_fgets(buf, (int)len, stdin) == NULL){ term_set_echo(true); return false; } term_set_echo(true); ctrl_shell_printf("\n"); ctrl_shell_rtrim(buf); return true; } static int response_wait(void) { struct timespec timeout; int rc = 0; data.response_received = false; clock_gettime(CLOCK_REALTIME, &timeout); timeout.tv_sec += 2; while(data.response_received == false){ if(pthread_cond_timedwait(&data.response_cond, &data.response_mutex, &timeout) == ETIMEDOUT){ ctrl_shell_printf("Timed out with no response.\n"); rc = 1; break; } } return rc; } int ctrl_shell_publish_blocking(cJSON *j_command) { int rc = 0; cJSON *j_commands = cJSON_CreateObject(); cJSON *j_array = cJSON_AddArrayToObject(j_commands, "commands"); cJSON_AddItemToArray(j_array, j_command); char *payload = cJSON_PrintUnformatted(j_commands); cJSON_Delete(j_commands); pthread_mutex_lock(&data.response_mutex); mosquitto_publish(data.mosq, NULL, data.request_topic, (int)strlen(payload), payload, 1, false); FREE(payload); /* Check for publish callback */ rc = response_wait(); if(rc){ pthread_mutex_unlock(&data.response_mutex); return rc; } if(data.publish_rc >= 128){ pthread_mutex_unlock(&data.response_mutex); return 1; } /* Check for message callback */ rc = response_wait(); pthread_mutex_unlock(&data.response_mutex); return rc; } void ctrl_shell__connect_blocking(const char *hostname, int port) { pthread_mutex_lock(&data.response_mutex); int rc = mosquitto_connect(data.mosq, hostname, port, 60); rc = mosquitto_loop_start(data.mosq); /* FIXME - do something with the error */ UNUSED(rc); response_wait(); pthread_mutex_unlock(&data.response_mutex); } void ctrl_shell_line_callback_set(void (*callback)(char *line)) { data.line_callback = callback; } int ctrl_shell_command_generic_arg0(const char *command) { cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); return ctrl_shell_publish_blocking(j_command); } int ctrl_shell_command_generic_arg1(const char *command, const char *itemlabel, char **saveptr) { const char *item; item = strtok_r(NULL, " ", saveptr); if(!item){ ctrl_shell_printf("%s %s\n", command, itemlabel); return MOSQ_ERR_INVAL; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); cJSON_AddStringToObject(j_command, itemlabel, item); return ctrl_shell_publish_blocking(j_command); } int ctrl_shell_command_generic_int_arg1(const char *command, const char *itemlabel, char **saveptr) { const char *item; item = strtok_r(NULL, " ", saveptr); if(!item){ ctrl_shell_printf("%s %s\n", command, itemlabel); return MOSQ_ERR_INVAL; } int intval = atoi(item); cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); cJSON_AddNumberToObject(j_command, itemlabel, intval); return ctrl_shell_publish_blocking(j_command); } int ctrl_shell_command_generic_arg2(const char *command, const char *itemlabel1, const char *itemlabel2, char **saveptr) { const char *item1, *item2; item1 = strtok_r(NULL, " ", saveptr); item2 = strtok_r(NULL, " ", saveptr); if(!item1 || !item2){ ctrl_shell_printf("%s %s %s\n", command, itemlabel1, itemlabel2); return MOSQ_ERR_INVAL; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); cJSON_AddStringToObject(j_command, itemlabel1, item1); cJSON_AddStringToObject(j_command, itemlabel2, item2); return ctrl_shell_publish_blocking(j_command); } static int ctrl_shell__subscribe_blocking(const char *topic, void (*module_on_subscribe)(void)) { int rc = 0; for(int i=0; i= 128){ rc = 1; }else{ if(module_on_subscribe){ module_on_subscribe(); } } return rc; } bool ctrl_shell_callback_final(char *line) { if(!line || !strcasecmp(line, "exit")){ data.run = 0; }else if(!strcasecmp(line, "disconnect")){ if(data.mosq){ ctrl_shell__disconnect(); }else{ return false; } }else if(!strcasecmp(line, "return")){ if(data.mod_cleanup){ data.mod_cleanup(); } ctrl_shell__post_connect_init(); }else{ return false; } return true; } void ctrl_shell_print_help_final(const char *command, const char *modul) { if(!strcasecmp(command, "disconnect")){ ctrl_shell_print_help_command("disconnect"); ctrl_shell_printf("\nDisconnect from the broker\n"); }else if(!strcasecmp(command, "exit")){ ctrl_shell_print_help_command("exit"); ctrl_shell_printf("\nQuit the program\n"); }else if(!strcasecmp(command, "help")){ ctrl_shell_print_help_command("help "); ctrl_shell_printf("\nFind help on a command using 'help '\n"); ctrl_shell_printf("Press tab multiple times to find currently available commands.\n"); }else if(modul && !strcasecmp(command, "return")){ ctrl_shell_print_help_command("return"); ctrl_shell_printf("\nLeave %s mode.\n", modul); }else{ ctrl_shell_printf("Unknown command '%s'\n", command); } } static void calc_generator_arg(int start) { char *text_heap; char *text_arg, *saveptr = NULL; int ga = 0; if(start == 0){ generator_arg = -1; return; } text_heap = strdup(rl_line_buffer); if(!text_heap){ return; } text_heap[start] = '\0'; text_arg = strtok_r(text_heap, " ", &saveptr); while(text_arg){ ga++; text_arg = strtok_r(NULL, " ", &saveptr); } FREE(text_heap); generator_arg = ga-1; } char *completion_generator(const char *text, int state) { static size_t len; static struct completion_tree_cmd *cmd, *cmd_prev; static struct completion_tree_arg *arg; if(!data.commands){ return NULL; } if(!state){ len = strlen(text); if(generator_arg < 0){ cmd = data.commands->commands; }else if(current_cmd_match && generator_arg < current_cmd_match->arg_list_count){ arg = current_cmd_match->arg_lists[generator_arg]->args; }else{ return NULL; } } if(generator_arg < 0){ while(cmd){ char *name = cmd->name; cmd_prev = cmd; cmd = cmd->next; if(strncasecmp(name, text, len) == 0){ current_cmd_match = cmd_prev; return strdup(name); } } }else{ while(arg){ char *name = arg->name; arg = arg->next; if(strncasecmp(name, text, len) == 0){ return strdup(name); } } } return NULL; } void ctrl_shell_completion_commands_set(struct completion_tree_root *new_commands) { data.commands = new_commands; } char **completion_matcher(const char *text, int start, int end) { char **matches; UNUSED(end); rl_attempted_completion_over = 1; calc_generator_arg(start); matches = rl_completion_matches(text, completion_generator); return matches; } int my_get_address(int sock, char *buf, size_t len, uint16_t *remote_port) { struct sockaddr_storage addr; socklen_t addrlen; if(sock < 0){ memset(buf, 0, len); *remote_port = 0; return 1; } memset(&addr, 0, sizeof(struct sockaddr_storage)); addrlen = sizeof(addr); if(!getpeername(sock, (struct sockaddr *)&addr, &addrlen)){ if(addr.ss_family == AF_INET){ if(remote_port){ *remote_port = ntohs(((struct sockaddr_in *)&addr)->sin_port); } if(inet_ntop(AF_INET, &((struct sockaddr_in *)&addr)->sin_addr.s_addr, buf, (socklen_t)len)){ return 0; } }else if(addr.ss_family == AF_INET6){ if(remote_port){ *remote_port = ntohs(((struct sockaddr_in6 *)&addr)->sin6_port); } if(inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr.s6_addr, buf, (socklen_t)len)){ return 0; } } } return 1; } static void on_connect_reconnect(struct mosquitto *mosq, void *userdata, int rc) { UNUSED(userdata); UNUSED(rc); for(int i=0; ipayload); cJSON *j_endpoint_error = cJSON_GetObjectItem(j_tree, "error"); if(j_endpoint_error && !strcmp(j_endpoint_error->valuestring, "endpoint not available")){ ctrl_shell_printf("%s$CONTROL endpoint for this module not available.%s\n", ANSI_ERROR, ANSI_RESET); }else{ cJSON *j_responses = cJSON_GetObjectItem(j_tree, "responses"); cJSON *j_command_obj = cJSON_GetArrayItem(j_responses, 0); cJSON *j_command = cJSON_GetObjectItem(j_command_obj, "command"); cJSON *j_error = cJSON_GetObjectItem(j_command_obj, "error"); cJSON *j_data = cJSON_GetObjectItem(j_command_obj, "data"); if(j_error){ ctrl_shell_printf("%s%s%s\n", ANSI_ERROR, j_error->valuestring, ANSI_RESET); }else if(j_data && data.response_callback){ data.response_callback(j_command->valuestring, j_data, msg->payload); }else if(j_command){ ctrl_shell_printf("OK\n"); }else{ ctrl_shell_printf("Invalid response from broker.\n"); } } cJSON_Delete(j_tree); data.response_received = true; pthread_mutex_unlock(&data.response_mutex); pthread_cond_signal(&data.response_cond); } void ctrl_shell__on_publish(struct mosquitto *mosq, void *userdata, int mid, int reason_code, const mosquitto_property *props) { UNUSED(mosq); UNUSED(userdata); UNUSED(mid); UNUSED(props); if(reason_code >= 128){ ctrl_shell_printf("Publish failed, check you have permission to access this module.\n"); data.publish_rc = reason_code; } data.response_received = true; pthread_mutex_unlock(&data.response_mutex); pthread_cond_signal(&data.response_cond); } void ctrl_shell__on_subscribe(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos) { UNUSED(mosq); UNUSED(userdata); UNUSED(mid); if(qos_count == 1 && granted_qos[0] >= 128){ ctrl_shell_printf("Subscribe failed, check you have permission to access this module.\n"); data.subscribe_rc = granted_qos[0]; } data.response_received = true; pthread_mutex_unlock(&data.response_mutex); pthread_cond_signal(&data.response_cond); } void ctrl_shell__load_module(void (*mod_init)(struct ctrl_shell__module *mod)) { struct ctrl_shell__module mod; memset(&mod, 0, sizeof(mod)); mod_init(&mod); data.request_topic = mod.request_topic; data.response_callback = mod.response_callback; data.mod_cleanup = mod.cleanup; ctrl_shell_completion_commands_set(mod.completion_commands); ctrl_shell_line_callback_set(mod.line_callback); ctrl_shell__subscribe_blocking(mod.response_topic, mod.on_subscribe); } void set_no_colour(void) { ANSI_URL = ""; ANSI_MODULE = ""; ANSI_INPUT = ""; ANSI_ERROR = ""; ANSI_LABEL = ANSI_LABEL_none; ANSI_RESET = ""; ANSI_TOPIC = ""; ANSI_POSITIVE = ""; ANSI_NEGATIVE = ""; } static void set_bg_light(void) { ANSI_URL = ANSI_URL_light; ANSI_MODULE = ANSI_MODULE_light; ANSI_INPUT = ANSI_INPUT_light; ANSI_ERROR = ANSI_ERROR_light; ANSI_LABEL = ANSI_LABEL_light; ANSI_TOPIC = ANSI_TOPIC_light; ANSI_POSITIVE = ANSI_POSITIVE_light; ANSI_NEGATIVE = ANSI_NEGATIVE_light; } static void set_bg_dark(void) { ANSI_URL = ANSI_URL_dark; ANSI_MODULE = ANSI_MODULE_dark; ANSI_INPUT = ANSI_INPUT_dark; ANSI_ERROR = ANSI_ERROR_dark; ANSI_LABEL = ANSI_LABEL_dark; ANSI_TOPIC = ANSI_TOPIC_dark; ANSI_POSITIVE = ANSI_POSITIVE_dark; ANSI_NEGATIVE = ANSI_NEGATIVE_dark; } static int get_bg(void) { int opt; /* Set non-blocking */ opt = fcntl(STDIN_FILENO, F_GETFL, 0); if(fcntl(STDIN_FILENO, F_SETFL, opt | O_NONBLOCK) < 0){ fprintf(stderr, "Error: Unable to set terminal flags required."); return 1; } set_bg_dark(); term_set_echo(false); term_set_canon(false); ctrl_shell_printf("\e]10;?\a\e]11;?\a"); fflush(stdout); char buf[50] = {0}; ssize_t rl; usleep(100000); do{ rl = read(STDIN_FILENO, buf, sizeof(buf)); }while(rl == 0); if(fcntl(STDIN_FILENO, F_SETFL, opt) < 0){ fprintf(stderr, "Error: Unable to reset terminal flags."); return 1; } term_set_echo(true); term_set_canon(true); for(int i=0; i bg){ set_bg_dark(); }else{ set_bg_light(); } return 0; } void ctrl_shell__cleanup(void) { FREE(data.hostname); for(int i=0; ino_colour){ set_no_colour(); }else{ if(get_bg()){ return; } } if(config){ if(config->host){ data.hostname = strdup(config->host); } if(config->port != PORT_UNDEFINED){ data.port = config->port; } if(config->username){ data.username = strdup(config->username); } if(config->password){ data.password = strdup(config->password); } if(config->id){ data.clientid = strdup(config->id); } if(config->cafile){ data.tls_cafile = strdup(config->cafile); } if(config->capath){ data.tls_capath = strdup(config->capath); } if(config->certfile){ data.tls_certfile = strdup(config->certfile); } if(config->keyfile){ data.tls_keyfile = strdup(config->keyfile); } } pthread_mutex_init(&data.response_mutex, NULL); pthread_cond_init(&data.response_cond, NULL); rl_readline_name = "mosquitto_ctrl"; rl_completion_entry_function = completion_generator; rl_attempted_completion_function = completion_matcher; rl_bind_key('\t', rl_complete); signal(SIGWINCH, signal_winch); signal(SIGTERM, signal_term); signal(SIGINT, signal_term); ctrl_shell_printf("mosquitto_ctrl shell v" VERSION "\n"); if(data.hostname){ if(ctrl_shell__connect()){ ctrl_shell__cleanup(); return; } }else{ ctrl_shell__pre_connect_init(); } while(data.run){ current_cmd_match = NULL; char *line = readline(prompt); if(data.line_callback){ data.line_callback(line); } } clear_history(); ctrl_shell_printf("\n"); ctrl_shell__cleanup(); } static void print_label(unsigned int level, const char *label) { char *str = calloc(1, level*2 + strlen(label) + 30); for(unsigned int i=0; i All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef CTRL_SHELL_H #define CTRL_SHELL_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include extern const char *ANSI_URL; extern const char *ANSI_MODULE; extern const char *ANSI_INPUT; extern const char *ANSI_ERROR; extern const char **ANSI_LABEL; extern const char *ANSI_RESET; extern const char *ANSI_TOPIC; extern const char *ANSI_POSITIVE; extern const char *ANSI_NEGATIVE; /* This relatively complex structure is used to store the command tree. It * allows for commands to be shared between different parts of the tree, and * for commands to have multiple arguments. The completion_tree_arg_list * members exist to have a fixed memory location that never changes, so that * we can reallocate the list for where we have dynamically updated arguments, * for e.g. dynsec clients, where we want many commands to use the same list. */ struct completion_tree_arg { struct completion_tree_arg *next; char name[]; }; struct completion_tree_arg_list { struct completion_tree_arg *args; bool is_shared; }; struct completion_tree_cmd { struct completion_tree_cmd *next; struct completion_tree_arg_list **arg_lists; int arg_list_count; char name[]; }; struct completion_tree_root { struct completion_tree_cmd *commands; }; extern char prompt[200]; /* Helper functions for sending commands to the broker. */ int ctrl_shell_command_generic_arg0(const char *command); int ctrl_shell_command_generic_arg1(const char *command, const char *itemlabel, char **saveptr); int ctrl_shell_command_generic_arg2(const char *command, const char *itemlabel1, const char *itemlabel2, char **saveptr); int ctrl_shell_command_generic_int_arg1(const char *command, const char *itemlabel, char **saveptr); void ctrl_shell_completion_commands_set(struct completion_tree_root *new_commands); void ctrl_shell_line_callback_set(void (*callback)(char *line)); /* Helper functions for building the command tree. */ void completion_tree_arg_list_free(struct completion_tree_arg_list *arg_list); void completion_tree_arg_list_args_free(struct completion_tree_arg_list *arg_list); void completion_tree_cmd_free(struct completion_tree_cmd *cmd); void completion_tree_free(struct completion_tree_root *tree); struct completion_tree_cmd *completion_tree_cmd_add(struct completion_tree_root *root, struct completion_tree_arg_list *help_arg_list, const char *name); struct completion_tree_arg_list *completion_tree_cmd_new_arg_list(void); void completion_tree_cmd_append_arg_list(struct completion_tree_cmd *cmd, struct completion_tree_arg_list *new_list); struct completion_tree_arg_list *completion_tree_cmd_add_arg_list(struct completion_tree_cmd *cmd); void completion_tree_arg_list_add_arg(struct completion_tree_arg_list *arg_list, const char *name); void ctrl_shell_pre_connect_init(void); void ctrl_shell_post_connect_init(void); int ctrl_shell_publish_blocking(cJSON *j_command); bool ctrl_shell_callback_final(char *line); char *ctrl_shell_fgets(char *s, int size, FILE *stream); bool ctrl_shell_get_password(char *buf, size_t len); void ctrl_shell_rtrim(char *buf); void ctrl_shell_printf(const char *fmt, ...); void ctrl_shell_vprintf(const char *fmt, va_list va); void ctrl_shell_print_label(unsigned int level, const char *label); void ctrl_shell_print_label_value(unsigned int level, const char *label, int align, const char *fmt, ...); void ctrl_shell_print_value(unsigned int level, const char *fmt, ...); void ctrl_shell_print_help_command(const char *cmd); void ctrl_shell_print_help_desc(const char *desc); void ctrl_shell_print_help_final(const char *command, const char *modul); #ifdef __cplusplus } #endif #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_broker.c000066400000000000000000000130601514232433600275570ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include "ctrl_shell_internal.h" #include "json_help.h" #ifdef WITH_CTRL_SHELL #define UNUSED(A) (void)(A) static struct completion_tree_root *commands_broker = NULL; static void command_tree_create(void) { struct completion_tree_cmd *cmd; struct completion_tree_arg_list *help_arg_list; if(commands_broker){ return; } commands_broker = calloc(1, sizeof(struct completion_tree_root)); cmd = completion_tree_cmd_add(commands_broker, NULL, "help"); help_arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_cmd_add(commands_broker, help_arg_list, "listPlugins"); completion_tree_cmd_add(commands_broker, help_arg_list, "listListeners"); completion_tree_cmd_add(commands_broker, help_arg_list, "disconnect"); completion_tree_cmd_add(commands_broker, help_arg_list, "return"); completion_tree_cmd_add(commands_broker, help_arg_list, "exit"); } static void print_help(char **saveptr) { char *command = strtok_r(NULL, " ", saveptr); if(command){ if(!strcasecmp(command, "listPlugins")){ ctrl_shell_print_help_command("listPlugins"); ctrl_shell_printf("\nLists currently loaded plugins.\n"); }else if(!strcasecmp(command, "listListeners")){ ctrl_shell_print_help_command("listListeners"); ctrl_shell_printf("\nLists current listeners.\n"); }else{ ctrl_shell_print_help_final(command, "broker"); } }else{ ctrl_shell_printf("This is the mosquitto_ctrl interactive shell, for controlling aspects of a mosquitto broker.\n"); ctrl_shell_printf("You are in broker mode, for controlling some core broker functionality.\n"); ctrl_shell_printf("Use '%sreturn%s' to leave this mode.\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Find help on a command using '%shelp %s'\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Press tab multiple times to find currently available commands.\n\n"); } } static void line_callback(char *line) { if(!line){ ctrl_shell_callback_final(NULL); return; } ctrl_shell_rtrim(line); if(strlen(line) > 0){ add_history(line); }else{ free(line); return; } char *saveptr = NULL; char *command = strtok_r(line, " ", &saveptr); if(!command){ free(line); return; } if(!strcasecmp(command, "listPlugins")){ ctrl_shell_command_generic_arg0("listPlugins"); }else if(!strcasecmp(command, "listListeners")){ ctrl_shell_command_generic_arg0("listListeners"); }else if(!strcasecmp(command, "help")){ print_help(&saveptr); }else{ if(!ctrl_shell_callback_final(line)){ ctrl_shell_printf("Unknown command '%s'\n", command); } } free(line); } static void print_plugins(cJSON *j_data) { cJSON *j_plugins, *j_plugin; j_plugins = cJSON_GetObjectItem(j_data, "plugins"); cJSON_ArrayForEach(j_plugin, j_plugins){ const char *name; if(json_get_string(j_plugin, "name", &name, false) != MOSQ_ERR_SUCCESS){ ctrl_shell_printf("Invalid response from broker.\n"); return; } ctrl_shell_print_label(0, "Plugin:"); ctrl_shell_print_value(1, "%s\n", name); cJSON *j_endpoints, *j_endpoint; j_endpoints = cJSON_GetObjectItem(j_plugin, "control-endpoints"); if(j_endpoints){ ctrl_shell_print_label(0, "Control endpoints:"); cJSON_ArrayForEach(j_endpoint, j_endpoints){ ctrl_shell_print_value(1, "%s\n", j_endpoint->valuestring); } } ctrl_shell_print_value(0, "\n"); } } static void print_listeners(cJSON *j_data) { cJSON *j_listeners, *j_listener; j_listeners = cJSON_GetObjectItem(j_data, "listeners"); cJSON_ArrayForEach(j_listener, j_listeners){ int port; const char *protocol; bool tls; if(json_get_int(j_listener, "port", &port, false, -1) != MOSQ_ERR_SUCCESS || json_get_string(j_listener, "protocol", &protocol, false) != MOSQ_ERR_SUCCESS || json_get_bool(j_listener, "tls", &tls, false, false) != MOSQ_ERR_SUCCESS){ ctrl_shell_printf("Invalid response from broker.\n"); return; } ctrl_shell_print_label(0, "Listener:"); ctrl_shell_print_label(1, "Port:"); ctrl_shell_print_value(2, "%d\n", port); ctrl_shell_print_label(1, "Protocol:"); ctrl_shell_print_value(2, "%s\n", protocol); ctrl_shell_print_label(1, "TLS:"); ctrl_shell_print_value(2, "%s\n\n", tls?"true":"false"); } } static void handle_response(const char *command, cJSON *j_data, const char *payload) { if(!strcmp(command, "listPlugins")){ print_plugins(j_data); }else if(!strcmp(command, "listListeners")){ print_listeners(j_data); }else{ ctrl_shell_printf("%s %s\n", command, payload); } } static void on_subscribe(void) { } static void ctrl_shell__broker_cleanup(void) { completion_tree_free(commands_broker); commands_broker = NULL; } void ctrl_shell__broker_init(struct ctrl_shell__module *mod) { command_tree_create(); mod->completion_commands = commands_broker; mod->request_topic = "$CONTROL/broker/v1"; mod->response_topic = "$CONTROL/broker/v1/response"; mod->line_callback = line_callback; mod->response_callback = handle_response; mod->on_subscribe = on_subscribe; mod->cleanup = ctrl_shell__broker_cleanup; } #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_client.c000066400000000000000000000054411514232433600275550ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include "ctrl_shell_internal.h" #ifdef WITH_CTRL_SHELL #define UNUSED(A) (void)(A) int ctrl_shell__connect(void) { if(data.port == PORT_UNDEFINED){ data.port = 1883; } if(data.mosq){ mosquitto_destroy(data.mosq); } data.mosq = mosquitto_new(data.clientid, true, NULL); if(!strcmp(data.url_scheme, "mqtts") || !strcmp(data.url_scheme, "wss")){ mosquitto_int_option(data.mosq, MOSQ_OPT_TLS_USE_OS_CERTS, 1); } if(data.transport == MOSQ_T_WEBSOCKETS){ mosquitto_int_option(data.mosq, MOSQ_OPT_TRANSPORT, data.transport); } if(data.username && data.password){ mosquitto_username_pw_set(data.mosq, data.username, data.password); } if(data.tls_cafile || data.tls_capath || data.tls_certfile || data.tls_keyfile){ int rc = mosquitto_tls_set(data.mosq, data.tls_cafile, data.tls_capath, data.tls_certfile, data.tls_keyfile, NULL); if(rc){ if(rc == MOSQ_ERR_INVAL){ ctrl_shell_printf("%sError setting TLS options: File not found.%s\n", ANSI_ERROR, ANSI_RESET); }else{ ctrl_shell_printf("%sError setting TLS options: %s.%s\n", ANSI_ERROR, mosquitto_strerror(rc), ANSI_RESET); } } } mosquitto_int_option(data.mosq, MOSQ_OPT_PROTOCOL_VERSION, 5); mosquitto_connect_callback_set(data.mosq, ctrl_shell__on_connect); mosquitto_subscribe_callback_set(data.mosq, ctrl_shell__on_subscribe); mosquitto_publish_v5_callback_set(data.mosq, ctrl_shell__on_publish); mosquitto_message_callback_set(data.mosq, ctrl_shell__on_message); ctrl_shell__connect_blocking(data.hostname, data.port); if(data.connect_rc){ ctrl_shell_printf("%sUnable to connect: %s%s\n", ANSI_ERROR, mosquitto_reason_string(data.connect_rc), ANSI_RESET); return 1; } ctrl_shell__post_connect_init(); return 0; } void ctrl_shell__disconnect(void) { if(!data.mosq){ return; } mosquitto_disconnect(data.mosq); mosquitto_loop_stop(data.mosq, false); mosquitto_destroy(data.mosq); data.mosq = NULL; for(int i=0; i All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include "ctrl_shell_internal.h" #ifdef WITH_CTRL_SHELL #define UNUSED(A) (void)(A) void completion_tree_arg_list_args_free(struct completion_tree_arg_list *arg_list) { struct completion_tree_arg *arg, *next; if(!arg_list){ return; } arg = arg_list->args; while(arg){ next = arg->next; free(arg); arg = next; } arg_list->args = NULL; } void completion_tree_arg_list_free(struct completion_tree_arg_list *arg_list) { if(!arg_list){ return; } if(arg_list->is_shared){ return; } completion_tree_arg_list_args_free(arg_list); free(arg_list); } void completion_tree_cmd_free(struct completion_tree_cmd *cmd) { if(!cmd){ return; } for(int i=0; iarg_list_count; i++){ completion_tree_arg_list_free(cmd->arg_lists[i]); } free(cmd->arg_lists); free(cmd); } void completion_tree_free(struct completion_tree_root *tree) { struct completion_tree_cmd *cmd, *next; if(!tree){ return; } cmd = tree->commands; while(cmd){ next = cmd->next; completion_tree_cmd_free(cmd); cmd = next; } free(tree); } struct completion_tree_cmd *completion_tree_cmd_add(struct completion_tree_root *root, struct completion_tree_arg_list *help_arg_list, const char *name) { struct completion_tree_cmd *new_node; new_node = calloc(1, sizeof(struct completion_tree_cmd) + strlen(name) + 1); if(!new_node){ return NULL; } strcpy(new_node->name, name); new_node->next = root->commands; root->commands = new_node; completion_tree_arg_list_add_arg(help_arg_list, name); return new_node; } struct completion_tree_arg_list *completion_tree_cmd_new_arg_list(void) { return calloc(1, sizeof(struct completion_tree_arg_list)); } void completion_tree_cmd_append_arg_list(struct completion_tree_cmd *cmd, struct completion_tree_arg_list *new_list) { struct completion_tree_arg_list **arg_list; arg_list = realloc(cmd->arg_lists, (size_t)(cmd->arg_list_count+1)*sizeof(struct completion_tree_arg_list *)); if(!arg_list){ return; } cmd->arg_lists = arg_list; cmd->arg_lists[cmd->arg_list_count] = new_list; cmd->arg_list_count++; } struct completion_tree_arg_list *completion_tree_cmd_add_arg_list(struct completion_tree_cmd *cmd) { if(!cmd){ return NULL; } struct completion_tree_arg_list *new_list; new_list = completion_tree_cmd_new_arg_list(); if(!new_list){ return NULL; } completion_tree_cmd_append_arg_list(cmd, new_list); return new_list; } void completion_tree_arg_list_add_arg(struct completion_tree_arg_list *arg_list, const char *name) { if(!arg_list || !name){ return; } struct completion_tree_arg *new_node; new_node = calloc(1, sizeof(struct completion_tree_arg) + strlen(name) + 1); if(!new_node){ return; } strcpy(new_node->name, name); new_node->next = arg_list->args; arg_list->args = new_node; } #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_dynsec.c000066400000000000000000001175151514232433600275720ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include #include "ctrl_shell_internal.h" #include "json_help.h" #ifdef WITH_CTRL_SHELL #define UNUSED(A) (void)(A) static struct completion_tree_root *commands_dynsec = NULL; static struct completion_tree_arg_list *tree_clients = NULL; static struct completion_tree_arg_list *tree_groups = NULL; static struct completion_tree_arg_list *tree_roles = NULL; static bool do_print_list = false; static void command_tree_create(void) { struct completion_tree_cmd *cmd; struct completion_tree_arg_list *arg_list; struct completion_tree_arg_list *help_arg_list; completion_tree_arg_list_args_free(tree_clients); completion_tree_arg_list_args_free(tree_groups); completion_tree_arg_list_args_free(tree_roles); if(commands_dynsec){ return; } commands_dynsec = calloc(1, sizeof(struct completion_tree_root)); if(!tree_clients){ tree_clients = completion_tree_cmd_new_arg_list(); tree_clients->is_shared = true; } if(!tree_groups){ tree_groups = completion_tree_cmd_new_arg_list(); tree_groups->is_shared = true; } if(!tree_roles){ tree_roles = completion_tree_cmd_new_arg_list(); tree_roles->is_shared = true; } cmd = completion_tree_cmd_add(commands_dynsec, NULL, "help"); help_arg_list = completion_tree_cmd_add_arg_list(cmd); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "addClientRole"); completion_tree_cmd_append_arg_list(cmd, tree_clients); completion_tree_cmd_append_arg_list(cmd, tree_roles); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "addGroupClient"); completion_tree_cmd_append_arg_list(cmd, tree_groups); completion_tree_cmd_append_arg_list(cmd, tree_clients); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "addGroupRole"); completion_tree_cmd_append_arg_list(cmd, tree_groups); completion_tree_cmd_append_arg_list(cmd, tree_roles); { cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "addRoleACL"); completion_tree_cmd_append_arg_list(cmd, tree_roles); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "publishClientReceive"); completion_tree_arg_list_add_arg(arg_list, "publishClientSend"); completion_tree_arg_list_add_arg(arg_list, "subscribeLiteral"); completion_tree_arg_list_add_arg(arg_list, "subscribePattern"); completion_tree_arg_list_add_arg(arg_list, "unsubscribeLiteral"); completion_tree_arg_list_add_arg(arg_list, "unsubscribePattern"); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "allow"); completion_tree_arg_list_add_arg(arg_list, "deny"); } completion_tree_cmd_add(commands_dynsec, help_arg_list, "createClient"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "createGroup"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "createRole"); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "deleteClient"); completion_tree_cmd_append_arg_list(cmd, tree_clients); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "deleteGroup"); completion_tree_cmd_append_arg_list(cmd, tree_groups); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "deleteRole"); completion_tree_cmd_append_arg_list(cmd, tree_roles); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "disableClient"); completion_tree_cmd_append_arg_list(cmd, tree_clients); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "enableClient"); completion_tree_cmd_append_arg_list(cmd, tree_clients); completion_tree_cmd_add(commands_dynsec, help_arg_list, "getAnonymousGroup"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "getDetails"); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "getClient"); completion_tree_cmd_append_arg_list(cmd, tree_clients); completion_tree_cmd_add(commands_dynsec, help_arg_list, "getDefaultACLAccess"); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "getGroup"); completion_tree_cmd_append_arg_list(cmd, tree_groups); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "getRole"); completion_tree_cmd_append_arg_list(cmd, tree_roles); completion_tree_cmd_add(commands_dynsec, help_arg_list, "listClients"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "listGroups"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "listRoles"); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "removeClientRole"); completion_tree_cmd_append_arg_list(cmd, tree_clients); completion_tree_cmd_append_arg_list(cmd, tree_roles); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "removeGroupClient"); completion_tree_cmd_append_arg_list(cmd, tree_groups); completion_tree_cmd_append_arg_list(cmd, tree_clients); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "removeGroupRole"); completion_tree_cmd_append_arg_list(cmd, tree_groups); completion_tree_cmd_append_arg_list(cmd, tree_roles); { cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "removeRoleACL"); completion_tree_cmd_append_arg_list(cmd, tree_roles); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "publishClientReceive"); completion_tree_arg_list_add_arg(arg_list, "publishClientSend"); completion_tree_arg_list_add_arg(arg_list, "subscribeLiteral"); completion_tree_arg_list_add_arg(arg_list, "subscribePattern"); completion_tree_arg_list_add_arg(arg_list, "unsubscribeLiteral"); completion_tree_arg_list_add_arg(arg_list, "unsubscribePattern"); } cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "setAnonymousGroup"); completion_tree_cmd_append_arg_list(cmd, tree_groups); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "setClientId"); completion_tree_cmd_append_arg_list(cmd, tree_clients); cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "setClientPassword"); completion_tree_cmd_append_arg_list(cmd, tree_clients); { cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "modifyClient"); completion_tree_cmd_append_arg_list(cmd, tree_clients); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "textName"); completion_tree_arg_list_add_arg(arg_list, "textDescription"); } { cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "modifyGroup"); completion_tree_cmd_append_arg_list(cmd, tree_groups); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "textName"); completion_tree_arg_list_add_arg(arg_list, "textDescription"); } { cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "modifyRole"); completion_tree_cmd_append_arg_list(cmd, tree_roles); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "allowWildcardSubs"); completion_tree_arg_list_add_arg(arg_list, "textName"); completion_tree_arg_list_add_arg(arg_list, "textDescription"); } { cmd = completion_tree_cmd_add(commands_dynsec, help_arg_list, "setDefaultACLAccess"); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "publishClientReceive"); completion_tree_arg_list_add_arg(arg_list, "publishClientSend"); completion_tree_arg_list_add_arg(arg_list, "subscribe"); completion_tree_arg_list_add_arg(arg_list, "unsubscribe"); arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_arg_list_add_arg(arg_list, "allow"); completion_tree_arg_list_add_arg(arg_list, "deny"); } completion_tree_cmd_add(commands_dynsec, help_arg_list, "disconnect"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "return"); completion_tree_cmd_add(commands_dynsec, help_arg_list, "exit"); } static void print_help(char **saveptr) { char *command = strtok_r(NULL, " ", saveptr); if(command){ if(!strcasecmp(command, "addClientRole")){ ctrl_shell_print_help_command("addClientRole "); ctrl_shell_printf("\nAdds a role directly to a client.\n"); }else if(!strcasecmp(command, "addGroupClient")){ ctrl_shell_print_help_command("addGroupClient "); ctrl_shell_printf("\nAdds a client to a group.\n"); }else if(!strcasecmp(command, "addGroupRole")){ ctrl_shell_print_help_command("addGroupRole "); ctrl_shell_printf("\nAdds a role to a group.\n"); }else if(!strcasecmp(command, "addRoleACL")){ ctrl_shell_print_help_command("addRoleACL publishClientReceive allow|deny [priority] "); ctrl_shell_print_help_command("addRoleACL publishClientSend allow|deny [priority] "); ctrl_shell_print_help_command("addRoleACL subscribeLiteral allow|deny [priority] "); ctrl_shell_print_help_command("addRoleACL subscribePattern allow|deny [priority] "); ctrl_shell_print_help_command("addRoleACL unsubscribeLiteral allow|deny [priority] "); ctrl_shell_print_help_command("addRoleACL unsubscribePattern allow|deny [priority] "); ctrl_shell_printf("\nAdds an ACL to a role, with an optional priority.\n"); ctrl_shell_printf("\nACLs of a specific type within a role are processed in order from highest to lowest priority with the first matching ACL applying.\n"); }else if(!strcasecmp(command, "createClient")){ ctrl_shell_print_help_command("createClient [password [clientid]]"); ctrl_shell_printf("\nCreate a client with password and optional client id.\n"); }else if(!strcasecmp(command, "createGroup")){ ctrl_shell_print_help_command("createGroup "); ctrl_shell_printf("\nCreate a new group.\n"); }else if(!strcasecmp(command, "createRole")){ ctrl_shell_print_help_command("createRole "); ctrl_shell_printf("\nCreate a new role.\n"); }else if(!strcasecmp(command, "deleteClient")){ ctrl_shell_print_help_command("deleteClient "); ctrl_shell_printf("\nDelete a client\n"); }else if(!strcasecmp(command, "deleteGroup")){ ctrl_shell_print_help_command("deleteGroup "); ctrl_shell_printf("\nDelete a group\n"); }else if(!strcasecmp(command, "deleteRole")){ ctrl_shell_print_help_command("deleteRole "); ctrl_shell_printf("\nDelete a role\n"); }else if(!strcasecmp(command, "disableClient")){ ctrl_shell_print_help_command("disableClient "); ctrl_shell_printf("\nDisable a client. This client will not be able to log in, and will be kicked if it has an existing session.\n"); }else if(!strcasecmp(command, "enableClient")){ ctrl_shell_print_help_command("enableClient "); ctrl_shell_printf("\nEnable a client. Disabled clients are unable to log in.\n"); }else if(!strcasecmp(command, "getAnonymousGroup")){ ctrl_shell_print_help_command("getAnonymousGroup"); ctrl_shell_printf("\nPrint the group configured as the anonymous group.\n"); }else if(!strcasecmp(command, "getDetails")){ ctrl_shell_print_help_command("getDetails"); ctrl_shell_printf("\nPrint details including the client, group, and role count, and the current change index.\n"); }else if(!strcasecmp(command, "getClient")){ ctrl_shell_print_help_command("getClient "); ctrl_shell_printf("\nPrint details of a client and its groups and direct roles.\n"); }else if(!strcasecmp(command, "getDefaultACLAccess")){ ctrl_shell_print_help_command("getDefaultACLAccess"); ctrl_shell_printf("\nPrint the default allow/deny values for the different classes of ACL.\n"); }else if(!strcasecmp(command, "getGroup")){ ctrl_shell_print_help_command("getGroup "); ctrl_shell_printf("\nPrint details of a group and its roles.\n"); }else if(!strcasecmp(command, "getRole")){ ctrl_shell_print_help_command("getRole "); ctrl_shell_printf("\nPrint details of a role and its ACLs.\n"); }else if(!strcasecmp(command, "listClients")){ ctrl_shell_print_help_command("listClients [count [offset]]"); ctrl_shell_printf("\nPrint a list of clients configured in the dynsec plugin, with an optional total count and list offset.\n"); }else if(!strcasecmp(command, "listGroups")){ ctrl_shell_print_help_command("listGroups [count [offset]]"); ctrl_shell_printf("\nPrint a list of groups configured in the dynsec plugin, with an optional total count and list offset.\n"); }else if(!strcasecmp(command, "listRoles")){ ctrl_shell_print_help_command("listRoles [count [offset]]"); ctrl_shell_printf("\nPrint a list of roles configured in the dynsec plugin, with an optional total count and list offset.\n"); }else if(!strcasecmp(command, "removeClientRole")){ ctrl_shell_print_help_command("removeClientRole "); ctrl_shell_printf("\nRemoves a role from a client, where the role was directly attached to the client.\n"); }else if(!strcasecmp(command, "removeGroupClient")){ ctrl_shell_print_help_command("removeGroupClient "); ctrl_shell_printf("\nRemoves a client from a group.\n"); }else if(!strcasecmp(command, "removeGroupRole")){ ctrl_shell_print_help_command("removeGroupRole "); ctrl_shell_printf("\nRemoves a role from a group.\n"); }else if(!strcasecmp(command, "removeRoleACL")){ ctrl_shell_print_help_command("removeRoleACL publishClientReceive "); ctrl_shell_print_help_command("removeRoleACL publishClientSend "); ctrl_shell_print_help_command("removeRoleACL subscribeLiteral "); ctrl_shell_print_help_command("removeRoleACL subscribePattern "); ctrl_shell_print_help_command("removeRoleACL unsubscribeLiteral "); ctrl_shell_print_help_command("removeRoleACL unsubscribePattern "); ctrl_shell_printf("\nRemoves an ACL from a role.\n"); }else if(!strcasecmp(command, "setAnonymousGroup")){ ctrl_shell_print_help_command("setAnonymousGroup "); ctrl_shell_printf("\nSets the anonymous group to a new group.\n"); }else if(!strcasecmp(command, "setClientId")){ ctrl_shell_print_help_command("setClientId "); ctrl_shell_print_help_command("setClientId "); ctrl_shell_printf("\nSets or clears the clientid associated with a client. If a client has a clientid, all three of username, password, and clientid must match for a client to be able to authenticate.\n"); }else if(!strcasecmp(command, "setClientPassword")){ ctrl_shell_print_help_command("setClientPassword [password]"); ctrl_shell_printf("\nSets a new password for a client.\n"); }else if(!strcasecmp(command, "setDefaultACLAccess")){ ctrl_shell_print_help_command("setDefaultACLAccess publishClientReceive allow|deny"); ctrl_shell_print_help_command("setDefaultACLAccess publishClientSend allow|deny"); ctrl_shell_print_help_command("setDefaultACLAccess subscribe allow|deny"); ctrl_shell_print_help_command("setDefaultACLAccess unsubscribe allow|deny"); ctrl_shell_printf("\nSets the default ACL access to use for an ACL type. The default access will be applied if no other ACL rules match.\n"); ctrl_shell_printf("Setting a rule to 'allow' means that if no ACLs match, it will be accepted.\n"); ctrl_shell_printf("Setting a rule to 'deny' means that if no ACLs match, it will be denied.\n"); }else if(!strcasecmp(command, "modifyClient")){ ctrl_shell_print_help_command("modifyClient textName "); ctrl_shell_print_help_command("modifyClient textDescription "); ctrl_shell_printf("\nModify the text name or text description for a client.\n"); ctrl_shell_printf("These are free-text fields for your own use.\n"); }else if(!strcasecmp(command, "modifyGroup")){ ctrl_shell_print_help_command("modifyGroup textName "); ctrl_shell_print_help_command("modifyGroup textDescription "); ctrl_shell_printf("\nModify the text name or text description for a group.\n"); ctrl_shell_printf("These are free-text fields for your own use.\n"); }else if(!strcasecmp(command, "modifyRole")){ ctrl_shell_print_help_command("modifyRole allowWildcardSubs true|false"); ctrl_shell_print_help_command("modifyRole textName "); ctrl_shell_print_help_command("modifyRole textDescription "); ctrl_shell_printf("\nModify the text name or text description for a role.\n"); ctrl_shell_printf("These are free-text fields for your own use.\n"); }else{ ctrl_shell_print_help_final(command, "dynsec"); } }else{ ctrl_shell_printf("This is the mosquitto_ctrl interactive shell, for controlling aspects of a mosquitto broker.\n"); ctrl_shell_printf("You are in dynsec mode, for controlling the dynamic-security clients, groups, and roles used in authentication and authorisation.\n"); ctrl_shell_printf("Use '%sreturn%s' to leave dynsec mode.\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Find help on a command using '%shelp %s'\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Press tab multiple times to find currently available commands.\n\n"); } } static int send_set_default_acl_access(char **saveptr) { const char *acltype, *allow_s; acltype = strtok_r(NULL, " ", saveptr); if(!acltype || ( strcasecmp(acltype, "publishClientReceive") && strcasecmp(acltype, "publishClientSend") && strcasecmp(acltype, "subscribe") && strcasecmp(acltype, "unsubscribe") )){ ctrl_shell_printf("setDefaultACLAccess acltype allow|deny\n"); return MOSQ_ERR_INVAL; } allow_s = strtok_r(NULL, " ", saveptr); if(!allow_s || ( strcasecmp(allow_s, "allow") && strcasecmp(allow_s, "deny") )){ ctrl_shell_printf("setDefaultACLAccess acltype allow|deny\n"); return MOSQ_ERR_INVAL; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", "setDefaultACLAccess"); cJSON *j_acls = cJSON_AddArrayToObject(j_command, "acls"); cJSON *j_acl = cJSON_CreateObject(); cJSON_AddItemToArray(j_acls, j_acl); cJSON_AddStringToObject(j_acl, "acltype", acltype); cJSON_AddBoolToObject(j_acl, "allow", !strcmp(allow_s, "allow")); return ctrl_shell_publish_blocking(j_command); } static int list_update(const char *command) { cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); do_print_list = false; return ctrl_shell_publish_blocking(j_command); } static int list_generic(const char *command, char **saveptr) { const char *count, *offset; count = strtok_r(NULL, " ", saveptr); offset = strtok_r(NULL, " ", saveptr); cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); if(count){ cJSON_AddNumberToObject(j_command, "count", atoi(count)); } if(offset){ cJSON_AddNumberToObject(j_command, "offset", atoi(offset)); } return ctrl_shell_publish_blocking(j_command); } static int send_create_client(char **saveptr) { char *username = strtok_r(NULL, " ", saveptr); if(!username){ ctrl_shell_printf("createClient username [password [clientid]]\n"); ctrl_shell_printf("createClient username password [clientid]\n"); return MOSQ_ERR_INVAL; } char *password = strtok_r(NULL, " ", saveptr); char pwbuf1[200]; char pwbuf2[200]; char *clientid = NULL; if(password){ clientid = strtok_r(NULL, " ", saveptr); }else{ if(!ctrl_shell_get_password(pwbuf1, sizeof(pwbuf1)) || !ctrl_shell_get_password(pwbuf2, sizeof(pwbuf2))){ ctrl_shell_printf("No password.\n"); return MOSQ_ERR_INVAL; } if(strcmp(pwbuf1, pwbuf2)){ ctrl_shell_printf("Passwords do not match.\n"); return MOSQ_ERR_INVAL; } password = pwbuf1; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", "createClient"); cJSON_AddStringToObject(j_command, "username", username); cJSON_AddStringToObject(j_command, "password", password); if(clientid){ cJSON_AddStringToObject(j_command, "clientid", clientid); } ctrl_shell_publish_blocking(j_command); return MOSQ_ERR_SUCCESS; } static int send_add_role_acl(char **saveptr) { char *rolename = strtok_r(NULL, " ", saveptr); char *acltype = strtok_r(NULL, " ", saveptr); char *allow_s = strtok_r(NULL, " ", saveptr); char *s_priority = strtok_r(NULL, " ", saveptr); char *topic = strtok_r(NULL, " ", saveptr); int priority = -1; if(s_priority){ if(topic){ priority = atoi(s_priority); }else{ topic = s_priority; } } if(!rolename || !acltype || !allow_s || !topic){ ctrl_shell_printf("addRoleACL rolename acltype allow|deny [priority] topic\n"); return MOSQ_ERR_INVAL; } if(strcasecmp(acltype, "publishClientReceive") && strcasecmp(acltype, "publishClientSend") && strcasecmp(acltype, "subscribeLiteral") && strcasecmp(acltype, "subscribePattern") && strcasecmp(acltype, "unsubscribeLiteral") && strcasecmp(acltype, "unsubscribePattern") ){ ctrl_shell_printf("addRoleACL rolename acltype allow|deny [priority] topic\n"); ctrl_shell_printf("Invalid acltype '%s'\n", acltype); return MOSQ_ERR_INVAL; } if(strcasecmp(allow_s, "allow") && strcasecmp(allow_s, "deny")){ ctrl_shell_printf("addRoleACL rolename acltype allow|deny [priority] topic\n"); ctrl_shell_printf("Invalid allow/deny '%s'\n", allow_s); return MOSQ_ERR_INVAL; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", "addRoleACL"); cJSON_AddStringToObject(j_command, "rolename", rolename); cJSON_AddStringToObject(j_command, "acltype", acltype); cJSON_AddNumberToObject(j_command, "priority", priority); cJSON_AddStringToObject(j_command, "topic", topic); cJSON_AddBoolToObject(j_command, "allow", !strcasecmp(allow_s, "allow")); return ctrl_shell_publish_blocking(j_command); } static int send_remove_role_acl(char **saveptr) { char *rolename = strtok_r(NULL, " ", saveptr); char *acltype = strtok_r(NULL, " ", saveptr); char *topic = strtok_r(NULL, " ", saveptr); if(!rolename || !acltype || !topic){ ctrl_shell_printf("removeRoleACL rolename acltype topic\n"); return MOSQ_ERR_INVAL; } if(strcasecmp(acltype, "publishClientReceive") && strcasecmp(acltype, "publishClientSend") && strcasecmp(acltype, "subscribeLiteral") && strcasecmp(acltype, "subscribePattern") && strcasecmp(acltype, "unsubscribeLiteral") && strcasecmp(acltype, "unsubscribePattern") ){ ctrl_shell_printf("removeRoleACL rolename acltype topic\n"); ctrl_shell_printf("Invalid acltype '%s'\n", acltype); return MOSQ_ERR_INVAL; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", "removeRoleACL"); cJSON_AddStringToObject(j_command, "rolename", rolename); cJSON_AddStringToObject(j_command, "acltype", acltype); cJSON_AddStringToObject(j_command, "topic", topic); return ctrl_shell_publish_blocking(j_command); } static int send_modify(const char *command, const char *objectname, char **saveptr) { char *name = strtok_r(NULL, " ", saveptr); char *itemlabel = strtok_r(NULL, " ", saveptr); char *itemvalue = *saveptr; if(!name || !itemlabel || !itemvalue){ ctrl_shell_printf("%s %s \n", command, objectname); return MOSQ_ERR_INVAL; } if(strcasecmp(itemlabel, "textName") && strcasecmp(itemlabel, "textDescription") && strcasecmp(itemlabel, "allowWildcardSubs")){ ctrl_shell_printf("%s %s \n", command, objectname); ctrl_shell_printf("Unknown property '%s'\n", itemlabel); return MOSQ_ERR_INVAL; } if(!strcasecmp(itemlabel, "allowWildcardSubs")){ if(strcasecmp(itemvalue, "true") && strcasecmp(itemvalue, "false")){ ctrl_shell_printf("%s %s \n", command, objectname); ctrl_shell_printf("Invalid value '%s'\n", itemvalue); return MOSQ_ERR_INVAL; } } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", command); cJSON_AddStringToObject(j_command, objectname, name); if(!strcasecmp(itemlabel, "allowWildcardSubs")){ cJSON_AddBoolToObject(j_command, itemlabel, !strcasecmp(itemvalue, "true")); }else{ cJSON_AddStringToObject(j_command, itemlabel, itemvalue); } return ctrl_shell_publish_blocking(j_command); } static int send_set_client_password(char **saveptr) { char *username, *password; char pwbuf1[200], pwbuf2[200]; username = strtok_r(NULL, " ", saveptr); if(!username){ ctrl_shell_printf("setClientPassword [password]\n"); return MOSQ_ERR_INVAL; } password = strtok_r(NULL, " ", saveptr); if(!password){ if(!ctrl_shell_get_password(pwbuf1, sizeof(pwbuf1)) || !ctrl_shell_get_password(pwbuf2, sizeof(pwbuf2))){ ctrl_shell_printf("No password.\n"); return MOSQ_ERR_INVAL; } if(strcmp(pwbuf1, pwbuf2)){ ctrl_shell_printf("Passwords do not match.\n"); return MOSQ_ERR_INVAL; } password = pwbuf1; } cJSON *j_command = cJSON_CreateObject(); cJSON_AddStringToObject(j_command, "command", "setClientPassword"); cJSON_AddStringToObject(j_command, "username", username); cJSON_AddStringToObject(j_command, "password", password); return ctrl_shell_publish_blocking(j_command); } static void line_callback(char *line) { if(!line){ ctrl_shell_callback_final(NULL); return; } ctrl_shell_rtrim(line); if(strlen(line) > 0){ add_history(line); }else{ free(line); return; } char *saveptr = NULL; char *command = strtok_r(line, " ", &saveptr); if(!command){ free(line); return; } if(!strcasecmp(command, "addClientRole")){ ctrl_shell_command_generic_arg2("addClientRole", "username", "rolename", &saveptr); }else if(!strcasecmp(command, "addGroupClient")){ ctrl_shell_command_generic_arg2("addGroupClient", "groupname", "username", &saveptr); }else if(!strcasecmp(command, "addGroupRole")){ ctrl_shell_command_generic_arg2("addGroupRole", "groupname", "rolename", &saveptr); }else if(!strcasecmp(command, "addRoleACL")){ send_add_role_acl(&saveptr); }else if(!strcasecmp(command, "createClient")){ if(send_create_client(&saveptr) == MOSQ_ERR_SUCCESS){ list_update("listClients"); } }else if(!strcasecmp(command, "createGroup")){ if(ctrl_shell_command_generic_arg1("createGroup", "groupname", &saveptr) == MOSQ_ERR_SUCCESS){ list_update("listGroups"); } }else if(!strcasecmp(command, "createRole")){ if(ctrl_shell_command_generic_arg1("createRole", "rolename", &saveptr) == MOSQ_ERR_SUCCESS){ list_update("listRoles"); } }else if(!strcasecmp(command, "deleteClient")){ if(ctrl_shell_command_generic_arg1("deleteClient", "username", &saveptr) == MOSQ_ERR_SUCCESS){ list_update("listClients"); } }else if(!strcasecmp(command, "deleteGroup")){ if(ctrl_shell_command_generic_arg1("deleteGroup", "groupname", &saveptr) == MOSQ_ERR_SUCCESS){ list_update("listGroups"); } }else if(!strcasecmp(command, "deleteRole")){ if(ctrl_shell_command_generic_arg1("deleteRole", "rolename", &saveptr) == MOSQ_ERR_SUCCESS){ list_update("listRoles"); } }else if(!strcasecmp(command, "disableClient")){ ctrl_shell_command_generic_arg1("disableClient", "username", &saveptr); }else if(!strcasecmp(command, "enableClient")){ ctrl_shell_command_generic_arg1("enableClient", "username", &saveptr); }else if(!strcasecmp(command, "getAnonymousGroup")){ ctrl_shell_command_generic_arg0("getAnonymousGroup"); }else if(!strcasecmp(command, "getDetails")){ ctrl_shell_command_generic_arg0("getDetails"); }else if(!strcasecmp(command, "getClient")){ ctrl_shell_command_generic_arg1("getClient", "username", &saveptr); }else if(!strcasecmp(command, "getDefaultACLAccess")){ ctrl_shell_command_generic_arg0("getDefaultACLAccess"); }else if(!strcasecmp(command, "getGroup")){ ctrl_shell_command_generic_arg1("getGroup", "groupname", &saveptr); }else if(!strcasecmp(command, "getRole")){ ctrl_shell_command_generic_arg1("getRole", "rolename", &saveptr); }else if(!strcasecmp(command, "listClients")){ do_print_list = true; list_generic("listClients", &saveptr); }else if(!strcasecmp(command, "listGroups")){ do_print_list = true; list_generic("listGroups", &saveptr); }else if(!strcasecmp(command, "listRoles")){ do_print_list = true; list_generic("listRoles", &saveptr); }else if(!strcasecmp(command, "removeClientRole")){ ctrl_shell_command_generic_arg2("removeClientRole", "username", "rolename", &saveptr); }else if(!strcasecmp(command, "removeGroupClient")){ ctrl_shell_command_generic_arg2("removeGroupClient", "groupname", "username", &saveptr); }else if(!strcasecmp(command, "removeGroupRole")){ ctrl_shell_command_generic_arg2("removeGroupRole", "groupname", "rolename", &saveptr); }else if(!strcasecmp(command, "removeRoleACL")){ send_remove_role_acl(&saveptr); }else if(!strcasecmp(command, "setAnonymousGroup")){ ctrl_shell_command_generic_arg1("setAnonymousGroup", "groupname", &saveptr); }else if(!strcasecmp(command, "setClientId")){ ctrl_shell_command_generic_arg2("setClientId", "username", "clientid", &saveptr); }else if(!strcasecmp(command, "setClientPassword")){ send_set_client_password(&saveptr); }else if(!strcasecmp(command, "modifyClient")){ send_modify("modifyClient", "username", &saveptr); }else if(!strcasecmp(command, "modifyGroup")){ send_modify("modifyGroup", "groupname", &saveptr); }else if(!strcasecmp(command, "modifyRole")){ send_modify("modifyRole", "rolename", &saveptr); }else if(!strcasecmp(command, "setDefaultACLAccess")){ send_set_default_acl_access(&saveptr); }else if(!strcasecmp(command, "help")){ print_help(&saveptr); }else{ if(!ctrl_shell_callback_final(line)){ ctrl_shell_printf("Unknown command '%s'\n", command); } } free(line); } static void print_json_value(cJSON *value, const char *null_value) { if(value){ if(cJSON_IsString(value)){ if(value->valuestring){ ctrl_shell_print_value(0, "%s", value->valuestring); } }else{ char buffer[1024]; cJSON_PrintPreallocated(value, buffer, sizeof(buffer), 0); ctrl_shell_print_value(0, "%s", buffer); } }else if(null_value){ ctrl_shell_print_value(0, "%s", null_value); } } static void print_json_array(cJSON *j_list, const char *label, const char *element_name, const char *optional_element_name, const char *optional_element_null_value) { cJSON *j_elem; if(j_list && cJSON_IsArray(j_list) && cJSON_GetArraySize(j_list) > 0){ ctrl_shell_print_label(0, label); cJSON_ArrayForEach(j_elem, j_list){ if(cJSON_IsObject(j_elem)){ const char *stmp; if(json_get_string(j_elem, element_name, &stmp, false) != MOSQ_ERR_SUCCESS){ continue; } ctrl_shell_print_value(1, "%s", stmp); if(optional_element_name){ ctrl_shell_print_value(0, " (%s: ", optional_element_name); print_json_value(cJSON_GetObjectItem(j_elem, optional_element_name), optional_element_null_value); ctrl_shell_print_value(0, ")"); } }else if(cJSON_IsString(j_elem) && j_elem->valuestring){ ctrl_shell_print_value(1, "%s", j_elem->valuestring); } ctrl_shell_print_value(0, "\n"); } } } static void print_details(cJSON *j_data) { int64_t clientcount; int64_t groupcount; int64_t rolecount; int64_t changeindex; int align = (int)strlen("Change index: "); json_get_int64(j_data, "clientCount", &clientcount, true, 0); json_get_int64(j_data, "groupCount", &groupcount, true, 0); json_get_int64(j_data, "roleCount", &rolecount, true, 0); json_get_int64(j_data, "changeIndex", &changeindex, true, 0); ctrl_shell_print_label_value(0, "Client count:", align, "%ld\n", clientcount); ctrl_shell_print_label_value(0, "Group count:", align, "%ld\n", groupcount); ctrl_shell_print_label_value(0, "Role count:", align, "%ld\n", rolecount); ctrl_shell_print_label_value(0, "Change index:", align, "%ld\n", changeindex); } static void print_client(cJSON *j_data) { cJSON *j_client, *jtmp; j_client = cJSON_GetObjectItem(j_data, "client"); if(j_client == NULL){ ctrl_shell_printf("Invalid response from broker.\n"); return; } const char *username; if(json_get_string(j_client, "username", &username, false) != MOSQ_ERR_SUCCESS){ ctrl_shell_printf("Invalid response from broker.\n"); return; } ctrl_shell_print_label(0, "Username:"); ctrl_shell_print_value(1, "%s\n", username); const char *clientid; if(json_get_string(j_client, "clientid", &clientid, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Clientid:"); ctrl_shell_print_value(1, "%s\n", clientid); } jtmp = cJSON_GetObjectItem(j_client, "disabled"); if(jtmp && cJSON_IsBool(jtmp) && cJSON_IsTrue(jtmp)){ ctrl_shell_print_label(0, "Disabled:"); ctrl_shell_print_value(1, "true\n"); } const char *textname; if(json_get_string(j_client, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Text name:"); ctrl_shell_print_value(1, "%s\n", textname); } const char *textdescription; if(json_get_string(j_client, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Text description:"); ctrl_shell_print_value(1, "%s\n", textdescription); } print_json_array(cJSON_GetObjectItem(j_client, "roles"), "Roles:", "rolename", "priority", "-1"); print_json_array(cJSON_GetObjectItem(j_client, "groups"), "Groups:", "groupname", "priority", "-1"); } static void print_group(cJSON *j_data) { cJSON *j_group; j_group = cJSON_GetObjectItem(j_data, "group"); if(j_group == NULL){ ctrl_shell_printf("Invalid response from broker.\n"); return; } const char *groupname; if(json_get_string(j_group, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ ctrl_shell_printf("Invalid response from broker.\n"); return; } ctrl_shell_print_label(0, "Group name:"); ctrl_shell_print_value(1, "%s\n", groupname); const char *textname; if(json_get_string(j_group, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Text name:"); ctrl_shell_print_value(1, "%s\n", textname); } const char *textdescription; if(json_get_string(j_group, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Text description:"); ctrl_shell_print_value(1, "%s\n", textdescription); } print_json_array(cJSON_GetObjectItem(j_group, "roles"), "Roles:", "rolename", "priority", "-1"); print_json_array(cJSON_GetObjectItem(j_group, "clients"), "Clients:", "username", NULL, NULL); } static void print_role(cJSON *j_data) { cJSON *j_role; j_role = cJSON_GetObjectItem(j_data, "role"); if(j_role == NULL){ ctrl_shell_printf("Invalid response from broker.\n"); return; } const char *rolename; if(json_get_string(j_role, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ ctrl_shell_printf("Invalid response from broker.\n"); return; } ctrl_shell_print_label(0, "Role name:"); ctrl_shell_print_value(1, "%s\n", rolename); const char *textname; if(json_get_string(j_role, "textname", &textname, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Text name:"); ctrl_shell_print_value(1, "%s\n", textname); } const char *textdescription; if(json_get_string(j_role, "textdescription", &textdescription, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Text description:"); ctrl_shell_print_value(1, "%s\n", textdescription); } bool allowwildcardsubs; if(json_get_bool(j_role, "allowwildcardsubs", &allowwildcardsubs, false, false) == MOSQ_ERR_SUCCESS){ ctrl_shell_print_label(0, "Allow wildcard subscriptions:"); ctrl_shell_print_value(1, "%s\n", allowwildcardsubs?"true":"false"); } cJSON *j_acls = cJSON_GetObjectItem(j_role, "acls"); if(j_acls && cJSON_GetArraySize(j_acls) > 0){ ctrl_shell_print_label(0, "ACLs:"); cJSON *j_acl; cJSON_ArrayForEach(j_acl, j_acls){ const char *acltype; const char *topic; int priority; bool allow; if(json_get_string(j_acl, "acltype", &acltype, false) == MOSQ_ERR_SUCCESS && json_get_string(j_acl, "topic", &topic, false) == MOSQ_ERR_SUCCESS && json_get_int(j_acl, "priority", &priority, true, -1) == MOSQ_ERR_SUCCESS && json_get_bool(j_acl, "allow", &allow, false, false) == MOSQ_ERR_SUCCESS ){ const char *ANSI_ALLOW = allow?ANSI_POSITIVE:ANSI_NEGATIVE; ctrl_shell_print_value(1, "%-*s %s%s%s %s%s%s (priority %d)\n", (int)strlen("publishClientReceive"), acltype, ANSI_ALLOW, allow?"allow":"deny", ANSI_RESET, ANSI_TOPIC, topic, ANSI_RESET, priority); } } } } static void print_default_acls(cJSON *j_data) { cJSON *j_acls = cJSON_GetObjectItem(j_data, "acls"); if(j_acls && cJSON_GetArraySize(j_acls) > 0){ cJSON *j_acl; cJSON_ArrayForEach(j_acl, j_acls){ const char *acltype; bool allow; if(json_get_string(j_acl, "acltype", &acltype, false) == MOSQ_ERR_SUCCESS && json_get_bool(j_acl, "allow", &allow, false, false) == MOSQ_ERR_SUCCESS ){ ctrl_shell_print_value(0, "%-*s %s\n", (int)strlen("publishClientReceive"), acltype, allow?"allow":"deny"); } } } } static void response_callback(const char *command, cJSON *j_data, const char *payload) { UNUSED(payload); if(!strcmp(command, "listClients")){ completion_tree_arg_list_args_free(tree_clients); cJSON *clients, *client; clients = cJSON_GetObjectItem(j_data, "clients"); cJSON_ArrayForEach(client, clients){ if(do_print_list){ ctrl_shell_print_value(0, "%s\n", client->valuestring); } completion_tree_arg_list_add_arg(tree_clients, client->valuestring); } do_print_list = false; }else if(!strcmp(command, "listGroups")){ completion_tree_arg_list_args_free(tree_groups); cJSON *groups, *group; groups = cJSON_GetObjectItem(j_data, "groups"); cJSON_ArrayForEach(group, groups){ if(do_print_list){ ctrl_shell_print_value(0, "%s\n", group->valuestring); } completion_tree_arg_list_add_arg(tree_groups, group->valuestring); } do_print_list = false; }else if(!strcmp(command, "listRoles")){ completion_tree_arg_list_args_free(tree_roles); cJSON *roles, *role; roles = cJSON_GetObjectItem(j_data, "roles"); cJSON_ArrayForEach(role, roles){ if(do_print_list){ ctrl_shell_print_value(0, "%s\n", role->valuestring); } completion_tree_arg_list_add_arg(tree_roles, role->valuestring); } do_print_list = false; }else if(!strcasecmp(command, "getAnonymousGroup")){ cJSON *group, *groupname; group = cJSON_GetObjectItem(j_data, "group"); groupname = cJSON_GetObjectItem(group, "groupname"); ctrl_shell_print_value(0, "%s\n", groupname->valuestring); }else if(!strcasecmp(command, "getDetails")){ print_details(j_data); }else if(!strcasecmp(command, "getClient")){ print_client(j_data); }else if(!strcasecmp(command, "getGroup")){ print_group(j_data); }else if(!strcasecmp(command, "getRole")){ print_role(j_data); }else if(!strcasecmp(command, "getDefaultACLAccess")){ print_default_acls(j_data); }else{ //ctrl_shell_printf("%s %s\n", command, payload); } } static void on_subscribe(void) { int rc; rc = list_update("listClients"); if(rc){ ctrl_shell_printf("Check the dynsec module is configured on the broker.\n"); return; } list_update("listGroups"); list_update("listRoles"); } static void ctrl_shell__dynsec_cleanup(void) { completion_tree_free(commands_dynsec); commands_dynsec = NULL; completion_tree_arg_list_args_free(tree_clients); completion_tree_arg_list_args_free(tree_groups); completion_tree_arg_list_args_free(tree_roles); free(tree_clients); free(tree_groups); free(tree_roles); tree_clients = NULL; tree_groups = NULL; tree_roles = NULL; } void ctrl_shell__dynsec_init(struct ctrl_shell__module *mod) { command_tree_create(); mod->completion_commands = commands_dynsec; mod->request_topic = "$CONTROL/dynamic-security/v1"; mod->response_topic = "$CONTROL/dynamic-security/v1/response"; mod->line_callback = line_callback; mod->response_callback = response_callback; mod->on_subscribe = on_subscribe; mod->cleanup = ctrl_shell__dynsec_cleanup; } #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_internal.h000066400000000000000000000061111514232433600301130ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef CTRL_SHELL_INTERNAL_H #define CTRL_SHELL_INTERNAL_H #ifdef WITH_CTRL_SHELL #ifdef __cplusplus extern "C" { #endif #include #include #ifdef WITH_EDITLINE #include #elif defined(WITH_READLINE) #include #include #endif #include #include "ctrl_shell.h" #include "mosquitto_ctrl.h" struct ctrl_shell { char **subscription_list; int subscription_list_count; int run; struct mosquitto *mosq; rl_vcpfunc_t *line_callback; pthread_cond_t response_cond; pthread_mutex_t response_mutex; bool response_received; const char *request_topic; struct completion_tree_root *commands; void (*response_callback)(const char *command, cJSON *data, const char *payload); void (*mod_cleanup)(void); const char *url_scheme; char *username; char *password; char *clientid; char *tls_cafile; char *tls_capath; char *tls_certfile; char *tls_keyfile; char *hostname; int port; int connect_rc; int subscribe_rc; int publish_rc; int transport; }; struct ctrl_shell__module { struct completion_tree_root *completion_commands; const char *request_topic; const char *response_topic; void (*response_callback)(const char *command, cJSON *data, const char *payload); void (*line_callback)(char *line); void (*on_subscribe)(void); void (*cleanup)(void); }; extern struct ctrl_shell data; extern char prompt[200]; void term__set_echo(bool echo); int do_connect(void); void ctrl_shell__pre_connect_init(void); void ctrl_shell__pre_connect_cleanup(void); void ctrl_shell__post_connect_init(void); void ctrl_shell__post_connect_cleanup(void); void ctrl_shell__load_module(void (*mod_init)(struct ctrl_shell__module *mod)); void ctrl_shell__connect_blocking(const char *hostname, int port); void ctrl_shell__on_connect(struct mosquitto *mosq, void *userdata, int rc); void ctrl_shell__on_subscribe(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos); void ctrl_shell__on_publish(struct mosquitto *mosq, void *userdata, int mid, int reason_code, const mosquitto_property *props); void ctrl_shell__on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg); void ctrl_shell__broker_init(struct ctrl_shell__module *mod); void ctrl_shell__dynsec_init(struct ctrl_shell__module *mod); void ctrl_shell__main(struct mosq_config *config); int ctrl_shell__connect(void); void ctrl_shell__disconnect(void); void ctrl_shell__output(const char *buf); #ifdef __cplusplus } #endif #endif #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_io.c000066400000000000000000000014441514232433600267050ustar00rootroot00000000000000/* Copyright (c) 2025 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include "ctrl_shell.h" void ctrl_shell__output(const char *buf) { printf("%s", buf); } char *ctrl_shell_fgets(char *s, int size, FILE *stream) { return fgets(s, size, stream); } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_post_connect.c000066400000000000000000000102321514232433600307670ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include "ctrl_shell_internal.h" #ifdef WITH_CTRL_SHELL #define UNUSED(A) (void)(A) static struct completion_tree_root *commands_post_connect = NULL; static void command_tree_create(void) { struct completion_tree_cmd *cmd; struct completion_tree_arg_list *help_arg_list; if(commands_post_connect){ return; } commands_post_connect = calloc(1, sizeof(struct completion_tree_root)); cmd = completion_tree_cmd_add(commands_post_connect, NULL, "help"); help_arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_cmd_add(commands_post_connect, help_arg_list, "broker"); completion_tree_cmd_add(commands_post_connect, help_arg_list, "disconnect"); completion_tree_cmd_add(commands_post_connect, help_arg_list, "dynsec"); completion_tree_cmd_add(commands_post_connect, help_arg_list, "exit"); } static void print_help(char **saveptr) { char *command = strtok_r(NULL, " ", saveptr); if(command){ if(!strcasecmp(command, "dynsec")){ ctrl_shell_print_help_command("dynsec"); ctrl_shell_printf("\nStart the dynamic-security control mode.\n"); }else if(!strcasecmp(command, "broker")){ ctrl_shell_print_help_command("broker"); ctrl_shell_printf("\nStart the broker control mode.\n"); }else{ ctrl_shell_print_help_final(command, NULL); } }else{ ctrl_shell_printf("This is the mosquitto_ctrl interactive shell, for controlling aspects of a mosquitto broker.\n"); ctrl_shell_printf("Find help on a command using '%shelp %s'\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Press tab multiple times to find currently available commands.\n\n"); ctrl_shell_printf("Example workflow:\n\n"); ctrl_shell_printf("> auth\n"); ctrl_shell_printf("username: admin\n"); ctrl_shell_printf("password:\n"); ctrl_shell_printf("> connect mqtt://localhost\n"); ctrl_shell_printf("mqtt://localhost:1883> dynsec\n"); ctrl_shell_printf("mqtt://localhost:1883|dynsec> createGroup newgroup\n"); ctrl_shell_printf("OK\n\n"); } } struct module_data { const char *name; void (*mod_init)(struct ctrl_shell__module *mod); }; const struct module_data mod_data[] = { { "broker", ctrl_shell__broker_init }, { "dynsec", ctrl_shell__dynsec_init }, }; static void line_callback(char *line) { if(!line){ ctrl_shell_callback_final(NULL); return; } ctrl_shell_rtrim(line); if(strlen(line) > 0){ add_history(line); }else{ free(line); return; } char *saveptr = NULL; bool found = false; char *command = strtok_r(line, " ", &saveptr); if(!command){ free(line); return; } for(size_t i=0; i%s ", ANSI_URL, data.url_scheme, data.hostname, data.port, ANSI_RESET, ANSI_MODULE, mod_data[i].name, ANSI_INPUT, ANSI_RESET); ctrl_shell__load_module(mod_data[i].mod_init); found = true; break; } } if(!strcasecmp(command, "help")){ found = true; print_help(&saveptr); } if(found == false){ if(!ctrl_shell_callback_final(line)){ ctrl_shell_printf("Unknown command '%s'\n", command); } } free(line); } void ctrl_shell__post_connect_cleanup(void) { completion_tree_free(commands_post_connect); commands_post_connect = NULL; } void ctrl_shell__post_connect_init(void) { command_tree_create(); ctrl_shell_completion_commands_set(commands_post_connect); ctrl_shell_line_callback_set(line_callback); snprintf(prompt, sizeof(prompt), "%s%s://%s:%d%s>%s ", ANSI_URL, data.url_scheme, data.hostname, data.port, ANSI_INPUT, ANSI_RESET); } #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_pre_connect.c000066400000000000000000000140511514232433600305730ustar00rootroot00000000000000/* Copyright (c) 2023 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include "ctrl_shell_internal.h" #ifdef WITH_CTRL_SHELL #define UNUSED(A) (void)(A) static struct completion_tree_root *commands_pre_connect = NULL; static void command_tree_create(void) { struct completion_tree_cmd *cmd; struct completion_tree_arg_list *help_arg_list; if(commands_pre_connect){ return; } commands_pre_connect = calloc(1, sizeof(struct completion_tree_root)); cmd = completion_tree_cmd_add(commands_pre_connect, NULL, "help"); help_arg_list = completion_tree_cmd_add_arg_list(cmd); completion_tree_cmd_add(commands_pre_connect, help_arg_list, "auth"); completion_tree_cmd_add(commands_pre_connect, help_arg_list, "connect"); completion_tree_cmd_add(commands_pre_connect, help_arg_list, "exit"); } void print_help(char **saveptr) { char *command = strtok_r(NULL, " ", saveptr); if(command){ if(!strcasecmp(command, "auth")){ ctrl_shell_print_help_command("auth [username]"); ctrl_shell_printf("\nSet a username and password prior to connecting to a broker.\n"); }else if(!strcasecmp(command, "connect")){ ctrl_shell_print_help_command("connect"); ctrl_shell_print_help_command("connect mqtt://hostname[:port]"); ctrl_shell_print_help_command("connect mqtts://hostname[:port]"); ctrl_shell_print_help_command("connect ws://hostname[:port]"); ctrl_shell_print_help_command("connect wss://hostname[:port]"); ctrl_shell_printf("\nConnect to a broker using the provided transport and port.\n"); ctrl_shell_printf("If no URL is provided, connects to mqtt://localhost:1883\n"); }else if(!strcasecmp(command, "exit")){ ctrl_shell_print_help_command("exit"); ctrl_shell_printf("\nQuit the program\n"); }else if(!strcasecmp(command, "help")){ ctrl_shell_print_help_command("help "); ctrl_shell_printf("\nFind help on a command using '%shelp %s'\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Press tab multiple times to find currently available commands.\n"); }else{ ctrl_shell_printf("Unknown command '%s'\n", command); } }else{ ctrl_shell_printf("This is the mosquitto_ctrl interactive shell, for controlling aspects of a mosquitto broker.\n"); ctrl_shell_printf("Find help on a command using '%shelp %s'\n", ANSI_INPUT, ANSI_RESET); ctrl_shell_printf("Press tab multiple times to find currently available commands.\n\n"); ctrl_shell_printf("Example workflow:\n\n"); ctrl_shell_printf("> auth\n"); ctrl_shell_printf("username: admin\n"); ctrl_shell_printf("password:\n"); ctrl_shell_printf("> connect mqtt://localhost\n"); ctrl_shell_printf("mqtt://localhost:1883> dynsec\n"); ctrl_shell_printf("mqtt://localhost:1883|dynsec> createGroup newgroup\n"); ctrl_shell_printf("OK\n\n"); } } static void line_callback(char *line) { char *saveptr = NULL; char *command; if(!line){ ctrl_shell_callback_final(NULL); return; } ctrl_shell_rtrim(line); if(strlen(line) > 0){ add_history(line); }else{ free(line); return; } command = strtok_r(line, " ", &saveptr); if(!command){ free(line); return; } if(!strcasecmp(command, "auth")){ free(data.username); char *username_i = strtok_r(NULL, " ", &saveptr); if(username_i){ data.username = strdup(username_i); }else{ char promptbuf[50]; snprintf(promptbuf, sizeof(promptbuf), "%susername:%s ", ANSI_INPUT, ANSI_RESET); data.username = readline(promptbuf); } char pwbuf[200]; if(!ctrl_shell_get_password(pwbuf, sizeof(pwbuf))){ ctrl_shell_printf("No password.\n"); free(line); return; } free(data.password); data.password = strdup(pwbuf); }else if(!strcasecmp(command, "connect")){ char *url = strtok_r(NULL, " ", &saveptr); if(url){ if(!strncasecmp(url, "mqtt://", 7)){ url += 7; data.port = 1883; data.url_scheme = "mqtt"; }else if(!strncasecmp(url, "mqtts://", 8)){ #ifdef WITH_TLS url += 8; data.port = 8883; data.url_scheme = "mqtts"; #else ctrl_shell_printf("TLS support not available.\n"); free(line); return; #endif }else if(!strncasecmp(url, "ws://", 5)){ url += 5; data.port = 1883; data.transport = MOSQ_T_WEBSOCKETS; data.url_scheme = "ws"; }else if(!strncasecmp(url, "wss://", 6)){ #ifdef WITH_TLS url += 6; data.port = 8883; data.transport = MOSQ_T_WEBSOCKETS; data.url_scheme = "wss"; #else ctrl_shell_printf("TLS support not available.\n"); free(line); return; #endif } char *hostname_i = strtok_r(url, ":", &saveptr); char *port_i = strtok_r(NULL, ":", &saveptr); if(!hostname_i){ ctrl_shell_printf("connect mqtt[s]://:port\n"); free(line); return; } free(data.hostname); data.hostname = strdup(hostname_i); if(port_i){ data.port = atoi(port_i); } }else{ if(data.hostname == NULL){ data.hostname = strdup("localhost"); } if(data.port == PORT_UNDEFINED){ data.port = 1883; } } ctrl_shell__connect(); }else if(!strcasecmp(command, "help")){ print_help(&saveptr); }else if(!strcasecmp(command, "exit")){ data.run = 0; }else{ ctrl_shell_printf("Unknown command '%s'\n", command); } free(line); } void ctrl_shell__pre_connect_cleanup(void) { completion_tree_free(commands_pre_connect); commands_pre_connect = NULL; } void ctrl_shell__pre_connect_init(void) { command_tree_create(); ctrl_shell_completion_commands_set(commands_pre_connect); ctrl_shell_line_callback_set(line_callback); snprintf(prompt, sizeof(prompt), "%s>%s ", ANSI_INPUT, ANSI_RESET); } #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/ctrl_shell_printf.c000066400000000000000000000016561514232433600276050ustar00rootroot00000000000000/* Copyright (c) 2025 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include "ctrl_shell_internal.h" void ctrl_shell_vprintf(const char *fmt, va_list va) { char buf[2000]; vsnprintf(buf, sizeof(buf), fmt, va); ctrl_shell__output(buf); } void ctrl_shell_printf(const char *fmt, ...) { va_list va; va_start(va, fmt); ctrl_shell_vprintf(fmt, va); va_end(va); } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/dynsec.c000066400000000000000000000661471514232433600253630ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #define CJSON_VERSION_FULL (CJSON_VERSION_MAJOR*1000000+CJSON_VERSION_MINOR*1000+CJSON_VERSION_PATCH) #include #include #include #ifndef WIN32 # include # include # include #endif #include "mosquitto_ctrl.h" #include "mosquitto.h" #include "json_help.h" #include "get_password.h" #define MAX_STRING_LEN 4096 void dynsec__print_usage(void) { printf("\nDynamic Security module\n"); printf("=======================\n"); printf("\nInitialisation\n--------------\n"); printf("Create a new configuration file with an admin user:\n"); printf(" mosquitto_ctrl dynsec init [admin-password]\n"); printf("\nGeneral\n-------\n"); printf("Get ACL default access: getDefaultACLAccess\n"); printf("Set ACL default access: setDefaultACLAccess allow|deny\n"); printf("Get group for anonymous clients: getAnonymousGroup\n"); printf("Set group for anonymous clients: setAnonymousGroup \n"); printf("\nClients\n-------\n"); printf("Create a new client: createClient [-i clientid] [-p password]\n"); printf("Delete a client: deleteClient \n"); printf("Set a client password: setClientPassword [password]\n"); printf("Set a client password on an existing file:\n"); printf(" mosquitto_ctrl -f dynsec setClientPassword \n"); printf("Set a client id: setClientId [clientid]\n"); printf("Add a role to a client: addClientRole [priority]\n"); printf(" Higher priority (larger numerical value) roles are evaluated first.\n"); printf("Remove role from a client: removeClientRole \n"); printf("Get client information: getClient \n"); printf("List all clients: listClients [count [offset]]\n"); printf("Enable client: enableClient \n"); printf("Disable client: disableClient \n"); printf("\nGroups\n------\n"); printf("Create a new group: createGroup \n"); printf("Delete a group: deleteGroup \n"); printf("Add a role to a group: addGroupRole [priority]\n"); printf(" Higher priority (larger numerical value) roles are evaluated first.\n"); printf("Remove role from a group: removeGroupRole \n"); printf("Add client to a group: addGroupClient [priority]\n"); printf(" Priority sets the group priority for the given client only.\n"); printf(" Higher priority (larger numerical value) groups are evaluated first.\n"); printf("Remove client from a group: removeGroupClient \n"); printf("Get group information: getGroup \n"); printf("List all groups: listGroups [count [offset]]\n"); printf("\nRoles\n------\n"); printf("Create a new role: createRole \n"); printf("Delete a role: deleteRole \n"); printf("Add an ACL to a role: addRoleACL [priority]\n"); printf(" Higher priority (larger numerical value) ACLs are evaluated first.\n"); printf("Remove ACL from a role: removeRoleACL \n"); printf("Get role information: getRole \n"); printf("List all roles: listRoles [count [offset]]\n"); printf("\naclspec: allow|deny\n"); printf("acltype: publishClientSend|publishClientReceive\n"); printf(" |subscribeLiteral|subscribePattern\n"); printf(" |unsubscribeLiteral|unsubscribePattern\n"); printf("\nFor more information see:\n"); printf(" https://mosquitto.org/documentation/dynamic-security/\n\n"); } /* ################################################################ * # * # Payload callback * # * ################################################################ */ static void print_list(cJSON *j_response, const char *arrayname, const char *keyname) { cJSON *j_data, *j_array, *j_elem; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_array = cJSON_GetObjectItem(j_data, arrayname); if(j_array == NULL || !cJSON_IsArray(j_array)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } cJSON_ArrayForEach(j_elem, j_array){ if(cJSON_IsObject(j_elem)){ const char *stmp; if(json_get_string(j_elem, keyname, &stmp, false) == MOSQ_ERR_SUCCESS){ printf("%s\n", stmp); } }else if(cJSON_IsString(j_elem) && j_elem->valuestring){ printf("%s\n", j_elem->valuestring); } } } static void print_json_value(cJSON *value, const char *null_value) { if(value){ if(cJSON_IsString(value)){ if(value->valuestring){ printf("%s", value->valuestring); } }else{ char buffer[MAX_STRING_LEN]; cJSON_PrintPreallocated(value, buffer, sizeof(buffer), 0); printf("%s", buffer); } }else if(null_value){ printf("%s", null_value); } } static void print_json_array(cJSON *j_list, int slen, const char *label, const char *element_name, const char *optional_element_name, const char *optional_element_null_value) { cJSON *j_elem; if(j_list && cJSON_IsArray(j_list)){ cJSON_ArrayForEach(j_elem, j_list){ if(cJSON_IsObject(j_elem)){ const char *stmp; if(json_get_string(j_elem, element_name, &stmp, false) != MOSQ_ERR_SUCCESS){ continue; } printf("%-*s %s", (int)slen, label, stmp); if(optional_element_name){ printf(" (%s: ", optional_element_name); print_json_value(cJSON_GetObjectItem(j_elem, optional_element_name), optional_element_null_value); printf(")"); } }else if(cJSON_IsString(j_elem) && j_elem->valuestring){ printf("%-*s %s", (int)slen, label, j_elem->valuestring); } label = ""; printf("\n"); } }else{ printf("%s\n", label); } } static void print_client(cJSON *j_response) { cJSON *j_data, *j_client, *jtmp; const int label_width = (int)strlen("Connections:"); j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_client = cJSON_GetObjectItem(j_data, "client"); if(j_client == NULL || !cJSON_IsObject(j_client)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } const char *username; if(json_get_string(j_client, "username", &username, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("%-*s %s\n", label_width, "Username:", username); const char *clientid; if(json_get_string(j_client, "clientid", &clientid, false) == MOSQ_ERR_SUCCESS){ printf("%-*s %s\n", label_width, "Clientid:", clientid); }else{ printf("Clientid:\n"); } jtmp = cJSON_GetObjectItem(j_client, "disabled"); if(jtmp && cJSON_IsBool(jtmp)){ printf("%-*s %s\n", label_width, "Disabled:", cJSON_IsTrue(jtmp)?"true":"false"); } print_json_array(cJSON_GetObjectItem(j_client, "roles"), label_width, "Roles:", "rolename", "priority", "-1"); print_json_array(cJSON_GetObjectItem(j_client, "groups"), label_width, "Groups:", "groupname", "priority", "-1"); print_json_array(cJSON_GetObjectItem(j_client, "connections"), label_width, "Connections:", "address", NULL, NULL); } static void print_group(cJSON *j_response) { cJSON *j_data, *j_group; int label_width = (int)strlen("Groupname:"); const char *groupname; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_group = cJSON_GetObjectItem(j_data, "group"); if(j_group == NULL || !cJSON_IsObject(j_group)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } if(json_get_string(j_group, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("Groupname: %s\n", groupname); print_json_array(cJSON_GetObjectItem(j_group, "roles"), label_width, "Roles:", "rolename", "priority", "-1"); print_json_array(cJSON_GetObjectItem(j_group, "clients"), label_width, "Clients:", "username", NULL, NULL); } static void print_role(cJSON *j_response) { cJSON *j_data, *j_role, *j_array, *j_elem, *jtmp; bool first; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_role = cJSON_GetObjectItem(j_data, "role"); if(j_role == NULL || !cJSON_IsObject(j_role)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } const char *rolename; if(json_get_string(j_role, "rolename", &rolename, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("Rolename: %s\n", rolename); j_array = cJSON_GetObjectItem(j_role, "acls"); if(j_array && cJSON_IsArray(j_array)){ first = true; cJSON_ArrayForEach(j_elem, j_array){ const char *acltype; if(json_get_string(j_elem, "acltype", &acltype, false) == MOSQ_ERR_SUCCESS){ if(first){ first = false; printf("ACLs: %-20s", acltype); }else{ printf(" %-20s", acltype); } jtmp = cJSON_GetObjectItem(j_elem, "allow"); if(jtmp && cJSON_IsBool(jtmp)){ printf(" : %s", cJSON_IsTrue(jtmp)?"allow":"deny "); } const char *topic; if(json_get_string(j_elem, "topic", &topic, false) == MOSQ_ERR_SUCCESS){ printf(" : %s", topic); } jtmp = cJSON_GetObjectItem(j_elem, "priority"); if(jtmp && cJSON_IsNumber(jtmp)){ printf(" (priority: %d)", (int)jtmp->valuedouble); }else{ printf(" (priority: -1)"); } printf("\n"); } } } } static void print_anonymous_group(cJSON *j_response) { cJSON *j_data, *j_group; const char *groupname; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_group = cJSON_GetObjectItem(j_data, "group"); if(j_group == NULL || !cJSON_IsObject(j_group)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } if(json_get_string(j_group, "groupname", &groupname, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("%s\n", groupname); } static void print_default_acl_access(cJSON *j_response) { cJSON *j_data, *j_acls, *j_acl; j_data = cJSON_GetObjectItem(j_response, "data"); if(j_data == NULL || !cJSON_IsObject(j_data)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } j_acls = cJSON_GetObjectItem(j_data, "acls"); if(j_acls == NULL || !cJSON_IsArray(j_acls)){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } cJSON_ArrayForEach(j_acl, j_acls){ const char *acltype; bool allow; if(json_get_string(j_acl, "acltype", &acltype, false) != MOSQ_ERR_SUCCESS || json_get_bool(j_acl, "allow", &allow, false, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Invalid response from server.\n"); return; } printf("%-20s : %s\n", acltype, allow?"allow":"deny"); } } static void dynsec__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, const void *payload) { cJSON *tree, *j_responses, *j_response; const char *command, *error; UNUSED(ctrl); #if CJSON_VERSION_FULL < 1007013 UNUSED(payloadlen); tree = cJSON_Parse(payload); #else tree = cJSON_ParseWithLength(payload, (size_t)payloadlen); #endif if(tree == NULL){ fprintf(stderr, "Error: Payload not JSON.\n"); return; } j_responses = cJSON_GetObjectItem(tree, "responses"); if(j_responses == NULL || !cJSON_IsArray(j_responses)){ fprintf(stderr, "Error: Payload missing data.\n"); cJSON_Delete(tree); return; } j_response = cJSON_GetArrayItem(j_responses, 0); if(j_response == NULL){ fprintf(stderr, "Error: Payload missing data.\n"); cJSON_Delete(tree); return; } if(json_get_string(j_response, "command", &command, false) != MOSQ_ERR_SUCCESS){ fprintf(stderr, "Error: Payload missing data.\n"); cJSON_Delete(tree); return; } if(json_get_string(j_response, "error", &error, false) == MOSQ_ERR_SUCCESS){ fprintf(stderr, "%s: Error: %s.\n", command, error); }else{ if(!strcasecmp(command, "listClients")){ print_list(j_response, "clients", "username"); }else if(!strcasecmp(command, "listGroups")){ print_list(j_response, "groups", "groupname"); }else if(!strcasecmp(command, "listRoles")){ print_list(j_response, "roles", "rolename"); }else if(!strcasecmp(command, "getClient")){ print_client(j_response); }else if(!strcasecmp(command, "getGroup")){ print_group(j_response); }else if(!strcasecmp(command, "getRole")){ print_role(j_response); }else if(!strcasecmp(command, "getDefaultACLAccess")){ print_default_acl_access(j_response); }else if(!strcasecmp(command, "getAnonymousGroup")){ print_anonymous_group(j_response); }else{ /* fprintf(stderr, "%s: Success\n", command); */ } } cJSON_Delete(tree); } /* ################################################################ * # * # Default ACL access * # * ################################################################ */ static int dynsec__set_default_acl_access(int argc, char *argv[], cJSON *j_command) { char *acltype, *access; bool b_access; cJSON *j_acls, *j_acl; if(argc == 2){ acltype = argv[0]; access = argv[1]; }else{ return MOSQ_ERR_INVAL; } if(strcasecmp(acltype, "publishClientSend") && strcasecmp(acltype, "publishClientReceive") && strcasecmp(acltype, "subscribe") && strcasecmp(acltype, "unsubscribe")){ return MOSQ_ERR_INVAL; } if(!strcasecmp(access, "allow")){ b_access = true; }else if(!strcasecmp(access, "deny")){ b_access = false; }else{ fprintf(stderr, "Error: access must be \"allow\" or \"deny\".\n"); return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "setDefaultACLAccess") == NULL || (j_acls = cJSON_AddArrayToObject(j_command, "acls")) == NULL ){ return MOSQ_ERR_NOMEM; } j_acl = cJSON_CreateObject(); if(j_acl == NULL){ return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_acls, j_acl); if(cJSON_AddStringToObject(j_acl, "acltype", acltype) == NULL || cJSON_AddBoolToObject(j_acl, "allow", b_access) == NULL ){ return MOSQ_ERR_NOMEM; } return MOSQ_ERR_SUCCESS; } static int dynsec__get_default_acl_access(int argc, char *argv[], cJSON *j_command) { UNUSED(argc); UNUSED(argv); if(cJSON_AddStringToObject(j_command, "command", "getDefaultACLAccess") == NULL ){ return MOSQ_ERR_NOMEM; } return MOSQ_ERR_SUCCESS; } /* ################################################################ * # * # Init * # * ################################################################ */ static cJSON *init_add_acl_to_role(cJSON *j_acls, const char *type, const char *topic) { cJSON *j_acl; j_acl = cJSON_CreateObject(); if(j_acl == NULL){ return NULL; } if(cJSON_AddStringToObject(j_acl, "acltype", type) == NULL || cJSON_AddStringToObject(j_acl, "topic", topic) == NULL || cJSON_AddBoolToObject(j_acl, "allow", true) == NULL ){ cJSON_Delete(j_acl); return NULL; } cJSON_AddItemToArray(j_acls, j_acl); return j_acl; } static cJSON *init_add_role(const char *rolename) { cJSON *j_role, *j_acls; j_role = cJSON_CreateObject(); if(j_role == NULL){ return NULL; } if(cJSON_AddStringToObject(j_role, "rolename", rolename) == NULL){ cJSON_Delete(j_role); return NULL; } j_acls = cJSON_CreateArray(); if(j_acls == NULL){ cJSON_Delete(j_role); return NULL; } cJSON_AddItemToObject(j_role, "acls", j_acls); if(init_add_acl_to_role(j_acls, "publishClientSend", "$CONTROL/dynamic-security/#") == NULL || init_add_acl_to_role(j_acls, "publishClientReceive", "$CONTROL/dynamic-security/#") == NULL || init_add_acl_to_role(j_acls, "subscribePattern", "$CONTROL/dynamic-security/#") == NULL || init_add_acl_to_role(j_acls, "publishClientReceive", "$SYS/#") == NULL || init_add_acl_to_role(j_acls, "subscribePattern", "$SYS/#") == NULL || init_add_acl_to_role(j_acls, "publishClientReceive", "#") == NULL || init_add_acl_to_role(j_acls, "subscribePattern", "#") == NULL || init_add_acl_to_role(j_acls, "unsubscribePattern", "#") == NULL ){ cJSON_Delete(j_role); return NULL; } return j_role; } static cJSON *init_add_client(const char *username, const char *password, const char *rolename) { cJSON *j_client, *j_roles, *j_role; struct mosquitto_pw *pw; if(mosquitto_pw_new(&pw, MOSQ_PW_DEFAULT) || mosquitto_pw_hash_encoded(pw, password)){ mosquitto_pw_cleanup(pw); return NULL; } j_client = cJSON_CreateObject(); if(j_client == NULL){ mosquitto_pw_cleanup(pw); return NULL; } if(cJSON_AddStringToObject(j_client, "username", username) == NULL || cJSON_AddStringToObject(j_client, "textName", "Dynsec admin user") == NULL ){ cJSON_Delete(j_client); mosquitto_pw_cleanup(pw); return NULL; } if(cJSON_AddStringToObject(j_client, "encoded_password", mosquitto_pw_get_encoded(pw)) == NULL){ cJSON_Delete(j_client); mosquitto_pw_cleanup(pw); return NULL; } mosquitto_pw_cleanup(pw); j_roles = cJSON_CreateArray(); if(j_roles == NULL){ cJSON_Delete(j_client); return NULL; } cJSON_AddItemToObject(j_client, "roles", j_roles); j_role = cJSON_CreateObject(); if(j_role == NULL){ cJSON_Delete(j_client); return NULL; } cJSON_AddItemToArray(j_roles, j_role); if(cJSON_AddStringToObject(j_role, "rolename", rolename) == NULL){ cJSON_Delete(j_client); return NULL; } return j_client; } static cJSON *init_create(const char *username, const char *password, const char *rolename) { cJSON *tree, *j_clients, *j_client, *j_roles, *j_role; cJSON *j_default_access; tree = cJSON_CreateObject(); if(tree == NULL){ return NULL; } if((j_clients = cJSON_AddArrayToObject(tree, "clients")) == NULL || (j_roles = cJSON_AddArrayToObject(tree, "roles")) == NULL || (j_default_access = cJSON_AddObjectToObject(tree, "defaultACLAccess")) == NULL ){ cJSON_Delete(tree); return NULL; } /* Set default behaviour: * * Client can not publish to the broker by default. * * Broker *CAN* publish to the client by default. * * Client con not subscribe to topics by default. * * Client *CAN* unsubscribe from topics by default. */ if(cJSON_AddBoolToObject(j_default_access, "publishClientSend", false) == NULL || cJSON_AddBoolToObject(j_default_access, "publishClientReceive", true) == NULL || cJSON_AddBoolToObject(j_default_access, "subscribe", false) == NULL || cJSON_AddBoolToObject(j_default_access, "unsubscribe", true) == NULL ){ cJSON_Delete(tree); return NULL; } j_client = init_add_client(username, password, rolename); if(j_client == NULL){ cJSON_Delete(tree); return NULL; } cJSON_AddItemToArray(j_clients, j_client); j_role = init_add_role(rolename); if(j_role == NULL){ cJSON_Delete(tree); return NULL; } cJSON_AddItemToArray(j_roles, j_role); return tree; } /* mosquitto_ctrl dynsec init [role-name] */ static int dynsec_init(int argc, char *argv[]) { char *filename; char *admin_user; char *admin_password; char *json_str; cJSON *tree; FILE *fptr; char prompt[200], verify_prompt[200]; char password[200]; int rc; if(argc < 2){ fprintf(stderr, "dynsec init: Not enough arguments - filename, or admin-user missing.\n"); return MOSQ_ERR_INVAL; } if(argc > 3){ fprintf(stderr, "dynsec init: Too many arguments.\n"); return MOSQ_ERR_INVAL; } filename = argv[0]; admin_user = argv[1]; if(argc == 3){ admin_password = argv[2]; }else{ snprintf(prompt, sizeof(prompt), "New password for %s: ", admin_user); snprintf(verify_prompt, sizeof(verify_prompt), "Reenter password for %s: ", admin_user); rc = get_password(prompt, verify_prompt, false, password, sizeof(password)); if(rc){ mosquitto_lib_cleanup(); return -1; } admin_password = password; } tree = init_create(admin_user, admin_password, "admin"); if(tree == NULL){ fprintf(stderr, "dynsec init: Out of memory.\n"); return MOSQ_ERR_NOMEM; } json_str = cJSON_PrintUnformatted(tree); cJSON_Delete(tree); #ifdef WIN32 fptr = mosquitto_fopen(filename, "wb", true); #else int fd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0640); if(fd < 0){ free(json_str); fprintf(stderr, "dynsec init: Unable to open '%s' for writing (%s).\n", filename, strerror(errno)); return -1; } fptr = fdopen(fd, "wb"); #endif if(fptr){ fprintf(fptr, "%s", json_str); free(json_str); fclose(fptr); }else{ free(json_str); fprintf(stderr, "dynsec init: Unable to open '%s' for writing.\n", filename); return -1; } printf("The client '%s' has been created in the file '%s'.\n", admin_user, filename); printf("This client is configured to allow you to administer the dynamic security plugin only.\n"); printf("It does not have access to publish messages to normal topics.\n"); printf("You should create your application clients to do that, for example:\n"); printf(" mosquitto_ctrl dynsec createClient \n"); printf(" mosquitto_ctrl dynsec createRole \n"); printf(" mosquitto_ctrl dynsec addRoleACL publishClientSend my/topic [priority]\n"); printf(" mosquitto_ctrl dynsec addClientRole [priority]\n"); printf("See https://mosquitto.org/documentation/dynamic-security/ for details of all commands.\n"); return -1; /* Suppress client connection */ } /* ################################################################ * # * # Main * # * ################################################################ */ int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl) { int rc = -1; cJSON *j_tree; cJSON *j_commands, *j_command; if(!strcasecmp(argv[0], "help")){ dynsec__print_usage(); return -1; }else if(!strcasecmp(argv[0], "init")){ return dynsec_init(argc-1, &argv[1]); }else if(ctrl->cfg.data_file && !strcasecmp(argv[0], "setClientPassword")){ return dynsec_client__file_set_password(argc-1, &argv[1], ctrl->cfg.data_file); } /* The remaining commands need a network connection and JSON command. */ ctrl->payload_callback = dynsec__payload_callback; ctrl->request_topic = strdup("$CONTROL/dynamic-security/v1"); ctrl->response_topic = strdup("$CONTROL/dynamic-security/v1/response"); if(ctrl->request_topic == NULL || ctrl->response_topic == NULL){ return MOSQ_ERR_NOMEM; } j_tree = cJSON_CreateObject(); if(j_tree == NULL){ return MOSQ_ERR_NOMEM; } j_commands = cJSON_AddArrayToObject(j_tree, "commands"); if(j_commands == NULL){ cJSON_Delete(j_tree); j_tree = NULL; return MOSQ_ERR_NOMEM; } j_command = cJSON_CreateObject(); if(j_command == NULL){ cJSON_Delete(j_tree); j_tree = NULL; return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(j_commands, j_command); if(!strcasecmp(argv[0], "setDefaultACLAccess")){ rc = dynsec__set_default_acl_access(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "getDefaultACLAccess")){ rc = dynsec__get_default_acl_access(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "createClient")){ rc = dynsec_client__create(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "deleteClient")){ rc = dynsec_client__delete(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "getClient")){ rc = dynsec_client__get(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "listClients")){ rc = dynsec_client__list_all(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "setClientId")){ rc = dynsec_client__set_id(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "setClientPassword")){ rc = dynsec_client__set_password(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "addClientRole")){ rc = dynsec_client__add_remove_role(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "removeClientRole")){ rc = dynsec_client__add_remove_role(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "enableClient")){ rc = dynsec_client__enable_disable(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "disableClient")){ rc = dynsec_client__enable_disable(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "createGroup")){ rc = dynsec_group__create(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "deleteGroup")){ rc = dynsec_group__delete(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "getGroup")){ rc = dynsec_group__get(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "listGroups")){ rc = dynsec_group__list_all(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "addGroupRole")){ rc = dynsec_group__add_remove_role(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "removeGroupRole")){ rc = dynsec_group__add_remove_role(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "addGroupClient")){ rc = dynsec_group__add_remove_client(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "removeGroupClient")){ rc = dynsec_group__add_remove_client(argc-1, &argv[1], j_command, argv[0]); }else if(!strcasecmp(argv[0], "setAnonymousGroup")){ rc = dynsec_group__set_anonymous(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "getAnonymousGroup")){ rc = dynsec_group__get_anonymous(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "createRole")){ rc = dynsec_role__create(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "deleteRole")){ rc = dynsec_role__delete(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "getRole")){ rc = dynsec_role__get(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "listRoles")){ rc = dynsec_role__list_all(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "addRoleACL")){ rc = dynsec_role__add_acl(argc-1, &argv[1], j_command); }else if(!strcasecmp(argv[0], "removeRoleACL")){ rc = dynsec_role__remove_acl(argc-1, &argv[1], j_command); }else{ fprintf(stderr, "Command '%s' not recognised.\n", argv[0]); cJSON_Delete(j_tree); j_tree = NULL; return MOSQ_ERR_UNKNOWN; } if(rc == MOSQ_ERR_SUCCESS){ ctrl->payload = cJSON_PrintUnformatted(j_tree); cJSON_Delete(j_tree); if(ctrl->payload == NULL){ fprintf(stderr, "Error: Out of memory.\n"); return MOSQ_ERR_NOMEM; } }else{ cJSON_Delete(j_tree); } return rc; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/dynsec_client.c000066400000000000000000000244161514232433600267120ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include "mosquitto.h" #include "mosquitto_ctrl.h" #include "get_password.h" #include "json_help.h" #include "dynamic_security.h" int dynsec_client__create(int argc, char *argv[], cJSON *j_command) { char *username = NULL, *password = NULL, *clientid = NULL; char prompt[200], verify_prompt[200]; char password_buf[200]; int rc; int i; bool request_password = true; if(argc == 0){ return MOSQ_ERR_INVAL; } username = argv[0]; for(i=1; i= 2){ username = argv[0]; password = argv[1]; }else{ return MOSQ_ERR_INVAL; } for(i=2; i 0 && cJSON_AddIntToObject(j_command, "count", count) == NULL) || (offset > 0 && cJSON_AddIntToObject(j_command, "offset", offset) == NULL) ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/dynsec_group.c000066400000000000000000000110361514232433600265620ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include "mosquitto.h" #include "mosquitto_ctrl.h" #include "json_help.h" int dynsec_group__create(int argc, char *argv[], cJSON *j_command) { char *groupname = NULL; if(argc == 1){ groupname = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "createGroup") == NULL || cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__delete(int argc, char *argv[], cJSON *j_command) { char *groupname = NULL; if(argc == 1){ groupname = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "deleteGroup") == NULL || cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__get_anonymous(int argc, char *argv[], cJSON *j_command) { UNUSED(argc); UNUSED(argv); if(cJSON_AddStringToObject(j_command, "command", "getAnonymousGroup") == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__set_anonymous(int argc, char *argv[], cJSON *j_command) { char *groupname = NULL; if(argc == 1){ groupname = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "setAnonymousGroup") == NULL || cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__get(int argc, char *argv[], cJSON *j_command) { char *groupname = NULL; if(argc == 1){ groupname = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "getGroup") == NULL || cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command) { char *groupname = NULL, *rolename = NULL; int priority = -1; if(argc == 2){ groupname = argv[0]; rolename = argv[1]; }else if(argc == 3){ groupname = argv[0]; rolename = argv[1]; priority = atoi(argv[2]); }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", command) == NULL || cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL || cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL || (priority != -1 && cJSON_AddIntToObject(j_command, "priority", priority) == NULL) ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__list_all(int argc, char *argv[], cJSON *j_command) { int count = -1, offset = -1; if(argc == 0){ /* All groups */ }else if(argc == 1){ count = atoi(argv[0]); }else if(argc == 2){ count = atoi(argv[0]); offset = atoi(argv[1]); }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "listGroups") == NULL || (count > 0 && cJSON_AddIntToObject(j_command, "count", count) == NULL) || (offset > 0 && cJSON_AddIntToObject(j_command, "offset", offset) == NULL) ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_group__add_remove_client(int argc, char *argv[], cJSON *j_command, const char *command) { char *username, *groupname; int priority = -1; if(argc == 2){ groupname = argv[0]; username = argv[1]; }else if(argc == 3){ groupname = argv[0]; username = argv[1]; priority = atoi(argv[2]); }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", command) == NULL || cJSON_AddStringToObject(j_command, "username", username) == NULL || cJSON_AddStringToObject(j_command, "groupname", groupname) == NULL || (priority != -1 && cJSON_AddIntToObject(j_command, "priority", priority) == NULL) ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/dynsec_role.c000066400000000000000000000113231514232433600263660ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #ifndef WIN32 # include #endif #include "mosquitto.h" #include "mosquitto_ctrl.h" #include "json_help.h" int dynsec_role__create(int argc, char *argv[], cJSON *j_command) { char *rolename = NULL; if(argc == 1){ rolename = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "createRole") == NULL || cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_role__delete(int argc, char *argv[], cJSON *j_command) { char *rolename = NULL; if(argc == 1){ rolename = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "deleteRole") == NULL || cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_role__get(int argc, char *argv[], cJSON *j_command) { char *rolename = NULL; if(argc == 1){ rolename = argv[0]; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "getRole") == NULL || cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_role__list_all(int argc, char *argv[], cJSON *j_command) { int count = -1, offset = -1; if(argc == 0){ /* All roles */ }else if(argc == 1){ count = atoi(argv[0]); }else if(argc == 2){ count = atoi(argv[0]); offset = atoi(argv[1]); }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "listRoles") == NULL || (count > 0 && cJSON_AddIntToObject(j_command, "count", count) == NULL) || (offset > 0 && cJSON_AddIntToObject(j_command, "offset", offset) == NULL) ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_role__add_acl(int argc, char *argv[], cJSON *j_command) { char *rolename, *acltype, *topic, *action; bool allow; int priority = -1; if(argc == 5){ rolename = argv[0]; acltype = argv[1]; topic = argv[2]; action = argv[3]; priority = atoi(argv[4]); }else if(argc == 4){ rolename = argv[0]; acltype = argv[1]; topic = argv[2]; action = argv[3]; }else{ return MOSQ_ERR_INVAL; } if(strcasecmp(acltype, "publishClientSend") && strcasecmp(acltype, "publishClientReceive") && strcasecmp(acltype, "subscribeLiteral") && strcasecmp(acltype, "subscribePattern") && strcasecmp(acltype, "unsubscribeLiteral") && strcasecmp(acltype, "unsubscribePattern")){ return MOSQ_ERR_INVAL; } if(!strcasecmp(action, "allow")){ allow = true; }else if(!strcasecmp(action, "deny")){ allow = false; }else{ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "addRoleACL") == NULL || cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL || cJSON_AddStringToObject(j_command, "acltype", acltype) == NULL || cJSON_AddStringToObject(j_command, "topic", topic) == NULL || cJSON_AddBoolToObject(j_command, "allow", allow) == NULL || (priority != -1 && cJSON_AddIntToObject(j_command, "priority", priority) == NULL) ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } int dynsec_role__remove_acl(int argc, char *argv[], cJSON *j_command) { char *rolename, *acltype, *topic; if(argc == 3){ rolename = argv[0]; acltype = argv[1]; topic = argv[2]; }else{ return MOSQ_ERR_INVAL; } if(strcasecmp(acltype, "publishClientSend") && strcasecmp(acltype, "publishClientReceive") && strcasecmp(acltype, "subscribeLiteral") && strcasecmp(acltype, "subscribePattern") && strcasecmp(acltype, "unsubscribeLiteral") && strcasecmp(acltype, "unsubscribePattern")){ return MOSQ_ERR_INVAL; } if(cJSON_AddStringToObject(j_command, "command", "removeRoleACL") == NULL || cJSON_AddStringToObject(j_command, "rolename", rolename) == NULL || cJSON_AddStringToObject(j_command, "acltype", acltype) == NULL || cJSON_AddStringToObject(j_command, "topic", topic) == NULL ){ return MOSQ_ERR_NOMEM; }else{ return MOSQ_ERR_SUCCESS; } } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/example.c000066400000000000000000000021061514232433600255120ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #ifndef WIN32 # include #endif #include "mosquitto_ctrl.h" void ctrl_help(void) { printf("\nExample module\n"); printf("==============\n"); printf(" mosquitto_ctrl example help\n"); } int ctrl_main(int argc, char *argv[], struct mosq_ctrl *ctrl) { UNUSED(argc); UNUSED(ctrl); if(!strcasecmp(argv[0], "help")){ ctrl_help(); return -1; }else{ return MOSQ_ERR_INVAL; } } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/mosquitto_ctrl.c000066400000000000000000000063001514232433600271470ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #ifndef WIN32 # include #endif #include "lib_load.h" #include "mosquitto.h" #include "mosquitto_ctrl.h" #include "ctrl_shell_internal.h" static void print_version(void) { int major, minor, revision; mosquitto_lib_version(&major, &minor, &revision); printf("mosquitto_ctrl version %s running on libmosquitto %d.%d.%d.\n", VERSION, major, minor, revision); } static void print_usage(void) { printf("mosquitto_ctrl is a tool for administering certain Mosquitto features.\n"); print_version(); printf("\nGeneral usage: mosquitto_ctrl \n"); printf("For module specific help use: mosquitto_ctrl help\n"); printf("\nModules available: broker dynsec\n"); printf("\nFor more information see:\n"); printf(" https://mosquitto.org/man/mosquitto_ctrl-1.html\n\n"); } int main(int argc, char *argv[]) { struct mosq_ctrl ctrl; int rc = MOSQ_ERR_SUCCESS; FUNC_ctrl_main l_ctrl_main = NULL; void *lib = NULL; char lib_name[200]; if(argc == 1){ #ifdef WITH_CTRL_SHELL ctrl_shell__main(NULL); #else print_usage(); #endif return 0; } memset(&ctrl, 0, sizeof(ctrl)); init_config(&ctrl.cfg); /* Shift program name out of args */ argc--; argv++; rc = ctrl_config_parse(&ctrl.cfg, &argc, &argv); if(rc){ client_config_cleanup(&ctrl.cfg); print_usage(); return rc; } #ifdef WITH_CTRL_SHELL if(argc == 0){ ctrl_shell__main(&ctrl.cfg); return 0; }else #endif { if(argc < 2){ print_usage(); client_config_cleanup(&ctrl.cfg); return 1; } } /* In built modules */ if(!strcasecmp(argv[0], "broker")){ l_ctrl_main = broker__main; }else if(!strcasecmp(argv[0], "dynsec")){ l_ctrl_main = dynsec__main; }else{ /* Attempt external module */ snprintf(lib_name, sizeof(lib_name), "mosquitto_ctrl_%s.so", argv[0]); lib = LIB_LOAD(lib_name); if(lib){ l_ctrl_main = (FUNC_ctrl_main)LIB_SYM(lib, "ctrl_main"); } } if(l_ctrl_main == NULL){ fprintf(stderr, "Error: Module '%s' not supported.\n", argv[0]); rc = MOSQ_ERR_NOT_SUPPORTED; } if(l_ctrl_main){ rc = l_ctrl_main(argc-1, &argv[1], &ctrl); if(rc < 0){ /* Usage print */ rc = 0; }else if(rc == MOSQ_ERR_SUCCESS){ if(ctrl.cfg.data_file == NULL){ rc = client_request_response(&ctrl); } }else if(rc == MOSQ_ERR_UNKNOWN){ /* Message printed already */ }else{ fprintf(stderr, "Error: %s.\n", mosquitto_strerror(rc)); } } free(ctrl.payload); free(ctrl.request_topic); free(ctrl.response_topic); client_config_cleanup(&ctrl.cfg); return rc; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/mosquitto_ctrl.h000066400000000000000000000102261514232433600271560ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef MOSQUITTO_CTRL_H #define MOSQUITTO_CTRL_H #include #ifndef __cplusplus #include #endif #include "mosquitto.h" #define PORT_UNDEFINED -1 #define PORT_UNIX 0 #ifdef __cplusplus extern "C" { #endif struct mosq_config { char *id; int protocol_version; int keepalive; char *host; int port; int qos; char *bind_address; bool debug; bool quiet; char *username; char *password; char *options_file; char *cafile; char *capath; char *certfile; char *keyfile; char *ciphers; bool insecure; char *tls_alpn; char *tls_version; char *tls_engine; char *tls_engine_kpass_sha1; bool tls_use_os_certs; char *keyform; char *psk; char *psk_identity; bool verbose; /* sub */ unsigned int timeout; /* sub */ char *socks5_host; int socks5_port; char *socks5_username; char *socks5_password; char *data_file; bool no_colour; }; struct mosq_ctrl { struct mosq_config cfg; char *request_topic; char *response_topic; char *payload; void (*payload_callback)(struct mosq_ctrl *, long, const void *); void *userdata; }; typedef int (*FUNC_ctrl_main)(int argc, char *argv[], struct mosq_ctrl *ctrl); void init_config(struct mosq_config *cfg); int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[]); int client_config_load(struct mosq_config *cfg); void client_config_cleanup(struct mosq_config *cfg); int client_request_response(struct mosq_ctrl *ctrl); int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg); int client_connect(struct mosquitto *mosq, struct mosq_config *cfg); void broker__print_usage(void); int broker__main(int argc, char *argv[], struct mosq_ctrl *ctrl); void dynsec__print_usage(void); int dynsec__main(int argc, char *argv[], struct mosq_ctrl *ctrl); int dynsec_client__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command); int dynsec_client__create(int argc, char *argv[], cJSON *j_command); int dynsec_client__delete(int argc, char *argv[], cJSON *j_command); int dynsec_client__enable_disable(int argc, char *argv[], cJSON *j_command, const char *command); int dynsec_client__file_set_password(int argc, char *argv[], const char *file); int dynsec_client__get(int argc, char *argv[], cJSON *j_command); int dynsec_client__list_all(int argc, char *argv[], cJSON *j_command); int dynsec_client__set_id(int argc, char *argv[], cJSON *j_command); int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command); int dynsec_group__add_remove_client(int argc, char *argv[], cJSON *j_command, const char *command); int dynsec_group__add_remove_role(int argc, char *argv[], cJSON *j_command, const char *command); int dynsec_group__create(int argc, char *argv[], cJSON *j_command); int dynsec_group__delete(int argc, char *argv[], cJSON *j_command); int dynsec_group__get(int argc, char *argv[], cJSON *j_command); int dynsec_group__list_all(int argc, char *argv[], cJSON *j_command); int dynsec_group__set_anonymous(int argc, char *argv[], cJSON *j_command); int dynsec_group__get_anonymous(int argc, char *argv[], cJSON *j_command); int dynsec_role__create(int argc, char *argv[], cJSON *j_command); int dynsec_role__delete(int argc, char *argv[], cJSON *j_command); int dynsec_role__get(int argc, char *argv[], cJSON *j_command); int dynsec_role__list_all(int argc, char *argv[], cJSON *j_command); int dynsec_role__add_acl(int argc, char *argv[], cJSON *j_command); int dynsec_role__remove_acl(int argc, char *argv[], cJSON *j_command); /* Functions to implement as an external module: */ void ctrl_help(void); int ctrl_main(int argc, char *argv[], struct mosq_ctrl *ctrl); #ifdef __cplusplus } #endif #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/options.c000066400000000000000000000555341514232433600255670ustar00rootroot00000000000000/* Copyright (c) 2014-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #include #ifndef WIN32 #include #include #else #include #include #define snprintf sprintf_s #define strncasecmp _strnicmp #endif #include #include "mosquitto_ctrl.h" #include "get_password.h" #ifdef WITH_SOCKS static int mosquitto__parse_socks_url(struct mosq_config *cfg, char *url); #endif static int client_config_line_proc(struct mosq_config *cfg, int *argc, char **argvp[]); void init_config(struct mosq_config *cfg) { cfg->qos = 1; cfg->port = PORT_UNDEFINED; cfg->protocol_version = MQTT_PROTOCOL_V5; } void client_config_cleanup(struct mosq_config *cfg) { free(cfg->id); free(cfg->host); free(cfg->bind_address); free(cfg->username); free(cfg->password); free(cfg->options_file); #ifdef WITH_TLS free(cfg->cafile); free(cfg->capath); free(cfg->certfile); free(cfg->keyfile); free(cfg->ciphers); free(cfg->tls_alpn); free(cfg->tls_version); free(cfg->tls_engine); free(cfg->tls_engine_kpass_sha1); free(cfg->keyform); # ifdef FINAL_WITH_TLS_PSK free(cfg->psk); free(cfg->psk_identity); # endif #endif #ifdef WITH_SOCKS free(cfg->socks5_host); free(cfg->socks5_username); free(cfg->socks5_password); #endif free(cfg->data_file); } int ctrl_config_parse(struct mosq_config *cfg, int *argc, char **argv[]) { int rc; init_config(cfg); /* Deal with real argc/argv */ rc = client_config_line_proc(cfg, argc, argv); if(rc){ return rc; } /* Load options from config file - this must be after `-o` has been processed */ rc = client_config_load(cfg); if(rc){ return rc; } #ifdef WITH_TLS if((cfg->certfile && !cfg->keyfile) || (cfg->keyfile && !cfg->certfile)){ fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is set.\n"); return 1; } if((cfg->keyform && !cfg->keyfile)){ fprintf(stderr, "Error: If keyform is set, keyfile must be also specified.\n"); return 1; } if((cfg->tls_engine_kpass_sha1 && (!cfg->keyform || !cfg->tls_engine))){ fprintf(stderr, "Error: when using tls-engine-kpass-sha1, both tls-engine and keyform must also be provided.\n"); return 1; } #endif #ifdef FINAL_WITH_TLS_PSK if((cfg->cafile || cfg->capath) && cfg->psk){ fprintf(stderr, "Error: Only one of --psk or --cafile/--capath may be used at once.\n"); return 1; } if(cfg->psk && !cfg->psk_identity){ fprintf(stderr, "Error: --psk-identity required if --psk used.\n"); return 1; } #endif if(!cfg->host){ cfg->host = strdup("localhost"); if(!cfg->host){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } } return MOSQ_ERR_SUCCESS; } /* Process a tokenised single line from a file or set of real argc/argv */ static int client_config_line_proc(struct mosq_config *cfg, int *argc, char **argvp[]) { char **argv = *argvp; while((*argc) && argv[0][0] == '-'){ if(!strcmp(argv[0], "-A")){ if((*argc) == 1){ fprintf(stderr, "Error: -A argument given but no address specified.\n\n"); return 1; }else{ cfg->bind_address = strdup(argv[1]); } argv++; (*argc)--; #ifdef WITH_TLS }else if(!strcmp(argv[0], "--cafile")){ if((*argc) == 1){ fprintf(stderr, "Error: --cafile argument given but no file specified.\n\n"); return 1; }else{ cfg->cafile = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--capath")){ if((*argc) == 1){ fprintf(stderr, "Error: --capath argument given but no directory specified.\n\n"); return 1; }else{ cfg->capath = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--cert")){ if((*argc) == 1){ fprintf(stderr, "Error: --cert argument given but no file specified.\n\n"); return 1; }else{ cfg->certfile = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--ciphers")){ if((*argc) == 1){ fprintf(stderr, "Error: --ciphers argument given but no ciphers specified.\n\n"); return 1; }else{ cfg->ciphers = strdup(argv[1]); } argv++; (*argc)--; #endif }else if(!strcmp(argv[0], "-d") || !strcmp(argv[0], "--debug")){ cfg->debug = true; }else if(!strcmp(argv[0], "-f")){ if((*argc) == 1){ fprintf(stderr, "Error: -f argument given but no data file specified.\n\n"); return 1; }else{ cfg->data_file = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--help")){ return 1; }else if(!strcmp(argv[0], "-h") || !strcmp(argv[0], "--host")){ if((*argc) == 1){ fprintf(stderr, "Error: -h argument given but no host specified.\n\n"); return 1; }else{ cfg->host = strdup(argv[1]); } argv++; (*argc)--; #ifdef WITH_TLS }else if(!strcmp(argv[0], "--insecure")){ cfg->insecure = true; #endif }else if(!strcmp(argv[0], "-i") || !strcmp(argv[0], "--id")){ if((*argc) == 1){ fprintf(stderr, "Error: -i argument given but no id specified.\n\n"); return 1; }else{ cfg->id = strdup(argv[1]); } argv++; (*argc)--; #ifdef WITH_TLS }else if(!strcmp(argv[0], "--key")){ if((*argc) == 1){ fprintf(stderr, "Error: --key argument given but no file specified.\n\n"); return 1; }else{ cfg->keyfile = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--keyform")){ if((*argc) == 1){ fprintf(stderr, "Error: --keyform argument given but no keyform specified.\n\n"); return 1; }else{ cfg->keyform = strdup(argv[1]); } argv++; (*argc)--; #endif }else if(!strcmp(argv[0], "-L") || !strcmp(argv[0], "--url")){ if((*argc) == 1){ fprintf(stderr, "Error: -L argument given but no URL specified.\n\n"); return 1; }else{ char *url = argv[1]; char *topic; char *tmp; if(!strncasecmp(url, "mqtt://", 7)){ url += 7; cfg->port = 1883; }else if(!strncasecmp(url, "mqtts://", 8)){ url += 8; cfg->port = 8883; cfg->tls_use_os_certs = true; }else{ fprintf(stderr, "Error: Unsupported URL scheme.\n\n"); return 1; } topic = strchr(url, '/'); if(!topic){ fprintf(stderr, "Error: Invalid URL for -L argument specified - topic missing.\n"); return 1; } *topic++ = 0; tmp = strchr(url, '@'); if(tmp){ *tmp++ = 0; char *colon = strchr(url, ':'); if(colon){ *colon = 0; cfg->password = strdup(colon + 1); } if(strlen(url) == 0){ fprintf(stderr, "Error: Empty username in URL.\n"); return 1; } cfg->username = strdup(url); url = tmp; } cfg->host = url; tmp = strchr(url, ':'); if(tmp){ *tmp++ = 0; if(strlen(tmp) == 0){ cfg->host = NULL; /* Prevent free of non-heap memory later */ fprintf(stderr, "Error: Empty port in URL.\n"); return 1; } cfg->port = atoi(tmp); } /* Now we've removed the port, time to get the host on the heap */ cfg->host = strdup(cfg->host); } argv++; (*argc)--; }else if(!strcmp(argv[0], "-o")){ if((*argc) == 1){ fprintf(stderr, "Error: -o argument given but no options file specified.\n\n"); return 1; }else{ cfg->options_file = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "-p") || !strcmp(argv[0], "--port")){ if((*argc) == 1){ fprintf(stderr, "Error: -p argument given but no port specified.\n\n"); return 1; }else{ cfg->port = atoi(argv[1]); if(cfg->port<0 || cfg->port>65535){ fprintf(stderr, "Error: Invalid port given: %d\n", cfg->port); return 1; } } argv++; (*argc)--; }else if(!strcmp(argv[0], "-P") || !strcmp(argv[0], "--pw")){ if((*argc) == 1){ fprintf(stderr, "Error: -P argument given but no password specified.\n\n"); return 1; }else{ cfg->password = strdup(argv[1]); } argv++; (*argc)--; #ifdef WITH_SOCKS }else if(!strcmp(argv[0], "--proxy")){ if((*argc) == 1){ fprintf(stderr, "Error: --proxy argument given but no proxy url specified.\n\n"); return 1; }else{ if(mosquitto__parse_socks_url(cfg, argv[1])){ return 1; } } argv++; (*argc)--; #endif #ifdef FINAL_WITH_TLS_PSK }else if(!strcmp(argv[0], "--psk")){ if((*argc) == 1){ fprintf(stderr, "Error: --psk argument given but no key specified.\n\n"); return 1; }else{ cfg->psk = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--psk-identity")){ if((*argc) == 1){ fprintf(stderr, "Error: --psk-identity argument given but no identity specified.\n\n"); return 1; }else{ cfg->psk_identity = strdup(argv[1]); } argv++; (*argc)--; #endif }else if(!strcmp(argv[0], "-q") || !strcmp(argv[0], "--qos")){ if((*argc) == 1){ fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n"); return 1; }else{ cfg->qos = atoi(argv[1]); if(cfg->qos<0 || cfg->qos>2){ fprintf(stderr, "Error: Invalid QoS given: %d\n", cfg->qos); return 1; } } argv++; (*argc)--; }else if(!strcmp(argv[0], "--quiet")){ cfg->quiet = true; #ifdef WITH_TLS }else if(!strcmp(argv[0], "--tls-alpn")){ if((*argc) == 1){ fprintf(stderr, "Error: --tls-alpn argument given but no protocol specified.\n\n"); return 1; }else{ cfg->tls_alpn = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--tls-engine")){ if((*argc) == 1){ fprintf(stderr, "Error: --tls-engine argument given but no engine_id specified.\n\n"); return 1; }else{ cfg->tls_engine = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--tls-engine-kpass-sha1")){ if((*argc) == 1){ fprintf(stderr, "Error: --tls-engine-kpass-sha1 argument given but no kpass sha1 specified.\n\n"); return 1; }else{ cfg->tls_engine_kpass_sha1 = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--tls-use-os-certs")){ cfg->tls_use_os_certs = true; }else if(!strcmp(argv[0], "--tls-version")){ if((*argc) == 1){ fprintf(stderr, "Error: --tls-version argument given but no version specified.\n\n"); return 1; }else{ cfg->tls_version = strdup(argv[1]); } argv++; (*argc)--; #endif }else if(!strcmp(argv[0], "-u") || !strcmp(argv[0], "--username")){ if((*argc) == 1){ fprintf(stderr, "Error: -u argument given but no username specified.\n\n"); return 1; }else{ cfg->username = strdup(argv[1]); } argv++; (*argc)--; }else if(!strcmp(argv[0], "--unix")){ if((*argc) == 1){ fprintf(stderr, "Error: --unix argument given but no socket path specified.\n\n"); return 1; }else{ cfg->host = strdup(argv[1]); cfg->port = 0; } argv++; (*argc)--; }else if(!strcmp(argv[0], "-V") || !strcmp(argv[0], "--protocol-version")){ if((*argc) == 1){ fprintf(stderr, "Error: --protocol-version argument given but no version specified.\n\n"); return 1; }else{ if(!strcmp(argv[1], "mqttv31") || !strcmp(argv[1], "31")){ cfg->protocol_version = MQTT_PROTOCOL_V31; }else if(!strcmp(argv[1], "mqttv311") || !strcmp(argv[1], "311")){ cfg->protocol_version = MQTT_PROTOCOL_V311; }else if(!strcmp(argv[1], "mqttv5") || !strcmp(argv[1], "5")){ cfg->protocol_version = MQTT_PROTOCOL_V5; }else{ fprintf(stderr, "Error: Invalid protocol version argument given.\n\n"); return 1; } } argv++; (*argc)--; }else if(!strcmp(argv[0], "-v") || !strcmp(argv[0], "--verbose")){ cfg->verbose = 1; }else if(!strcmp(argv[0], "--version")){ return 1; }else{ goto unknown_option; } argv++; (*argc)--; } *argvp = argv; return MOSQ_ERR_SUCCESS; unknown_option: fprintf(stderr, "Error: Unknown option '%s'.\n", argv[0]); return 1; } static char *get_default_cfg_location(void) { char *loc = NULL; size_t len; #ifndef WIN32 char *env; #else char env[1024]; int rc; #endif #ifndef WIN32 env = getenv("XDG_CONFIG_HOME"); if(env){ len = strlen(env) + strlen("/mosquitto_ctrl") + 1; loc = malloc(len); if(!loc){ fprintf(stderr, "Error: Out of memory.\n"); return NULL; } snprintf(loc, len, "%s/mosquitto_ctrl", env); loc[len-1] = '\0'; }else{ env = getenv("HOME"); if(env){ len = strlen(env) + strlen("/.config/mosquitto_ctrl") + 1; loc = malloc(len); if(!loc){ fprintf(stderr, "Error: Out of memory.\n"); return NULL; } snprintf(loc, len, "%s/.config/mosquitto_ctrl", env); loc[len-1] = '\0'; } } #else rc = GetEnvironmentVariable("USERPROFILE", env, 1024); if(rc > 0 && rc < 1024){ len = strlen(env) + strlen("\\mosquitto_ctrl.conf") + 1; loc = malloc(len); if(!loc){ fprintf(stderr, "Error: Out of memory.\n"); return NULL; } snprintf(loc, len, "%s\\mosquitto_ctrl.conf", env); loc[len-1] = '\0'; } #endif return loc; } int client_config_load(struct mosq_config *cfg) { int rc; FILE *fptr = NULL; char line[1024]; int count; char **local_args, **args; char *default_cfg; if(cfg->options_file){ fptr = fopen(cfg->options_file, "rt"); }else{ default_cfg = get_default_cfg_location(); if(default_cfg){ fptr = fopen(default_cfg, "rt"); free(default_cfg); } } if(fptr){ local_args = malloc(3*sizeof(char *)); if(local_args == NULL){ fprintf(stderr, "Error: Out of memory.\n"); fclose(fptr); return 1; } while(fgets(line, sizeof(line), fptr)){ if(line[0] == '#'){ /* Comments */ continue; } while(line[strlen(line)-1] == 10 || line[strlen(line)-1] == 13){ line[strlen(line)-1] = 0; } local_args[0] = strtok(line, " "); if(local_args[0]){ local_args[1] = strtok(NULL, " "); if(local_args[1]){ count = 2; }else{ count = 1; } args = local_args; rc = client_config_line_proc(cfg, &count, &args); if(rc){ fclose(fptr); free(local_args); return rc; } } } fclose(fptr); free(local_args); } return 0; } int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) { int rc; char prompt[1000]; char password[1000]; mosquitto_int_option(mosq, MOSQ_OPT_PROTOCOL_VERSION, cfg->protocol_version); if(cfg->username && cfg->password == NULL){ /* Ask for password */ snprintf(prompt, sizeof(prompt), "Password for %s: ", cfg->username); rc = get_password(prompt, NULL, false, password, sizeof(password)); if(rc){ fprintf(stderr, "Error getting password.\n"); return 1; } cfg->password = strdup(password); if(cfg->password == NULL){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } } if((cfg->username || cfg->password) && mosquitto_username_pw_set(mosq, cfg->username, cfg->password)){ fprintf(stderr, "Error: Problem setting username and/or password.\n"); return 1; } #ifdef WITH_TLS if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){ fprintf(stderr, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n"); return 1; } if(cfg->cafile || cfg->capath){ rc = mosquitto_tls_set(mosq, cfg->cafile, cfg->capath, cfg->certfile, cfg->keyfile, NULL); if(rc){ if(rc == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Problem setting TLS options: File not found.\n"); }else{ fprintf(stderr, "Error: Problem setting TLS options: %s.\n", mosquitto_strerror(rc)); } return 1; } # ifdef FINAL_WITH_TLS_PSK }else if(cfg->psk){ if(mosquitto_tls_psk_set(mosq, cfg->psk, cfg->psk_identity, NULL)){ fprintf(stderr, "Error: Problem setting TLS-PSK options.\n"); mosquitto_lib_cleanup(); return 1; } # endif }else if(cfg->port == 8883){ mosquitto_int_option(mosq, MOSQ_OPT_TLS_USE_OS_CERTS, 1); } if(cfg->tls_use_os_certs){ mosquitto_int_option(mosq, MOSQ_OPT_TLS_USE_OS_CERTS, 1); } mosquitto_tls_insecure_set(mosq, cfg->insecure); if(cfg->tls_engine && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE, cfg->tls_engine)){ fprintf(stderr, "Error: Problem setting TLS engine, is %s a valid engine?\n", cfg->tls_engine); return 1; } if(cfg->tls_engine_kpass_sha1 && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE_KPASS_SHA1, cfg->tls_engine_kpass_sha1)){ fprintf(stderr, "Error: Problem setting TLS engine key pass sha, is it a 40 character hex string?\n"); return 1; } if(cfg->tls_alpn && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ALPN, cfg->tls_alpn)){ fprintf(stderr, "Error: Problem setting TLS ALPN protocol.\n"); return 1; } if((cfg->tls_version || cfg->ciphers) && mosquitto_tls_opts_set(mosq, 1, cfg->tls_version, cfg->ciphers)){ fprintf(stderr, "Error: Problem setting TLS options, check the options are valid.\n"); return 1; } #endif #ifdef WITH_SOCKS if(cfg->socks5_host){ rc = mosquitto_socks5_set(mosq, cfg->socks5_host, cfg->socks5_port, cfg->socks5_username, cfg->socks5_password); if(rc){ return rc; } } #endif return MOSQ_ERR_SUCCESS; } int client_connect(struct mosquitto *mosq, struct mosq_config *cfg) { #ifndef WIN32 char *err; #else char err[1024]; #endif int rc; int port; if(cfg->port == PORT_UNDEFINED){ #ifdef WITH_TLS if(cfg->cafile || cfg->capath # ifdef FINAL_WITH_TLS_PSK || cfg->psk # endif ){ port = 8883; }else #endif { port = 1883; } }else{ port = cfg->port; } rc = mosquitto_connect_bind_v5(mosq, cfg->host, port, 60, cfg->bind_address, NULL); if(rc>0){ if(rc == MOSQ_ERR_ERRNO){ #ifndef WIN32 err = strerror(errno); #else FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL); #endif fprintf(stderr, "Error: %s.\n", err); }else{ fprintf(stderr, "Unable to connect (%s).\n", mosquitto_strerror(rc)); } return rc; } return MOSQ_ERR_SUCCESS; } #ifdef WITH_SOCKS /* Convert %25 -> %, %3a, %3A -> :, %40 -> @ */ static int mosquitto__urldecode(char *str) { size_t i, j; size_t len; if(!str){ return 0; } if(!strchr(str, '%')){ return 0; } len = strlen(str); for(i=0; i= len){ return 1; } if(str[i+1] == '2' && str[i+2] == '5'){ str[i] = '%'; len -= 2; for(j=i+1; j start){ len = i-start; if(host){ /* Have already seen a @ , so this must be of form * socks5h://username[:password]@host:port */ port = malloc(len + 1); if(!port){ fprintf(stderr, "Error: Out of memory.\n"); goto cleanup; } memcpy(port, &(str[start]), len); port[len] = '\0'; }else if(username_or_host){ /* Haven't seen a @ before, so must be of form * socks5h://host:port */ host = username_or_host; username_or_host = NULL; port = malloc(len + 1); if(!port){ fprintf(stderr, "Error: Out of memory.\n"); goto cleanup; } memcpy(port, &(str[start]), len); port[len] = '\0'; }else{ host = malloc(len + 1); if(!host){ fprintf(stderr, "Error: Out of memory.\n"); goto cleanup; } memcpy(host, &(str[start]), len); host[len] = '\0'; } } if(!host){ fprintf(stderr, "Error: Invalid proxy.\n"); goto cleanup; } if(mosquitto__urldecode(username)){ fprintf(stderr, "Error: Invalid URL encoding in username.\n"); goto cleanup; } if(mosquitto__urldecode(password)){ fprintf(stderr, "Error: Invalid URL encoding in password.\n"); goto cleanup; } if(port){ port_int = atoi(port); if(port_int < 1 || port_int > 65535){ fprintf(stderr, "Error: Invalid proxy port %d\n", port_int); goto cleanup; } free(port); }else{ port_int = 1080; } cfg->socks5_username = username; cfg->socks5_password = password; cfg->socks5_host = host; cfg->socks5_port = port_int; return 0; cleanup: free(username_or_host); free(username); free(password); free(host); free(port); return 1; } #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/test/000077500000000000000000000000001514232433600246735ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_ctrl/test/Makefile000066400000000000000000000060771514232433600263450ustar00rootroot00000000000000R=../../.. include ${R}/config.mk .PHONY: all check test-compile test test-broker test-lib clean coverage LOCAL_CPPFLAGS+= \ -DWITH_THREADING \ -DWITH_TLS \ -I../ \ -I${R}/include \ -I${R} \ -I${R}/lib \ -I${R}/lib/mock \ -I${R}/test/mock LOCAL_CXXFLAGS+=-std=c++20 -Wall -ggdb -D TEST_SOURCE_DIR='"$(realpath .)"' LOCAL_LDFLAGS+= LOCAL_LIBADD+=-lcjson -lgmock -lgtest_main -lgtest ${R}/libcommon/libmosquitto_common.a OBJS = \ ../ctrl_shell_broker.o \ ../ctrl_shell.o \ ../ctrl_shell_client.o \ ../ctrl_shell_completion_tree.o \ ../ctrl_shell_dynsec.o \ ../ctrl_shell_ha.o \ ../ctrl_shell_inspect.o \ ../ctrl_shell_license.o \ ../ctrl_shell_post_connect.o \ ../ctrl_shell_pre_connect.o \ ../ctrl_shell_printf.o \ ../ctrl_shell_topictree.o LIBMOSQ_MOCKS = \ ${R}/lib/mock/libmosquitto_mock.o \ ${R}/lib/mock/actions_publish_mock.o \ ${R}/lib/mock/actions_subscribe_mock.o \ ${R}/lib/mock/callbacks_mock.o \ ${R}/lib/mock/connect_mock.o \ ${R}/lib/mock/loop_mock.o \ ${R}/lib/mock/options_mock.o \ ${R}/lib/mock/thread_mosq_mock.o LIB_OBJS = \ ${OBJS} \ json_help.o \ ctrl_shell_mock.o \ ${R}/test/mock/editline_mock.o \ ${R}/test/mock/pthread_mock.o \ ${LIBMOSQ_MOCKS} TEST_OBJS = \ ctrl_shell_broker_test.o \ ctrl_shell_dynsec_test.o \ ctrl_shell_help_test.o \ ctrl_shell_pre_connect_test.o ALL_TESTS = \ ctrl_shell_broker_test \ ctrl_shell_dynsec_test \ ctrl_shell_help_test \ ctrl_shell_pre_connect_test all : test-compile check : test # DEPS ${LIBMOSQ_MOCKS}: $(MAKE) -C ${R}/lib ${OBJS} : $(MAKE) -C ../ json_help.o : ${R}/common/json_help.c ${R}/common/json_help.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ # MOCKS ctrl_shell_mock.o : ctrl_shell_mock.cpp ctrl_shell_mock.hpp $(CROSS_COMPILE)$(CXX) $(LOCAL_CPPFLAGS) $(LOCAL_CXXFLAGS) -c $< -o $@ ${R}/test/mock/editline_mock.o : ${R}/test/mock/editline_mock.cpp ${R}/test/mock/editline_mock.hpp $(MAKE) -C ${R}/test/mock test-compile ${R}/test/mock/pthread_mock.o : ${R}/test/mock/pthread_mock.cpp ${R}/test/mock/pthread_mock.hpp $(MAKE) -C ${R}/test/mock test-compile # TESTS ${TEST_OBJS} : %.o: %.cpp $(CROSS_COMPILE)$(CXX) $(LOCAL_CPPFLAGS) $(LOCAL_CXXFLAGS) -c $< -o $@ ctrl_shell_broker_test : ctrl_shell_broker_test.o ${LIB_OBJS} $(CROSS_COMPILE)$(CXX) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $^ $(LOCAL_LIBADD) ctrl_shell_dynsec_test : ctrl_shell_dynsec_test.o ${LIB_OBJS} $(CROSS_COMPILE)$(CXX) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $^ $(LOCAL_LIBADD) ctrl_shell_help_test : ctrl_shell_help_test.o ${LIB_OBJS} $(CROSS_COMPILE)$(CXX) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $^ $(LOCAL_LIBADD) ctrl_shell_pre_connect_test : ctrl_shell_pre_connect_test.o ${LIB_OBJS} $(CROSS_COMPILE)$(CXX) $(LOCAL_CPPFLAGS) $(LOCAL_LDFLAGS) -o $@ $^ $(LOCAL_LIBADD) test-compile : $(ALL_TESTS) test : test-compile ./ctrl_shell_help_test ./ctrl_shell_dynsec_test clean : -rm -rf $(ALL_TESTS) -rm -rf *.o *.gcda *.gcno coverage.info out/ coverage : lcov --capture --directory . --output-file coverage.info genhtml coverage.info --output-directory out install: uninstall: eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_passwd/000077500000000000000000000000001514232433600242515ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_passwd/CMakeLists.txt000066400000000000000000000010221514232433600270040ustar00rootroot00000000000000if(WITH_TLS) add_executable(mosquitto_passwd mosquitto_passwd.c get_password.c get_password.h ) target_include_directories(mosquitto_passwd PRIVATE "${mosquitto_SOURCE_DIR}" "${mosquitto_SOURCE_DIR}/common" "${mosquitto_SOURCE_DIR}/include" "${mosquitto_SOURCE_DIR}/lib" "${mosquitto_SOURCE_DIR}/src" ) target_link_libraries(mosquitto_passwd PRIVATE common-options libmosquitto_common OpenSSL::SSL ) install(TARGETS mosquitto_passwd RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) endif() eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_passwd/Makefile000066400000000000000000000017251514232433600257160ustar00rootroot00000000000000R=../.. include ${R}/config.mk LOCAL_CFLAGS+= LOCAL_CPPFLAGS+=-I${R}/lib LOCAL_LDFLAGS+= LOCAL_LDADD+=-lcrypto ${LIBMOSQ_COMMON} .PHONY: all install uninstall clean reallyclean OBJS= \ mosquitto_passwd.o \ get_password.o \ OBJS_EXTERNAL= ifeq ($(WITH_TLS),yes) ifeq ($(WITH_FUZZING),yes) all : mosquitto_passwd.a else all : mosquitto_passwd endif else all: endif mosquitto_passwd : ${OBJS} ${OBJS_EXTERNAL} ${CROSS_COMPILE}${CC} ${LOCAL_LDFLAGS} $^ -o $@ $(LOCAL_LDADD) mosquitto_passwd.a : ${OBJS} ${OBJS_EXTERNAL} ${CROSS_COMPILE}$(AR) cr $@ $^ ${OBJS} : %.o: %.c ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ install : all ifeq ($(WITH_TLS),yes) $(INSTALL) -d "${DESTDIR}$(prefix)/bin" $(INSTALL) ${STRIP_OPTS} mosquitto_passwd "${DESTDIR}${prefix}/bin/mosquitto_passwd" endif uninstall : -rm -f "${DESTDIR}${prefix}/bin/mosquitto_passwd" clean : -rm -f *.o *.a mosquitto_passwd *.gcda *.gcno reallyclean : clean -rm -rf *.orig *.db eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_passwd/get_password.c000066400000000000000000000055471514232433600271310ustar00rootroot00000000000000/* Copyright (c) 2012-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #ifdef WIN32 # include # include # define snprintf sprintf_s # include # include #else # include # include # include #endif #include "get_password.h" #define MAX_BUFFER_LEN 65500 void get_password__reset_term(void) { #ifndef WIN32 struct termios ts; tcgetattr(0, &ts); ts.c_lflag |= ECHO | ICANON; tcsetattr(0, TCSANOW, &ts); #endif } static int gets_quiet(char *s, int len) { #ifdef WIN32 HANDLE h; DWORD con_orig, con_quiet = 0; DWORD read_len = 0; memset(s, 0, len); h = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(h, &con_orig); con_quiet = con_orig; con_quiet &= ~ENABLE_ECHO_INPUT; con_quiet |= ENABLE_LINE_INPUT; SetConsoleMode(h, con_quiet); if(!ReadConsole(h, s, len, &read_len, NULL)){ SetConsoleMode(h, con_orig); return 1; } while(s[strlen(s)-1] == 10 || s[strlen(s)-1] == 13){ s[strlen(s)-1] = 0; } if(strlen(s) == 0){ return 1; } SetConsoleMode(h, con_orig); return 0; #else struct termios ts_quiet; char *rs; memset(s, 0, (size_t)len); tcgetattr(0, &ts_quiet); ts_quiet.c_lflag &= (unsigned int)(~(ECHO | ICANON)); tcsetattr(0, TCSANOW, &ts_quiet); rs = fgets(s, len, stdin); get_password__reset_term(); if(!rs){ return 1; }else{ while(strlen(s) > 0 && (s[strlen(s)-1] == 10 || s[strlen(s)-1] == 13)){ s[strlen(s)-1] = 0; } if(strlen(s) == 0){ return 1; } } return 0; #endif } int get_password(const char *prompt, const char *verify_prompt, bool quiet, char *password, size_t len) { char pw1[MAX_BUFFER_LEN], pw2[MAX_BUFFER_LEN]; size_t minLen; minLen = len < MAX_BUFFER_LEN ? len : MAX_BUFFER_LEN; printf("%s", prompt); fflush(stdout); if(gets_quiet(pw1, (int)minLen)){ if(!quiet){ fprintf(stderr, "Error: Empty password.\n"); } return 1; } printf("\n"); if(verify_prompt){ printf("%s", verify_prompt); fflush(stdout); if(gets_quiet(pw2, (int)minLen)){ if(!quiet){ fprintf(stderr, "Error: Empty password.\n"); } return 1; } printf("\n"); if(strcmp(pw1, pw2)){ if(!quiet){ fprintf(stderr, "Error: Passwords do not match.\n"); } return 2; } } strncpy(password, pw1, minLen); return 0; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_passwd/get_password.h000066400000000000000000000014431514232433600271250ustar00rootroot00000000000000#ifndef GET_PASSWORD_H #define GET_PASSWORD_H /* Copyright (c) 2012-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include void get_password__reset_term(void); int get_password(const char *prompt, const char *verify_prompt, bool quiet, char *password, size_t len); #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_passwd/mosquitto_passwd.c000066400000000000000000000420041514232433600300420ustar00rootroot00000000000000/* Copyright (c) 2012-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #include #include #include #include "mosquitto.h" #include "get_password.h" #ifdef WIN32 # include # include # ifndef __cplusplus # if defined(_MSC_VER) && _MSC_VER < 1900 # define bool char # define true 1 # define false 0 # else # include # endif # endif # define snprintf sprintf_s # include # include #else # include # include # include # include #endif #define MAX_BUFFER_LEN 65500 struct cb_helper { const char *line; const char *username; const char *password; int iterations; bool found; }; static enum mosquitto_pwhash_type hashtype = MOSQ_PW_SHA512_PBKDF2; #ifdef WIN32 static FILE *mpw_tmpfile(void) { return tmpfile(); } #else static char unsigned alphanum[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static unsigned char tmpfile_path[36]; static FILE *mpw_tmpfile(void) { int fd; size_t i; if(RAND_bytes(tmpfile_path, sizeof(tmpfile_path)) != 1){ return NULL; } strcpy((char *)tmpfile_path, "/tmp/"); for(i=strlen((char *)tmpfile_path); i 0){ mosquitto_pw_set_param(pw, MOSQ_PW_PARAM_ITERATIONS, iterations); } rc = mosquitto_pw_hash_encoded(pw, password); if(rc){ mosquitto_pw_cleanup(pw); fprintf(stderr, "Error: Unable to hash password.\n"); return rc; } fprintf(fptr, "%s:%s\n", username, mosquitto_pw_get_encoded(pw)); mosquitto_pw_cleanup(pw); return rc; } static int pwfile_iterate(FILE *fptr, FILE *ftmp, int (*cb)(FILE *, FILE *, const char *, const char *, const char *, struct cb_helper *), struct cb_helper *helper) { char *buf; int buflen = 1024; char *lbuf; int lbuflen; int rc = 1; int line = 0; char *username, *password; buf = malloc((size_t)buflen); if(buf == NULL){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } lbuflen = buflen; lbuf = malloc((size_t)lbuflen); if(lbuf == NULL){ fprintf(stderr, "Error: Out of memory.\n"); free(buf); return 1; } while(!feof(fptr) && mosquitto_fgets(&buf, &buflen, fptr)){ if(lbuflen != buflen){ free(lbuf); lbuflen = buflen; lbuf = malloc((size_t)lbuflen); if(lbuf == NULL){ fprintf(stderr, "Error: Out of memory.\n"); free(buf); return 1; } } memcpy(lbuf, buf, (size_t)buflen); line++; username = strtok(buf, ":"); password = strtok(NULL, ":"); if(username && password){ username = mosquitto_trimblanks(username); password = mosquitto_trimblanks(password); } if(username == NULL || strlen(username) == 0 || password == NULL || strlen(password) == 0){ fprintf(stderr, "Error: Corrupt password file at line %d.\n", line); free(lbuf); free(buf); return 1; } rc = cb(fptr, ftmp, username, password, lbuf, helper); if(rc){ break; } } free(lbuf); free(buf); return rc; } /* ====================================================================== * Delete a user from the password file * ====================================================================== */ static int delete_pwuser_cb(FILE *fptr, FILE *ftmp, const char *username, const char *password, const char *line, struct cb_helper *helper) { UNUSED(fptr); UNUSED(password); UNUSED(line); if(strcmp(username, helper->username)){ /* If this isn't the username to delete, write it to the new file */ fprintf(ftmp, "%s", line); }else{ /* Don't write the matching username to the file. */ helper->found = true; } return 0; } static int delete_pwuser(FILE *fptr, FILE *ftmp, const char *username) { struct cb_helper helper; int rc; memset(&helper, 0, sizeof(helper)); helper.username = username; rc = pwfile_iterate(fptr, ftmp, delete_pwuser_cb, &helper); if(helper.found == false){ fprintf(stderr, "Warning: User %s not found in password file.\n", username); return 1; } return rc; } /* ====================================================================== * Update a plain text password file to use hashes * ====================================================================== */ static int update_file_cb(FILE *fptr, FILE *ftmp, const char *username, const char *password, const char *line, struct cb_helper *helper) { UNUSED(fptr); UNUSED(line); if(helper){ return output_new_password(ftmp, username, password, helper->iterations); }else{ return output_new_password(ftmp, username, password, -1); } } static int update_file(FILE *fptr, FILE *ftmp) { return pwfile_iterate(fptr, ftmp, update_file_cb, NULL); } /* ====================================================================== * Update an existing user password / create a new password * ====================================================================== */ static int update_pwuser_cb(FILE *fptr, FILE *ftmp, const char *username, const char *password, const char *line, struct cb_helper *helper) { int rc = 0; UNUSED(fptr); UNUSED(password); if(helper->found || strcmp(username, helper->username)){ /* If this isn't the matching user, then writing out the exiting line */ fprintf(ftmp, "%s", line); }else{ /* Write out a new line for our matching username */ helper->found = true; rc = output_new_password(ftmp, username, helper->password, helper->iterations); } return rc; } static int update_pwuser(FILE *fptr, FILE *ftmp, const char *username, const char *password, int iterations) { struct cb_helper helper; int rc; memset(&helper, 0, sizeof(helper)); helper.username = username; helper.password = password; helper.iterations = iterations; rc = pwfile_iterate(fptr, ftmp, update_pwuser_cb, &helper); if(helper.found){ printf("Updating password for user %s\n", username); return rc; }else{ printf("Adding password for user %s\n", username); return output_new_password(ftmp, username, password, iterations); } } static int copy_contents(FILE *src, FILE *dest) { char buf[MAX_BUFFER_LEN]; size_t len; rewind(src); rewind(dest); #ifdef WIN32 _chsize(fileno(dest), 0); #else if(ftruncate(fileno(dest), 0)){ return 1; } #endif while(!feof(src)){ len = fread(buf, 1, MAX_BUFFER_LEN, src); if(len > 0){ if(fwrite(buf, 1, len, dest) != len){ return 1; } }else{ return !feof(src); } } return 0; } static int create_backup(char *backup_file, FILE *fptr) { FILE *fbackup; #ifdef WIN32 fbackup = mosquitto_fopen(backup_file, "wt", true); #else int fd; umask(077); fd = mkstemp(backup_file); if(fd < 0){ fprintf(stderr, "Error creating backup password file \"%s\", not continuing.\n", backup_file); return 1; } fbackup = fdopen(fd, "wt"); #endif if(!fbackup){ fprintf(stderr, "Error creating backup password file \"%s\", not continuing.\n", backup_file); return 1; } if(copy_contents(fptr, fbackup)){ fprintf(stderr, "Error copying data to backup password file \"%s\", not continuing.\n", backup_file); fclose(fbackup); return 1; } fclose(fbackup); rewind(fptr); return 0; } static void handle_sigint(int signal) { get_password__reset_term(); UNUSED(signal); #ifndef WITH_FUZZING exit(0); #endif } static bool is_username_valid(const char *username) { size_t i; size_t slen; if(username){ slen = strlen(username); if(slen > 65535){ fprintf(stderr, "Error: Username must be less than 65536 characters long.\n"); return false; } for(i=0; i 0.\n"); return 1; } }else if(!strcmp(argv[idx], "-U")){ do_update_file = true; }else{ break; } } if(create_new && delete_user){ fprintf(stderr, "Error: -c and -D cannot be used together.\n"); return 1; } if(create_new && do_update_file){ fprintf(stderr, "Error: -c and -U cannot be used together.\n"); return 1; } if(delete_user && do_update_file){ fprintf(stderr, "Error: -D and -U cannot be used together.\n"); return 1; } if(delete_user && batch_mode){ fprintf(stderr, "Error: -b and -D cannot be used together.\n"); return 1; } if(create_new){ if(batch_mode){ if(idx+2 >= argc){ fprintf(stderr, "Error: -c argument given but password file, username, or password missing.\n"); return 1; }else{ if(!strcmp(argv[idx], "-")){ use_stdout = true; }else{ password_file_tmp = argv[idx]; } username = argv[idx+1]; password_cmd = argv[idx+2]; } }else{ if(idx+1 >= argc){ fprintf(stderr, "Error: -c argument given but password file or username missing.\n"); return 1; }else{ if(!strcmp(argv[idx], "-")){ use_stdout = true; }else{ password_file_tmp = argv[idx]; } username = argv[idx+1]; } } }else if(delete_user){ if(idx+1 >= argc){ fprintf(stderr, "Error: -D argument given but password file or username missing.\n"); return 1; }else{ password_file_tmp = argv[idx]; username = argv[idx+1]; } }else if(do_update_file){ if(idx+1 != argc){ fprintf(stderr, "Error: -U argument given but password file missing.\n"); return 1; }else{ password_file_tmp = argv[idx]; } }else if(batch_mode == true && idx+3 == argc){ password_file_tmp = argv[idx]; username = argv[idx+1]; password_cmd = argv[idx+2]; }else if(batch_mode == false && idx+2 == argc){ password_file_tmp = argv[idx]; username = argv[idx+1]; }else{ print_usage(); return 1; } if(!is_username_valid(username)){ return 1; } if(password_cmd && strlen(password_cmd) > 65535){ fprintf(stderr, "Error: Password must be less than 65536 characters long.\n"); return 1; } if(!use_stdout){ #ifdef WIN32 password_file = _fullpath(NULL, password_file_tmp, 0); if(!password_file){ fprintf(stderr, "Error getting full path for password file.\n"); return 1; } #else password_file = realpath(password_file_tmp, NULL); if(!password_file){ if(errno == ENOENT){ password_file = strdup(password_file_tmp); if(!password_file){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } }else{ fprintf(stderr, "Error reading password file: %s\n", strerror(errno)); return 1; } } #endif } if(create_new){ if(batch_mode == false){ rc = get_password("Password: ", "Reenter password: ", false, password, MAX_BUFFER_LEN); if(rc){ free(password_file); return rc; } password_cmd = password; } if(use_stdout){ fptr = stdout; }else{ fptr = mosquitto_fopen(password_file, "wt", true); if(!fptr){ fprintf(stderr, "Error: Unable to open file %s for writing. %s.\n", password_file, strerror(errno)); free(password_file); return 1; } free(password_file); } if(!use_stdout){ printf("Adding password for user %s\n", username); } rc = output_new_password(fptr, username, password_cmd, iterations); fclose(fptr); return rc; }else{ fptr = mosquitto_fopen(password_file, "r+t", true); if(!fptr){ fprintf(stderr, "Error: Unable to open password file %s. %s.\n", password_file, strerror(errno)); free(password_file); return 1; } size_t len = strlen(password_file) + strlen(".backup.XXXXXX") + 1; backup_file = malloc(len); if(!backup_file){ fprintf(stderr, "Error: Out of memory.\n"); free(password_file); return 1; } snprintf(backup_file, len, "%s.backup.XXXXXX", password_file); free(password_file); password_file = NULL; if(create_backup(backup_file, fptr)){ fclose(fptr); free(backup_file); return 1; } ftmp = mpw_tmpfile(); if(!ftmp){ fprintf(stderr, "Error: Unable to open temporary file. %s.\n", strerror(errno)); fclose(fptr); free(backup_file); return 1; } if(delete_user){ rc = delete_pwuser(fptr, ftmp, username); }else if(do_update_file){ rc = update_file(fptr, ftmp); }else{ if(batch_mode){ /* Update password for individual user */ rc = update_pwuser(fptr, ftmp, username, password_cmd, iterations); }else{ rc = get_password("Password: ", "Reenter password: ", false, password, MAX_BUFFER_LEN); if(rc == 0){ /* Update password for individual user */ rc = update_pwuser(fptr, ftmp, username, password, iterations); } } } if(rc){ fclose(fptr); fclose(ftmp); unlink(backup_file); free(backup_file); return rc; } if(copy_contents(ftmp, fptr)){ fprintf(stderr, "Error occurred updating password file.\n"); fprintf(stderr, "Password file may be corrupt, check the backup file: %s.\n", backup_file); rc = 1; } fclose(fptr); fclose(ftmp); if(rc == 0){ /* Everything was ok so backup no longer needed. May contain old * passwords so shouldn't be kept around. */ unlink(backup_file); } free(backup_file); } return 0; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/000077500000000000000000000000001514232433600242255ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/CMakeLists.txt000066400000000000000000000006501514232433600267660ustar00rootroot00000000000000set(SRC mosquitto_signal.c ) if(WIN32) set(SRC ${SRC} signal_windows.c) else() set(SRC ${SRC} signal_unix.c) endif() add_executable(mosquitto_signal ${SRC}) target_include_directories(mosquitto_signal PRIVATE "${mosquitto_SOURCE_DIR}" "${mosquitto_SOURCE_DIR}/include" "${mosquitto_SOURCE_DIR}/lib" "${mosquitto_SOURCE_DIR}/src" ) install(TARGETS mosquitto_signal RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/Makefile000066400000000000000000000013641514232433600256710ustar00rootroot00000000000000R=../.. include ${R}/config.mk LOCAL_CFLAGS+= LOCAL_CPPFLAGS+=-I${R}/lib LOCAL_LDFLAGS+= LOCAL_LDADD+= .PHONY: all install uninstall clean reallyclean OBJS= \ mosquitto_signal.o \ signal_unix.o \ all : mosquitto_signal mosquitto_signal : ${OBJS} ${OBJS_EXTERNAL} ${CROSS_COMPILE}${CC} ${LOCAL_LDFLAGS} $^ -o $@ $(LOCAL_LDADD) ${OBJS} : %.o: %.c ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ install : all ifeq ($(WITH_TLS),yes) $(INSTALL) -d "${DESTDIR}$(prefix)/bin" $(INSTALL) ${STRIP_OPTS} mosquitto_signal "${DESTDIR}${prefix}/bin/mosquitto_signal" endif uninstall : -rm -f "${DESTDIR}${prefix}/bin/mosquitto_signal" clean : -rm -f *.o *.a mosquitto_signal *.gcda *.gcno reallyclean : clean -rm -rf *.orig *.db eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/mosquitto_signal.c000066400000000000000000000060561514232433600300010ustar00rootroot00000000000000/* Copyright (c) 2024 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include "mosquitto_signal.h" static void print_usage(void) { printf("mosquitto_signal is a tool for sending control signals to mosquitto.\n"); printf(" it is primarily useful on Windows.\n"); printf(" on other systems the `kill` tool can be used.\n\n"); printf("Usage: mosquitto_signal {-a | -p } \n"); printf(" mosquitto_signal --help\n\n"); #ifdef WIN32 printf(" -a : signal all processes that match the name 'mosquitto.exe'.\n"); #else printf(" -a : signal all processes that match the name 'mosquitto'.\n"); #endif printf(" -p : specify a process ID to signal\n\n"); printf(" may be one of:\n"); printf(" config-reload - reload the configuration file, if in use.\n"); printf(" log-rotate - if using `file` logging ask the broker to close and reopen the\n"); printf(" log file.\n"); printf(" shutdown - quit the broker.\n"); printf(" tree-print - (debug) print out subscription and retain tree information to\n"); printf(" stdout.\n"); printf(" xtreport - (debug) write internal data to xtmosquitto.kcg..\n"); printf("\nSee https://mosquitto.org/ for more information.\n\n"); } int main(int argc, char *argv[]) { int idx; int pid = -2; enum mosq_signal msig = 0; if(argc == 1){ print_usage(); return 1; } idx = 1; for(idx = 1; idx < argc; idx++){ if(!strcmp(argv[idx], "--help")){ print_usage(); return 1; }else if(!strcmp(argv[idx], "-a")){ pid = -1; }else if(!strcmp(argv[idx], "-p")){ if(idx+1 == argc){ fprintf(stderr, "Error: -p argument given but process ID missing.\n"); return 1; } pid = atoi(argv[idx+1]); if(pid < 1){ fprintf(stderr, "Error: Process ID must be >0.\n"); return 1; } idx++; }else{ break; } } if(pid == -2){ fprintf(stderr, "Error: One of -a or -p must be used.\n"); return 1; } if(idx == argc){ fprintf(stderr, "Error: No signal given.\n"); return 1; } if(!strcmp(argv[idx], "config-reload")){ msig = MSIG_CONFIG_RELOAD; }else if(!strcmp(argv[idx], "log-rotate")){ msig = MSIG_LOG_ROTATE; }else if(!strcmp(argv[idx], "shutdown")){ msig = MSIG_SHUTDOWN; }else if(!strcmp(argv[idx], "tree-print")){ msig = MSIG_TREE_PRINT; }else if(!strcmp(argv[idx], "xtreport")){ msig = MSIG_XTREPORT; }else{ fprintf(stderr, "Error: Unknown signal '%s'.\n", argv[idx]); return 1; } send_signal(pid, msig); return 0; } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/mosquitto_signal.h000066400000000000000000000003731514232433600300020ustar00rootroot00000000000000#ifndef MOSQUITTO_SIGNAL_H #define MOSQUITTO_SIGNAL_H enum mosq_signal { MSIG_CONFIG_RELOAD, MSIG_LOG_ROTATE, MSIG_SHUTDOWN, MSIG_TREE_PRINT, MSIG_XTREPORT, }; void signal_all(int sig); void send_signal(int pid, enum mosq_signal msig); #endif eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/signal_unix.c000066400000000000000000000042621514232433600267150ustar00rootroot00000000000000/* Copyright (c) 2024 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #include #include #include #include "mosquitto_signal.h" #ifndef PATH_MAX # define PATH_MAX 4096 #endif void signal_all(int sig) { DIR *dir; struct dirent *d; char pathbuf[PATH_MAX+1]; char cmdline[256]; const char *cmd; FILE *fptr; pid_t pid; dir = opendir("/proc"); if(dir == NULL){ fprintf(stderr, "Error reading /proc: %s.\n", strerror(errno)); return; } while((d = readdir(dir))){ #ifdef DT_DIR if(d->d_type == DT_DIR) #endif { pid = atoi(d->d_name); if(pid > 0){ snprintf(pathbuf, sizeof(pathbuf), "/proc/%s/cmdline", d->d_name); fptr = fopen(pathbuf, "r"); if(fptr){ if(fgets(cmdline, sizeof(cmdline), fptr)){ cmd = strrchr(cmdline, '/'); if(cmd){ cmd += 1; }else{ cmd = cmdline; } if(!strcmp(cmd, "mosquitto")){ if(kill(pid, sig) < 0){ fprintf(stderr, "Unable to signal process %d: %s\n", pid, strerror(errno)); } } } fclose(fptr); } } } } closedir(dir); } void send_signal(int pid, enum mosq_signal msig) { int sig; switch(msig){ case MSIG_CONFIG_RELOAD: sig = SIGHUP; break; case MSIG_LOG_ROTATE: sig = SIGHUP; break; case MSIG_SHUTDOWN: sig = SIGINT; break; case MSIG_TREE_PRINT: sig = SIGUSR2; break; #ifdef SIGRTMIN case MSIG_XTREPORT: sig = SIGRTMIN; break; #endif default: return; } if(pid > 0){ if(kill(pid, sig) != 0){ fprintf(stderr, "Error sending signal to process %d: %s\n", pid, strerror(errno)); } }else{ signal_all(sig); } } eclipse-mosquitto-mosquitto-691eab3/apps/mosquitto_signal/signal_windows.c000066400000000000000000000043331514232433600274230ustar00rootroot00000000000000/* Copyright (c) 2024 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #include #include #include #include "mosquitto_signal.h" #undef WITH_TLS #include "config.h" static const char *msig_to_string(enum mosq_sig msig) { switch(msig){ case MSIG_CONFIG_RELOAD: return "reload"; case MSIG_LOG_ROTATE: return "log_rotate"; case MSIG_SHUTDOWN: return "shutdown"; case MSIG_TREE_PRINT: return "tree_print"; case MSIG_XTREPORT: return "xtreport"; default: return ""; } } void signal_all(enum mosq_signal msig) { DWORD processes[2048], cbneeded, count; int pid; if(!EnumProcesses(processes, sizeof(processes), &cbneeded)){ fprintf(stderr, "Error enumerating processes.\n"); return; } count = cbneeded / sizeof(DWORD); for(DWORD i=0; i All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #ifndef WIN32 #include #include #else #include #include #define snprintf sprintf_s #define strncasecmp _strnicmp #endif #include "mosquitto.h" #include "client_shared.h" enum prop_type { PROP_TYPE_BYTE, PROP_TYPE_INT16, PROP_TYPE_INT32, PROP_TYPE_BINARY, PROP_TYPE_STRING, PROP_TYPE_STRING_PAIR, }; /* This parses property inputs. It should work for any command type, but is limited at the moment. * * Format: * * command property value * command property key value * * Example: * * publish message-expiry-interval 32 * connect user-property key value */ int cfg_parse_property(struct mosq_config *cfg, int argc, char *argv[], int *idx) { char *cmdname = NULL, *propname = NULL; char *key = NULL, *value = NULL; int cmd, identifier, type; mosquitto_property **proplist; int rc; long tmpl; size_t szt; /* idx now points to "command" */ if((*idx)+2 > argc-1){ /* Not enough args */ fprintf(stderr, "Error: --property argument given but not enough arguments specified.\n\n"); return MOSQ_ERR_INVAL; } cmdname = argv[*idx]; if(mosquitto_string_to_command(cmdname, &cmd)){ fprintf(stderr, "Error: Invalid command %s given in --property argument.\n\n", cmdname); return MOSQ_ERR_INVAL; } propname = argv[(*idx)+1]; if(mosquitto_string_to_property_info(propname, &identifier, &type)){ fprintf(stderr, "Error: Invalid property name %s given in --property argument.\n\n", propname); return MOSQ_ERR_INVAL; } if(mosquitto_property_check_command(cmd, identifier)){ fprintf(stderr, "Error: %s property not allowed for %s in --property argument.\n\n", propname, cmdname); return MOSQ_ERR_INVAL; } if(identifier == MQTT_PROP_USER_PROPERTY){ if((*idx)+3 > argc-1){ /* Not enough args */ fprintf(stderr, "Error: --property argument given but not enough arguments specified.\n\n"); return MOSQ_ERR_INVAL; } key = argv[(*idx)+2]; value = argv[(*idx)+3]; (*idx) += 3; }else{ value = argv[(*idx)+2]; (*idx) += 2; } switch(cmd){ case CMD_CONNECT: proplist = &cfg->connect_props; break; case CMD_PUBLISH: if(identifier == MQTT_PROP_TOPIC_ALIAS){ cfg->have_topic_alias = true; } if(identifier == MQTT_PROP_SUBSCRIPTION_IDENTIFIER){ fprintf(stderr, "Error: %s property not supported for %s in --property argument.\n\n", propname, cmdname); return MOSQ_ERR_INVAL; } proplist = &cfg->publish_props; break; case CMD_SUBSCRIBE: proplist = &cfg->subscribe_props; break; case CMD_UNSUBSCRIBE: proplist = &cfg->unsubscribe_props; break; case CMD_DISCONNECT: proplist = &cfg->disconnect_props; break; case CMD_AUTH: fprintf(stderr, "Error: %s property not supported for %s in --property argument.\n\n", propname, cmdname); return MOSQ_ERR_NOT_SUPPORTED; case CMD_WILL: proplist = &cfg->will_props; break; case CMD_PUBACK: case CMD_PUBREC: case CMD_PUBREL: case CMD_PUBCOMP: case CMD_SUBACK: case CMD_UNSUBACK: fprintf(stderr, "Error: %s property not supported for %s in --property argument.\n\n", propname, cmdname); return MOSQ_ERR_NOT_SUPPORTED; default: return MOSQ_ERR_INVAL; } switch(type){ case MQTT_PROP_TYPE_BYTE: tmpl = atol(value); if(tmpl < 0 || tmpl > UINT8_MAX){ fprintf(stderr, "Error: Property value (%ld) out of range for property %s.\n\n", tmpl, propname); return MOSQ_ERR_INVAL; } rc = mosquitto_property_add_byte(proplist, identifier, (uint8_t )tmpl); break; case MQTT_PROP_TYPE_INT16: tmpl = atol(value); if(tmpl < 0 || tmpl > UINT16_MAX){ fprintf(stderr, "Error: Property value (%ld) out of range for property %s.\n\n", tmpl, propname); return MOSQ_ERR_INVAL; } rc = mosquitto_property_add_int16(proplist, identifier, (uint16_t )tmpl); break; case MQTT_PROP_TYPE_INT32: tmpl = atol(value); if(tmpl < 0 || tmpl > UINT32_MAX){ fprintf(stderr, "Error: Property value (%ld) out of range for property %s.\n\n", tmpl, propname); return MOSQ_ERR_INVAL; } rc = mosquitto_property_add_int32(proplist, identifier, (uint32_t )tmpl); break; case MQTT_PROP_TYPE_VARINT: tmpl = atol(value); if(tmpl < 0 || tmpl > UINT32_MAX){ fprintf(stderr, "Error: Property value (%ld) out of range for property %s.\n\n", tmpl, propname); return MOSQ_ERR_INVAL; } rc = mosquitto_property_add_varint(proplist, identifier, (uint32_t )tmpl); break; case MQTT_PROP_TYPE_BINARY: szt = strlen(value); if(szt > UINT16_MAX){ fprintf(stderr, "Error: Property value too long for property %s.\n\n", propname); return MOSQ_ERR_INVAL; } rc = mosquitto_property_add_binary(proplist, identifier, value, (uint16_t )szt); break; case MQTT_PROP_TYPE_STRING: rc = mosquitto_property_add_string(proplist, identifier, value); break; case MQTT_PROP_TYPE_STRING_PAIR: rc = mosquitto_property_add_string_pair(proplist, identifier, key, value); break; default: return MOSQ_ERR_INVAL; } if(rc){ fprintf(stderr, "Error adding property %s %d\n", propname, type); return rc; } return MOSQ_ERR_SUCCESS; } eclipse-mosquitto-mosquitto-691eab3/client/client_shared.c000066400000000000000000001363741514232433600241250ustar00rootroot00000000000000/* Copyright (c) 2014-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #include #ifndef WIN32 # include # include # include #else # include # include # define snprintf sprintf_s # define strncasecmp _strnicmp #endif #include #include "client_shared.h" #ifdef WITH_SOCKS static int mosquitto__parse_socks_url(struct mosq_config *cfg, char *url); #endif static int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, char *argv[]); #ifdef WITH_TLS static void tls_keylog_callback(const SSL *ssl, const char *line); static int tls_ex_index_cfg = -1; #endif const char hexseplist[32] = { '!', '"', '#', '$', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~', ' ', }; static int check_format(const char *str) { size_t i; size_t len; len = strlen(str); for(i=0; i= '0' && str[i+1] <= '9'){ i++; if(i == len-1){ /* error */ fprintf(stderr, "Error: Incomplete format specifier.\n"); return 1; } } if(str[i+1] == '.'){ /* Precision specifier */ i++; if(i == len-1){ /* error */ fprintf(stderr, "Error: Incomplete format specifier.\n"); return 1; } /* Precision */ while(str[i+1] >= '0' && str[i+1] <= '9'){ i++; if(i == len-1){ /* error */ fprintf(stderr, "Error: Incomplete format specifier.\n"); return 1; } } } /* Hex field separator character */ for(size_t j=0; jport = PORT_UNDEFINED; cfg->max_inflight = 20; cfg->keepalive = 60; cfg->clean_session = true; cfg->eol = true; cfg->repeat_count = 1; cfg->repeat_delay.tv_sec = 0; cfg->repeat_delay.tv_usec = 0; cfg->random_filter = 10000; if(pub_or_sub == CLIENT_RR){ cfg->protocol_version = MQTT_PROTOCOL_V5; cfg->msg_count = 1; }else{ cfg->protocol_version = MQTT_PROTOCOL_V311; } cfg->session_expiry_interval = -1; /* -1 means unset here, the user can't set it to -1. */ cfg->transport = MOSQ_T_TCP; } void client_config_cleanup(struct mosq_config *cfg) { int i; free(cfg->id); free(cfg->id_prefix); free(cfg->host); free(cfg->file_input); mosquitto_FREE(cfg->message); free(cfg->topic); free(cfg->bind_address); free(cfg->username); free(cfg->password); free(cfg->will_topic); free(cfg->will_payload); free(cfg->format); free(cfg->response_topic); #ifdef WITH_TLS free(cfg->cafile); free(cfg->capath); free(cfg->certfile); free(cfg->keyfile); free(cfg->ciphers); free(cfg->tls_alpn); free(cfg->tls_version); free(cfg->tls_engine); free(cfg->tls_engine_kpass_sha1); free(cfg->keyform); # ifdef FINAL_WITH_TLS_PSK free(cfg->psk); free(cfg->psk_identity); # endif #endif if(cfg->topics){ for(i=0; itopic_count; i++){ free(cfg->topics[i]); } free(cfg->topics); } if(cfg->filter_outs){ for(i=0; ifilter_out_count; i++){ free(cfg->filter_outs[i]); } free(cfg->filter_outs); } if(cfg->unsub_topics){ for(i=0; iunsub_topic_count; i++){ free(cfg->unsub_topics[i]); } free(cfg->unsub_topics); } #ifdef WITH_SOCKS free(cfg->socks5_host); free(cfg->socks5_username); free(cfg->socks5_password); #endif mosquitto_property_free_all(&cfg->connect_props); mosquitto_property_free_all(&cfg->publish_props); mosquitto_property_free_all(&cfg->subscribe_props); mosquitto_property_free_all(&cfg->unsubscribe_props); mosquitto_property_free_all(&cfg->disconnect_props); mosquitto_property_free_all(&cfg->will_props); free(cfg->options_file); } /* Find if there is "-o" in the options */ static int client_config_options_file(struct mosq_config *cfg, int argc, char *argv[]) { int i; for(i=1; ioptions_file){ fprintf(stderr, "Error: Duplicate -o argument given.\n\n"); return 1; } if(i==argc-1){ fprintf(stderr, "Error: -o argument given but no options file specified.\n\n"); return 1; }else{ cfg->options_file = strdup(argv[i+1]); } } } return 0; } int client_config_load(struct mosq_config *cfg, int pub_or_sub, int argc, char *argv[]) { int rc; FILE *fptr; char line[1024]; int count; char *loc = NULL; size_t len; char *args[3]; #ifndef WIN32 char *env; #else char env[1024]; #endif args[0] = NULL; init_config(cfg, pub_or_sub); if(client_config_options_file(cfg, argc, argv)){ return 1; } if(cfg->options_file == NULL){ /* Default config file */ #ifndef WIN32 env = getenv("XDG_CONFIG_HOME"); if(env){ len = strlen(env) + strlen("/mosquitto_pub") + 1; loc = malloc(len); if(!loc){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } if(pub_or_sub == CLIENT_PUB){ snprintf(loc, len, "%s/mosquitto_pub", env); }else if(pub_or_sub == CLIENT_SUB){ snprintf(loc, len, "%s/mosquitto_sub", env); }else{ snprintf(loc, len, "%s/mosquitto_rr", env); } loc[len-1] = '\0'; }else{ env = getenv("HOME"); if(env){ len = strlen(env) + strlen("/.config/mosquitto_pub") + 1; loc = malloc(len); if(!loc){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } if(pub_or_sub == CLIENT_PUB){ snprintf(loc, len, "%s/.config/mosquitto_pub", env); }else if(pub_or_sub == CLIENT_SUB){ snprintf(loc, len, "%s/.config/mosquitto_sub", env); }else{ snprintf(loc, len, "%s/.config/mosquitto_rr", env); } loc[len-1] = '\0'; } } #else rc = GetEnvironmentVariable("USERPROFILE", env, 1024); if(rc > 0 && rc < 1024){ len = strlen(env) + strlen("\\mosquitto_pub.conf") + 1; loc = malloc(len); if(!loc){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } if(pub_or_sub == CLIENT_PUB){ snprintf(loc, len, "%s\\mosquitto_pub.conf", env); }else if(pub_or_sub == CLIENT_SUB){ snprintf(loc, len, "%s\\mosquitto_sub.conf", env); }else{ snprintf(loc, len, "%s\\mosquitto_rr.conf", env); } loc[len-1] = '\0'; } #endif } if(cfg->options_file){ fptr = fopen(cfg->options_file, "rt"); }else if(loc){ fptr = fopen(loc, "rt"); free(loc); loc = NULL; }else{ fptr = NULL; } if(fptr){ while(fgets(line, 1024, fptr)){ if(line[0] == '#'){ /* Comments */ continue; } while(line[strlen(line)-1] == 10 || line[strlen(line)-1] == 13){ line[strlen(line)-1] = 0; } /* All offset by one "args" here, because real argc/argv has * program name as the first entry. */ args[1] = strtok(line, " "); if(args[1]){ args[2] = strtok(NULL, ""); if(args[2]){ count = 3; }else{ count = 2; } rc = client_config_line_proc(cfg, pub_or_sub, count, args); if(rc){ fclose(fptr); free(loc); return rc; } } } fclose(fptr); } /* Deal with real argc/argv */ rc = client_config_line_proc(cfg, pub_or_sub, argc, argv); if(rc){ return rc; } if(cfg->will_payload && !cfg->will_topic){ fprintf(stderr, "Error: Will payload given, but no will topic given.\n"); return 1; } if(cfg->will_retain && !cfg->will_topic){ fprintf(stderr, "Error: Will retain given, but no will topic given.\n"); return 1; } #ifdef WITH_TLS if((cfg->certfile && !cfg->keyfile) || (cfg->keyfile && !cfg->certfile)){ fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is set.\n"); return 1; } if((cfg->keyform && !cfg->keyfile)){ fprintf(stderr, "Error: If keyform is set, keyfile must be also specified.\n"); return 1; } if((cfg->tls_engine_kpass_sha1 && (!cfg->keyform || !cfg->tls_engine))){ fprintf(stderr, "Error: when using tls-engine-kpass-sha1, both tls-engine and keyform must also be provided.\n"); return 1; } #endif #ifdef FINAL_WITH_TLS_PSK if((cfg->cafile || cfg->capath) && cfg->psk){ fprintf(stderr, "Error: Only one of --psk or --cafile/--capath may be used at once.\n"); return 1; } if(cfg->psk && !cfg->psk_identity){ fprintf(stderr, "Error: --psk-identity required if --psk used.\n"); return 1; } #endif if(cfg->protocol_version == 5){ if(cfg->clean_session == false && cfg->session_expiry_interval == -1){ /* User hasn't set session-expiry-interval, but has cleared clean * session so default to persistent session. */ cfg->session_expiry_interval = UINT32_MAX; } if(cfg->session_expiry_interval > 0){ if(cfg->session_expiry_interval == UINT32_MAX && (cfg->id_prefix || !cfg->id)){ fprintf(stderr, "Error: You must provide a client id if you are using an infinite session expiry interval.\n"); return 1; } rc = mosquitto_property_add_int32(&cfg->connect_props, MQTT_PROP_SESSION_EXPIRY_INTERVAL, (uint32_t )cfg->session_expiry_interval); if(rc){ fprintf(stderr, "Error adding property session-expiry-interval\n"); } } }else{ if(cfg->clean_session == false && (cfg->id_prefix || !cfg->id)){ fprintf(stderr, "Error: You must provide a client id if you are using the -c option.\n"); return 1; } } if(pub_or_sub == CLIENT_SUB){ if(cfg->topic_count == 0 && cfg->unsub_topic_count == 0){ fprintf(stderr, "Error: You must specify a topic to subscribe to (-t) or unsubscribe from (-U).\n"); return 1; } } if(!cfg->host){ cfg->host = strdup("localhost"); if(!cfg->host){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } } return MOSQ_ERR_SUCCESS; } static int cfg_add_topic(struct mosq_config *cfg, int type, char *topic, const char *arg) { if(mosquitto_validate_utf8(topic, (int )strlen(topic))){ fprintf(stderr, "Error: Malformed UTF-8 in %s argument.\n\n", arg); return 1; } if(type == CLIENT_PUB || type == CLIENT_RR){ if(mosquitto_pub_topic_check(topic) == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Invalid publish topic '%s', does it contain '+' or '#'?\n", topic); return 1; } cfg->topic = strdup(topic); }else if(type == CLIENT_RESPONSE_TOPIC){ if(mosquitto_pub_topic_check(topic) == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Invalid response topic '%s', does it contain '+' or '#'?\n", topic); return 1; } cfg->response_topic = strdup(topic); }else{ if(mosquitto_sub_topic_check(topic) == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Invalid subscription topic '%s', are all '+' and '#' wildcards correct?\n", topic); return 1; } cfg->topic_count++; cfg->topics = realloc(cfg->topics, (size_t )cfg->topic_count*sizeof(char *)); if(!cfg->topics){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } cfg->topics[cfg->topic_count-1] = strdup(topic); } return 0; } /* Process a tokenised single line from a file or set of real argc/argv */ int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, char *argv[]) { int i; int tmpi; float f; size_t szt; for(i=1; ibind_address = strdup(argv[i+1]); } i++; #ifdef WITH_TLS }else if(!strcmp(argv[i], "--cafile")){ if(i==argc-1){ fprintf(stderr, "Error: --cafile argument given but no file specified.\n\n"); return 1; }else{ cfg->cafile = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--capath")){ if(i==argc-1){ fprintf(stderr, "Error: --capath argument given but no directory specified.\n\n"); return 1; }else{ cfg->capath = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--cert")){ if(i==argc-1){ fprintf(stderr, "Error: --cert argument given but no file specified.\n\n"); return 1; }else{ cfg->certfile = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--ciphers")){ if(i==argc-1){ fprintf(stderr, "Error: --ciphers argument given but no ciphers specified.\n\n"); return 1; }else{ cfg->ciphers = strdup(argv[i+1]); } i++; #endif }else if(!strcmp(argv[i], "-C")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; }else{ if(i==argc-1){ fprintf(stderr, "Error: -C argument given but no count specified.\n\n"); return 1; }else{ cfg->msg_count = atoi(argv[i+1]); if(cfg->msg_count < 1){ fprintf(stderr, "Error: Invalid message count \"%d\".\n\n", cfg->msg_count); return 1; } } i++; } }else if(!strcmp(argv[i], "-c") || !strcmp(argv[i], "--disable-clean-session")){ cfg->clean_session = false; }else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")){ cfg->debug = true; }else if(!strcmp(argv[i], "-D") || !strcmp(argv[i], "--property")){ i++; if(cfg_parse_property(cfg, argc, argv, &i)){ return 1; } cfg->protocol_version = MQTT_PROTOCOL_V5; }else if(!strcmp(argv[i], "-e")){ if(pub_or_sub != CLIENT_RR){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: -e argument given but no response topic specified.\n\n"); return 1; }else{ if(cfg_add_topic(cfg, CLIENT_RESPONSE_TOPIC, argv[i+1], "-e")){ return 1; } } i++; }else if(!strcmp(argv[i], "-E")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } cfg->exit_after_sub = true; }else if(!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file")){ if(pub_or_sub == CLIENT_SUB){ goto unknown_option; } if(cfg->pub_mode != MSGMODE_NONE){ fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); return 1; }else if(i==argc-1){ fprintf(stderr, "Error: -f argument given but no file specified.\n\n"); return 1; }else{ cfg->pub_mode = MSGMODE_FILE; cfg->file_input = strdup(argv[i+1]); if(!cfg->file_input){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } } i++; }else if(!strcmp(argv[i], "-F")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: -F argument given but no format specified.\n\n"); return 1; }else{ cfg->format = strdup(argv[i+1]); if(!cfg->format){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } if(check_format(cfg->format)){ return 1; } } i++; }else if(!strcmp(argv[i], "--help")){ return 2; }else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--host")){ if(i==argc-1){ fprintf(stderr, "Error: -h argument given but no host specified.\n\n"); return 1; }else{ cfg->host = strdup(argv[i+1]); } i++; #ifdef WITH_TLS }else if(!strcmp(argv[i], "--insecure")){ cfg->insecure = true; #endif }else if(!strcmp(argv[i], "-i") || !strcmp(argv[i], "--id")){ if(cfg->id_prefix){ fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); return 1; } if(i==argc-1){ fprintf(stderr, "Error: -i argument given but no id specified.\n\n"); return 1; }else{ cfg->id = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "-I") || !strcmp(argv[i], "--id-prefix")){ if(cfg->id){ fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); return 1; } if(i==argc-1){ fprintf(stderr, "Error: -I argument given but no id prefix specified.\n\n"); return 1; }else{ cfg->id_prefix = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "-k") || !strcmp(argv[i], "--keepalive")){ if(i==argc-1){ fprintf(stderr, "Error: -k argument given but no keepalive specified.\n\n"); return 1; }else{ cfg->keepalive = atoi(argv[i+1]); if(cfg->keepalive<5 || cfg->keepalive>UINT16_MAX){ fprintf(stderr, "Error: Invalid keepalive given, it must be between 5 and 65535 inclusive.\n\n"); return 1; } } i++; #ifdef WITH_TLS }else if(!strcmp(argv[i], "--key")){ if(i==argc-1){ fprintf(stderr, "Error: --key argument given but no file specified.\n\n"); return 1; }else{ cfg->keyfile = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--keyform")){ if(i==argc-1){ fprintf(stderr, "Error: --keyform argument given but no keyform specified.\n\n"); return 1; }else{ cfg->keyform = strdup(argv[i+1]); } i++; #endif }else if(!strcmp(argv[i], "-L") || !strcmp(argv[i], "--url")){ if(i==argc-1){ fprintf(stderr, "Error: -L argument given but no URL specified.\n\n"); return 1; }else{ char *url = argv[i+1]; char *topic; char *tmp; if(!strncasecmp(url, "mqtt://", 7)){ url += 7; cfg->port = 1883; }else if(!strncasecmp(url, "mqtts://", 8)){ #ifdef WITH_TLS url += 8; cfg->port = 8883; cfg->tls_use_os_certs = true; #else fprintf(stderr, "Error: TLS support not available.\n\n"); return 1; #endif }else if(!strncasecmp(url, "ws://", 5)){ url += 5; cfg->port = 1883; cfg->transport = MOSQ_T_WEBSOCKETS; }else if(!strncasecmp(url, "wss://", 6)){ #ifdef WITH_TLS url += 6; cfg->port = 8883; cfg->tls_use_os_certs = true; cfg->transport = MOSQ_T_WEBSOCKETS; #else fprintf(stderr, "Error: TLS support not available.\n\n"); return 1; #endif }else{ fprintf(stderr, "Error: Unsupported URL scheme.\n\n"); return 1; } topic = strchr(url, '/'); if(!topic){ fprintf(stderr, "Error: Invalid URL for -L argument specified - topic missing.\n"); return 1; } *topic++ = 0; if(cfg_add_topic(cfg, pub_or_sub, topic, "-L topic")){ return 1; } tmp = strchr(url, '@'); if(tmp){ char *colon; *tmp++ = 0; colon = strchr(url, ':'); if(colon){ *colon = 0; cfg->password = strdup(colon + 1); } cfg->username = strdup(url); url = tmp; } cfg->host = url; tmp = strchr(url, ':'); if(tmp){ *tmp++ = 0; cfg->port = atoi(tmp); } /* Now we've removed the port, time to get the host on the heap */ cfg->host = strdup(cfg->host); } i++; }else if(!strcmp(argv[i], "-l") || !strcmp(argv[i], "--stdin-line")){ if(pub_or_sub != CLIENT_PUB){ goto unknown_option; } if(cfg->pub_mode != MSGMODE_NONE){ fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); return 1; }else{ cfg->pub_mode = MSGMODE_STDIN_LINE; } }else if(!strcmp(argv[i], "--latency")){ if(pub_or_sub != CLIENT_RR){ goto unknown_option; } cfg->measure_latency = true; cfg->tcp_nodelay = true; /* Remove influence of nagle */ }else if(!strcmp(argv[i], "-m") || !strcmp(argv[i], "--message")){ if(pub_or_sub == CLIENT_SUB){ goto unknown_option; } if(cfg->pub_mode != MSGMODE_NONE){ fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); return 1; }else if(i==argc-1){ fprintf(stderr, "Error: -m argument given but no message specified.\n\n"); return 1; }else{ cfg->message = mosquitto_strdup(argv[i+1]); if(cfg->message == NULL){ fprintf(stderr, "Error: Out of memory.\n\n"); return 1; } szt = strlen(cfg->message); if(szt > MQTT_MAX_PAYLOAD){ fprintf(stderr, "Error: Message length must be less than %u bytes.\n\n", MQTT_MAX_PAYLOAD); return 1; } cfg->msglen = (int )szt; cfg->pub_mode = MSGMODE_CMD; } i++; }else if(!strcmp(argv[i], "-M")){ if(i==argc-1){ fprintf(stderr, "Error: -M argument given but max_inflight not specified.\n\n"); return 1; }else{ tmpi = atoi(argv[i+1]); if(tmpi < 1){ fprintf(stderr, "Error: Maximum inflight messages must be greater than 0.\n\n"); return 1; } cfg->max_inflight = (unsigned int )tmpi; } i++; }else if(!strcmp(argv[i], "--message-rate")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } cfg->message_rate = true; }else if(!strcmp(argv[i], "--nodelay")){ cfg->tcp_nodelay = true; }else if(!strcmp(argv[i], "--no-tls")){ cfg->no_tls = true; }else if(!strcmp(argv[i], "-n") || !strcmp(argv[i], "--null-message")){ if(pub_or_sub == CLIENT_SUB){ goto unknown_option; } if(cfg->pub_mode != MSGMODE_NONE){ fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); return 1; }else{ cfg->pub_mode = MSGMODE_NULL; } }else if(!strcmp(argv[i], "-N")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } cfg->eol = false; }else if(!strcmp(argv[i], "-o")){ /* Already handled */ i++; }else if(!strcmp(argv[i], "-p") || !strcmp(argv[i], "--port")){ if(i==argc-1){ fprintf(stderr, "Error: -p argument given but no port specified.\n\n"); return 1; }else{ cfg->port = atoi(argv[i+1]); if(cfg->port<0 || cfg->port>65535){ fprintf(stderr, "Error: Invalid port given: %d\n", cfg->port); return 1; } } i++; }else if(!strcmp(argv[i], "--pretty")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } cfg->pretty = true; }else if(!strcmp(argv[i], "-P") || !strcmp(argv[i], "--pw")){ if(i==argc-1){ fprintf(stderr, "Error: -P argument given but no password specified.\n\n"); return 1; }else{ cfg->password = strdup(argv[i+1]); } i++; #ifdef WITH_SOCKS }else if(!strcmp(argv[i], "--proxy")){ if(i==argc-1){ fprintf(stderr, "Error: --proxy argument given but no proxy url specified.\n\n"); return 1; }else{ if(mosquitto__parse_socks_url(cfg, argv[i+1])){ return 1; } i++; } #endif #ifdef FINAL_WITH_TLS_PSK }else if(!strcmp(argv[i], "--psk")){ if(i==argc-1){ fprintf(stderr, "Error: --psk argument given but no key specified.\n\n"); return 1; }else{ cfg->psk = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--psk-identity")){ if(i==argc-1){ fprintf(stderr, "Error: --psk-identity argument given but no identity specified.\n\n"); return 1; }else{ cfg->psk_identity = strdup(argv[i+1]); } i++; #endif }else if(!strcmp(argv[i], "-q") || !strcmp(argv[i], "--qos")){ if(i==argc-1){ fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n"); return 1; }else{ cfg->qos = atoi(argv[i+1]); if(cfg->qos<0 || cfg->qos>2){ fprintf(stderr, "Error: Invalid QoS given: %d\n", cfg->qos); return 1; } } i++; }else if(!strcmp(argv[i], "--quiet")){ cfg->quiet = true; }else if(!strcmp(argv[i], "-r") || !strcmp(argv[i], "--retain")){ if(pub_or_sub != CLIENT_PUB){ goto unknown_option; } cfg->retain = 1; }else if(!strcmp(argv[i], "-R")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } cfg->no_retain = true; cfg->sub_opts |= MQTT_SUB_OPT_SEND_RETAIN_NEVER; }else if(!strcmp(argv[i], "--random-filter")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: --random-filter argument given but no chance specified.\n\n"); return 1; }else{ cfg->random_filter = (int)(10.0*atof(argv[i+1])); if(cfg->random_filter > 10000 || cfg->random_filter < 1){ fprintf(stderr, "Error: --random-filter chance must be between 0.1-100.0\n\n"); return 1; } } i++; }else if(!strcmp(argv[i], "--remove-retained")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } cfg->remove_retained = true; }else if(!strcmp(argv[i], "--repeat")){ if(pub_or_sub != CLIENT_PUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: --repeat argument given but no count specified.\n\n"); return 1; }else{ cfg->repeat_count = atoi(argv[i+1]); if(cfg->repeat_count < 1){ fprintf(stderr, "Error: --repeat argument must be >0.\n\n"); return 1; } } i++; }else if(!strcmp(argv[i], "--repeat-delay")){ if(pub_or_sub != CLIENT_PUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: --repeat-delay argument given but no time specified.\n\n"); return 1; }else{ f = (float )atof(argv[i+1]); if(f < 0.0f){ fprintf(stderr, "Error: --repeat-delay argument must be >=0.0.\n\n"); return 1; } f *= 1.0e6f; cfg->repeat_delay.tv_sec = (int)f/1000000; cfg->repeat_delay.tv_usec = (int)f%1000000; } i++; }else if(!strcmp(argv[i], "--retain-as-published")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } cfg->sub_opts |= MQTT_SUB_OPT_RETAIN_AS_PUBLISHED; }else if(!strcmp(argv[i], "--retain-handling")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: --retain-handling argument given but no option specified.\n\n"); return 1; }else{ if(!strcmp(argv[i+1], "always")){ MQTT_SUB_OPT_SET_RETAIN_HANDLING(cfg->sub_opts, MQTT_SUB_OPT_SEND_RETAIN_ALWAYS); }else if(!strcmp(argv[i+1], "new")){ MQTT_SUB_OPT_SET_RETAIN_HANDLING(cfg->sub_opts, MQTT_SUB_OPT_SEND_RETAIN_NEW); }else if(!strcmp(argv[i+1], "never")){ MQTT_SUB_OPT_SET_RETAIN_HANDLING(cfg->sub_opts, MQTT_SUB_OPT_SEND_RETAIN_NEVER); }else{ fprintf(stderr, "Error: Unknown value '%s' for --retain-handling.\n\n", argv[i+1]); return 1; } } i++; }else if(!strcmp(argv[i], "--retained-only")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } cfg->retained_only = true; }else if(!strcmp(argv[i], "-s") || !strcmp(argv[i], "--stdin-file")){ if(pub_or_sub == CLIENT_SUB){ goto unknown_option; } if(cfg->pub_mode != MSGMODE_NONE){ fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); return 1; }else{ cfg->pub_mode = MSGMODE_STDIN_FILE; } #ifdef WITH_SRV }else if(!strcmp(argv[i], "-S")){ cfg->use_srv = true; #endif }else if(!strcmp(argv[i], "-t") || !strcmp(argv[i], "--topic")){ if(i==argc-1){ fprintf(stderr, "Error: -t argument given but no topic specified.\n\n"); return 1; }else{ if(cfg_add_topic(cfg, pub_or_sub, argv[i + 1], "-t")){ return 1; } i++; } }else if(!strcmp(argv[i], "-T") || !strcmp(argv[i], "--filter-out")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: -T argument given but no topic filter specified.\n\n"); return 1; }else{ if(mosquitto_validate_utf8(argv[i+1], (int )strlen(argv[i+1]))){ fprintf(stderr, "Error: Malformed UTF-8 in -T argument.\n\n"); return 1; } if(mosquitto_sub_topic_check(argv[i+1]) == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Invalid filter topic '%s', are all '+' and '#' wildcards correct?\n", argv[i+1]); return 1; } cfg->filter_out_count++; cfg->filter_outs = realloc(cfg->filter_outs, (size_t )cfg->filter_out_count*sizeof(char *)); if(!cfg->filter_outs){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } cfg->filter_outs[cfg->filter_out_count-1] = strdup(argv[i+1]); } i++; #ifdef WITH_TLS }else if(!strcmp(argv[i], "--tls-alpn")){ if(i==argc-1){ fprintf(stderr, "Error: --tls-alpn argument given but no protocol specified.\n\n"); return 1; }else{ cfg->tls_alpn = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--tls-engine")){ if(i==argc-1){ fprintf(stderr, "Error: --tls-engine argument given but no engine_id specified.\n\n"); return 1; }else{ cfg->tls_engine = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--tls-engine-kpass-sha1")){ if(i==argc-1){ fprintf(stderr, "Error: --tls-engine-kpass-sha1 argument given but no kpass sha1 specified.\n\n"); return 1; }else{ cfg->tls_engine_kpass_sha1 = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--tls-use-os-certs")){ cfg->tls_use_os_certs = true; }else if(!strcmp(argv[i], "--tls-version")){ if(i==argc-1){ fprintf(stderr, "Error: --tls-version argument given but no version specified.\n\n"); return 1; }else{ cfg->tls_version = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--tls-keylog")){ if(i==argc-1){ fprintf(stderr, "Error: --tls-keylog argument given but no file specified.\n\n"); return 1; }else{ cfg->tls_keylog = strdup(argv[i+1]); } i++; #endif }else if(!strcmp(argv[i], "-U") || !strcmp(argv[i], "--unsubscribe")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } if(i==argc-1){ fprintf(stderr, "Error: -U argument given but no unsubscribe topic specified.\n\n"); return 1; }else{ if(mosquitto_validate_utf8(argv[i+1], (int )strlen(argv[i+1]))){ fprintf(stderr, "Error: Malformed UTF-8 in -U argument.\n\n"); return 1; } if(mosquitto_sub_topic_check(argv[i+1]) == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Invalid unsubscribe topic '%s', are all '+' and '#' wildcards correct?\n", argv[i+1]); return 1; } cfg->unsub_topic_count++; cfg->unsub_topics = realloc(cfg->unsub_topics, (size_t )cfg->unsub_topic_count*sizeof(char *)); if(!cfg->unsub_topics){ fprintf(stderr, "Error: Out of memory.\n"); return 1; } cfg->unsub_topics[cfg->unsub_topic_count-1] = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "-u") || !strcmp(argv[i], "--username")){ if(i==argc-1){ fprintf(stderr, "Error: -u argument given but no username specified.\n\n"); return 1; }else{ cfg->username = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--unix")){ if(i==argc-1){ fprintf(stderr, "Error: --unix argument given but no socket path specified.\n\n"); return 1; }else{ cfg->host = strdup(argv[i+1]); cfg->port = 0; } i++; }else if(!strcmp(argv[i], "-V") || !strcmp(argv[i], "--protocol-version")){ if(i==argc-1){ fprintf(stderr, "Error: --protocol-version argument given but no version specified.\n\n"); return 1; }else{ if(!strcmp(argv[i+1], "mqttv31") || !strcmp(argv[i+1], "31")){ cfg->protocol_version = MQTT_PROTOCOL_V31; }else if(!strcmp(argv[i+1], "mqttv311") || !strcmp(argv[i+1], "311")){ cfg->protocol_version = MQTT_PROTOCOL_V311; }else if(!strcmp(argv[i+1], "mqttv5") || !strcmp(argv[i+1], "5")){ cfg->protocol_version = MQTT_PROTOCOL_V5; }else{ fprintf(stderr, "Error: Invalid protocol version argument given.\n\n"); return 1; } i++; } }else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; } cfg->verbose = 1; }else if(!strcmp(argv[i], "--version")){ return 3; }else if(!strcmp(argv[i], "-W")){ if(pub_or_sub == CLIENT_PUB){ goto unknown_option; }else{ if(i==argc-1){ fprintf(stderr, "Error: -W argument given but no timeout specified.\n\n"); return 1; }else{ tmpi = atoi(argv[i+1]); if(tmpi < 1){ fprintf(stderr, "Error: Invalid timeout \"%d\".\n\n", tmpi); return 1; } cfg->timeout = (unsigned int )tmpi; } i++; } }else if(!strcmp(argv[i], "-w") || !strcmp(argv[i], "--watch")){ if(pub_or_sub != CLIENT_SUB){ goto unknown_option; } #ifdef WIN32 fprintf(stderr, "Error: --watch not supported on Windows.\n\n"); return 1; #else cfg->watch = true; #endif }else if(!strcmp(argv[i], "--will-payload")){ if(i==argc-1){ fprintf(stderr, "Error: --will-payload argument given but no will payload specified.\n\n"); return 1; }else{ cfg->will_payload = strdup(argv[i+1]); cfg->will_payloadlen = (int )strlen(cfg->will_payload); } i++; }else if(!strcmp(argv[i], "--will-qos")){ if(i==argc-1){ fprintf(stderr, "Error: --will-qos argument given but no will QoS specified.\n\n"); return 1; }else{ cfg->will_qos = atoi(argv[i+1]); if(cfg->will_qos < 0 || cfg->will_qos > 2){ fprintf(stderr, "Error: Invalid will QoS %d.\n\n", cfg->will_qos); return 1; } } i++; }else if(!strcmp(argv[i], "--will-retain")){ cfg->will_retain = true; }else if(!strcmp(argv[i], "--will-topic")){ if(i==argc-1){ fprintf(stderr, "Error: --will-topic argument given but no will topic specified.\n\n"); return 1; }else{ if(mosquitto_validate_utf8(argv[i+1], (int )strlen(argv[i+1]))){ fprintf(stderr, "Error: Malformed UTF-8 in --will-topic argument.\n\n"); return 1; } if(mosquitto_pub_topic_check(argv[i+1]) == MOSQ_ERR_INVAL){ fprintf(stderr, "Error: Invalid will topic '%s', does it contain '+' or '#'?\n", argv[i+1]); return 1; } cfg->will_topic = strdup(argv[i+1]); } i++; }else if(!strcmp(argv[i], "--ws")){ cfg->transport = MOSQ_T_WEBSOCKETS; }else if(!strcmp(argv[i], "-x")){ if(i==argc-1){ fprintf(stderr, "Error: -x argument given but no session expiry interval specified.\n\n"); return 1; }else{ if(!strcmp(argv[i+1], "∞")){ cfg->session_expiry_interval = UINT32_MAX; }else{ char *endptr = NULL; cfg->session_expiry_interval = strtol(argv[i+1], &endptr, 0); if(endptr == argv[i+1] || endptr[0] != '\0'){ /* Entirety of argument wasn't a number */ fprintf(stderr, "Error: session-expiry-interval not a number.\n\n"); return 1; } if(cfg->session_expiry_interval > UINT32_MAX || cfg->session_expiry_interval < -1){ fprintf(stderr, "Error: session-expiry-interval out of range.\n\n"); return 1; } if(cfg->session_expiry_interval == -1){ /* Convenience value for infinity. */ cfg->session_expiry_interval = UINT32_MAX; } } cfg->protocol_version = MQTT_PROTOCOL_V5; } i++; }else{ goto unknown_option; } } return MOSQ_ERR_SUCCESS; unknown_option: fprintf(stderr, "Error: Unknown option '%s'.\n", argv[i]); return 1; } #ifdef WITH_TLS static int client_tls_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) { int rc; if(cfg->no_tls){ return MOSQ_ERR_SUCCESS; } if(cfg->tls_keylog){ if(tls_ex_index_cfg == -1){ tls_ex_index_cfg = SSL_CTX_get_ex_new_index(0, "client config", NULL, NULL, NULL); } cfg->ssl_ctx = SSL_CTX_new(TLS_client_method()); if(!cfg->ssl_ctx){ err_printf(cfg, "Error: Unable to create SSL_CTX.\n"); return 1; } if(!SSL_CTX_set_ex_data(cfg->ssl_ctx, tls_ex_index_cfg, cfg)){ err_printf(cfg, "Error: Unable to set SSL_CTX ex data.\n"); return 1; } mosquitto_void_option(mosq, MOSQ_OPT_SSL_CTX, cfg->ssl_ctx); mosquitto_int_option(mosq, MOSQ_OPT_SSL_CTX_WITH_DEFAULTS, 1); SSL_CTX_set_keylog_callback(cfg->ssl_ctx, tls_keylog_callback); } if(cfg->cafile || cfg->capath){ rc = mosquitto_tls_set(mosq, cfg->cafile, cfg->capath, cfg->certfile, cfg->keyfile, NULL); if(rc){ if(rc == MOSQ_ERR_INVAL){ err_printf(cfg, "Error: Problem setting TLS options: File not found.\n"); }else{ err_printf(cfg, "Error: Problem setting TLS options: %s.\n", mosquitto_strerror(rc)); } return 1; } # ifdef FINAL_WITH_TLS_PSK }else if(cfg->psk){ if(mosquitto_tls_psk_set(mosq, cfg->psk, cfg->psk_identity, NULL)){ err_printf(cfg, "Error: Problem setting TLS-PSK options.\n"); return 1; } # endif }else if(cfg->port == 8883){ mosquitto_int_option(mosq, MOSQ_OPT_TLS_USE_OS_CERTS, 1); } if(cfg->tls_use_os_certs){ mosquitto_int_option(mosq, MOSQ_OPT_TLS_USE_OS_CERTS, 1); } if(cfg->insecure && mosquitto_tls_insecure_set(mosq, true)){ err_printf(cfg, "Error: Problem setting TLS insecure option.\n"); return 1; } if(cfg->tls_engine && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE, cfg->tls_engine)){ err_printf(cfg, "Error: Problem setting TLS engine, is %s a valid engine?\n", cfg->tls_engine); return 1; } if(cfg->keyform && mosquitto_string_option(mosq, MOSQ_OPT_TLS_KEYFORM, cfg->keyform)){ err_printf(cfg, "Error: Problem setting key form, it must be one of 'pem' or 'engine'.\n"); return 1; } if(cfg->tls_engine_kpass_sha1 && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ENGINE_KPASS_SHA1, cfg->tls_engine_kpass_sha1)){ err_printf(cfg, "Error: Problem setting TLS engine key pass sha, is it a 40 character hex string?\n"); return 1; } if(cfg->tls_alpn && mosquitto_string_option(mosq, MOSQ_OPT_TLS_ALPN, cfg->tls_alpn)){ err_printf(cfg, "Error: Problem setting TLS ALPN protocol.\n"); return 1; } if((cfg->tls_version || cfg->ciphers) && mosquitto_tls_opts_set(mosq, !cfg->insecure, cfg->tls_version, cfg->ciphers)){ err_printf(cfg, "Error: Problem setting TLS options, check the options are valid.\n"); return 1; } return MOSQ_ERR_SUCCESS; } #endif int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) { #if defined(WITH_SOCKS) int rc; #endif mosquitto_int_option(mosq, MOSQ_OPT_PROTOCOL_VERSION, cfg->protocol_version); mosquitto_int_option(mosq, MOSQ_OPT_TRANSPORT, cfg->transport); if(cfg->will_topic && mosquitto_will_set_v5(mosq, cfg->will_topic, cfg->will_payloadlen, cfg->will_payload, cfg->will_qos, cfg->will_retain, cfg->will_props)){ err_printf(cfg, "Error: Problem setting will.\n"); return 1; } cfg->will_props = NULL; if((cfg->username || cfg->password) && mosquitto_username_pw_set(mosq, cfg->username, cfg->password)){ err_printf(cfg, "Error: Problem setting username and/or password.\n"); return 1; } #ifdef WITH_TLS if(client_tls_opts_set(mosq, cfg)){ return 1; } #endif mosquitto_max_inflight_messages_set(mosq, cfg->max_inflight); #ifdef WITH_SOCKS if(cfg->socks5_host){ rc = mosquitto_socks5_set(mosq, cfg->socks5_host, cfg->socks5_port, cfg->socks5_username, cfg->socks5_password); if(rc){ return rc; } } #endif if(cfg->tcp_nodelay){ mosquitto_int_option(mosq, MOSQ_OPT_TCP_NODELAY, 1); } if(cfg->msg_count > 0 && cfg->msg_count < 20){ /* 20 is the default "receive maximum" * If we don't set this, then we can receive > msg_count messages * before we quit.*/ mosquitto_int_option(mosq, MOSQ_OPT_RECEIVE_MAXIMUM, cfg->msg_count); } return MOSQ_ERR_SUCCESS; } int clientid_generate(struct mosq_config *cfg) { if(cfg->id_prefix){ cfg->id = malloc(strlen(cfg->id_prefix)+10); if(!cfg->id){ err_printf(cfg, "Error: Out of memory.\n"); return 1; } snprintf(cfg->id, strlen(cfg->id_prefix)+10, "%s%d", cfg->id_prefix, getpid()); } return MOSQ_ERR_SUCCESS; } int client_connect(struct mosquitto *mosq, struct mosq_config *cfg) { #ifndef WIN32 char *err; #else char err[1024]; #endif int rc; int port; #ifndef WIN32 signal(SIGPIPE, SIG_IGN); #endif if(cfg->port == PORT_UNDEFINED){ #ifdef WITH_TLS if(cfg->cafile || cfg->capath # ifdef FINAL_WITH_TLS_PSK || cfg->psk # endif ){ port = 8883; }else #endif { port = 1883; } }else{ port = cfg->port; } #ifdef WITH_SRV if(cfg->use_srv){ rc = mosquitto_connect_srv(mosq, cfg->host, cfg->keepalive, cfg->bind_address); }else{ rc = mosquitto_connect_bind_v5(mosq, cfg->host, port, cfg->keepalive, cfg->bind_address, cfg->connect_props); } #else rc = mosquitto_connect_bind_v5(mosq, cfg->host, port, cfg->keepalive, cfg->bind_address, cfg->connect_props); #endif if(rc>0){ if(rc == MOSQ_ERR_ERRNO){ #ifndef WIN32 err = strerror(errno); #else FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL); #endif err_printf(cfg, "Error: %s\n", err); }else{ err_printf(cfg, "Unable to connect (%s).\n", mosquitto_strerror(rc)); } mosquitto_lib_cleanup(); return rc; } return MOSQ_ERR_SUCCESS; } #ifdef WITH_SOCKS /* Convert %25 -> %, %3a, %3A -> :, %40 -> @ */ static int mosquitto__urldecode(char *str) { size_t i, j; size_t len; if(!str){ return 0; } if(!strchr(str, '%')){ return 0; } len = strlen(str); for(i=0; i= len){ return 1; } if(str[i+1] == '2' && str[i+2] == '5'){ str[i] = '%'; len -= 2; for(j=i+1; j start){ len = i-start; if(host){ /* Have already seen a @ , so this must be of form * socks5h://username[:password]@host:port */ port = malloc(len + 1); if(!port){ err_printf(cfg, "Error: Out of memory.\n"); goto cleanup; } memcpy(port, &(str[start]), len); port[len] = '\0'; }else{ host = malloc(len + 1); if(!host){ err_printf(cfg, "Error: Out of memory.\n"); goto cleanup; } memcpy(host, &(str[start]), len); host[len] = '\0'; } } if(!host){ err_printf(cfg, "Error: Invalid proxy.\n"); goto cleanup; } if(mosquitto__urldecode(username)){ goto cleanup; } if(mosquitto__urldecode(password)){ goto cleanup; } if(port){ port_int = atoi(port); if(port_int < 1 || port_int > 65535){ err_printf(cfg, "Error: Invalid proxy port %d\n", port_int); goto cleanup; } free(port); }else{ port_int = 1080; } cfg->socks5_username = username; cfg->socks5_password = password; cfg->socks5_host = host; cfg->socks5_port = port_int; return 0; cleanup: free(username); free(password); free(host); free(port); return 1; } #endif void err_printf(const struct mosq_config *cfg, const char *fmt, ...) { va_list va; if(cfg->quiet){ return; } va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); } #ifdef WITH_TLS static void tls_keylog_callback(const SSL *ssl, const char *line) { struct mosq_config *cfg; FILE *fptr; UNUSED(ssl); cfg = SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), tls_ex_index_cfg); if(cfg && cfg->tls_keylog){ fptr = fopen(cfg->tls_keylog, "at"); if(fptr){ fprintf(fptr, "%s\n", line); fclose(fptr); } } } #endif eclipse-mosquitto-mosquitto-691eab3/client/client_shared.h000066400000000000000000000073661514232433600241300ustar00rootroot00000000000000/* Copyright (c) 2014-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef CLIENT_SHARED_H #define CLIENT_SHARED_H #include #ifdef WIN32 # include #else # include #endif #ifdef WITH_TLS # include #endif #ifndef __GNUC__ #define __attribute__(attrib) #endif /* pub_client.c modes */ #define MSGMODE_NONE 0 #define MSGMODE_CMD 1 #define MSGMODE_STDIN_LINE 2 #define MSGMODE_STDIN_FILE 3 #define MSGMODE_FILE 4 #define MSGMODE_NULL 5 #define CLIENT_PUB 1 #define CLIENT_SUB 2 #define CLIENT_RR 3 #define CLIENT_RESPONSE_TOPIC 4 #define PORT_UNDEFINED -1 #define PORT_UNIX 0 struct mosq_config { char *id; char *id_prefix; int protocol_version; int keepalive; char *host; int port; int qos; bool retain; int pub_mode; /* pub, rr */ char *file_input; /* pub, rr */ char *message; /* pub, rr */ int msglen; /* pub, rr */ char *topic; /* pub, rr */ char *bind_address; int repeat_count; /* pub */ struct timeval repeat_delay; /* pub */ #ifdef WITH_SRV bool use_srv; #endif bool debug; bool quiet; unsigned int max_inflight; char *username; char *password; char *will_topic; char *will_payload; int will_payloadlen; int will_qos; bool will_retain; #ifdef WITH_TLS char *cafile; char *capath; char *certfile; char *keyfile; char *ciphers; bool insecure; char *tls_alpn; char *tls_version; char *tls_engine; char *tls_engine_kpass_sha1; char *keyform; bool tls_use_os_certs; char *tls_keylog; SSL_CTX *ssl_ctx; # ifdef FINAL_WITH_TLS_PSK char *psk; char *psk_identity; # endif #endif bool clean_session; char **topics; /* sub, rr */ int topic_count; /* sub, rr */ bool exit_after_sub; /* sub */ bool no_retain; /* sub */ bool retained_only; /* sub */ bool remove_retained; /* sub */ char **filter_outs; /* sub */ int filter_out_count; /* sub */ char **unsub_topics; /* sub */ int unsub_topic_count; /* sub */ bool verbose; /* sub */ bool eol; /* sub */ int msg_count; /* sub */ char *format; /* sub, rr */ bool pretty; /* sub, rr */ unsigned int timeout; /* sub */ int sub_opts; /* sub */ long session_expiry_interval; int random_filter; /* sub */ int transport; #ifndef WIN32 bool watch; /* sub */ #endif #ifdef WITH_SOCKS char *socks5_host; int socks5_port; char *socks5_username; char *socks5_password; #endif mosquitto_property *connect_props; mosquitto_property *publish_props; mosquitto_property *subscribe_props; mosquitto_property *unsubscribe_props; mosquitto_property *disconnect_props; mosquitto_property *will_props; char *response_topic; /* rr */ char *options_file; bool have_topic_alias; /* pub */ bool tcp_nodelay; bool no_tls; bool message_rate; /* sub */ bool measure_latency; /* rr */ }; extern const char hexseplist[32]; int client_config_load(struct mosq_config *config, int pub_or_sub, int argc, char *argv[]); void client_config_cleanup(struct mosq_config *cfg); int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg); int clientid_generate(struct mosq_config *cfg); int client_connect(struct mosquitto *mosq, struct mosq_config *cfg); int cfg_parse_property(struct mosq_config *cfg, int argc, char *argv[], int *idx); void err_printf(const struct mosq_config *cfg, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #endif eclipse-mosquitto-mosquitto-691eab3/client/pub_client.c000066400000000000000000000475341514232433600234440ustar00rootroot00000000000000/* Copyright (c) 2009-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #ifndef WIN32 #include #include #else #include #include #define snprintf sprintf_s #endif #include #include "client_shared.h" #include "pub_shared.h" /* Global variables for use in callbacks. See sub_client.c for an example of * using a struct to hold variables for use in callbacks. */ static bool first_publish = true; static int last_mid = -1; static int last_mid_sent = -1; static char *line_buf = NULL; static int line_buf_len = 1024; static bool disconnect_sent = false; static int publish_count = 0; static bool ready_for_repeat = false; static volatile int status = STATUS_CONNECTING; static int connack_result = 0; #ifdef WIN32 static uint64_t next_publish_tv; static void set_repeat_time(void) { uint64_t ticks = GetTickCount64(); next_publish_tv = ticks + cfg.repeat_delay.tv_sec*1000 + cfg.repeat_delay.tv_usec/1000; } static int check_repeat_time(void) { uint64_t ticks = GetTickCount64(); if(ticks > next_publish_tv){ return 1; }else{ return 0; } } #else static struct timeval next_publish_tv; static void set_repeat_time(void) { gettimeofday(&next_publish_tv, NULL); next_publish_tv.tv_sec += cfg.repeat_delay.tv_sec; next_publish_tv.tv_usec += cfg.repeat_delay.tv_usec; next_publish_tv.tv_sec += next_publish_tv.tv_usec/1000000; next_publish_tv.tv_usec = next_publish_tv.tv_usec%1000000; } static int check_repeat_time(void) { struct timeval tv; gettimeofday(&tv, NULL); if(tv.tv_sec > next_publish_tv.tv_sec){ return 1; }else if(tv.tv_sec == next_publish_tv.tv_sec && tv.tv_usec > next_publish_tv.tv_usec){ return 1; } return 0; } #endif void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc, const mosquitto_property *properties) { UNUSED(mosq); UNUSED(obj); UNUSED(rc); UNUSED(properties); if(rc == 0){ status = STATUS_DISCONNECTED; } } int my_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen, void *payload, int qos, bool retain) { ready_for_repeat = false; if(cfg.protocol_version == MQTT_PROTOCOL_V5 && cfg.have_topic_alias && first_publish == false){ return mosquitto_publish_v5(mosq, mid, NULL, payloadlen, payload, qos, retain, cfg.publish_props); }else{ first_publish = false; return mosquitto_publish_v5(mosq, mid, topic, payloadlen, payload, qos, retain, cfg.publish_props); } } void my_connect_callback(struct mosquitto *mosq, void *obj, int result, int flags, const mosquitto_property *properties) { int rc = MOSQ_ERR_SUCCESS; UNUSED(obj); UNUSED(flags); UNUSED(properties); connack_result = result; if(!result){ first_publish = true; switch(cfg.pub_mode){ case MSGMODE_CMD: case MSGMODE_FILE: case MSGMODE_STDIN_FILE: rc = my_publish(mosq, &mid_sent, cfg.topic, cfg.msglen, cfg.message, cfg.qos, cfg.retain); break; case MSGMODE_NULL: rc = my_publish(mosq, &mid_sent, cfg.topic, 0, NULL, cfg.qos, cfg.retain); break; case MSGMODE_STDIN_LINE: status = STATUS_CONNACK_RECVD; break; } if(rc){ switch(rc){ case MOSQ_ERR_INVAL: err_printf(&cfg, "Error: Invalid input. Does your topic contain '+' or '#'?\n"); break; case MOSQ_ERR_NOMEM: err_printf(&cfg, "Error: Out of memory when trying to publish message.\n"); break; case MOSQ_ERR_NO_CONN: err_printf(&cfg, "Error: Client not connected when trying to publish.\n"); break; case MOSQ_ERR_PROTOCOL: err_printf(&cfg, "Error: Protocol error when communicating with broker.\n"); break; case MOSQ_ERR_PAYLOAD_SIZE: err_printf(&cfg, "Error: Message payload is too large.\n"); break; case MOSQ_ERR_QOS_NOT_SUPPORTED: err_printf(&cfg, "Error: Message QoS not supported on broker, try a lower QoS.\n"); break; } mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); } }else{ if(result){ if(cfg.protocol_version == MQTT_PROTOCOL_V5){ if(result == MQTT_RC_UNSUPPORTED_PROTOCOL_VERSION){ err_printf(&cfg, "Connection error: %s. Try connecting to an MQTT v5 broker, or use MQTT v3.x mode.\n", mosquitto_reason_string(result)); }else{ err_printf(&cfg, "Connection error: %s\n", mosquitto_reason_string(result)); } }else{ err_printf(&cfg, "Connection error: %s\n", mosquitto_connack_string(result)); } /* let the loop know that this is an unrecoverable connection */ status = STATUS_NOHOPE; } } } void my_publish_callback(struct mosquitto *mosq, void *obj, int mid, int reason_code, const mosquitto_property *properties) { char *reason_string = NULL; UNUSED(obj); UNUSED(properties); last_mid_sent = mid; if(reason_code > 127){ err_printf(&cfg, "Warning: Publish %d failed: %s.\n", mid, mosquitto_reason_string(reason_code)); mosquitto_property_read_string(properties, MQTT_PROP_REASON_STRING, &reason_string, false); if(reason_string){ err_printf(&cfg, "%s\n", reason_string); free(reason_string); } } publish_count++; if(cfg.pub_mode == MSGMODE_STDIN_LINE){ if(mid == last_mid){ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); disconnect_sent = true; } }else if(publish_count < cfg.repeat_count){ ready_for_repeat = true; set_repeat_time(); }else if(disconnect_sent == false){ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); disconnect_sent = true; } } int pub_shared_init(void) { line_buf = malloc((size_t )line_buf_len); if(!line_buf){ err_printf(&cfg, "Error: Out of memory.\n"); return 1; } return 0; } static int pub_stdin_line_loop(struct mosquitto *mosq) { char *buf2; int buf_len_actual = 0; int pos; int rc = MOSQ_ERR_SUCCESS; int read_len; bool stdin_finished = false; rc = mosquitto_loop_start(mosq); if(rc != MOSQ_ERR_SUCCESS){ return rc; } stdin_finished = false; do{ if(status == STATUS_CONNECTING){ #ifdef WIN32 Sleep(100); #else struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; nanosleep(&ts, NULL); #endif } if(status == STATUS_NOHOPE){ return MOSQ_ERR_CONN_REFUSED; } if(status == STATUS_CONNACK_RECVD){ pos = 0; read_len = line_buf_len; while(status == STATUS_CONNACK_RECVD && fgets(&line_buf[pos], read_len, stdin)){ buf_len_actual = (int )strlen(line_buf); if(line_buf[buf_len_actual-1] == '\n'){ line_buf[buf_len_actual-1] = '\0'; rc = my_publish(mosq, &mid_sent, cfg.topic, buf_len_actual-1, line_buf, cfg.qos, cfg.retain); pos = 0; if(rc != MOSQ_ERR_SUCCESS && rc != MOSQ_ERR_NO_CONN){ return rc; } break; }else{ line_buf_len += 1024; pos += read_len-1; read_len = 1024; buf2 = realloc(line_buf, (size_t )line_buf_len); if(!buf2){ err_printf(&cfg, "Error: Out of memory.\n"); return MOSQ_ERR_NOMEM; } line_buf = buf2; } } if(pos != 0){ rc = my_publish(mosq, &mid_sent, cfg.topic, buf_len_actual, line_buf, cfg.qos, cfg.retain); if(rc){ if(cfg.qos>0){ return rc; } } } if(feof(stdin)){ if(mid_sent == -1){ /* Empty file */ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); disconnect_sent = true; status = STATUS_DISCONNECTING; }else{ last_mid = mid_sent; status = STATUS_WAITING; } stdin_finished = true; }else if(status == STATUS_DISCONNECTED){ /* Not end of stdin, so we've lost our connection and must * reconnect */ } } if(status == STATUS_WAITING){ if(last_mid_sent == last_mid && disconnect_sent == false){ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); disconnect_sent = true; } #ifdef WIN32 Sleep(100); #else struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 100000000; nanosleep(&ts, NULL); #endif } }while(stdin_finished == false); mosquitto_loop_stop(mosq, false); if(status == STATUS_DISCONNECTED){ return MOSQ_ERR_SUCCESS; }else{ return rc; } } static int pub_other_loop(struct mosquitto *mosq) { int rc; int loop_delay = 1000; if(cfg.repeat_count > 1 && (cfg.repeat_delay.tv_sec == 0 || cfg.repeat_delay.tv_usec != 0)){ loop_delay = (int )cfg.repeat_delay.tv_usec / 2000; } do{ rc = mosquitto_loop(mosq, loop_delay, 1); if(ready_for_repeat && check_repeat_time()){ rc = MOSQ_ERR_SUCCESS; switch(cfg.pub_mode){ case MSGMODE_CMD: case MSGMODE_FILE: case MSGMODE_STDIN_FILE: rc = my_publish(mosq, &mid_sent, cfg.topic, cfg.msglen, cfg.message, cfg.qos, cfg.retain); break; case MSGMODE_NULL: rc = my_publish(mosq, &mid_sent, cfg.topic, 0, NULL, cfg.qos, cfg.retain); break; } if(rc != MOSQ_ERR_SUCCESS && rc != MOSQ_ERR_NO_CONN){ err_printf(&cfg, "Error sending repeat publish: %s", mosquitto_strerror(rc)); } } }while(rc == MOSQ_ERR_SUCCESS && disconnect_sent == false); if(status == STATUS_DISCONNECTED){ return MOSQ_ERR_SUCCESS; }else{ return rc; } } int pub_shared_loop(struct mosquitto *mosq) { if(cfg.pub_mode == MSGMODE_STDIN_LINE){ return pub_stdin_line_loop(mosq); }else{ return pub_other_loop(mosq); } } void pub_shared_cleanup(void) { free(line_buf); } static void print_version(void) { int major, minor, revision; mosquitto_lib_version(&major, &minor, &revision); printf("mosquitto_pub version %s running on libmosquitto %d.%d.%d.\n", VERSION, major, minor, revision); } static void print_usage(void) { int major, minor, revision; mosquitto_lib_version(&major, &minor, &revision); printf("mosquitto_pub is a simple mqtt client that will publish a message on a single topic and exit.\n"); printf("mosquitto_pub version %s running on libmosquitto %d.%d.%d.\n\n", VERSION, major, minor, revision); printf("Usage: mosquitto_pub {[-h host] [--unix path] [-p port] [-u username] [-P password] [--ws] -t topic | -L URL}\n"); printf(" {-f file | -l | -n | -m message}\n"); printf(" [-c] [-k keepalive] [-q qos] [-r] [--repeat N] [--repeat-delay time] [-x session-expiry]\n"); #ifdef WITH_SRV printf(" [-A bind_address] [--nodelay] [-S]\n"); #else printf(" [-A bind_address] [--nodelay]\n"); #endif printf(" [-i id] [-I id_prefix]\n"); printf(" [-d] [--quiet]\n"); printf(" [-M max_inflight]\n"); printf(" [-u username [-P password]]\n"); printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n"); #ifdef WITH_TLS printf(" [--no-tls]\n"); printf(" [{--cafile file | --capath dir} [--cert file] [--key file]\n"); printf(" [--ciphers ciphers] [--insecure]\n"); printf(" [--tls-alpn protocol]\n"); printf(" [--tls-engine engine] [--keyform keyform] [--tls-engine-kpass-sha1]]\n"); printf(" [--tls-use-os-certs]\n"); #ifdef FINAL_WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif #endif #ifdef WITH_SOCKS printf(" [--proxy socks-url]\n"); #endif printf(" [--property command identifier value]\n"); printf(" [-D command identifier value]\n"); printf(" [-o options-file]\n"); printf(" mosquitto_pub --help\n\n"); printf(" -A : bind the outgoing socket to this host/ip address. Use to control which interface\n"); printf(" the client communicates over.\n"); printf(" -d : enable debug messages.\n"); printf(" -c : disable clean session/enable persistent client mode\n"); printf(" When this argument is used, the broker will be instructed not to clean existing sessions\n"); printf(" for the same client id when the client connects, and sessions will never expire when the\n"); printf(" client disconnects. MQTT v5 clients can change their session expiry interval with the -x\n"); printf(" argument.\n"); printf(" -D : Define MQTT v5 properties. See the documentation for more details.\n"); printf(" -f : send the contents of a file as the message.\n"); printf(" -h : mqtt host to connect to. Defaults to localhost.\n"); printf(" -i : id to use for this client. Defaults to mosquitto_pub_ appended with the process id.\n"); printf(" -I : define the client id as id_prefix appended with the process id. Useful for when the\n"); printf(" broker is using the clientid_prefixes option.\n"); printf(" -k : keep alive in seconds for this client. Defaults to 60.\n"); printf(" -L : specify user, password, hostname, port and topic as a URL in the form:\n"); printf(" mqtt(s)://[username[:password]@]host[:port]/topic\n"); printf(" ws(s)://[username[:password]@]host[:port]/topic\n"); printf(" -l : read messages from stdin, sending a separate message for each line.\n"); printf(" -m : message payload to send.\n"); printf(" -M : the maximum inflight messages for QoS 1/2..\n"); printf(" -n : send a null (zero length) message.\n"); printf(" -o : provide options in a file rather than on the command line.\n"); printf(" See the Options section of https://mosquitto.org/man/mosquitto_pub-1.html\n"); printf(" -p : network port to connect to. Defaults to 1883 for plain MQTT and 8883 for MQTT over TLS.\n"); printf(" -P : provide a password\n"); printf(" -q : quality of service level to use for all messages. Defaults to 0.\n"); printf(" -r : message should be retained.\n"); printf(" -s : read message from stdin, sending the entire input as a message.\n"); #ifdef WITH_SRV printf(" -S : use SRV lookups to determine which host to connect to.\n"); #endif printf(" -t : mqtt topic to publish to.\n"); printf(" -u : provide a username\n"); printf(" -V : specify the version of the MQTT protocol to use when connecting.\n"); printf(" Can be mqttv5, mqttv311 or mqttv31. Defaults to mqttv311.\n"); printf(" -x : Set the session-expiry-interval property on the CONNECT packet. Applies to MQTT v5\n"); printf(" clients only. Set to 0-4294967294 to specify the session will expire in that many\n"); printf(" seconds after the client disconnects, or use -1, 4294967295, or ∞ for a session\n"); printf(" that does not expire. Defaults to -1 if -c is also given, or 0 if -c not given.\n"); printf(" --help : display this message.\n"); printf(" --nodelay : disable Nagle's algorithm, to reduce socket sending latency at the possible\n"); printf(" expense of more packets being sent.\n"); printf(" --quiet : don't print error messages.\n"); printf(" --repeat : if publish mode is -f, -m, or -s, then repeat the publish N times.\n"); printf(" --repeat-delay : if using --repeat, wait time seconds between publishes. Defaults to 0.\n"); printf(" --unix : connect to a broker through a unix domain socket instead of a TCP socket,\n"); printf(" e.g. /tmp/mosquitto.sock\n"); printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n"); printf(" unexpected disconnection. If not given and will-topic is set, a zero\n"); printf(" length message will be sent.\n"); printf(" --will-qos : QoS level for the client Will.\n"); printf(" --will-retain : if given, make the client Will retained.\n"); printf(" --will-topic : the topic on which to publish the client Will.\n"); printf(" --ws : connect using WebSockets.\n"); #ifdef WITH_TLS printf(" --cafile : path to a file containing trusted CA certificates to enable encrypted\n"); printf(" communication.\n"); printf(" --capath : path to a directory containing trusted CA certificates to enable encrypted\n"); printf(" communication.\n"); printf(" --cert : client certificate for authentication, if required by server.\n"); printf(" --key : client private key for authentication, if required by server.\n"); printf(" --keyform : keyfile type, can be either \"pem\" or \"engine\".\n"); printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n"); printf(" --tls-version : TLS protocol version, can be one of tlsv1.3 or tlsv1.2.\n"); printf(" Defaults to tlsv1.2 if available.\n"); printf(" --insecure : do not verify the the server certificate. Using this option means that\n"); printf(" you cannot be sure that the remote host is the server you wish to connect\n"); printf(" to and so is insecure.\n"); printf(" Do not use this option in a production environment.\n"); printf(" --tls-engine : If set, enables the use of a TLS engine device.\n"); printf(" --tls-engine-kpass-sha1 : SHA1 of the key password to be used with the selected SSL engine.\n"); printf(" --tls-use-os-certs : Load and trust OS provided CA certificates.\n"); # ifdef FINAL_WITH_TLS_PSK printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); # endif #endif #ifdef WITH_SOCKS printf(" --proxy : SOCKS5 proxy URL of the form:\n"); printf(" socks5h://[username[:password]@]hostname[:port]\n"); printf(" Only \"none\" and \"username\" authentication is supported.\n"); #endif printf("\nSee https://mosquitto.org/ for more information.\n\n"); } int main(int argc, char *argv[]) { struct mosquitto *mosq = NULL; int rc; mosquitto_lib_init(); if(pub_shared_init()){ return 1; } rc = client_config_load(&cfg, CLIENT_PUB, argc, argv); if(rc){ if(rc == 2){ /* --help */ print_usage(); }else if(rc == 3){ print_version(); }else{ fprintf(stderr, "\nUse 'mosquitto_pub --help' to see usage.\n"); } goto cleanup; } #ifndef WITH_THREADING if(cfg.pub_mode == MSGMODE_STDIN_LINE){ fprintf(stderr, "Error: '-l' mode not available, threading support has not been compiled in.\n"); goto cleanup; } #endif if(cfg.pub_mode == MSGMODE_STDIN_FILE){ if(load_stdin()){ err_printf(&cfg, "Error loading input from stdin.\n"); goto cleanup; } }else if(cfg.file_input){ if(load_file(cfg.file_input)){ err_printf(&cfg, "Error loading input file \"%s\".\n", cfg.file_input); goto cleanup; } } if(!cfg.topic || cfg.pub_mode == MSGMODE_NONE){ fprintf(stderr, "Error: Both topic and message must be supplied.\n"); print_usage(); goto cleanup; } if(clientid_generate(&cfg)){ goto cleanup; } mosq = mosquitto_new(cfg.id, cfg.clean_session, NULL); if(!mosq){ switch(errno){ case ENOMEM: err_printf(&cfg, "Error: Out of memory.\n"); break; case EINVAL: err_printf(&cfg, "Error: Invalid id.\n"); break; } goto cleanup; } if(cfg.debug){ mosquitto_log_callback_set(mosq, my_log_callback); } mosquitto_connect_v5_callback_set(mosq, my_connect_callback); mosquitto_disconnect_v5_callback_set(mosq, my_disconnect_callback); mosquitto_publish_v5_callback_set(mosq, my_publish_callback); if(client_opts_set(mosq, &cfg)){ goto cleanup; } rc = client_connect(mosq, &cfg); if(rc){ goto cleanup; } rc = pub_shared_loop(mosq); if(cfg.message && cfg.pub_mode == MSGMODE_FILE){ free(cfg.message); cfg.message = NULL; } mosquitto_destroy(mosq); mosquitto_lib_cleanup(); client_config_cleanup(&cfg); pub_shared_cleanup(); if(rc){ err_printf(&cfg, "Error: %s\n", mosquitto_strerror(rc)); } if(connack_result){ return connack_result; }else{ return rc; } cleanup: mosquitto_destroy(mosq); mosquitto_lib_cleanup(); client_config_cleanup(&cfg); pub_shared_cleanup(); return 1; } eclipse-mosquitto-mosquitto-691eab3/client/pub_shared.c000066400000000000000000000050121514232433600234150ustar00rootroot00000000000000/* Copyright (c) 2009-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #ifndef WIN32 #include #else #include #include #define snprintf sprintf_s #endif #include #include "client_shared.h" #include "pub_shared.h" /* Global variables for use in callbacks. See sub_client.c for an example of * using a struct to hold variables for use in callbacks. */ int mid_sent = -1; struct mosq_config cfg; void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str) { UNUSED(mosq); UNUSED(obj); UNUSED(level); printf("%s\n", str); } int load_stdin(void) { size_t pos = 0, rlen; char buf[1024]; char *aux_message = NULL; cfg.pub_mode = MSGMODE_STDIN_FILE; while(!feof(stdin)){ rlen = fread(buf, 1, 1024, stdin); aux_message = mosquitto_realloc(cfg.message, pos+rlen); if(!aux_message){ err_printf(&cfg, "Error: Out of memory.\n"); mosquitto_FREE(cfg.message); return 1; }else{ cfg.message = aux_message; } memcpy(&(cfg.message[pos]), buf, rlen); pos += rlen; } if(pos > MQTT_MAX_PAYLOAD){ err_printf(&cfg, "Error: Message length must be less than %u bytes.\n\n", MQTT_MAX_PAYLOAD); mosquitto_FREE(cfg.message); return 1; } cfg.msglen = (int )pos; if(!cfg.msglen){ err_printf(&cfg, "Error: Zero length input.\n"); return 1; } return 0; } int load_file(const char *filename) { size_t buflen; char *buf; cfg.pub_mode = MSGMODE_FILE; int rc = mosquitto_read_file(filename, false, &buf, &buflen); if(rc){ err_printf(&cfg, "Error: Unable to read file \"%s\": %s.\n", filename, mosquitto_strerror(rc)); return 1; } if(buflen > MQTT_MAX_PAYLOAD){ err_printf(&cfg, "Error: File must be less than %u bytes.\n\n", MQTT_MAX_PAYLOAD); mosquitto_FREE(buf); return 1; }else if(buflen == 0){ cfg.message = NULL; cfg.msglen = 0; return 0; } cfg.msglen = (int )buflen; cfg.message = buf; return 0; } eclipse-mosquitto-mosquitto-691eab3/client/pub_shared.h000066400000000000000000000030631514232433600234260ustar00rootroot00000000000000/* Copyright (c) 2009-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef PUB_SHARED_H #define PUB_SHARED_H #define STATUS_CONNECTING 0 #define STATUS_CONNACK_RECVD 1 #define STATUS_WAITING 2 #define STATUS_DISCONNECTING 3 #define STATUS_DISCONNECTED 4 #define STATUS_NOHOPE 5 extern int mid_sent; extern struct mosq_config cfg; void my_connect_callback(struct mosquitto *mosq, void *obj, int result, int flags, const mosquitto_property *properties); void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc, const mosquitto_property *properties); void my_publish_callback(struct mosquitto *mosq, void *obj, int mid, int reason_code, const mosquitto_property *properties); void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str); int load_stdin(void); int load_file(const char *filename); int my_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen, void *payload, int qos, bool retain); int pub_shared_init(void); int pub_shared_loop(struct mosquitto *mosq); void pub_shared_cleanup(void); #endif eclipse-mosquitto-mosquitto-691eab3/client/pub_test_properties000077500000000000000000000015421514232433600251700ustar00rootroot00000000000000LD_LIBRARY_PATH=../lib ./mosquitto_pub \ \ -t asdf -V mqttv5 -m '{"key":"value"}' \ \ -D connect authentication-data password \ -D connect authentication-method something \ -D connect maximum-packet-size 0191 \ -D connect receive-maximum 1000 \ -D connect request-problem-information 1 \ -D connect request-response-information 1 \ -D connect session-expiry-interval 39 \ -D connect topic-alias-maximum 123 \ -D connect user-property connect up \ \ -D publish content-type application/json \ -D publish correlation-data some-data \ -D publish message-expiry-interval 59 \ -D publish payload-format-indicator 1 \ -D publish response-topic /dev/null \ -D publish topic-alias 4 \ -D publish user-property publish up \ \ -D disconnect reason-string "reason" \ -D disconnect session-expiry-interval 40 \ -D disconnect user-property disconnect up eclipse-mosquitto-mosquitto-691eab3/client/rr_client.c000066400000000000000000000360351514232433600232730ustar00rootroot00000000000000/* Copyright (c) 2009-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #ifndef WIN32 #include #include #else #include #include #define snprintf sprintf_s #endif #include #include "client_shared.h" #include "pub_shared.h" #include "sub_client_output.h" enum rr__state { rr_s_new, rr_s_connected, rr_s_subscribed, rr_s_ready_to_publish, rr_s_wait_for_response, rr_s_disconnect, }; static enum rr__state client_state = rr_s_new; bool process_messages = true; int msg_count = 0; struct mosquitto *g_mosq = NULL; static bool timed_out = false; static int connack_result = 0; static struct timespec publish_send_time; static struct timespec publish_recv_time; #ifndef WIN32 static void my_signal_handler(int signum) { if(signum == SIGALRM){ process_messages = false; mosquitto_disconnect_v5(g_mosq, MQTT_RC_DISCONNECT_WITH_WILL_MSG, cfg.disconnect_props); timed_out = true; } } #endif int my_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen, void *payload, int qos, bool retain) { mosquitto_time_ns(&publish_send_time.tv_sec, &publish_send_time.tv_nsec); if(cfg.protocol_version < MQTT_PROTOCOL_V5){ return mosquitto_publish_v5(mosq, mid, topic, payloadlen, payload, qos, retain, NULL); }else{ return mosquitto_publish_v5(mosq, mid, topic, payloadlen, payload, qos, retain, cfg.publish_props); } } static void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message, const mosquitto_property *properties) { UNUSED(mosq); UNUSED(obj); UNUSED(properties); if(process_messages == false){ return; } if(message->retain && cfg.no_retain){ return; } mosquitto_time_ns(&publish_recv_time.tv_sec, &publish_recv_time.tv_nsec); print_message(&cfg, message, properties); switch(cfg.pub_mode){ case MSGMODE_CMD: case MSGMODE_FILE: case MSGMODE_STDIN_FILE: case MSGMODE_NULL: client_state = rr_s_disconnect; break; case MSGMODE_STDIN_LINE: client_state = rr_s_ready_to_publish; break; } } void my_connect_callback(struct mosquitto *mosq, void *obj, int result, int flags, const mosquitto_property *properties) { UNUSED(obj); UNUSED(flags); UNUSED(properties); connack_result = result; if(!result){ client_state = rr_s_connected; mosquitto_subscribe_v5(mosq, NULL, cfg.response_topic, cfg.qos, cfg.sub_opts, cfg.subscribe_props); }else{ client_state = rr_s_disconnect; if(result){ err_printf(&cfg, "Connection error: %s\n", mosquitto_reason_string(result)); } mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); } } static void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos) { UNUSED(obj); UNUSED(mid); UNUSED(qos_count); if(granted_qos[0] < 128){ client_state = rr_s_ready_to_publish; }else{ client_state = rr_s_disconnect; err_printf(&cfg, "%s\n", mosquitto_reason_string(granted_qos[0])); mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); } } static void print_version(void) { int major, minor, revision; mosquitto_lib_version(&major, &minor, &revision); printf("mosquitto_rr version %s running on libmosquitto %d.%d.%d.\n", VERSION, major, minor, revision); } static void print_usage(void) { int major, minor, revision; mosquitto_lib_version(&major, &minor, &revision); printf("mosquitto_rr is an mqtt client that can be used to publish a request message and wait for a response.\n"); printf(" Defaults to MQTT v5, where the Request-Response feature will be used, but v3.1.1 can also be used\n"); printf(" with v3.1.1 brokers.\n"); printf("mosquitto_rr version %s running on libmosquitto %d.%d.%d.\n\n", VERSION, major, minor, revision); printf("Usage: mosquitto_rr {[-h host] [--unix path] [-p port] [-u username] [-P password] -t topic | -L URL} -e response-topic\n"); printf(" {-f file | -l | -n | -m message}\n"); printf(" [-c] [-k keepalive] [-q qos] [-R] [-x session-expiry-interval]\n"); printf(" [-F format]\n"); #ifndef WIN32 printf(" [-W timeout_secs]\n"); #endif #ifdef WITH_SRV printf(" [-A bind_address] [--nodelay] [-S]\n"); #else printf(" [-A bind_address] [--nodelay]\n"); #endif printf(" [-i id] [-I id_prefix]\n"); printf(" [-d] [-N] [--quiet] [-v]\n"); printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n"); #ifdef WITH_TLS printf(" [--no-tls]\n"); printf(" [{--cafile file | --capath dir} [--cert file] [--key file]\n"); printf(" [--ciphers ciphers] [--insecure]\n"); printf(" [--tls-alpn protocol]\n"); printf(" [--tls-engine engine] [--keyform keyform] [--tls-engine-kpass-sha1]]\n"); printf(" [--tls-use-os-certs]\n"); #ifdef FINAL_WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif #endif #ifdef WITH_SOCKS printf(" [--proxy socks-url]\n"); #endif printf(" [-D command identifier value]\n"); printf(" [-o options-file]\n"); printf(" mosquitto_rr --help\n\n"); printf(" -A : bind the outgoing socket to this host/ip address. Use to control which interface\n"); printf(" the client communicates over.\n"); printf(" -c : disable clean session/enable persistent client mode\n"); printf(" When this argument is used, the broker will be instructed not to clean existing sessions\n"); printf(" for the same client id when the client connects, and sessions will never expire when the\n"); printf(" client disconnects. MQTT v5 clients can change their session expiry interval with the -x\n"); printf(" argument.\n"); printf(" -d : enable debug messages.\n"); printf(" -D : Define MQTT v5 properties. See the documentation for more details.\n"); printf(" -e : Response topic. The client will subscribe to this topic to wait for a response.\n"); printf(" -F : output format.\n"); printf(" -h : mqtt host to connect to. Defaults to localhost.\n"); printf(" -i : id to use for this client. Defaults to mosquitto_rr_ appended with the process id.\n"); printf(" -k : keep alive in seconds for this client. Defaults to 60.\n"); printf(" -L : specify user, password, hostname, port and topic as a URL in the form:\n"); printf(" mqtt(s)://[username[:password]@]host[:port]/topic\n"); printf(" ws(s)://[username[:password]@]host[:port]/topic\n"); printf(" -N : do not add an end of line character when printing the payload.\n"); printf(" -o : provide options in a file rather than on the command line.\n"); printf(" See the Options section of https://mosquitto.org/man/mosquitto_pub-1.html\n"); printf(" -p : network port to connect to. Defaults to 1883 for plain MQTT and 8883 for MQTT over TLS.\n"); printf(" -P : provide a password\n"); printf(" -q : quality of service level to use for communications. Defaults to 0.\n"); printf(" -R : do not print stale messages (those with retain set).\n"); #ifdef WITH_SRV printf(" -S : use SRV lookups to determine which host to connect to.\n"); #endif printf(" -t : topic where the request message will be sent.\n"); printf(" -u : provide a username\n"); printf(" -v : print received messages verbosely.\n"); printf(" -V : specify the version of the MQTT protocol to use when connecting.\n"); printf(" Defaults to 5.\n"); #ifndef WIN32 printf(" -W : Specifies a timeout in seconds how long to wait for a response.\n"); #endif printf(" -x : Set the session-expiry-interval property on the CONNECT packet. Applies to MQTT v5\n"); printf(" clients only. Set to 0-4294967294 to specify the session will expire in that many\n"); printf(" seconds after the client disconnects, or use -1, 4294967295, or ∞ for a session\n"); printf(" that does not expire. Defaults to -1 if -c is also given, or 0 if -c not given.\n"); printf(" --help : display this message.\n"); printf(" --nodelay : disable Nagle's algorithm, to reduce socket sending latency at the possible\n"); printf(" expense of more packets being sent.\n"); printf(" --pretty : print formatted output rather than minimised output when using the\n"); printf(" JSON output format option.\n"); printf(" --quiet : don't print error messages.\n"); printf(" --unix : connect to a broker through a unix domain socket instead of a TCP socket,\n"); printf(" e.g. /tmp/mosquitto.sock\n"); printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n"); printf(" unexpected disconnection. If not given and will-topic is set, a zero\n"); printf(" length message will be sent.\n"); printf(" --will-qos : QoS level for the client Will.\n"); printf(" --will-retain : if given, make the client Will retained.\n"); printf(" --will-topic : the topic on which to publish the client Will.\n"); printf(" --ws : connect using WebSockets.\n"); #ifdef WITH_TLS printf(" --cafile : path to a file containing trusted CA certificates to enable encrypted\n"); printf(" certificate based communication.\n"); printf(" --capath : path to a directory containing trusted CA certificates to enable encrypted\n"); printf(" communication.\n"); printf(" --cert : client certificate for authentication, if required by server.\n"); printf(" --key : client private key for authentication, if required by server.\n"); printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n"); printf(" --tls-use-os-certs : Load and trust OS provided CA certificates.\n"); printf(" --tls-version : TLS protocol version, can be one of tlsv1.3 or tlsv1.2.\n"); printf(" Defaults to tlsv1.2 if available.\n"); printf(" --insecure : do not verify the the server certificate. Using this option means that\n"); printf(" you cannot be sure that the remote host is the server you wish to connect\n"); printf(" to and so is insecure.\n"); printf(" Do not use this option in a production environment.\n"); #ifdef WITH_TLS_PSK printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); #endif #endif #ifdef WITH_SOCKS printf(" --proxy : SOCKS5 proxy URL of the form:\n"); printf(" socks5h://[username[:password]@]hostname[:port]\n"); printf(" Only \"none\" and \"username\" authentication is supported.\n"); #endif printf("\nSee https://mosquitto.org/ for more information.\n\n"); } static void report_latency(void) { if(cfg.measure_latency){ time_t s = publish_recv_time.tv_sec - publish_send_time.tv_sec; long ns = publish_recv_time.tv_nsec - publish_send_time.tv_nsec; if(ns < 0){ s--; ns += 1000000000; } if(s > 0){ printf("Latency: %ld.%09ld\n", s, ns); }else{ if(ns < 1000){ printf("Latency: %ldns\n", ns); }else if(ns < 1000000){ printf("Latency: %fµs\n", ((double)ns)/1000.0); }else{ printf("Latency: %fms\n", ((double)ns)/1000000.0); } } } } int main(int argc, char *argv[]) { int rc; #ifndef WIN32 struct sigaction sigact; #endif mosquitto_lib_init(); rc = client_config_load(&cfg, CLIENT_RR, argc, argv); if(rc){ if(rc == 2){ /* --help */ print_usage(); }else if(rc == 3){ /* --version */ print_version(); }else{ fprintf(stderr, "\nUse 'mosquitto_rr --help' to see usage.\n"); } goto cleanup; } if(cfg.pub_mode == MSGMODE_STDIN_FILE){ if(load_stdin()){ err_printf(&cfg, "Error loading input from stdin.\n"); goto cleanup; } }else if(cfg.file_input){ if(load_file(cfg.file_input)){ err_printf(&cfg, "Error loading input file \"%s\".\n", cfg.file_input); goto cleanup; } } if(!cfg.topic || cfg.pub_mode == MSGMODE_NONE || !cfg.response_topic){ fprintf(stderr, "Error: All of topic, message, and response topic must be supplied.\n"); fprintf(stderr, "\nUse 'mosquitto_rr --help' to see usage.\n"); goto cleanup; } rc = mosquitto_property_add_string(&cfg.publish_props, MQTT_PROP_RESPONSE_TOPIC, cfg.response_topic); if(rc){ fprintf(stderr, "Error adding property RESPONSE_TOPIC.\n"); goto cleanup; } rc = mosquitto_property_check_all(CMD_PUBLISH, cfg.publish_props); if(rc){ err_printf(&cfg, "Error in PUBLISH properties: Duplicate response topic.\n"); goto cleanup; } output_init(&cfg); if(clientid_generate(&cfg)){ goto cleanup; } g_mosq = mosquitto_new(cfg.id, cfg.clean_session, &cfg); if(!g_mosq){ switch(errno){ case ENOMEM: err_printf(&cfg, "Error: Out of memory.\n"); break; case EINVAL: err_printf(&cfg, "Error: Invalid id and/or clean_session.\n"); break; } goto cleanup; } if(client_opts_set(g_mosq, &cfg)){ goto cleanup; } if(cfg.debug){ mosquitto_log_callback_set(g_mosq, my_log_callback); } mosquitto_connect_v5_callback_set(g_mosq, my_connect_callback); mosquitto_subscribe_callback_set(g_mosq, my_subscribe_callback); mosquitto_message_v5_callback_set(g_mosq, my_message_callback); rc = client_connect(g_mosq, &cfg); if(rc){ goto cleanup; } #ifndef WIN32 sigact.sa_handler = my_signal_handler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; if(sigaction(SIGALRM, &sigact, NULL) == -1){ perror("sigaction"); goto cleanup; } if(cfg.timeout){ alarm(cfg.timeout); } #endif do{ rc = mosquitto_loop(g_mosq, -1, 1); if(client_state == rr_s_ready_to_publish){ client_state = rr_s_wait_for_response; switch(cfg.pub_mode){ case MSGMODE_CMD: case MSGMODE_FILE: case MSGMODE_STDIN_FILE: rc = my_publish(g_mosq, &mid_sent, cfg.topic, cfg.msglen, cfg.message, cfg.qos, cfg.retain); break; case MSGMODE_NULL: rc = my_publish(g_mosq, &mid_sent, cfg.topic, 0, NULL, cfg.qos, cfg.retain); break; case MSGMODE_STDIN_LINE: /* FIXME */ break; } } }while(rc == MOSQ_ERR_SUCCESS && client_state != rr_s_disconnect); report_latency(); mosquitto_destroy(g_mosq); mosquitto_lib_cleanup(); if(cfg.msg_count>0 && rc == MOSQ_ERR_NO_CONN){ rc = 0; } client_config_cleanup(&cfg); if(timed_out){ err_printf(&cfg, "Timed out\n"); return MOSQ_ERR_TIMEOUT; }else if(rc){ err_printf(&cfg, "Error: %s\n", mosquitto_strerror(rc)); } if(connack_result){ return connack_result; }else{ return rc; } cleanup: mosquitto_lib_cleanup(); client_config_cleanup(&cfg); return 1; } eclipse-mosquitto-mosquitto-691eab3/client/sub_client.c000066400000000000000000000416571514232433600234470ustar00rootroot00000000000000/* Copyright (c) 2009-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include #ifndef WIN32 #include #include #else #include #include #define snprintf sprintf_s #endif #include #include "client_shared.h" #include "sub_client_output.h" struct mosq_config cfg; static bool run = true; bool process_messages = true; int msg_count = 0; int message_rate_msg_count = 0; struct mosquitto *g_mosq = NULL; int last_mid = 0; static bool timed_out = false; static int connack_result = 0; bool connack_received = false; #ifdef WIN32 static HANDLE timeout_h = NULL; #endif #ifdef WIN32 void CALLBACK timeout_cb(PVOID lpParameter, BOOLEAN TimerOrWaitFired) { UNUSED(lpParameter); UNUSED(TimerOrWaitFired); if(connack_received){ process_messages = false; mosquitto_disconnect_v5(g_mosq, MQTT_RC_DISCONNECT_WITH_WILL_MSG, cfg.disconnect_props); }else{ exit(-1); } timed_out = true; (void)DeleteTimerQueueTimer(NULL, timeout_h, NULL); timeout_h = NULL; } #else static void my_signal_handler(int signum) { if(signum == SIGALRM || signum == SIGTERM || signum == SIGINT){ if(connack_received){ process_messages = false; mosquitto_disconnect_v5(g_mosq, MQTT_RC_DISCONNECT_WITH_WILL_MSG, cfg.disconnect_props); }else{ exit(-1); } run = false; } if(signum == SIGALRM){ timed_out = true; } } #endif static void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message, const mosquitto_property *properties) { int i; bool res; UNUSED(obj); UNUSED(properties); message_rate_msg_count++; if(process_messages == false){ return; } if(cfg.retained_only && !message->retain && process_messages){ process_messages = false; if(last_mid == 0){ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); } return; } if(message->retain && cfg.no_retain){ return; } if(cfg.filter_outs){ for(i=0; itopic, &res); if(res){ return; } } } if(cfg.remove_retained && message->retain){ mosquitto_publish(mosq, &last_mid, message->topic, 0, NULL, 1, true); } print_message(&cfg, message, properties); if(ferror(stdout)){ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); } if(cfg.msg_count>0){ msg_count++; if(cfg.msg_count == msg_count){ process_messages = false; if(last_mid == 0){ mosquitto_disconnect_v5(mosq, 0, cfg.disconnect_props); } } } } static void my_connect_callback(struct mosquitto *mosq, void *obj, int result, int flags, const mosquitto_property *properties) { int i; UNUSED(obj); UNUSED(flags); UNUSED(properties); connack_received = true; connack_result = result; if(!result){ mosquitto_subscribe_multiple(mosq, NULL, cfg.topic_count, cfg.topics, cfg.qos, cfg.sub_opts, cfg.subscribe_props); for(i=0; i0 && rc == MOSQ_ERR_NO_CONN){ rc = 0; } client_config_cleanup(&cfg); if(timed_out){ err_printf(&cfg, "Timed out\n"); return MOSQ_ERR_TIMEOUT; }else if(rc){ err_printf(&cfg, "Error: %s\n", mosquitto_strerror(rc)); } if(connack_result){ return connack_result; }else{ return rc; } cleanup: mosquitto_destroy(g_mosq); mosquitto_lib_cleanup(); client_config_cleanup(&cfg); return 1; } eclipse-mosquitto-mosquitto-691eab3/client/sub_client_output.c000066400000000000000000000502011514232433600250500ustar00rootroot00000000000000/* Copyright (c) 2009-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #ifdef WIN32 /* For rand_s on Windows */ # define _CRT_RAND_S # include # include #endif #include #include #include #include #include #include #include #ifndef WIN32 #include #else #include #include #define snprintf sprintf_s #endif #undef uthash_malloc #undef uthash_free #include #ifdef __APPLE__ # include #endif #include #include "client_shared.h" #include "sub_client_output.h" extern struct mosq_config cfg; struct fieldoptions { int field_width; int precision; char hexsepchar; char align; char pad; }; struct watch_topic { UT_hash_handle hh; char *topic; int line; }; static int watch_max = 2; static struct watch_topic *watch_items = NULL; static int get_time(struct tm **ti, long *ns) { #ifdef WIN32 SYSTEMTIME st; #elif defined(__APPLE__) struct timeval tv; #else struct timespec ts; #endif time_t s; #ifdef WIN32 s = time(NULL); GetLocalTime(&st); *ns = st.wMilliseconds*1000000L; #elif defined(__APPLE__) gettimeofday(&tv, NULL); s = tv.tv_sec; *ns = tv.tv_usec*1000; #else if(clock_gettime(CLOCK_REALTIME, &ts) != 0){ err_printf(&cfg, "Error obtaining system time.\n"); return 1; } s = ts.tv_sec; *ns = ts.tv_nsec; #endif *ti = localtime(&s); if(!(*ti)){ err_printf(&cfg, "Error obtaining system time.\n"); return 1; } return 0; } static const signed char nibble_to_hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; static void hexsep(int xpos, int precision, char sepchar) { if(precision > 0 && xpos%precision == (precision-1)){ putchar(sepchar); } } static void write_payload(const unsigned char *payload, int payloadlen, int hex, struct fieldoptions *fopts) { int i; int padlen; if(fopts->field_width > 0){ if(payloadlen > fopts->field_width){ payloadlen = fopts->field_width; } if(hex > 0){ padlen = fopts->field_width - payloadlen*2; }else{ padlen = fopts->field_width - payloadlen; } }else{ padlen = fopts->field_width - payloadlen; } int xpos = 0; if(fopts->align != '-'){ for(i=0; ipad); if(hex > 0){ hexsep(xpos, fopts->precision, fopts->hexsepchar); } xpos++; } } if(hex == 0){ (void)fwrite(payload, 1, (size_t )payloadlen, stdout); }else{ signed char casemod = (hex == 1?0x20:0x00); for(i=0; i> 4)] | casemod); hexsep(xpos, fopts->precision, fopts->hexsepchar); xpos += 1; putchar(nibble_to_hex[(payload[i] & 0x0F)] | casemod); if(i < payloadlen-1){ hexsep(xpos, fopts->precision, fopts->hexsepchar); xpos += 1; } } } if(fopts->align == '-'){ printf("%*s", padlen, ""); } } static int json_print_properties(cJSON *root, const mosquitto_property *properties) { int identifier; uint8_t i8value = 0; uint16_t i16value = 0; uint32_t i32value = 0; char *strname = NULL, *strvalue = NULL; char *binvalue = NULL; cJSON *tmp, *prop_json, *user_props = NULL, *user_json; const mosquitto_property *prop = NULL; prop_json = cJSON_CreateObject(); if(prop_json == NULL){ cJSON_Delete(prop_json); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(root, "properties", prop_json); for(prop=properties; prop != NULL; prop = mosquitto_property_next(prop)){ tmp = NULL; identifier = mosquitto_property_identifier(prop); switch(identifier){ case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR: mosquitto_property_read_byte(prop, MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, &i8value, false); tmp = cJSON_CreateNumber(i8value); break; case MQTT_PROP_MESSAGE_EXPIRY_INTERVAL: mosquitto_property_read_int32(prop, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, &i32value, false); tmp = cJSON_CreateNumber(i32value); break; case MQTT_PROP_CONTENT_TYPE: case MQTT_PROP_RESPONSE_TOPIC: mosquitto_property_read_string(prop, identifier, &strvalue, false); if(strvalue == NULL){ return MOSQ_ERR_NOMEM; } tmp = cJSON_CreateString(strvalue); free(strvalue); strvalue = NULL; break; case MQTT_PROP_CORRELATION_DATA: mosquitto_property_read_binary(prop, MQTT_PROP_CORRELATION_DATA, (void **)&binvalue, &i16value, false); if(binvalue == NULL){ return MOSQ_ERR_NOMEM; } tmp = cJSON_CreateString(binvalue); free(binvalue); binvalue = NULL; break; case MQTT_PROP_SUBSCRIPTION_IDENTIFIER: mosquitto_property_read_varint(prop, MQTT_PROP_SUBSCRIPTION_IDENTIFIER, &i32value, false); tmp = cJSON_CreateNumber(i32value); break; case MQTT_PROP_TOPIC_ALIAS: mosquitto_property_read_int16(prop, MQTT_PROP_TOPIC_ALIAS, &i16value, false); tmp = cJSON_CreateNumber(i16value); break; case MQTT_PROP_USER_PROPERTY: if(user_props == NULL){ user_props = cJSON_CreateArray(); if(user_props == NULL){ return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(prop_json, "user-properties", user_props); } user_json = cJSON_CreateObject(); if(user_json == NULL){ return MOSQ_ERR_NOMEM; } cJSON_AddItemToArray(user_props, user_json); mosquitto_property_read_string_pair(prop, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, false); if(strname == NULL || strvalue == NULL){ free(strname); free(strvalue); return MOSQ_ERR_NOMEM; } tmp = cJSON_CreateString(strvalue); free(strvalue); if(tmp == NULL){ free(strname); return MOSQ_ERR_NOMEM; } cJSON_AddItemToObject(user_json, strname, tmp); free(strname); strname = NULL; strvalue = NULL; tmp = NULL; /* Don't add this to prop_json below */ break; } if(tmp != NULL){ cJSON_AddItemToObject(prop_json, mosquitto_property_identifier_to_string(identifier), tmp); } } return MOSQ_ERR_SUCCESS; } static void format_time_8601(const struct tm *ti, int ns, char *buf, size_t len) { char c; strftime(buf, len, "%Y-%m-%dT%H:%M:%S.000000%z", ti); c = buf[strlen("2020-05-06T21:48:00.000000")]; snprintf(&buf[strlen("2020-05-06T21:48:00.")], 9, "%06d", ns/1000); buf[strlen("2020-05-06T21:48:00.000000")] = c; } static int json_print(const struct mosquitto_message *message, const mosquitto_property *properties, const struct tm *ti, int ns, bool escaped, bool pretty) { char buf[100]; cJSON *root; cJSON *tmp; char *json_str; const char *return_parse_end; root = cJSON_CreateObject(); if(root == NULL){ return MOSQ_ERR_NOMEM; } format_time_8601(ti, ns, buf, sizeof(buf)); if(cJSON_AddStringToObject(root, "tst", buf) == NULL || cJSON_AddStringToObject(root, "topic", message->topic) == NULL || cJSON_AddNumberToObject(root, "qos", message->qos) == NULL || cJSON_AddBoolToObject(root, "retain", message->retain) == NULL || cJSON_AddNumberToObject(root, "payloadlen", message->payloadlen) == NULL || (message->qos > 0 && cJSON_AddNumberToObject(root, "mid", message->mid) == NULL) || (properties && json_print_properties(root, properties)) ){ cJSON_Delete(root); return MOSQ_ERR_NOMEM; } /* Payload */ if(escaped){ if(message->payload){ tmp = cJSON_AddStringToObject(root, "payload", message->payload); }else{ tmp = cJSON_AddNullToObject(root, "payload"); } if(tmp == NULL){ cJSON_Delete(root); return MOSQ_ERR_NOMEM; } }else{ return_parse_end = NULL; if(message->payload){ tmp = cJSON_ParseWithOpts(message->payload, &return_parse_end, true); if(tmp == NULL || return_parse_end != (char *)message->payload + message->payloadlen){ cJSON_Delete(root); return MOSQ_ERR_INVAL; } }else{ tmp = cJSON_CreateNull(); if(tmp == NULL){ cJSON_Delete(root); return MOSQ_ERR_INVAL; } } cJSON_AddItemToObject(root, "payload", tmp); } if(pretty){ json_str = cJSON_Print(root); }else{ json_str = cJSON_PrintUnformatted(root); } cJSON_Delete(root); if(json_str == NULL){ return MOSQ_ERR_NOMEM; } fputs(json_str, stdout); free(json_str); return MOSQ_ERR_SUCCESS; } static void formatted_print_blank(struct fieldoptions *fopts) { int i; for(i=0; ifield_width; i++){ putchar(fopts->pad); } } #ifdef __STDC_IEC_559__ static int formatted_print_float(const unsigned char *payload, int payloadlen, char format, struct fieldoptions *fopts) { float float_value; double value = 0.0; if(format == 'f'){ if(sizeof(float_value) != payloadlen){ return -1; } memcpy(&float_value, payload, sizeof(float_value)); value = float_value; }else if(format == 'd'){ if(sizeof(value) != payloadlen){ return -1; } memcpy(&value, payload, sizeof(value)); } if(fopts->field_width == 0){ printf("%.*f", fopts->precision, value); }else{ if(fopts->align == '-'){ printf("%-*.*f", fopts->field_width, fopts->precision, value); }else{ if(fopts->pad == '0'){ printf("%0*.*f", fopts->field_width, fopts->precision, value); }else{ printf("%*.*f", fopts->field_width, fopts->precision, value); } } } return 0; } #endif static void formatted_print_int(int value, struct fieldoptions *fopts) { if(fopts->field_width == 0){ printf("%d", value); }else{ if(fopts->align == '-'){ printf("%-*d", fopts->field_width, value); }else{ if(fopts->pad == '0'){ printf("%0*d", fopts->field_width, value); }else{ printf("%*d", fopts->field_width, value); } } } } static void formatted_print_str(const char *value, struct fieldoptions *fopts) { if(fopts->field_width == 0 && fopts->precision == -1){ fputs(value, stdout); }else{ if(fopts->precision == -1){ if(fopts->align == '-'){ printf("%-*s", fopts->field_width, value); }else{ printf("%*s", fopts->field_width, value); } }else if(fopts->field_width == 0){ if(fopts->align == '-'){ printf("%-.*s", fopts->precision, value); }else{ printf("%.*s", fopts->precision, value); } }else{ if(fopts->align == '-'){ printf("%-*.*s", fopts->field_width, fopts->precision, value); }else{ printf("%*.*s", fopts->field_width, fopts->precision, value); } } } } static void formatted_print_percent(const struct mosq_config *lcfg, const struct mosquitto_message *message, const mosquitto_property *properties, char format, struct fieldoptions *fopts) { struct tm *ti = NULL; long ns = 0; char buf[100]; int rc; uint8_t i8value; uint16_t i16value; uint32_t i32value; char *binvalue = NULL, *strname, *strvalue; const mosquitto_property *prop; switch(format){ case '%': fputc('%', stdout); break; case 'A': if(mosquitto_property_read_int16(properties, MQTT_PROP_TOPIC_ALIAS, &i16value, false)){ formatted_print_int(i16value, fopts); }else{ formatted_print_blank(fopts); } break; case 'C': if(mosquitto_property_read_string(properties, MQTT_PROP_CONTENT_TYPE, &strvalue, false)){ formatted_print_str(strvalue, fopts); free(strvalue); }else{ formatted_print_blank(fopts); } break; case 'D': if(mosquitto_property_read_binary(properties, MQTT_PROP_CORRELATION_DATA, (void **)&binvalue, &i16value, false)){ fwrite(binvalue, 1, i16value, stdout); free(binvalue); } break; case 'E': if(mosquitto_property_read_int32(properties, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, &i32value, false)){ formatted_print_int((int)i32value, fopts); }else{ formatted_print_blank(fopts); } break; case 'F': if(mosquitto_property_read_byte(properties, MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, &i8value, false)){ formatted_print_int(i8value, fopts); }else{ formatted_print_blank(fopts); } break; case 'I': if(!ti){ if(get_time(&ti, &ns)){ err_printf(lcfg, "Error obtaining system time.\n"); return; } } if(strftime(buf, 100, "%FT%T%z", ti) != 0){ formatted_print_str(buf, fopts); }else{ formatted_print_blank(fopts); } break; case 'j': if(!ti){ if(get_time(&ti, &ns)){ err_printf(lcfg, "Error obtaining system time.\n"); return; } } if(json_print(message, properties, ti, (int)ns, true, lcfg->pretty) != MOSQ_ERR_SUCCESS){ err_printf(lcfg, "Error: Out of memory.\n"); return; } break; case 'J': if(!ti){ if(get_time(&ti, &ns)){ err_printf(lcfg, "Error obtaining system time.\n"); return; } } rc = json_print(message, properties, ti, (int)ns, false, lcfg->pretty); if(rc == MOSQ_ERR_NOMEM){ err_printf(lcfg, "Error: Out of memory.\n"); return; }else if(rc == MOSQ_ERR_INVAL){ err_printf(lcfg, "Error: Message payload is not valid JSON on topic %s.\n", message->topic); return; } break; case 'l': formatted_print_int(message->payloadlen, fopts); break; case 'm': formatted_print_int(message->mid, fopts); break; case 'P': strname = NULL; strvalue = NULL; prop = mosquitto_property_read_string_pair(properties, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, false); while(prop){ printf("%s:%s", strname, strvalue); free(strname); free(strvalue); strname = NULL; strvalue = NULL; prop = mosquitto_property_read_string_pair(prop, MQTT_PROP_USER_PROPERTY, &strname, &strvalue, true); if(prop){ fputc(' ', stdout); } } free(strname); free(strvalue); break; case 'p': write_payload(message->payload, message->payloadlen, 0, fopts); break; case 'q': fputc(message->qos + 48, stdout); break; case 'R': if(mosquitto_property_read_string(properties, MQTT_PROP_RESPONSE_TOPIC, &strvalue, false)){ formatted_print_str(strvalue, fopts); free(strvalue); } break; case 'r': if(message->retain){ fputc('1', stdout); }else{ fputc('0', stdout); } break; case 'S': if(mosquitto_property_read_varint(properties, MQTT_PROP_SUBSCRIPTION_IDENTIFIER, &i32value, false)){ formatted_print_int((int)i32value, fopts); }else{ formatted_print_blank(fopts); } break; case 't': formatted_print_str(message->topic, fopts); break; case 'U': if(!ti){ if(get_time(&ti, &ns)){ err_printf(lcfg, "Error obtaining system time.\n"); return; } } if(strftime(buf, 100, "%s", ti) != 0){ printf("%s.%09ld", buf, ns); } break; case 'x': write_payload(message->payload, message->payloadlen, 1, fopts); break; case 'X': write_payload(message->payload, message->payloadlen, 2, fopts); break; #ifdef __STDC_IEC_559__ case 'f': if(formatted_print_float(message->payload, message->payloadlen, 'f', fopts)){ err_printf(lcfg, "requested float printing, but non-float data received"); } break; case 'd': if(formatted_print_float(message->payload, message->payloadlen, 'd', fopts)){ err_printf(lcfg, "requested double printing, but non-double data received"); } break; #endif } } static void formatted_print(const struct mosq_config *lcfg, const struct mosquitto_message *message, const mosquitto_property *properties) { size_t len; struct tm *ti = NULL; long ns = 0; len = strlen(lcfg->format); for(size_t i=0; iformat[i] == '%'){ struct fieldoptions fopts = {0, -1, ' ', '\0', ' '}; if(i < len-1){ i++; /* Optional alignment */ if(lcfg->format[i] == '-'){ fopts.align = lcfg->format[i]; if(i < len-1){ i++; } } /* "%-040p" is allowed by this combination of checks, but isn't * a valid format specifier, the '0' will be ignored. */ /* Optional zero padding */ if(lcfg->format[i] == '0'){ fopts.pad = '0'; if(i < len-1){ i++; } } /* Optional field width */ while(i < len-1 && lcfg->format[i] >= '0' && lcfg->format[i] <= '9'){ fopts.field_width *= 10; fopts.field_width += lcfg->format[i]-'0'; i++; } /* Optional precision */ if(lcfg->format[i] == '.'){ if(i < len-1){ i++; fopts.precision = 0; while(i < len-1 && lcfg->format[i] >= '0' && lcfg->format[i] <= '9'){ fopts.precision *= 10; fopts.precision += lcfg->format[i]-'0'; i++; } } } /* Optional hex field separator character */ for(size_t j=0; jformat[i] == hexseplist[j]){ fopts.hexsepchar = hexseplist[j]; i++; break; } } if(i < len){ formatted_print_percent(lcfg, message, properties, lcfg->format[i], &fopts); //align, pad, field_width, precision, hexsepchar); } } }else if(lcfg->format[i] == '@'){ if(i < len-1){ i++; if(lcfg->format[i] == '@'){ fputc('@', stdout); }else{ if(!ti){ if(get_time(&ti, &ns)){ err_printf(lcfg, "Error obtaining system time.\n"); return; } } char strf[3] = {0, 0, 0}; strf[0] = '%'; strf[1] = lcfg->format[i]; strf[2] = 0; if(lcfg->format[i] == 'N'){ printf("%09ld", ns); }else{ char buf[100]; if(strftime(buf, sizeof(buf), strf, ti) != 0){ fputs(buf, stdout); } } } } }else if(lcfg->format[i] == '\\'){ if(i < len-1){ i++; switch(lcfg->format[i]){ case '\\': fputc('\\', stdout); break; case '0': fputc('\0', stdout); break; case 'a': fputc('\a', stdout); break; case 'e': fputc('\033', stdout); break; case 'n': fputc('\n', stdout); break; case 'r': fputc('\r', stdout); break; case 't': fputc('\t', stdout); break; case 'v': fputc('\v', stdout); break; } } }else{ fputc(lcfg->format[i], stdout); } } if(lcfg->eol){ fputc('\n', stdout); } fflush(stdout); } static void rand_init(void) { #ifndef WIN32 struct tm *ti = NULL; long ns; if(!get_time(&ti, &ns)){ srandom((unsigned int)ns); } #endif } #ifndef WIN32 static void watch_print(const struct mosquitto_message *message) { struct watch_topic *item = NULL; HASH_FIND(hh, watch_items, message->topic, strlen(message->topic), item); if(item == NULL){ item = calloc(1, sizeof(struct watch_topic)); if(item == NULL){ return; } item->line = watch_max++; item->topic = strdup(message->topic); if(item->topic == NULL){ free(item); return; } HASH_ADD_KEYPTR(hh, watch_items, item->topic, strlen(item->topic), item); } printf("\e[%d;1H", item->line); } #endif void print_message(struct mosq_config *lcfg, const struct mosquitto_message *message, const mosquitto_property *properties) { #ifdef WIN32 unsigned int r = 0; #else long r = 0; #endif struct fieldoptions fopts = {0, 0, ' ', '\0', ' '}; #ifndef WIN32 if(lcfg->watch){ watch_print(message); } #endif if(lcfg->random_filter < 10000){ #ifdef WIN32 rand_s(&r); #else /* coverity[dont_call] - we don't care about random() not being cryptographically secure here */ r = random(); #endif if((long)(r%10000) >= lcfg->random_filter){ return; } } if(lcfg->format){ formatted_print(lcfg, message, properties); }else if(lcfg->verbose){ if(message->payloadlen){ printf("%s ", message->topic); write_payload(message->payload, message->payloadlen, false, &fopts); if(lcfg->eol){ printf("\n"); } }else{ if(lcfg->eol){ printf("%s (null)\n", message->topic); } } fflush(stdout); }else{ if(message->payloadlen){ write_payload(message->payload, message->payloadlen, false, &fopts); if(lcfg->eol){ printf("\n"); } fflush(stdout); } } #ifndef WIN32 if(lcfg->watch){ printf("\e[%d;1H\n", watch_max-1); } #endif } void output_init(struct mosq_config *lcfg) { rand_init(); #ifndef WIN32 if(lcfg->watch){ printf("\e[2J\e[1;1H"); printf("Broker: %s\n", lcfg->host); } #endif #ifdef WIN32 /* Disable text translation so binary payloads aren't modified */ (void)_setmode(_fileno(stdout), _O_BINARY); #endif } eclipse-mosquitto-mosquitto-691eab3/client/sub_client_output.h000066400000000000000000000015441514232433600250630ustar00rootroot00000000000000/* Copyright (c) 2019-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef SUB_CLIENT_OUTPUT_H #define SUB_CLIENT_OUTPUT_H #include "mosquitto.h" #include "client_shared.h" void output_init(struct mosq_config *cfg); void print_message(struct mosq_config *cfg, const struct mosquitto_message *message, const mosquitto_property *properties); #endif eclipse-mosquitto-mosquitto-691eab3/client/sub_test_fixed_width000077500000000000000000000027561514232433600253050ustar00rootroot00000000000000LD_LIBRARY_PATH=../lib ./mosquitto_sub \ -h test.mosquitto.org \ --retained-only \ --remove-retained \ -t FW/# \ -W 1>/dev/null 2>/dev/null LD_LIBRARY_PATH=../lib ./mosquitto_pub \ -h test.mosquitto.org \ -D publish content-type "application/json" \ -D publish message-expiry-interval 360000 \ -D publish payload-format-indicator 1 \ -D publish response-topic response-topic \ -m ABCDEFGHIJKLMNOPQRSTUVWXYZ \ -q 2 \ -r \ -t FW/truncate \ -V 5 LD_LIBRARY_PATH=../lib ./mosquitto_pub \ -h test.mosquitto.org \ -D publish content-type "null" \ -D publish message-expiry-interval 3600 \ -D publish payload-format-indicator 1 \ -D publish response-topic r-t \ -m Off \ -q 2 \ -r \ -t FW/expire \ -V 5 LD_LIBRARY_PATH=../lib ./mosquitto_pub \ -h test.mosquitto.org \ -D publish payload-format-indicator 1 \ -D publish response-topic rt \ -m Offline \ -q 2 \ -r \ -t FW/1 \ -V 5 LD_LIBRARY_PATH=../lib ./mosquitto_sub \ -h test.mosquitto.org \ -C 3 \ -F "| %10t | %-10t | %7x | %-7x | %08x | %7X | %-7X | %08X | %8p | %-8p | %3m | %-3m | %03m | %3l | %-3l | %03l | %2F | %-2F | %02F | %5C | %-5C | %5E | %-5E | %05E | %5A | %5R | %-5R |" \ -q 2 \ -t 'FW/#' \ -V 5 echo LD_LIBRARY_PATH=../lib ./mosquitto_sub \ -h test.mosquitto.org \ -C 3 \ -F "| %10.10t | %.5t | %-10.10t | %7x | %-7x | %08x | %7X | %-7X | %08X | %8p | %-8p | %3m | %-3m | %03m | %3l | %-3l | %03l | %2F | %-2F | %02F | %5C | %-5C | %5E | %-5E | %05E | %5.5A | %5.5R | %-5.5R |" \ -q 2 \ -t 'FW/#' \ -V 5 eclipse-mosquitto-mosquitto-691eab3/client/sub_test_properties000077500000000000000000000020211514232433600251640ustar00rootroot00000000000000LD_LIBRARY_PATH=../lib ./mosquitto_sub \ \ -V mqttv5 -C 10 -t \$SYS/# -v -U unsub --will-topic will --will-payload '{"key":"value"}' \ \ -D connect authentication-data password \ -D connect authentication-method something \ -D connect maximum-packet-size 0191 \ -D connect receive-maximum 1000 \ -D connect request-problem-information 1 \ -D connect request-response-information 1 \ -D connect session-expiry-interval 39 \ -D connect topic-alias-maximum 123 \ -D connect user-property connect up \ \ -D will content-type application/json \ -D will correlation-data some-data \ -D will message-expiry-interval 59 \ -D will payload-format-indicator 1 \ -D will response-topic /dev/null \ -D will user-property will up \ -D will will-delay-interval 100 \ \ -D subscribe subscription-identifier 1 \ -D subscribe user-property subscribe up \ \ -D unsubscribe user-property unsubscribe up \ \ -D disconnect reason-string "reason" \ -D disconnect session-expiry-interval 40 \ -D disconnect user-property disconnect up eclipse-mosquitto-mosquitto-691eab3/cmake/000077500000000000000000000000001514232433600207415ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/cmake/FindCUnit.cmake000066400000000000000000000015641514232433600235740ustar00rootroot00000000000000find_package(PkgConfig) pkg_check_modules(PC_CUnit QUIET cunit) find_path(CUnit_INCLUDE_DIR NAMES CUnit/CUnit.h PATHS ${PC_CUnit_INCLUDE_DIRS} ) find_library(CUnit_LIBRARY NAMES cunit PATHS ${PC_CUnit_LIBRARY_DIRS} ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CUnit FOUND_VAR CUnit_FOUND REQUIRED_VARS CUnit_LIBRARY CUnit_INCLUDE_DIR VERSION_VAR CUnit_VERSION ) if(CUnit_FOUND) set(CUnit_LIBRARIES ${CUnit_LIBRARY}) set(CUnit_INCLUDE_DIRS ${CUnit_INCLUDE_DIR}) set(CUnit_DEFINITIONS ${PC_CUnit_CFLAGS_OTHER}) endif() if(CUnit_FOUND AND NOT TARGET CUnit::CUnit) add_library(CUnit::CUnit UNKNOWN IMPORTED) set_target_properties(CUnit::CUnit PROPERTIES IMPORTED_LOCATION "${CUnit_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_CUnit_CFLAGS_OTHER}" INTERFACE_INCLUDE_DIRECTORIES "${CUnit_INCLUDE_DIR}" ) endif() eclipse-mosquitto-mosquitto-691eab3/cmake/FindLineEditing.cmake000066400000000000000000000036251514232433600247450ustar00rootroot00000000000000include(FindPackageHandleStandardArgs) set(FIND_PATH_OPTS "") if(APPLE) list(APPEND FIND_PATH_OPTS NO_CMAKE_SYSTEM_PATH NO_SYSTEM_ENVIRONMENT_PATH ) endif() # Checks an environment variable; note that the first check # does not require the usual CMake $-sign. if(DEFINED env{EDITLINE_DIR}) set(EDITLINE_DIR "$ENV{EDITLINE_DIR}") endif() find_path( EDITLINE_INCLUDE_DIR editline/readline.h HINTS EDITLINE_DIR ${FIND_PATH_OPTS} ) find_library(EDITLINE_LIBRARY NAMES edit HINTS ${EDITLINE_DIR} ) if(EDITLINE_INCLUDE_DIR AND EDITLINE_LIBRARY) set(EDITLINE_FOUND TRUE) set(LINEEDITING_FOUND TRUE) set(LINEEDITING_INCLUDE_DIRS ${EDITLINE_INCLUDE_DIR}) set(LINEEDITING_LIBRARIES ${EDITLINE_LIBRARY}) if(NOT TARGET LineEditing::LineEditing) add_library(LineEditing::LineEditing UNKNOWN IMPORTED) set_target_properties(LineEditing::LineEditing PROPERTIES IMPORTED_LOCATION "${EDITLINE_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${EDITLINE_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS "WITH_EDITLINE" ) endif() else() find_path( READLINE_INCLUDE_DIR readline/readline.h HINTS READLINE_DIR ${FIND_PATH_OPTS} ) find_library(READLINE_LIBRARY NAMES readline HINTS ${READLINE_DIR} ) if(READLINE_INCLUDE_DIR AND READLINE_LIBRARY) set(LINEEDITING_FOUND TRUE) set(LINEEDITING_INCLUDE_DIRS ${READLINE_INCLUDE_DIR}) set(LINEEDITING_LIBRARIES ${READLINE_LIBRARY}) if(NOT TARGET LineEditing::LineEditing) add_library(LineEditing::LineEditing UNKNOWN IMPORTED) set_target_properties(LineEditing::LineEditing PROPERTIES IMPORTED_LOCATION "${READLINE_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${READLINE_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS "WITH_READLINE" ) endif() endif() endif() find_package_handle_standard_args(LineEditing REQUIRED_VARS LINEEDITING_LIBRARIES LINEEDITING_INCLUDE_DIRS FAIL_MESSAGE "Could not find libedit or readline library" ) eclipse-mosquitto-mosquitto-691eab3/cmake/Findargon2.cmake000066400000000000000000000017621514232433600237420ustar00rootroot00000000000000INCLUDE( FindPackageHandleStandardArgs ) # Checks an environment variable; note that the first check # does not require the usual CMake $-sign. IF( DEFINED ENV{ARGON2_DIR} ) SET( ARGON2_DIR "$ENV{ARGON2_DIR}" ) ENDIF() FIND_PATH( ARGON2_INCLUDE_DIR argon2.h HINTS ARGON2_DIR ) FIND_LIBRARY( ARGON2_LIBRARY NAMES argon2 HINTS ${ARGON2_DIR} ) FIND_PACKAGE_HANDLE_STANDARD_ARGS( argon2 DEFAULT_MSG ARGON2_INCLUDE_DIR ARGON2_LIBRARY ) IF( ARGON2_FOUND ) SET( ARGON2_INCLUDE_DIRS ${ARGON2_INCLUDE_DIR} ) SET( ARGON2_LIBRARIES ${ARGON2_LIBRARY} ) MARK_AS_ADVANCED( ARGON2_LIBRARY ARGON2_INCLUDE_DIR ARGON2_DIR ) add_library(argon2 SHARED IMPORTED) set_target_properties(argon2 PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ARGON2_INCLUDE_DIRS}" ) set_target_properties(argon2 PROPERTIES IMPORTED_LOCATION "${ARGON2_LIBRARY}" IMPORTED_IMPLIB "${ARGON2_LIBRARY}" ) ELSE() SET( ARGON2_DIR "" CACHE STRING "An optional hint to a directory for finding `argon2`" ) ENDIF() eclipse-mosquitto-mosquitto-691eab3/cmake/FindcJSON.cmake000066400000000000000000000017341514232433600234650ustar00rootroot00000000000000INCLUDE( FindPackageHandleStandardArgs ) # Checks an environment variable; note that the first check # does not require the usual CMake $-sign. IF( DEFINED ENV{CJSON_DIR} ) SET( CJSON_DIR "$ENV{CJSON_DIR}" ) ENDIF() FIND_PATH( CJSON_INCLUDE_DIR cjson/cJSON.h HINTS CJSON_DIR ) FIND_LIBRARY( CJSON_LIBRARY NAMES cjson HINTS ${CJSON_DIR} ) FIND_PACKAGE_HANDLE_STANDARD_ARGS( cJSON DEFAULT_MSG CJSON_INCLUDE_DIR CJSON_LIBRARY ) IF( CJSON_FOUND ) SET( CJSON_INCLUDE_DIRS ${CJSON_INCLUDE_DIR} ) SET( CJSON_LIBRARIES ${CJSON_LIBRARY} ) MARK_AS_ADVANCED( CJSON_LIBRARY CJSON_INCLUDE_DIR CJSON_DIR ) add_library(cJSON SHARED IMPORTED) set_target_properties(cJSON PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CJSON_INCLUDE_DIRS}" ) set_target_properties(cJSON PROPERTIES IMPORTED_LOCATION "${CJSON_LIBRARY}" IMPORTED_IMPLIB "${CJSON_LIBRARY}" ) ELSE() SET( CJSON_DIR "" CACHE STRING "An optional hint to a directory for finding `cJSON`" ) ENDIF() eclipse-mosquitto-mosquitto-691eab3/codecov.yml000066400000000000000000000003201514232433600220210ustar00rootroot00000000000000codecov: max_report_age: off ignore: - "deps/picohttpparser" - "deps/*.h" - "plugins/examples" - "test" - "^.*/test/.*" parsers: gcov: branch_detection: conditional: no loop: no eclipse-mosquitto-mosquitto-691eab3/common/000077500000000000000000000000001514232433600211515ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/common/json_help.c000066400000000000000000000054211514232433600233000ustar00rootroot00000000000000/* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include "config.h" #include #include #include #include #include "json_help.h" #include "mosquitto.h" int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, bool default_value) { cJSON *jtmp; if(optional == true){ *value = default_value; } jtmp = cJSON_GetObjectItem(json, name); if(jtmp){ if(cJSON_IsBool(jtmp) == false){ return MOSQ_ERR_INVAL; } *value = cJSON_IsTrue(jtmp); }else{ if(optional == false){ return MOSQ_ERR_INVAL; } } return MOSQ_ERR_SUCCESS; } int json_get_int(cJSON *json, const char *name, int *value, bool optional, int default_value) { cJSON *jtmp; if(optional == true){ *value = default_value; } jtmp = cJSON_GetObjectItem(json, name); if(jtmp){ if(cJSON_IsNumber(jtmp) == false){ return MOSQ_ERR_INVAL; } *value = jtmp->valueint; }else{ if(optional == false){ return MOSQ_ERR_INVAL; } } return MOSQ_ERR_SUCCESS; } int json_get_int64(cJSON *json, const char *name, int64_t *value, bool optional, int64_t default_value) { cJSON *jtmp; if(optional == true){ *value = default_value; } jtmp = cJSON_GetObjectItem(json, name); if(jtmp){ if(cJSON_IsNumber(jtmp) == false){ return MOSQ_ERR_INVAL; } *value = (int64_t)jtmp->valuedouble; }else{ if(optional == false){ return MOSQ_ERR_INVAL; } } return MOSQ_ERR_SUCCESS; } int json_get_string(cJSON *json, const char *name, const char **value, bool optional) { cJSON *jtmp; *value = NULL; jtmp = cJSON_GetObjectItem(json, name); if(jtmp){ if(cJSON_IsString(jtmp) == false){ return MOSQ_ERR_INVAL; } if(jtmp->valuestring){ *value = jtmp->valuestring; }else{ *value = ""; } }else{ if(optional == false){ return MOSQ_ERR_INVAL; } } return MOSQ_ERR_SUCCESS; } cJSON *cJSON_AddIntToObject(cJSON * const object, const char * const name, long long number) { char buf[30]; snprintf(buf, sizeof(buf), "%lld", number); return cJSON_AddRawToObject(object, name, buf); } cJSON *cJSON_AddUIntToObject(cJSON * const object, const char * const name, unsigned long long number) { char buf[30]; snprintf(buf, sizeof(buf), "%llu", number); return cJSON_AddRawToObject(object, name, buf); } eclipse-mosquitto-mosquitto-691eab3/common/json_help.h000066400000000000000000000027201514232433600233040ustar00rootroot00000000000000#ifndef JSON_HELP_H #define JSON_HELP_H /* Copyright (c) 2020-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #include #include #include #ifdef __cplusplus extern "C" { #endif /* "optional==false" can also be taken to mean "only return success if the key exists and is valid" */ int json_get_bool(cJSON *json, const char *name, bool *value, bool optional, bool default_value); int json_get_int(cJSON *json, const char *name, int *value, bool optional, int default_value); int json_get_int64(cJSON *json, const char *name, int64_t *value, bool optional, int64_t default_value); int json_get_string(cJSON *json, const char *name, const char **value, bool optional); cJSON *cJSON_AddIntToObject(cJSON * const object, const char * const name, long long number); cJSON *cJSON_AddUIntToObject(cJSON * const object, const char * const name, unsigned long long number); cJSON *cJSON_CreateInt(int num); #ifdef __cplusplus } #endif #endif eclipse-mosquitto-mosquitto-691eab3/common/lib_load.h000066400000000000000000000021071514232433600230670ustar00rootroot00000000000000/* Copyright (c) 2012-2021 Roger Light All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at https://www.eclipse.org/legal/epl-2.0/ and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause Contributors: Roger Light - initial implementation and documentation. */ #ifndef LIB_LOAD_H #define LIB_LOAD_H #ifdef WIN32 # include #else # include #endif #ifdef WIN32 # define LIB_LOAD(A) LoadLibrary(A) # define LIB_CLOSE(A) FreeLibrary(A) # define LIB_SYM(HANDLE, SYM) GetProcAddress(HANDLE, SYM) #else # define LIB_LOAD(A) dlopen(A, RTLD_NOW|RTLD_LOCAL) # define LIB_CLOSE(A) dlclose(A) # define LIB_SYM(HANDLE, SYM) dlsym(HANDLE, SYM) #endif #define LIB_SYM_EASY(MEMBER, HANDLE, SYM) if(!(MEMBER = LIB_SYM(HANDLE, SYM)) return 1 #endif eclipse-mosquitto-mosquitto-691eab3/config.h000066400000000000000000000042411514232433600213000ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H /* ============================================================ * Platform options * ============================================================ */ #ifdef __APPLE__ # define __DARWIN_C_SOURCE #elif defined(__FreeBSD__) || defined(__NetBSD__) # define HAVE_NETINET_IN_H #elif defined(__QNX__) # define _XOPEN_SOURCE 600 # define __BSD_VISIBLE 1 # define HAVE_NETINET_IN_H #elif defined(_AIX) # define HAVE_NETINET_IN_H #endif #define OPENSSL_LOAD_CONF /* ============================================================ * Compatibility defines * ============================================================ */ #if defined(_MSC_VER) && _MSC_VER < 1900 # define snprintf sprintf_s # define EPROTO ECONNABORTED # ifndef ECONNABORTED # define ECONNABORTED WSAECONNABORTED # endif # ifndef ENOTCONN # define ENOTCONN WSAENOTCONN # endif # ifndef ECONNREFUSED # define ECONNREFUSED WSAECONNREFUSED # endif #endif #ifdef WIN32 # define strcasecmp _stricmp # define strncasecmp _strnicmp # define strtok_r strtok_s # define strerror_r(e, b, l) strerror_s(b, l, e) # ifdef _MSC_VER # include typedef SSIZE_T ssize_t; # endif #endif #define uthash_malloc(sz) mosquitto_malloc(sz) #define uthash_free(ptr, sz) mosquitto_free(ptr) #ifdef WITH_TLS # include # if defined(WITH_TLS_PSK) && !defined(OPENSSL_NO_PSK) # define FINAL_WITH_TLS_PSK # endif #endif #ifdef __COVERITY__ # include /* These are "wrong", but we don't use them so it doesn't matter */ # define _Float32 uint32_t # define _Float32x uint32_t # define _Float64 uint64_t # define _Float64x uint64_t # define _Float128 uint64_t #endif #define UNUSED(A) (void)(A) /* Android Bionic libpthread implementation doesn't have pthread_cancel */ #if !defined(ANDROID) && !defined(WIN32) # define HAVE_PTHREAD_CANCEL #endif #define WS_IS_LWS 1 #define WS_IS_BUILTIN 2 #ifdef WITH_BROKER # ifdef __GNUC__ # define BROKER_EXPORT __attribute__((__used__)) # else # define BROKER_EXPORT # endif #else # define BROKER_EXPORT #endif #define TOPIC_HIERARCHY_LIMIT 200 #ifdef WITH_ADNS # define _GNU_SOURCE #endif #endif eclipse-mosquitto-mosquitto-691eab3/config.mk000066400000000000000000000205541514232433600214650ustar00rootroot00000000000000# ============================================================================= # User configuration section. # # These options control compilation on all systems apart from Windows and Mac # OS X. Use CMake to compile on Windows and Mac. # # Largely, these are options that are designed to make mosquitto run more # easily in restrictive environments by removing features. # # Modify the variable below to enable/disable features. # # Can also be overridden at the command line, e.g.: # # make WITH_TLS=no # ============================================================================= # Uncomment to compile the broker with tcpd/libwrap support. #WITH_WRAP:=yes # Comment out to disable SSL/TLS support in the broker and client. # Disabling this will also mean that passwords must be stored in plain text. It # is strongly recommended that you only disable WITH_TLS if you are not using # password authentication at all. WITH_TLS:=yes # Comment out to disable TLS/PSK support in the broker and client. Requires # WITH_TLS=yes. # This must be disabled if using openssl < 1.0. WITH_TLS_PSK:=yes # Comment out to disable client threading support. WITH_THREADING:=yes # Comment out to remove bridge support from the broker. This allow the broker # to connect to other brokers and subscribe/publish to topics. You probably # want to leave this included unless you want to save a very small amount of # memory size and CPU time. WITH_BRIDGE:=yes # Comment out to remove persistent database support from the broker. This # allows the broker to store retained messages and durable subscriptions to a # file periodically and on shutdown. This is usually desirable (and is # suggested by the MQTT spec), but it can be disabled if required. WITH_PERSISTENCE:=yes # Comment out to remove memory tracking support from the broker. If disabled, # mosquitto won't track heap memory usage nor export '$SYS/broker/heap/current # size', but will use slightly less memory and CPU time. WITH_MEMORY_TRACKING:=yes # Uncomment to activate a consistency check on the usage of the memory tracking # alloc/free function use. Any memory allocated without a tracking function, # but freed with the tracking function will trigger an invalid memory read in # memory trackers like valgrind memcheck or ASAN. #ALLOC_MISMATCH_INVALID_READ:=yes # Uncomment to activate consistency check on the usage of the memory tracking # alloc/free function use. Any memory allocated without a tracking function, # but freed with the tracking function will trigger an abort. #ALLOC_MISMATCH_ABORT:=yes # Compile with database upgrading support? If disabled, mosquitto won't # automatically upgrade old database versions. # Not currently supported. #WITH_DB_UPGRADE:=yes # Comment out to remove publishing of the $SYS topic hierarchy containing # information about the broker state. WITH_SYS_TREE:=yes # Build with systemd support. If enabled, mosquitto will notify systemd after # initialization. See README in service/systemd/ for more information. # Setting to yes means the libsystemd-dev or similar package will need to be # installed. WITH_SYSTEMD:=no # Build with SRV lookup support. WITH_SRV:=no # Build with websockets support on the broker. # Set to yes to build with new websockets support # Set to lws to build with old libwebsockets code # Set to no to disable WITH_WEBSOCKETS:=yes # Build man page documentation by default. WITH_DOCS:=yes # Build with client support for SOCK5 proxy. WITH_SOCKS:=yes # Strip executables and shared libraries on install. WITH_STRIP:=no # Build static libraries WITH_STATIC_LIBRARIES:=no # Use this variable to add extra library dependencies when building the clients # with the static libmosquitto library. This may be required on some systems # where e.g. -lz or -latomic are needed for openssl. CLIENT_STATIC_LDADD:= # Build shared libraries WITH_SHARED_LIBRARIES:=yes # Build with async dns lookup support for bridges (temporary). Requires glibc. #WITH_ADNS:=yes # Build with epoll support. WITH_EPOLL:=yes # Build with bundled uthash.h WITH_BUNDLED_DEPS:=yes # Build with coverage options WITH_COVERAGE:=no # Build with unix domain socket support WITH_UNIX_SOCKETS:=yes # Build mosquitto with support for the $CONTROL topics. WITH_CONTROL:=yes # Build the broker with the jemalloc allocator WITH_JEMALLOC:=no # Build with xtreport capability. This is for debugging purposes and is # probably of no particular interest to end users. WITH_XTREPORT=no # Use the old O(n) keepalive check routine, instead of the new O(1) keepalive # check routine. See src/keepalive.c for notes on this. WITH_OLD_KEEPALIVE=no # Use link time optimisation - note that enabling this currently prevents # broker plugins from working. #WITH_LTO=yes # Build with sqlite3 support - this enables the sqlite persistence plugin. WITH_SQLITE=yes # Use gmock for testing WITH_GMOCK:=yes # Build broker for fuzzing only - does not work as a normal broker. This is # currently only suitable for use with oss-fuzz. WITH_FUZZING=no # Build using clang and with address sanitiser enabled WITH_ASAN=no # Build with editline support to allow the mosquitto_ctrl shell WITH_EDITLINE=yes # Build with basic HTTP API support WITH_HTTP_API=yes # ============================================================================= # End of user configuration # ============================================================================= # Also bump lib/mosquitto.h, CMakeLists.txt, # installer/mosquitto.nsi, installer/mosquitto64.nsi VERSION=2.1.2 # Client library SO version. Bump if incompatible API/ABI changes are made. SOVERSION=1 # Man page generation requires xsltproc and docbook-xsl XSLTPROC=xsltproc --nonet # For html generation DB_HTML_XSL=man/html.xsl #MANCOUNTRIES=en_GB MAKE_ALL:=mosquitto UNAME:=$(shell uname -s) ARCH:=$(shell uname -p) INSTALL?=install prefix?=/usr/local incdir?=${prefix}/include libdir?=${prefix}/lib${LIB_SUFFIX} localedir?=${prefix}/share/locale mandir?=${prefix}/share/man STRIP?=strip ifeq ($(UNAME),SunOS) ifeq ($(CC),cc) CFLAGS?=-O else CFLAGS?=-Wall -ggdb -O2 endif else CFLAGS?=-Wall -ggdb -O3 -Wconversion -Wextra -std=gnu99 -Werror=switch CXXFLAGS?=-Wall -ggdb -O3 -Wconversion -Wextra endif LOCAL_CPPFLAGS=$(CPPFLAGS) LOCAL_CFLAGS=$(CFLAGS) LOCAL_CXXFLAGS=$(CXXFLAGS) LOCAL_LDFLAGS=$(LDFLAGS) LOCAL_LIBADD=$(LIBADD) LOCAL_CPPFLAGS+=-DVERSION=\""${VERSION}\"" -I${R} -I. -I${R}/include -I${R}/common ifneq ($(or $(findstring $(UNAME),FreeBSD), $(findstring $(UNAME),OpenBSD), $(findstring $(UNAME),NetBSD)),) SEDINPLACE:=-i "" else ifeq ($(UNAME),SunOS) SEDINPLACE:= else SEDINPLACE:=-i endif endif ifeq ($(UNAME),QNX) LOCAL_LDADD+=-lsocket endif ifeq ($(UNAME),SunOS) LOCAL_LDADD+=-lsocket -lnsl LOCAL_LIBADD+=-lsocket -lnsl endif ifeq ($(WITH_FUZZING),yes) WITH_GMOCK:=no WITH_SHARED_LIBRARIES:=no WITH_STATIC_LIBRARIES:=yes endif ifeq ($(WITH_SHARED_LIBRARIES),yes) LIBMOSQ:=${R}/lib/libmosquitto.so.${SOVERSION} else LIBMOSQ:=${R}/lib/libmosquitto.a endif LIBMOSQ_COMMON:=-Wl,--whole-archive ${R}/libcommon/libmosquitto_common.a -Wl,--no-whole-archive -lcjson ifeq ($(WITH_TLS),yes) LOCAL_CPPFLAGS+=-DWITH_TLS ifeq ($(WITH_TLS_PSK),yes) LOCAL_CPPFLAGS+=-DWITH_TLS_PSK endif endif ifeq ($(WITH_ASAN),yes) CC:=clang CXX:=clang++ LOCAL_CFLAGS+=-fsanitize=address -fno-omit-frame-pointer LOCAL_CXXFLAGS+=-fsanitize=address -fno-omit-frame-pointer LOCAL_LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer -static-libsan endif ifeq ($(WITH_LTO),yes) LOCAL_CFLAGS+=-flto LOCAL_LDFLAGS+=-flto endif ifeq ($(WITH_DOCS),yes) MAKE_ALL+=docs endif ifeq ($(WITH_JEMALLOC),yes) LOCAL_LDADD+=-ljemalloc endif ifeq ($(WITH_UNIX_SOCKETS),yes) LOCAL_CPPFLAGS+=-DWITH_UNIX_SOCKETS endif ifeq ($(WITH_WEBSOCKETS),yes) ifeq ($(WITH_TLS),yes) LOCAL_CPPFLAGS+=-DWITH_WEBSOCKETS=WS_IS_BUILTIN -I${R}/deps/picohttpparser endif endif ifeq ($(WITH_WEBSOCKETS),lws) LOCAL_CPPFLAGS+=-DWITH_WEBSOCKETS=WS_IS_LWS LOCAL_LDADD+=-lwebsockets endif ifeq ($(WITH_STRIP),yes) STRIP_OPTS?=-s --strip-program=${CROSS_COMPILE}${STRIP} endif ifeq ($(WITH_BUNDLED_DEPS),yes) LOCAL_CPPFLAGS+=-I${R}/deps endif ifeq ($(WITH_COVERAGE),yes) LOCAL_CFLAGS+=-coverage LOCAL_CXXFLAGS+=-coverage LOCAL_LDFLAGS+=-coverage endif ifeq ($(WITH_FUZZING),yes) MAKE_ALL+=fuzzing LOCAL_CPPFLAGS+=-DWITH_FUZZING LOCAL_CFLAGS+=-fPIC LOCAL_LDFLAGS+=-shared $(LOCAL_CFLAGS) endif ifeq ($(WITH_ARGON2),yes) LOCAL_CPPFLAGS+=-DWITH_ARGON2 LIB_ARGON2=-largon2 LIBMOSQ_COMMON+=${LIB_ARGON2} endif eclipse-mosquitto-mosquitto-691eab3/dashboard/000077500000000000000000000000001514232433600216105ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/dashboard/README.md000066400000000000000000000013301514232433600230640ustar00rootroot00000000000000### Simple web-based graphical user interface for Mosquitto To develop UI locally. 1) Install tailwind: ```sh npm -g install tailwindcss@3 ``` 2) Go into `src` and run tailwind to generate a CSS file based on tailwind classes used in `index.html`: ```sh tailwindcss -i ./css/styles.css -o ./tailwind/styles.css ``` 3) Run mosquitto http api mock 4) Change mosquitto api endponits in `src/consts.js` 5) Go into `src` and run a simple http server, e.g. `python3 -m http.server 3000` Dependencies (in `src/lib` directory): * chartjs 4.3.0 (https://cdnjs.com/libraries/Chart.js/4.3.0) * chartjs-plugin-zoom 2.2.0 (https://cdnjs.com/libraries/chartjs-plugin-zoom) * hammer.js 2.0.8 (https://cdnjs.com/libraries/hammer.js) eclipse-mosquitto-mosquitto-691eab3/dashboard/src/000077500000000000000000000000001514232433600223775ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/dashboard/src/app/000077500000000000000000000000001514232433600231575ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/dashboard/src/app/consts.js000066400000000000000000000010561514232433600250300ustar00rootroot00000000000000const MAX_POINTS_IN_CHART = 5_000; const KEEP_DATAPOINTS_FOR_INTERVAL = 1000 * // 1 sec 60 * // 1 minute 60 * // 1 hour 2; // 2 hours const CHART_UPDATE_INTERVAL_IN_MILLISECONDS = 1000 * // 1 sec 60 * // 1 minute 1; const SMOOTHED_CHART_UPDATE_INTERVAL_IN_MILLISECONDS = 1000 * // 1 sec 60 * // 1 minute 5; // 5 minutes const INTERVAL_5SECS_IN_MILLISECONDS = 1000 * 5; const CHARTJS_ANIMATION_DURATION_MS = 400; const SYSTOPIC_ENDPOINT = "/api/v1/systree"; const LISTENERS_ENDPOINT = "/api/v1/listeners"; const CHART_DISPLAY_WINDOW = 16; eclipse-mosquitto-mosquitto-691eab3/dashboard/src/app/dashboard.js000066400000000000000000001510051514232433600254460ustar00rootroot00000000000000const MAIN_CHART_COLOR = "#fd602e"; const SUPPLEMENTARY_CHART_COLOR = "#6366f1"; class MosquittoDashboard { constructor(headless = false) { this.abort = new AbortController(); registerAbortController(this.abort, this); this.headlessMode = !!headless; !this.headlessMode && window.Chart.register(window.ChartZoom); // chartjs comes from lib/ this.previousDataFetchFailed = false; this.brokerOnline = true; this.version = ""; this.dashboardDataObject = { lastSysTopics: [] }; const dashboardDataObject = this.composeDashboardObject(); const [entitiesToUpdate] = this.getElementsToUpdate( dashboardDataObject.lastSysTopics, ); // if metrics were present in the session store, update them immediately if (!this.headlessMode && entitiesToUpdate) { Object.entries(entitiesToUpdate).forEach(([id, value]) => { this.updateHtmlElementById(id, value); }); } this.dashboardDataObject = dashboardDataObject; this.charts = {}; this.timeoutHandler = null; !this.headlessMode && this.initializeCharts(); !this.headlessMode && this.addToggle(); this.startDataUpdates(); } getChartDataFromStore(chartId) { const chartDataString = sessionStorage.getItem(chartId); let chartDataObject = null; try { if (chartDataString) { chartDataObject = JSON.parse(chartDataString); } return chartDataObject; } catch (error) { const errorMsg = `Error while creating dashboards: ${error?.message}. Chart data string: "${chartDataString}"`; console.error(errorMsg, error); alert(errorMsg); throw new Error(errorMsg); } } createOptions() { return { chartDataType: "raw", }; } createChartDataObject() { // we are already updating dashboardDataObject directly in updateChartInner return { data: { rawData: [], smoothedData: [], }, labels: { rawLabels: [], smoothedLabels: [], }, options: { mustUpdate: false, }, }; } composeDashboardObject() { const dashboardDataObject = { charts: { "chart-messages-dropped": this.getChartDataFromStore("chart-messages-dropped") || this.createChartDataObject(), "chart-messages-sent": this.getChartDataFromStore("chart-messages-sent") || this.createChartDataObject(), "chart-messages-received": this.getChartDataFromStore("chart-messages-received") || this.createChartDataObject(), "chart-messages-sent-rate": this.getChartDataFromStore("chart-messages-sent-rate") || this.createChartDataObject(), "chart-messages-received-rate": this.getChartDataFromStore("chart-messages-received-rate") || this.createChartDataObject(), "chart-clients-connected": this.getChartDataFromStore("chart-clients-connected") || this.createChartDataObject(), "chart-clients-disconnected": this.getChartDataFromStore("chart-clients-disconnected") || this.createChartDataObject(), }, lastSysTopics: this.getChartDataFromStore("sysTopics") || {}, lastUpdateDueToIntervalTimestamp: this.getChartDataFromStore("updateDueToIntervalTimestamp") || 0, // used to make sure we also always update graphs at certain intervals options: this.getChartDataFromStore("options") || this.createOptions(), }; return dashboardDataObject; } setBrokerVersion() { if (this.headlessMode) { return; } this.updateHtmlElementById("broker-version", this.version); } setBrokerStatus() { if (this.headlessMode) { return; } this.removeHtmlElementClass( "broker-status", this.brokerOnline ? "broker-inactive" : "broker-active", ); this.addHtmlElementClass( "broker-status", this.brokerOnline ? "broker-active" : "broker-inactive", ); this.updateHtmlElementById( "broker-status-text", this.brokerOnline ? "Online" : "Offline", ); } createLineChart( canvasId, label, color = SUPPLEMENTARY_CHART_COLOR, chartDataType, ) { const labelsType = chartDataType === "raw" ? "rawLabels" : "smoothedLabels"; const dataType = chartDataType === "raw" ? "rawData" : "smoothedData"; const ctx = document.getElementById(canvasId).getContext("2d"); const totalLen = this.dashboardDataObject.charts[canvasId].labels[labelsType].length; const windowSize = CHART_DISPLAY_WINDOW; const startIndex = Math.max(0, totalLen - windowSize); const chart = new window.Chart(ctx, { type: "line", data: { labels: this.dashboardDataObject.charts[canvasId].labels[labelsType], datasets: [ { label: label, data: this.dashboardDataObject.charts[canvasId].data[dataType], borderColor: color, backgroundColor: color + "20", borderWidth: 2, fill: true, tension: 0.4, pointRadius: 3, pointHoverRadius: 5, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false, }, zoom: { zoom: { wheel: { enabled: true, }, pinch: { enabled: true, }, mode: "xy", }, pan: { enabled: true, mode: "xy", rangeMin: startIndex, rangeMax: totalLen - 1, }, }, }, scales: { x: { display: true, grid: { color: "#f3f4f6", }, ticks: { font: { size: 10, }, maxRotation: 45, }, min: startIndex, max: totalLen - 1, }, y: { display: true, beginAtZero: true, grid: { color: "#f3f4f6", }, ticks: { font: { size: 10, }, }, }, }, interaction: { intersect: false, mode: "index", }, }, }); return chart; } createSentVsReceivedChart(chartDataType) { const labelsType = chartDataType === "raw" ? "rawLabels" : "smoothedLabels"; const dataType = chartDataType === "raw" ? "rawData" : "smoothedData"; const ctx = document .getElementById("chart-message-overview") .getContext("2d"); const totalLen = this.dashboardDataObject.charts["chart-messages-sent"].labels[labelsType] .length; const windowSize = CHART_DISPLAY_WINDOW; const startIndex = Math.max(0, totalLen - windowSize); const chart = new window.Chart(ctx, { type: "line", data: { labels: this.dashboardDataObject.charts["chart-messages-sent"].labels[ labelsType ], datasets: [ { label: "Messages Sent", data: this.dashboardDataObject.charts["chart-messages-sent"].data[ dataType ], borderColor: SUPPLEMENTARY_CHART_COLOR, backgroundColor: SUPPLEMENTARY_CHART_COLOR + "20", borderWidth: 2, fill: false, tension: 0.4, }, { label: "Messages Received", data: this.dashboardDataObject.charts["chart-messages-received"] .data[dataType], borderColor: MAIN_CHART_COLOR, backgroundColor: MAIN_CHART_COLOR + "20", borderWidth: 2, fill: false, tension: 0.4, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: "top", }, zoom: { zoom: { wheel: { enabled: true, }, pinch: { enabled: true, }, mode: "xy", }, pan: { enabled: true, mode: "xy", rangeMin: startIndex, rangeMax: totalLen - 1, }, }, }, scales: { x: { display: true, grid: { color: "#f3f4f6", }, min: startIndex, max: totalLen - 1, }, y: { display: true, beginAtZero: true, grid: { color: "#f3f4f6", }, }, }, interaction: { intersect: false, mode: "index", }, }, }); return chart; } createRateSentVsReceivedChart(chartDataType) { const labelsType = chartDataType === "raw" ? "rawLabels" : "smoothedLabels"; const dataType = chartDataType === "raw" ? "rawData" : "smoothedData"; const ctx = document .getElementById("chart-message-rate-overview") .getContext("2d"); const totalLen = this.dashboardDataObject.charts["chart-messages-sent-rate"].labels[ labelsType ].length; const windowSize = CHART_DISPLAY_WINDOW; const startIndex = Math.max(0, totalLen - windowSize); const chart = new window.Chart(ctx, { type: "line", data: { labels: this.dashboardDataObject.charts["chart-messages-sent-rate"].labels[ labelsType ], datasets: [ { label: "Messages Sent per Minute", data: this.dashboardDataObject.charts["chart-messages-sent-rate"] .data[dataType], borderColor: SUPPLEMENTARY_CHART_COLOR, backgroundColor: SUPPLEMENTARY_CHART_COLOR + "20", borderWidth: 2, fill: false, tension: 0.4, }, { label: "Messages Received per Minute", data: this.dashboardDataObject.charts[ "chart-messages-received-rate" ].data[dataType], borderColor: MAIN_CHART_COLOR, backgroundColor: MAIN_CHART_COLOR + "20", borderWidth: 2, fill: false, tension: 0.4, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: "top", }, zoom: { zoom: { wheel: { enabled: true, }, pinch: { enabled: true, }, mode: "xy", }, pan: { enabled: true, mode: "xy", rangeMin: startIndex, rangeMax: totalLen - 1, }, }, }, scales: { x: { display: true, grid: { color: "#f3f4f6", }, min: startIndex, max: totalLen - 1, }, y: { display: true, beginAtZero: true, grid: { color: "#f3f4f6", }, }, }, interaction: { intersect: false, mode: "index", }, }, }); return chart; } handleChartAction(chartId, action) { const chart = this.charts[chartId]; if (!chart) { const errorMsg = "Couldn't find the chart: " + chartId; console.error(errorMsg + ". Charts:", Object.keys(this.charts)); alert(errorMsg); } switch (action) { case "zoom-in": chart.zoom(1.2); break; case "zoom-out": chart.zoom(0.8); break; case "pan-left": chart.pan({ x: 100 }); break; case "pan-right": chart.pan({ x: -100 }); break; case "reset": const newTotalLen = chart.data.labels.length; const newStart = Math.max(0, newTotalLen - CHART_DISPLAY_WINDOW); chart.options.scales.x.min = newStart; chart.options.scales.x.max = newTotalLen - 1; chart.update(); //chart.update("none"); //Object.values(chart.options.scales).forEach((axisOptions) => { // delete axisOptions.min; // delete axisOptions.max; //}); chart.resetZoom(); break; default: const errorMsg = `Unrecognized action "${action}"`; console.error(errorMsg); alert(errorMsg); } } addToggle() { const toggleChartDataTypeButton = document.getElementById( "chart-data-type-global-toggle", ); const toggleChartDataTypeText = document.getElementById( "chart-data-type-text", ); // set to an opposite state and toggle once to refresh the button captions etc if (this.dashboardDataObject.options.chartDataType === "raw") { this.dashboardDataObject.options.chartDataType = "smooth"; } else { this.dashboardDataObject.options.chartDataType = "raw"; } const handleChartDataTypeToggle = () => { if (this.dashboardDataObject.options.chartDataType === "raw") { this.dashboardDataObject.options.chartDataType = "smooth"; this.destroyCharts(); toggleChartDataTypeText.textContent = "Show Raw Data"; this.addHtmlElementClass("smooth-state-svg", "hidden"); this.removeHtmlElementClass("raw-state-svg", "hidden"); } else { this.dashboardDataObject.options.chartDataType = "raw"; this.destroyCharts(); toggleChartDataTypeText.textContent = "Show Smoothed Data"; this.addHtmlElementClass("raw-state-svg", "hidden"); this.removeHtmlElementClass("smooth-state-svg", "hidden"); } this.initializeCharts(); sessionStorage.setItem( "options", JSON.stringify(this.dashboardDataObject.options), ); }; toggleChartDataTypeButton.addEventListener("click", () => { queue.enqueue(toAsyncAndWaitAfter(handleChartDataTypeToggle)); }); queue.enqueue(toAsyncAndWaitAfter(handleChartDataTypeToggle)); } initializeCharts() { let id = ""; id = "chart-messages-dropped"; this.charts[id] = this.createLineChart( id, "Dropped Messages", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-messages-sent"; this.charts[id] = this.createLineChart( id, "Messages Sent", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-messages-received"; this.charts[id] = this.createLineChart( id, "Messages Received", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-messages-sent-rate"; this.charts[id] = this.createLineChart( id, "Sent Rate", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-messages-received-rate"; this.charts[id] = this.createLineChart( id, "Received Rate", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-clients-connected"; this.charts[id] = this.createLineChart( id, "Connected Clients", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-clients-disconnected"; this.charts[id] = this.createLineChart( id, "Disconnected Persistent Clients", MAIN_CHART_COLOR, this.dashboardDataObject.options.chartDataType, ); id = "chart-message-overview"; this.charts[id] = this.createSentVsReceivedChart( this.dashboardDataObject.options.chartDataType, ); id = "chart-message-rate-overview"; this.charts[id] = this.createRateSentVsReceivedChart( this.dashboardDataObject.options.chartDataType, ); document.addEventListener("click", (e) => { if (e.target.dataset.action) { const chartId = e.target.dataset.chart; if (chartId) { const action = e.target.dataset.action; queue.enqueue( toAsyncAndWaitAfter(() => this.handleChartAction(chartId, action)), ); } } }); } getChartDatasets(chartId) { let labels, dataset, dataset2; let id1, id2; if (chartId === "chart-message-overview") { id1 = "chart-messages-sent"; id2 = "chart-messages-received"; labels = this.dashboardDataObject.charts[id1].labels; dataset = this.dashboardDataObject.charts[id1].data; dataset2 = this.dashboardDataObject.charts[id2].data; assertExistence(labels, `Labels not found for "${id1}"`); assertExistence(labels, `Dataset not found for "${id1}"`); assertExistence(labels, `Dataset not found for "${id2}"`); } else if (chartId === "chart-message-rate-overview") { id1 = "chart-messages-sent-rate"; id2 = "chart-messages-received-rate"; labels = this.dashboardDataObject.charts[id1].labels; dataset = this.dashboardDataObject.charts[id1].data; dataset2 = this.dashboardDataObject.charts[id2].data; assertExistence(labels, `Labels not found for "${id1}"`); assertExistence(labels, `Dataset not found for "${id1}"`); assertExistence(labels, `Dataset not found for "${id2}"`); } else { labels = this.dashboardDataObject.charts[chartId].labels; dataset = this.dashboardDataObject.charts[chartId].data; assertExistence(labels, `Labels not found for chartId "${chartId}"`); assertExistence(labels, `Dataset not found for chartId "${chartId}"`); } return [labels, dataset, dataset2]; } getChartPositionalData(chart) { const lastX = chart.data.labels.length - 1; const secondToLastX = chart.data.labels.length - 2; const currentViewFieldEnd = chart.scales.x.max; const zoomLevel = chart.getZoomLevel(); return [zoomLevel, currentViewFieldEnd, lastX, secondToLastX]; } isEndElementVisibleAndDefaultZoom(lastX, currentEnd, zoomLevel) { if (currentEnd === lastX && zoomLevel === 1) { return true; } return false; } slideChart(chart) { const newTotalLen = chart.data.labels.length; const newStart = Math.max(0, newTotalLen - CHART_DISPLAY_WINDOW); chart.options.scales.x.min = newStart; chart.options.scales.x.max = newTotalLen - 1; } destroyCharts() { for (const [chartId, _] of Object.entries(this.charts)) { let data; let data2; let labels; [labels, data, data2] = this.getChartDatasets(chartId); this.charts[chartId].destroy(); } } updateLastSysTopics(id, value) { this.dashboardDataObject.lastSysTopics[id] = value; } updateMatchingChart(chartId, sysTopics, chartIdsToUpdate) { const createErrorMsg = (matchingChartId, matchingChartSysTopic) => `datapoint doesn't exist in current sysTopic data for the chart "${matchingChartId}" matching the chart "${chartId}". Matching chart sys topic: ${matchingChartSysTopic}. Available sys topics: ${JSON.stringify( sysTopics, )}`; if (chartId === "chart-messages-sent") { const matchingChartId = "chart-messages-received"; const matchingChartSysTopic = "$SYS/broker/messages/received"; const datapoint = sysTopics[matchingChartSysTopic]; assertExistence( datapoint, createErrorMsg(matchingChartId, matchingChartSysTopic), ); chartIdsToUpdate[matchingChartId] = datapoint; } if (chartId === "chart-messages-received") { const matchingChartId = "chart-messages-sent"; const matchingChartSysTopic = "$SYS/broker/messages/sent"; const datapoint = sysTopics[matchingChartSysTopic]; assertExistence( datapoint, createErrorMsg(matchingChartId, matchingChartSysTopic), ); chartIdsToUpdate[matchingChartId] = datapoint; } if (chartId === "chart-messages-sent-rate") { const matchingChartId = "chart-messages-received-rate"; const matchingChartSysTopic = "$SYS/broker/load/messages/received/1min"; const datapoint = sysTopics[matchingChartSysTopic]; assertExistence( datapoint, createErrorMsg(matchingChartId, matchingChartSysTopic), ); chartIdsToUpdate[matchingChartId] = datapoint; } if (chartId === "chart-messages-received-rate") { const matchingChartId = "chart-messages-sent-rate"; const matchingChartSysTopic = "$SYS/broker/load/messages/sent/1min"; const datapoint = sysTopics[matchingChartSysTopic]; assertExistence( datapoint, createErrorMsg(matchingChartId, matchingChartSysTopic), ); chartIdsToUpdate[matchingChartId] = datapoint; } } getElementsToUpdate(sysTopics) { // we only update what has actually changed let htmlIdsToUpdate = {}; let chartIdsToUpdate = {}; let topic = ""; // sys topics object looks as follows: //{ // "$SYS/broker/uptime": 99999, // "$SYS/broker/clients/total": 0, // "$SYS/broker/clients/maximum": 1, // "$SYS/broker/clients/disconnected": 0, // "$SYS/broker/clients/connected": 0, // "$SYS/broker/clients/expired": 0, // "$SYS/broker/messages/stored": 2, // "$SYS/broker/store/messages/bytes": 32, // "$SYS/broker/subscriptions/count": 0, // "$SYS/broker/shared_subscriptions/count": 0, // "$SYS/broker/retained messages/count": 2, // "$SYS/broker/heap/current": 796624, // "$SYS/broker/heap/maximum": 796704, // "$SYS/broker/messages/received": 0, // "$SYS/broker/messages/sent": 0, // "$SYS/broker/bytes/received": 0, // "$SYS/broker/bytes/sent": 0, // "$SYS/broker/publish/bytes/received": 0, // "$SYS/broker/publish/bytes/sent": 0, // "$SYS/broker/packet/out/count": 0, // "$SYS/broker/packet/out/bytes": 0, // "$SYS/broker/connections/socket/count": 0, // "$SYS/broker/publish/messages/dropped": 0, // "$SYS/broker/publish/messages/received": 0, // "$SYS/broker/publish/messages/sent": 0 //} topic = "$SYS/broker/uptime"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["broker-uptime"] = secondsToIntervalString( sysTopics[topic], ); } topic = "$SYS/broker/clients/total"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["clients-total"] = prettifyNumber(sysTopics[topic]); htmlIdsToUpdate["systopic-clients-total"] = sysTopics[topic]; } topic = "$SYS/broker/clients/disconnected"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["clients-disconnected"] = prettifyNumber( sysTopics[topic], ); htmlIdsToUpdate["systopic-clients-disconnected"] = sysTopics[topic]; chartIdsToUpdate["chart-clients-disconnected"] = sysTopics[topic]; } topic = "$SYS/broker/clients/connected"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["clients-connected"] = prettifyNumber(sysTopics[topic]); htmlIdsToUpdate["systopic-clients-connected"] = sysTopics[topic]; chartIdsToUpdate["chart-clients-connected"] = sysTopics[topic]; } topic = "$SYS/broker/clients/maximum"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics["clients-maximum"] !== sysTopics[topic] ) { this.updateLastSysTopics("clients-disconnected", sysTopics[topic]); htmlIdsToUpdate["clients-maximum"] = sysTopics[topic]; htmlIdsToUpdate["systopic-clients-max"] = sysTopics[topic]; } topic = "$SYS/broker/clients/expired"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["clients-expired"] = prettifyNumber(sysTopics[topic]); htmlIdsToUpdate["systopic-clients-expired"] = sysTopics[topic]; } topic = "$SYS/broker/subscriptions/count"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["total-subscriptions"] = prettifyNumber(sysTopics[topic]); htmlIdsToUpdate["systopic-total-subscriptions"] = sysTopics[topic]; } topic = "$SYS/broker/shared_subscriptions/count"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-total-shared-subscriptions"] = sysTopics[topic]; } topic = "$SYS/broker/heap/current"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-heap-current"] = sysTopics[topic]; } topic = "$SYS/broker/heap/maximum"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-heap-max"] = sysTopics[topic]; } topic = "$SYS/broker/connections/socket/count"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["connection-sockets"] = sysTopics[topic]; htmlIdsToUpdate["systopic-connection-sockets"] = sysTopics[topic]; } topic = "$SYS/broker/load/messages/received/1min"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); const chartId = "chart-messages-received-rate"; chartIdsToUpdate[chartId] = sysTopics[topic]; this.updateMatchingChart(chartId, sysTopics, chartIdsToUpdate); htmlIdsToUpdate["systopic-messages-received-1min"] = sysTopics[topic]; } topic = "$SYS/broker/load/messages/received/10min"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-received-10min"] = sysTopics[topic]; } topic = "$SYS/broker/load/messages/received/15min"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-received-15min"] = sysTopics[topic]; } topic = "$SYS/broker/load/messages/sent/1min"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); const chartId = "chart-messages-sent-rate"; chartIdsToUpdate[chartId] = sysTopics[topic]; this.updateMatchingChart(chartId, sysTopics, chartIdsToUpdate); htmlIdsToUpdate["systopic-messages-sent-1min"] = sysTopics[topic]; } topic = "$SYS/broker/load/messages/sent/10min"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-sent-10min"] = sysTopics[topic]; } topic = "$SYS/broker/load/messages/sent/15min"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-sent-15min"] = sysTopics[topic]; } topic = "$SYS/broker/messages/stored"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["messages-stored"] = sysTopics[topic]; htmlIdsToUpdate["systopic-messages-stored"] = sysTopics[topic]; } topic = "$SYS/broker/retained messages/count"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["messages-retained"] = sysTopics[topic]; htmlIdsToUpdate["systopic-messages-retained"] = sysTopics[topic]; } topic = "$SYS/broker/messages/received"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-received"] = sysTopics[topic]; const chartId = "chart-messages-received"; chartIdsToUpdate[chartId] = sysTopics[topic]; this.updateMatchingChart(chartId, sysTopics, chartIdsToUpdate); } topic = "$SYS/broker/messages/sent"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-sent"] = sysTopics[topic]; const chartId = "chart-messages-sent"; chartIdsToUpdate[chartId] = sysTopics[topic]; this.updateMatchingChart(chartId, sysTopics, chartIdsToUpdate); } topic = "$SYS/broker/store/messages/bytes"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-messages-stored-bytes"] = sysTopics[topic]; } topic = "$SYS/broker/bytes/received"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-received-bytes"] = sysTopics[topic]; } topic = "$SYS/broker/bytes/sent"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-sent-bytes"] = sysTopics[topic]; } topic = "$SYS/broker/publish/bytes/received"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-publish-received-bytes"] = sysTopics[topic]; } topic = "$SYS/broker/publish/bytes/sent"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-publish-sent-bytes"] = sysTopics[topic]; } topic = "$SYS/broker/publish/messages/dropped"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["messages-dropped"] = sysTopics[topic]; htmlIdsToUpdate["systopic-messages-dropped"] = sysTopics[topic]; chartIdsToUpdate["chart-messages-dropped"] = sysTopics[topic]; } topic = "$SYS/broker/publish/messages/received"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["messages-published-to-broker"] = sysTopics[topic]; htmlIdsToUpdate["systopic-messages-published-to-broker"] = sysTopics[topic]; } topic = "$SYS/broker/publish/messages/sent"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["messages-published-by-broker"] = sysTopics[topic]; htmlIdsToUpdate["systopic-messages-published-by-broker"] = sysTopics[topic]; } topic = "$SYS/broker/packet/out/count"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-out-packets"] = sysTopics[topic]; } topic = "$SYS/broker/packet/out/bytes"; if ( sysTopics[topic] !== undefined && this.dashboardDataObject.lastSysTopics[topic] !== sysTopics[topic] ) { this.updateLastSysTopics(topic, sysTopics[topic]); htmlIdsToUpdate["systopic-out-bytes"] = sysTopics[topic]; } if (!Object.keys(htmlIdsToUpdate).length) { htmlIdsToUpdate = null; } if (!Object.keys(chartIdsToUpdate).length) { chartIdsToUpdate = null; } return [htmlIdsToUpdate, chartIdsToUpdate]; } trimChartDataWindow(labels, dataset, timestampNow) { let earliestTimestamp = 0; while ( timestampNow - earliestTimestamp >= KEEP_DATAPOINTS_FOR_INTERVAL && labels.length && dataset.length ) { earliestTimestamp = timeStringToTimestamp(labels[0]); labels.shift(); dataset.shift(); } } processChartOverflow(labels, dataset, timestamp) { if (!labels.length || !dataset.length) { return; } if ( labels.length > MAX_POINTS_IN_CHART && dataset.length > MAX_POINTS_IN_CHART ) { labels.shift(); dataset.shift(); } if ( timestamp - timeStringToTimestamp(labels[0]) >= KEEP_DATAPOINTS_FOR_INTERVAL ) { this.trimChartDataWindow(labels, dataset, timestamp); } } datapointsAreSufficientlyDifferent(datapoint1, datapoint2) { if (Math.abs(datapoint1 - datapoint2) / datapoint1 > 0.2) { return true; } return false; } labelsAreFarApart(earlierTimeString, laterTimeString) { const earlierTimestamp = timeStringToTimestamp(earlierTimeString); const laterTimestamp = timeStringToTimestamp(laterTimeString); if ( laterTimestamp - earlierTimestamp > SMOOTHED_CHART_UPDATE_INTERVAL_IN_MILLISECONDS ) { // also works at the bound between two days because laterTimestamp - earlierTimestamp will in this case be negative, so we return false for the check happening when one timestamp (earlierTimestamp) is coming from the previous day and the current timestamp (laterTimestamp) is for the new day, just after midnight return true; } return false; } setMustUpdateForMatchingGraph(chartId) { const createAssertErrorMsg = (id) => `mustUpdate option not found for chart "${id}". Available options: ${JSON.stringify( this.dashboardDataObject.charts[id]?.options, )}. Available charts: ${Object.keys(this.dashboardDataObject.charts)}`; let oppositeChartId; if (chartId === "chart-messages-sent") { oppositeChartId = "chart-messages-received"; assertExistence( this.dashboardDataObject.charts[oppositeChartId]?.options?.mustUpdate, createAssertErrorMsg(oppositeChartId), ); this.dashboardDataObject.charts[oppositeChartId].options.mustUpdate = true; } else if (chartId === "chart-messages-received") { oppositeChartId = "chart-messages-sent"; assertExistence( this.dashboardDataObject.charts[oppositeChartId]?.options?.mustUpdate, createAssertErrorMsg(oppositeChartId), ); this.dashboardDataObject.charts[oppositeChartId].options.mustUpdate = true; } else if (chartId === "chart-messages-sent-rate") { oppositeChartId = "chart-messages-received-rate"; assertExistence( this.dashboardDataObject.charts[oppositeChartId]?.options?.mustUpdate, createAssertErrorMsg(oppositeChartId), ); this.dashboardDataObject.charts[oppositeChartId].options.mustUpdate = true; } else if (chartId === "chart-messages-received-rate") { oppositeChartId = "chart-messages-sent-rate"; assertExistence( this.dashboardDataObject.charts[oppositeChartId]?.options?.mustUpdate, createAssertErrorMsg(oppositeChartId), ); this.dashboardDataObject.charts[oppositeChartId].options.mustUpdate = true; } } addSmoothedDataPoint( chartData, chartLabels, chartOptions, datapoint, timeString, chartId, ) { const lastElement = chartData.smoothedData.pop(); const lastLabel = chartLabels.smoothedLabels.pop(); const smoothedDataLen = chartData.smoothedData.length; const smoothedLabelsLen = chartLabels.smoothedLabels.length; if (smoothedDataLen != smoothedLabelsLen) { const errorMessage = `Smoothed data and label set size deviated: ${smoothedDataLen} vs ${smoothedLabelsLen}. Broken state`; throw new Error(errorMessage); } if ( lastElement && lastLabel && (chartOptions.mustUpdate || // Compare popped datapoint and the one that is left in the array before it. We check that the differences between datapoints' values reaches a certain threshold or these datapoints have large time interval between insertions (smoothedDataLen == 0 && smoothedLabelsLen == 0) || this.datapointsAreSufficientlyDifferent( chartData.smoothedData[smoothedDataLen - 1], lastElement, ) || this.labelsAreFarApart( chartLabels.smoothedLabels[smoothedLabelsLen - 1], lastLabel, )) ) { !chartOptions.mustUpdate && this.setMustUpdateForMatchingGraph(chartId); // check is needed to avoud a circular update chartOptions.mustUpdate = false; chartData.smoothedData.push(lastElement); chartLabels.smoothedLabels.push(lastLabel); } // always append the latest datapoint regardless if its value is sufficiently different or not. this is to keep the graph up to date with the latest change and not make it appear stale or simply empty chartData.smoothedData.push(datapoint); chartLabels.smoothedLabels.push(timeString); } updateChartInner(id, datapoint, timestamp, dashboardDataObject) { const chart = this.charts[id]; // note: in headless mode chart will be undefined as no chart objects are initialized const chartData = dashboardDataObject.charts[id].data; const chartLabels = dashboardDataObject.charts[id].labels; const chartOptions = dashboardDataObject.charts[id].options; !this.headlessMode && assertExistence( chart, `Chart "${id}" not found. Available charts: ${Object.keys(this.charts)}`, ); assertExistence( chartData, `Data for the chart "${id}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); assertExistence( chartLabels, `Labels for the chart "${id}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); assertExistence( chartOptions, `Options for the chart "${id}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); this.processChartOverflow( chartLabels.rawLabels, chartData.rawData, timestamp, ); let zoomLevel, currentEnd, lastX; if (!this.headlessMode) { [zoomLevel, currentEnd, lastX] = this.getChartPositionalData(chart); } const timeString = toTimeString(new Date(timestamp)); chartData.rawData.push(datapoint); chartLabels.rawLabels.push(timeString); this.addSmoothedDataPoint( chartData, chartLabels, chartOptions, datapoint, timeString, id, ); if ( !this.headlessMode && this.isEndElementVisibleAndDefaultZoom(lastX, currentEnd, zoomLevel) ) { this.slideChart(chart); } !this.headlessMode && chart.update(); // put 'none' for no animation on updates } getOverviewChartSubchartIds(id) { if (id == "chart-message-overview") { return ["chart-messages-sent", "chart-messages-received"]; } else if (id == "chart-message-rate-overview") { return ["chart-messages-sent-rate", "chart-messages-received-rate"]; } else { throw new Error(`No such overview chart id: ${id}`); } } updateOverviewChartInner(id, firstSubChartId, secondSubChartId) { if (this.headlessMode) { // this function only update the chart chart view itself, no data manipulations are done as it simply consumes other line charts. So we have nothing to do in headless mode return; } const chart = this.charts[id]; const firstChartData = this.dashboardDataObject.charts[firstSubChartId].data; const firstChartLabels = this.dashboardDataObject.charts[firstSubChartId].labels; const secondChartData = this.dashboardDataObject.charts[secondSubChartId].data; const secondChartLabels = this.dashboardDataObject.charts[secondSubChartId].labels; assertExistence( chart, `Chart "${id}" not found. Available charts: ${Object.keys(this.charts)}`, ); assertExistence( firstChartData, `Data for the first sub chart with id "${firstSubChartId}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); assertExistence( firstChartLabels, `Labels for the first sub chart with id "${firstSubChartId}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); assertExistence( secondChartData, `Data for the second sub chart with "${secondSubChartId}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); assertExistence( secondChartLabels, `Labels for the second sub chart with id "${secondSubChartId}" not found. Available charts: ${Object.keys( this.dashboardDataObject.charts, )}`, ); const [zoomLevel, currentEnd, lastX, secondToLastX] = this.getChartPositionalData(chart); // compare to both last and second to last x because x may have already moved forward one step since it comes from a separate graph that gets rendered before overview graphs if ( this.isEndElementVisibleAndDefaultZoom(lastX, currentEnd, zoomLevel) || this.isEndElementVisibleAndDefaultZoom( secondToLastX, currentEnd, zoomLevel, ) ) { this.slideChart(chart); } chart.update(); } updateHtmlElementById(elementId, value) { const element = document.getElementById(elementId); if (element) { element.textContent = value; } } removeHtmlElementClass(elementId, className) { const element = document.getElementById(elementId); if (element) { element.classList.remove(className); } } addHtmlElementClass(elementId, className) { const element = document.getElementById(elementId); if (element) { element.classList.add(className); } } updateChart( id, datapoint, timestampMilliseconds, dashboardDataObject, lastDataPoint, isUpdatingAllCharts, ) { if (datapoint !== undefined || isUpdatingAllCharts) { this.updateChartInner( id, datapoint !== undefined ? datapoint : lastDataPoint, timestampMilliseconds, dashboardDataObject, ); } } updateOverviewChart( id, firstChartDatapoint, secondChartDatapoint, isUpdatingAllCharts, firstSubChartId, secondSubChartId, ) { if ( firstChartDatapoint !== undefined || secondChartDatapoint !== undefined || isUpdatingAllCharts ) { this.updateOverviewChartInner(id, firstSubChartId, secondSubChartId); } } getLastChartsDataPoints(dashboardDataObject) { const lastChartsDataPoints = { "chart-messages-dropped": dashboardDataObject.lastSysTopics[ "$SYS/broker/publish/messages/dropped" ], "chart-messages-sent": dashboardDataObject.lastSysTopics["$SYS/broker/messages/sent"], "chart-messages-received": dashboardDataObject.lastSysTopics["$SYS/broker/messages/received"], "chart-messages-sent-rate": dashboardDataObject.lastSysTopics[ "$SYS/broker/load/messages/sent/1min" ], "chart-messages-received-rate": dashboardDataObject.lastSysTopics[ "$SYS/broker/load/messages/received/1min" ], "chart-clients-connected": dashboardDataObject.lastSysTopics["$SYS/broker/clients/connected"], "chart-clients-disconnected": dashboardDataObject.lastSysTopics["$SYS/broker/clients/disconnected"], }; return lastChartsDataPoints; } updateCharts( chartData, dashboardDataObject, timestampMilliseconds, isUpdatingAllCharts, ) { const lastDataPoints = this.getLastChartsDataPoints(dashboardDataObject); let id = ""; id = "chart-messages-dropped"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-messages-sent"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-messages-received"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-messages-sent-rate"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-messages-received-rate"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-clients-connected"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-clients-disconnected"; this.updateChart( id, chartData[id], timestampMilliseconds, dashboardDataObject, lastDataPoints[id], isUpdatingAllCharts, ); id = "chart-messages-sent"; let id2 = "chart-messages-received"; this.updateOverviewChart( "chart-message-overview", chartData[id], chartData[id2], isUpdatingAllCharts, id, id2, ); id = "chart-messages-sent-rate"; id2 = "chart-messages-received-rate"; this.updateOverviewChart( "chart-message-rate-overview", chartData[id], chartData[id2], isUpdatingAllCharts, id, id2, ); } mustInsertDatapointDueToInterval( lastUpdateDueToIntervalTimestamp, nowTimestampMilliseconds, ) { if ( lastUpdateDueToIntervalTimestamp === 0 || nowTimestampMilliseconds - lastUpdateDueToIntervalTimestamp >= CHART_UPDATE_INTERVAL_IN_MILLISECONDS ) { return true; } return false; } async checkForDataUpdates() { const nowTimestampMilliseconds = new Date().getTime(); let sysTopics = null; try { sysTopics = await fetchData(SYSTOPIC_ENDPOINT, { signal: this.abort.signal, cache: "no-store", }); this.version = sysTopics?.["$SYS/broker/version"]; this.previousDataFetchFailed = false; this.brokerOnline = true; this.setBrokerVersion(); this.setBrokerStatus(); } catch (error) { const errorMsg = `Error fetching sys topics: ${error?.message}`; if (this.abort.signal.aborted || error?.name === "AbortError") { console.log("Fetching systopics aborted"); } else { console.error(errorMsg); if (!this.previousDataFetchFailed) { alert(errorMsg); } } this.brokerOnline = false; this.setBrokerStatus(); this.previousDataFetchFailed = true; } if (!sysTopics) { return; } const [metricsToUpdate, chartsToUpdate] = this.getElementsToUpdate(sysTopics); this.dashboardDataObject.lastSysTopics = sysTopics; if (!this.headlessMode && metricsToUpdate) { Object.entries(metricsToUpdate).forEach(([id, value]) => { this.updateHtmlElementById(id, value); }); } let updateAllCharts = false; if ( this.mustInsertDatapointDueToInterval( this.dashboardDataObject.lastUpdateDueToIntervalTimestamp, nowTimestampMilliseconds, ) ) { updateAllCharts = true; this.dashboardDataObject.lastUpdateDueToIntervalTimestamp = nowTimestampMilliseconds; } if (chartsToUpdate || updateAllCharts) { this.updateCharts( chartsToUpdate || {}, this.dashboardDataObject, nowTimestampMilliseconds, updateAllCharts, ); let chartsIds; if (updateAllCharts) { const lastDataPointsOfAllCharts = this.getLastChartsDataPoints( this.dashboardDataObject, ); // importantly this gives us ids of all charts chartsIds = Object.keys(lastDataPointsOfAllCharts); } else if (chartsToUpdate) { chartsIds = Object.keys(chartsToUpdate); } else { chartsIds = []; // nothing to update } this.updateStore(this.dashboardDataObject, chartsIds); } } updateStore(dashboardDataObject, idsOfChartsToUpdate) { try { sessionStorage.setItem( "options", JSON.stringify(dashboardDataObject.options), ); sessionStorage.setItem( "sysTopics", JSON.stringify(dashboardDataObject.lastSysTopics), ); sessionStorage.setItem( "updateDueToIntervalTimestamp", JSON.stringify(dashboardDataObject.lastUpdateDueToIntervalTimestamp), ); for (const key of idsOfChartsToUpdate) { const chartData = dashboardDataObject.charts[key]; if (!chartData) { throw new Error( `dashboardDataObject.charts does not contain key ${key}`, ); } sessionStorage.setItem(key, JSON.stringify(chartData)); } } catch (error) { const errorMsg = `Error while updating sessionStorage`; console.error(errorMsg); throw new Error(errorMsg + ": " + error?.message); } } async startDataUpdates() { const checkForDataUpdatesWrapper = async () => { try { await this.checkForDataUpdates(); } catch (error) { const errorMsg = `Error while checking for dashboard data updates ${error?.message}. Reopen the page to try again.`; console.error(errorMsg); alert(errorMsg); throw error; } }; try { await checkForDataUpdatesWrapper(); } catch (error) { return; } // we assume that we want to perform data updates every 5 seconds const timestampNow = new Date().getTime(); const nextTimestampDivisibleBy5Seconds = INTERVAL_5SECS_IN_MILLISECONDS * Math.floor(timestampNow / INTERVAL_5SECS_IN_MILLISECONDS) + INTERVAL_5SECS_IN_MILLISECONDS; // integer division and then reconstruct the actual number const interval = nextTimestampDivisibleBy5Seconds - timestampNow; const doDataUpdate = async () => { clearTimeout(this.timeoutHandler); let startTs, endTs; try { startTs = Date.now(); await checkForDataUpdatesWrapper(); endTs = Date.now(); } catch (error) { return; } const executionTimeMs = endTs - startTs; this.timeoutHandler = setTimeout( // don't want anything to get into a contending state while animation is running, so wait a bit after doDataUpdate returns () => queue.enqueue( toAsyncAndWaitAfter( doDataUpdate, CHARTJS_ANIMATION_DURATION_MS + 50, ), ), INTERVAL_5SECS_IN_MILLISECONDS - executionTimeMs > 0 ? INTERVAL_5SECS_IN_MILLISECONDS - executionTimeMs : 0, ); }; this.timeoutHandler = setTimeout( () => queue.enqueue(doDataUpdate), interval, ); } } eclipse-mosquitto-mosquitto-691eab3/dashboard/src/app/index.js000066400000000000000000000054171514232433600246330ustar00rootroot00000000000000document.addEventListener("DOMContentLoaded", () => { new Sidebar(); new MosquittoDashboard(); }); function checkNormalBannerImage(bannerImage, bannerCard) { const imageSrc = "https://mosquitto.org/banner/image"; // no extension on the file - it can svg or png const probe = new Image(); probe.onload = () => { bannerImage.src = imageSrc; }; probe.onerror = () => { console.warn("Banner-image didn't loaded"); }; probe.src = imageSrc; } function checkSvgBannerImage(bannerImage, bannerCard, bannerLink, bannerInner) { // if a full fledged svg found, display it and remove the default link const svgSrc = "https://mosquitto.org/banner/image.svg"; const svgProbe = new Image(); svgProbe.onload = () => { bannerImage.src = svgSrc; // Only if the svg exists (was loaded successfully) make a call to fetch it and then inline it. Requires CORS to be set on svgSrc fetch(svgSrc) .then((r) => r.text()) .then((svg) => { bannerInner.innerHTML = svg; bannerLink.removeAttribute("href"); }) .catch((error) => console.warn("SVG banner-image couldn't be fetched:", error), ); }; svgProbe.onerror = () => { console.warn("SVG Banner-image didn't loaded"); }; svgProbe.src = svgSrc; } document.addEventListener("DOMContentLoaded", function () { const toggleButton = document.getElementById("layout-toggle"); const chartsGrid = document.getElementById("charts-grid"); const layoutText = document.getElementById("layout-text"); const bannerImage = document.getElementById("banner-img"); const bannerCard = document.getElementById("banner-card"); const bannerInner = document.getElementById("banner-inner"); const bannerLink = document.getElementById("banner-link"); checkNormalBannerImage(bannerImage, bannerCard); checkSvgBannerImage(bannerImage, bannerCard, bannerLink, bannerInner); let isGridView = true; let storedSetting = sessionStorage.getItem("isGridView"); const toggleView = () => { if (isGridView) { // switch to single column chartsGrid.className = "grid grid-cols-1 gap-4"; layoutText.textContent = "Grid View"; isGridView = false; } else { // switch back to grid chartsGrid.className = "grid grid-cols-1 lg:grid-cols-2 gap-4"; layoutText.textContent = "Single Column"; isGridView = true; } sessionStorage.setItem("isGridView", JSON.stringify(isGridView)); }; if (storedSetting) { storedSetting = JSON.parse(storedSetting); if (storedSetting === false) { // set isGridView from the default value of true to match the "false" coming from the session store by calling the toggle function queue.enqueue(toAsyncAndWaitAfter(toggleView)); } } toggleButton.addEventListener("click", toggleView); }); eclipse-mosquitto-mosquitto-691eab3/dashboard/src/app/listeners.js000066400000000000000000000223121514232433600255250ustar00rootroot00000000000000class Listeners { constructor() { this.abort = new AbortController(); registerAbortController(this.abort, this); this.init(); } async init() { try { const listeners = await fetchData(LISTENERS_ENDPOINT, { signal: this.abort.signal, cache: "no-store", }); this.displayListeners(listeners); } catch (error) { if ( this.pageHiding || this.abort.signal.aborted || error?.name === "AbortError" ) { console.log("Fetching listeners aborted"); } else { console.error("Error fetching listeners:", error); alert(`Error loading listeners: ${error}`); } } } displayListeners(data) { const listenersContainer = document.getElementById("listeners-container"); const brokerAnonymListenerCnt = document.getElementById( "broker-anonym-listener-cnt", ); const brokerAllListenerCnt = document.getElementById( "broker-all-listener-cnt", ); brokerAnonymListenerCnt.innerHTML = ""; brokerAllListenerCnt.innerHTML = ""; listenersContainer.innerHTML = ""; if (!data || !data.listeners) { listenersContainer.innerHTML = '

No listeners available

'; return; } const listeners = data.listeners; brokerAllListenerCnt.textContent = listeners.length; const anonymousCount = listeners.filter((l) => l.allow_anonymous).length; if (anonymousCount > 0) { const warningText = document.createElement("span"); warningText.textContent = anonymousCount; const warningBadge = document.createElement("span"); warningBadge.className = "ml-2 px-2 py-1 text-xs font-medium rounded-full"; warningBadge.style.backgroundColor = "#fee2e2"; // red-100 warningBadge.style.color = "#dc2626"; // red-600 warningBadge.textContent = "UNSAFE"; brokerAnonymListenerCnt.appendChild(warningText); brokerAnonymListenerCnt.appendChild(warningBadge); } else { brokerAnonymListenerCnt.textContent = "none"; } listeners.forEach((listener, index) => { const listenerCard = this.createListenerCard(listener, index); listenersContainer.appendChild(listenerCard); }); } createCommandSection(listener, type, addMargin = true) { const commandSection = document.createElement("div"); if (addMargin) { commandSection.className = "mt-4"; } const commandHeader = document.createElement("div"); commandHeader.style.display = "flex"; commandHeader.style.alignItems = "center"; commandHeader.style.justifyContent = "space-between"; commandHeader.style.marginBottom = "0.5rem"; const commandContainer = document.createElement("div"); commandContainer.style.display = "flex"; const copyButton = document.createElement("button"); copyButton.className = "p-2 hover:bg-c-orange transition-colors"; copyButton.style.cursor = "pointer"; copyButton.style.border = "0.1px solid #d3d3d3"; copyButton.title = "Copy"; // create an svg copy icon const copyIcon = document.createElementNS( "http://www.w3.org/2000/svg", "svg", ); copyIcon.setAttribute("width", "16"); copyIcon.setAttribute("height", "16"); copyIcon.setAttribute("viewBox", "0 0 24 24"); copyIcon.setAttribute("fill", "none"); copyIcon.setAttribute("stroke", "currentColor"); copyIcon.setAttribute("stroke-width", "2"); copyIcon.style.color = "#6b7280"; // gray-500 copyButton.addEventListener("mouseenter", () => { copyIcon.style.stroke = "white"; checkIcon.style.stroke = "white"; }); copyButton.addEventListener("mouseleave", () => { copyIcon.style.stroke = "#6b7280"; // back to gray for copy icon and green for the checkmark checkIcon.style.stroke = "#10b981"; }); const copyPath = document.createElementNS( "http://www.w3.org/2000/svg", "path", ); copyPath.setAttribute( "d", "M8 4H6a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V6a2 2 0 00-2-2h-2m-4-1v1m0 0V2a1 1 0 011-1h2a1 1 0 011 1v1m-4 0h4", ); copyPath.setAttribute("stroke-linecap", "round"); copyPath.setAttribute("stroke-linejoin", "round"); copyIcon.appendChild(copyPath); copyButton.appendChild(copyIcon); // create an svg checkmark icon for the success state after copying const checkIcon = document.createElementNS( "http://www.w3.org/2000/svg", "svg", ); checkIcon.setAttribute("width", "16"); checkIcon.setAttribute("height", "16"); checkIcon.setAttribute("viewBox", "0 0 24 24"); checkIcon.setAttribute("fill", "none"); checkIcon.setAttribute("stroke", "currentColor"); checkIcon.setAttribute("stroke-width", "2"); checkIcon.style.color = "#10b981"; // green-500 checkIcon.style.display = "none"; const checkPath = document.createElementNS( "http://www.w3.org/2000/svg", "path", ); checkPath.setAttribute("d", "M5 13l4 4L19 7"); checkPath.setAttribute("stroke-linecap", "round"); checkPath.setAttribute("stroke-linejoin", "round"); checkIcon.appendChild(checkPath); copyButton.appendChild(checkIcon); copyButton.addEventListener("click", async () => { try { const command = this.generateConnectionCommand(listener, type); copyToClipboard(command); copyIcon.style.display = "none"; checkIcon.style.display = "block"; copyButton.title = "Copied!"; setTimeout(() => { copyIcon.style.display = "block"; checkIcon.style.display = "none"; copyButton.title = "Copy command"; }, 2000); } catch (err) { console.error("Error when copying to clipboard:", err); } }); const commandText = document.createElement("pre"); commandText.className = "bg-gray-100 p-2 text-sm font-mono"; commandText.style.overflowX = "auto"; commandText.style.whiteSpace = "pre-wrap"; commandText.style.width = "100%"; commandText.style.wordBreak = "break-all"; commandText.textContent = this.generateConnectionCommand(listener, type); commandContainer.appendChild(commandText); commandContainer.appendChild(copyButton); commandSection.appendChild(commandContainer); return commandSection; } createListenerCard(listener, index) { const card = document.createElement("div"); card.className = "card p-4 border border-gray-200"; const title = document.createElement("h3"); title.className = "font-semibold mb-4"; title.textContent = `Listener ${index + 1}`; const details = document.createElement("div"); details.className = "grid gap-2 text-sm mb-4"; if (listener.port) { details.appendChild(this.createDetailRow("Port", listener.port)); } if (listener.path) { details.appendChild(this.createDetailRow("Unix Socket", listener.path)); } if (listener.protocol) { details.appendChild(this.createDetailRow("Protocol", listener.protocol)); } details.appendChild( this.createDetailRow("TLS", listener.tls ? "Yes" : "No"), ); details.appendChild( this.createDetailRow("mTLS", listener.mtls ? "Yes" : "No"), ); details.appendChild( this.createDetailRow( "Allow Anonymous", listener.allow_anonymous ? "Yes" : "No", ), ); let commandSectionPub; if (listener.protocol !== "httpapi") { commandSectionPub = this.createCommandSection(listener, "mosquitto_pub"); } card.appendChild(title); card.appendChild(details); commandSectionPub && card.appendChild(commandSectionPub); return card; } createDetailRow(label, value) { const row = document.createElement("div"); row.className = "flex items-center"; const labelSpan = document.createElement("span"); labelSpan.className = "text-gray-500 mr-2"; labelSpan.style.width = "120px"; labelSpan.style.display = "inline-block"; labelSpan.textContent = label + ":"; const valueSpan = document.createElement("span"); valueSpan.className = "px-3 py-1 text-xs font-medium rounded-full text-center"; valueSpan.style.display = "inline-block"; valueSpan.style.border = "0.1px solid #d3d3d3"; valueSpan.textContent = value; row.appendChild(labelSpan); row.appendChild(valueSpan); return row; } generateConnectionCommand(listener, commandType = "mosquitto_pub") { if (listener.protocol === "httpapi") { return "HTTP API Listener - use REST calls instead of mosquitto_pub/sub"; } let command = commandType; if (listener.path) { command += ` --unix ${listener.path}`; } else { command += ` -h -p ${listener.port}`; } if (listener.mtls) { command += " --cert --key "; } if (listener.tls) { command += " --cafile "; } if (listener.protocol === "websockets") { command += " --ws"; } if (!listener.allow_anonymous) { command += " -u -P "; } command += " -t "; if (commandType === "mosquitto_pub") { command += " -m "; } return command; } } document.addEventListener("DOMContentLoaded", () => { new Sidebar(); new Listeners(); new MosquittoDashboard(true); }); eclipse-mosquitto-mosquitto-691eab3/dashboard/src/app/sidebar.js000066400000000000000000000040051514232433600251250ustar00rootroot00000000000000class Sidebar { constructor() { this.menuToggle = document.getElementById("menu-toggle"); this.menuClose = document.getElementById("menu-close"); this.slidingMenu = document.getElementById("sliding-menu"); this.menuOverlay = document.getElementById("menu-overlay"); this.mainContent = document.getElementById("main-content"); this.root = document.documentElement; this.isOpen = sessionStorage.getItem("isSidebarOpen") === "true"; // !isMobile() becase we don't want the sidebar to be preloaded as open on mobile - there is no space anyway if (!isMobile() && this.isOpen) { this.openMenu(); // the initial open of the sidebar is hanlded by the inlined preload script but calling openMenu will properly set hamburger icon to be an arrow icon } this.bindEvents(); } bindEvents() { this.menuToggle.addEventListener("click", () => this.toggleMenu()); this.menuClose.addEventListener("click", () => this.closeMenu()); this.menuOverlay.addEventListener("click", () => this.closeMenu()); document.addEventListener("keydown", (e) => { if (e.key === "Escape" && this.isOpen) { this.closeMenu(); } }); window.addEventListener("resize", () => { this.syncUi(); }); } toggleMenu() { if (this.isOpen) { this.closeMenu(); } else { this.openMenu(); } } syncUi() { document .getElementById("hamburger-icon") .classList.toggle("hidden", this.isOpen); document .getElementById("arrow-icon") .classList.toggle("hidden", !this.isOpen); const showOverlay = this.isOpen && isMobile(); document.body.classList.toggle("sidebar-lock-scroll", showOverlay); } openMenu() { this.isOpen = true; sessionStorage.setItem("isSidebarOpen", "true"); this.root.classList.add("sidebar-open"); this.syncUi(); } closeMenu() { this.isOpen = false; sessionStorage.setItem("isSidebarOpen", "false"); this.root.classList.remove("sidebar-open"); this.syncUi(); } } eclipse-mosquitto-mosquitto-691eab3/dashboard/src/css/000077500000000000000000000000001514232433600231675ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/dashboard/src/css/styles.css000066400000000000000000000047701514232433600252340ustar00rootroot00000000000000@tailwind base; @tailwind components; @tailwind utilities; .bg-c-orange { background-color: #fd602e; } .hover\:bg-c-orange:hover { background-color: #fd602e; } #menu-overlay { transition: opacity 300ms; opacity: 0; pointer-events: none; background: rgba(0,0,0,0.45); } @media (min-width: 1024px) { #menu-overlay { display: none; } #sliding-menu { box-shadow: none; } #menu-close { visibility: hidden; } #broker-info-icon { display: block; } } #sliding-menu { transform: translateX(-100%); transition: transform 300ms; } .sidebar-open #sliding-menu { transform: translateX(0); } @media (max-width: 1023px) { .sidebar-open #menu-overlay { opacity: 1; pointer-events: auto; } } @media (min-width: 1024px) { .sidebar-open #main-content { margin-left: 320px; } } /* lock scroll on mobile when sidebar open */ .sidebar-lock-scroll { overflow: hidden; } .sidebar-preload #sliding-menu { transition: none; } @media (max-width: 1024px) { #layout-toggle { display: none; } } @media (max-width: 375px) { #logo-icon { display: block; } #logo-text { display: none } } .broker-active { background-color: rgb(34 197 94); /* green-500 */ } .broker-inactive { background-color: rgb(239 68 68); /* red-500 */ } @layer components { .card { @apply bg-white rounded-lg border border-gray-200 shadow-sm; } .card-header { @apply px-6 py-4 border-b border-gray-200; } .card-content { @apply px-6 py-4; } .metric-value { @apply text-3xl font-bold text-gray-900; } .metric-label { @apply text-sm font-medium text-gray-500 mb-2; } .status-dot { @apply w-3 h-3 rounded-full; } .chart-container { position: relative; height: 200px; width: 100%; } .nav-btn { @apply inline-flex items-center justify-center w-8 h-8 text-gray-500 bg-white border border-gray-300 rounded hover:bg-gray-50 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1 transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed; } .nav-btn:hover:not(:disabled) { @apply shadow-sm; } .nav-btn.active { @apply bg-blue-50 border-blue-300 text-blue-600; } .nav-separator { @apply w-px h-6 bg-gray-300 mx-2; } .nav-btn svg { pointer-events: none; } } eclipse-mosquitto-mosquitto-691eab3/dashboard/src/index.html000066400000000000000000002373511514232433600244070ustar00rootroot00000000000000 Mosquitto Broker Dashboard

Broker Information

version:
uptime: ?
status:
clients online
?
clients total
?
clients expired
?
clients offline
?
max simultaneous clients
?
connection sockets
?
total subscriptions
?
messages published to the broker
?
messages published by the broker
?
publishes dropped
?
messages retained
?
messages stored
?
messages dropped
online clients
offline clients
messages sent
messages received
messages sent per minute
messages received per minute

Published vs Received Messages Total

Publish vs Received Messages Rates

System Metrics

$SYS/broker/clients/total ?
$SYS/broker/clients/disconnected ?
$SYS/broker/clients/connected ?
$SYS/broker/clients/maximum ?
$SYS/broker/clients/expired ?
$SYS/broker/subscriptions/count ?
$SYS/broker/shared_subscriptions/count ?
$SYS/broker/heap/current ?
$SYS/broker/heap/maximum ?
$SYS/broker/connections/socket/count ?

Traffic Metrics

$SYS/broker/load/messages/received/1min ?
$SYS/broker/load/messages/received/10min ?
$SYS/broker/load/messages/received/15min ?
$SYS/broker/load/messages/sent/1min ?
$SYS/broker/load/messages/sent/10min ?
$SYS/broker/load/messages/sent/15min ?
$SYS/broker/messages/stored ?
$SYS/broker/retained messages/count ?
$SYS/broker/messages/received ?
$SYS/broker/messages/sent ?
$SYS/broker/store/messages/bytes ?
$SYS/broker/bytes/received ?
$SYS/broker/bytes/sent ?
$SYS/broker/publish/bytes/received ?
$SYS/broker/publish/bytes/sent ?
$SYS/broker/publish/messages/dropped ?
$SYS/broker/publish/messages/received ?
$SYS/broker/publish/messages/sent ?
$SYS/broker/packet/out/count ?
$SYS/broker/packet/out/bytes ?
eclipse-mosquitto-mosquitto-691eab3/dashboard/src/lib/000077500000000000000000000000001514232433600231455ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/dashboard/src/lib/chart.umd.js000066400000000000000000006173351514232433600254070ustar00rootroot00000000000000/*! * Chart.js v4.3.0 * https://www.chartjs.org * (c) 2023 Chart.js Contributors * Released under the MIT License */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Ko},get Decimation(){return Jo},get Filler(){return pa},get Legend(){return _a},get SubTitle(){return wa},get Title(){return va},get Tooltip(){return Va}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function W(t){return!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:h,max:c,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=J(Math.min(it(r,a.axis,h).lo,i?s:it(e,l,a.getPixelForValue(h)).lo),0,s-1)),o=u?J(Math.max(it(r,a.axis,c,!0).hi+1,i?0:it(e,l,a.getPixelForValue(c),!0).hi+1),n,s)-n:s-n}return{start:n,count:o}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class bt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var xt=new bt; /*! * @kurkle/color v0.3.2 * https://github.com/kurkle/color#readme * (c) 2023 Jukka Kurkela * Released under the MIT License */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Zt{constructor(t){if(t instanceof Zt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Zt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Jt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Jt(t)?t:new Zt(t)}function te(t){return Jt(t)?t:new Zt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function be(t,e){return me(t).getPropertyValue(e)}const xe=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=xe[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function Pe(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Je(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Je(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Je(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e))return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Ze(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Je(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Ze(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const bi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,xi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(bi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(xi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:Z,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hx||l(n,b,p)&&0!==r(n,b),v=()=>!x||0===r(o,p)||l(o,b,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==b&&(x=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,b=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r{t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Xi={evaluateInteractionItems:Hi,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tji(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Yi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>Ui(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>Ui(t,ve(e,t),"y",i.intersect,s)}};const qi=["left","top","right","bottom"];function Ki(t,e){return t.filter((t=>t.pos===e))}function Gi(t,e){return t.filter((t=>-1===qi.indexOf(t.pos)&&t.box.axis===e))}function Zi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function Ji(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!qi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function ss(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Zi(Ki(e,"left"),!0),n=Zi(Ki(e,"right")),o=Zi(Ki(e,"top"),!0),a=Zi(Ki(e,"bottom")),r=Gi(e,"x"),l=Gi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ki(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);ts(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=Ji(l.concat(h),d);ss(r.fullSize,g,d,p),ss(l,g,d,p),ss(h,g,d,p)&&ss(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),os(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,os(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class rs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class ls extends rs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const hs="$chartjs",cs={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ds=t=>null===t||""===t;const us=!!Se&&{passive:!0};function fs(t,e,i){t.canvas.removeEventListener(e,i,us)}function gs(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function ps(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.addedNodes,s),e=e&&!gs(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function ms(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.removedNodes,s),e=e&&!gs(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const bs=new Map;let xs=0;function _s(){const t=window.devicePixelRatio;t!==xs&&(xs=t,bs.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function ys(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){bs.size||window.addEventListener("resize",_s),bs.set(t,e)}(t,o),a}function vs(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){bs.delete(t),bs.size||window.removeEventListener("resize",_s)}(t)}function Ms(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=cs[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t.addEventListener(e,i,us)}(s,e,n),n}class ws extends rs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[hs]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ds(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(ds(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[hs])return!1;const i=e[hs].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[hs],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:ps,detach:ms,resize:ys}[e]||Ms;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:vs,detach:vs,resize:vs}[e]||fs)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=ge(t);return!(!e||!e.isConnected)}}function ks(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?ls:ws}var Ss=Object.freeze({__proto__:null,BasePlatform:rs,BasicPlatform:ls,DomPlatform:ws,_detectPlatform:ks});const Ps="transparent",Ds={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Ps),n=s.valid&&Qt(e||Ps);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class Cs{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Ds[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new Cs(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(xt.add(this._chart,i),!0):void 0}}function As(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Ts(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function zs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Vs(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Bs=t=>"reset"===t||"none"===t,Ns=(t,e)=>e?t:Object.assign({},t);class Ws{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Es(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Vs(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Fs(t,"x")),o=e.yAxisID=l(i.yAxisID,Fs(t,"y")),a=e.rAxisID=l(i.rAxisID,Fs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Vs(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e))this._data=function(t){const e=Object.keys(t),i=new Array(e.length);let s,n,o;for(s=0,n=e.length;s0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Ts(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Ns(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Os(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Bs(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Bs(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Bs(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function js(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for($s(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,Us=(t,e)=>Math.min(e||t,t);function Xs(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Ks(t){return t.drawTicks?t.tickLength:0}function Gs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Zs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class Js extends Hs{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=J(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Ks(t.grid)-e.padding-Gs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(J((h.highest.height+6)/o,-1,1)),Math.asin(J(a/r,-1,1))-Math.asin(J(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Gs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Ks(n)+o):(t.height=this.maxHeight,t.width=Ks(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Ks(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,b=function(t){return Ae(i,t,p)};let x,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)x=b(this.bottom),w=this.bottom-u,S=x-m,D=b(t.top)+m,O=t.bottom;else if("bottom"===a)x=b(this.top),D=t.top,O=b(t.bottom)-m,w=x+m,S=this.top+u;else if("left"===a)x=b(this.right),M=this.right-u,k=x-m,P=b(t.left)+m,C=t.right;else if("right"===a)x=b(this.left),P=t.left,C=b(t.right)-m,M=x+m,k=this.left+u;else if("x"===e){if("center"===a)x=b((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=x+m,S=w+u}else if("y"===e){if("center"===a)x=b((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}M=x-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class tn{constructor(){this.controllers=new Qs(Ws,"datasets",!0),this.elements=new Qs(Hs,"elements"),this.plugins=new Qs(Object,"plugins"),this.scales=new Qs(Js,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function nn(t,e){return e||!1!==t?!0===t?{}:t:null}function on(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function an(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function rn(t){if("x"===t||"y"===t||"r"===t)return t}function ln(t,...e){if(rn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&rn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function hn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function cn(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=an(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=ln(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return hn(t,"x",i[0])||hn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=x(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||an(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),x(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];x(e,[ue.scales[e.type],ue.scale])})),a}function dn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=cn(t,e)}function un(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const fn=new Map,gn=new Set;function pn(t,e){let i=fn.get(t);return i||(i=e(),fn.set(t,i),gn.add(i)),i}const mn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class bn{constructor(t){this._config=function(t){return(t=t||{}).data=un(t.data),dn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=un(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),dn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return pn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return pn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return pn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return pn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>mn(r,t,e)))),e.forEach((t=>mn(r,s,t))),e.forEach((t=>mn(r,re[n]||{},t))),e.forEach((t=>mn(r,ue,t))),e.forEach((t=>mn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),gn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=xn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||_n(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=xn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function xn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const _n=t=>o(t)&&Object.getOwnPropertyNames(t).reduce(((e,i)=>e||S(t[i])),!1);const yn=["top","bottom","left","right","chartArea"];function vn(t,e){return"top"===t||"bottom"===t||-1===yn.indexOf(t)&&"x"===e}function Mn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function wn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function kn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Sn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Pn={},Dn=t=>{const e=Sn(t);return Object.values(Pn).filter((t=>t.canvas===e)).pop()};function Cn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}class On{static defaults=ue;static instances=Pn;static overrides=re;static registry=en;static version="4.3.0";static getChart=Dn;static register(...t){en.add(...t),An()}static unregister(...t){en.remove(...t),An()}constructor(t,e){const s=this.config=new bn(e),n=Sn(t),o=Dn(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||ks(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new sn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Pn[this.id]=this,r&&l?(xt.listen(this,"complete",wn),xt.listen(this,"progress",kn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return en}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return xt.stop(this),this}resize(t,e){xt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=ln(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=ln(o,n),r=l(n.type,e.dtype);void 0!==n.position&&vn(n.position,a)===vn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(en.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{as.configure(this,t,t.options),as.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(Mn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{as.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){Cn(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;as.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=function(t){const{xScale:e,yScale:i}=t;if(e&&i)return{left:e.left,right:e.right,top:i.top,bottom:i.bottom}}(t)||this.chartArea,o={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",o)&&(s&&Ie(e,{left:!1===i.left?0:n.left-i.left,right:!1===i.right?this.width:n.right+i.right,top:!1===i.top?0:n.top-i.top,bottom:!1===i.bottom?this.height:n.bottom+i.bottom}),t.controller.draw(),s&&ze(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Xi.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),xt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function An(){return u(On.instances,(t=>t._plugins.invalidate()))}function Tn(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Ln{static override(t){Object.assign(Ln.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return Tn()}parse(){return Tn()}format(){return Tn()}add(){return Tn()}diff(){return Tn()}startOf(){return Tn()}endOf(){return Tn()}}var En={_date:Ln};function Rn(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function zn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nZ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>Z(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),b=g(C,h,d),x=g(C+E,c,u);s=(p-b)/2,n=(m-x)/2,o=-(p+b)/2,a=-(m+x)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),b=(i.width-o)/f,x=(i.height-o)/g,_=Math.max(Math.min(b,x)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var $n=Object.freeze({__proto__:null,BarController:class extends Ws{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return zn(t,e,i,s)}parseArrayData(t,e,i,s){return zn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=t=>{const i=t.controller.getParsed(e),n=i&&i[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!r(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(b-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);b=Math.max(Math.min(b,h),o),d=b+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(b))}if(b===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;b+=t,u-=t}return{size:u,base:b,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,l="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=b?g:{};if(i=x){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),b||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends Hn{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:jn,RadarController:class extends Ws{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>b,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),x||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Yn(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return J(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:J(n.innerStart,0,a),innerEnd:J(n.innerEnd,0,a)}}function Un(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function Xn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,b=n-p-f,{outerStart:x,outerEnd:_,innerStart:y,innerEnd:v}=Yn(e,u,d,b-m),M=d-x,w=d-_,k=m+x/M,S=b-_/w,P=u+y,D=u+v,O=m+y/P,A=b-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=Un(w,S,a,r);t.arc(e.x,e.y,_,S,b+E)}const i=Un(D,b,a,r);if(t.lineTo(i.x,i.y),v>0){const e=Un(D,A,a,r);t.arc(e.x,e.y,v,b+E,A+Math.PI)}const s=(b-v/u+(m+y/u))/2;if(t.arc(a,r,u,b-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=Un(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=Un(M,m,a,r);if(t.lineTo(n.x,n.y),x>0){const e=Un(M,k,a,r);t.arc(e.x,e.y,x,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function qn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u}=l,f="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,f?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let g=e.endAngle;if(o){Xn(t,e,i,s,g,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,g),o||(Xn(t,e,i,s,g,n),t.stroke())}function Kn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Gn(t,e,i){t.lineTo(i.x,i.y)}function Zn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[x(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[x(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(b*m+e)/++b):(_(),t.lineTo(e,i),u=s,b=0,f=g=i),p=i}_()}function to(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?Qn:Jn}const eo="function"==typeof Path2D;function io(t,e,i,s){eo&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Kn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=to(e);for(const r of n)Kn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class so extends Hs{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a)>=O||Z(n,a,r),g=tt(o,h+u,c+u);return f&&g}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){Xn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function go(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,b=!s(a),x=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!b&&!x)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),b&&x&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=b?a:M,w=x?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(b&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return x&&u&&w!==r?i.length&&V(i[i.length-1].value,r,po(r,y,t))?i[i.length-1].value=r:i.push({value:r}):x&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class bo extends mo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const xo=t=>Math.floor(z(t)),_o=(t,e)=>Math.pow(10,xo(t)+e);function yo(t){return 1===t/Math.pow(10,xo(t))}function vo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function Mo(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=xo(e);let o=function(t,e){let i=xo(e-t);for(;vo(t,e,i)>10;)i++;for(;vo(t,e,i)<10;)i--;return Math.min(i,xo(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:yo(g),significand:u}),s}class wo extends Js{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=mo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===_o(this.min,0)?_o(this.min,-1):_o(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(_o(i,-1)),o(_o(s,1)))),i<=0&&n(_o(s,-1)),s<=0&&o(_o(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=Mo({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function ko(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function So(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Po(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Co(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function Oo(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function Ao(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function To(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(ko(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/ko(this.options))}generateTickLabels(t){mo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Po(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));Ao(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;We(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash),o.lineDashOffset=n.dashOffset,o.beginPath(),To(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}We(t,s.label,0,-n,l,{color:r.color})})),t.restore()}drawTitle(){}}const Eo={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Ro=Object.keys(Eo);function Io(t,e){return t-e}function zo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!W(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Fo(t,e,i,s){const n=Ro.length;for(let o=Ro.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function Bo(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class No extends Js{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new En._date(t.adapters.date);s.init(e),x(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:zo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Fo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=Ro.length-1;o>=Ro.indexOf(i);o--){const i=Ro[o];if(Eo[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return Ro[i?Ro.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=Ro.indexOf(t)+1,i=Ro.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=J(s,0,o),n=J(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Fo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=W(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;dt-e)).map((t=>+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var Ho=Object.freeze({__proto__:null,CategoryScale:class extends Js{static id="category";static defaults={ticks:{callback:go}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:J(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:fo(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return go.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:bo,LogarithmicScale:wo,RadialLinearScale:Lo,TimeScale:No,TimeSeriesScale:class extends No{static id="timeseries";static defaults=No.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=Wo(e,this.min),this._tableRange=Wo(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot.replace("rgb(","rgba(").replace(")",", 0.5)")));function Yo(t){return jo[t%jo.length]}function Uo(t){return $o[t%$o.length]}function Xo(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof Hn?e=function(t,e){return t.backgroundColor=t.data.map((()=>Yo(e++))),e}(i,e):n instanceof jn?e=function(t,e){return t.backgroundColor=t.data.map((()=>Uo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Yo(e),t.backgroundColor=Uo(e),++e}(i,e))}}function qo(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Ko={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n;if(!i.forceOverride&&(qo(s)||(a=n)&&(a.borderColor||a.backgroundColor)||o&&qo(o)))return;var a;const r=Xo(t);s.forEach(r)}};function Go(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Zo(t){t.data.datasets.forEach((t=>{Go(t)}))}var Jo={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Zo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=J(it(e,o.axis,a).lo,0,i-1)),s=h?J(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Go(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const b=[],x=e+i-1,_=t[e].x,y=t[x].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&b.push({...t[e],x:p}),s!==u&&s!==i&&b.push({...t[s],x:p})}o>0&&i!==u&&b.push(t[i]),b.push(a),h=e,m=0,f=g=l,c=d=u=o}}return b}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Zo(t)}};function Qo(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ta(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function ea(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function ia(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ta(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new so({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function sa(t){return t&&!1!==t.fill}function na(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function oa(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function aa(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&ca(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;sa(i)&&ca(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;sa(s)&&"beforeDatasetDraw"===i.drawTime&&ca(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const ma=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class ba extends Hs{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ma(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=xa(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=ma(o,d),b=this.isHorizontal(),x=this._computeTitleHeight();f=b?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+x,line:0}:{x:this.left+c,y:ft(n,this.top+x+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),b?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+x+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,b?S+k:this.right,t.rtl),function(t,e,i){We(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),b)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=xa(y,t)}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,We(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class ya extends Hs{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);We(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var va={id:"title",_element:ya,start(t,e,i){!function(t,e){const i=new ya({ctx:t.ctx,options:e,chart:t});as.configure(t,i,e),as.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;as.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Ma=new WeakMap;var wa={id:"subtitle",start(t,e,i){const s=new ya({ctx:t.ctx,options:i,chart:t});as.configure(t,s,i),as.addBox(t,s),Ma.set(t,s)},stop(t){as.removeBox(t,Ma.get(t)),Ma.delete(t)},beforeUpdate(t,e,i){const s=Ma.get(t);as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const ka={average(t){if(!t.length)return!1;let e,i,s=0,n=0,o=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function Da(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Ca(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,b=0,x=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-g)*l.lineHeight+(x-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){b=Math.max(b,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),b+=p.width,{width:b,height:m}}function Oa(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Aa(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||Oa(t,e,i,s),yAlign:s}}function Ta(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:J(g,0,s.width-e.width),y:J(p,0,s.height-e.height)}}function La(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function Ea(t){return Sa([],Pa(t))}function Ra(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const Ia={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Ra(i,t);Sa(e.before,Pa(za(n,"beforeLabel",this,t))),Sa(e.lines,za(n,"label",this,t)),Sa(e.after,Pa(za(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return Ea(za(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=za(i,"beforeFooter",this,t),n=za(i,"footer",this,t),o=za(i,"afterFooter",this,t);let a=[];return a=Sa(a,Pa(s)),a=Sa(a,Pa(n)),a=Sa(a,Pa(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Ra(t.callbacks,e);s.push(za(i,"labelColor",this,e)),n.push(za(i,"labelPointStyle",this,e)),o.push(za(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=ka[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Ca(this,i),a=Object.assign({},t,e),r=Aa(this.chart,i,a),l=Ta(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,b,x,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,x=_+o,y=_-o):(p=d+f,m=p+o,x=_-o,y=_+o),b=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(x=u,_=x-o,p=m-o,b=m+o):(x=u+g,_=x+o,p=m+o,b=m-o),y=x),{x1:p,x2:m,x3:b,y1:x,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=La(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let b,x,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=La(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=ka[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Ca(this,t),a=Object.assign({},i,this._size),r=Aa(e,t,a),l=Ta(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e;const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=ka[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Va={id:"tooltip",_element:Fa,positioners:ka,afterInit(t,e,i){i&&(t.tooltip=new Fa({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Ia},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return On.register($n,Ho,uo,t),On.helpers={...Ni},On._adapters=En,On.Animation=Cs,On.Animations=Os,On.animator=xt,On.controllers=en.controllers.items,On.DatasetController=Ws,On.Element=Hs,On.elements=uo,On.Interaction=Xi,On.layouts=as,On.platforms=Ss,On.Scale=Js,On.Ticks=ae,Object.assign(On,$n,Ho,uo,t,Ss),On.Chart=On,"undefined"!=typeof window&&(window.Chart=On),On})); //# sourceMappingURL=chart.umd.js.map eclipse-mosquitto-mosquitto-691eab3/dashboard/src/lib/chartjs-plugin-zoom.min.js000066400000000000000000000355431514232433600302130ustar00rootroot00000000000000/*! * chartjs-plugin-zoom v2.2.0 * https://www.chartjs.org/chartjs-plugin-zoom/2.2.0/ * (c) 2016-2024 chartjs-plugin-zoom Contributors * Released under the MIT License */ !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("chart.js"),require("hammerjs"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","hammerjs","chart.js/helpers"],n):(t="undefined"!=typeof globalThis?globalThis:t||self).ChartZoom=n(t.Chart,t.Hammer,t.Chart.helpers)}(this,(function(t,n,e){"use strict";const o=t=>t&&t.enabled&&t.modifierKey,a=(t,n)=>t&&n[t+"Key"],i=(t,n)=>t&&!n[t+"Key"];function r(t,n,e){return void 0===t||("string"==typeof t?-1!==t.indexOf(n):"function"==typeof t&&-1!==t({chart:e}).indexOf(n))}function c(t,n){return"function"==typeof t&&(t=t({chart:n})),"string"==typeof t?{x:-1!==t.indexOf("x"),y:-1!==t.indexOf("y")}:{x:!1,y:!1}}function s(t,n,o){const{mode:a="xy",scaleMode:i,overScaleMode:r}=t||{},s=function({x:t,y:n},e){const o=e.scales,a=Object.keys(o);for(let e=0;e=i.top&&n<=i.bottom&&t>=i.left&&t<=i.right)return i}return null}(n,o),l=c(a,o),m=c(i,o);if(r){const t=c(r,o);for(const n of["x","y"])t[n]&&(m[n]=l[n],l[n]=!1)}if(s&&m[s.axis])return[s];const u=[];return e.each(o.scales,(function(t){l[t.axis]&&u.push(t)})),u}const l=new WeakMap;function m(t){let n=l.get(t);return n||(n={originalScaleLimits:{},updatedScaleLimits:{},handlers:{},panDelta:{},dragging:!1,panning:!1},l.set(t,n)),n}function u(t,n,e,o){const a=Math.max(0,Math.min(1,(t-n)/e||0));return{min:o*a,max:o*(1-a)}}function d(t,n){const e=t.isHorizontal()?n.x:n.y;return t.getValueForPixel(e)}function f(t,n,e){const o=t.max-t.min,a=o*(n-1);return u(d(t,e),t.min,o,a)}function p(t,n,o,a,i){let r=o[a];if("original"===r){const o=t.originalScaleLimits[n.id][a];r=e.valueOrDefault(o.options,o.scale)}return e.valueOrDefault(r,i)}function h(t,{min:n,max:o},a,i=!1){const r=m(t.chart),{options:c}=t,s=function(t,n){return n&&(n[t.id]||n[t.axis])||{}}(t,a),{minRange:l=0}=s,u=p(r,t,s,"min",-1/0),d=p(r,t,s,"max",1/0);if("pan"===i&&(nd))return!0;const f=t.max-t.min,h=i?Math.max(o-n,l):f;if(i&&h===l&&f<=l)return!0;const g=function(t,{min:n,max:o,minLimit:a,maxLimit:i},r){const c=(t-o+n)/2;n-=c,o+=c;const s=r.min.options??r.min.scale,l=r.max.options??r.max.scale,m=t/1e6;return e.almostEquals(n,s,m)&&(n=s),e.almostEquals(o,l,m)&&(o=l),ni&&(o=i,n=Math.max(i-t,a)),{min:n,max:o}}(h,{min:n,max:o,minLimit:u,maxLimit:d},r.originalScaleLimits[t.id]);return c.min=g.min,c.max=g.max,r.updatedScaleLimits[t.id]=g,t.parse(g.min)!==t.min||t.parse(g.max)!==t.max}const g=t=>0===t||isNaN(t)?0:t<0?Math.min(Math.round(t),-1):Math.max(Math.round(t),1);const x={second:500,minute:3e4,hour:18e5,day:432e5,week:3024e5,month:1296e6,quarter:5184e6,year:157248e5};function b(t,n,e,o=!1){const{min:a,max:i,options:r}=t,c=r.time&&r.time.round,s=x[c]||0,l=t.getValueForPixel(t.getPixelForValue(a+s)-n),m=t.getValueForPixel(t.getPixelForValue(i+s)-n);return!(!isNaN(l)&&!isNaN(m))||h(t,{min:l,max:m},e,!!o&&"pan")}function y(t,n,e){return b(t,n,e,!0)}const v={category:function(t,n,e,o){const a=f(t,n,e);return t.min===t.max&&n<1&&function(t){const n=t.getLabels().length-1;t.min>0&&(t.min-=1),t.maxc&&(a=Math.max(0,a-s),i=1===r?a:a+r,l=0===a),h(t,{min:a,max:i},e)||l},default:b,logarithmic:y,timeseries:y};function M(t,n){e.each(t,((e,o)=>{n[o]||delete t[o]}))}function k(t,n){const{scales:o}=t,{originalScaleLimits:a,updatedScaleLimits:i}=n;return e.each(o,(function(t){(function(t,n,e){const{id:o,options:{min:a,max:i}}=t;if(!n[o]||!e[o])return!0;const r=e[o];return r.min!==a||r.max!==i})(t,a,i)&&(a[t.id]={min:{scale:t.min,options:t.options.min},max:{scale:t.max,options:t.options.max}})})),M(a,o),M(i,o),a}function S(t,n,o,a){const i=v[t.type]||v.default;e.callback(i,[t,n,o,a])}function P(t,n,o,a){const i=w[t.type]||w.default;e.callback(i,[t,n,o,a])}function D(t){const n=t.chartArea;return{x:(n.left+n.right)/2,y:(n.top+n.bottom)/2}}function C(t,n,o="none",a="api"){const{x:i=1,y:r=1,focalPoint:c=D(t)}="number"==typeof n?{x:n,y:n}:n,l=m(t),{options:{limits:u,zoom:d}}=l;k(t,l);const f=1!==i,p=1!==r,h=s(d,c,t);e.each(h||t.scales,(function(t){t.isHorizontal()&&f?S(t,i,c,u):!t.isHorizontal()&&p&&S(t,r,c,u)})),t.update(o),e.callback(d.onZoom,[{chart:t,trigger:a}])}function Z(t,n,o,a="none",i="api"){const c=m(t),{options:{limits:s,zoom:l}}=c,{mode:u="xy"}=l;k(t,c);const d=r(u,"x",t),f=r(u,"y",t);e.each(t.scales,(function(t){t.isHorizontal()&&d?P(t,n.x,o.x,s):!t.isHorizontal()&&f&&P(t,n.y,o.y,s)})),t.update(a),e.callback(l.onZoom,[{chart:t,trigger:i}])}function j(t){const n=m(t);let o=1,a=1;return e.each(t.scales,(function(t){const i=function(t,n){const o=t.originalScaleLimits[n];if(!o)return;const{min:a,max:i}=o;return e.valueOrDefault(i.options,i.scale)-e.valueOrDefault(a.options,a.scale)}(n,t.id);if(i){const n=Math.round(i/(t.max-t.min)*100)/100;o=Math.min(o,n),a=Math.max(a,n)}})),o<1?o:a}function L(t,n,o,a){const{panDelta:i}=a,r=i[t.id]||0;e.sign(r)===e.sign(n)&&(n+=r);const c=z[t.type]||z.default;e.callback(c,[t,n,o])?i[t.id]=0:i[t.id]=n}function O(t,n,o,a="none"){const{x:i=0,y:r=0}="number"==typeof n?{x:n,y:n}:n,c=m(t),{options:{pan:s,limits:l}}=c,{onPan:u}=s||{};k(t,c);const d=0!==i,f=0!==r;e.each(o||t.scales,(function(t){t.isHorizontal()&&d?L(t,i,l,c):!t.isHorizontal()&&f&&L(t,r,l,c)})),t.update(a),e.callback(u,[{chart:t}])}function R(t){const n=m(t);k(t,n);const e={};for(const o of Object.keys(t.scales)){const{min:t,max:a}=n.originalScaleLimits[o]||{min:{},max:{}};e[o]={min:t.scale,max:a.scale}}return e}function E(t){const n=m(t);return n.panning||n.dragging}const F=(t,n,e)=>Math.min(e,Math.max(n,t));function N(t,n){const{handlers:e}=m(t),o=e[n];o&&o.target&&(o.target.removeEventListener(n,o),delete e[n])}function A(t,n,e,o){const{handlers:a,options:i}=m(t),r=a[e];if(r&&r.target===n)return;N(t,e),a[e]=n=>o(t,n,i),a[e].target=n;const c="wheel"!==e&&void 0;n.addEventListener(e,a[e],{passive:c})}function H(t,n){const e=m(t);e.dragStart&&(e.dragging=!0,e.dragEnd=n,t.update("none"))}function T(t,n){const e=m(t);e.dragStart&&"Escape"===n.key&&(N(t,"keydown"),e.dragging=!1,e.dragStart=e.dragEnd=null,t.update("none"))}function Y(t,n){if(t.target!==n.canvas){const e=n.canvas.getBoundingClientRect();return{x:t.clientX-e.left,y:t.clientY-e.top}}return e.getRelativePosition(t,n)}function q(t,n,o){const{onZoomStart:a,onZoomRejected:i}=o;if(a){const o=Y(n,t);if(!1===e.callback(a,[{chart:t,event:n,point:o}]))return e.callback(i,[{chart:t,event:n}]),!1}}function V(t,n){if(t.legend){const o=e.getRelativePosition(n,t);if(e._isPointInArea(o,t.legend))return}const r=m(t),{pan:c,zoom:s={}}=r.options;if(0!==n.button||a(o(c),n)||i(o(s.drag),n))return e.callback(s.onZoomRejected,[{chart:t,event:n}]);!1!==q(t,n,s)&&(r.dragStart=n,A(t,t.canvas.ownerDocument,"mousemove",H),A(t,window.document,"keydown",T))}function X(t,n,e,{min:o,max:a,prop:i}){t[o]=F(Math.min(e.begin[i],e.end[i]),n[o],n[a]),t[a]=F(Math.max(e.begin[i],e.end[i]),n[o],n[a])}function B(t,n,e){const o={begin:Y(n.dragStart,t),end:Y(n.dragEnd,t)};if(e){!function({begin:t,end:n},e){let o=n.x-t.x,a=n.y-t.y;const i=Math.abs(o/a);i>e?o=Math.sign(o)*Math.abs(a*e):i=0?2-1/(1-s):1+s;C(t,{x:l,y:l,focalPoint:{x:n.clientX-c.left,y:n.clientY-c.top}},"zoom","wheel"),e.callback(a,[{chart:t}])}function U(t,n,o,a){o&&(m(t).handlers[n]=function(t,n){let e;return function(){return clearTimeout(e),e=setTimeout(t,n),n}}((()=>e.callback(o,[{chart:t}])),a))}function _(t,n){return function(r,c){const{pan:s,zoom:l={}}=n.options;if(!s||!s.enabled)return!1;const m=c&&c.srcEvent;return!m||(!(!n.panning&&"mouse"===c.pointerType&&(i(o(s),m)||a(o(l.drag),m)))||(e.callback(s.onPanRejected,[{chart:t,event:c}]),!1))}}function G(t,n,e){if(n.scale){const{center:o,pointers:a}=e,i=1/n.scale*e.scale,c=e.target.getBoundingClientRect(),s=function(t,n){const e=Math.abs(t.clientX-n.clientX),o=Math.abs(t.clientY-n.clientY),a=e/o;let i,r;return a>.3&&a<1.7?i=r=!0:e>o?i=!0:r=!0,{x:i,y:r}}(a[0],a[1]),l=n.options.zoom.mode;C(t,{x:s.x&&r(l,"x",t)?i:1,y:s.y&&r(l,"y",t)?i:1,focalPoint:{x:o.x-c.left,y:o.y-c.top}},"zoom","pinch"),n.scale=e.scale}}function J(t,n,e){const o=n.delta;o&&(n.panning=!0,O(t,{x:e.deltaX-o.x,y:e.deltaY-o.y},n.panScales),n.delta={x:e.deltaX,y:e.deltaY})}const Q=new WeakMap;function $(t,o){const a=m(t),i=t.canvas,{pan:r,zoom:c}=o,l=new n.Manager(i);c&&c.pinch.enabled&&(l.add(new n.Pinch),l.on("pinchstart",(n=>function(t,n,o){if(n.options.zoom.pinch.enabled){const a=e.getRelativePosition(o,t);!1===e.callback(n.options.zoom.onZoomStart,[{chart:t,event:o,point:a}])?(n.scale=null,e.callback(n.options.zoom.onZoomRejected,[{chart:t,event:o}])):n.scale=1}}(t,a,n))),l.on("pinch",(n=>G(t,a,n))),l.on("pinchend",(n=>function(t,n,o){n.scale&&(G(t,n,o),n.scale=null,e.callback(n.options.zoom.onZoomComplete,[{chart:t}]))}(t,a,n)))),r&&r.enabled&&(l.add(new n.Pan({threshold:r.threshold,enable:_(t,a)})),l.on("panstart",(n=>function(t,n,o){const{enabled:a,onPanStart:i,onPanRejected:r}=n.options.pan;if(!a)return;const c=o.target.getBoundingClientRect(),l={x:o.center.x-c.left,y:o.center.y-c.top};if(!1===e.callback(i,[{chart:t,event:o,point:l}]))return e.callback(r,[{chart:t,event:o}]);n.panScales=s(n.options.pan,l,t),n.delta={x:0,y:0},J(t,n,o)}(t,a,n))),l.on("panmove",(n=>J(t,a,n))),l.on("panend",(()=>function(t,n){n.delta=null,n.panning&&(n.panning=!1,n.filterNextClick=!0,e.callback(n.options.pan.onPanComplete,[{chart:t}]))}(t,a)))),Q.set(t,l)}function tt(t){const n=Q.get(t);n&&(n.remove("pinchstart"),n.remove("pinch"),n.remove("pinchend"),n.remove("panstart"),n.remove("pan"),n.remove("panend"),n.destroy(),Q.delete(t))}function nt(t,n,e){const o=e.zoom.drag,{dragStart:a,dragEnd:i}=m(t);if(o.drawTime!==n||!i)return;const{left:r,top:c,width:s,height:l}=K(t,e.zoom.mode,{dragStart:a,dragEnd:i},o.maintainAspectRatio),u=t.ctx;u.save(),u.beginPath(),u.fillStyle=o.backgroundColor||"rgba(225,225,225,0.3)",u.fillRect(r,c,s,l),o.borderWidth>0&&(u.lineWidth=o.borderWidth,u.strokeStyle=o.borderColor||"rgba(225,225,225)",u.strokeRect(r,c,s,l)),u.restore()}var et={id:"zoom",version:"2.2.0",defaults:{pan:{enabled:!1,mode:"xy",threshold:10,modifierKey:null},zoom:{wheel:{enabled:!1,speed:.1,modifierKey:null},drag:{enabled:!1,drawTime:"beforeDatasetsDraw",modifierKey:null},pinch:{enabled:!1},mode:"xy"}},start:function(t,o,a){m(t).options=a,Object.prototype.hasOwnProperty.call(a.zoom,"enabled")&&console.warn("The option `zoom.enabled` is no longer supported. Please use `zoom.wheel.enabled`, `zoom.drag.enabled`, or `zoom.pinch.enabled`."),(Object.prototype.hasOwnProperty.call(a.zoom,"overScaleMode")||Object.prototype.hasOwnProperty.call(a.pan,"overScaleMode"))&&console.warn("The option `overScaleMode` is deprecated. Please use `scaleMode` instead (and update `mode` as desired)."),n&&$(t,a),t.pan=(n,e,o)=>O(t,n,e,o),t.zoom=(n,e)=>C(t,n,e),t.zoomRect=(n,e,o)=>Z(t,n,e,o),t.zoomScale=(n,o,a)=>function(t,n,o,a="none",i="api"){const r=m(t);k(t,r),h(t.scales[n],o,void 0,!0),t.update(a),e.callback(r.options.zoom?.onZoom,[{chart:t,trigger:i}])}(t,n,o,a),t.resetZoom=n=>function(t,n="default"){const o=m(t),a=k(t,o);e.each(t.scales,(function(t){const n=t.options;a[t.id]?(n.min=a[t.id].min.options,n.max=a[t.id].max.options):(delete n.min,delete n.max),delete o.updatedScaleLimits[t.id]})),t.update(n),e.callback(o.options.zoom.onZoomComplete,[{chart:t}])}(t,n),t.getZoomLevel=()=>j(t),t.getInitialScaleBounds=()=>R(t),t.getZoomedScaleBounds=()=>function(t){const n=m(t),e={};for(const o of Object.keys(t.scales))e[o]=n.updatedScaleLimits[o];return e}(t),t.isZoomedOrPanned=()=>function(t){const n=R(t);for(const e of Object.keys(t.scales)){const{min:o,max:a}=n[e];if(void 0!==o&&t.scales[e].min!==o)return!0;if(void 0!==a&&t.scales[e].max!==a)return!0}return!1}(t),t.isZoomingOrPanning=()=>E(t)},beforeEvent(t,{event:n}){if(E(t))return!1;if("click"===n.type||"mouseup"===n.type){const n=m(t);if(n.filterNextClick)return n.filterNextClick=!1,!1}},beforeUpdate:function(t,n,e){const o=m(t),a=o.options;o.options=e,function(t,n){const{pan:e,zoom:o}=t,{pan:a,zoom:i}=n;return o?.zoom?.pinch?.enabled!==i?.zoom?.pinch?.enabled||e?.enabled!==a?.enabled||e?.threshold!==a?.threshold}(a,e)&&(tt(t),$(t,e)),function(t,n){const e=t.canvas,{wheel:o,drag:a,onZoomComplete:i}=n.zoom;o.enabled?(A(t,e,"wheel",I),U(t,"onZoomComplete",i,250)):N(t,"wheel"),a.enabled?(A(t,e,"mousedown",V),A(t,e.ownerDocument,"mouseup",W)):(N(t,"mousedown"),N(t,"mousemove"),N(t,"mouseup"),N(t,"keydown"))}(t,e)},beforeDatasetsDraw(t,n,e){nt(t,"beforeDatasetsDraw",e)},afterDatasetsDraw(t,n,e){nt(t,"afterDatasetsDraw",e)},beforeDraw(t,n,e){nt(t,"beforeDraw",e)},afterDraw(t,n,e){nt(t,"afterDraw",e)},stop:function(t){!function(t){N(t,"mousedown"),N(t,"mousemove"),N(t,"mouseup"),N(t,"wheel"),N(t,"click"),N(t,"keydown")}(t),n&&tt(t),function(t){l.delete(t)}(t)},panFunctions:z,zoomFunctions:v,zoomRectFunctions:w};return t.Chart.register(et),et})); eclipse-mosquitto-mosquitto-691eab3/dashboard/src/lib/hammer.min.js000066400000000000000000000504361514232433600255460ustar00rootroot00000000000000/*! Hammer.JS - v2.0.7 - 2016-04-22 * http://hammerjs.github.io/ * * Copyright (c) 2016 Jorik Tangelder; * Licensed under the MIT license */ !function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance Mosquitto Broker Dashboard

Listener Information

Number of listeners: ?
Anonymous listeners: none
eclipse-mosquitto-mosquitto-691eab3/dashboard/src/media/000077500000000000000000000000001514232433600234565ustar00rootroot00000000000000eclipse-mosquitto-mosquitto-691eab3/dashboard/src/media/banner.svg000066400000000000000000000072501514232433600254500ustar00rootroot00000000000000Brought to you byThe Mosquitto Team,Cedalo, and the community eclipse-mosquitto-mosquitto-691eab3/dashboard/src/media/favicon-16x16.png000066400000000000000000000016231514232433600263760ustar00rootroot00000000000000PNG  IHDR(-SgAMA a cHRMz&u0`:pQ<PLTE6QDUTRwxa_ c{LmIgbnTKG0-i8bKGDH pHYssst"}tIME|-IDAT]PC#TT{s3UY%B]1>¢D$ OR"N1(tThTʤS 9uV( L#EIQF+(J0-U8j2QZ`hu;Ԇ0lXgS>џVk`p85 noyd'3<,%tEXtdate:create2017-05-14T22:07:27+02:00<%tEXtdate:modify2017-05-14T22:07:27+02:00MHtEXtSoftwarewww.inkscape.org<WzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`eclipse-mosquitto-mosquitto-691eab3/dashboard/src/media/favicon-32x32.png000066400000000000000000000015101514232433600263650ustar00rootroot00000000000000PNG  IHDR DgAMA asRGBPLTELiq9Q9Q9Q9Qvbp9Q8Y9Q9P9QZ[r9Q7O9Q:Q9Q:P9Q9Q8Qx8Q7Q9Q9Q9O9P9P8Q8Q8Rw9Qww8Q9Rywxywywx9Q|T.tRNS<v SC7\2f$#ɛN1CVh{] pHYs}IDAT8Sio0\CBҤ1uעU귪,/1 ,õvp5 N+ǁr8%6BEBM9[4I 2D՟) EGGI!8Tdp6GI*Z]bo~n Y:(wJvD5w :P;3?rVՕB巤wjQWl &Hw/Uu&+y42 $]$՜~MV+9aDWwEiy|G&qC7K^^(9MgQS H"_.&j k@"1`WW-kv"UԽ3@9ۖkSr]F(<!^tNp8vⰌ |%8˲^hX5kn}Q8e/4Ms8#/a LuVv/{W<؎ &tqGz'w]p;,f:AGot}<ݐzw)`KAOذ1'xX_hp8yEzrrIanyOrZ歞hc\> c@r87Ò,~4s8|>5˺G>9-W--<ܜ4|;:t49+J/aTp8[ 65GxBciyK< usSJk.ΧMpr~.0+ܕ7+Tyk'Y^30U$W9p8]gyg JP2P;Mu懹ر'QVfC1l>1D1G7c6Bp8z\?!:o噬W|x$ZOے܊N: M}Lg ;TDp8vʚo JיCNHb,}}eɝ_kk̘*32(+0c+g-l(4'r?v4˜:y< -&HYId|EoJU;:G.$+ (,6NUǙRUgl:eN4-$+/\vFd؊D87bثL ;p4; F a^. ׽h -a @CɈ&nEǔU(؝~*'Œ'3$znOS̊'1+?!0&M=qW.pҊ묎Mcy1~Ms0p8r&+\ 9|icꈔ|mRfmLB{{6qjziҘ2?N~cm`9y(|gޛ co-s(]UN?jM󧬳 3i/]oMqZ~_,?~QwVʪ^vmSd1QCƢyiU~1υ'3;aeiii˶:d5(emID3"YYDŏ2C 83^+&3Y  |4hw c'JMc P ={oذ 9[(72\T[8vM:9,ehmee&|~c{s9&< Omns zgt9! 5s_>suӞ:H(hh5B6F q8\IכEm:?U'`Yg.ۤ١|uety6jqÆ߆H8% 4;XOiE)]d 0WG͑XO(5|։nX%&۳p䌿oG9-<8}J'EE ̚ɟWEFV frPBI-GOHל<]'+kβ`2ޅg@V']oD8>g;p8ݡd7mp8qeۧhv{Uec D䮈re-9_ELV 5m5W{ݙz{^_u8j2ՙǔϼ+Wuupqͥ^Kaxgĵ(+3}Cf?P Qiր oQ†P2 HEiQ/+/{/44k%׎dxS\^lTU}s=7N3%Xΐ_G`o^IrxCm%fxS\P$_Et`QBUGx@|~Xk}ɺ)~X~Y DlsoGcsʦ|6v9Jn[nS1=6V2izduvm=b䜀 gJ i-<6 "t'z+i((NB\Ir^گB*q; Ky_kӵfYOmk:,o"./<3y"ǔܻPZV[Xo~vlxq#>fnߢ ؊kȼg*h7[+ nr\ \ ܖ(y)0DW<:+eur^wlE (j  <•Mu/a| C<j5o/1>N+T ct*Zf"YaN1_)390FBoo˸.,%-"SF^}oHvkz3Z& (cgFy=r3Ί3J+jQ@`>zjh0km؁T XJVXwDə<5ʄ>-p{f}qVVW Jr}7ؔVTfNϜ)V%w׎?R g># sGm\sA`=CU2~b뺌jЛ>tXѷ6%1`d8W<EO9T?&+ zޗU=/{bpeaIno M9v;BOOsňno8vk$YYs.3ux7 =5'cO[p)꫷(SGD`8K+k"Y^MUƃ6[jYm'zZ#NYoؔaݔ޴+= F!!YYsJ;;O;U;oCXD Oed<,hiBtj4߼f;=!6&jS/͖?49 Jh_۶R(^nKъ2HC=>."Jm nW8S 7VT.%w5KݴQX[nA.q֡r?X IU*=?ez핀N薳r{Q!n-q4=8z^Gvlw n/J&b ;/[]|_!nd #%0C+U]n湽NB쏈'pA?n=]e|O4;vL̽[3U=8,7tIwmԢө;,xwзq \2&h%k|De26CM#\{n!bp=]h^W䬦T6^&݅5j,e70v;?y@awu9M }Wy+V?~~ȓ>pXAp-pv9vQM2c <*巴fZ(5)`Ri%DZ"t/ OK yBe*loR@ZצEr7'3xPP|ND<9uS',S$cA~e?ֶdyj[Ė'jH[,WJGlr wD)Yf@l){x };^LY zs."mI";oEcө_yx8+%T:Y/EVЬW?dpS>8s,At|@SӚ~b4mcS2fQ:߂G8M, /ka35!!z\a#m\v!Ha*maʏ7{Z4߼&a&_h"Y#l:k~vy8bL+D+(S;^$^,ED {RN Xg>%ئ<\_JVD֛)3;;*6 4[7J.~AiKk2j!`ᮦnZ\{Br֫O}m4~3 Xc' Ke58r?O;G>b][&5UOhp_Xi+ [HHhMEêvczEHHNbA[nʩqT (_]on{<~Hj#{=Grhrn7.PUL! 8=7x<.ձ~GzlK,ކ@hT'll)q]޵q!]oV>TVeD{9 (ULܛEUcWl':5$NyBmמQo,]{;K(Ķ`)0k08cj G6!jxä6}<]#",b//\& $qxlV[fwhY`X1 $nSہsKss&PzwwDjꈈ5(|,\7͝Q;۩qTCgJ+w 1UUÀ^丮o>VQGyhU;yX !dk.?B*G09!n񰇞a((idY?ŵW[";"Qd)Qv u*/d-_)J1}n&Y4-Hye)nӚMDjmA5iq+YB!w6e7=YnrGDB#] 5rHU Gv.W$T{;B/&- tl'A{77߰/.!J 5]v;&~ 4^sp?K#hyY÷V2ʒ!<9/2YU›=9YEc;,Aݨچ8, 6npbcm~TT3YfEZ\;oi`poّ+FM fMX42eS௶/b;nX6/|DmU]xmp@^]~1U/v󯼢=zK κD_۷;)Xx(.R0ϼXZz%L6˽x]Llכ5h(wk5m/G}n(a^$S-[o@s\ްFX$"¢}S{ؖ, pdd6YmdsCաBz").#*=煟yi7>;U+I`+KkMK:e i(: ntҖR\ɖk`!vBy]B G;"1#Bq=FďZ'W"",DwhH6d]-$ 6"al<2pk ;(׭,].D7LqgYCwQqelG.x/XbTtߴO9))33;ec*M͞OV餁A\mRBrWK/B݉B*gq>:ln(0xjUU ψG}S4&d/b;A8-~\HBkP䲉fPjV pv$\.y;KaO_1ǕA㏖5@<ژ2cEt\ },_w"Q:)I9t ~j"U?(-ZE$ hTb:",;ĖPЌMۖoy.Xv$D$׭wق+ jijk_{'ЮG-Iqr ų^Oɶ`7y`j÷] SV#z 0Ց3qRMx-w&m̕ƺ7XӉ_Qӆr֨LA//{I"D0;0q;_$s oq@Yz _n%V\rc˫Z'VU:D{!~\~#"ʡY94Z,أX%\:ߓ 4 ~/y* qYhWBe]+z(qNJZ#>=z3{L {K1aOMk/mŖ@;)YiNgs je=rZKC ;6Dؓ`B+*cD|ƈݢpOOL/\9>|KkMKcfsTU 筛| ayݠYˏݐm-Ɨ#\cP0Z6eO^*Mmc|UF"`̡ؓsdRó- XoF؟#nо(m%FT(CnX6!X%ܹZ2U! 8 aڌQĒFD|ƞ_HCXᅁfkT~O+SJݞl\u%59ƦSvbSQ/\uWZrdkB7 ߋflWU{n۲ʽqJ= 84'y!{DNڊ^ludHmPc~!wAO̿ڣnGE8,jodyT!-'Ql}O> $pϞ^v'܏IDcemc;M*Tgr[󩴼(u3OmW_dI{ӘH6Xc_iE^>2Vtx.gz{!M ˪ZHLi8QS3YT|)UMItV\1/^@J+WlvDMuտ ;8vpuZJKpZķ&YJlQ\ޣ)e'+Rf%l?&7 1b-K0}ѻ40,S(L 53"5X s EY\ZaV)мfGD M-I]W?]C;`EK+] TP=r+-ܕnw2T7mif2\\ݖ/v=7)Yoj0? ePŵWezYiuEiOE(O%oȄlIgy ~8+ہrJ{ϣ.)Ts<]HN {{l=!Dz2@/5q>dQyÃĂi7[ZYs Ƚ;2̭=#V~YA7 *1K<|oC>Z{!]W}}m{C[)_xk| nؚ05ՙ%TߓY+֛ a{h3sDVd@{dh&hÒ#sUZEӬ3EFE*?43<9rP`t_]sx2PNoiƞOTmq[B EGv{QY;u=cQl6w- \Y3Ek|| rqc}xegpmIԮE9,N~u+ξkYR5ÚXo~| oƄϨ(mrSUhhrgG,bLE9,;TknSam%h@{ܛ>Jgw\e`({Q#l+}.d=?(C)늛UJU,[V:,^Txv͏.ig^Mxaǒ5!adMDآԴ?JۓGV?f)[J; ĉi`s EdjW( eL*QxI&,_FD#ʡ9 6IC4<%&.i:vk]T q>+5Uώ֬Fb-0mJk& a;?P{u'V. gÎA޹>9)egڪ Z*O|X ӣA|]ۃ(I6L, wjr/CB=fSΜشG*Stw]}Sշe)jr-wF:f`m&HjB(0t ҁe&WUvʕW/]8,1v+iO4q"oE>ٽl:͞ 2_Jך]S?d" fEYIF˦?K+tA(x?p1ɪpNŵ&oO,0k9y"K#Hq|c\ouWcW?ɠ]#pCV qzm':w?~nz. qxBOߣ(|z=vB1ݕuŦi&W6sPcܸ+o{s!|F3F7 K}WoH. Cso&ce_/fp5ݮoJǁO ?b{YN*UZXaꁣ6s?;1uH է6L~|#t~ﮟ:rɠ^N RW\Y?T8{W-lBy O겞?]v"/pR{xA"(wKq2]oSiDDH(mջ"SO]}ͶQ c,[;zskᲉKJim=eݔWn#L'8eמ,7Dx8$  xyl_d-maԧSAڱS#c'xGu$04@|ݬ*x[Yh5˪A#" Q(a#j]E+OZ| yk#/ "2O5̛"^만8RQu07J%fkRU< a1DyO he-GxGsWx5}%*xUX!% {f_i&Yi~E=/?aVA9i{[dݔ߀m 5pku˪YRաYc=f&P#rvGOx#k'G:<(_ZOZHñY2G؟>P].Œ|^*ܖ>vxtj &+ >〨8)U=6 |o$k}lq81(O˷&AUغ;{tm!p"Jj7oz` LiJzZ!)6U -4ԚoN[BpObq6T\Uĕ,:;W#&٦s}#zp6tCkҦ 'Z4B8ptxbȁVؘ2z.ʚ[V^fiiI MW V6ˬ&ή- GPt&>llݔBKc!kDpRG~uA9S!vrXVɎOHt\r!eȮF9ޣhk$HBNJ{GQNBX'U䪏--i3iqMWn+aͭTL҃\H]p:f? >Eԫ(KP%\cw f~ZxXk>֎/_7mm\o^nrn(MJ<95*yYᱦT1Nv*ߊXORl 0bNz<³% gJǮN4:D8֡ۀ69z. d{E੬Rh5o$ލt"4έcG÷غ ̵@>iQp8f^#W&dKohq]_p% ۞X:$uԑty@+ St|`{NJowˢyDX|aa5_:xwV8%@Ӗ1j|Xݚ:O:<_Y\3@ wo|,ccuXͽ{a(\RY%DZt6­9wɝ_kF KךͭjdhD@QI>9ysl면u-^%:4"@񸂈< Ao)`B{@[^L"`l|'j>Awo6&VQ|>JP/YiN B$xG˭:ñ^0(8 N ױ'a!=4:HKj0Ϭvy+;`2 *h7e;MDkm,: 'l8%:b9,x5b e;nIvKYIxp/`MmLG^<*` Myf_pb;,O,0kLV*Vs{jXqK@vKC:HYWG$b zg)Tp{ty'jR:)IF /IDAT.簔V/|;j\p8vn%L'N ױۓ覆3a`*+3 .A}S^7p:RKv𿖟4$\NK. dNsI+cȂmN"|@:VpݘuSF.Y7ۣG:΍^2mp8E",'*v؂ܲ|+~p8pKgV:p8 tBV!fYp8+aY%p8NsXCTVᶄةpKgVp86`gC