30 Commits

Author SHA1 Message Date
43845343a2 CI: update Codecov action to v5
Reviewers: korina

Reviewed By: korina

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D33801
2025-02-18 12:03:43 +01:00
3dfe07546f Upload coverage report to boostorg Codecov page
Summary:
https://docs.codecov.com/docs/codecov-tokens#uploading-without-a-token
> If you wish to do a tokenless upload, there is no need to generate or set any tokens or secrets on your project or your workflow. **You can still send in a token, but if the request qualifies as a tokenless upload, the token sent with your request will not be checked.**

Reviewers: korina

Reviewed By: korina

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D33794
2025-02-18 10:48:22 +01:00
c752845144 Merge branch 'master' into develop 2025-02-17 14:58:19 +01:00
684277dc40 Fix async prefix in examples
Summary: related to T15996

Reviewers: iljazovic

Reviewed By: iljazovic

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D33773
2025-02-17 13:27:13 +01:00
40ad372736 Bump copyright year
Summary:
related to T15996

updates copyright year to 2025

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33759
2025-02-17 10:47:39 +01:00
37257354cc docs workflow fix
Reviewers: korina

Reviewed By: korina

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D33736
2025-02-13 15:48:29 +01:00
b362be6a64 coverage and docs workflows
Summary: Relates to T15996

Reviewers: korina

Reviewed By: korina

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D33719
2025-02-13 13:38:08 +01:00
4b3b710300 Add Windows builds to CI
Summary: related to T15996

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33712
2025-02-13 13:31:31 +01:00
7d18f20e56 Add Jamfile, make CMake scripts Boost compliant
Summary: related to T15996

Reviewers: ivica, korina

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33480
2025-02-12 15:54:14 +01:00
53fe64d015 boost/mqtt5 in coverage
Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Differential Revision: https://repo.mireo.local/D33488
2025-02-03 11:59:21 +01:00
8b41f0dd4d Gracefully shutdown Websocket and TLS streams
Summary:
Resolves #18
Ref T15241

Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Maniphest Tasks: T15241

Differential Revision: https://repo.mireo.local/D33395
2025-01-30 18:18:05 +01:00
4df9dbbb07 Fix build
Summary: related to T15996

Reviewers: iljazovic

Reviewed By: iljazovic

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D33388
2025-01-27 10:31:57 +01:00
b42014f379 Simplify document generation and replace Async.MQTT5 with Boost.MQTT5 in docs
Summary:
related to T15996

- docs are now buildable with b2 (in a very simplified way) like mysql docs
- change Async.MQTT5 -> Boost.MQTT5, async_mqtt5 namespace to boost::mqtt5 namespace

- once changes are approved, Ill update all tabs to 4 spaces in .qbk files

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33373
2025-01-27 08:22:13 +01:00
9a0d41b7a7 fix for default completion tokens on Boost 1.87.0
Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Differential Revision: https://repo.mireo.local/D33181
2025-01-15 16:01:09 +01:00
82a437999a fix for default completion tokens on Boost 1.87.0
Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Differential Revision: https://repo.mireo.local/D33181
2025-01-15 09:37:31 +01:00
afc270f10e Async.MQTT5 -> Boost.MQTT5
Summary:
related to T15996

folder structure include/async_mqtt5 -> include/boost/mqtt5
namespace async_mqtt5 -> namespace boost::mqtt5
all tabs replaced with 4 spaces (because tabs are banned)
boost-like order of includes

TODO: fix all docs

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D33152
2025-01-14 08:57:44 +01:00
1225cc778a fix for Boost 1.87.0
Summary: Boost.Asio made basic_resolver_results' inheritance of basic_resolver_iterator private

Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Differential Revision: https://repo.mireo.local/D32797
2024-12-16 17:16:08 +01:00
9a6788c913 Examples rework
Summary:
related to T15261, T15262
- all examples are added to CI checks
	- as a result, CMakeLists.txt in example folder is reworked to build all examples instead of one
	- also removed chapter Building with CMake temporarily (it will be back)
- all examples accept runtime arguments (brokers, port, clientid) (default: hivemq public broker)
- implemented example suggestions by R.Perez, most notable:
	- all examples include mqtt headers they use (no more unified headers)
	- multithreaded examples use thread_pool instead of asio::io_context
- all examples use logger with log_level::info by default

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32602
2024-12-04 09:45:18 +01:00
d19f466e3e Remove Beast dependency from connect_op.hpp
Summary: resolves T15243

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Maniphest Tasks: T15243

Differential Revision: https://repo.mireo.local/D32561
2024-12-02 15:47:19 +01:00
8d4502551a Fix example
Summary: related to T13767

Reviewers: iljazovic

Reviewed By: iljazovic

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D32565
2024-12-02 15:16:50 +01:00
e5d36cf088 Move logger traits out of detail namespace into logger_traits.hpp
Summary:
related to T15252, #24
- move logger traits out of detail namespace into logger_traits.hpp to allow writers of their own loggers to verify that their implementation satisfies the LoggerType requirements
- move impl/codecs/traits to detail/traits, traits functions are now in detail namespace
- logger outputs the contents of props in debug mode

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32524
2024-12-02 10:31:22 +01:00
319d024981 Document LoggerType concept and improve Getting Started chapter
Summary:
related to T15252, T15261, T15263, #24

- documented LoggerType concept
- configuring the client chapter changed to getting started chapter with new additions:
	- code examples for each section (choosing underlying type, configuring the client, and using it)
	- added a secion on debugging the client using our logger implementation
- minor fixes related to reviewer's suggestions

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32487
2024-12-02 07:56:13 +01:00
67152be209 fix warnings
Reviewers: korina

Reviewed By: korina

Subscribers: miljen

Differential Revision: https://repo.mireo.local/D32452
2024-11-27 14:53:51 +01:00
f22a3c7bec Add log invoke and logger
Summary: related to T15252, #24

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Maniphest Tasks: T15252

Differential Revision: https://repo.mireo.local/D32382
2024-11-27 13:45:23 +01:00
9240c51b28 remove thread mutex from async_mutex
Summary: also fixes per-operation-cancelled operations preventing unlock of mutex

Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Differential Revision: https://repo.mireo.local/D32427
2024-11-27 13:28:41 +01:00
944de413a3 Allow different bound executors on completion handlers
Summary: Resolves T15251, #23

Reviewers: ivica

Reviewed By: ivica

Subscribers: korina, miljen

Maniphest Tasks: T15251

Differential Revision: https://repo.mireo.local/D32249
2024-11-27 13:24:47 +01:00
913d8a102d README.md revisited
Summary:
related to T15263
If accepted, the changes will be applied to 01_intro.qbk in this commit.
This revision addresses most of the Peter Tucan's suggestions about README/Introduction.

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32287
2024-11-21 13:27:41 +01:00
edb94108b6 Remove Beast dependency from rebind_executor.hpp and async_traits.hpp
Summary:
related to T15243
remove beast as dependancy from async_traits and rebind_executor

run mqtt_features test on websocket-tcp client instead (tcp broker is usually overloaded)

add tests for next_layer/lowest_layer

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32212
2024-11-19 09:07:14 +01:00
f80c189767 Allow IPv6 connections
Summary: related to T13767, #19

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32140
2024-11-13 11:46:20 +01:00
2fa908cfc8 Add clang 16-18, address and UB sanitizer builds to CI
Summary: related to T15253

Reviewers: ivica

Reviewed By: ivica

Subscribers: iljazovic, miljen

Differential Revision: https://repo.mireo.local/D32168
2024-11-12 15:08:56 +01:00
203 changed files with 21681 additions and 20738 deletions

273
.github/workflows/ci-posix.yml vendored Normal file
View File

@ -0,0 +1,273 @@
#
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: CI-posix
on: [push, pull_request]
jobs:
posix-cmake:
name: "CMake ${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.cxxflags }}"
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- toolset: gcc-9
install: g++-9
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: gcc-10
install: g++-10
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: gcc-12
install: g++-12
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: gcc-13
install: g++-13
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 20
cxxflags: ''
ldflags: ''
- toolset: gcc-14
install: g++-14
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Debug'
cxxstd: 20
cxxflags: '-fsanitize=address,undefined -fno-sanitize-recover=all'
ldflags: '-fsanitize=address,undefined'
- toolset: clang-12
install: clang++-12
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: '-fdeclspec'
ldflags: ''
- toolset: clang-13
install: clang++-13
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: '-fdeclspec'
ldflags: ''
- toolset: clang-14
install: 'clang++-14 libc++-14-dev libc++abi-14-dev'
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: '-stdlib=libc++'
ldflags: '-lc++'
- toolset: clang-15
install: clang++-15
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: clang-16
install: clang++-16
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: clang-17
install: clang++-17
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 20
cxxflags: ''
ldflags: ''
- toolset: clang-18
install: clang++-18
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Debug'
cxxstd: 20
cxxflags: '-fsanitize=address,undefined -fno-sanitize-recover=all'
ldflags: '-fsanitize=address,undefined'
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
env:
CXXFLAGS: ${{ matrix.cxxflags }} -Wall -Wextra
LDFLAGS: ${{ matrix.ldflags }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup container environment
if: matrix.container
run: |
apt-get update
apt-get -y install --no-install-recommends \
sudo git g++ cmake make openssl libssl-dev ca-certificates pkg-config python3
- name: Install toolset
run: sudo apt-get install -y ${{ matrix.install }}
- name: Setup Boost
run: |
python3 tools/ci.py setup-boost \
--source-dir=$(pwd)
- name: Build a Boost distribution using B2
run : |
python3 tools/ci.py build-b2-distro \
--toolset ${{ matrix.toolset }}
- name: Build a Boost distribution using CMake
run: |
python3 tools/ci.py build-cmake-distro \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Build standalone examples using CMake
run: |
python3 tools/ci.py build-cmake-standalone-examples \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Build standalone tests using CMake
run: |
python3 tools/ci.py build-cmake-standalone-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run standalone tests
run: |
python3 tools/ci.py run-cmake-standalone-tests \
--build-type ${{ matrix.build-type }}
- name: Run CMake find_package test with B2 distribution
run: |
python3 tools/ci.py run-cmake-b2-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run CMake find_package test with CMake distribution
run : |
python3 tools/ci.py run-cmake-find-package-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Run CMake add_subdirectory test with CMake distribution
run: |
python3 tools/ci.py run-cmake-add-subdirectory-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
posix-b2:
name: "B2 ${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.cxxflags }}"
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- toolset: gcc-11
install: g++-11
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'debug,release'
cxxstd: "17,20"
cxxflags: ''
ldflags: ''
- toolset: clang-14
install: clang-14
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'debug,release'
cxxstd: "17,20"
cxxflags: ''
ldflags: ''
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
env:
CXXFLAGS: ${{ matrix.cxxflags }} -Wall -Wextra
LDFLAGS: ${{ matrix.ldflags }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup container environment
if: matrix.container
run: |
apt-get update
apt-get -y install --no-install-recommends \
sudo git g++ ca-certificates pkg-config python3
- name: Install toolset
run: sudo apt-get install -y ${{ matrix.install }}
- name: Setup Boost
run: |
python3 tools/ci.py setup-boost \
--source-dir=$(pwd)
- name: Build a Boost distribution using B2
run : |
python3 tools/ci.py build-b2-distro \
--toolset ${{ matrix.toolset }}
- name: Build and run tests using B2
run: |
python3 tools/ci.py run-b2-tests \
--toolset ${{ matrix.toolset }} \
--cxxstd ${{ matrix.cxxstd }} \
--variant ${{ matrix.build-type }}

182
.github/workflows/ci-windows.yml vendored Normal file
View File

@ -0,0 +1,182 @@
#
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: CI-Windows
on: [push, pull_request]
jobs:
windows-cmake:
name: "CMake ${{ matrix.toolset }} ${{ matrix.architecture }} std=c++${{ matrix.cxxstd }}"
defaults:
run:
shell: cmd
strategy:
fail-fast: false
matrix:
include:
- toolset: msvc-14.3
os: windows-2022
architecture: Win32
generator: Visual Studio 17 2022
cxxstd: 20
build-type: 'Debug'
cxxflags: ''
ldflags: ''
- toolset: msvc-14.3
os: windows-2022
architecture: x64
generator: Visual Studio 17 2022
cxxstd: 20
build-type: 'Debug'
cxxflags: ''
ldflags: ''
runs-on: ${{ matrix.os }}
env:
CXXFLAGS: ${{ matrix.cxxflags }} /D_WIN32_WINNT=0x0601 /DWIN32_LEAN_AND_MEAN=1 /DNOMINMAX=1 /D_FILE_OFFSET_BITS=64 /DBOOST_ALL_NO_LIB /EHsc /bigobj
LDFLAGS: ${{ matrix.ldflags }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup OpenSSL
env:
OPENSSL_ROOT: "C:\\OpenSSL"
run: |
if "${{ matrix.architecture }}" == "x64" (
choco install --no-progress -y openssl --x64
)
if "${{ matrix.architecture }}" == "Win32" (
set openssl_install_dir="C:\\Program Files (x86)\\OpenSSL-Win32"
choco install --no-progress -y openssl --forcex86 --version 1.1.1.2100
)
if "${{ matrix.architecture }}" == "x64" (
if exist "C:\Program Files\OpenSSL\" (
set openssl_install_dir="C:\\Program Files\\OpenSSL"
) else (
set openssl_install_dir="C:\\Program Files\\OpenSSL-Win64"
)
)
mklink /D %OPENSSL_ROOT% %openssl_install_dir%
refreshenv
set
- name: Setup CMake
run: choco install cmake
- name: Setup Boost
run: python3 tools/ci.py setup-boost --source-dir=%cd%
- name: Build a Boost distribution using B2
run : |
python3 tools/ci.py build-b2-distro ^
--toolset ${{ matrix.toolset }}
- name: Build a Boost distribution using CMake
run: |
python3 tools/ci.py build-cmake-distro ^
--build-type ${{ matrix.build-type }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--toolset ${{ matrix.toolset }} ^
--generator "${{ matrix.generator }}"
- name: Build standalone examples using CMake
run: |
python3 tools/ci.py build-cmake-standalone-examples ^
--build-type ${{ matrix.build-type }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--toolset ${{ matrix.toolset }} ^
--generator "${{ matrix.generator }}"
- name: Build standalone tests using CMake
run: |
python3 tools/ci.py build-cmake-standalone-tests ^
--build-type ${{ matrix.build-type }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--toolset ${{ matrix.toolset }} ^
--generator "${{ matrix.generator }}"
- name: Run standalone tests
run: |
python3 tools/ci.py run-cmake-standalone-tests ^
--build-type ${{ matrix.build-type }}
- name: Run CMake find_package test with B2 distribution
run: |
python3 tools/ci.py run-cmake-b2-find-package-tests ^
--build-type ${{ matrix.build-type }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--toolset ${{ matrix.toolset }} ^
--generator "${{ matrix.generator }}"
- name: Run CMake find_package test with CMake distribution
run: |
python3 tools/ci.py run-cmake-find-package-tests ^
--build-type ${{ matrix.build-type }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--toolset ${{ matrix.toolset }} ^
--generator "${{ matrix.generator }}"
- name: Run CMake add_subdirectory test with CMake distribution
run: |
python3 tools/ci.py run-cmake-add-subdirectory-tests ^
--build-type ${{ matrix.build-type }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--toolset ${{ matrix.toolset }} ^
--generator "${{ matrix.generator }}"
windows-b2:
name: "B2 ${{ matrix.toolset }} ${{ matrix.architecture }} std=c++${{ matrix.cxxstd }}"
defaults:
run:
shell: cmd
strategy:
fail-fast: false
matrix:
include:
- toolset: msvc-14.3
os: windows-2022
architecture: Win32
generator: Visual Studio 17 2022
cxxstd: "17,20"
build-type: 'release'
cxxflags: ''
ldflags: ''
- toolset: msvc-14.3
os: windows-2022
architecture: x64
generator: Visual Studio 17 2022
cxxstd: "17,20"
build-type: 'release'
cxxflags: ''
ldflags: ''
runs-on: ${{ matrix.os }}
env:
CXXFLAGS: ${{ matrix.cxxflags }} /D_WIN32_WINNT=0x0601 /DWIN32_LEAN_AND_MEAN=1 /DNOMINMAX=1 /D_FILE_OFFSET_BITS=64 /DBOOST_ALL_NO_LIB /EHsc /bigobj
LDFLAGS: ${{ matrix.ldflags }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Boost
run: python3 tools/ci.py setup-boost --source-dir=%cd%
- name: Build and run project tests using B2
run: |
python3 tools/ci.py run-b2-tests ^
--toolset ${{ matrix.toolset }} ^
--cxxstd ${{ matrix.cxxstd }} ^
--variant ${{ matrix.build-type }}

View File

@ -1,260 +0,0 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: CI
on: [push, pull_request]
env:
BOOST_VERSION: 1.82.0
BOOST_DIR_VER_NAME: 1_82_0
jobs:
windows:
name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }}"
defaults:
run:
shell: cmd
strategy:
fail-fast: false
matrix:
include:
# internal compiler error
# - toolset: msvc-14.2 win64
# os: windows-2019
# architecture: x64
# generator: "Visual Studio 16 2019"
# cxxstd: 17
# build-type: 'Debug'
# cxxflags: ''
# ldflags: '/machine:x64'
- toolset: msvc-14.3 win32
os: windows-2022
architecture: Win32
generator: Visual Studio 17 2022
cxxstd: 20
build-type: 'Debug'
cxxflags: ''
ldflags: ''
- toolset: msvc-14.3 win64
os: windows-2022
architecture: x64
generator: Visual Studio 17 2022
cxxstd: 20
build-type: 'Debug'
cxxflags: ''
ldflags: ''
runs-on: ${{ matrix.os }}
env:
CXXFLAGS: ${{ matrix.cxxflags }} /D_WIN32_WINNT=0x0601 /DWIN32_LEAN_AND_MEAN=1 /DNOMINMAX=1 /D_FILE_OFFSET_BITS=64 /DBOOST_ALL_NO_LIB /EHsc /bigobj
LDFLAGS: ${{ matrix.ldflags }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup OpenSSL
env:
OPENSSL_ROOT: "C:\\OpenSSL"
run: |
if "${{ matrix.architecture }}" == "x64" (
choco install --no-progress -y openssl --x64
)
if "${{ matrix.architecture }}" == "Win32" (
set openssl_install_dir="C:\\Program Files (x86)\\OpenSSL-Win32"
choco install --no-progress -y openssl --forcex86 --version 1.1.1.2100
)
if "${{ matrix.architecture }}" == "x64" (
if exist "C:\Program Files\OpenSSL\" (
set openssl_install_dir="C:\\Program Files\\OpenSSL"
) else (
set openssl_install_dir="C:\\Program Files\\OpenSSL-Win64"
)
)
mklink /D %OPENSSL_ROOT% %openssl_install_dir%
refreshenv
set
- name: Setup Cmake
run: |
choco install cmake
- name: Setup Boost
run: |
curl -o boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz https://archives.boost.io/release/${{ env.BOOST_VERSION }}/source/boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
tar -xf boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
mkdir ${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}
move boost_${{ env.BOOST_DIR_VER_NAME }}\\boost ${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}
del boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
- name: Setup library
run: |
cmake -S . -B build -DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}"
cmake --install build
- name: Build tests
run: |
cmake -S test -B test\\build -A ${{ matrix.architecture }} ^
-DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" ^
-DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" ^
-DBoost_INCLUDE_DIR="${{ github.workspace }}\\boost_${{ env.BOOST_DIR_VER_NAME }}"
cmake --build test\\build -j 4
- name: Run tests
run: |
.\\test\\build\\${{ matrix.build-type }}\\mqtt-test.exe
posix:
name: "${{ matrix.toolset }} std=c++${{ matrix.cxxstd }} ${{ matrix.container }}"
defaults:
run:
shell: bash
strategy:
fail-fast: false
matrix:
include:
- toolset: g++-9
compiler: g++-9
install: g++-9
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-10
compiler: g++-10
install: g++-10
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-12
compiler: g++-12
install: g++-12
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-13
compiler: g++-13
install: g++-13
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 17
cxxflags: ''
ldflags: ''
- toolset: g++-14
compiler: g++-14
install: g++-14
os: ubuntu-24.04
container: ubuntu:24.04
build-type: 'Release'
cxxstd: 20
cxxflags: ''
ldflags: ''
- toolset: clang++-12
compiler: clang++-12
install: clang++-12
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: '-fdeclspec'
ldflags: ''
- toolset: clang++-13
compiler: clang++-13
install: clang++-13
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: '-fdeclspec'
ldflags: ''
- toolset: clang++-14-libc++-14
compiler: clang++-14
install: 'clang++-14 libc++-14-dev libc++abi-14-dev'
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 17
cxxflags: '-stdlib=libc++'
ldflags: '-lc++'
- toolset: clang++-15
compiler: clang++-15
install: clang++-15
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Release'
cxxstd: 20
cxxflags: ''
ldflags: ''
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
env:
CXXFLAGS: ${{ matrix.cxxflags }} -Wall -Wextra
LDFLAGS: ${{ matrix.ldflags }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup container environment
if: matrix.container
run: |
apt-get update
apt-get -y install sudo wget tar cmake openssl libssl-dev pkg-config
- name: Install compiler
run: sudo apt-get install -y ${{ matrix.install }}
- name: Setup Boost
run: |
wget https://archives.boost.io/release/${{ env.BOOST_VERSION }}/source/boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
tar xzf boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
mkdir /usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}
mv boost_${{ env.BOOST_DIR_VER_NAME }}/boost /usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}
rm boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
- name: Setup library
run: |
cmake -S . -B build -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" \
-DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}"
sudo cmake --install build
- name: Build tests
run: |
cmake -S test -B test/build \
-DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ env.CXXFLAGS }}" \
-DCMAKE_CXX_STANDARD="${{ matrix.cxxstd }}" -DCMAKE_EXE_LINKER_FLAGS="${{ env.LDFLAGS }}" -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" \
-DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}"
cmake --build test/build -j 4
- name: Run tests
run: ./test/build/mqtt-test

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
@ -9,13 +9,9 @@ name: coverage
on: [push, pull_request]
env:
BOOST_VERSION: 1.82.0
BOOST_DIR_VER_NAME: 1_82_0
jobs:
posix:
name: "coverage ${{ matrix.compiler }} -std=c++${{ matrix.cxxstd }} ${{ matrix.container }}"
name: "coverage ${{ matrix.toolset }} -std=c++${{ matrix.cxxstd }} ${{ matrix.container }}"
defaults:
run:
shell: bash
@ -24,10 +20,11 @@ jobs:
fail-fast: false
matrix:
include:
- toolset: coverage
compiler: g++-11
- toolset: gcc-11
install: g++-11
os: ubuntu-latest
container: ubuntu:22.04
build-type: 'Coverage'
cxxstd: 20
cxxflags: '-g -O0 -std=c++20 --coverage -fkeep-inline-functions -fkeep-static-functions'
ldflags: '--coverage'
@ -35,6 +32,8 @@ jobs:
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
env:
CXXFLAGS: ${{ matrix.cxxflags }}
LDFLAGS: ${{ matrix.ldflags }}
CMAKE_BUILD_PARALLEL_LEVEL: 4
steps:
@ -45,49 +44,41 @@ jobs:
if: matrix.container
run: |
apt-get update
apt-get -y install sudo wget tar cmake openssl libssl-dev pkg-config lcov gpg git
apt-get -y install --no-install-recommends \
sudo git g++ cmake make openssl libssl-dev ca-certificates pkg-config \
python3 lcov gpg gpg-agent curl
- name: Install compiler
run: sudo apt-get install -y ${{ matrix.compiler }}
run: sudo apt-get install -y ${{ matrix.install }}
- name: Setup Boost
run: |
wget https://archives.boost.io/release/${{ env.BOOST_VERSION }}/source/boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
tar xzf boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
mkdir /usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}
mv boost_${{ env.BOOST_DIR_VER_NAME }}/boost /usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}
rm boost_${{ env.BOOST_DIR_VER_NAME }}.tar.gz
python3 tools/ci.py setup-boost \
--source-dir=$(pwd)
- name: Setup library
- name: Build standalone tests using CMake
run: |
cmake -S . -B build -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" \
-DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}"
sudo cmake --install build
python3 tools/ci.py build-cmake-standalone-tests \
--build-type ${{ matrix.build-type }} \
--cxxstd ${{ matrix.cxxstd }} \
--toolset ${{ matrix.toolset }}
- name: Build tests
- name: Run standalone tests
run: |
cmake -S test -B test/build -DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" -DCMAKE_CXX_FLAGS="${{ matrix.cxxflags }}" \
-DCMAKE_EXE_LINKER_FLAGS="${{ matrix.ldflags }}" -DCMAKE_BUILD_TYPE="Coverage" \
-DBoost_INCLUDE_DIR="/usr/local/boost_${{ env.BOOST_DIR_VER_NAME }}"
cmake --build test/build -j 4
- name: Run tests
run: ./test/build/mqtt-test
python3 tools/ci.py run-cmake-standalone-tests \
--build-type ${{ matrix.build-type }}
- name: Generate Coverage Report
run: |
lcov --capture --output-file coverage.info --directory test/build
lcov --remove coverage.info '/usr/include/*' --output-file coverage.info
lcov --remove coverage.info '**/test/*' --output-file coverage.info
lcov --remove coverage.info '**/boost/*' --output-file coverage.info
lcov --capture --output-file coverage.info \
--directory ~/boost-root/libs/mqtt5/__build_standalone_tests__/test
lcov --extract coverage.info '**/boost/mqtt5/*' --output-file coverage.info
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
verbose: true
file: coverage.info
files: coverage.info
disable_search: true
fail_ci_if_error: true
plugin: noop
token: ${{ secrets.CODECOV_TOKEN }}
plugins: noop

43
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,43 @@
#
# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
name: docs
on: [push]
jobs:
posix:
name: "Docs ${{ matrix.container }}"
runs-on: ubuntu-latest
container: ubuntu:22.04
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup container environment
run: |
apt-get update
export DEBIAN_FRONTEND=noninteractive # for tzdata
apt-get -y install --no-install-recommends \
doxygen docbook-xsl docutils-doc xsltproc wget ca-certificates g++ \
python3 python-is-python3 python3-jinja2 rsync git unzip
- name: Setup Boost
run: |
python3 tools/ci.py setup-boost \
--source-dir=$(pwd) \
--docs-install=1
- name: Build docs
run: |
python3 tools/ci.py build-docs

View File

@ -1,50 +1,80 @@
cmake_minimum_required(VERSION 3.15)
#
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
project(async-mqtt5 VERSION 1.0.1 LANGUAGES CXX)
cmake_minimum_required(VERSION 3.8...3.20)
project(boost_mqtt5 VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)
add_library(async_mqtt5 INTERFACE)
add_library(Async::MQTT5 ALIAS async_mqtt5)
set_property(
TARGET async_mqtt5 PROPERTY
EXPORT_NAME MQTT5
)
target_include_directories(
async_mqtt5 ${warning_guard}
INTERFACE
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)
target_compile_features(async_mqtt5 INTERFACE cxx_std_17)
find_package(Boost 1.82 REQUIRED)
target_link_libraries(async_mqtt5
INTERFACE
Boost::headers
)
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/install-rules.cmake)
# Determine if this is the superproject or called from add_subdirectory.
if(NOT DEFINED BOOST_MQTT5_MAIN_PROJECT)
set(BOOST_MQTT5_MAIN_PROJECT OFF)
if(PROJECT_IS_TOP_LEVEL)
set(BOOST_MQTT5_MAIN_PROJECT ON)
endif()
endif()
if(PROJECT_IS_TOP_LEVEL)
option(BUILD_EXAMPLES "Build examples tree." "${async-mqtt5_DEVELOPER_MODE}")
add_library(boost_mqtt5 INTERFACE)
add_library(Boost::mqtt5 ALIAS boost_mqtt5)
# If non-Boost dependencies are not found, we just bail out.
find_package(Threads)
if(NOT Threads_FOUND)
message(STATUS "Boost.MQTT5 has been disabled, because the required package Threads hasn't been found")
return()
endif()
target_include_directories(boost_mqtt5 INTERFACE include)
target_compile_features(boost_mqtt5 INTERFACE cxx_std_17)
if(BOOST_MQTT5_MAIN_PROJECT)
find_package(Boost 1.82 REQUIRED)
if (NOT Boost_FOUND)
message(STATUS "Cannot find Boost!")
return()
endif()
target_link_libraries(boost_mqtt5 INTERFACE Boost::headers Threads::Threads)
else()
target_link_libraries(
boost_mqtt5
INTERFACE
Boost::asio
Boost::assert
# Boost::beast # Optional, only used for MQTT connections over WebSocket.
Boost::container
Boost::core
Boost::endian
Boost::fusion
Boost::optional
Boost::random
Boost::range
Boost::smart_ptr
Boost::spirit
Boost::system
Boost::type_traits
Threads::Threads
)
endif()
option(BOOST_MQTT5_PUBLIC_BROKER_TESTS OFF "Whether to run tests requiring a public MQTT broker")
mark_as_advanced(BOOST_MQTT5_PUBLIC_BROKER_TESTS)
if(BUILD_TESTING)
# Custom target tests; required by the Boost superproject
if(NOT TARGET tests)
add_custom_target(tests)
endif()
add_subdirectory(test)
endif()
if(BOOST_MQTT5_MAIN_PROJECT)
option(BUILD_EXAMPLES "Whether to build examples")
if(BUILD_EXAMPLES)
add_subdirectory(example)
endif()
endif()
if(NOT async-mqtt5_DEVELOPER_MODE)
return()
elseif(NOT PROJECT_IS_TOP_LEVEL)
message(
AUTHOR_WARNING
"Developer mode is intended for developers of async-mqtt5"
)
endif()
include(cmake/dev-mode.cmake)

203
README.md
View File

@ -1,116 +1,28 @@
Async.MQTT5: A C++17 MQTT client based on Boost.Asio
Boost.MQTT5: A C++17 MQTT client based on Boost.Asio
===============================
Branch | Windows/Linux Build | Coverage | Documentation |
-------|---------------------|----------|---------------|
[`master`](https://github.com/mireo/async-mqtt5/tree/master) | [![build status](https://github.com/mireo/async-mqtt5/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/mireo/async-mqtt5/actions/workflows/ci.yml) | [![codecov](https://codecov.io/gh/mireo/async-mqtt5/branch/master/graph/badge.svg)](https://codecov.io/gh/mireo/async-mqtt5/branch/master) | [Documentation](https://spacetime.mireo.com/async-mqtt5/)
Branch | Linux Build | Windows Build | Coverage | Documentation |
-------|-------------|---------------|----------|---------------|
[`master`](https://github.com/boostorg/mqtt5/tree/master) | [![build status](https://github.com/boostorg/mqtt5/actions/workflows/ci-posix.yml/badge.svg?branch=master)](https://github.com/boostorg/mqtt5/actions/workflows/ci-posix.yml) | [![build status](https://github.com/boostorg/mqtt5/actions/workflows/ci-windows.yml/badge.svg?branch=master)](https://github.com/boostorg/mqtt5/actions/workflows/ci-windows.yml) | [![codecov](https://codecov.io/gh/boostorg/mqtt5/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/mqtt5/tree/master) | [documentation](https://www.boost.org/doc/libs/master/libs/mqtt5/doc/html/index.html)
[`develop`](https://github.com/boostorg/mqtt5/tree/develop) | [![build status](https://github.com/boostorg/mqtt5/actions/workflows/ci-posix.yml/badge.svg?branch=develop)](https://github.com/boostorg/mqtt5/actions/workflows/ci-posix.yml) | [![build status](https://github.com/boostorg/mqtt5/actions/workflows/ci-windows.yml/badge.svg?branch=develop)](https://github.com/boostorg/mqtt5/actions/workflows/ci-windows.yml) | [![codecov](https://codecov.io/gh/boostorg/mqtt5/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/mqtt5/tree/develop) | [documentation](https://www.boost.org/doc/libs/develop/libs/mqtt5/doc/html/index.html)
Async.MQTT5 is a professional, industrial-grade C++17 client built on [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio.html). This Client is designed for publishing or receiving messages from an MQTT 5.0 compatible Broker. Async.MQTT5 represents a comprehensive implementation of the MQTT 5.0 protocol standard, offering full support for publishing or receiving messages with QoS 0, 1, and 2.
Our clear intention is to include the Async.MQTT5 library into [Boost](https://www.boost.org/). We are actively working on it.
Boost.MQTT5 is a professional, industrial-grade C++17 client built on [Boost.Asio](https://www.boost.org/doc/libs/master/doc/html/boost_asio.html). This Client is designed for publishing or receiving messages from an MQTT 5.0 compatible Broker. Boost.MQTT5 represents a comprehensive implementation of the MQTT 5.0 protocol standard, offering full support for publishing or receiving messages with QoS 0, 1, and 2.
Motivation
---------
The [MQTT](https://mqtt.org/) protocol is widely utilised for communication in various real-world scenarios, primarily serving as a reliable communication protocol for data transfer to and from IoT devices. While the MQTT protocol itself is relatively straightforward, integrating it into an application can be complex, especially due to the challenging implementation of message retransmission after a disconnect/reconnect sequence.
The [MQTT](https://mqtt.org/) protocol is widely used for communication in various real-world scenarios, primarily as a reliable communication protocol for data transfer to and from IoT devices. While the MQTT protocol is relatively straightforward, integrating it into an application can be complex, primarily due to the challenging implementation of message retransmission after a disconnect/reconnect sequence. To address these challenges and simplify MQTT integration once and for all, Boost.MQTT5 was designed with the following core objectives in mind:
The aim of Async.MQTT5 is to provide a very simple asynchronous C++ interface for application developers. The internal Client's implementation manages network and MQTT protocol details. Notably, the Client does not expose connect functions (nor asynchronous connect functions); instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the Client.
Objective | Description |
----------|-------------|
"Plug and play" interface that abstracts away MQTT protocol internals, low-level network events, message retransmission and other complexities | Enables developers to publish or receive messages with just a single line of code, significantly reducing the learning curve and development time. Getting started requires basic MQTT knowledge, making the library accessible to developers of all skill levels. |
Highly reliable and robust Client that handles network losses, unreliable data transport, network latencies, and other unpredictable events | The Client does not expose connect functions (nor asynchronous connect functions); instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the Client while strictly adhering to the MQTT specification. This automation eliminates the need for developers to write extensive and error-prone code to handle these scenarios, allowing them to focus on the application's core functionality. |
Complete adherence to the Boost.Asio's universal asynchronous model | The interfaces and implementation strategies are built upon the foundations of Boost.Asio. This compatibility enables seamless integration with any other library within the Boost.Asio ecosystem. |
The Async.MQTT5 interface aligns seamlessly with the Boost.Asio asynchronous model. The Client's asynchronous functions are compatible with all completion tokens supported by Boost.Asio.
Features
When to Use
---------
Async.MQTT5 is a library designed with the core belief that users should focus solely on their application logic, not the network complexities.
The library attempts to embody this belief with a range of key features designed to elevate the development experience:
Boost.MQTT5 might be suitable for you if any of the following statements is true:
- **Complete TCP, TLS/SSL, and WebSocket support**
- **User-focused simplicity**: Providing an interface that is as simple as possible without compromising functionality.
- **Prioritised efficiency**: Utilising network and memory resources as efficiently as possible.
- **Minimal memory footprint**: Ensuring optimal performance in resource-constrained environments typical of IoT devices.
- **Automatic reconnect**: Automatically attempt to re-establish a connection in the event of a disconnection.
- **Fully Boost.Asio compliant**: The interfaces and implementation strategies are built upon the foundations of Boost.Asio. Boost.Asio and Boost.Beast users will have no issues understanding and integrating Async.MQTT5. Furthermore, Async.MQTT5 integrates well with any other library within the Boost.Asio ecosystem.
- **Custom allocators**: Support for custom allocators allows extra flexibility and control over the memory resources. Async.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation.
- **Per-Operation Cancellation**: All asynchronous operations support individual, targeted cancellation as per Asio's [Per-Operation Cancellation](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview/core/cancellation.html).
- **Completion Token**: All asynchronous functions support CompletionToken, allowing for versatile usage with callbacks, coroutines, futures, and more.
- **Full implementation of MQTT 5.0 specification**
- **Support for QoS 0, QoS 1, and QoS 2**
- **Custom authentication**: Async.MQTT5 defines an interface for your own custom authenticators to perform Enhanced Authentication.
- **High availability**: Async.MQTT5 supports listing multiple Brokers within the same cluster to which the Client can connect.
In the event of a connection failure with one Broker, the Client switches to the next in the list.
- **Offline buffering**: While offline, it automatically buffers all the packets to send when the connection is re-established.
Using the library
---------
1. Download [Boost](https://www.boost.org/users/download/), and add it to your include path.
2. If you use SSL, download [OpenSSL](https://www.openssl.org/), link the library and add it to your include path.
3. Add Async.MQTT5's `include` folder to your include path.
You can compile the example below with the following command line on Linux:
$ clang++ -std=c++17 <source-cpp-file> -o example -I<path-to-boost> -Iinclude -pthread
Usage and API
---------
The following example illustrates a simple scenario of configuring a Client and publishing a
"Hello World!" Application Message with `QoS` 0.
```cpp
#include <iostream>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
c.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&c](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
c.async_disconnect(boost::asio::detached); // disconnect and close the client
}
);
ioc.run();
}
```
To see more examples, visit [Examples](https://github.com/mireo/async-mqtt5/tree/master/example).
Building with CMake
---------
You can use the `CMakeLists.txt` provided in our repository to compile and run any of the [examples](https://github.com/mireo/async-mqtt5/tree/master/example) or your own source files.
The following commands demonstrate compiling and running the previous code using CMake on Linux.
The source file is located at [example/hello_world_over_tcp.cpp](https://github.com/mireo/async-mqtt5/blob/master/example/hello_world_over_tcp.cpp).
```bash
# navigate to the root folder of Async.MQTT5
# compile the example
cmake -S . -B {build-folder} -DBUILD_EXAMPLES=ON -DCMAKE_EXE_LINKER_FLAGS="-pthread"
cmake --build {build-folder}
# run the example
./{build-folder}/example/example
```
You can edit the [example/CMakeLists.txt](https://github.com/mireo/async-mqtt5/blob/master/example/CMakeLists.txt) file to compile the source file of your choice.
By default, it will compile [example/hello_world_over_tcp.cpp](https://github.com/mireo/async-mqtt5/blob/master/example/hello_world_over_tcp.cpp).
When to use
---------
Async.MQTT5 might be suitable for you if any of the following statements is true:
- Your application uses Boost.Asio and requires integrating a MQTT Client.
- Your application uses Boost.Asio and requires integrating an MQTT Client.
- You require asynchronous access to an MQTT Broker.
- You are developing a higher-level component that requires a connection to an MQTT Broker.
- You require a dependable and resilient MQTT Client to manage all network-related issues automatically.
@ -119,19 +31,88 @@ It may not be suitable for you if:
- You solely require synchronous access to an MQTT Broker.
- The MQTT Broker you connect to does not support the MQTT 5 version.
Requirements
Features
---------
Async.MQTT5 is a header-only library. To use Async.MQTT5 it requires the following:
- **C++17 capable compiler**
- **Boost 1.82 or later**. In addition to Asio, we use other header-only libraries such as Beast, Spirit, and more.
- **OpenSSL**. Only if you require an SSL connection by using [boost::asio::ssl::stream](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/reference/ssl__stream.html).
Boost.MQTT5 is a library designed with the core belief that users should focus solely on their application logic, not the network complexities.
The library attempts to embody this belief with a range of key features designed to elevate the development experience:
Async.MQTT5 has been tested with the following compilers:
- clang 12.0 - 15.0 (Linux)
Feature | Description |
--------|-------------|
**Complete TCP, TLS/SSL, and WebSocket support** | The MQTT protocol requires an underlying network protocol that provides ordered, lossless, bi-directional connection (stream). Users can customize this stream through a template parameter. The Boost.MQTT5 library has been tested with the most used transport protocols: TCP/IP using `boost::asio::ip::tcp::socket`, TLS/SSL using `boost::asio::ssl::stream`, WebSocket/TCP and WebSocket/TLS using `boost::beast::websocket::stream`). |
**Automatic reconnect and retry mechanism** | Automatically handles connection loss, backoffs, reconnections, and message transmissions. Automating these processes enables users to focus entirely on the application's functionality. See [Built-in Auto-Reconnect and Retry Mechanism](https://www.boost.org/doc/libs/master/libs/mqtt5/doc/html/mqtt5/auto_reconnect.html). |
**Prioritised efficiency** | Utilises network and memory resources as efficiently as possible. |
**Minimal memory footprint** | Ensures optimal performance in resource-constrained environments typical of IoT devices. |
**Completion token** | All asynchronous functions are implemented using Boost.Asio's universal asynchronous model and support CompletionToken. This allows versatile usage with callbacks, coroutines, and futures. |
**Custom allocators** | Support for custom allocators allows extra flexibility and control over the memory resources. Boost.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation. |
**Per-operation cancellation** | All asynchronous operations support individual, targeted cancellation as per Asio's [Per-Operation Cancellation](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview/core/cancellation.html). This enables all asynchronous functions to be used in [Parallel Operations](https://www.boost.org/doc/libs/1_86_0/doc/html/boost_asio/overview/composition/parallel_group.html), which is especially beneficial for executing operations that require a timeout mechanism.
**Full implementation of MQTT 5.0 specification** | Ensures full compatibility with [MQTT 5.0 standard](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html). This latest version introduces essential features that enhance system robustness, including advanced error-handling mechanisms, session and message expiry, and other improvements designed to support modern IoT use cases. |
**Support for QoS 0, QoS 1, and QoS 2**| Fully implements all quality-of-service levels defined by the MQTT protocol to match different reliability needs in message delivery. |
**Custom authentication** | Defines an interface for your custom authenticators to perform [Enhanced Authentication](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901256). In addition to username/password authentication, this customization point enables the implementation of any authentication mechanism supported by the SASL protocol, offering flexibility for advanced security requirements. |
**High availability** | Supports listing multiple Brokers within the same cluster to which the Client can connect. In the event of a connection failure with one Broker, the Client switches to the next in the list. |
**Offline buffering** | Automatically buffers all requests made while offline, ensuring they are sent when the connection is re-established. This allows users to call any asynchronous function freely, regardless of current connection status. |
Getting Started
---------
Boost.MQTT5 is a header-only library. To use Boost.MQTT5 it requires the following:
- **C++17 capable compiler**
- **Boost 1.82 or later**. In addition to Asio, we use other header-only libraries.
- **OpenSSL**. If you require an SSL connection by using [boost::asio::ssl::stream](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/reference/ssl__stream.html).
Boost.MQTT5 has been tested with the following compilers:
- clang 12.0 - 18.0 (Linux)
- GCC 9 - 14 (Linux)
- MSVC 14.37 - Visual Studio 2022 (Windows)
Using the Library
---------
1. Download [Boost](https://www.boost.org/users/download/), and add it to your include path.
2. If you use SSL, download [OpenSSL](https://www.openssl.org/), link the library and add it to your include path.
3. Add Boost.MQTT5's `include` folder to your include path.
You can compile the example below with the following command line on Linux:
$ clang++ -std=c++17 <source-cpp-file> -o example -I<path-to-boost> -Iinclude -pthread
Example
---------
The following example illustrates a scenario of configuring a Client and publishing a
"Hello World!" Application Message with `QoS` 0.
```cpp
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
int main() {
boost::asio::io_context ioc;
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.brokers("<your-mqtt-broker>", 1883)
.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.async_run(boost::asio::detached);
c.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[&c](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
c.async_disconnect(boost::asio::detached); // disconnect and close the Client
}
);
ioc.run();
}
```
To see more examples, visit [Examples](https://github.com/boostorg/mqtt5/tree/master/example).
Contributing
---------
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

37
build.jam Normal file
View File

@ -0,0 +1,37 @@
#
# Copyright (c) 2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
require-b2 5.2 ;
constant boost_dependencies :
/boost/asio//boost_asio
/boost/assert//boost_assert
/boost/container//boost_container
/boost/core//boost_core
/boost/endian//boost_endian
/boost/fusion//boost_fusion
/boost/optional//boost_optional
/boost/random//boost_random
/boost/range//boost_range
/boost/smart_ptr//boost_smart_ptr
/boost/spirit//boost_spirit
/boost/system//boost_system
/boost/type_traits//boost_type_traits
;
project /boost/mqtt5
: common-requirements
<include>include
;
explicit
[ alias boost_mqtt5 : : : : <library>$(boost_dependencies) ]
[ alias all : boost_mqtt5 test ]
;
call-if : boost-library mqtt5
;

View File

@ -1,4 +0,0 @@
include(CTest)
if(BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -1,4 +0,0 @@
include(CMakeFindDependencyMacro)
find_dependency(Boost 1.82)
include("${CMAKE_CURRENT_LIST_DIR}/async-mqtt5Targets.cmake")

View File

@ -1,66 +0,0 @@
if(PROJECT_IS_TOP_LEVEL)
set(
CMAKE_INSTALL_INCLUDEDIR "/async-mqtt5-${PROJECT_VERSION}"
CACHE STRING ""
)
set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH)
endif()
# Project is configured with no languages, so tell GNUInstallDirs the lib dir
set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "")
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
# find_package(<package>) call for consumers to find this project
set(package async-mqtt5)
install(
DIRECTORY include/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT async-mqtt5_Development
)
install(
TARGETS async_mqtt5
EXPORT async-mqtt5Targets
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
write_basic_package_version_file(
"${package}ConfigVersion.cmake"
COMPATIBILITY SameMajorVersion
ARCH_INDEPENDENT
)
# Allow package maintainers to freely override the path for the configs
set(
async-mqtt5_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}"
CACHE STRING "CMake package config location relative to the install prefix"
)
set_property(CACHE async-mqtt5_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
mark_as_advanced(async-mqtt5_INSTALL_CMAKEDIR)
install(
FILES cmake/install-config.cmake
DESTINATION "${async-mqtt5_INSTALL_CMAKEDIR}"
RENAME "${package}Config.cmake"
COMPONENT async-mqtt5_Development
)
install(
FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
DESTINATION "${async-mqtt5_INSTALL_CMAKEDIR}"
COMPONENT async-mqtt5_Development
)
install(
EXPORT async-mqtt5Targets
NAMESPACE Async::
DESTINATION "${async-mqtt5_INSTALL_CMAKEDIR}"
COMPONENT async-mqtt5_Development
)
if(PROJECT_IS_TOP_LEVEL)
include(CPack)
endif()

View File

@ -1,28 +0,0 @@
# ---- Developer mode ----
# Developer mode enables targets and code paths in the CMake scripts that are
# only relevant for the developer(s) of async-mqtt5
# Targets necessary to build the project must be provided unconditionally, so
# consumers can trivially build and package the project
if(PROJECT_IS_TOP_LEVEL)
option(async-mqtt5_DEVELOPER_MODE "Enable developer mode" OFF)
endif()
# ---- Warning guard ----
# target_include_directories with the SYSTEM modifier will request the compiler
# to omit warnings from the provided paths, if the compiler supports that
# This is to provide a user experience similar to find_package when
# add_subdirectory or FetchContent is used to consume this project
set(warning_guard "")
if(NOT PROJECT_IS_TOP_LEVEL)
option(
async-mqtt5_INCLUDES_WITH_SYSTEM
"Use SYSTEM modifier for async-mqtt5's includes, disabling warnings"
ON
)
mark_as_advanced(async-mqtt5_INCLUDES_WITH_SYSTEM)
if(async-mqtt5_INCLUDES_WITH_SYSTEM)
set(warning_guard SYSTEM)
endif()
endif()

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
@ -31,4 +31,4 @@ github_checks:
annotations: true
fixes:
- ".*/async_mqtt5/::include/async_mqtt5/"
- ".*/boost/mqtt5/::include/boost/mqtt5/"

View File

@ -1,149 +1,96 @@
#
# Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
project async_mqtt5/doc ;
import os ;
local BOOST_ROOT = [ os.environ DevRoot ] ;
using boostbook
: $(BOOST_ROOT)3rdParty/boost-doc/boostbook/docbook-xsl-1.79.1
: $(BOOST_ROOT)3rdParty/boost-doc/boostbook/docbook-dtd-4.2
: $(BOOST_ROOT)3rdParty/boost-doc/boostbook
;
using xsltproc ;
using doxygen ;
# we shall use os.platform to correctly map quickbook executable
# echo [ os.platform ] ;
if [ os.name ] = MACOSX
{
using quickbook : $(BOOST_ROOT)build/macos-arm64-release/bin/quickbook ;
}
else if [ os.name ] = LINUX
{
using quickbook : $(BOOST_ROOT)build/linux-native-x64-release/bin/quickbook ;
}
project mqtt5/doc ;
import boostbook ;
import os ;
import path ;
import-search /boost/docca ;
import docca ;
make xml/index.xml
:
reference.dox
# additional dependencies
../include/async_mqtt5/error.hpp
../include/async_mqtt5/reason_codes.hpp
../include/async_mqtt5/types.hpp
../include/async_mqtt5/mqtt_client.hpp
:
@call-doxygen
;
local include-prefix = [ path.root $(__file__:D) [ path.pwd ] ] ;
include-prefix = [ path.native $(include-prefix:D)/include ] ;
# combine.xslt is generated after using doxygen but bjam is unaware of it
make xml/combine.xslt
:
xml/index.xml
:
@null-action
;
local doxygen_include =
error.hpp
logger.hpp
reason_codes.hpp
types.hpp
mqtt_client.hpp
;
make xml/all.xml
:
xml/combine.xslt
xml/index.xml
:
@call-xsltproc
;
docca.pyreference reference.qbk
:
[ glob-tree-ex ../include/boost/mqtt5 : $(doxygen_include) : detail impl ]
:
<doxygen:param>PROJECT_NAME=MQTT5
<doxygen:param>PROJECT_BRIEF="C++ MQTT5 Client Library"
<doxygen:param>DISTRIBUTE_GROUP_DOC=YES
<doxygen:param>ENABLE_PREPROCESSING=YES
<doxygen:param>MACRO_EXPANSION=YES
<doxygen:param>EXPAND_ONLY_PREDEF=YES
<doxygen:param>SEARCH_INCLUDES=NO
<doxygen:param>STRIP_FROM_PATH=$(include-prefix)
make reference.qbk
:
reference.xsl
xml/all.xml
:
@call-xsltproc
;
<doxygen:param>SKIP_FUNCTION_MACROS=NO
<doxygen:param>OUTPUT_LANGUAGE=English
<doxygen:param>ABBREVIATE_BRIEF=
<doxygen:param>AUTOLINK_SUPPORT=NO
<doxygen:param>EXTRACT_ALL=YES
<doxygen:param>HIDE_UNDOC_MEMBERS=YES
<doxygen:param>HIDE_UNDOC_CLASSES=YES
<doxygen:param>HIDE_FRIEND_COMPOUNDS=YES
<doxygen:param>CASE_SENSE_NAMES=YES
<doxygen:param>SHOW_INCLUDE_FILES=NO
<doxygen:param>INLINE_INFO=NO
<doxygen:param>SORT_MEMBER_DOCS=NO
<doxygen:param>SORT_MEMBERS_CTORS_1ST=YES
<doxygen:param>SHOW_USED_FILES=NO
<doxygen:param>SHOW_FILES=NO
<doxygen:param>SHOW_NAMESPACES=NO
# We have to make a copy of reference.qbk and put it
# in a place where the static .qbk files can find it
install qbk/reference : reference.qbk ;
actions null-action
{
# the action is used with "make" rule to make bjam aware that a file exists
}
actions call-doxygen
{
doxygen $(2)
}
actions call-xsltproc
{
xsltproc $(2) > $(1)
}
install stylesheets
:
$(BOOST_ROOT)3rdParty/boost-doc/style/boostbook.css
:
<location>html/
;
explicit stylesheets ;
install callouts
:
[ glob $(BOOST_ROOT)3rdParty/boost-doc/style/images/callouts/*.png ]
:
<location>html/images/callouts
;
explicit callout ;
<docca:config>config.json
;
install images
:
[ glob $(BOOST_ROOT)3rdParty/boost-doc/style/images/*.png ]
:
<location>html/images
;
:
[ glob images/*.png ]
:
<location>html/mqtt5/images
;
explicit images ;
xml async_mqtt5_doc
:
qbk/00_main.qbk
:
<dependency>reference.qbk
<dependency>images
;
xml mqtt5_doc
:
qbk/00_main.qbk
:
<dependency>reference.qbk
<dependency>images
;
explicit async_mqtt5_doc ;
explicit mqtt5_doc ;
boostbook async_mqtt5
:
async_mqtt5_doc
:
<xsl:param>"boost.root=https://www.boost.org/doc/libs/1_82_0"
<xsl:param>boost.graphics.root=images/
<xsl:param>nav.layout=none
<xsl:param>chapter.autolabel=1
<xsl:param>chunk.section.depth=8
<xsl:param>chunk.first.sections=1
<xsl:param>toc.max.depth=2
<xsl:param>generate.toc="chapter toc,title section nop reference nop part toc"
<xsl:param>html.stylesheet=boostbook.css
:
<dependency>stylesheets
<dependency>images
;
boostbook mqtt5
:
mqtt5_doc
:
<xsl:param>boost.root=../../../..
<xsl:param>chapter.autolabel=1
<xsl:param>chunk.section.depth=8
<xsl:param>chunk.first.sections=1
<xsl:param>toc.max.depth=2
<xsl:param>generate.toc="chapter toc,title section nop reference nop part toc"
<include>../../../tools/boostbook/dtd
:
<dependency>images
;
# These are used to inform the build system of the
# means to build the integrated and stand-alone docs.
@ -151,5 +98,5 @@ boostbook async_mqtt5
alias boostdoc ;
explicit boostdoc ;
alias boostrelease : async_mqtt5 ;
alias boostrelease : mqtt5 ;
explicit boostrelease ;

17
doc/config.json Normal file
View File

@ -0,0 +1,17 @@
{
"include_private": false,
"legacy_behavior": false,
"external_marker": "!EXTERNAL!",
"link_prefix": "mqtt5.ref.",
"convenience_header": "boost/mqtt5.hpp",
"replace_strings": {
"__see_below__": "``['see-below]``",
"\\btypename CompletionToken\\b": "typename __CompletionToken__",
"\\btypename Executor\\b": "typename __Executor__",
"\\btypename ExecutionContext\\b": "typename __ExecutionContext__",
"\\btypename TlsContext\\b": "typename __TlsContext__",
"\\btypename StreamType\\b": "typename __StreamType__",
"\\bis_authenticator\\b": "__is_authenticator__",
"\\btypename LoggerType\\b": "typename __LoggerType__"
}
}

View File

@ -1,13 +1,13 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[library Async.MQTT5: a C++17 MQTT client
[library Boost.MQTT5: a C++17 MQTT client
[quickbook 1.7]
[copyright 2023-2024 Mireo]
[id async_mqtt5]
[copyright 2023-2025 Mireo]
[id mqtt5]
[purpose C++17 MQTT client]
[license
Distributed under the Boost Software License, Version 1.0.
@ -22,13 +22,14 @@
[template nochunk[] [block '''<?dbhtml stop-chunking?>''']]
[template mdash[] '''&mdash; ''']
[template hr[] '''<phrase role="silver">'''[mdash]'''</phrase>''']
[template include_file[path][^<'''<ulink url="GITHUB_LINK">'''[path]'''</ulink>'''>]]
[template link_to_file[path][^'''<ulink url="https://github.com/boostorg/mqtt5/blob/master/'''[path]'''">'''[path]'''</ulink>''']]
[template include_file[path][^<'''<ulink url="https://github.com/boostorg/mqtt5/blob/master/include/'''[path]'''">'''[path]'''</ulink>'''>]]
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
[template ghreflink[path text] [@https://github.com/mireo/async-mqtt5/blob/master/[path] [text]]]
[template reflink2[id text][link async_mqtt5.ref.[id] [^[text]]]]
[template reflink[id][reflink2 [id] [id]]]
[template ghreflink[path text] [@https://github.com/boostorg/mqtt5/blob/master/[path] [text]]]
[template reflink2[id text][link mqtt5.ref.boost__mqtt5__[id] [^[text]]]]
[template reflink[id text][link mqtt5.ref.[id] [text]]]
[template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]]
[template refmemunq[class mem][reflink2 [class].[mem] [mem]]]
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^boost::asio::[term]]]]
@ -37,13 +38,15 @@
[template mqttlink[id text][@https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc[id] [text]]]
[def __OPENSSL__ [@https://www.openssl.org/ OpenSSL]]
[def __HIVEMQ__ [@https://www.hivemq.com/ HiveMQ]]
[def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers CompletionToken]]
[def __Executor__ [@boost:/doc/html/boost_asio/reference/Executor1.html Executor]]
[def __ExecutionContext__ [reflink ExecutionContext]]
[def __StreamType__ [reflink StreamType]]
[def __TlsContext__ [reflink TlsContext]]
[def __is_authenticator__ [reflink is_authenticator]]
[def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers ['CompletionToken]]]
[def __Executor__ [@boost:/doc/html/boost_asio/reference/Executor1.html ['Executor]]]
[def __ExecutionContext__ [reflink ExecutionContext ['ExecutionContext]]]
[def __StreamType__ [reflink StreamType ['StreamType]]]
[def __TlsContext__ [reflink TlsContext ['TlsContext]]]
[def __is_authenticator__ [reflink is_authenticator ['is_authenticator]]]
[def __LoggerType__ [reflink LoggerType ['LoggerType]]]
[def __Boost__ [@https://www.boost.org/ Boost]]
[def __Asio__ [@boost:/libs/asio/index.html Boost.Asio]]
@ -58,6 +61,7 @@
[def __STRAND__ [@boost:doc/html/boost_asio/reference/io_context__strand.html `boost::asio::io_context::strand`]]
[def __DISPATCH__ [@boost:doc/html/boost_asio/reference/dispatch.html `boost::asio::dispatch`]]
[def __POST__ [@boost:doc/html/boost_asio/reference/post.html `boost::asio::post`]]
[def __ASYNC_IMMEDIATE__ [@boost:doc/html/boost_asio/reference/async_immediate.html `boost::asio::async_immediate`]]
[def __CO_SPAWN__ [@boost:/doc/html/boost_asio/reference/co_spawn.html `boost::asio::co_spawn`]]
[def __USE_AWAITABLE__ [@boost:/doc/html/boost_asio/reference/use_awaitable.html `boost::asio::use_awaitable`]]
[def __USE_FUTURE__ [@boost:/doc/html/boost_asio/reference/use_future.html `boost::asio::use_future`]]
@ -71,7 +75,7 @@
[/ MQTT ]
[def __MQTT__ [@https://mqtt.org/ MQTT]]
[def __Self__ [@https://github.com/mireo/async-mqtt5/ Async.MQTT5]]
[def __Self__ [@https://github.com/boostorg/mqtt5/ Boost.MQTT5]]
[def __Client__ [reflink2 mqtt_client `mqtt_client`]]
[def __UTF8_STRING_PAIR__ [mqttlink 3901013 `UTF-8 String Pair`]]
@ -83,6 +87,7 @@
[def __RETAIN__ [mqttlink 3901104 `RETAIN`]]
[def __SUBSCRIBE_OPTIONS__ [mqttlink 3901169 `Subscribe Options`]]
[def __ENHANCED_AUTH__ [mqttlink 3901256 `Enhanced Authentication`]]
[def __RE_AUTHENTICATION__ [mqttlink 3901257 `Re-authentication`]]
[def __TOPIC_SEMANTIC_AND_USAGE__ [mqttlink 3901247 `Topic semantic and usage`]]
[def __CONNECT__ [mqttlink 3901033 `CONNECT`]]
@ -101,46 +106,78 @@
[def __DISCONNECT__ [mqttlink 3901205 `DISCONNECT`]]
[def __AUTH__ [mqttlink 3901217 `AUTH`]]
[def __WILL_PROPS__ [reflink2 will_props async_mqtt5::will_props]]
[def __CONNECT_PROPS__ [reflink2 connect_props async_mqtt5::connect_props]]
[def __CONNACK_PROPS__ [reflink2 connack_props async_mqtt5::connack_props]]
[def __PUBLISH_PROPS__ [reflink2 publish_props async_mqtt5::publish_props]]
[def __PUBACK_PROPS__ [reflink2 puback_props async_mqtt5::puback_props]]
[def __PUBREC_PROPS__ [reflink2 pubrec_props async_mqtt5::pubrec_props]]
[def __PUBREL_PROPS__ [reflink2 pubrel_props async_mqtt5::pubrel_props]]
[def __PUBCOMP_PROPS__ [reflink2 pubcomp_props async_mqtt5::pubcomp_props]]
[def __SUBSCRIBE_PROPS__ [reflink2 subscribe_props async_mqtt5::subscribe_props]]
[def __SUBACK_PROPS__ [reflink2 suback_props async_mqtt5::suback_props]]
[def __UNSUBSCRIBE_PROPS__ [reflink2 unsubscribe_props async_mqtt5::unsubscribe_props]]
[def __UNSUBACK_PROPS__ [reflink2 unsuback_props async_mqtt5::unsuback_props]]
[def __DISCONNECT_PROPS__ [reflink2 disconnect_props async_mqtt5::disconnect_props]]
[def __AUTH_PROPS__ [reflink2 auth_props async_mqtt5::auth_props]]
[def __WILL_PROPS__ [reflink will_props `boost::mqtt5::will_props`]]
[def __CONNECT_PROPS__ [reflink connect_props `boost::mqtt5::connect_props`]]
[def __CONNACK_PROPS__ [reflink connack_props `boost::mqtt5::connack_props`]]
[def __PUBLISH_PROPS__ [reflink publish_props `boost::mqtt5::publish_props`]]
[def __PUBACK_PROPS__ [reflink puback_props `boost::mqtt5::puback_props`]]
[def __PUBREC_PROPS__ [reflink pubrec_props `boost::mqtt5::pubrec_props`]]
[def __PUBREL_PROPS__ [reflink pubrel_props `boost::mqtt5::pubrel_props`]]
[def __PUBCOMP_PROPS__ [reflink pubcomp_props `boost::mqtt5::pubcomp_props`]]
[def __SUBSCRIBE_PROPS__ [reflink subscribe_props `boost::mqtt5::subscribe_props`]]
[def __SUBACK_PROPS__ [reflink suback_props `boost::mqtt5::suback_props`]]
[def __UNSUBSCRIBE_PROPS__ [reflink unsubscribe_props `boost::mqtt5::unsubscribe_props`]]
[def __UNSUBACK_PROPS__ [reflink unsuback_props `boost::mqtt5::unsuback_props`]]
[def __DISCONNECT_PROPS__ [reflink disconnect_props `boost::mqtt5::disconnect_props`]]
[def __AUTH_PROPS__ [reflink auth_props `boost::mqtt5::auth_props`]]
[def __KEEP_ALIVE__ [mqttlink 3901045 `Keep Alive`]]
[def __SERVER_KEEP_ALIVE__ [mqttlink 3901094 `Server Keep Alive`]]
[def __ERROR_CODE__ [reflink2 error_code `async_mqtt5::error_code`]]
[def __REASON_CODE__ [reflink2 reason_code `async_mqtt5::reason_code`]]
[def __ERROR_CODE__ [reflink2 error_code `boost::mqtt5::error_code`]]
[def __REASON_CODE__ [reflink2 reason_code `boost::mqtt5::reason_code`]]
[def __REASON_CODES__ [reflink2 Reason_codes `Reason Codes`]]
[def __ERROR_HANDLING__ [reflink2 Error_handling `Error handling`]]
[def __REASON_CODES__ [reflink Reason_codes `Reason Codes`]]
[def __ERROR_HANDLING__ [reflink Error_handling `Error handling`]]
[import ../../example/hello_world_over_tcp.cpp]
[import ../../example/hello_world_over_tls.cpp]
[import ../../example/hello_world_over_websocket_tcp.cpp]
[import ../../example/hello_world_over_websocket_tls.cpp]
[import ../../example/publisher.cpp]
[import ../../example/receiver.cpp]
[import ../../example/multiflight_client.cpp]
[import ../../example/timeout_with_parallel_group.cpp]
[import ../../example/timeout_with_awaitable_operators.cpp]
[import ../../example/hello_world_in_multithreaded_env.cpp]
[import ../../example/hello_world_in_coro_multithreaded_env.cpp]
[include 01_intro.qbk]
[include 02_configuring_the_client.qbk]
[include 02_getting_started.qbk]
[include 03_auto_reconnect.qbk]
[include 04_maintaining_a_stable_connection.qbk]
[include 05_optimising_communication.qbk]
[include 06_disconnecting_the_client.qbk]
[include 07_asio_compliance.qbk]
[include 11_multithreading.qbk]
[include 15_examples.qbk]
[include 12_examples.qbk]
[include examples/Examples.qbk]
[section:ref Reference]
[xinclude reference/quickref.xml]
[block'''<part label="Two: Reference">''']
[include reference/reference.qbk]
[include reference.qbk]
[include reference/Error_handling.qbk]
[include reference/concepts/ExecutionContext.qbk]
[include reference/concepts/StreamType.qbk]
[include reference/concepts/TlsContext.qbk]
[include reference/concepts/is_authenticator.qbk]
[include reference/concepts/LoggerType.qbk]
[include reference/reason_codes/Reason_codes.qbk]
[include reference/properties/will_props.qbk]
[include reference/properties/connect_props.qbk]
[include reference/properties/connack_props.qbk]
[include reference/properties/publish_props.qbk]
[include reference/properties/puback_props.qbk]
[include reference/properties/pubrec_props.qbk]
[include reference/properties/pubrel_props.qbk]
[include reference/properties/pubcomp_props.qbk]
[include reference/properties/subscribe_props.qbk]
[include reference/properties/suback_props.qbk]
[include reference/properties/unsubscribe_props.qbk]
[include reference/properties/unsuback_props.qbk]
[include reference/properties/disconnect_props.qbk]
[include reference/properties/auth_props.qbk]
[block'''</part>''']
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -15,98 +15,41 @@ offering full support for publishing or receiving messages with QoS 0, 1, and 2.
[heading Motivation]
The __MQTT__ protocol is widely utilised for communication in various real-world scenarios,
primarily serving as a reliable communication protocol for data transfer to and from IoT devices.
While the MQTT protocol itself is relatively straightforward, integrating it into an application can be complex,
especially due to the challenging implementation of message retransmission after a disconnect/reconnect sequence.
While the MQTT protocol is relatively straightforward, integrating it into an application can be complex,
primarily due to the challenging implementation of message retransmission after a disconnect/reconnect sequence.
To address these challenges and simplify MQTT integration once and for all, __Self__ was designed with the following core objectives in mind:
The aim of __Self__ is to provide a very simple asynchronous C++ interface for application developers.
The internal Client's implementation manages network and MQTT protocol details.
Notably, the Client does not expose connect functions (nor asynchronous connect functions);
instead, network connectivity, MQTT handshake, and message retransmission are automatically handled within the Client.
[table:objectives Core objectives
[[Objective] [Description]]
[
["Plug and play" interface that abstracts away MQTT protocol internals, low-level network events, message retransmission and other complexities]
[
Enables developers to publish or receive messages with just a single line of code, significantly reducing the learning curve and development time.
Getting started requires basic MQTT knowledge, making the library accessible to developers of all skill levels.
]
]
[
[Highly reliable and robust Client that handles network losses, unreliable data transport, network latencies, and other unpredictable events]
[
The Client does not expose connect functions (nor asynchronous connect functions); instead, network connectivity, MQTT handshake, and message retransmission are automatically
handled within the Client while strictly adhering to the MQTT specification.
This automation eliminates the need for developers to write extensive and error-prone code to handle these scenarios, allowing them to focus on the application's core functionality.
]
]
[
[Complete adherence to the Boost.Asio's universal asynchronous model]
[
The interfaces and implementation strategies are built upon the foundations of Boost.Asio.
This compatibility enables seamless integration with any other library within the Boost.Asio ecosystem.
]
]
]
The __Self__ interface aligns seamlessly with the __Asio__ asynchronous model.
The Client's asynchronous functions are compatible with all completion tokens supported by __Asio__.
[heading Features]
__Self__ is a library designed with the core belief that users should focus solely on their application logic, not the network complexities. [br]
The library attempts to embody this belief with a range of key features designed to elevate the development experience:
* [*Complete TCP, TLS/SLL, and WebSocket support]
* [*User-focused simplicity]: Providing an interface that is as simple as possible without compromising functionality.
* [*Prioritised efficiency]: Utilising network and memory resources as efficiently as possible.
* [*Minimal memory footprint]: Ensuring optimal performance in resource-constrained environments typical of IoT devices.
* [*Automatic reconnect]: Automatically attempt to re-establish a connection in the event of a disconnection.
* [*Fully Boost.Asio compliant]: The interfaces and implementation strategies are built upon the foundations of __Asio__. [br]
__Asio__ and __Beast__ users will have no issues understanding and integrating __Self__. [br]
Furthermore, __Self__ integrates well with any other library within the Boost.Asio ecosystem.
* [*Custom allocators]: Support for custom allocators allows extra flexibility and control over the memory resources. [br]
__Self__ will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation.
* [*Per-Operation Cancellation]: All asynchronous operations support individual, targeted cancellation as per Asios __ASIO_PER_OP_CANCELLATION__.
* [*Completion Token]: All asynchronous functions support __CompletionToken__, allowing for versatile usage with callbacks, coroutines, futures, and more.
* [*Full implementation of MQTT 5.0 specification]
* [*Support for QoS 0, QoS 1, and QoS 2]
* [*Custom authentication]: __Self__ defines an interface for your own custom authenticators to perform Enhanced Authentication.
* [*High availability]: __Self__ supports listing multiple Brokers within the same cluster to which the Client can connect. [br]
In the event of a connection failure with one Broker, the Client switches to the next in the list.
* [*Offline buffering]: While offline, it automatically buffers all the packets to send when the connection is re-established.
[heading Example]
The following example illustrates a simple scenario of configuring a Client and publishing a
"Hello World!" Application Message with `QoS` 0.
[!c++]
#include <iostream>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
int main() {
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
c.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&c](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
c.async_disconnect(boost::asio::detached); // disconnect and close the client
}
);
ioc.run();
}
To see more examples, visit [link async_mqtt5.examples Examples].
[heading Building with CMake]
You can use the `CMakeLists.txt` provided in our repository to compile and run any of the [link async_mqtt5.examples examples] or your own source files.
The following commands demonstrate compiling and running the previous code using CMake on Linux.
The source file is located at [ghreflink example/hello_world_over_tcp.cpp example/hello_world_over_tcp.cpp].
# navigate to the root folder of Async.MQTT5
# compile the example
cmake -S . -B {build-folder} -DBUILD_EXAMPLES=ON -DCMAKE_EXE_LINKER_FLAGS="-pthread"
cmake --build {build-folder}
# run the example
./{build-folder}/example/example
You can edit the [ghreflink example/CMakeLists.txt example/CMakeLists.txt] file to compile the source file of your choice.
By default, it will compile [ghreflink example/hello_world_over_tcp.cpp example/hello_world_over_tcp.cpp].
[heading When to use]
[heading When to Use]
__Self__ might be suitable for you if any of the following statements is true:
* Your application uses __Asio__ and requires integrating a MQTT Client.
* Your application uses __Asio__ and requires integrating an MQTT Client.
* You require asynchronous access to an MQTT Broker.
* You are developing a higher-level component that requires a connection to an MQTT Broker.
* You require a dependable and resilient MQTT Client to manage all network-related issues automatically.
@ -116,21 +59,156 @@ It may not be suitable for you if:
* You solely require synchronous access to an MQTT Broker.
* The MQTT Broker you connect to does not support the MQTT 5 version.
[heading Requirements]
[heading Features]
__Self__ is a library designed with the core belief that users should focus solely on their application logic, not the network complexities. [br]
The library attempts to embody this belief with a range of key features designed to elevate the development experience:
[table:features Features
[[Feature] [Description]]
[
[[*Complete TCP, TLS/SSL, and WebSocket support]]
[
The MQTT protocol requires an underlying network protocol that provides ordered, lossless, bi-directional connection (stream).
Users can customize this stream through a template parameter.
The Boost.MQTT5 library has been tested with the most used transport protocols:
TCP/IP using `boost::asio::ip::tcp::socket`, TLS/SSL using `boost::asio::ssl::stream`, WebSocket/TCP and WebSocket/TLS using `boost::beast::websocket::stream`).
]
]
[
[[*Automatic reconnect and retry mechanism]]
[
Automatically handles connection loss, backoffs, reconnections, and message transmissions.
Automating these processes enables users to focus entirely on the application's functionality.
See [link mqtt5.auto_reconnect Built-in Auto-Reconnect and Retry Mechanism].
]
]
[
[[*Prioritised efficiency]]
[Utilises network and memory resources as efficiently as possible.]
]
[
[[*Minimal memory footprint]]
[Ensures optimal performance in resource-constrained environments typical of IoT devices.]
]
[
[[*Completion token]]
[
All asynchronous functions are implemented using Boost.Asio's universal asynchronous model and support CompletionToken.
This allows versatile usage with callbacks, coroutines, and futures.
]
]
[
[[*Custom allocators]]
[
Support for custom allocators allows extra flexibility and control over the memory resources.
Boost.MQTT5 will use allocators associated with handlers from asynchronous functions to create instances of objects needed in the library implementation.
]
]
[
[[*Per-operation cancellation]]
[
All asynchronous operations support individual, targeted cancellation as per Asio's __ASIO_PER_OP_CANCELLATION__.
This enables all asynchronous functions to be used in [@boost:/doc/html/boost_asio/overview/composition/parallel_group.html Parallel Operations],
which is especially beneficial for executing operations that require a timeout mechanism.
]
]
[
[[*Full implementation of MQTT 5.0 specification]]
[
Ensures full compatibility with [@https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html MQTT 5.0 standard].
This latest version introduces essential features that enhance system robustness, including advanced error-handling mechanisms,
session and message expiry, and other improvements designed to support modern IoT use cases.
]
]
[
[[*Support for QoS 0, QoS 1, and QoS 2]]
[Fully implements all quality-of-service levels defined by the MQTT protocol to match different reliability needs in message delivery.]
]
[
[[*Custom authentication]]
[
Defines an interface for your custom authenticators to perform __ENHANCED_AUTH__.
In addition to username/password authentication, this customization point enables the implementation of any authentication mechanism supported by the SASL protocol,
offering flexibility for advanced security requirements.
]
]
[
[[*High availability]]
[
Supports listing multiple Brokers within the same cluster to which the Client can connect.
In the event of a connection failure with one Broker, the Client switches to the next in the list.
]
]
[
[[*Offline buffering]]
[
Automatically buffers all requests made while offline, ensuring they are sent when the connection is re-established.
This allows users to call any asynchronous function freely, regardless of current connection status.
]
]
]
[heading Getting Started]
__Self__ is a header-only library.
To use __Self__ it requires the following:
* [*C++17 capable compiler]
* [*Boost 1.82 or later]. In addition to Asio, we use other header-only libraries such as Beast, Spirit, and more.
* [*OpenSSL]. Only if you require an SSL connection by using [asioreflink ssl__stream ssl::stream].
* [*Boost 1.82 or later]. In addition to Asio, we use other header-only libraries.
* [*OpenSSL]. If you require an SSL connection by using [asioreflink ssl__stream ssl::stream].
__Self__ has been tested with the following compilers:
* clang 12.0 - 15.0 (Linux)
* clang 12.0 - 18.0 (Linux)
* GCC 9 - 14 (Linux)
* MSVC 14.37 - Visual Studio 2022 (Windows)
[heading Using the Library]
# Download [@https://www.boost.org/users/download/ Boost], and add it to your include path.
# If you use SSL, download [@https://www.openssl.org/ OpenSSL], link the library and add it to your include path.
# Add Boost.MQTT5's `include` folder to your include path.
You can compile the example below with the following command line on Linux:
clang++ -std=c++17 <source-cpp-file> -o example -I<path-to-boost> -Iinclude -pthread
[heading Example]
The following example illustrates a scenario of configuring a Client and publishing a
"Hello World!" Application Message with `QoS` 0.
[!c++]
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
int main() {
boost::asio::io_context ioc;
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> c(ioc);
c.brokers("<your-mqtt-broker>", 1883)
.credentials("<your-client-id>", "<client-username>", "<client-pwd>")
.async_run(boost::asio::detached);
c.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[&c](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
c.async_disconnect(boost::asio::detached); // disconnect and close the Client
}
);
ioc.run();
}
To see more examples, visit [link mqtt5.examples Examples].
[heading Acknowledgements]
We thank [@https://github.com/chriskohlhoff Christopher Kohlhoff] for his outstanding __Asio__ library,
which inspired the design of all interfaces and implementation strategies.

View File

@ -1,10 +1,10 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:configuring_the_client Configuring the Client]
[section:getting_started Getting Started]
[nochunk]
This section guides you through the steps to properly configure the __Client__ to establish a connection with your chosen MQTT Broker.
@ -14,7 +14,7 @@ Instead, it features a function that will start the Client (see [refmem mqtt_cli
All configuration of the __Client__ must be finalised before invoking [refmem mqtt_client async_run].
Upon calling this function, the __Client__ makes its initial attempt to connect.
[section:transport_protocol Choosing a suitable transport protocol]
[section:transport_protocol Choosing a Suitable Transport Protocol]
The initial step is selecting an appropriate transport protocol for your MQTT connection.
@ -25,23 +25,24 @@ and WebSocket over TCP/IP and TLS/SSL.
MQTT Brokers come in various implementations and support different transport protocols.
Therefore, it is important to familiarise yourself with the protocols your chosen Broker supports.
Additionally, gather any necessary details such as certificate authority, client certificate, or private keys,
Additionally, gather any necessary details, such as certificate authority, client certificate, or private keys,
which might be required for establishing a secure connection.
The upcoming examples will demonstrate the configuration and the publishing of a "Hello World!" application message at `QoS` 0,
using various transport protocols:
In this example, we choose TCP/IP as the underlying protocol to initialize the __Client__.
* [link async_mqtt5.hello_world_over_tcp hello_world_over_tcp.cpp]
* [link async_mqtt5.hello_world_over_tls hello_world_over_tls.cpp]
* [link async_mqtt5.hello_world_over_websocket_tcp hello_world_over_websocket_tcp.cpp]
* [link async_mqtt5.hello_world_over_websocket_tls hello_world_over_websocket_tls.cpp]
[!c++]
// Initialize the execution context required to run I/O operations.
boost::asio::io_context ioc;
// Construct the Client with ``__TCP_SOCKET__`` as the underlying stream.
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
[endsect] [/transport_protocol]
[section:customisation Customising your MQTT connection]
[section:configuration Configuring Your MQTT Connection]
The __Client__ offers a variety of functions for customising your MQTT connection settings.
These functionalities include:
The __Client__ offers a variety of functions for configuring your MQTT connection settings.
These functionalities include:
* *Specifying a list of Brokers:* You *must* use this function to assign a list of Brokers that the __Client__ will try to connect to (see [refmem mqtt_client brokers]).
The __Client__ allows for the specification of multiple Brokers for the connections.
@ -52,13 +53,22 @@ Listing Brokers from different clusters may lead to inconsistencies between MQTT
* *Assign a custom user-implemented authenticator:* The custom authentication will be used for __ENHANCED_AUTH__ ([refmem mqtt_client authenticator]).
* *Defining CONNECT Packet Properties:* Specify properties that will be included in the __CONNECT__ packet sent during connection initiation (see [refmem mqtt_client connect_property] and [refmem mqtt_client connect_properties]).
Firstly, we will specify the Broker we want to connect to using the [refmem mqtt_client brokers] function.
This can be __HIVEMQ__'s public Broker available at `broker.hivemq.com`:1883.
Additionally, we can set the Client Identifier using the [refmem mqtt_client credentials] function. This is not mandatory, as some Brokers allow anonymous connections.
After configuring the `mqtt_client`, we call the [refmem mqtt_client async_run] function.
This starts the process of establishing a connection with the Broker.
[configure_tcp_client]
It is important to note that these configurations apply to every Broker listed in [refmem mqtt_client brokers].
To modify any configuration parameters, you must first stop the __Client__ using [refmem mqtt_client cancel] or [refmem mqtt_client async_disconnect].
Afterwards, you can re-apply the configurations and restart the __Client__ with [refmem mqtt_client async_run].
[endsect] [/customisation]
[endsect] [/configuration]
[section:establishing_a_connection Establishing a connection]
[section:establishing_a_connection Establishing a Connection]
The __Client__ initiates a connection with the first Broker from the list of Brokers assigned using [refmem mqtt_client brokers].
A single attempt at connecting involves several steps:
@ -80,11 +90,43 @@ If this connection attempt is unsuccessful, it proceeds to the next Broker in or
Upon reaching the end of the list without a successful connection, the __Client__ enters a backoff period before making another round of attempts with the entire list.
The duration of this backoff period in milliseconds is determined by the formula:
2^(min(retry_count, 4)) * 1000 + jitter
2^(min(retry_count, 4)) * 1000 + jitter
In this formula, `retry_count` represents the number of complete cycles through the list without a successful connection,
and `jitter` is a randomly chosen value between `-500ms` to `500ms`, intended to prevent synchronised reconnection attempts in scenarios with multiple clients.
[endsect] [/establishing_a_connection]
[endsect] [/configuring_the_client]
[section:using_the_client Using the Client]
After calling [refmem mqtt_client async_run], you can now use the Client according to your application needs.
In this case, we will publish a "Hello World!" message to `boost-mqtt5/test` topic with Quality of Service 0 (at most once) using the [refmem mqtt_client async_publish] function.
[publish_hello_world]
You can find the full program listing for this chapter at the link below:
* [link mqtt5.hello_world_over_tcp hello_world_over_tcp.cpp]
Additional "Hello World!" examples for different underlying transport protocols can be found here:
* [link mqtt5.hello_world_over_tls hello_world_over_tls.cpp]
* [link mqtt5.hello_world_over_websocket_tcp hello_world_over_websocket_tcp.cpp]
* [link mqtt5.hello_world_over_websocket_tls hello_world_over_websocket_tls.cpp]
[endsect] [/using_the_client]
[section:debugging Debugging the Client]
If you encounter configuration or connection issues, you can use our logging mechanism to debug problems.
The __Client__ introduces a __LoggerType__ as its third template parameter, which specifies the type used for logging events within the __Client__.
The __Self__ library provides a built-in [ghreflink include/boost/mqtt5/logger.hpp logger] implementation that outputs operation results to stderr.
This logger outputs detailed information about each step in the connection process, including DNS resolution, TCP connection, TLS handshake, WebSocket handshake, and MQTT handshake.
To enable this functionality, construct the __Client__ with an instance of this logger class:
[init_tcp_client_with_logger]
[endsect] [/debugging]
[endsect] [/getting_started]

View File

@ -1,10 +1,10 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:auto_reconnect Built-in auto-reconnect and retry mechanism]
[section:auto_reconnect Built-in Auto-Reconnect and Retry Mechanism]
[nochunk]
The auto-reconnect and retry mechanism is a key feature of the __Self__ library.
@ -24,49 +24,49 @@ Note that the same principle applies to all other asynchronous functions within
(see /Completion condition/ under [refmem mqtt_client async_publish], [refmem mqtt_client async_subscribe], [refmem mqtt_client async_unsubscribe],
and [refmem mqtt_client async_disconnect]).
// Publishing with QoS 1 involves a two-step process: sending a PUBLISH message to the Broker and awaiting a PUBACK (acknowledgement) response.
// The scenarios that might happen are:
// 1) The Client will immediately send the PUBLISH message and start to wait for the PUBACK reply.
// The user's completion handler will not be invoked yet, regardless of whether the Client successfully sent the message.
// 2) When the Client fails to send the message, it will try to reconnect to the Broker automatically.
// If it succeeds, it will resend the message. If reconnect fails, the Client will try to connect again until it succeeds.
// Meanwhile, the user's completion handler will NOT be invoked except when the Client detects an unrecoverable error (for example, when the list of configured brokers is empty).
// Note that the Client will try to connect indefinitely, meaning the user's completion handler may never be invoked.
// 3) When the Client successfully sends the PUBLISH message, the Broker is required to respond with a PUBACK message.
// The reply message may indicate success or an MQTT error (for example, signalling that the message is not accepted due to implementation or administrative limits).
// The Client will read the PUBACK message and call the user's completion handler with the appropriate arguments.
// 4) The PUBACK message should arrive within 20 seconds of the PUBLISH message being successfully sent.
// If it does not, the PUBLISH message will be sent again. In the meantime, the user's callback will not be invoked.
// Publishing with QoS 1 involves a two-step process: sending a PUBLISH message to the Broker and awaiting a PUBACK (acknowledgement) response.
// The scenarios that might happen are:
// 1) The Client will immediately send the PUBLISH message and start to wait for the PUBACK reply.
// The user's completion handler will not be invoked yet, regardless of whether the Client successfully sent the message.
// 2) When the Client fails to send the message, it will try to reconnect to the Broker automatically.
// If it succeeds, it will resend the message. If reconnect fails, the Client will try to connect again until it succeeds.
// Meanwhile, the user's completion handler will NOT be invoked except when the Client detects an unrecoverable error (for example, when the list of configured brokers is empty).
// Note that the Client will try to connect indefinitely, meaning the user's completion handler may never be invoked.
// 3) When the Client successfully sends the PUBLISH message, the Broker is required to respond with a PUBACK message.
// The reply message may indicate success or an MQTT error (for example, signalling that the message is not accepted due to implementation or administrative limits).
// The Client will read the PUBACK message and call the user's completion handler with the appropriate arguments.
// 4) The PUBACK message should arrive within 20 seconds of the PUBLISH message being successfully sent.
// If it does not, the PUBLISH message will be sent again. In the meantime, the user's callback will not be invoked.
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"my-topic", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::puback_props props) {
// This callback is invoked under any of the following circumstances:
// a) The Client successfully sends the PUBLISH packet and receives a PUBACK from the Broker.
// b) The Client encounters a non-recoverable error, such as a cancellation or providing invalid parameters
// to async_publish, which prevents the message from being sent.
}
);
client.async_publish<boost::mqtt5::qos_e::at_least_once>(
"my-topic", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[](boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc, boost::mqtt5::puback_props props) {
// This callback is invoked under any of the following circumstances:
// a) The Client successfully sends the PUBLISH packet and receives a PUBACK from the Broker.
// b) The Client encounters a non-recoverable error, such as a cancellation or providing invalid parameters
// to async_publish, which prevents the message from being sent.
}
);
[section:sentry ]
[endsect] [/sentry]
[section:cons Considerations and limitations]
[section:cons Considerations and Limitations]
The integrated auto-reconnect and retry mechanism greatly improves the user experience
by simplifying complex processes and ensuring continuous connections.
However, it is important to be mindful of certain limitations and considerations associated with this feature.
[heading Delayed handler invocation]
[heading Delayed Handler Invocation]
During extended periods of __Client__ downtime, the completion handlers for asynchronous functions,
such as those used in [refmem mqtt_client async_publish], may face considerable delays before invocation.
This can result in users being left in the dark regarding the status of their requests due to the absence of prompt feedback on the initiated actions.
[heading Concealing configuration-related issues]
[heading Concealing Configuration-Related Issues]
The __Client__ will always try to reconnect to the Broker(s) regardless of the reason why the connection was previously closed.
This is desirable behaviour when the connection gets dropped due to underlying stream transport issues,
@ -93,10 +93,10 @@ By design, one of the main functional requirements of the __Client__ was to hand
If the decision for reconnection were left to the user, then the user would need to handle all those error states manually,
which would dramatically increase the complexity of the user's code, not to mention how difficult it would be to cover all possible error states.
The proposed approach for detecting configuration errors in the __Client__ is to use some simple logging facility during development.
Log lines should be injected directly into the __Client__ code (typically in the connect_op.hpp file), and logs would uncover misconfigurations (if any).
The proposed approach for detecting configuration errors in the __Client__ is to use our logging mechanism as described
in [link mqtt5.getting_started.debugging Debugging the Client]
[heading Increased resource consumption]
[heading Increased Resource Consumption]
The __Client__ is designed to automatically buffer requests that are initiated while it is offline.
During extended downtime or when a high volume of requests accumulates, this can lead to an increase in memory usage.

View File

@ -1,10 +1,10 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:connection_maintenance Maintaining a stable connection]
[section:connection_maintenance Maintaining a Stable Connection]
[nochunk]
This chapter delves into the strategies used by the __Client__ to sustain a stable connection with the Broker.
@ -33,14 +33,14 @@ Use [refmem mqtt_client keep_alive] function during the __Client__ configuration
Otherwise, the __Client__ defaults to a `Keep Alive` period of `60 seconds`.
[note
The MQTT does not use the TCP's built-in keep-alive mechanism as it is inflexible and limited in that it can be configured solely at the operating system level.
The default time a connection must be idle before sending the first keep-alive packet is typically set to 2 hours,
exceeding the tolerances of most MQTT applications.
The MQTT protocol does not use the TCP's built-in keep-alive mechanism as it is inflexible and limited in that it can be configured solely at the operating system level.
The default time a connection must be idle before sending the first keep-alive packet is typically set to 2 hours,
exceeding the tolerances of most MQTT applications.
]
[endsect] [/keep_alive]
[section:half_open_connections Detecting and handling half-open connections]
[section:half_open_connections Detecting and Handling Half-open Connections]
Ensuring the integrity and stability of network connections is a complex task,
given the number of potential disruptions such as software anomalies,
@ -80,7 +80,7 @@ to receive some data
[footnote The __Client__ is not required to specifically receive __PINGRESP__ to its __PINGREQ__. Any data from the Broker will suffice to confirm its status.]
from the Broker within `1.5` times the negotiated `Keep Alive` seconds.
If no data is received within this time, the __Client__ will assume a half-open state and initiate a reconnect procedure
described in the [link async_mqtt5.auto_reconnect Built-in auto-reconnect and retry mechanism].
described in the [link mqtt5.auto_reconnect Built-in auto-reconnect and retry mechanism].
[important If the negotiated `Keep Alive` value is set to `0`, the timeout mechanism to detect a half-open connection
is disabled. As a result, the __Client__ loses its capability to identify and adequately respond to half-open scenarios.]

View File

@ -1,10 +1,10 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:optimising_communication Optimising communication]
[section:optimising_communication Optimising Communication]
[nochunk]
This chapter provides a detailed breakdown of how __Client__ optimises its communications with the Broker
@ -12,7 +12,7 @@ with multiflight mode for simultaneous message handling and strategies for effic
These techniques are key to getting the most out of MQTT in scenarios demanding fast and
dependable message delivery, all while meeting the protocol's quality of service requirements and network efficiency standards.
[section:multiflight The multiflight mode]
[section:multiflight The Multiflight Mode]
The __Self__ library introduces a multiflight feature.
This allows the initiation of multiple asynchronous requests simultaneously, without waiting for the completion of the previous requests.
@ -35,12 +35,12 @@ It is important to note that there is no guarantee that the final handlers will
in the same order as the corresponding `async_xxx` calls were initiated.
[import ../../example/multiflight_client.cpp]
Source: [link async_mqtt5.multiflight_client multiflight_client.cpp]
Source: [link mqtt5.multiflight_client multiflight_client.cpp]
[multiflight_client]
[endsect] [/multiflight]
[section:packet_queuing Efficient bandwidth usage with packet queuing]
[section:packet_queuing Efficient Bandwidth Usage With Packet Queuing]
The __Client__ employs a strategic queuing mechanism crucial in optimising network usage and performance for the user's requests.
This mechanism bundles multiple MQTT packets for transmission within a single TCP packet whenever feasible
@ -56,13 +56,13 @@ __Client__ evaluates the current count of unacknowledged __PUBLISH__ packets aga
If the count is below this threshold, __Client__ dispatches the __PUBLISH__ packet.
Otherwise, it remains in the queue until the count decreases below the threshold.
As a result, in the [link async_mqtt5.multiflight_client multiflight_client.cpp] example,
As a result, in the [link mqtt5.multiflight_client multiflight_client.cpp] example,
the __Client__ will transmit all `5` __PUBLISH__ packets in a single TCP packet
if possible [footnote The Broker's `Receive Maximum` is equal to or greater than `5`.].
[endsect] [/packet_queuing]
[section:packet_ordering Packet ordering]
[section:packet_ordering Packet Ordering]
The __Client__ uses a packet ordering mechanism to manage the queued packets pending dispatch to the Broker.
The most important ordering rules are:
@ -75,7 +75,7 @@ A Broker can set this value to limit the number of simultaneous QoS > 0 messages
potentially causing QoS 0 messages to be transmitted ahead of QoS > 0 messages in the delivery order.
- The __DISCONNECT__ packet is sent *in a single TCP packet before any other packets* in the queue.
See [link async_mqtt5.disconnecting_the_client Disconnecting the client] for more information about disconnecting.
See [link mqtt5.disconnecting_the_client Disconnecting the client] for more information about disconnecting.
[endsect] [/packet_ordering]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -38,23 +38,23 @@ followed by the request to disconnect the __Client__.
```
int main() {
boost::asio::io_context ioc;
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
}
);
client.async_disconnect(boost::asio::detached);
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
}
);
client.async_disconnect(boost::asio::detached);
ioc.run();
ioc.run();
}
```
@ -65,7 +65,7 @@ order of events will unfold:
# The Client will send a __DISCONNECT__ packet with Reason Code `0x00` (`Normal Disconnection`).
It is important to note that the __PUBLISH__ packet containing the "Hello World!" message will not be transmitted.
As outlined in the `Packet Ordering` in [link async_mqtt5.optimising_communication Optimising communication] section,
As outlined in the `Packet Ordering` in [link mqtt5.optimising_communication Optimising communication] section,
[refmem mqtt_client async_publish] and [refmem mqtt_client async_disconnect] will place their corresponding
packets in the queue. However, __DISCONNECT__ packets are prioritised and sent exclusively, ahead of other queued packets.
Therefore, the connection will terminate immediately.
@ -80,43 +80,42 @@ In this case, the proper way to disconnect would be to call [refmem mqtt_client
[refmem mqtt_client async_publish] has been completed.
```
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[&client](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
```
[section:reusing_the_client Restarting the Client after disconnection]
[section:reusing_the_client Restarting the Client After Disconnection]
Once the __Client__ has been successfully stopped, reactivating it is straightforward and requires invoking [refmem mqtt_client async_run].
This method can be called right after initiating [refmem mqtt_client async_disconnect], without waiting for it to complete.
The __Client__ is configurable again in the interval between stopping and restarting.
See `Customising your MQTT connection` in [link async_mqtt5.configuring_the_client Configuring the Client]
for more information.
See [link mqtt5.getting_started.configuration Configuring Your MQTT Connection] for more information.
```
int main() {
boost::asio::io_context ioc;
boost::asio::io_context ioc;
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
client.async_disconnect(boost::asio::detached);
client.async_disconnect(boost::asio::detached);
// The Client can be reconfigured again.
client.connect_property(async_mqtt5::prop::session_expiry_interval, 120)
.keep_alive(30)
.async_run(boost::asio::detached); // Restart the Client again.
// The Client can be reconfigured again.
client.connect_property(boost::mqtt5::prop::session_expiry_interval, 120)
.keep_alive(30)
.async_run(boost::asio::detached); // Restart the Client again.
// Use the Client...
// Use the Client...
ioc.run();
ioc.run();
}
```

View File

@ -1,10 +1,10 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:asio_compliance Compliance with Boost.Asio]
[section:asio_compliance Compliance With Boost.Asio]
Every asynchronous operation in __Asio__ has associated characteristics that specify their behaviour.
@ -15,11 +15,11 @@ Every asynchronous operation in __Asio__ has associated characteristics that spe
This section expands further into the roles of allocators,
cancellation slots, and executors, highlighting their integration and usage within the __Client__.
* See [link async_mqtt5.asio_compliance.allocators Allocators] for more information about how the
* See [link mqtt5.asio_compliance.allocators Allocators] for more information about how the
__Client__ supports and uses associated allocators.
* See [link async_mqtt5.asio_compliance.per_op_cancellation Per-Operation Cancellation] for more information about how
* See [link mqtt5.asio_compliance.per_op_cancellation Per-Operation Cancellation] for more information about how
asynchronous operations within the __Client__ support cancellation.
* See [link async_mqtt5.asio_compliance.executors Executors] for more information about executors.
* See [link mqtt5.asio_compliance.executors Executors] for more information about executors.
[include 08_allocators.qbk]
[include 09_per_op_cancellation.qbk]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -18,7 +18,7 @@ to store the state associated with the operation.
Specifically, the allocator is used to reserve memory for MQTT Control Packet bytes
(__PUBLISH__, __SUBSCRIBE__,...) created by each `async_xxx` operation request.
Moreover, the __Client__'s internal packet queue
(described in the section ['Efficient bandwidth usage with packet queuing] in [link async_mqtt5.optimising_communication Optimising communication])
(described in the section ['Efficient bandwidth usage with packet queuing] in [link mqtt5.optimising_communication Optimising communication])
associates the allocator of the first packet in the queue to the low-level __Asio__ function
`async_write` on the transport layer.

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -42,25 +42,25 @@ In such cases, the cancellation would specifically target resending the __PUBLIS
preventing it from being retransmitted should the client reconnect during the ongoing operation.
```
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
boost::asio::cancellation_signal signal;
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
boost::asio::cancellation_signal signal;
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
boost::asio::bind_cancellation_slot(
signal.slot(),
[&client](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::puback_props props ) {
std::cout << ec.message() << std::endl;
}
)
);
client.async_publish<boost::mqtt5::qos_e::at_least_once>(
"<topic>", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
boost::asio::bind_cancellation_slot(
signal.slot(),
[&client](boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc, boost::mqtt5::puback_props props ) {
std::cout << ec.message() << std::endl;
}
)
);
signal.emit(boost::asio::cancellation_type_t::terminal);
signal.emit(boost::asio::cancellation_type_t::terminal);
```
[section:parallel_group parallel_group/operator || and asynchronous functions in the mqtt_client]
@ -70,8 +70,8 @@ This feature is especially beneficial for executing operations that require a ti
Below are two examples illustrating how to implement a timeout:
* [link async_mqtt5.timeout_with_parallel_group timeout_with_parallel_group.cpp]
* [link async_mqtt5.timeout_with_awaitable_operators timeout_with_awaitable_operators.cpp]
* [link mqtt5.timeout_with_parallel_group timeout_with_parallel_group.cpp]
* [link mqtt5.timeout_with_awaitable_operators timeout_with_awaitable_operators.cpp]
[endsect] [/parallel_group]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -19,15 +19,7 @@ Upon creating an instance of the __Client__, it is necessary to provide an execu
The specified executor (or __ExecutionContext__'s executor) will become the default executor associated with the __Client__
and will be used for the execution of all the intermediate operations and a final completion handler for all asynchronous operations
that have not bound an executor.
If an executor is bound to an asynchronous operation, that executor will be used instead.
The [refmem mqtt_client async_run] operation starts the __Client__, which initiates a series of internal asynchronous operations, all of which need an executor.
If the [refmem mqtt_client async_run] is called with a completion handler that has an associated executor,
then all the internal asynchronous operations will associate the same executor.
[important
The same executor *must* execute [refmem mqtt_client async_run] and all the subsequent `async_xxx` operations.
]
If an executor is bound to an asynchronous operation, that executor will be used for the final completion handler instead.
The following examples will demonstrate the previously described interactions.
@ -39,27 +31,27 @@ which is used to execute the [refmem mqtt_client async_publish] operation.
```
int main() {
boost::asio::io_context ioc;
boost::asio::io_context ioc;
// Construct the Client with a strand.
auto strand = boost::asio::make_strand(ioc.get_executor());
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(strand);
// Construct the Client with a strand.
auto strand = boost::asio::make_strand(ioc.get_executor());
boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(strand);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// This asynchronous operation will use the default associated executor,
// which is the strand with which the Client is constructed.
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[&client, &strand](async_mqtt5::error_code /* ec */) {
assert(strand.running_in_this_thread());
client.cancel();
}
);
// This asynchronous operation will use the default associated executor,
// which is the strand with which the Client is constructed.
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[&client, &strand](boost::mqtt5::error_code /* ec */) {
assert(strand.running_in_this_thread());
client.cancel();
}
);
ioc.run();
ioc.run();
}
```

View File

@ -1,16 +1,16 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:multithreading Using the mqtt_client in a multithreaded environment]
[section:multithreading Using the mqtt_client in a Multithreaded Environment]
[nochunk]
This chapter provides information about thread safety of the __Client__ and other __Asio__-compliant objects
and provides examples of how to write thread-safe code in multithreaded environments.
[section:thread_safety Thread safety of ASIO-compliant objects]
[section:thread_safety Thread Safety of ASIO-Compliant Objects]
A common misconception exists regarding the "thread safety" of ASIO-compliant asynchronous objects,
specifically around the belief that initialising such an object with a __STRAND__
@ -37,7 +37,7 @@ Instead, it encourages developers to implement their own concurrency management
[endsect] [/thread_safety]
[section:executors_threads_strands Executors, threads, and strands]
[section:executors_threads_strands Executors, Threads, and Strands]
Before delving into thread-safe programming, it is essential to understand the distinction between executors and threads.
Executors are not threads but mechanisms for scheduling how and when work gets done.
@ -56,7 +56,7 @@ Refer to __ASIO_STRANDS__ for more details.
[endsect] [/executors_threads_strands]
[section:thread_safe_code Writing thread-safe code]
[section:thread_safe_code Writing Thread-Safe Code]
As mentioned previously, it is the user's responsibility to ensure that none of the __Client__'s member functions
are called concurrently from separate threads.
@ -68,23 +68,22 @@ or explicit strand.
Specifically, use __POST__ or __DISPATCH__ to delegate a function call to a strand,
or __CO_SPAWN__ to spawn the coroutine into the strand.
For asynchronous functions, this will ensure that the initiation code is executed within the strand in a thread-safe manner.
The associated executor of all `async_xxx`'s completion handlers must be the same strand.
__Client__'s executor must be that same strand.
This will guarantee that the entire sequence of operations
- from the initiation code through any intermediate operations to the execution of the completion handler -
- the initiation code and any intermediate operation -
is carried out within the strand, thereby ensuring thread safety.
[important
To conclude, to achieve thread safety,
all the member functions of the __Client__ *must* be executed in *the same strand*.
This strand *must* be the associated executor of all the completion handlers across
all `async_xxx` invocations.
To conclude, to achieve thread safety,
all the member functions of the __Client__ *must* be executed in *the same strand*.
This strand must be given in the __Client__ constructor.
]
The examples below demonstrate how to publish a "Hello World" Application Message
in a multithreaded setting using callbacks (`post`/`dispatch`) and coroutines (`co_spawn`):
* [link async_mqtt5.hello_world_in_multithreaded_env hello_world_in_multithreaded_env.cpp]
* [link async_mqtt5.hello_world_in_coro_multithreaded_env hello_world_in_coro_multithreaded_env.cpp]
* [link mqtt5.hello_world_in_multithreaded_env hello_world_in_multithreaded_env.cpp]
* [link mqtt5.hello_world_in_coro_multithreaded_env hello_world_in_coro_multithreaded_env.cpp]
[endsect] [/thread_safe_code]

65
doc/qbk/12_examples.qbk Normal file
View File

@ -0,0 +1,65 @@
[/
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:examples Examples]
[nochunk]
The following list contains all the examples that showcase how to use the __Client__:
[variablelist
[
[[link mqtt5.publisher publisher.cpp]]
[Shows how to use the __Client__ as a publisher. The __Client__ publishes sensor readings every `5 seconds`.]
]
[
[[link mqtt5.receiver receiver.cpp]]
[Shows how to use the __Client__ as a receiver. The __Client__ subscribes and indefinitely receives Application Messages from the Broker.]
]
[
[[link mqtt5.hello_world_over_tcp hello_world_over_tcp.cpp]]
[Publishes a "Hello World" message via TCP/IP.]
]
[
[[link mqtt5.hello_world_over_tls hello_world_over_tls.cpp]]
[Publishes a "Hello World" message via TLS/SSL.]
]
[
[[link mqtt5.hello_world_over_websocket_tcp hello_world_over_websocket_tcp.cpp]]
[Publishes a "Hello World" message via Websocket/TLS.]
]
[
[[link mqtt5.hello_world_over_websocket_tls hello_world_over_websocket_tls.cpp]]
[Publishes a "Hello World" message via Websocket/TLS.]
]
[
[[link mqtt5.multiflight_client multiflight_client.cpp]]
[Shows how to use the __Client__ to simultaneously dispatch multiple requests.]
]
[
[[link mqtt5.timeout_with_parallel_group timeout_with_parallel_group.cpp]]
[
Shows how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using a parallel group.
]
]
[
[[link mqtt5.timeout_with_awaitable_operators timeout_with_awaitable_operators.cpp]]
[
Shows how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using awaitable operators.
]
]
[
[[link mqtt5.hello_world_in_multithreaded_env hello_world_in_multithreaded_env.cpp]]
[Shows how to publish a "Hello World" message in a multithreaded environment using callbacks (`post`/`dispatch`).]
]
[
[[link mqtt5.hello_world_in_coro_multithreaded_env hello_world_in_coro_multithreaded_env.cpp]]
[Shows how to publish a "Hello World" message in a multithreaded environment using coroutines (`co_spawn`).]
]
]
[endsect][/examples]

View File

@ -1,65 +0,0 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:examples Examples]
[nochunk]
The following list contains all the examples that showcase how to use the __Client__:
[variablelist
[
[[link async_mqtt5.publisher publisher.cpp]]
[Shows how to use the __Client__ as a publisher. The __Client__ publishes sensor readings every `5 seconds`.]
]
[
[[link async_mqtt5.receiver receiver.cpp]]
[Shows how to use the __Client__ as a receiver. The __Client__ subscribes and indefinitely receives Application Messages from the Broker.]
]
[
[[link async_mqtt5.hello_world_over_tcp hello_world_over_tcp.cpp]]
[Publishes a "Hello World" message via TCP/IP.]
]
[
[[link async_mqtt5.hello_world_over_tls hello_world_over_tls.cpp]]
[Publishes a "Hello World" message via TLS/SSL.]
]
[
[[link async_mqtt5.hello_world_over_websocket_tcp hello_world_over_websocket_tcp.cpp]]
[Publishes a "Hello World" message via Websocket/TLS.]
]
[
[[link async_mqtt5.hello_world_over_websocket_tls hello_world_over_websocket_tls.cpp]]
[Publishes a "Hello World" message via Websocket/TLS.]
]
[
[[link async_mqtt5.multiflight_client multiflight_client.cpp]]
[Shows how to use the __Client__ to simultaneously dispatch multiple requests.]
]
[
[[link async_mqtt5.timeout_with_parallel_group timeout_with_parallel_group.cpp]]
[
Shows how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using a parallel group.
]
]
[
[[link async_mqtt5.timeout_with_awaitable_operators timeout_with_awaitable_operators.cpp]]
[
Shows how to use the __Client__ with its support for per-operation cancellation to perform operations under a time constraint
using awaitable operators.
]
]
[
[[link async_mqtt5.hello_world_in_multithreaded_env hello_world_in_multithreaded_env.cpp]]
[Shows how to publish a "Hello World" message in a multithreaded environment using callbacks (`post`/`dispatch`).]
]
[
[[link async_mqtt5.hello_world_in_coro_multithreaded_env hello_world_in_coro_multithreaded_env.cpp]]
[Shows how to publish a "Hello World" message in a multithreaded environment using coroutines (`co_spawn`).]
]
]
[endsect][/examples]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -8,49 +8,40 @@
[section:hello_world_over_tcp Hello World over TCP/IP]
This example illustrates the process of setting up the Client to connect to the Broker via TCP/IP and publish a "Hello World!" message.
[import ../../../example/hello_world_over_tcp.cpp]
[hello_world_over_tcp]
[endsect] [/hello_world_over_tcp]
[section:hello_world_over_tls Hello World over TLS/SSL]
This example illustrates the process of setting up the Client to connect to the Broker via TLS/SSL and publish a "Hello World!" message.
[import ../../../example/hello_world_over_tls.cpp]
[hello_world_over_tls]
[endsect] [/hello_world_over_tls]
[section:hello_world_over_websocket_tcp Hello World over Websocket/TCP]
This example illustrates the process of setting up the Client to connect to the Broker via Websocket/TCP and publish a "Hello World!" message.
[import ../../../example/hello_world_over_websocket_tcp.cpp]
[hello_world_over_websocket_tcp]
[endsect] [/hello_world_over_websocket_tcp]
[section:hello_world_over_websocket_tls Hello World over Websocket/TLS]
This example illustrates the process of setting up the Client to connect to the Broker via Websocket/TLS and publish a "Hello World!" message.
[import ../../../example/hello_world_over_websocket_tls.cpp]
[hello_world_over_websocket_tls]
[endsect] [/hello_world_over_websocket_tls]
[section:publisher The publisher]
This example shows how to use __Client__ as a publisher that publishes sensor readings every `5` seconds.
The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ as the completion token.
[import ../../../example/publisher.cpp]
[publisher]
[endsect]
[section:receiver The receiver]
This example shows how to use __Client__ as a receiver.
The __Client__ subscribes and indefinitely receives Application Messages from the Broker.
The __Client__ uses TCP to connect to the Broker and modified __USE_AWAITABLE__ as the completion token.
[import ../../../example/receiver.cpp]
[receiver]
[endsect]
[section:multiflight_client The multiflight Client]
This example shows how to use __Client__ to simultaneously dispatch multiple requests.
[import ../../../example/multiflight_client.cpp]
[multiflight_client]
[endsect]
@ -59,7 +50,6 @@ This example demonstrates how to use the __Client__ with its support for per-ope
using parallel group.
Specifically, in this example, the __Client__ will subscribe to a Topic and try to receive a message from the Topic within `5 seconds`.
[import ../../../example/timeout_with_parallel_group.cpp]
[timeout_with_parallel_group]
[endsect]
@ -69,21 +59,18 @@ using awaitable operators.
Specifically, in this example, a call to [refmem mqtt_client async_publish] and [refmem mqtt_client async_disconnect] must complete
within `5 seconds`. Otherwise, they will be cancelled.
[import ../../../example/timeout_with_awaitable_operators.cpp]
[timeout_with_awaitable_operators]
[endsect]
[section:hello_world_in_multithreaded_env Hello World in a multithreaded environment using callbacks]
This example demonstrates how to publish a "Hello World" message in a multithreaded environment using callbacks (`post`/`dispatch`).
[import ../../../example/hello_world_in_multithreaded_env.cpp]
[hello_world_in_multithreaded_env]
[endsect]
[section:hello_world_in_coro_multithreaded_env Hello World in a multithreaded environment using coroutines]
This example demonstrates how to publish a "Hello World" message in a multithreaded environment using coroutines (`co_spawn`).
[import ../../../example/hello_world_in_coro_multithreaded_env.cpp]
[hello_world_in_coro_multithreaded_env]
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -10,82 +10,82 @@ The table below provides a reference of all __ERROR_CODE__ instances that asynch
may complete with, along with the reasons for their occurrence.
[table:error_codes Error codes
[[Error code] [Cause]]
[[`boost::system::errc::errc_t::success`] [The operation completed successfully.]]
[[`boost::asio::error::opreation_aborted`] [
The operation has been cancelled.
The cancellation of the operation can be triggered by invoking either [refmem mqtt_client cancel]
or [refmem mqtt_client async_disconnect]. Furthermore, if a cancellation slot has been associated
with the __CompletionToken__ provided and the corresponding cancellation signal is emitted,
the operation will also finish with this error code (see __ASIO_PER_OP_CANCELLATION__).
]]
[[`boost::asio::no_recovery`] [
An non-recoverable error occurred during the attempt by the [reflink2 mqtt_client `mqtt_client`]
to establish a connection with the Server. The cause of this error may be attributed to the connection
related parameters used during the initialization of the [reflink2 mqtt_client `mqtt_client`].
]]
[[`async_mqtt5::client::error::malformed_packet`][
The Client has attempted to send a packet that does not conform to the specification.
This issue can arise from improperly formed UTF-8 encoded strings.
Additionally, this error can be caused by providing out-of-range values.
]]
[[`async_mqtt5::client::error::packet_too_large`][
The Client has attempted to send a packet larger than the Maximum Packet Size the Server
is willing to process.
]]
[[`async_mqtt5::client::error::session_expired`][
The Client has established a successful connection with a Server, but either the session does not exist or has expired.
In cases where the Client had previously set up subscriptions to Topics, these subscriptions are also expired.
Therefore, the Client should re-subscribe.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_receive] calls.
]]
[[`async_mqtt5::client::error::pid_overrun`] [
This error code signifies that the Client was unable to allocate a Packet Identifier for
the current operation due to the exhaustion of the available identifiers.
This occurs when there are 65535 outgoing Packets awaiting their responses.
]]
[[`async_mqtt5::client::error::invalid_topic`] [
The Client has attempted to perform an action (publish, subscribe or unsubscribe) on an invalid Topic.
See __TOPIC_SEMANTIC_AND_USAGE__ for information on properly formed Topics.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish],
[refmem mqtt_client async_subscribe], and [refmem mqtt_client async_unsubscribe] calls.
In the case of [refmem mqtt_client async_subscribe] and [refmem mqtt_client async_unsubscribe], this error code
occurs if at least one Topic provided is malformed.
]]
[[`async_mqtt5::client::error::qos_not_supported`] [
The Client has attempted to publish an Application Message with __QOS__ higher
than the Maximum __QOS__ specified by the Server.
The Server does not support this __QOS__ (see __MAXIMUM_QOS__).
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish] calls.
]]
[[`async_mqtt5::client::error::retain_not_available`] [
The Client has attempted to publish an Application Message with the __RETAIN__ flag set to 1.
However, the Server does not support retained messages (see __RETAIN_AVAILABLE__).
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish] calls.
]]
[[`async_mqtt5::client::error::topic_alias_maximum`] [
The Client has attempted to publish an Application Message with the Topic Alias
exceeding the Server's supported Topic Alias Maximum. Additionally, this error code
will arise in instances when the Server does NOT support Topic Aliases, and the
Client has attempted to use them. See __TOPIC_ALIAS_MAX__.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish] calls.
]]
[[`async_mqtt5::client::error::wildcard_subscription_not_available`] [
The Client has attempted to subscribe to multiple Topics using Wildcard Character (`+` and/or `#`).
However, the Server does not support Wildcard Subscriptions.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_subscribe] calls.
]]
[[`async_mqtt5::client::error::subscription_identifier_not_available`] [
The Client has attempted to associate a subscription with a Subscription Identifier.
However, the Server either does not support Subscription Identifiers or the Subscription Identifier provided
is out of range (the Subscription Identifier can have a value of 1 to 268,435,455).
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_subscribe] calls.
]]
[[`async_mqtt5::client::error::shared_subscription_not_available`] [
The Client has attempted to establish a Shared Subscription.
However, the Server does not support Shared Subscriptions.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_subscribe] calls.
]]
[[Error code] [Cause]]
[[`boost::system::errc::errc_t::success`] [The operation completed successfully.]]
[[`boost::asio::error::opreation_aborted`] [
The operation has been cancelled.
The cancellation of the operation can be triggered by invoking either [refmem mqtt_client cancel]
or [refmem mqtt_client async_disconnect]. Furthermore, if a cancellation slot has been associated
with the __CompletionToken__ provided and the corresponding cancellation signal is emitted,
the operation will also finish with this error code (see __ASIO_PER_OP_CANCELLATION__).
]]
[[`boost::asio::no_recovery`] [
An non-recoverable error occurred during the attempt by the [reflink2 mqtt_client `mqtt_client`]
to establish a connection with the Server. The cause of this error may be attributed to the connection
related parameters used during the initialization of the [reflink2 mqtt_client `mqtt_client`].
]]
[[`boost::mqtt5::client::error::malformed_packet`][
The Client has attempted to send a packet that does not conform to the specification.
This issue can arise from improperly formed UTF-8 encoded strings.
Additionally, this error can be caused by providing out-of-range values.
]]
[[`boost::mqtt5::client::error::packet_too_large`][
The Client has attempted to send a packet larger than the Maximum Packet Size the Server
is willing to process.
]]
[[`boost::mqtt5::client::error::session_expired`][
The Client has established a successful connection with a Server, but either the session does not exist or has expired.
In cases where the Client had previously set up subscriptions to Topics, these subscriptions are also expired.
Therefore, the Client should re-subscribe.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_receive] calls.
]]
[[`boost::mqtt5::client::error::pid_overrun`] [
This error code signifies that the Client was unable to allocate a Packet Identifier for
the current operation due to the exhaustion of the available identifiers.
This occurs when there are 65535 outgoing Packets awaiting their responses.
]]
[[`boost::mqtt5::client::error::invalid_topic`] [
The Client has attempted to perform an action (publish, subscribe or unsubscribe) on an invalid Topic.
See __TOPIC_SEMANTIC_AND_USAGE__ for information on properly formed Topics.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish],
[refmem mqtt_client async_subscribe], and [refmem mqtt_client async_unsubscribe] calls.
In the case of [refmem mqtt_client async_subscribe] and [refmem mqtt_client async_unsubscribe], this error code
occurs if at least one Topic provided is malformed.
]]
[[`boost::mqtt5::client::error::qos_not_supported`] [
The Client has attempted to publish an Application Message with __QOS__ higher
than the Maximum __QOS__ specified by the Server.
The Server does not support this __QOS__ (see __MAXIMUM_QOS__).
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish] calls.
]]
[[`boost::mqtt5::client::error::retain_not_available`] [
The Client has attempted to publish an Application Message with the __RETAIN__ flag set to 1.
However, the Server does not support retained messages (see __RETAIN_AVAILABLE__).
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish] calls.
]]
[[`boost::mqtt5::client::error::topic_alias_maximum`] [
The Client has attempted to publish an Application Message with the Topic Alias
exceeding the Server's supported Topic Alias Maximum. Additionally, this error code
will arise in instances when the Server does NOT support Topic Aliases, and the
Client has attempted to use them. See __TOPIC_ALIAS_MAX__.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_publish] calls.
]]
[[`boost::mqtt5::client::error::wildcard_subscription_not_available`] [
The Client has attempted to subscribe to multiple Topics using Wildcard Character (`+` and/or `#`).
However, the Server does not support Wildcard Subscriptions.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_subscribe] calls.
]]
[[`boost::mqtt5::client::error::subscription_identifier_not_available`] [
The Client has attempted to associate a subscription with a Subscription Identifier.
However, the Server either does not support Subscription Identifiers or the Subscription Identifier provided
is out of range (the Subscription Identifier can have a value of 1 to 268,435,455).
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_subscribe] calls.
]]
[[`boost::mqtt5::client::error::shared_subscription_not_available`] [
The Client has attempted to establish a Shared Subscription.
However, the Server does not support Shared Subscriptions.
This error code is exclusive to completion handlers associated with [refmem mqtt_client async_subscribe] calls.
]]
]
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]

View File

@ -0,0 +1,87 @@
[/
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
[section:LoggerType LoggerType concept]
`LoggerType` represents an object type that will be used by the __Client__ to log events.
A type satisfies the `LoggerType` concept if it defines [*any number (including zero)] of the following functions:
[table:log_functions
[[Function signature] [Arguments] [Description]]
[
[`void at_resolve(error_code ec, std::string_view host, std::string_view port, const asio::ip::tcp::resolver::results_type& eps);`]
[
[*`ec`] is the `error_code` returned by the resolve operation.
[*`host`] is the hostname used in the resolve.
[*`port`] is the port used in the resolve.
[*`eps`] is a list of endpoints returned by the resolve operation.
]
[Invoked when the resolve operation is complete.]
]
[
[`void at_tcp_connect(error_code ec, asio::ip::tcp::endpoint ep);`]
[
[*`ec`] is the `error_code` returned by the TCP connect operation.
[*`ep`] is a TCP endpoint used to establish the TCP connection.
]
[Invoked when the TCP connect operation is complete.]
]
[
[`void at_tls_handshake(error_code ec, asio::ip::tcp::endpoint ep);`]
[
[*`ec`] is the `error_code` returned by the the TLS handshake operation.
[*`ep`] is a TCP endpoint used to establish the TLS handshake.
]
[Invoked when the TLS handshake operation is complete.]
]
[
[`void at_ws_handshake(error_code ec, asio::ip::tcp::endpoint ep);`]
[
[*`ec`] is the `error_code` returned by the the WebSocket handshake operation.
[*`ep`] is a TCP endpoint used to establish the WebSocket handshake.
]
[Invoked when the WebSocket handshake operation is complete.]
]
[
[`void at_connack(reason_code rc, bool session_present, const connack_props& ca_props);`]
[
[*`rc`] is the `reason_code` received in the __CONNACK__ packet indicating the result of the MQTT handshake.
[*`session_present`] A flag indicating whether the Broker already has a session associated with this connection.
[*`ca_props`] __CONNACK_PROPS__ received in the __CONNACK__ packet.
]
[Invoked when the __CONNACK__ packet is received, marking the completion of the MQTT handshake. ]
]
[
[`void at_disconnect(reason_code rc, const disconnect_props& dc_props);`]
[
[*`rc`] is the `reason_code` received in the __DISCONNECT__ packet specifying the reason behind the disconnection.
[*`dc_props`] __DISCONNECT_PROPS__ received in the __DISCONNECT__ packet.
]
[Invoked when the __DISCONNECT__ packet is received, indicating that the Broker wants to close this connection. ]
]
]
For example, a type `T` that defines `at_connack` and `at_disconnect` functions with their respective arguments is considered a valid `LoggerType`.
This allows you to create your own `LoggerType` classes with functions of interest.
All defined functions are invoked directly within the __Client__ using its default executor.
If the __Client__ is initialized with an explicit or implicit strand, none of the functions will be invoked concurrently.
[warning Defined functions should not block and stop the __Client__ from doing work. ]
A class that satifies this concept is [ghreflink include/boost/mqtt5/logger.hpp logger].
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -10,10 +10,13 @@
`StreamType` should meet the [beastconceptslink streams AsyncStream] concept.
Additionally, it should follow Asio's layered stream model by having a `lowest_layer_type` member type,
It should follow Asio's layered stream model by having a `lowest_layer_type` member type,
and a `lowest_layer` member function, returing a `lowest_layer_type&`.
The `lowest_layer_type` should inherit from __TCP_SOCKET__.
Additionally, it should have an overload of [ghreflink include/boost/mqtt5/detail/shutdown.hpp async_shutdown]
function that is discoverable via argument-dependent lookup (ADL).
The types __TCP_SOCKET__, __SSL_STREAM__ and __WEBSOCKET_STREAM__ meet these requirements.
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -9,23 +9,23 @@
A type `Authenticator` satisfies `is_authenticator` concept if it satisifes the requirements listed below.
[table
[[operation] [type] [arguments]]
[
[```a.async_auth(step, data, h)```]
[`void`]
[
[*`step`] is [reflink2 auth_step_e async_mqtt5::auth_step_e] that specifies current authentication stage.
[[operation] [type] [arguments]]
[
[```a.async_auth(step, data, h)```]
[`void`]
[
[*`step`] is [reflink2 auth_step_e boost::mqtt5::auth_step_e] that specifies current authentication stage.
[*`data`] is `std::string`, server's authentication data.
[*`h`] is [asioreflink any_completion_handler any_completion_handler] with signature `void(__ERROR_CODE__ ec, std::string client_data)`. If `ec` is non-trivial, authentication is aborted.
]
]
[
[```a.method()```]
[`std::string_view`, authentication method]
[]
]
]
]
[
[```a.method()```]
[`std::string_view`, authentication method]
[]
]
]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,45 +12,45 @@ A Property consists of an Identifier and a value.
This section lists all possible __AUTH__ Properties and describes their usage:
[table:auth_props AUTH properties
[[Identifier] [Value type] [Description]]
[[authentication_method] [`std::string`] [A UTF-8 Encoded String containing the name of the authentication method used for extended authentication.]]
[[authentication_data] [`std::string`] [Binary Data containing authentication data. The contents of the data are defined by the authentication method.]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[authentication_method] [`std::string`] [A UTF-8 Encoded String containing the name of the authentication method used for extended authentication.]]
[[authentication_data] [`std::string`] [Binary Data containing authentication data. The contents of the data are defined by the authentication method.]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::auth_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::auth_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::auth_props props;
props[async_mqtt5::prop::authentication_method] = "SCRAM-SHA-1";
props[async_mqtt5::prop::authentication_data] = "data";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::auth_props props;
props[boost::mqtt5::prop::authentication_method] = "SCRAM-SHA-1";
props[boost::mqtt5::prop::authentication_data] = "data";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> auth_data = props[async_mqtt5::prop::authentication_data];
if (auth_data.has_value())
// authentication data property was previously set
else
// authentication data property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> auth_data = props[boost::mqtt5::prop::authentication_data];
if (auth_data.has_value())
// authentication data property was previously set
else
// authentication data property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,61 +12,61 @@ A Property consists of an Identifier and a value.
This section lists all possible __CONNACK__ Properties and describes their usage:
[table:connack_props CONNACK properties
[[Identifier] [Value type] [Description]]
[[session_expiry_interval] [`uint32_t`] [Represents the Session Expiry Internal in seconds.]]
[[receive_maximum] [`uint16_t`] [The maximum number of QoS 1 and QoS 2 publications that the Server is willing to process concurrently.]]
[[maximum_qos] [`uint8_t`] [The highest QoS the Server supports.]]
[[retain_available] [`uint8_t`] [A value of 0 means that retained message are not supported. A value of 1 means they are supported.]]
[[maximum_packet_size] [`uint32_t`] [The maximum __PACKET_SIZE__ in bytes as defined by the specification that the Server is willing to accept.]]
[[assigned_client_identifier] [`std::string`] [The Client Identifier which was assigned by the Server because a zero length Client Identifier was found in the __CONNECT__ packet]]
[[topic_alias_maximum] [`uint16_t`] [The highest value that the Server will accept as a Topic Alias sent by the Client.]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[wildcard_subscription_available] [`uint8_t`] [A value of 0 means that Wildcard Subscriptions are not supported.
A value of 1 means they are supported. If not present, they are supported.]]
[[subscription_identifier_available] [`uint8_t`] [A value of 0 means that Subscriptions Identifiers are not supported.
A value of 1 means they are supported. If not present, they are supported.]]
[[shared_subscription_available] [`uint8_t`] [A value of 0 means that Shared Subscriptions are not supported.
A value of 1 means they are supported. If not present, they are supported.]]
[[server_keep_alive] [`uint16_t`] [The Keep Alive time assigned by the Server.]]
[[response_information] [`std::string`] [A UTF-8 Encoded String which is used as the basis for creating a Response Topic.]]
[[server_reference] [`std::string`] [A UTF-8 Encoded String which can be used by the Client to identfy another Server to use.]]
[[authentication_method] [`std::string`] [A UTF-8 Encoded String containing the name of the authentication method used for extended authentication.]]
[[authentication_data] [`std::string`] [Binary Data containing authentication data. The contents of the data are defined by the authentication method.]]
[[Identifier] [Value type] [Description]]
[[session_expiry_interval] [`uint32_t`] [Represents the Session Expiry Internal in seconds.]]
[[receive_maximum] [`uint16_t`] [The maximum number of QoS 1 and QoS 2 publications that the Server is willing to process concurrently.]]
[[maximum_qos] [`uint8_t`] [The highest QoS the Server supports.]]
[[retain_available] [`uint8_t`] [A value of 0 means that retained message are not supported. A value of 1 means they are supported.]]
[[maximum_packet_size] [`uint32_t`] [The maximum __PACKET_SIZE__ in bytes as defined by the specification that the Server is willing to accept.]]
[[assigned_client_identifier] [`std::string`] [The Client Identifier which was assigned by the Server because a zero length Client Identifier was found in the __CONNECT__ packet]]
[[topic_alias_maximum] [`uint16_t`] [The highest value that the Server will accept as a Topic Alias sent by the Client.]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[wildcard_subscription_available] [`uint8_t`] [A value of 0 means that Wildcard Subscriptions are not supported.
A value of 1 means they are supported. If not present, they are supported.]]
[[subscription_identifier_available] [`uint8_t`] [A value of 0 means that Subscriptions Identifiers are not supported.
A value of 1 means they are supported. If not present, they are supported.]]
[[shared_subscription_available] [`uint8_t`] [A value of 0 means that Shared Subscriptions are not supported.
A value of 1 means they are supported. If not present, they are supported.]]
[[server_keep_alive] [`uint16_t`] [The Keep Alive time assigned by the Server.]]
[[response_information] [`std::string`] [A UTF-8 Encoded String which is used as the basis for creating a Response Topic.]]
[[server_reference] [`std::string`] [A UTF-8 Encoded String which can be used by the Client to identfy another Server to use.]]
[[authentication_method] [`std::string`] [A UTF-8 Encoded String containing the name of the authentication method used for extended authentication.]]
[[authentication_data] [`std::string`] [Binary Data containing authentication data. The contents of the data are defined by the authentication method.]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::connack_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::connack_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::connack_props props;
props[async_mqtt5::prop::maximum_packet_size] = 65535;
props[async_mqtt5::prop::assigned_client_identifier] = "ClientID";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::connack_props props;
props[boost::mqtt5::prop::maximum_packet_size] = 65535;
props[boost::mqtt5::prop::assigned_client_identifier] = "ClientID";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> auth_method = props[async_mqtt5::prop::authentication_method];
if (auth_method.has_value())
// authentication method property was previously set
else
// authentication method property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> auth_method = props[boost::mqtt5::prop::authentication_method];
if (auth_method.has_value())
// authentication method property was previously set
else
// authentication method property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,52 +12,52 @@ A Property consists of an Identifier and a value.
This section lists all possible __CONNECT__ Properties and describes their usage:
[table:connect_props CONNECT properties
[[Identifier] [Value type] [Description]]
[[session_expiry_interval] [`uint32_t`] [Represents the Session Expiry Internal in seconds.]]
[[receive_maximum] [`uint16_t`] [The maximum number of QoS 1 and QoS 2 publications that the Client is willing to process concurrently.]]
[[maximum_packet_size] [`uint32_t`] [The maximum __PACKET_SIZE__ in bytes as defined by the specification that the Client is willing to process.]]
[[topic_alias_maximum] [`uint16_t`] [The highest value that the Client will accept as a Topic Alias sent by the Server.]]
[[request_response_information] [`uint8_t`] [The value of 0 signals that the Server MUST NOT return Response Information in __CONNACK__. If the value if 1, it MAY return it.]]
[[request_problem_information] [`uint8_t`] [The value of 0 signals that the Server MAY return a Reason String or User Properties on a __CONNACK__ or __DISCONNECT__ packet,
but MUST NOT send them on any packet other than __PUBLISH__, __CONNACK__, or __DISCONNECT__.
If the value is 1, the Server MAY return a Reason String or User Properties where it is allowed.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[authentication_method] [`std::string`] [A UTF-8 Encoded String containing the name of the authentication method used for extended authentication.]]
[[authentication_data] [`std::string`] [Binary Data containing authentication data. The contents of the data are defined by the authentication method.]]
[[Identifier] [Value type] [Description]]
[[session_expiry_interval] [`uint32_t`] [Represents the Session Expiry Internal in seconds.]]
[[receive_maximum] [`uint16_t`] [The maximum number of QoS 1 and QoS 2 publications that the Client is willing to process concurrently.]]
[[maximum_packet_size] [`uint32_t`] [The maximum __PACKET_SIZE__ in bytes as defined by the specification that the Client is willing to process.]]
[[topic_alias_maximum] [`uint16_t`] [The highest value that the Client will accept as a Topic Alias sent by the Server.]]
[[request_response_information] [`uint8_t`] [The value of 0 signals that the Server MUST NOT return Response Information in __CONNACK__. If the value if 1, it MAY return it.]]
[[request_problem_information] [`uint8_t`] [The value of 0 signals that the Server MAY return a Reason String or User Properties on a __CONNACK__ or __DISCONNECT__ packet,
but MUST NOT send them on any packet other than __PUBLISH__, __CONNACK__, or __DISCONNECT__.
If the value is 1, the Server MAY return a Reason String or User Properties where it is allowed.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[authentication_method] [`std::string`] [A UTF-8 Encoded String containing the name of the authentication method used for extended authentication.]]
[[authentication_data] [`std::string`] [Binary Data containing authentication data. The contents of the data are defined by the authentication method.]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::connect_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::connect_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::connect_props props;
props[async_mqtt5::prop::session_expiry_interval] = 1200;
props[async_mqtt5::prop::receive_maximum] = uint16_t(100);
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::connect_props props;
props[boost::mqtt5::prop::session_expiry_interval] = 1200;
props[boost::mqtt5::prop::receive_maximum] = uint16_t(100);
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> auth_method = props[async_mqtt5::prop::authentication_method];
if (auth_method.has_value())
// authentication method property was previously set
else
// authentication method property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> auth_method = props[boost::mqtt5::prop::authentication_method];
if (auth_method.has_value())
// authentication method property was previously set
else
// authentication method property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,44 +12,44 @@ A Property consists of an Identifier and a value.
This section lists all possible __DISCONNECT__ Properties and describes their usage:
[table:disconnect_props DISCONNECT properties
[[Identifier] [Value type] [Description]]
[[session_expiry_interval] [`uint32_t`] [Represents the Session Expiry Internal in seconds. Can only be sent by the Client.]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[server_reference] [`std::string`] [A UTF-8 Encoded String which can be used by the Client to identfy another Server to use.]]
[[Identifier] [Value type] [Description]]
[[session_expiry_interval] [`uint32_t`] [Represents the Session Expiry Internal in seconds. Can only be sent by the Client.]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[server_reference] [`std::string`] [A UTF-8 Encoded String which can be used by the Client to identfy another Server to use.]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::disconnect_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::disconnect_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::disconnect_props props;
props[async_mqtt5::prop::reason_string] = "Lost connection!";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::disconnect_props props;
props[boost::mqtt5::prop::reason_string] = "Lost connection!";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,42 +12,42 @@ A Property consists of an Identifier and a value.
This section lists all possible __PUBACK__ Properties and describes their usage:
[table:puback_props PUBACK properties
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::puback_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::puback_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::puback_props props;
props[async_mqtt5::prop::reason_string] = "Some reason...";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::puback_props props;
props[boost::mqtt5::prop::reason_string] = "Some reason...";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,42 +12,42 @@ A Property consists of an Identifier and a value.
This section lists all possible __PUBCOMP__ Properties and describes their usage:
[table:pubcomp_props PUBCOMP properties
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::pubcomp_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::pubcomp_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::pubcomp_props props;
props[async_mqtt5::prop::reason_string] = "Some reason...";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::pubcomp_props props;
props[boost::mqtt5::prop::reason_string] = "Some reason...";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,52 +12,52 @@ A Property consists of an Identifier and a value.
This section lists all possible __PUBLISH__ Properties and describes their usage:
[table:publish_props PUBLISH properties
[[Identifier] [Value type] [Description]]
[[payload_format_indicator] [`uint8_t`] [Value of 0 indicates that the Payload is in unspecified bytes. Value of 1 indicates that the Payload is UTF-8 Encoded Character Data.]]
[[message_expiry_interval] [`uint32_t`] [The lifetime of the Application Message in seconds.]]
[[topic_alias] [`uint16_t`] [Two Byte integer representing the Topic Alias, an integer value that is used to identify the Topic instead of using the Topic Name.]]
[[response_topic] [`std::string`] [A UTF-8 Encoded String which is used as the Topic Name for a response message.]]
[[correlation_data] [`std::string`] [Binary Data used by the sender of the Request Message to identify which request the Response Message is for when it is received.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[subscription_identifier] [`int32_t`] [Identifier of the matching subscription. If there are multiple matching subscriptions, multiple identifiers may be included.]]
[[content_type] [`std::string`] [A UTF-8 Encoded String describing the content of the Application Message.]]
[[Identifier] [Value type] [Description]]
[[payload_format_indicator] [`uint8_t`] [Value of 0 indicates that the Payload is in unspecified bytes. Value of 1 indicates that the Payload is UTF-8 Encoded Character Data.]]
[[message_expiry_interval] [`uint32_t`] [The lifetime of the Application Message in seconds.]]
[[topic_alias] [`uint16_t`] [Two Byte integer representing the Topic Alias, an integer value that is used to identify the Topic instead of using the Topic Name.]]
[[response_topic] [`std::string`] [A UTF-8 Encoded String which is used as the Topic Name for a response message.]]
[[correlation_data] [`std::string`] [Binary Data used by the sender of the Request Message to identify which request the Response Message is for when it is received.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[subscription_identifier] [`int32_t`] [Identifier of the matching subscription. If there are multiple matching subscriptions, multiple identifiers may be included.]]
[[content_type] [`std::string`] [A UTF-8 Encoded String describing the content of the Application Message.]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::publish_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::publish_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property` and `async_mqtt5::prop::subscription_identifier`, where it will return an instance of
`std::vector<std::pair<std::string, std::string>>` and `async_mqtt5::prop::subscription_identifiers` respectively.
`async_mqtt5::prop::subscription_identifiers` has the interface of `boost::container::small_vector`.]
except for `boost::mqtt5::prop::user_property` and `boost::mqtt5::prop::subscription_identifier`, where it will return an instance of
`std::vector<std::pair<std::string, std::string>>` and `boost::mqtt5::prop::subscription_identifiers` respectively.
`boost::mqtt5::prop::subscription_identifiers` has the interface of `boost::container::small_vector`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::publish_props props;
props[async_mqtt5::prop::payload_format_indicator] = uint8_t(1);
props[async_mqtt5::prop::topic_alias] = uint16_t(12);
props[async_mqtt5::prop::response_topic] = "response_topic";
props[async_mqtt5::prop::subscription_identifier].push_back(40);
boost::mqtt5::publish_props props;
props[boost::mqtt5::prop::payload_format_indicator] = uint8_t(1);
props[boost::mqtt5::prop::topic_alias] = uint16_t(12);
props[boost::mqtt5::prop::response_topic] = "response_topic";
props[boost::mqtt5::prop::subscription_identifier].push_back(40);
The following example shows how to retrieve a Property value:
[!c++]
std::optional<uint16_t> topic_alias = props[async_mqtt5::prop::topic_alias];
if (topic_alias.has_value())
// topic alias property was previously set
else
// topic alias property was not set
async_mqtt5::prop::subscription_identifiers& sub_ids = props[async_mqtt5::prop::subscription_identifier];
if (!sub_ids.empty())
// subscription identifier property was previously set
else
// subscription identifier property was not set
std::optional<uint16_t> topic_alias = props[boost::mqtt5::prop::topic_alias];
if (topic_alias.has_value())
// topic alias property was previously set
else
// topic alias property was not set
boost::mqtt5::prop::subscription_identifiers& sub_ids = props[boost::mqtt5::prop::subscription_identifier];
if (!sub_ids.empty())
// subscription identifier property was previously set
else
// subscription identifier property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,42 +12,42 @@ A Property consists of an Identifier and a value.
This section lists all possible __PUBREC__ Properties and describes their usage:
[table:pubrec_props PUBREC properties
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::pubrec_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::pubrec_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::pubrec_props props;
props[async_mqtt5::prop::reason_string] = "Some reason...";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::pubrec_props props;
props[boost::mqtt5::prop::reason_string] = "Some reason...";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,41 +12,41 @@ A Property consists of an Identifier and a value.
This section lists all possible __PUBREL__ Properties and describes their usage:
[table:pubrel_props PUBREL properties
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::pubrel_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::pubrel_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::pubrel_props props;
props[async_mqtt5::prop::reason_string] = "Some reason...";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::pubrel_props props;
props[boost::mqtt5::prop::reason_string] = "Some reason...";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,42 +12,42 @@ A Property consists of an Identifier and a value.
This section lists all possible __SUBACK__ Properties and describes their usage:
[table:suback_props SUBACK properties
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::suback_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::suback_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::suback_props props;
props[async_mqtt5::prop::reason_string] = "Some reason...";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::suback_props props;
props[boost::mqtt5::prop::reason_string] = "Some reason...";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,45 +12,45 @@ A Property consists of an Identifier and a value.
This section lists all possible __SUBSCRIBE__ Properties and describes their usage:
[table:subscribe_props SUBSCRIBE properties
[[Identifier] [Value type] [Description]]
[[subscription_identifier] [`int32_t`] [Identifier of the Subscription in range of 1 to 268,435,455.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property can be used to send subscription related properties from the Client to the Server.
The meaning of these properties is not defined by the specification ]]
[[Identifier] [Value type] [Description]]
[[subscription_identifier] [`int32_t`] [Identifier of the Subscription in range of 1 to 268,435,455.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property can be used to send subscription related properties from the Client to the Server.
The meaning of these properties is not defined by the specification ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::subscribe_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::subscribe_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property` and `async_mqtt5::prop::subscription_identifier`, where it will return an instance of
`std::vector<std::pair<std::string, std::string>>` and `async_mqtt5::prop::subscription_identifiers` respectively.
`async_mqtt5::prop::subscription_identifiers` has the interface of `std::optional<int32_t>`.]
except for `boost::mqtt5::prop::user_property` and `boost::mqtt5::prop::subscription_identifier`, where it will return an instance of
`std::vector<std::pair<std::string, std::string>>` and `boost::mqtt5::prop::subscription_identifiers` respectively.
`boost::mqtt5::prop::subscription_identifiers` has the interface of `std::optional<int32_t>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::subscribe_props props;
props[async_mqtt5::prop::subscription_identifier] = 1234;
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::subscribe_props props;
props[boost::mqtt5::prop::subscription_identifier] = 1234;
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
async_mqtt5::prop::subscription_identifiers sub_id = props[async_mqtt5::prop::subscription_identifier];
if (sub_id.has_value())
// subscription identifier property was previously set
else
// subscription identifier property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
boost::mqtt5::prop::subscription_identifiers sub_id = props[boost::mqtt5::prop::subscription_identifier];
if (sub_id.has_value())
// subscription identifier property was previously set
else
// subscription identifier property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,42 +12,42 @@ A Property consists of an Identifier and a value.
This section lists all possible __UNSUBACK__ Properties and describes their usage:
[table:unsuback_props UNSUBACK properties
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
[[Identifier] [Value type] [Description]]
[[reason_string] [`std::string`] [A UTF-8 Encoded String representing the reason associated with this response.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property may be used to provide additional diagnostic or other information. ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::unsuback_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::unsuback_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::unsuback_props props;
props[async_mqtt5::prop::reason_string] = "Some reason...";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::unsuback_props props;
props[boost::mqtt5::prop::reason_string] = "Some reason...";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> reason_string = props[async_mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::optional<std::string> reason_string = props[boost::mqtt5::prop::reason_string];
if (reason_string.has_value())
// reason string property was previously set
else
// reason string property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -12,36 +12,36 @@ A Property consists of an Identifier and a value.
This section lists all possible __UNSUBSCRIBE__ Properties and describes their usage:
[table:unsubscribe_props UNSUBSCRIBE properties
[[Identifier] [Value type] [Description]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property can be used to send subscription related properties from the Client to the Server.
The meaning of these properties is not defined by the specification ]]
[[Identifier] [Value type] [Description]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
This property can be used to send subscription related properties from the Client to the Server.
The meaning of these properties is not defined by the specification ]]
]
[h4 Usage]
After obtaining an instance of `async_mqtt5::unsubscribe_props`, the subscript operator can be used to access a Property.
After obtaining an instance of `boost::mqtt5::unsubscribe_props`, the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::unsubscribe_props props;
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::unsubscribe_props props;
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]
@ -13,48 +13,48 @@ A Property consists of an Identifier and a value.
This section lists all possible [reflink2 will Will] Properties and describes their usage:
[table:will_props Will properties
[[Identifier] [Value type] [Description]]
[[will_delay_interval] [`uint32_t`] [The delay in seconds that need to pass before Server publishes the Client's Will Message.]]
[[payload_format_indicator] [`uint8_t`] [Value of 0 indicates that the Will Message is in unspecified bytes. Value of 1 indicates that the Will Message is UTF-8 Encoded Character Data.]]
[[message_expiry_interval] [`uint32_t`] [The lifetime of the Will Message in seconds. It is send as Publication Expiry Interval when it is published.]]
[[content_type] [`std::string`] [A UTF-8 Encoded String describing the content of the Will Message.]]
[[response_topic] [`std::string`] [A UTF-8 Encoded String which is used as the Topic Name for a response message.]]
[[correlation_data] [`std::string`] [Binary Data used by the sender of the Request Message to identify which request the Response Message is for when it is received.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
[[Identifier] [Value type] [Description]]
[[will_delay_interval] [`uint32_t`] [The delay in seconds that need to pass before Server publishes the Client's Will Message.]]
[[payload_format_indicator] [`uint8_t`] [Value of 0 indicates that the Will Message is in unspecified bytes. Value of 1 indicates that the Will Message is UTF-8 Encoded Character Data.]]
[[message_expiry_interval] [`uint32_t`] [The lifetime of the Will Message in seconds. It is send as Publication Expiry Interval when it is published.]]
[[content_type] [`std::string`] [A UTF-8 Encoded String describing the content of the Will Message.]]
[[response_topic] [`std::string`] [A UTF-8 Encoded String which is used as the Topic Name for a response message.]]
[[correlation_data] [`std::string`] [Binary Data used by the sender of the Request Message to identify which request the Response Message is for when it is received.]]
[[user_property] [`std::pair<std::string, std::string>`] [Name, value pair (__UTF8_STRING_PAIR__) defining User Property. There can be multiple pairs in one packet.
The meaning of these properties is not defined by the specification.]]
]
[h4 Usage]
After creating an instance of [reflink2 will `async_mqtt5::will`], the subscript operator can be used to access a Property.
After creating an instance of [reflink2 will `boost::mqtt5::will`], the subscript operator can be used to access a Property.
The Identifiers listed in the table above are available within the `async_mqtt5::prop` namespace for Property access.
The Identifiers listed in the table above are available within the `boost::mqtt5::prop` namespace for Property access.
[note When accessing a property value, the subscript operator will return a `std::optional` of the value type for all properties,
except for `async_mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
except for `boost::mqtt5::prop::user_property`, where it will return an instance of `std::vector<std::pair<std::string, std::string>>`.]
[h4 Example]
The following example shows how to set a Property value:
[!c++]
async_mqtt5::will will;
will[async_mqtt5::prop::message_expiry_interval] = 90;
will[async_mqtt5::prop::content_type] = "Notification";
props[async_mqtt5::prop::user_property].emplace_back("name", "value");
boost::mqtt5::will will;
will[boost::mqtt5::prop::message_expiry_interval] = 90;
will[boost::mqtt5::prop::content_type] = "Notification";
props[boost::mqtt5::prop::user_property].emplace_back("name", "value");
The following example shows how to retrieve a Property value:
[!c++]
std::optional<std::string> c_type = will[async_mqtt5::prop::content_type];
if (c_type.has_value())
// content type property was previously set
else
// content type property was not set
std::optional<std::string> c_type = will[boost::mqtt5::prop::content_type];
if (c_type.has_value())
// content type property was previously set
else
// content type property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[async_mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
std::vector<std::pair<std::string, std::string>>& user_props = props[boost::mqtt5::prop::user_property];
if (!user_props.empty())
// user property was previously set
else
// user property was not set
[endsect]

View File

@ -2,7 +2,7 @@
<!DOCTYPE library PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN" "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
<!--
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
-->
@ -14,64 +14,65 @@
<entry valign="top">
<bridgehead renderas="sect3">Classes</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.authority_path">authority_path</link></member>
<member><link linkend="async_mqtt5.ref.mqtt_client">mqtt_client</link></member>
<member><link linkend="async_mqtt5.ref.reason_code">reason_code</link></member>
<member><link linkend="async_mqtt5.ref.subscribe_options">subscribe_options</link></member>
<member><link linkend="async_mqtt5.ref.subscribe_topic">subscribe_topic</link></member>
<member><link linkend="async_mqtt5.ref.will">will</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__authority_path">authority_path</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__mqtt_client">mqtt_client</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_code">reason_code</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__subscribe_options">subscribe_options</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__subscribe_topic">subscribe_topic</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__will">will</link></member>
</simplelist>
<bridgehead renderas="sect3">Concepts</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.ExecutionContext">ExecutionContext</link></member>
<member><link linkend="async_mqtt5.ref.StreamType">StreamType</link></member>
<member><link linkend="async_mqtt5.ref.TlsContext">TlsContext</link></member>
<member><link linkend="async_mqtt5.ref.is_authenticator">is_authenticator</link></member>
<member><link linkend="mqtt5.ref.ExecutionContext">ExecutionContext</link></member>
<member><link linkend="mqtt5.ref.StreamType">StreamType</link></member>
<member><link linkend="mqtt5.ref.TlsContext">TlsContext</link></member>
<member><link linkend="mqtt5.ref.is_authenticator">is_authenticator</link></member>
</simplelist>
</entry>
<entry valign="top">
<bridgehead renderas="sect3">Type aliases</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.error_code">error_code</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__error_code">error_code</link></member>
</simplelist>
<bridgehead renderas="sect3">Enumerations</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.auth_step_e">auth_step_e</link></member>
<member><link linkend="async_mqtt5.ref.client.error">client::error</link></member>
<member><link linkend="async_mqtt5.ref.disconnect_rc_e">disconnect_rc_e</link></member>
<member><link linkend="async_mqtt5.ref.qos_e">qos_e</link></member>
<member><link linkend="async_mqtt5.ref.retain_e">retain_e</link></member>
</simplelist>
<bridgehead renderas="sect3">Functions</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.client.get_error_code_category">get_error_code_category</link></member>
<member><link linkend="async_mqtt5.ref.client.make_error_code">make_error_code</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__auth_step_e">auth_step_e</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__client__error">client::error</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__disconnect_rc_e">disconnect_rc_e</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__qos_e">qos_e</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__retain_e">retain_e</link></member>
</simplelist>
</entry>
<entry valign="top">
<bridgehead renderas="sect3">Properties</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.will_props">will_props</link></member>
<member><link linkend="async_mqtt5.ref.connect_props">connect_props</link></member>
<member><link linkend="async_mqtt5.ref.connack_props">connack_props</link></member>
<member><link linkend="async_mqtt5.ref.publish_props">publish_props</link></member>
<member><link linkend="async_mqtt5.ref.puback_props">puback_props</link></member>
<member><link linkend="async_mqtt5.ref.pubrec_props">pubrec_props</link></member>
<member><link linkend="async_mqtt5.ref.pubrel_props">pubrel_props</link></member>
<member><link linkend="async_mqtt5.ref.pubcomp_props">pubcomp_props</link></member>
<member><link linkend="async_mqtt5.ref.subscribe_props">subscribe_props</link></member>
<member><link linkend="async_mqtt5.ref.suback_props">suback_props</link></member>
<member><link linkend="async_mqtt5.ref.unsubscribe_props">unsubscribe_props</link></member>
<member><link linkend="async_mqtt5.ref.unsuback_props">unsuback_props</link></member>
<member><link linkend="async_mqtt5.ref.disconnect_props">disconnect_props</link></member>
<member><link linkend="async_mqtt5.ref.auth_props">auth_props</link></member>
<member><link linkend="mqtt5.ref.will_props">will_props</link></member>
<member><link linkend="mqtt5.ref.connect_props">connect_props</link></member>
<member><link linkend="mqtt5.ref.connack_props">connack_props</link></member>
<member><link linkend="mqtt5.ref.publish_props">publish_props</link></member>
<member><link linkend="mqtt5.ref.puback_props">puback_props</link></member>
<member><link linkend="mqtt5.ref.pubrec_props">pubrec_props</link></member>
<member><link linkend="mqtt5.ref.pubrel_props">pubrel_props</link></member>
<member><link linkend="mqtt5.ref.pubcomp_props">pubcomp_props</link></member>
<member><link linkend="mqtt5.ref.subscribe_props">subscribe_props</link></member>
<member><link linkend="mqtt5.ref.suback_props">suback_props</link></member>
<member><link linkend="mqtt5.ref.unsubscribe_props">unsubscribe_props</link></member>
<member><link linkend="mqtt5.ref.unsuback_props">unsuback_props</link></member>
<member><link linkend="mqtt5.ref.disconnect_props">disconnect_props</link></member>
<member><link linkend="mqtt5.ref.auth_props">auth_props</link></member>
</simplelist>
</entry>
<entry valign="top">
<bridgehead renderas="sect3">Reference tables</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.Reason_codes">Reason codes</link></member>
<member><link linkend="async_mqtt5.ref.Error_handling">Error handling</link></member>
<member><link linkend="mqtt5.ref.Reason_codes">Reason codes</link></member>
<member><link linkend="mqtt5.ref.Error_handling">Error handling</link></member>
</simplelist>
<bridgehead renderas="sect3">Logging</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="mqtt5.ref.LoggerType">LoggerType</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__logger">logger</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__log_level">log_level</link></member>
</simplelist>
</entry>
</row></tbody>

View File

@ -1,5 +1,5 @@
[/
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
]

View File

@ -2,7 +2,7 @@
<!DOCTYPE library PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN" "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
<!--
Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
-->
@ -14,59 +14,59 @@
<entry valign="top">
<bridgehead renderas="sect3">Success</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.reason_codes.continue_authentication">continue_authentication</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.disconnect_with_will_message">disconnect_with_will_message</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.granted_qos_0">granted_qos_0</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.granted_qos_1">granted_qos_1</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.granted_qos_2">granted_qos_2</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.no_matching_subscribers">no_matching_subscribers</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.no_subscription_existed">no_subscription_existed</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.normal_disconnection">normal_disconnection</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.reauthenticate">reauthenticate</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.success">success</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__continue_authentication">continue_authentication</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__disconnect_with_will_message">disconnect_with_will_message</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__granted_qos_0">granted_qos_0</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__granted_qos_1">granted_qos_1</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__granted_qos_2">granted_qos_2</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__no_matching_subscribers">no_matching_subscribers</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__no_subscription_existed">no_subscription_existed</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__normal_disconnection">normal_disconnection</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__reauthenticate">reauthenticate</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__success">success</link></member>
</simplelist>
</entry>
<entry valign="top">
<bridgehead renderas="sect3">Error</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.reason_codes.administrative_action">administrative_action</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.bad_authentication_method">bad_authentication_method</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.bad_username_or_password">bad_username_or_password</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.banned">banned</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.client_identifier_not_valid">client_identifier_not_valid</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.connection_rate_exceeded">connection_rate_exceeded</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.implementation_specific_error">implementation_specific_error</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.keep_alive_timeout">keep_alive_timeout</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.maximum_connect_time">maximum_connect_time</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.message_rate_too_high">message_rate_too_high</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.not_authorized">not_authorized</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.packet_identifier_in_use">packet_identifier_in_use</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.packet_identifier_not_found">packet_identifier_not_found</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.packet_too_large">packet_too_large</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.payload_format_invalid">payload_format_invalid</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.qos_not_supported">qos_not_supported</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.quota_exceeded">quota_exceeded</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.receive_maximum_exceeded">receive_maximum_exceeded</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.retain_not_supported">retain_not_supported</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.server_busy">server_busy</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.server_moved">server_moved</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.server_shutting_down">server_shutting_down</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.server_unavailable">server_unavailable</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.session_taken_over">session_taken_over</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.shared_subscriptions_not_supported">shared_subscriptions_not_supported</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.subscription_ids_not_supported">subscription_ids_not_supported</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.topic_alias_invalid">topic_alias_invalid</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.topic_filter_invalid">topic_filter_invalid</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.topic_name_invalid">topic_name_invalid</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.unsupported_protocol_version">unsupported_protocol_version</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.use_another_server">use_another_server</link></member>
<member><link linkend="async_mqtt5.ref.reason_codes.wildcard_subscriptions_not_supported">wildcard_subscriptions_not_supported</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__administrative_action">administrative_action</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__bad_authentication_method">bad_authentication_method</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__bad_username_or_password">bad_username_or_password</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__banned">banned</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__client_identifier_not_valid">client_identifier_not_valid</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__connection_rate_exceeded">connection_rate_exceeded</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__implementation_specific_error">implementation_specific_error</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__keep_alive_timeout">keep_alive_timeout</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__maximum_connect_time">maximum_connect_time</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__message_rate_too_high">message_rate_too_high</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__not_authorized">not_authorized</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__packet_identifier_in_use">packet_identifier_in_use</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__packet_identifier_not_found">packet_identifier_not_found</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__packet_too_large">packet_too_large</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__payload_format_invalid">payload_format_invalid</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__qos_not_supported">qos_not_supported</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__quota_exceeded">quota_exceeded</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__receive_maximum_exceeded">receive_maximum_exceeded</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__retain_not_supported">retain_not_supported</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__server_busy">server_busy</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__server_moved">server_moved</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__server_shutting_down">server_shutting_down</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__server_unavailable">server_unavailable</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__session_taken_over">session_taken_over</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__shared_subscriptions_not_supported">shared_subscriptions_not_supported</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__subscription_ids_not_supported">subscription_ids_not_supported</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__topic_alias_invalid">topic_alias_invalid</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__topic_filter_invalid">topic_filter_invalid</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__topic_name_invalid">topic_name_invalid</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__unsupported_protocol_version">unsupported_protocol_version</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__use_another_server">use_another_server</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__wildcard_subscriptions_not_supported">wildcard_subscriptions_not_supported</link></member>
</simplelist>
</entry>
<entry valign="top">
<bridgehead renderas="sect3">Special</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="async_mqtt5.ref.reason_codes.empty">empty</link></member>
<member><link linkend="mqtt5.ref.boost__mqtt5__reason_codes__empty">empty</link></member>
</simplelist>
</entry>
</row></tbody>

View File

@ -1,213 +0,0 @@
# Doxyfile 1.9.8
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
PROJECT_NAME = "async_mqtt5 reference"
PROJECT_NUMBER =
OUTPUT_DIRECTORY = ./bin
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF =
ALWAYS_DETAILED_SEC = YES
INLINE_INHERITED_MEMB = YES
FULL_PATH_NAMES = YES
STRIP_FROM_PATH = ./../../../
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = YES
INHERIT_DOCS = NO
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 2
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = NO
BUILTIN_STL_SUPPORT = NO
DISTRIBUTE_GROUP_DOC = NO
SUBGROUPING = YES
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = NO
EXTRACT_LOCAL_METHODS = NO
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = NO
INLINE_INFO = NO
SORT_MEMBER_DOCS = NO
SORT_BRIEF_DOCS = NO
SORT_BY_SCOPE_NAME = NO
GENERATE_TODOLIST = NO
GENERATE_TESTLIST = NO
GENERATE_BUGLIST = NO
GENERATE_DEPRECATEDLIST= NO
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = NO
FILE_VERSION_FILTER =
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = ../include/async_mqtt5/error.hpp \
../include/async_mqtt5/reason_codes.hpp \
../include/async_mqtt5/types.hpp \
../include/async_mqtt5/mqtt_client.hpp
FILE_PATTERNS =
RECURSIVE = NO
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = YES
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
USE_HTAGS = NO
VERBATIM_HEADERS = NO
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = NO
HTML_OUTPUT = .
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
BINARY_TOC = NO
TOC_EXPAND = NO
DISABLE_INDEX = YES
ENUM_VALUES_PER_LINE = 1
GENERATE_TREEVIEW = NO
TREEVIEW_WIDTH = 250
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = NO
USE_PDFLATEX = NO
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = YES
XML_OUTPUT = xml
XML_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::additions related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
CLASS_GRAPH = YES
COLLABORATION_GRAPH = NO
GROUP_GRAPHS = NO
UML_LOOK = NO
TEMPLATE_RELATIONS = YES
INCLUDE_GRAPH = NO
INCLUDED_BY_GRAPH = NO
CALL_GRAPH = NO
GRAPHICAL_HIERARCHY = NO
DIRECTORY_GRAPH = NO
DOT_IMAGE_FORMAT = png
DOT_PATH =
DOTFILE_DIRS =
MAX_DOT_GRAPH_DEPTH = 0
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = NO
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Configuration::additions related to the search engine
#---------------------------------------------------------------------------
SEARCHENGINE = NO

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,28 @@
cmake_minimum_required(VERSION 3.15)
#
# Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
#
project(async-mqtt5-examples CXX)
cmake_minimum_required(VERSION 3.8...3.20)
include(../cmake/project-is-top-level.cmake)
project(boost-mqtt5-examples LANGUAGES CXX)
if(PROJECT_IS_TOP_LEVEL)
find_package(async-mqtt5 REQUIRED)
endif()
function(add_example name)
add_executable("${name}" ${ARGN})
target_link_libraries("${name}" PRIVATE Boost::mqtt5)
string(FIND "${example_name}" "tls" found_tls)
if(found_tls GREATER -1)
target_link_libraries("${name}" PRIVATE OpenSSL::SSL)
endif()
endfunction()
# set(EXAMPLE <your-source-file>.cpp)
set(EXAMPLE hello_world_over_tcp.cpp)
file(GLOB examples "*.cpp")
find_package(OpenSSL REQUIRED) # if you require SSL connection
add_executable(example ${EXAMPLE})
target_compile_features(example PRIVATE cxx_std_17) # or cxx_std_20
target_link_libraries(example PRIVATE Async::MQTT5 OpenSSL::SSL)
foreach(file_path ${examples})
get_filename_component(example_name "${file_path}" NAME_WE)
add_example("${example_name}" "${file_path}")
endforeach()
find_package(OpenSSL REQUIRED)

View File

@ -1,91 +1,116 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_in_coro_multithreaded_env
#include <vector>
#include <thread>
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[hello_world_in_coro_multithreaded_env
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/thread_pool.hpp>
#include <async_mqtt5.hpp>
#include <cassert>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
// client_type with logging enabled
using client_type = boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
>;
// client_type without logging
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred);
boost::asio::awaitable<void> publish_hello_world(
client_type& client,
const boost::asio::strand<boost::asio::io_context::executor_type>& strand
const config& cfg, client_type& client,
const boost::asio::strand<boost::asio::thread_pool::executor_type>& strand
) {
// Confirmation that the coroutine running in the strand.
assert(strand.running_in_this_thread());
// Confirmation that the coroutine running in the strand.
assert(strand.running_in_this_thread());
// All these function calls will be executed by the strand that is executing the coroutine.
// All the completion handler's associated executors will be that same strand
// because the Client was constructed with it as the default associated executor.
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// All these function calls will be executed by the strand that is executing the coroutine.
// All the completion handler's associated executors will be that same strand
// because the Client was constructed with it as the default associated executor.
client.brokers(cfg.brokers, cfg.port) // Set the Broker to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
auto&& [ec, rc, puback_props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", "Hello world!", async_mqtt5::retain_e::no,
async_mqtt5::publish_props {}, use_nothrow_awaitable);
auto&& [ec, rc, puback_props] = co_await client.async_publish<boost::mqtt5::qos_e::at_least_once>(
"boost-mqtt5/test" /* topic */, "Hello world!" /* payload*/, boost::mqtt5::retain_e::no,
boost::mqtt5::publish_props {}, use_nothrow_awaitable);
co_await client.async_disconnect(use_nothrow_awaitable);
co_return;
co_await client.async_disconnect(use_nothrow_awaitable);
co_return;
}
int main(int argc, char** argv) {
config cfg;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Create a thread pool with 4 threads.
boost::asio::thread_pool tp(4);
// Create an explicit strand from io_context's executor.
// The strand guarantees a serialised handler execution regardless of the
// number of threads running in the io_context.
boost::asio::strand strand = boost::asio::make_strand(tp.get_executor());
// Create the Client with the explicit strand as the default associated executor.
client_type client(strand, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
// Spawn the coroutine.
// The executor that executes the coroutine must be the same executor
// that is the Client's default associated executor.
co_spawn(
strand,
publish_hello_world(cfg, client, strand),
[](std::exception_ptr e) {
if (e)
std::rethrow_exception(e);
}
);
tp.join();
return 0;
}
//]
#else
#include <iostream>
int main() {
// Create a multithreaded environment where 4 threads
// will be calling ioc.run().
// Number of threads that will call io_context::run().
int thread_num = 4;
boost::asio::io_context ioc(4);
// Create the remaining threads (aside of this one).
std::vector<std::thread> threads;
threads.reserve(thread_num - 1);
// Create an explicit strand from io_context's executor.
// The strand guarantees a serialised handler execution regardless of the
// number of threads running in the io_context.
boost::asio::strand strand = boost::asio::make_strand(ioc.get_executor());
// Create the Client with the explicit strand as the default associated executor.
client_type client(strand);
// Spawn the coroutine.
// The executor that executes the coroutine must be the same executor
// that is the Client's default associated executor.
co_spawn(strand, publish_hello_world(client, strand), boost::asio::detached);
// Call ioc.run() in the other threads.
for (int i = 0; i < thread_num - 1; ++i)
threads.emplace_back([&ioc] { ioc.run(); });
// Call ioc.run() on this thread.
ioc.run();
for (auto& t : threads)
if (t.joinable()) t.join();
return 0;
std::cout << "This example requires C++20 standard to compile and run" << std::endl;
}
#endif
//]

View File

@ -1,97 +1,107 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_in_multithreaded_env
#include <iostream>
#include <vector>
#include <thread>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/thread_pool.hpp>
#include <async_mqtt5.hpp>
#include <cassert>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
int main() {
// Create a multithreaded environment where 4 threads
// will be calling ioc.run().
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
// Number of threads that will call io_context::run().
int thread_num = 4;
boost::asio::io_context ioc(4);
int main(int argc, char** argv) {
config cfg;
// Create the remaining threads (aside of this one).
std::vector<std::thread> threads;
threads.reserve(thread_num - 1);
// Create an explicit strand from io_context's executor.
// The strand guarantees a serialised handler execution regardless of the
// number of threads running in the io_context.
boost::asio::strand strand = boost::asio::make_strand(ioc.get_executor());
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Create the Client with the explicit strand as the default associated executor.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(strand);
// Create a thread pool with 4 threads.
boost::asio::thread_pool tp(4);
// Create an explicit strand from thread_pool's executor.
// The strand guarantees a serialised handler execution regardless of the
// number of threads running in the thread_pool.
boost::asio::strand strand = boost::asio::make_strand(tp.get_executor());
// Configure the client.
client.brokers("<your-mqtt-broker>", 1883);
// Create the Client with the explicit strand as the default associated executor.
boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
> client(strand, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
// Start the Client.
// The async_run function call must be posted/dispatched to the strand.
boost::asio::post(
strand,
[&client, &strand] {
// Considering that the default associated executor of all completion handlers is the strand,
// it is not necessary to explicitly bind it to async_run or other async_xxx's handlers.
client.async_run(boost::asio::detached);
}
);
// The async_publish function call must be posted/dispatched to the strand.
// The associated executor of async_publish's completion handler must be the same strand.
boost::asio::post(
strand,
[&client, &strand] {
assert(strand.running_in_this_thread());
// Configure the client.
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id); // Set the Client Identifier. (optional)
client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", "Hello world!", async_mqtt5::retain_e::no,
async_mqtt5::publish_props {},
// You may bind the strand to this handler, but it is not necessary
// as the strand is already the default associated handler.
// However, you must not bind it to any other executor!
[&client, &strand](
async_mqtt5::error_code ec, async_mqtt5::reason_code rc,
async_mqtt5::puback_props props
) {
assert(strand.running_in_this_thread());
// Start the Client.
// The async_run function call must be posted/dispatched to the strand.
boost::asio::dispatch(
boost::asio::bind_executor(
strand,
[&client, &strand, &cfg] {
// Considering that the default associated executor of all completion handlers is the strand,
// it is not necessary to explicitly bind it to async_run or other async_xxx's handlers.
client.async_run(boost::asio::detached); // Start the Client.
}
)
);
// The async_publish function call must be posted/dispatched to the strand.
// The associated executor of async_publish's completion handler must be the same strand.
boost::asio::dispatch(
boost::asio::bind_executor(
strand,
[&client, &strand, &cfg] {
assert(strand.running_in_this_thread());
std::cout << ec.message() << std::endl;
std::cout << rc.message() << std::endl;
client.async_publish<boost::mqtt5::qos_e::at_least_once>(
"boost-mqtt5/test", "Hello World!", boost::mqtt5::retain_e::no,
boost::mqtt5::publish_props {},
// You may bind the strand to this handler, but it is not necessary
// as the strand is already the default associated handler.
[&client, &strand](
boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc,
boost::mqtt5::puback_props props
) {
assert(strand.running_in_this_thread());
// Stop the Client. This will cause ioc.run() to return.
client.cancel();
}
);
}
);
std::cout << ec.message() << std::endl;
std::cout << rc.message() << std::endl;
// Call ioc.run() on the other threads.
for (int i = 0; i < thread_num - 1; ++i)
threads.emplace_back([&ioc] { ioc.run(); });
// Stop the Client.
client.cancel();
}
);
}
)
);
// Call ioc.run() on this thread.
ioc.run();
tp.join();
for (auto& t : threads)
if (t.joinable()) t.join();
return 0;
return 0;
}
//]

View File

@ -1,38 +1,69 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_tcp
#include <iostream>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
#include <string>
int main() {
boost::asio::io_context ioc;
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883; // 1883 is the default TCP MQTT port.
std::string client_id = "boost_mqtt5_tester";
};
// Construct the Client with ``__TCP_SOCKET__`` as the underlying stream.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
int main(int argc, char** argv) {
config cfg;
// 1883 is the default TCP MQTT port.
client.brokers("broker.hivemq.com", 1883)
.async_run(boost::asio::detached);
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"async-mqtt5/test", "Hello world!",
async_mqtt5::retain_e::yes, async_mqtt5::publish_props {},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
boost::asio::io_context ioc;
ioc.run();
//[init_tcp_client_with_logger
// Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging.
// Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate.
boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
> client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
//]
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
//[configure_tcp_client
client.brokers(cfg.brokers, cfg.port) // Set the Broker to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
//]
//[publish_hello_world
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"boost-mqtt5/test", "Hello world!",
boost::mqtt5::retain_e::yes, boost::mqtt5::publish_props {},
[&client](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
// Disconnnect the Client.
client.async_disconnect(boost::asio::detached);
}
);
//]
ioc.run();
}
//]

View File

@ -1,92 +1,102 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_tls
#include <iostream>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/ssl.hpp> // OpenSSL traits
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
#include <string>
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 8883; // 8883 is the default TLS MQTT port.
std::string client_id = "boost_mqtt5_tester";
};
// External customization point.
namespace async_mqtt5 {
namespace boost::mqtt5 {
// Specify that the TLS handshake will be performed as a client.
template <typename StreamBase>
struct tls_handshake_type<boost::asio::ssl::stream<StreamBase>> {
static constexpr auto client = boost::asio::ssl::stream_base::client;
static constexpr auto server = boost::asio::ssl::stream_base::server;
static constexpr auto client = boost::asio::ssl::stream_base::client;
};
// This client uses this function to indicate which hostname it is
// attempting to connect to at the start of the handshaking process.
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& /* ctx */,
boost::asio::ssl::stream<StreamBase>& stream
const authority_path& ap,
boost::asio::ssl::context& /* ctx */,
boost::asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
} // end namespace boost::mqtt5
// The certificate file in the PEM format.
constexpr char ca_cert[] =
"-----BEGIN CERTIFICATE-----\n"
"...........................\n"
"-----END CERTIFICATE-----\n"
;
int main(int argc, char** argv) {
config cfg;
int main() {
boost::asio::io_context ioc;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
boost::asio::io_context ioc;
async_mqtt5::error_code ec;
// TLS context that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
// Add the trusted certificate authority for performing verification.
context.add_certificate_authority(boost::asio::buffer(ca_cert), ec);
if (ec)
std::cout << "Failed to add certificate authority!" << std::endl;
ec.clear();
// Set up the TLS context.
// This step is highly dependent on the specific requirements of the Broker you are connecting to.
// Each broker may have its own standards and expectations for establishing a secure TLS/SSL connection.
// This can include verifying certificates, setting up private keys, PSK authentication, and others.
// Set peer verification mode used by the context.
// This will verify that the server's certificate is valid and signed by a trusted certificate authority.
context.set_verify_mode(boost::asio::ssl::verify_peer, ec);
if (ec)
std::cout << "Failed to set peer verification mode!" << std::endl;
ec.clear();
// Construct the Client with ``__SSL_STREAM__`` as the underlying stream
// with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type
// and logging enabled.
boost::mqtt5::mqtt_client<
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>,
boost::asio::ssl::context,
boost::mqtt5::logger
> client(ioc, std::move(context), boost::mqtt5::logger(boost::mqtt5::log_level::info));
// Construct the Client with ``__SSL_STREAM__`` as the underlying stream
// with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type.
async_mqtt5::mqtt_client<
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>,
boost::asio::ssl::context
> client(ioc, std::move(context));
// 8883 is the default TLS MQTT port.
client.brokers("<your-mqtt-broker>", 8883)
.async_run(boost::asio::detached);
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<
// boost::asio::ssl::stream<boost::asio::ip::tcp::socket>,
// boost::asio::ssl::context
//> client(ioc, std::move(context));
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props{},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
ioc.run();
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"boost-mqtt5/test", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props{},
[&client](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
}
//]

View File

@ -1,42 +1,65 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_websocket_tcp
#include <iostream>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/websocket.hpp> // WebSocket traits
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/websocket.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
#include <string>
int main() {
boost::asio::io_context ioc;
struct config {
std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt
uint16_t port = 8000; // 8083 is the default Webscoket/TCP MQTT port. However, HiveMQ's public broker uses 8000 instead.
std::string client_id = "boost_mqtt5_tester";
};
// Construct the Client with WebSocket/TCP as the underlying stream.
async_mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ip::tcp::socket>
> client(ioc);
int main(int argc, char** argv) {
config cfg;
// 8083 is the default Webscoket/TCP MQTT port.
client.brokers("<your-mqtt-broker-path>", 8083) // Path example: localhost/mqtt
.async_run(boost::asio::detached);
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props{},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
boost::asio::io_context ioc;
ioc.run();
// Construct the Client with WebSocket/TCP as the underlying stream and enabled logging.
// Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate.
boost::mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ip::tcp::socket>,
std::monostate,
boost::mqtt5::logger
> client(ioc, {}, boost::mqtt5::logger(boost::mqtt5::log_level::info));
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<boost::beast::websocket::stream<boost::asio::ip::tcp::socket>> client(ioc);
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"boost-mqtt5/test", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[&client](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
}
//]

View File

@ -1,95 +1,101 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[hello_world_over_websocket_tls
#include <iostream>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/mqtt5/websocket_ssl.hpp> // WebSocket and OpenSSL traits
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/ssl/ssl_stream.hpp> // async_teardown specialization for websocket ssl stream
#include <async_mqtt5.hpp>
#include <iostream>
#include <string>
struct config {
std::string brokers = "broker.hivemq.com/mqtt"; // Path example: localhost/mqtt
uint16_t port = 8884; // 8884 is the default Websocket/TLS MQTT port.
std::string client_id = "boost_mqtt5_tester";
};
// External customization point.
namespace async_mqtt5 {
namespace boost::mqtt5 {
// Specify that the TLS handshake will be performed as a client.
template <typename StreamBase>
struct tls_handshake_type<boost::asio::ssl::stream<StreamBase>> {
static constexpr auto client = boost::asio::ssl::stream_base::client;
static constexpr auto server = boost::asio::ssl::stream_base::server;
static constexpr auto client = boost::asio::ssl::stream_base::client;
};
// This client uses this function to indicate which hostname it is
// attempting to connect to at the start of the handshaking process.
template <typename StreamBase>
void assign_tls_sni(
const authority_path& ap,
boost::asio::ssl::context& /*ctx*/,
boost::asio::ssl::stream<StreamBase>& stream
const authority_path& ap,
boost::asio::ssl::context& /*ctx*/,
boost::asio::ssl::stream<StreamBase>& stream
) {
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
SSL_set_tlsext_host_name(stream.native_handle(), ap.host.c_str());
}
} // end namespace async_mqtt5
} // end namespace boost::mqtt5
// The certificate file in the PEM format.
constexpr char ca_cert[] =
"-----BEGIN CERTIFICATE-----\n"
"...........................\n"
"-----END CERTIFICATE-----\n"
;
int main(int argc, char** argv) {
config cfg;
int main() {
boost::asio::io_context ioc;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Context satisfying ``__TlsContext__`` requirements that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
boost::asio::io_context ioc;
async_mqtt5::error_code ec;
// TLS context that the underlying SSL stream will use.
// The purpose of the context is to allow us to set up TLS/SSL-related options.
// See ``__SSL__`` for more information and options.
boost::asio::ssl::context context(boost::asio::ssl::context::tls_client);
// Add the trusted certificate authority for performing verification.
context.add_certificate_authority(boost::asio::buffer(ca_cert), ec);
if (ec)
std::cout << "Failed to add certificate authority!" << std::endl;
ec.clear();
// Set up the TLS context.
// This step is highly dependent on the specific requirements of the Broker you are connecting to.
// Each broker may have its own standards and expectations for establishing a secure TLS/SSL connection.
// This can include verifying certificates, setting up private keys, PSK authentication, and others.
// Set peer verification mode used by the context.
// This will verify that the server's certificate is valid and signed by a trusted certificate authority.
context.set_verify_mode(boost::asio::ssl::verify_peer, ec);
if (ec)
std::cout << "Failed to set peer verification mode!" << std::endl;
ec.clear();
// Construct the Client with WebSocket/SSL as the underlying stream
// with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type and enabled logging.
boost::mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>,
boost::asio::ssl::context,
boost::mqtt5::logger
> client(ioc, std::move(context), boost::mqtt5::logger(boost::mqtt5::log_level::info));
// Construct the Client with WebSocket/SSL as the underlying stream
// with ``__SSL_CONTEXT__`` as the ``__TlsContext__`` type.
async_mqtt5::mqtt_client<
boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>,
boost::asio::ssl::context
> client(ioc, std::move(context));
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<
// boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>,
// boost::asio::ssl::context
//> client(ioc, std::move(context));
// 8884 is the default Websocket/TLS MQTT port.
client.brokers("<your-mqtt-broker-path>", 8884) // Path example: localhost/mqtt
.async_run(boost::asio::detached);
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
client.async_publish<async_mqtt5::qos_e::at_most_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props{},
[&client](async_mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
client.async_publish<boost::mqtt5::qos_e::at_most_once>(
"boost-mqtt5/test", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props{},
[&client](boost::mqtt5::error_code ec) {
std::cout << ec.message() << std::endl;
client.async_disconnect(boost::asio::detached);
}
);
ioc.run();
ioc.run();
}
//]

View File

@ -1,47 +1,70 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[multiflight_client
#include <iostream>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
#include <string>
int main() {
boost::asio::io_context ioc;
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
int main(int argc, char** argv) {
config cfg;
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Publish with QoS 2 five times in a row without waiting for the handler
// of the previous async_publish call to be invoked.
for (auto i = 1; i <= 5; ++i)
client.async_publish<async_mqtt5::qos_e::exactly_once>(
"<topic>", "Hello world!",
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
[i](async_mqtt5::error_code ec, async_mqtt5::reason_code rc, async_mqtt5::pubcomp_props) {
std::cout << "Publish number " << i << " completed with: " << std::endl;
std::cout << "\t ec: " << ec.message() << std::endl;
std::cout << "\t rc: " << rc.message() << std::endl;
}
);
boost::asio::io_context ioc;
// We can stop the Client by using signals.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client](async_mqtt5::error_code, int) {
client.async_disconnect(boost::asio::detached);
});
// Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging.
// Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate.
boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
> client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
ioc.run();
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the client.
// Publish with QoS 2 five times in a row without waiting for the handler
// of the previous async_publish call to be invoked.
for (auto i = 1; i <= 5; ++i)
client.async_publish<boost::mqtt5::qos_e::exactly_once>(
"boost-mqtt5/test", "Hello world!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
[i](boost::mqtt5::error_code ec, boost::mqtt5::reason_code rc, boost::mqtt5::pubcomp_props) {
std::cout << "Publish number " << i << " completed with: " << std::endl;
std::cout << "\t ec: " << ec.message() << std::endl;
std::cout << "\t rc: " << rc.message() << std::endl;
}
);
// We can stop the Client by using signals.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client](boost::mqtt5::error_code, int) {
client.async_disconnect(boost::asio::detached);
});
ioc.run();
}
//]

View File

@ -1,109 +1,148 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[publisher
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <string>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred);
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
// client_type with logging enabled
using client_type = boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
>;
// client_type without logging
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
int next_sensor_reading() {
srand(static_cast<unsigned int>(std::time(0)));
return rand() % 100;
srand(static_cast<unsigned int>(std::time(0)));
return rand() % 100;
}
boost::asio::awaitable<void> publish_sensor_readings(
client_type& client, boost::asio::steady_timer& timer
const config& cfg, client_type& client, boost::asio::steady_timer& timer
) {
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers("<your-mqtt-broker>", 1883) // Broker that we want to connect to. 1883 is the default TCP port.
.async_run(boost::asio::detached); // Start the client.
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
for (;;) {
// Get the next sensor reading.
auto reading = std::to_string(next_sensor_reading());
for (;;) {
// Get the next sensor reading.
auto reading = std::to_string(next_sensor_reading());
// Publish the sensor reading with QoS 1.
auto&& [ec, rc, props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", reading,
async_mqtt5::retain_e::no, async_mqtt5::publish_props {}, use_nothrow_awaitable
);
// An error can occur as a result of:
// a) wrong publish parameters
// b) mqtt_client::cancel is called while the Client is publishing the message
// resulting in cancellation.
if (ec) {
std::cout << "Publish error occurred: " << ec.message() << std::endl;
break;
}
// Publish the sensor reading with QoS 1.
auto&& [ec, rc, props] = co_await client.async_publish<boost::mqtt5::qos_e::at_least_once>(
"boost-mqtt5/test" /* topic */, reading /* payload */,
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {}, use_nothrow_awaitable
);
// An error can occur as a result of:
// a) wrong publish parameters
// b) mqtt_client::cancel is called while the Client is publishing the message
// resulting in cancellation.
if (ec) {
std::cout << "Publish error occurred: " << ec.message() << std::endl;
break;
}
// Reason code is the reply from the server presenting the result of the publish operation.
std::cout << "Result of publish request: " << rc.message() << std::endl;
if (!rc)
std::cout << "Published sensor reading: " << reading << std::endl;
// Reason code is the reply from the server presenting the result of the publish operation.
std::cout << "Result of publish request: " << rc.message() << std::endl;
if (!rc)
std::cout << "Published sensor reading: " << reading << std::endl;
// Wait 5 seconds before publishing the next reading.
timer.expires_after(std::chrono::seconds(5));
auto&& [tec] = co_await timer.async_wait(use_nothrow_awaitable);
// Wait 5 seconds before publishing the next reading.
timer.expires_after(std::chrono::seconds(5));
auto&& [tec] = co_await timer.async_wait(use_nothrow_awaitable);
// An error occurred if we cancelled the timer.
if (tec)
break;
}
// An error occurred if we cancelled the timer.
if (tec)
break;
}
co_return;
co_return;
}
int main(int argc, char** argv) {
config cfg;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Initialise execution context.
boost::asio::io_context ioc;
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
// Initialise the timer.
boost::asio::steady_timer timer(ioc);
// Set up signals to stop the program on demand.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client, &timer](boost::mqtt5::error_code /* ec */, int /* signal */) {
// After we are done with publishing all the messages, cancel the timer and the Client.
// Alternatively, use mqtt_client::async_disconnect.
timer.cancel();
client.cancel();
});
// Spawn the coroutine.
co_spawn(
ioc.get_executor(),
publish_sensor_readings(cfg, client, timer),
[](std::exception_ptr e) {
if (e)
std::rethrow_exception(e);
}
);
// Start the execution.
ioc.run();
}
//]
#else
#include <iostream>
int main() {
// Initialise execution context.
boost::asio::io_context ioc;
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc);
// Initialise the timer.
boost::asio::steady_timer timer(ioc);
// Set up signals to stop the program on demand.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client, &timer](async_mqtt5::error_code /* ec */, int /* signal */) {
// After we are done with publishing all the messages, cancel the timer and the Client.
// Alternatively, use mqtt_client::async_disconnect.
timer.cancel();
client.cancel();
});
// Spawn the coroutine.
co_spawn(ioc.get_executor(), publish_sensor_readings(client, timer), boost::asio::detached);
// Start the execution.
ioc.run();
std::cout << "This example requires C++20 standard to compile and run" << std::endl;
}
#endif
//]

View File

@ -1,117 +1,160 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/asio/use_awaitable.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[receiver
#include <iostream>
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <async_mqtt5.hpp>
#include <iostream>
#include <string>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred);
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
// client_type with logging enabled
using client_type = boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
>;
// client_type without logging
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
boost::asio::awaitable<bool> subscribe(client_type& client) {
// Configure the request to subscribe to a Topic.
async_mqtt5::subscribe_topic sub_topic = async_mqtt5::subscribe_topic{
"<your-mqtt-topic>",
async_mqtt5::subscribe_options {
async_mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2.
async_mqtt5::no_local_e::no, // Forward message from Clients with same ID.
async_mqtt5::retain_as_published_e::retain, // Keep the original RETAIN flag.
async_mqtt5::retain_handling_e::send // Send retained messages when the subscription is established.
}
};
// Configure the request to subscribe to a Topic.
boost::mqtt5::subscribe_topic sub_topic = boost::mqtt5::subscribe_topic{
"test" /* topic */,
boost::mqtt5::subscribe_options {
boost::mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2.
boost::mqtt5::no_local_e::no, // Forward message from Clients with same ID.
boost::mqtt5::retain_as_published_e::retain, // Keep the original RETAIN flag.
boost::mqtt5::retain_handling_e::send // Send retained messages when the subscription is established.
}
};
// Subscribe to a single Topic.
auto&& [ec, sub_codes, sub_props] = co_await client.async_subscribe(
sub_topic, async_mqtt5::subscribe_props {}, use_nothrow_awaitable
);
// Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call.
// Subscribe to a single Topic.
auto&& [ec, sub_codes, sub_props] = co_await client.async_subscribe(
sub_topic, boost::mqtt5::subscribe_props {}, use_nothrow_awaitable
);
// Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call.
// An error can occur as a result of:
// a) wrong subscribe parameters
// b) mqtt_client::cancel is called while the Client is in the process of subscribing
if (ec)
std::cout << "Subscribe error occurred: " << ec.message() << std::endl;
else
std::cout << "Result of subscribe request: " << sub_codes[0].message() << std::endl;
// An error can occur as a result of:
// a) wrong subscribe parameters
// b) mqtt_client::cancel is called while the Client is in the process of subscribing
if (ec)
std::cout << "Subscribe error occurred: " << ec.message() << std::endl;
else
std::cout << "Result of subscribe request: " << sub_codes[0].message() << std::endl;
co_return !ec && !sub_codes[0]; // True if the subscription was successfully established.
co_return !ec && !sub_codes[0]; // True if the subscription was successfully established.
}
boost::asio::awaitable<void> subscribe_and_receive(client_type& client) {
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers("<your-mqtt-broker>", 1883) // Broker that we want to connect to. 1883 is the default TCP port.
.async_run(boost::asio::detached); // Start the client.
boost::asio::awaitable<void> subscribe_and_receive(
const config& cfg, client_type& client
) {
// Configure the Client.
// It is mandatory to call brokers() and async_run() to configure the Brokers to connect to and start the Client.
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the client.
// Before attempting to receive an Application Message from the Topic we just subscribed to,
// it is advisable to verify that the subscription succeeded.
// It is not recommended to call mqtt_client::async_receive if you do not have any
// subscription established as the corresponding handler will never be invoked.
if (!(co_await subscribe(client)))
co_return;
// Before attempting to receive an Application Message from the Topic we just subscribed to,
// it is advisable to verify that the subscription succeeded.
// It is not recommended to call mqtt_client::async_receive if you do not have any
// subscription established as the corresponding handler will never be invoked.
if (!(co_await subscribe(client)))
co_return;
for (;;) {
// Receive an Appplication Message from the subscribed Topic(s).
auto&& [ec, topic, payload, publish_props] = co_await client.async_receive(use_nothrow_awaitable);
for (;;) {
// Receive an Appplication Message from the subscribed Topic(s).
auto&& [ec, topic, payload, publish_props] = co_await client.async_receive(use_nothrow_awaitable);
if (ec == async_mqtt5::client::error::session_expired) {
// The Client has reconnected, and the prior session has expired.
// As a result, any previous subscriptions have been lost and must be reinstated.
if (co_await subscribe(client))
continue;
else
break;
} else if (ec)
break;
if (ec == boost::mqtt5::client::error::session_expired) {
// The Client has reconnected, and the prior session has expired.
// As a result, any previous subscriptions have been lost and must be reinstated.
if (co_await subscribe(client))
continue;
else
break;
} else if (ec)
break;
std::cout << "Received message from the Broker" << std::endl;
std::cout << "\t topic: " << topic << std::endl;
std::cout << "\t payload: " << payload << std::endl;
}
std::cout << "Received message from the Broker" << std::endl;
std::cout << "\t topic: " << topic << std::endl;
std::cout << "\t payload: " << payload << std::endl;
}
co_return;
co_return;
}
int main(int argc, char** argv) {
config cfg;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Initialise execution context.
boost::asio::io_context ioc;
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
// Set up signals to stop the program on demand.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client](boost::mqtt5::error_code /* ec */, int /* signal */) {
// After we are done with publishing all the messages, cancel the timer and the Client.
// Alternatively, use mqtt_client::async_disconnect.
client.cancel();
});
// Spawn the coroutine.
co_spawn(
ioc,
subscribe_and_receive(cfg, client),
[](std::exception_ptr e) {
if (e)
std::rethrow_exception(e);
}
);
// Start the execution.
ioc.run();
}
//]
#else
#include <iostream>
int main() {
// Initialise execution context.
boost::asio::io_context ioc;
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc);
// Set up signals to stop the program on demand.
boost::asio::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&client](async_mqtt5::error_code /* ec */, int /* signal */) {
// After we are done with publishing all the messages, cancel the timer and the Client.
// Alternatively, use mqtt_client::async_disconnect.
client.cancel();
});
// Spawn the coroutine.
co_spawn(ioc, subscribe_and_receive(client), boost::asio::detached);
// Start the execution.
ioc.run();
std::cout << "This example requires C++20 standard to compile and run" << std::endl;
}
#endif
//]

View File

@ -1,74 +1,122 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[timeout_with_awaitable_operators
#include <chrono>
#include <iostream>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <async_mqtt5.hpp>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
//[timeout_with_awaitable_operators
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <chrono>
#include <iostream>
#include <string>
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
// Modified completion token that will prevent co_await from throwing exceptions.
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::deferred);
using client_type = async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
// client_type with logging enabled
using client_type = boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
>;
boost::asio::awaitable<void> send_over_mqtt(client_type& client, const std::string& message) {
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// client_type without logging
//using client_type = boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket>;
auto&& [pec, prc, puback_props] = co_await client.async_publish<async_mqtt5::qos_e::at_least_once>(
"<your-mqtt-topic>", message,
async_mqtt5::retain_e::no, async_mqtt5::publish_props {},
use_nothrow_awaitable
);
boost::asio::awaitable<void> send_over_mqtt(
const config& cfg, client_type& client
) {
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
co_await client.async_disconnect(use_nothrow_awaitable);
auto&& [pec, prc, puback_props] = co_await client.async_publish<boost::mqtt5::qos_e::at_least_once>(
"boost-mqtt5/test", "Hello World!",
boost::mqtt5::retain_e::no, boost::mqtt5::publish_props {},
use_nothrow_awaitable
);
co_await client.async_disconnect(use_nothrow_awaitable);
co_return;
}
int main(int argc, char** argv) {
config cfg;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
boost::asio::io_context ioc;
co_spawn(
ioc,
[&ioc, &cfg]() -> boost::asio::awaitable<void> {
// Initialise the Client to connect to the Broker over TCP.
client_type client(ioc);
// You can also initialise the Client and its logger with a specific log_level (default log_level::info).
//client_type client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::debug));
// Construct the timer.
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
using namespace boost::asio::experimental::awaitable_operators;
auto res = co_await (
send_over_mqtt(cfg, client) ||
timer.async_wait(boost::asio::as_tuple(boost::asio::use_awaitable))
);
// The timer expired first. The client is cancelled.
if (res.index() == 1)
std::cout << "Send over MQTT timed out!" << std::endl;
// send_over_mqtt completed first. The timer is cancelled.
else
std::cout << "Send over MQTT completed!" << std::endl;
},
[](std::exception_ptr e) {
if (e)
std::rethrow_exception(e);
}
);
ioc.run();
}
//]
#else
#include <iostream>
int main() {
boost::asio::io_context ioc;
co_spawn(ioc, [&ioc]() -> boost::asio::awaitable<void> {
// Construct the Client.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
// Construct the timer.
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
using namespace boost::asio::experimental::awaitable_operators;
auto res = co_await (
send_over_mqtt(client, "Hello world!") ||
timer.async_wait(use_nothrow_awaitable)
);
// The timer expired first. The client is cancelled.
if (res.index() == 1)
std::cout << "Send over MQTT timed out!" << std::endl;
// send_over_mqtt completed first. The timer is cancelled.
else
std::cout << "Send over MQTT completed!" << std::endl;
}, boost::asio::detached);
ioc.run();
std::cout << "This example requires C++20 standard to compile and run" << std::endl;
}
#endif
//]

View File

@ -1,75 +1,99 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
// Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//[timeout_with_parallel_group
#include <boost/mqtt5/logger.hpp>
#include <boost/mqtt5/mqtt_client.hpp>
#include <boost/mqtt5/reason_codes.hpp>
#include <boost/mqtt5/types.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/steady_timer.hpp>
#include <array>
#include <chrono>
#include <iostream>
#include <string>
#include <vector>
#include <boost/asio/io_context.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/ip/tcp.hpp>
struct config {
std::string brokers = "broker.hivemq.com";
uint16_t port = 1883;
std::string client_id = "boost_mqtt5_tester";
};
#include <async_mqtt5.hpp>
int main(int argc, char** argv) {
config cfg;
int main() {
boost::asio::io_context ioc;
if (argc == 4) {
cfg.brokers = argv[1];
cfg.port = uint16_t(std::stoi(argv[2]));
cfg.client_id = argv[3];
}
// Construct the Client.
async_mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
boost::asio::io_context ioc;
// Construct the timer.
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
// Construct the Client with ``__TCP_SOCKET__`` as the underlying stream and enabled logging.
// Since we are not establishing a secure connection, set the TlsContext template parameter to std::monostate.
boost::mqtt5::mqtt_client<
boost::asio::ip::tcp::socket, std::monostate /* TlsContext */, boost::mqtt5::logger
> client(ioc, {} /* tls_context */, boost::mqtt5::logger(boost::mqtt5::log_level::info));
client.brokers("<your-mqtt-broker>", 1883)
.async_run(boost::asio::detached);
// If you want to use the Client without logging, initialise it with the following line instead.
//boost::mqtt5::mqtt_client<boost::asio::ip::tcp::socket> client(ioc);
// Subscribe to a Topic.
client.async_subscribe(
{ "<your-mqtt-topic>" }, async_mqtt5::subscribe_props {},
[](async_mqtt5::error_code ec, std::vector<async_mqtt5::reason_code> rcs, async_mqtt5::suback_props) {
std::cout << "[subscribe ec]: " << ec.message() << std::endl;
std::cout << "[subscribe rc]: " << rcs[0].message() << std::endl;
}
);
// Construct the timer.
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
// Create a parallel group to wait up to 5 seconds to receive a message
// using client.async_receive(...).
boost::asio::experimental::make_parallel_group(
timer.async_wait(boost::asio::deferred),
client.async_receive(boost::asio::deferred)
).async_wait(
boost::asio::experimental::wait_for_one(),
[&client](
std::array<std::size_t, 2> ord, // Completion order
async_mqtt5::error_code /* timer_ec */, // timer.async_wait(...) handler signature
// client.async_receive(...) handler signature
async_mqtt5::error_code receive_ec,
std::string topic, std::string payload, async_mqtt5::publish_props /* props */
) {
if (ord[0] == 1) {
std::cout << "Received a message!" << std::endl;
std::cout << "[receive ec]: " << receive_ec.message() << std::endl;
std::cout << "[receive topic]: " << topic << std::endl;
std::cout << "[receive payload]: " << payload << std::endl;
}
else
std::cout << "Timed out! Did not receive a message within 5 seconds." << std::endl;
client.brokers(cfg.brokers, cfg.port) // Broker that we want to connect to.
.credentials(cfg.client_id) // Set the Client Identifier. (optional)
.async_run(boost::asio::detached); // Start the Client.
client.cancel();
}
);
// Subscribe to a Topic.
client.async_subscribe(
{ "test" /* Topic */}, boost::mqtt5::subscribe_props {},
[](boost::mqtt5::error_code ec, std::vector<boost::mqtt5::reason_code> rcs, boost::mqtt5::suback_props) {
std::cout << "[subscribe ec]: " << ec.message() << std::endl;
std::cout << "[subscribe rc]: " << rcs[0].message() << std::endl;
}
);
ioc.run();
// Create a parallel group to wait up to 5 seconds to receive a message
// using client.async_receive(...).
boost::asio::experimental::make_parallel_group(
timer.async_wait(boost::asio::deferred),
client.async_receive(boost::asio::deferred)
).async_wait(
boost::asio::experimental::wait_for_one(),
[&client](
std::array<std::size_t, 2> ord, // Completion order
boost::mqtt5::error_code /* timer_ec */, // timer.async_wait(...) handler signature
// client.async_receive(...) handler signature
boost::mqtt5::error_code receive_ec,
std::string topic, std::string payload, boost::mqtt5::publish_props /* props */
) {
if (ord[0] == 1) {
std::cout << "Received a message!" << std::endl;
std::cout << "[receive ec]: " << receive_ec.message() << std::endl;
std::cout << "[receive topic]: " << topic << std::endl;
std::cout << "[receive payload]: " << payload << std::endl;
}
else
std::cout << "Timed out! Did not receive a message within 5 seconds." << std::endl;
client.cancel();
}
);
ioc.run();
}
//]

View File

@ -1,17 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_HPP
#define ASYNC_MQTT5_HPP
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/mqtt_client.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#endif // !ASYNC_MQTT5_HPP

View File

@ -1,143 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ANY_AUTHENTICATOR
#define ASYNC_MQTT5_ANY_AUTHENTICATOR
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/system/error_code.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <boost/type_traits/is_detected_convertible.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
using auth_handler_type = asio::any_completion_handler<
void (error_code, std::string)
>;
template <typename T, typename ...Ts>
using async_auth_sig = decltype(
std::declval<T>().async_auth(std::declval<Ts>()...)
);
template <typename T>
using method_sig = decltype(
std::declval<T>().method()
);
template <typename T>
constexpr bool is_authenticator =
boost::is_detected<
async_auth_sig, T,
auth_step_e, std::string, auth_handler_type
>::value &&
boost::is_detected_convertible_v<std::string_view, method_sig, T>;
class auth_fun_base {
using auth_func = void(*)(
auth_step_e, std::string, auth_handler_type, auth_fun_base*
);
auth_func _auth_func;
public:
auth_fun_base(auth_func f) : _auth_func(f) {}
~auth_fun_base() = default;
void async_auth(
auth_step_e step, std::string data,
auth_handler_type auth_handler
) {
_auth_func(step, std::move(data), std::move(auth_handler), this);
}
};
template <
typename Authenticator,
typename = std::enable_if_t<is_authenticator<Authenticator>>
>
class auth_fun : public auth_fun_base {
Authenticator _authenticator;
public:
auth_fun(Authenticator authenticator) :
auth_fun_base(&async_auth),
_authenticator(std::forward<Authenticator>(authenticator))
{}
static void async_auth(
auth_step_e step, std::string data, auth_handler_type auth_handler,
auth_fun_base* base_ptr
) {
auto auth_fun_ptr = static_cast<auth_fun*>(base_ptr);
auth_fun_ptr->_authenticator.async_auth(
step, std::move(data), std::move(auth_handler)
);
}
};
class any_authenticator {
std::string _method;
std::shared_ptr<detail::auth_fun_base> _auth_fun;
public:
any_authenticator() = default;
template <
typename Authenticator,
std::enable_if_t<detail::is_authenticator<Authenticator>, bool> = true
>
any_authenticator(Authenticator&& a) :
_method(a.method()),
_auth_fun(
new detail::auth_fun<Authenticator>(
std::forward<Authenticator>(a)
)
)
{}
std::string_view method() const {
return _method;
}
template <typename CompletionToken>
decltype(auto) async_auth(
auth_step_e step, std::string data,
CompletionToken&& token
) {
using Signature = void (error_code, std::string);
auto initiation = [](
auto handler, any_authenticator& self,
auth_step_e step, std::string data
) {
self._auth_fun->async_auth(
step, std::move(data), std::move(handler)
);
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this), step, std::move(data)
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ANY_AUTHENTICATOR

View File

@ -1,234 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASYNC_MUTEX_HPP
#define ASYNC_MQTT5_ASYNC_MUTEX_HPP
#include <atomic>
#include <deque>
#include <mutex>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/execution.hpp>
#include <boost/asio/require.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/detail/spinlock.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
class async_mutex {
public:
using executor_type = asio::any_io_executor;
private:
using queued_op_t = asio::any_completion_handler<
void (error_code)
>;
using queue_t = std::deque<queued_op_t>;
// Handler with assigned tracking executor.
// Objects of this type are type-erased by any_completion_handler
// and stored in the waiting queue.
template <typename Handler, typename Executor>
class tracked_op {
tracking_type<Handler, Executor> _executor;
Handler _handler;
public:
tracked_op(Handler&& h, const Executor& ex) :
_executor(tracking_executor(h, ex)), _handler(std::move(h))
{}
tracked_op(tracked_op&&) = default;
tracked_op(const tracked_op&) = delete;
tracked_op& operator=(tracked_op&&) = default;
tracked_op& operator=(const tracked_op&) = delete;
using allocator_type = asio::associated_allocator_t<Handler>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<Handler>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = tracking_type<Handler, Executor>;
executor_type get_executor() const noexcept {
return _executor;
}
void operator()(error_code ec) {
std::move(_handler)(ec);
}
};
// Per-operation cancellation helper.
// It is safe to emit the cancellation signal from any thread
// provided there are no other concurrent calls to the async_mutex.
// The helper stores queue iterator to operation since the iterator
// would not be invalidated by other queue operations.
class cancel_waiting_op {
async_mutex& _owner;
queue_t::iterator _ihandler;
public:
cancel_waiting_op(async_mutex& owner, queue_t::iterator ih) :
_owner(owner), _ihandler(ih)
{}
void operator()(asio::cancellation_type_t type) {
if (type == asio::cancellation_type_t::none)
return;
std::unique_lock l { _owner._thread_mutex };
if (*_ihandler) {
auto h = std::move(*_ihandler);
auto ex = asio::get_associated_executor(h);
l.unlock();
asio::require(ex, asio::execution::blocking.possibly)
.execute([h = std::move(h)]() mutable {
std::move(h)(asio::error::operation_aborted);
});
}
}
};
spinlock _thread_mutex;
std::atomic<bool> _locked { false };
queue_t _waiting;
executor_type _ex;
public:
template <typename Executor>
explicit async_mutex(Executor&& ex) : _ex(std::forward<Executor>(ex)) {}
async_mutex(const async_mutex&) = delete;
async_mutex& operator=(const async_mutex&) = delete;
~async_mutex() {
cancel();
}
const executor_type& get_executor() const noexcept {
return _ex;
}
bool is_locked() const noexcept {
return _locked.load(std::memory_order_relaxed);
}
// Schedules mutex for lock operation and return immediately.
// Calls given completion handler when mutex is locked.
// It's the responsibility of the completion handler to unlock the mutex.
template <typename CompletionToken>
decltype(auto) lock(CompletionToken&& token) noexcept {
using Signature = void (error_code);
auto initiation = [] (auto handler, async_mutex& self) {
self.execute_or_queue(std::move(handler));
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this)
);
}
// Unlocks the mutex. The mutex must be in locked state.
// Next queued operation, if any, will be executed in a manner
// equivalent to asio::post.
void unlock() {
std::unique_lock l { _thread_mutex };
if (_waiting.empty()) {
_locked.store(false, std::memory_order_release);
return;
}
while (!_waiting.empty()) {
auto op = std::move(_waiting.front());
_waiting.pop_front();
if (!op) continue;
op.get_cancellation_slot().clear();
l.unlock();
execute_op(std::move(op));
break;
}
}
// Cancels all outstanding operations waiting on the mutex.
void cancel() {
std::unique_lock l { _thread_mutex };
while (!_waiting.empty()) {
auto op = std::move(_waiting.front());
_waiting.pop_front();
if (!op) continue;
op.get_cancellation_slot().clear();
asio::require(_ex, asio::execution::blocking.never)
.execute([ex = _ex, op = std::move(op)]() mutable {
auto opex = asio::get_associated_executor(op, ex);
opex.execute(
[op = std::move(op)]() mutable {
op(asio::error::operation_aborted);
}
);
});
}
}
private:
// Schedule operation to `opex` executor using `_ex` executor.
// The operation is equivalent to asio::post(_ex, op) but
// for some reason this form of execution is much faster.
void execute_op(queued_op_t op) {
asio::require(_ex, asio::execution::blocking.never)
.execute([ex = _ex, op = std::move(op)]() mutable {
auto opex = asio::get_associated_executor(op, ex);
opex.execute(
[op = std::move(op)]() mutable {
op(error_code {});
}
);
});
}
// Executes operation immediately if mutex is not locked
// or queues it for later execution otherwise. In both cases
// the operation will be executed in a manner equivalent
// to asio::post to avoid recursion.
template <typename Handler>
void execute_or_queue(Handler&& handler) noexcept {
std::unique_lock l { _thread_mutex };
tracked_op h { std::move(handler), _ex };
if (_locked.load(std::memory_order_relaxed)) {
_waiting.emplace_back(std::move(h));
auto slot = _waiting.back().get_cancellation_slot();
if (slot.is_connected())
slot.template emplace<cancel_waiting_op>(
*this, _waiting.end() - 1
);
}
else {
_locked.store(true, std::memory_order_release);
l.unlock();
execute_op(queued_op_t { std::move(h) });
}
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ASYNC_MUTEX_HPP

View File

@ -1,205 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASYNC_TRAITS_HPP
#define ASYNC_MQTT5_ASYNC_TRAITS_HPP
#include <type_traits>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/execution.hpp>
#include <boost/asio/prefer.hpp>
#include <boost/asio/write.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/type_traits/detected_or.hpp>
#include <boost/type_traits/is_detected.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5 {
namespace asio = boost::asio;
template <typename StreamType>
struct tls_handshake_type {};
template <typename TlsContext, typename TlsStream>
void assign_tls_sni(const authority_path& ap, TlsContext& ctx, TlsStream& s);
namespace detail {
template <typename Handler, typename DfltExecutor>
using tracking_type = std::decay_t<
typename asio::prefer_result<
asio::associated_executor_t<Handler, DfltExecutor>,
asio::execution::outstanding_work_t::tracked_t
>::type
>;
template <typename Handler, typename DfltExecutor>
tracking_type<Handler, DfltExecutor>
tracking_executor(const Handler& handler, const DfltExecutor& ex) {
return asio::prefer(
asio::get_associated_executor(handler, ex),
asio::execution::outstanding_work.tracked
);
}
template <typename T, typename ...Ts>
using async_write_sig = decltype(
std::declval<T&>().async_write(std::declval<Ts>()...)
);
constexpr auto write_handler_t = [](error_code, size_t) {};
template <typename T, typename B>
constexpr bool has_async_write = boost::is_detected<
async_write_sig, T, B, decltype(write_handler_t)
>::value;
constexpr auto handshake_handler_t = [](error_code) {};
template <typename T>
using tls_handshake_t = typename T::handshake_type;
template <typename T>
using tls_handshake_type_of = boost::detected_or_t<void, tls_handshake_t, T>;
template <typename T, typename ...Ts>
using async_tls_handshake_sig = decltype(
std::declval<T&>().async_handshake(std::declval<Ts>()...)
);
template <typename T>
constexpr bool has_tls_handshake = boost::is_detected<
async_tls_handshake_sig, T, tls_handshake_type_of<T>,
decltype(handshake_handler_t)
>::value;
template <typename T, typename ...Ts>
using async_ws_handshake_sig = decltype(
std::declval<T&>().async_handshake(std::declval<Ts>()...)
);
template <typename T>
constexpr bool has_ws_handshake = boost::is_detected<
async_ws_handshake_sig, T,
std::string_view, std::string_view,
decltype(handshake_handler_t)
>::value;
template <typename T>
using tls_context_sig = decltype(
std::declval<T&>().tls_context()
);
template <typename T>
constexpr bool has_tls_context = boost::is_detected<
tls_context_sig, T
>::value;
template <typename T>
using next_layer_sig = decltype(
std::declval<T&>().next_layer()
);
template <typename T>
constexpr bool has_next_layer = boost::is_detected<
next_layer_sig, T
>::value;
template <typename T, typename Enable = void>
struct next_layer_type {
using type = T;
};
template <typename T>
struct next_layer_type<
T, std::enable_if_t<has_next_layer<T>>
> {
using type = typename std::remove_reference_t<T>::next_layer_type;
};
template <typename T>
typename next_layer_type<T, std::enable_if_t<!has_next_layer<T>>>::type&
next_layer(T&& a) {
return a;
}
template <typename T>
typename next_layer_type<T, std::enable_if_t<has_next_layer<T>>>::type&
next_layer(T&& a) {
return a.next_layer();
}
template <typename S>
using lowest_layer_type = typename boost::beast::lowest_layer_type<S>;
template <typename S>
lowest_layer_type<S>& lowest_layer(S&& a) {
return boost::beast::get_lowest_layer(std::forward<S>(a));
}
template <typename T, typename Enable = void>
struct has_tls_layer_impl : std::false_type {};
template <typename T>
struct has_tls_layer_impl<
T, std::enable_if_t<has_tls_handshake<T>>
> : std::true_type {};
template <typename T>
struct has_tls_layer_impl<
T, std::enable_if_t<!has_tls_handshake<T> && has_next_layer<T>>
> : has_tls_layer_impl<
boost::remove_cv_ref_t<decltype(std::declval<T&>().next_layer())>
> {};
template <typename T>
constexpr bool has_tls_layer = has_tls_layer_impl<
boost::remove_cv_ref_t<T>
>::value;
template <
typename Stream,
typename ConstBufferSequence,
typename CompletionToken
>
decltype(auto) async_write(
Stream& stream, const ConstBufferSequence& buff, CompletionToken&& token
) {
if constexpr (has_async_write<Stream, ConstBufferSequence>)
return stream.async_write(
buff, std::forward<CompletionToken>(token)
);
else
return asio::async_write(
stream, buff, std::forward<CompletionToken>(token)
);
}
template <typename TlsContext, typename Stream>
void setup_tls_sni(const authority_path& ap, TlsContext& ctx, Stream& s) {
if constexpr (has_tls_handshake<Stream>)
assign_tls_sni(ap, ctx, s);
else if constexpr (has_next_layer<Stream>)
setup_tls_sni(ap, ctx, next_layer(s));
}
} // end namespace detail
} // end namespace async_mqtt5
#endif // !ASYNC_MQTT5_ASYNC_TRAITS_HPP

View File

@ -1,95 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
#define ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/associated_immediate_executor.hpp>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
namespace async_mqtt5::detail {
template <typename Handler, typename Executor>
class cancellable_handler {
Executor _executor;
Handler _handler;
tracking_type<Handler, Executor> _handler_ex;
asio::cancellation_state _cancellation_state;
public:
cancellable_handler(Handler&& handler, const Executor& ex) :
_executor(ex),
_handler(std::move(handler)),
_handler_ex(tracking_executor(_handler, ex)),
_cancellation_state(
asio::get_associated_cancellation_slot(_handler),
asio::enable_total_cancellation {},
asio::enable_terminal_cancellation {}
)
{}
cancellable_handler(cancellable_handler&&) = default;
cancellable_handler(const cancellable_handler&) = delete;
cancellable_handler& operator=(cancellable_handler&&) = default;
cancellable_handler& operator=(const cancellable_handler&) = delete;
using allocator_type = asio::associated_allocator_t<Handler>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type = asio::associated_cancellation_slot_t<Handler>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
using executor_type = tracking_type<Handler, Executor>;
executor_type get_executor() const noexcept {
return _handler_ex;
}
using immediate_executor_type =
asio::associated_immediate_executor_t<Handler, Executor>;
immediate_executor_type get_immediate_executor() const noexcept {
// get_associated_immediate_executor will require asio::execution::blocking.never
// on the default executor.
return asio::get_associated_immediate_executor(_handler, _executor);
}
asio::cancellation_type_t cancelled() const {
return _cancellation_state.cancelled();
}
template <typename... Args>
void complete(Args&&... args) {
asio::get_associated_cancellation_slot(_handler).clear();
std::move(_handler)(std::forward<Args>(args)...);
}
template <typename... Args>
void complete_immediate(Args&&... args) {
asio::get_associated_cancellation_slot(_handler).clear();
auto ex = get_immediate_executor();
asio::dispatch(
ex,
asio::prepend(std::move(_handler), std::forward<Args>(args)...)
);
}
};
} // end async_mqtt5::detail
#endif // !ASYNC_MQTT5_CANCELLABLE_HANDLER_HPP

View File

@ -1,103 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CHANNEL_TRAITS_HPP
#define ASYNC_MQTT5_CHANNEL_TRAITS_HPP
#include <deque>
#include <type_traits>
#include <boost/asio/error.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
template <typename Element>
class bounded_deque {
std::deque<Element> _buffer;
static constexpr size_t MAX_SIZE = 65535;
public:
bounded_deque() = default;
bounded_deque(size_t n) : _buffer(n) {}
size_t size() const {
return _buffer.size();
}
template <typename E>
void push_back(E&& e) {
if (_buffer.size() == MAX_SIZE)
_buffer.pop_front();
_buffer.push_back(std::forward<E>(e));
}
void pop_front() {
_buffer.pop_front();
}
void clear() {
_buffer.clear();
}
const auto& front() const noexcept {
return _buffer.front();
}
auto& front() noexcept {
return _buffer.front();
}
};
template <typename... Signatures>
struct channel_traits {
template <typename... NewSignatures>
struct rebind {
using other = channel_traits<NewSignatures...>;
};
};
template <typename R, typename... Args>
struct channel_traits<R(error_code, Args...)> {
static_assert(sizeof...(Args) > 0);
template <typename... NewSignatures>
struct rebind {
using other = channel_traits<NewSignatures...>;
};
template <typename Element>
struct container {
using type = bounded_deque<Element>;
};
using receive_cancelled_signature = R(error_code, Args...);
template <typename F>
static void invoke_receive_cancelled(F f) {
std::forward<F>(f)(
asio::error::operation_aborted,
typename std::decay_t<Args>()...
);
}
using receive_closed_signature = R(error_code, Args...);
template <typename F>
static void invoke_receive_closed(F f) {
std::forward<F>(f)(
asio::error::operation_aborted,
typename std::decay_t<Args>()...
);
}
};
} // namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_CHANNEL_TRAITS_HPP

View File

@ -1,193 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CONTROL_PACKET_HPP
#define ASYNC_MQTT5_CONTROL_PACKET_HPP
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <boost/smart_ptr/allocate_unique.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5::detail {
/* max varint number (268'435'455) + fixed header size (1 + 4) */
static constexpr int32_t default_max_send_size = 268'435'460;
enum class control_code_e : std::uint8_t {
no_packet = 0b00000000, // 0
connect = 0b00010000, // 1
connack = 0b00100000, // 2
publish = 0b00110000, // 3
puback = 0b01000000, // 4
pubrec = 0b01010000, // 5
pubrel = 0b01100000, // 6
pubcomp = 0b01110000, // 7
subscribe = 0b10000000, // 8
suback = 0b10010000, // 9
unsubscribe = 0b10100000, // 10
unsuback = 0b10110000, // 11
pingreq = 0b11000000, // 12
pingresp = 0b11010000, // 13
disconnect = 0b11100000, // 14
auth = 0b11110000, // 15
};
constexpr struct with_pid_ {} with_pid {};
constexpr struct no_pid_ {} no_pid {};
template <typename Allocator>
class control_packet {
uint16_t _packet_id;
using alloc_type = Allocator;
using deleter = boost::alloc_deleter<std::string, alloc_type>;
std::unique_ptr<std::string, deleter> _packet;
control_packet(
const Allocator& a,
uint16_t packet_id, std::string packet
) noexcept :
_packet_id(packet_id),
_packet(boost::allocate_unique<std::string>(a, std::move(packet)))
{}
public:
control_packet(control_packet&&) noexcept = default;
control_packet(const control_packet&) = delete;
control_packet& operator=(control_packet&&) noexcept = default;
control_packet& operator=(const control_packet&) = delete;
template <
typename EncodeFun,
typename ...Args
>
static control_packet of(
with_pid_, const Allocator& alloc,
EncodeFun&& encode, uint16_t packet_id, Args&&... args
) {
return control_packet {
alloc, packet_id, encode(packet_id, std::forward<Args>(args)...)
};
}
template <
typename EncodeFun,
typename ...Args
>
static control_packet of(
no_pid_, const Allocator& alloc,
EncodeFun&& encode, Args&&... args
) {
return control_packet {
alloc, uint16_t(0), encode(std::forward<Args>(args)...)
};
}
size_t size() const {
return _packet->size();
}
control_code_e control_code() const {
return control_code_e(uint8_t(*(_packet->data())) & 0b11110000);
}
uint16_t packet_id() const {
return _packet_id;
}
qos_e qos() const {
assert(control_code() == control_code_e::publish);
auto byte = (uint8_t(*(_packet->data())) & 0b00000110) >> 1;
return qos_e(byte);
}
control_packet& set_dup() {
assert(control_code() == control_code_e::publish);
auto& byte = *(_packet->data());
byte |= 0b00001000;
return *this;
}
std::string_view wire_data() const {
return *_packet;
}
};
class packet_id_allocator {
struct interval {
uint16_t start, end;
interval(uint16_t start, uint16_t end) :
start(start), end(end)
{}
};
std::vector<interval> _free_ids;
static constexpr uint16_t MAX_PACKET_ID = 65535;
public:
packet_id_allocator() {
_free_ids.emplace_back(MAX_PACKET_ID, uint16_t(0));
}
packet_id_allocator(packet_id_allocator&&) noexcept = default;
packet_id_allocator(const packet_id_allocator&) = delete;
packet_id_allocator& operator=(packet_id_allocator&&) noexcept = default;
packet_id_allocator& operator=(const packet_id_allocator&) = delete;
uint16_t allocate() {
if (_free_ids.empty()) return 0;
auto& last = _free_ids.back();
if (last.start == ++last.end) {
auto ret = last.end;
_free_ids.pop_back();
return ret;
}
return last.end;
}
void free(uint16_t pid) {
auto it = std::upper_bound(
_free_ids.begin(), _free_ids.end(), pid,
[](const uint16_t x, const interval& i) { return x > i.start; }
);
uint16_t* end_p = nullptr;
if (it != _free_ids.begin()) {
auto pit = std::prev(it);
if (pit->end == pid)
end_p = &pit->end;
}
if (it != _free_ids.end() && pid - 1 == it->start) {
if (!end_p)
it->start = pid;
else {
*end_p = it->end;
_free_ids.erase(it);
}
}
else {
if (!end_p)
_free_ids.insert(it, interval(pid, pid - 1));
else
*end_p = pid - 1;
}
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_CONTROL_PACKET_HPP

View File

@ -1,117 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_INTERNAL_TYPES_HPP
#define ASYNC_MQTT5_INTERNAL_TYPES_HPP
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include <async_mqtt5/detail/any_authenticator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5::detail {
using byte_citer = std::string::const_iterator;
using time_stamp = std::chrono::time_point<std::chrono::steady_clock>;
using duration = time_stamp::duration;
struct credentials {
std::string client_id;
std::optional<std::string> username;
std::optional<std::string> password;
credentials() = default;
credentials(
std::string client_id,
std::string username, std::string password
) :
client_id(std::move(client_id))
{
if (!username.empty())
this->username = std::move(username);
if (!password.empty())
this->password = std::move(password);
}
};
class session_state {
uint8_t _flags = 0b00;
static constexpr uint8_t session_present_flag = 0b01;
static constexpr uint8_t subscriptions_present_flag = 0b10;
public:
void session_present(bool present) {
return update_flag(present, session_present_flag);
}
bool session_present() const {
return _flags & session_present_flag;
}
void subscriptions_present(bool present) {
return update_flag(present, subscriptions_present_flag);
}
bool subscriptions_present() const {
return _flags & subscriptions_present_flag;
}
private:
void update_flag(bool set, uint8_t flag) {
if (set)
_flags |= flag;
else
_flags &= ~flag;
}
};
struct mqtt_ctx {
credentials creds;
std::optional<will> will_msg;
uint16_t keep_alive = 60;
connect_props co_props;
connack_props ca_props;
session_state state;
any_authenticator authenticator;
mqtt_ctx() = default;
mqtt_ctx(const mqtt_ctx& other) :
creds(other.creds), will_msg(other.will_msg),
keep_alive(other.keep_alive), co_props(other.co_props),
ca_props {}, state {},
authenticator(other.authenticator)
{}
};
struct disconnect_ctx {
disconnect_rc_e reason_code = disconnect_rc_e::normal_disconnection;
disconnect_props props = {};
bool terminal = false;
};
using serial_num_t = uint32_t;
constexpr serial_num_t no_serial = 0;
namespace send_flag {
constexpr unsigned none = 0b000;
constexpr unsigned throttled = 0b001;
constexpr unsigned prioritized = 0b010;
constexpr unsigned terminal = 0b100;
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_INTERNAL_TYPES_HPP

View File

@ -1,46 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_REBIND_EXECUTOR_HPP
#define ASYNC_MQTT5_REBIND_EXECUTOR_HPP
#include <boost/beast/websocket/stream.hpp>
namespace boost::asio::ssl {
// forward declare to preserve optional OpenSSL dependency
template <typename Stream>
class stream;
} // end namespace boost::asio::ssl
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename Stream, typename Executor>
struct rebind_executor {
using other = typename Stream::template rebind_executor<Executor>::other;
};
// asio::ssl::stream does not define a rebind_executor member type
template <typename Stream, typename Executor>
struct rebind_executor<asio::ssl::stream<Stream>, Executor> {
using other = typename asio::ssl::stream<typename rebind_executor<Stream, Executor>::other>;
};
template <typename Stream, typename Executor>
struct rebind_executor<boost::beast::websocket::stream<asio::ssl::stream<Stream>>, Executor> {
using other = typename boost::beast::websocket::stream<
asio::ssl::stream<typename rebind_executor<Stream, Executor>::other>,
boost::beast::websocket::stream<asio::ssl::stream<Stream>>::is_deflate_supported::value
>;
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_REBIND_EXECUTOR_HPP

View File

@ -1,65 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_SPINLOCK_HPP
#define ASYNC_MQTT5_SPINLOCK_HPP
#include <atomic>
namespace async_mqtt5::detail {
#if defined(_MSC_VER)
/* prefer using intrinsics directly instead of winnt.h macro */
/* http://software.intel.com/en-us/forums/topic/296168 */
//#include <intrin.h>
#if defined(_M_AMD64) || defined(_M_IX86)
#pragma intrinsic(_mm_pause)
#define __pause() _mm_pause()
/* (if pause not supported by older x86 assembler, "rep nop" is equivalent)*/
/*#define __pause() __asm rep nop */
#elif defined(_M_IA64)
#pragma intrinsic(__yield)
#define __pause() __yield()
#else
#define __pause() YieldProcessor()
#endif
#elif defined(__x86_64__) || defined(__i386__)
#define __pause() __asm__ __volatile__ ("pause")
#elif defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
#define __pause() __asm__ __volatile__ ("yield")
#endif
// https://rigtorp.se/spinlock/
class spinlock {
std::atomic<bool> lock_ { false };
public:
void lock() noexcept {
for (;;) {
// Optimistically assume the lock is free on the first try
if (!lock_.exchange(true, std::memory_order_acquire))
return;
// Wait for lock to be released without generating cache misses
while (lock_.load(std::memory_order_relaxed)) __pause();
}
}
bool try_lock() noexcept {
// First do a relaxed load to check if lock is free in order to prevent
// unnecessary cache misses if someone does while(!try_lock())
return !lock_.load(std::memory_order_relaxed) &&
!lock_.exchange(true, std::memory_order_acquire);
}
void unlock() noexcept {
lock_.store(false, std::memory_order_release);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_SPINLOCK_HPP

View File

@ -1,119 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_TOPIC_VALIDATION_HPP
#define ASYNC_MQTT5_TOPIC_VALIDATION_HPP
#include <cstdint>
#include <string_view>
#include <async_mqtt5/detail/utf8_mqtt.hpp>
namespace async_mqtt5::detail {
static constexpr uint32_t min_subscription_identifier = 1;
static constexpr uint32_t max_subscription_identifier = 268'435'455;
static constexpr std::string_view shared_sub_prefix = "$share/";
inline bool is_utf8_no_wildcard(validation_result result) {
return result == validation_result::valid;
}
inline bool is_not_empty(size_t sz) {
return sz != 0;
}
inline bool is_valid_topic_size(size_t sz) {
return is_not_empty(sz) && is_valid_string_size(sz);
}
inline validation_result validate_topic_name(std::string_view str) {
return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard);
}
inline validation_result validate_topic_alias_name(std::string_view str) {
return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard);
}
inline validation_result validate_shared_topic_name(std::string_view str) {
return validate_impl(str, is_not_empty, is_utf8_no_wildcard);
}
inline validation_result validate_topic_filter(std::string_view str) {
if (!is_valid_topic_size(str.size()))
return validation_result::invalid;
constexpr int multi_lvl_wildcard = '#';
constexpr int single_lvl_wildcard = '+';
// must be the last character preceded by '/' or stand alone
// #, .../#
if (str.back() == multi_lvl_wildcard) {
str.remove_suffix(1);
if (!str.empty() && str.back() != '/')
return validation_result::invalid;
}
int last_c = -1;
validation_result result;
while (!str.empty()) {
int c = pop_front_unichar(str);
// can be used at any level, but must occupy an entire level
// +, +/..., .../+/..., .../+
bool is_valid_single_lvl = (c == single_lvl_wildcard) &&
(str.empty() || str.front() == '/') &&
(last_c == -1 || last_c == '/');
result = validate_mqtt_utf8_char(c);
if (
result == validation_result::valid ||
is_valid_single_lvl
) {
last_c = c;
continue;
}
return validation_result::invalid;
}
return validation_result::valid;
}
inline validation_result validate_shared_topic_filter(
std::string_view str, bool wildcard_allowed = true
) {
if (!is_valid_topic_size(str.size()))
return validation_result::invalid;
if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0)
return validation_result::invalid;
str.remove_prefix(shared_sub_prefix.size());
size_t share_name_end = str.find_first_of('/');
if (share_name_end == std::string::npos)
return validation_result::invalid;
validation_result result;
result = validate_shared_topic_name(str.substr(0, share_name_end));
if (result != validation_result::valid)
return validation_result::invalid;
auto topic_filter = str.substr(share_name_end + 1);
return wildcard_allowed ?
validate_topic_filter(topic_filter) :
validate_topic_name(topic_filter)
;
}
} // end namespace async_mqtt5::detail
#endif //ASYNC_MQTT5_TOPIC_VALIDATION_HPP

View File

@ -1,116 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_UTF8_MQTT_HPP
#define ASYNC_MQTT5_UTF8_MQTT_HPP
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
namespace async_mqtt5::detail {
enum class validation_result : uint8_t {
valid = 0,
has_wildcard_character,
invalid
};
inline int pop_front_unichar(std::string_view& s) {
// assuming that s.length() is > 0
int n = s[0] & 0xF0;
int ch = -1;
if ((n & 0x80) == 0) {
ch = s[0];
s.remove_prefix(1);
}
else if ((n == 0xC0 || n == 0xD0) && s.size() > 1) {
ch = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F);
s.remove_prefix(2);
}
else if ((n == 0xE0) && s.size() > 2) {
ch = ((s[0] & 0x1F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F);
s.remove_prefix(3);
}
else if ((n == 0xF0) && s.size() > 3) {
ch = ((s[0] & 0x1F) << 18) | ((s[1] & 0x3F) << 12) |
((s[2] & 0x3F) << 6) | (s[3] & 0x3F);
s.remove_prefix(4);
}
return ch;
}
inline validation_result validate_mqtt_utf8_char(int c) {
constexpr int fe_flag = 0xFE;
constexpr int ff_flag = 0xFF;
constexpr int multi_lvl_wildcard = '#';
constexpr int single_lvl_wildcard = '+';
if (c == multi_lvl_wildcard || c == single_lvl_wildcard)
return validation_result::has_wildcard_character;
if (c > 0x001F && // U+0000...U+001F control characters
(c < 0x007F || c > 0x009F) && // U+007F...0+009F control characters
(c < 0xD800 || c > 0xDFFF) && // U+D800...U+DFFF surrogates
(c < 0xFDD0 || c > 0xFDEF) && // U+FDD0...U+FDEF non-characters
(c & fe_flag) != fe_flag && // non-characters
(c & ff_flag) != ff_flag
)
return validation_result::valid;
return validation_result::invalid;
}
inline bool is_valid_string_size(size_t sz) {
constexpr size_t max_sz = 65535;
return sz <= max_sz;
}
inline bool is_utf8(validation_result result) {
return result == validation_result::valid ||
result == validation_result::has_wildcard_character;
}
template <typename ValidSizeCondition, typename ValidCondition>
validation_result validate_impl(
std::string_view str,
ValidSizeCondition&& size_condition, ValidCondition&& condition
) {
if (!size_condition(str.size()))
return validation_result::invalid;
validation_result result;
while (!str.empty()) {
int c = pop_front_unichar(str);
result = validate_mqtt_utf8_char(c);
if (!condition(result))
return result;
}
return validation_result::valid;
}
inline validation_result validate_mqtt_utf8(std::string_view str) {
return validate_impl(str, is_valid_string_size, is_utf8);
}
inline bool is_valid_string_pair(
const std::pair<std::string, std::string>& str_pair
) {
return validate_mqtt_utf8(str_pair.first) == validation_result::valid &&
validate_mqtt_utf8(str_pair.second) == validation_result::valid;
}
} // namespace async_mqtt5::detail
#endif //ASYNC_MQTT5_UTF8_MQTT_HPP

View File

@ -1,168 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ERROR_HPP
#define ASYNC_MQTT5_ERROR_HPP
#include <cstdint>
#include <ostream>
#include <string>
#include <boost/asio/error.hpp>
namespace async_mqtt5 {
/**
* \brief A representation of Disconnect Reason Code.
*
* \details Represents all Reason Codes that the Client can send to the Server
* in the \__DISCONNECT\__ packet as the reason for the disconnection.
*/
enum class disconnect_rc_e : uint8_t {
/** Close the connection normally. Do not send the Will Message. */
normal_disconnection = 0x00,
/** The Client wishes to disconnect but requires that
the Server also publishes its Will Message. */
disconnect_with_will_message = 0x04
};
namespace detail {
enum class disconnect_rc_e : uint8_t {
normal_disconnection = 0x00,
disconnect_with_will_message = 0x04,
unspecified_error = 0x80,
malformed_packet = 0x81,
protocol_error = 0x82,
implementation_specific_error = 0x83,
topic_name_invalid = 0x90,
receive_maximum_exceeded = 0x93,
topic_alias_invalid = 0x94,
packet_too_large = 0x95,
message_rate_too_high = 0x96,
quota_exceeded = 0x97,
administrative_action = 0x98,
payload_format_invalid = 0x99
};
}
namespace client {
/**
* \brief Defines error codes related to MQTT client.
*
* \details Encapsulates errors that occur on the client side.
*/
enum class error : int {
/** The packet is malformed */
malformed_packet = 100,
/** The packet has exceeded the Maximum Packet Size the Server is willing to accept */
packet_too_large,
/** The Client's session does not exist or it has expired */
session_expired,
/** There are no more available Packet Identifiers to use */
pid_overrun,
/** The Topic is invalid and does not conform to the specification */
invalid_topic,
// publish
/** The Server does not support the specified \ref qos_e */
qos_not_supported,
/** The Server does not support retained messages */
retain_not_available,
/** The Client attempted to send a Topic Alias that is greater than Topic Alias Maximum */
topic_alias_maximum_reached,
// subscribe
/** The Server does not support Wildcard Subscriptions */
wildcard_subscription_not_available,
/** The Server does not support this Subscription Identifier */
subscription_identifier_not_available,
/** The Server does not support Shared Subscriptions */
shared_subscription_not_available
};
inline std::string client_error_to_string(error err) {
switch (err) {
case error::malformed_packet:
return "The packet is malformed";
case error::packet_too_large:
return "The packet has exceeded the Maximum Packet Size "
"the Server is willing to accept";
case error::session_expired:
return "The Client's session does not exist or it has expired";
case error::pid_overrun:
return "There are no more available Packet Identifiers to use";
case error::invalid_topic:
return "The Topic is invalid and "
"does not conform to the specification";
case error::qos_not_supported:
return "The Server does not support the specified QoS";
case error::retain_not_available:
return "The Server does not support retained messages";
case error::topic_alias_maximum_reached:
return "The Client attempted to send a Topic Alias "
"that is greater than Topic Alias Maximum";
case error::wildcard_subscription_not_available:
return "The Server does not support Wildcard Subscriptions";
case error::subscription_identifier_not_available:
return "The Server does not support this Subscription Identifier";
case error::shared_subscription_not_available:
return "The Server does not support Shared Subscriptions";
default:
return "Unknown client error";
}
}
struct client_ec_category : public boost::system::error_category {
const char* name() const noexcept override { return "mqtt_client_error"; }
std::string message(int ev) const noexcept override {
return client_error_to_string(static_cast<error>(ev));
}
};
/// Returns the error category associated with \ref client::error.
inline const client_ec_category& get_error_code_category() {
static client_ec_category cat;
return cat;
}
/// Creates an \ref error_code from a \ref client::error.
inline boost::system::error_code make_error_code(error r) {
return { static_cast<int>(r), get_error_code_category() };
}
inline std::ostream& operator<<(std::ostream& os, const error& err) {
os << get_error_code_category().name() << ":" << static_cast<int>(err);
return os;
}
} // end namespace client
} // end namespace async_mqtt5
namespace boost::system {
template <>
struct is_error_code_enum <async_mqtt5::client::error> : std::true_type {};
} // end namespace boost::system
#endif // !ASYNC_MQTT5_ERROR_HPP

View File

@ -1,242 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASSEMBLE_OP_HPP
#define ASYNC_MQTT5_ASSEMBLE_OP_HPP
#include <chrono>
#include <cstdint>
#include <string>
#include <utility>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
class data_span : private std::pair<byte_citer, byte_citer> {
using base = std::pair<byte_citer, byte_citer>;
public:
using base::base;
auto first() const {
return base::first;
}
auto last() const {
return base::second;
}
void expand_suffix(size_t num_chars) {
base::second += num_chars;
}
void remove_prefix(size_t num_chars) {
base::first += num_chars;
}
size_t size() const {
return std::distance(base::first, base::second);
}
};
template <typename ClientService, typename Handler>
class assemble_op {
using client_service = ClientService;
using handler_type = Handler;
struct on_read {};
static constexpr uint32_t max_recv_size = 65'536;
client_service& _svc;
handler_type _handler;
std::string& _read_buff;
data_span& _data_span;
public:
assemble_op(
client_service& svc, handler_type&& handler,
std::string& read_buff, data_span& active_span
) :
_svc(svc),
_handler(std::move(handler)),
_read_buff(read_buff), _data_span(active_span)
{}
assemble_op(assemble_op&&) noexcept = default;
assemble_op(const assemble_op&) = delete;
assemble_op& operator=(assemble_op&&) noexcept = default;
assemble_op& operator=(const assemble_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = typename client_service::executor_type;
executor_type get_executor() const noexcept {
return _svc.get_executor();
}
template <typename CompletionCondition>
void perform(CompletionCondition cc) {
_read_buff.erase(
_read_buff.cbegin(), _data_span.first()
);
_read_buff.resize(
_svc.connect_property(prop::maximum_packet_size).value_or(max_recv_size)
);
_data_span = {
_read_buff.cbegin(),
_read_buff.cbegin() + _data_span.size()
};
if (cc(error_code {}, 0) == 0 && _data_span.size()) {
return asio::post(
asio::prepend(
std::move(*this), on_read {}, error_code {},
0, std::move(cc)
)
);
}
// Must be evaluated before this is moved
auto store_begin = _read_buff.data() + _data_span.size();
auto store_size = std::distance(_data_span.last(), _read_buff.cend());
_svc._stream.async_read_some(
asio::buffer(store_begin, store_size), compute_read_timeout(),
asio::prepend(
asio::append(std::move(*this), std::move(cc)),
on_read {}
)
);
}
template <typename CompletionCondition>
void operator()(
on_read, error_code ec, size_t bytes_read,
CompletionCondition cc
) {
if (ec == asio::error::try_again) {
_svc.update_session_state();
_svc._async_sender.resend();
_data_span = { _read_buff.cend(), _read_buff.cend() };
return perform(std::move(cc));
}
if (ec)
return complete(ec, 0, {}, {});
_data_span.expand_suffix(bytes_read);
assert(_data_span.size());
auto control_byte = uint8_t(*_data_span.first());
if ((control_byte & 0b11110000) == 0)
// close the connection, cancel
return complete(client::error::malformed_packet, 0, {}, {});
auto first = _data_span.first() + 1;
auto varlen = decoders::type_parse(
first, _data_span.last(), decoders::basic::varint_
);
if (!varlen) {
if (_data_span.size() < 5)
return perform(asio::transfer_at_least(1));
return complete(client::error::malformed_packet, 0, {}, {});
}
auto recv_size = _svc.connect_property(prop::maximum_packet_size)
.value_or(max_recv_size);
if (static_cast<uint32_t>(*varlen) > recv_size - std::distance(_data_span.first(), first))
return complete(client::error::malformed_packet, 0, {}, {});
if (std::distance(first, _data_span.last()) < *varlen)
return perform(asio::transfer_at_least(1));
_data_span.remove_prefix(
std::distance(_data_span.first(), first) + *varlen
);
dispatch(control_byte, first, first + *varlen);
}
private:
duration compute_read_timeout() const {
auto negotiated_ka = _svc.negotiated_keep_alive();
return negotiated_ka ?
std::chrono::milliseconds(3 * negotiated_ka * 1000 / 2) :
duration(std::numeric_limits<duration::rep>::max());
}
static bool valid_header(uint8_t control_byte) {
auto code = control_code_e(control_byte & 0b11110000);
if (code == control_code_e::publish)
return true;
auto res = control_byte & 0b00001111;
if (code == control_code_e::pubrel)
return res == 0b00000010;
return res == 0b00000000;
}
void dispatch(
uint8_t control_byte, byte_citer first, byte_citer last
) {
using namespace decoders;
if (!valid_header(control_byte))
return complete(client::error::malformed_packet, 0, {}, {});
auto code = control_code_e(control_byte & 0b11110000);
if (code == control_code_e::pingresp)
return perform(asio::transfer_at_least(0));
bool is_reply = code != control_code_e::publish &&
code != control_code_e::auth &&
code != control_code_e::disconnect;
if (is_reply) {
auto packet_id = decoders::decode_packet_id(first).value();
_svc._replies.dispatch(error_code {}, code, packet_id, first, last);
return perform(asio::transfer_at_least(0));
}
complete(error_code {}, control_byte, first, last);
}
void complete(
error_code ec, uint8_t control_code,
byte_citer first, byte_citer last
) {
if (ec)
_data_span = { _read_buff.cend(), _read_buff.cend() };
std::move(_handler)(ec, control_code, first, last);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ASSEMBLE_OP_HPP

View File

@ -1,314 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ASYNC_SENDER_HPP
#define ASYNC_MQTT5_ASYNC_SENDER_HPP
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/bind_allocator.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
class write_req {
static constexpr unsigned SERIAL_BITS = sizeof(serial_num_t) * 8;
asio::const_buffer _buffer;
serial_num_t _serial_num;
unsigned _flags;
using handler_type = asio::any_completion_handler<void (error_code)>;
handler_type _handler;
public:
write_req(
asio::const_buffer buffer,
serial_num_t serial_num, unsigned flags,
handler_type handler
) :
_buffer(buffer), _serial_num(serial_num), _flags(flags),
_handler(std::move(handler))
{}
write_req(write_req&&) = default;
write_req(const write_req&) = delete;
write_req& operator=(write_req&&) = default;
write_req& operator=(const write_req&) = delete;
static serial_num_t next_serial_num(serial_num_t last) {
return last + 1;
}
asio::const_buffer buffer() const {
return _buffer;
}
void complete(error_code ec) {
std::move(_handler)(ec);
}
void complete_post(const asio::any_io_executor& ex, error_code ec) {
asio::post(
ex,
asio::prepend(std::move(_handler), ec)
);
}
bool empty() const {
return !_handler;
}
auto get_executor() {
return asio::get_associated_executor(_handler);
}
auto get_allocator() {
return asio::get_associated_allocator(_handler);
}
bool throttled() const {
return _flags & send_flag::throttled;
}
bool terminal() const {
return _flags & send_flag::terminal;
}
bool operator<(const write_req& other) const {
if (prioritized() != other.prioritized())
return prioritized();
auto s1 = _serial_num;
auto s2 = other._serial_num;
if (s1 < s2)
return (s2 - s1) < (1 << (SERIAL_BITS - 1));
return (s1 - s2) >= (1 << (SERIAL_BITS - 1));
}
private:
bool prioritized() const {
return _flags & send_flag::prioritized;
}
};
template <typename ClientService>
class async_sender {
using self_type = async_sender<ClientService>;
using client_service = ClientService;
using queue_allocator_type = asio::recycling_allocator<write_req>;
using write_queue_t = std::vector<write_req, queue_allocator_type>;
ClientService& _svc;
write_queue_t _write_queue;
bool _write_in_progress { false };
static constexpr uint16_t MAX_LIMIT = 65535;
uint16_t _limit { MAX_LIMIT };
uint16_t _quota { MAX_LIMIT };
serial_num_t _last_serial_num { 0 };
public:
explicit async_sender(ClientService& svc) : _svc(svc) {}
async_sender(async_sender&&) = default;
async_sender(const async_sender&) = delete;
async_sender& operator=(async_sender&&) = default;
async_sender& operator=(const async_sender&) = delete;
using allocator_type = queue_allocator_type;
allocator_type get_allocator() const noexcept {
return allocator_type {};
}
serial_num_t next_serial_num() {
return _last_serial_num = write_req::next_serial_num(_last_serial_num);
}
template <typename CompletionToken, typename BufferType>
decltype(auto) async_send(
const BufferType& buffer,
serial_num_t serial_num, unsigned flags,
CompletionToken&& token
) {
using Signature = void (error_code);
auto initiation = [](
auto handler, self_type& self, const BufferType& buffer,
serial_num_t serial_num, unsigned flags
) {
self._write_queue.emplace_back(
asio::buffer(buffer), serial_num, flags, std::move(handler)
);
self.do_write();
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this),
buffer, serial_num, flags
);
}
void cancel() {
auto ops = std::move(_write_queue);
for (auto& op : ops)
op.complete_post(_svc.get_executor(), asio::error::operation_aborted);
}
void resend() {
if (_write_in_progress)
return;
// The _write_in_progress flag is set to true to prevent any write
// operations executing before the _write_queue is filled with
// all the packets that require resending.
_write_in_progress = true;
auto new_limit = _svc._stream_context.connack_property(prop::receive_maximum);
_limit = new_limit.value_or(MAX_LIMIT);
_quota = _limit;
auto write_queue = std::move(_write_queue);
_svc._replies.resend_unanswered();
for (auto& op : write_queue)
op.complete(asio::error::try_again);
std::stable_sort(_write_queue.begin(), _write_queue.end());
_write_in_progress = false;
do_write();
}
void operator()(write_queue_t write_queue, error_code ec, size_t) {
_write_in_progress = false;
if (ec == asio::error::try_again) {
_svc.update_session_state();
_write_queue.insert(
_write_queue.begin(),
std::make_move_iterator(write_queue.begin()),
std::make_move_iterator(write_queue.end())
);
return resend();
}
if (ec == asio::error::no_recovery)
_svc.cancel();
// errors, if any, are propagated to ops
for (auto& op : write_queue)
op.complete(ec);
if (
ec == asio::error::operation_aborted ||
ec == asio::error::no_recovery
)
return;
do_write();
}
void throttled_op_done() {
if (_limit == MAX_LIMIT)
return;
++_quota;
do_write();
}
private:
void do_write() {
if (_write_in_progress || _write_queue.empty())
return;
_write_in_progress = true;
write_queue_t write_queue;
auto terminal_req = std::find_if(
_write_queue.begin(), _write_queue.end(),
[](const auto& op) { return op.terminal(); }
);
if (terminal_req != _write_queue.end()) {
write_queue.push_back(std::move(*terminal_req));
_write_queue.erase(terminal_req);
}
else if (_limit == MAX_LIMIT) {
write_queue = std::move(_write_queue);
}
else {
for (write_req& req : _write_queue)
if (!req.throttled())
write_queue.push_back(std::move(req));
else if (_quota > 0) {
--_quota;
write_queue.push_back(std::move(req));
}
if (write_queue.empty()) {
_write_in_progress = false;
return;
}
auto it = std::remove_if(
_write_queue.begin(), _write_queue.end(),
[](const write_req& req) { return req.empty(); }
);
_write_queue.erase(it, _write_queue.end());
}
std::vector<asio::const_buffer> buffers;
buffers.reserve(write_queue.size());
for (const auto& op : write_queue)
buffers.push_back(op.buffer());
_svc._replies.clear_fast_replies();
auto alloc = write_queue.front().get_allocator();
auto ex = write_queue.front().get_executor();
_svc._stream.async_write(
buffers,
asio::bind_allocator(
alloc,
asio::bind_executor(
ex,
asio::prepend(std::ref(*this), std::move(write_queue))
)
)
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ASYNC_SENDER_HPP

View File

@ -1,227 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
#define ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <boost/asio/async_result.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <async_mqtt5/detail/async_mutex.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/impl/endpoints.hpp>
#include <async_mqtt5/impl/read_op.hpp>
#include <async_mqtt5/impl/reconnect_op.hpp>
#include <async_mqtt5/impl/write_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using error_code = boost::system::error_code;
template <
typename StreamType,
typename StreamContext = std::monostate
>
class autoconnect_stream {
public:
using self_type = autoconnect_stream<StreamType, StreamContext>;
using stream_type = StreamType;
using stream_context_type = StreamContext;
using executor_type = typename stream_type::executor_type;
private:
using stream_ptr = std::shared_ptr<stream_type>;
executor_type _stream_executor;
async_mutex _conn_mtx;
asio::steady_timer _read_timer, _connect_timer;
endpoints _endpoints;
stream_ptr _stream_ptr;
stream_context_type& _stream_context;
template <typename Owner, typename Handler>
friend class read_op;
template <typename Owner, typename Handler>
friend class write_op;
template <typename Stream>
friend class reconnect_op;
template <typename Owner, typename DisconnectContext>
friend class disconnect_op;
public:
autoconnect_stream(
const executor_type& ex, stream_context_type& context
) :
_stream_executor(ex),
_conn_mtx(_stream_executor),
_read_timer(_stream_executor), _connect_timer(_stream_executor),
_endpoints(_stream_executor, _connect_timer),
_stream_context(context)
{
replace_next_layer(construct_next_layer());
}
autoconnect_stream(const autoconnect_stream&) = delete;
autoconnect_stream& operator=(const autoconnect_stream&) = delete;
using next_layer_type = stream_type;
next_layer_type& next_layer() {
return *_stream_ptr;
}
const next_layer_type& next_layer() const {
return *_stream_ptr;
}
executor_type get_executor() const noexcept {
return _stream_executor;
}
void brokers(std::string hosts, uint16_t default_port) {
_endpoints.brokers(std::move(hosts), default_port);
}
void clone_endpoints(const autoconnect_stream& other) {
_endpoints.clone_servers(other._endpoints);
}
bool is_open() const noexcept {
return lowest_layer(*_stream_ptr).is_open();
}
void open() {
open_lowest_layer(_stream_ptr);
}
void cancel() {
error_code ec;
lowest_layer(*_stream_ptr).cancel(ec);
_conn_mtx.cancel();
_connect_timer.cancel();
}
void close() {
error_code ec;
shutdown(asio::ip::tcp::socket::shutdown_both);
lowest_layer(*_stream_ptr).close(ec);
}
void shutdown(asio::ip::tcp::socket::shutdown_type what) {
error_code ec;
lowest_layer(*_stream_ptr).shutdown(what, ec);
}
bool was_connected() const {
error_code ec;
lowest_layer(*_stream_ptr).remote_endpoint(ec);
return ec == boost::system::errc::success;
}
template <typename BufferType, typename CompletionToken>
decltype(auto) async_read_some(
const BufferType& buffer, duration wait_for, CompletionToken&& token
) {
using Signature = void (error_code, size_t);
auto initiation = [](
auto handler, self_type& self,
const BufferType& buffer, duration wait_for
) {
read_op { self, std::move(handler) }.perform(buffer, wait_for);
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this), buffer, wait_for
);
}
template <typename BufferType, typename CompletionToken>
decltype(auto) async_write(
const BufferType& buffer, CompletionToken&& token
) {
using Signature = void (error_code, size_t);
auto initiation = [](
auto handler, self_type& self, const BufferType& buffer
) {
write_op { self, std::move(handler) }.perform(buffer);
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this), buffer
);
}
private:
static void open_lowest_layer(stream_ptr sptr) {
error_code ec;
auto& layer = lowest_layer(*sptr);
layer.open(asio::ip::tcp::v4(), ec);
layer.set_option(asio::socket_base::reuse_address(true), ec);
layer.set_option(asio::ip::tcp::no_delay(true), ec);
}
stream_ptr construct_next_layer() const {
stream_ptr sptr;
if constexpr (has_tls_context<StreamContext>)
sptr = std::make_shared<stream_type>(
_stream_executor, _stream_context.tls_context()
);
else
sptr = std::make_shared<stream_type>(_stream_executor);
return sptr;
}
stream_ptr construct_and_open_next_layer() const {
auto sptr = construct_next_layer();
open_lowest_layer(sptr);
return sptr;
}
void replace_next_layer(stream_ptr sptr) {
// close() will cancel all outstanding async operations on
// _stream_ptr; cancelling posts operation_aborted to handlers
// but handlers will be executed after std::exchange below;
// handlers should therefore treat (operation_aborted && is_open())
// equivalent to try_again.
if (_stream_ptr)
close();
std::exchange(_stream_ptr, std::move(sptr));
}
template <typename CompletionToken>
decltype(auto) async_reconnect(stream_ptr s, CompletionToken&& token) {
using Signature = void (error_code);
auto initiation = [](auto handler, self_type& self, stream_ptr s) {
reconnect_op { self, std::move(handler) }.perform(s);
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this), s
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_AUTOCONNECT_STREAM_HPP

View File

@ -1,562 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CLIENT_SERVICE_HPP
#define ASYNC_MQTT5_CLIENT_SERVICE_HPP
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <boost/asio/async_result.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/experimental/basic_channel.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/channel_traits.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/assemble_op.hpp>
#include <async_mqtt5/impl/async_sender.hpp>
#include <async_mqtt5/impl/autoconnect_stream.hpp>
#include <async_mqtt5/impl/ping_op.hpp>
#include <async_mqtt5/impl/read_message_op.hpp>
#include <async_mqtt5/impl/replies.hpp>
#include <async_mqtt5/impl/sentry_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <
typename StreamType, typename TlsContext,
typename Enable = void
>
class stream_context;
template <
typename StreamType, typename TlsContext
>
class stream_context<
StreamType, TlsContext,
std::enable_if_t<has_tls_layer<StreamType>>
> {
using tls_context_type = TlsContext;
mqtt_ctx _mqtt_context;
std::shared_ptr<tls_context_type> _tls_context_ptr;
public:
explicit stream_context(TlsContext tls_context) :
_tls_context_ptr(std::make_shared<tls_context_type>(std::move(tls_context)))
{}
stream_context(const stream_context& other) :
_mqtt_context(other._mqtt_context), _tls_context_ptr(other._tls_context_ptr)
{}
auto& mqtt_context() {
return _mqtt_context;
}
const auto& mqtt_context() const {
return _mqtt_context;
}
auto& tls_context() {
return *_tls_context_ptr;
}
auto& session_state() {
return _mqtt_context.state;
}
const auto& session_state() const {
return _mqtt_context.state;
}
void will(will will) {
_mqtt_context.will_msg = std::move(will);
}
template <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.ca_props[prop];
}
const auto& connack_properties() const {
return _mqtt_context.ca_props;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.co_props[prop];
}
template <prop::property_type p>
auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) {
return _mqtt_context.co_props[prop];
}
void connect_properties(connect_props props) {
_mqtt_context.co_props = std::move(props);
}
void credentials(
std::string client_id,
std::string username = "", std::string password = ""
) {
_mqtt_context.creds = {
std::move(client_id),
std::move(username), std::move(password)
};
}
template <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <typename StreamType>
class stream_context<
StreamType, std::monostate,
std::enable_if_t<!has_tls_layer<StreamType>>
> {
mqtt_ctx _mqtt_context;
public:
explicit stream_context(std::monostate) {}
stream_context(const stream_context& other) :
_mqtt_context(other._mqtt_context)
{}
auto& mqtt_context() {
return _mqtt_context;
}
const auto& mqtt_context() const {
return _mqtt_context;
}
auto& session_state() {
return _mqtt_context.state;
}
const auto& session_state() const {
return _mqtt_context.state;
}
void will(will will) {
_mqtt_context.will_msg = std::move(will);
}
template <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.ca_props[prop];
}
const auto& connack_properties() const {
return _mqtt_context.ca_props;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _mqtt_context.co_props[prop];
}
template <prop::property_type p>
auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) {
return _mqtt_context.co_props[prop];
}
void connect_properties(connect_props props) {
_mqtt_context.co_props = std::move(props);
}
void credentials(
std::string client_id,
std::string username = "", std::string password = ""
) {
_mqtt_context.creds = {
std::move(client_id),
std::move(username), std::move(password)
};
}
template <typename Authenticator>
void authenticator(Authenticator&& authenticator) {
_mqtt_context.authenticator = any_authenticator(
std::forward<Authenticator>(authenticator)
);
}
};
template <
typename StreamType,
typename TlsContext = std::monostate
>
class client_service {
using self_type = client_service<StreamType, TlsContext>;
using stream_context_type = stream_context<StreamType, TlsContext>;
using stream_type = autoconnect_stream<
StreamType, stream_context_type
>;
public:
using executor_type = typename stream_type::executor_type;
private:
using tls_context_type = TlsContext;
using receive_channel = asio::experimental::basic_channel<
executor_type,
channel_traits<>,
void (error_code, std::string, std::string, publish_props)
>;
template <typename ClientService>
friend class async_sender;
template <typename ClientService, typename Handler>
friend class assemble_op;
template <typename ClientService, typename Executor>
friend class ping_op;
template <typename ClientService, typename Executor>
friend class sentry_op;
template <typename ClientService>
friend class re_auth_op;
executor_type _executor;
stream_context_type _stream_context;
stream_type _stream;
packet_id_allocator _pid_allocator;
replies _replies;
async_sender<client_service> _async_sender;
std::string _read_buff;
data_span _active_span;
receive_channel _rec_channel;
asio::cancellation_signal _cancel_ping;
asio::cancellation_signal _cancel_sentry;
asio::any_completion_handler<void(error_code)> _run_handler;
client_service(const client_service& other) :
_executor(other._executor), _stream_context(other._stream_context),
_stream(_executor, _stream_context),
_replies(_executor),
_async_sender(*this),
_active_span(_read_buff.cend(), _read_buff.cend()),
_rec_channel(_executor, std::numeric_limits<size_t>::max())
{
_stream.clone_endpoints(other._stream);
}
public:
explicit client_service(
const executor_type& ex,
tls_context_type tls_context = {}
) :
_executor(ex),
_stream_context(std::move(tls_context)),
_stream(ex, _stream_context),
_replies(ex),
_async_sender(*this),
_active_span(_read_buff.cend(), _read_buff.cend()),
_rec_channel(ex, std::numeric_limits<size_t>::max())
{}
executor_type get_executor() const noexcept {
return _executor;
}
auto dup() const {
return std::shared_ptr<client_service>(new client_service(*this));
}
template <
typename Ctx = TlsContext,
std::enable_if_t<!std::is_same_v<Ctx, std::monostate>, bool> = true
>
decltype(auto) tls_context() {
return _stream_context.tls_context();
}
void will(will will) {
if (!is_open())
_stream_context.will(std::move(will));
}
void credentials(
std::string client_id,
std::string username = "", std::string password = ""
) {
if (!is_open())
_stream_context.credentials(
std::move(client_id),
std::move(username), std::move(password)
);
}
void brokers(std::string hosts, uint16_t default_port) {
if (!is_open())
_stream.brokers(std::move(hosts), default_port);
}
template <
typename Authenticator,
std::enable_if_t<is_authenticator<Authenticator>, bool> = true
>
void authenticator(Authenticator&& authenticator) {
if (!is_open())
_stream_context.authenticator(
std::forward<Authenticator>(authenticator)
);
}
uint16_t negotiated_keep_alive() const {
return connack_property(prop::server_keep_alive)
.value_or(_stream_context.mqtt_context().keep_alive);
}
void keep_alive(uint16_t seconds) {
if (!is_open())
_stream_context.mqtt_context().keep_alive = seconds;
}
template <prop::property_type p>
const auto& connect_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _stream_context.connect_property(prop);
}
template <prop::property_type p>
void connect_property(
std::integral_constant<prop::property_type, p> prop,
prop::value_type_t<p> value
){
if (!is_open())
_stream_context.connect_property(prop) = value;
}
void connect_properties(connect_props props) {
if (!is_open())
_stream_context.connect_properties(std::move(props));
}
template <prop::property_type p>
const auto& connack_property(
std::integral_constant<prop::property_type, p> prop
) const {
return _stream_context.connack_property(prop);
}
const auto& connack_properties() const {
return _stream_context.connack_properties();
}
template <typename Handler>
void run(Handler&& handler) {
_run_handler = std::move(handler);
auto slot = asio::get_associated_cancellation_slot(_run_handler);
if (slot.is_connected()) {
using c_t = asio::cancellation_type_t;
slot.assign([&svc = *this](c_t c) {
if ((c & c_t::terminal) != c_t::none)
svc.cancel();
});
}
_stream.open();
_rec_channel.reset();
}
void open_stream() {
_stream.open();
}
bool is_open() const {
return _stream.is_open();
}
void close_stream() {
_stream.close();
}
void cancel() {
if (!_run_handler) return;
_cancel_ping.emit(asio::cancellation_type::terminal);
_cancel_sentry.emit(asio::cancellation_type::terminal);
_rec_channel.close();
_replies.cancel_unanswered();
_async_sender.cancel();
_stream.cancel();
_stream.close();
asio::get_associated_cancellation_slot(_run_handler).clear();
asio::post(
get_executor(),
asio::prepend(
std::move(_run_handler),
asio::error::operation_aborted
)
);
}
uint16_t allocate_pid() {
return _pid_allocator.allocate();
}
void free_pid(uint16_t pid, bool was_throttled = false) {
_pid_allocator.free(pid);
if (was_throttled)
_async_sender.throttled_op_done();
}
serial_num_t next_serial_num() {
return _async_sender.next_serial_num();
}
bool subscriptions_present() const {
return _stream_context.session_state().subscriptions_present();
}
void subscriptions_present(bool present) {
_stream_context.session_state().subscriptions_present(present);
}
void update_session_state() {
auto& session_state = _stream_context.session_state();
if (!session_state.session_present()) {
_replies.clear_pending_pubrels();
session_state.session_present(true);
if (session_state.subscriptions_present()) {
channel_store_error(client::error::session_expired);
session_state.subscriptions_present(false);
}
}
_cancel_ping.emit(asio::cancellation_type::total);
}
bool channel_store(decoders::publish_message message) {
auto& [topic, packet_id, flags, props, payload] = message;
return _rec_channel.try_send(
error_code {}, std::move(topic),
std::move(payload), std::move(props)
);
}
bool channel_store_error(error_code ec) {
return _rec_channel.try_send(
ec, std::string {}, std::string {}, publish_props {}
);
}
template <typename BufferType, typename CompletionToken>
decltype(auto) async_send(
const BufferType& buffer,
serial_num_t serial_num, unsigned flags,
CompletionToken&& token
) {
return _async_sender.async_send(
buffer, serial_num, flags, std::forward<CompletionToken>(token)
);
}
template <typename CompletionToken>
decltype(auto) async_assemble(CompletionToken&& token) {
using Signature = void (error_code, uint8_t, byte_citer, byte_citer);
auto initiation = [] (
auto handler, self_type& self,
std::string& read_buff, data_span& active_span
) {
assemble_op {
self, std::move(handler), read_buff, active_span
}.perform(asio::transfer_at_least(0));
};
return asio::async_initiate<CompletionToken, Signature> (
initiation, token, std::ref(*this),
std::ref(_read_buff), std::ref(_active_span)
);
}
template <typename CompletionToken>
decltype(auto) async_wait_reply(
control_code_e code, uint16_t packet_id, CompletionToken&& token
) {
return _replies.async_wait_reply(
code, packet_id, std::forward<CompletionToken>(token)
);
}
template <typename CompletionToken>
decltype(auto) async_channel_receive(CompletionToken&& token) {
return _rec_channel.async_receive(std::forward<CompletionToken>(token));
}
};
template <typename ClientService>
class initiate_async_run {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_run(std::shared_ptr<ClientService> svc_ptr) :
_svc_ptr(std::move(svc_ptr))
{}
using executor_type = typename ClientService::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
}
template <typename Handler>
void operator()(Handler&& handler) {
auto ex = asio::get_associated_executor(handler, get_executor());
_svc_ptr->run(std::move(handler));
detail::ping_op { _svc_ptr, ex }.perform();
detail::read_message_op { _svc_ptr, ex }.perform();
detail::sentry_op { _svc_ptr, ex }.perform();
}
};
} // namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_CLIENT_SERVICE_HPP

View File

@ -1,460 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_BASE_DECODERS_HPP
#define ASYNC_MQTT5_BASE_DECODERS_HPP
#include <cstdint>
#include <string>
#include <utility>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/binary/binary.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/container/deque.hpp>
#include <boost/optional/optional.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/impl/codecs/traits.hpp>
namespace async_mqtt5::decoders {
namespace x3 = boost::spirit::x3;
template <typename T>
struct convert { using type = T; };
template <typename ...Args>
struct convert<boost::fusion::deque<Args...>> {
using type = std::tuple<typename convert<Args>::type...>;
};
template <typename T>
struct convert<boost::optional<T>> {
using type = std::optional<T>;
};
template <typename T>
struct convert<std::vector<T>> {
using type = std::vector<typename convert<T>::type>;
};
template <typename T, typename Parser>
constexpr auto as(Parser&& p) {
return x3::rule<struct _, T>{} = std::forward<Parser>(p);
}
template <typename It, typename Parser>
auto type_parse(It& first, const It last, const Parser& p) {
using ctx_type = decltype(x3::make_context<struct _>(std::declval<Parser&>()));
using attr_type = typename x3::traits::attribute_of<Parser, ctx_type>::type;
using rv_type = typename convert<attr_type>::type;
std::optional<rv_type> rv;
rv_type value {};
if (x3::phrase_parse(first, last, as<rv_type>(p), x3::eps(false), value))
rv = std::move(value);
return rv;
}
template <typename AttributeType, typename It, typename Parser>
auto type_parse(It& first, const It last, const Parser& p) {
std::optional<AttributeType> rv;
AttributeType value {};
if (x3::phrase_parse(first, last, as<AttributeType>(p), x3::eps(false), value))
rv = std::move(value);
return rv;
}
namespace basic {
template <typename T>
constexpr auto to(T& arg) {
return [&](auto& ctx) {
using ctx_type = decltype(ctx);
using attr_type = decltype(x3::_attr(std::declval<const ctx_type&>()));
if constexpr (is_boost_iterator<attr_type>)
arg = T { x3::_attr(ctx).begin(), x3::_attr(ctx).end() };
else
arg = x3::_attr(ctx);
};
}
template <typename LenParser, typename Subject, typename Enable = void>
class scope_limit {};
template <typename LenParser, typename Subject>
class scope_limit<
LenParser, Subject,
std::enable_if_t<x3::traits::is_parser<LenParser>::value>
> :
public x3::unary_parser<Subject, scope_limit<LenParser, Subject>>
{
using base_type = x3::unary_parser<Subject, scope_limit<LenParser, Subject>>;
LenParser _lp;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using attribute_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
static bool const has_attribute = true;
scope_limit(const LenParser& lp, const Subject& subject) :
base_type(subject), _lp(lp)
{}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
typename x3::traits::attribute_of<LenParser, Ctx>::type len;
if (!_lp.parse(iter, last, ctx, rctx, len))
return false;
if (iter + len > last)
return false;
if (!base_type::subject.parse(iter, iter + len, ctx, rctx, attr))
return false;
first = iter;
return true;
}
};
template <typename Size, typename Subject>
class scope_limit<
Size, Subject,
std::enable_if_t<std::is_arithmetic_v<Size>>
> :
public x3::unary_parser<Subject, scope_limit<Size, Subject>>
{
using base_type = x3::unary_parser<Subject, scope_limit<Size, Subject>>;
size_t _limit;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using attribute_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
static bool const has_attribute = true;
scope_limit(Size limit, const Subject& subject) :
base_type(subject), _limit(limit)
{}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
if (iter + _limit > last)
return false;
if (!base_type::subject.parse(iter, iter + _limit, ctx, rctx, attr))
return false;
first = iter;
return true;
}
};
template <typename LenParser, typename Enable = void>
struct scope_limit_gen {
template <typename Subject>
auto operator[](const Subject& p) const {
return scope_limit<LenParser, Subject> { _lp, x3::as_parser(p) };
}
LenParser _lp;
};
template <typename Size>
struct scope_limit_gen<
Size,
std::enable_if_t<std::is_arithmetic_v<Size>>
> {
template <typename Subject>
auto operator[](const Subject& p) const {
return scope_limit<Size, Subject> { limit, x3::as_parser(p) };
}
Size limit;
};
template <
typename Parser,
std::enable_if_t<x3::traits::is_parser<Parser>::value, bool> = true
>
scope_limit_gen<Parser> scope_limit_(const Parser& p) {
return { p };
}
template <
typename Size,
std::enable_if_t<std::is_arithmetic_v<Size>, bool> = true
>
scope_limit_gen<Size> scope_limit_(Size limit) {
return { limit };
}
struct verbatim_parser : x3::parser<verbatim_parser> {
using attribute_type = std::string;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(It& first, const It last, const Ctx&, RCtx&, Attr& attr) const {
attr = std::string { first, last };
first = last;
return true;
}
};
constexpr auto verbatim_ = verbatim_parser{};
struct varint_parser : x3::parser<varint_parser> {
using attribute_type = int32_t;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx&, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
if (iter == last)
return false;
int32_t result = 0; unsigned bit_shift = 0;
for (; iter != last && bit_shift < sizeof(int32_t) * 7; ++iter) {
auto val = *iter;
if (val & 0b1000'0000u) {
result |= (val & 0b0111'1111u) << bit_shift;
bit_shift += 7;
}
else {
result |= (static_cast<int32_t>(val) << bit_shift);
bit_shift = 0;
break;
}
}
if (bit_shift)
return false;
attr = result;
first = ++iter;
return true;
}
};
constexpr varint_parser varint_{};
struct len_prefix_parser : x3::parser<len_prefix_parser> {
using attribute_type = std::string;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
typename x3::traits::attribute_of<decltype(x3::big_word), Ctx>::type len;
if (x3::big_word.parse(iter, last, ctx, rctx, len)) {
if (std::distance(iter, last) < len)
return false;
}
else
return false;
attr = std::string(iter, iter + len);
first = iter + len;
return true;
}
};
constexpr len_prefix_parser utf8_{};
constexpr len_prefix_parser binary_{};
/*
Boost Spirit incorrectly deduces atribute type for a parser of the form
(eps(a) | parser1) >> (eps(b) | parser)
and we had to create if_ parser to remedy the issue
*/
template <typename Subject>
class conditional_parser :
public x3::unary_parser<Subject, conditional_parser<Subject>> {
using base_type = x3::unary_parser<Subject, conditional_parser<Subject>>;
bool _condition;
public:
using ctx_type =
decltype(x3::make_context<struct _>(std::declval<Subject&>()));
using subject_attr_type =
typename x3::traits::attribute_of<Subject, ctx_type>::type;
using attribute_type = boost::optional<subject_attr_type>;
static bool const has_attribute = true;
conditional_parser(const Subject& s, bool condition) :
base_type(s), _condition(condition) {}
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
if (!_condition)
return true;
It iter = first;
subject_attr_type sattr {};
if (!base_type::subject.parse(iter, last, ctx, rctx, sattr))
return false;
attr.emplace(std::move(sattr));
first = iter;
return true;
}
};
struct conditional_gen {
bool _condition;
template <typename Subject>
auto operator[](const Subject& p) const {
return conditional_parser<Subject> { p, _condition };
}
};
inline conditional_gen if_(bool condition) {
return { condition };
}
} // end namespace basic
namespace prop {
namespace basic = async_mqtt5::decoders::basic;
namespace detail {
template <typename It, typename Ctx, typename RCtx, typename Prop>
bool parse_to_prop(
It& iter, const It last,
const Ctx& ctx, RCtx& rctx, Prop& prop
) {
using prop_type = std::remove_reference_t<decltype(prop)>;
bool rv = false;
if constexpr (std::is_same_v<prop_type, uint8_t>)
rv = x3::byte_.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, uint16_t>)
rv = x3::big_word.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, int32_t>)
rv = basic::varint_.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, uint32_t>)
rv = x3::big_dword.parse(iter, last, ctx, rctx, prop);
else if constexpr (std::is_same_v<prop_type, std::string>)
rv = basic::utf8_.parse(iter, last, ctx, rctx, prop);
else if constexpr (is_optional<prop_type>) {
typename prop_type::value_type val;
rv = parse_to_prop(iter, last, ctx, rctx, val);
if (rv) prop.emplace(std::move(val));
}
else if constexpr (is_pair<prop_type>) {
rv = parse_to_prop(iter, last, ctx, rctx, prop.first);
rv = parse_to_prop(iter, last, ctx, rctx, prop.second);
}
else if constexpr (is_vector<prop_type> || is_small_vector<prop_type>) {
typename std::remove_reference_t<prop_type>::value_type value;
rv = parse_to_prop(iter, last, ctx, rctx, value);
if (rv) prop.push_back(std::move(value));
}
return rv;
}
} // end namespace detail
template <typename Props>
class prop_parser : public x3::parser<prop_parser<Props>> {
public:
using attribute_type = Props;
static bool const has_attribute = true;
template <typename It, typename Ctx, typename RCtx, typename Attr>
bool parse(
It& first, const It last,
const Ctx& ctx, RCtx& rctx, Attr& attr
) const {
It iter = first;
x3::skip_over(iter, last, ctx);
if (iter == last)
return true;
int32_t props_length;
if (!basic::varint_.parse(iter, last, ctx, rctx, props_length))
return false;
const It scoped_last = iter + props_length;
// attr = Props{};
while (iter < scoped_last) {
uint8_t prop_id = *iter++;
bool rv = true;
It saved = iter;
attr.apply_on(
prop_id,
[&rv, &iter, scoped_last, &ctx, &rctx](auto& prop) {
rv = detail::parse_to_prop(
iter, scoped_last, ctx, rctx, prop
);
}
);
// either rv = false or property with prop_id was not found
if (!rv || iter == saved)
return false;
}
first = iter;
return true;
}
};
template <typename Props>
constexpr auto props_ = prop_parser<Props> {};
} // end namespace prop
} // end namespace async_mqtt5::decoders
#endif // !ASYNC_MQTT5_BASE_DECODERS_HPP

View File

@ -1,513 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_BASE_ENCODERS_HPP
#define ASYNC_MQTT5_BASE_ENCODERS_HPP
#include <cstddef>
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include <boost/core/identity.hpp>
#include <boost/endian/conversion.hpp>
#include <boost/type_traits/is_detected_exact.hpp>
#include <boost/type_traits/remove_cv_ref.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/impl/codecs/traits.hpp>
namespace async_mqtt5::encoders {
namespace basic {
using varint_t = int*;
inline void to_variable_bytes(std::string& s, int32_t val) {
if (val > 0xfffffff) return;
while (val > 127) {
s.push_back(char((val & 0b01111111) | 0b10000000));
val >>= 7;
}
s.push_back(val & 0b01111111);
}
inline size_t variable_length(int32_t val) {
if (val > 0xfffffff) return 0;
size_t rv = 1;
for (; val > 127; ++rv) val >>= 7;
return rv;
}
struct encoder {};
template <size_t bits, typename repr = uint8_t>
class flag_def : public encoder {
template <size_t num_bits>
using least_type = std::conditional_t<
num_bits <= 8, uint8_t,
std::conditional_t<
num_bits <= 16, uint16_t,
std::conditional_t<
num_bits <= 32, uint32_t,
std::conditional_t<num_bits <= 64, uint64_t, void>
>
>
>;
template <size_t oth_bits, typename oth_repr>
friend class flag_def;
repr _val { 0 };
public:
flag_def(repr val) : _val(val) {}
flag_def() = default;
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
if constexpr (std::is_same_v<projection, boost::identity>) {
repr val = value.has_value();
return flag_def<bits, repr> { val };
}
else {
repr val = value.has_value() ?
static_cast<repr>(std::invoke(proj, *value)) : 0;
return flag_def<bits, repr> { val };
}
}
template <
typename T,
typename projection = boost::identity,
std::enable_if_t<!is_optional<T>, bool> = true
>
auto operator()(T&& value, projection proj = {}) const {
auto val = static_cast<repr>(std::invoke(proj, value));
return flag_def<bits, repr> { val };
}
size_t byte_size() const { return sizeof(repr); }
template <size_t rhs_bits, typename rhs_repr>
auto operator|(const flag_def<rhs_bits, rhs_repr>& rhs) const {
using res_repr = least_type<bits + rhs_bits>;
auto val = static_cast<res_repr>((_val << rhs_bits) | rhs._val);
return flag_def<bits + rhs_bits, res_repr> { val };
}
std::string& encode(std::string& s) const {
using namespace boost::endian;
size_t sz = s.size(); s.resize(sz + sizeof(repr));
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<repr, sizeof(repr), order::big>(p, _val);
return s;
}
};
template <size_t bits, typename repr = uint8_t>
constexpr auto flag = flag_def<bits, repr>{};
template <typename T, typename Repr>
class int_val : public encoder {
T _val;
public:
int_val(T val) : _val(val) {}
size_t byte_size() const {
if constexpr (is_optional<T>) {
if (_val) return val_length(*_val);
return 0;
}
else
return val_length(_val);
}
std::string& encode(std::string& s) const {
if constexpr (is_optional<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
else
return encode_val(s, _val);
}
private:
template <typename U>
static size_t val_length(U&& val) {
if constexpr (std::is_same_v<Repr, varint_t>)
return variable_length(int32_t(val));
else
return sizeof(Repr);
}
template <typename U>
static std::string& encode_val(std::string& s, U&& val) {
using namespace boost::endian;
if constexpr (std::is_same_v<Repr, varint_t>) {
to_variable_bytes(s, int32_t(val));
return s;
}
else {
size_t sz = s.size(); s.resize(sz + sizeof(Repr));
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<Repr, sizeof(Repr), order::big>(p, val);
return s;
}
}
};
template <typename Repr>
class int_def {
public:
template <typename T>
auto operator()(T&& val) const {
return int_val<T, Repr> { std::forward<T>(val) };
}
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
if (val.has_value())
return (*this)(std::invoke(proj, *val));
return int_val<rv_type, Repr> { rv_type {} };
}
else {
using rv_type = std::invoke_result_t<projection, T>;
return int_val<rv_type, Repr> { std::invoke(proj, val) };
}
}
};
constexpr auto byte_ = int_def<uint8_t> {};
constexpr auto int16_ = int_def<uint16_t> {};
constexpr auto int32_ = int_def<uint32_t> {};
constexpr auto varlen_ = int_def<varint_t> {};
template <typename T>
class array_val : public encoder {
T _val;
bool _with_length;
public:
array_val(T val, bool with_length) :
_val(val), _with_length(with_length)
{
static_assert(
std::is_reference_v<T> || std::is_same_v<T, std::string_view>
);
}
size_t byte_size() const {
if constexpr (is_optional<T>)
return _val ? _with_length * 2 + val_length(*_val) : 0;
else
return _with_length * 2 + val_length(_val);
}
std::string& encode(std::string& s) const {
if constexpr (is_optional<T>) {
if (_val) return encode_val(s, *_val);
return s;
}
else
return encode_val(s, _val);
}
private:
template <typename V>
using has_size = decltype(std::declval<V&>().size());
template <typename U>
static size_t val_length(U&& val) {
if constexpr (std::is_same_v<boost::remove_cv_ref_t<U>, const char*>)
return std::strlen(val);
if constexpr (boost::is_detected_exact_v<size_t, has_size, U>)
return val.size();
else // fallback to type const char (&)[N] (substract 1 for trailing 0)
return sizeof(val) - 1;
}
template <typename U>
std::string& encode_val(std::string& s, U&& u) const {
using namespace boost::endian;
auto byte_len = val_length(std::forward<U>(u));
if (byte_len == 0 && !_with_length) return s;
if (_with_length) {
size_t sz = s.size(); s.resize(sz + 2);
auto p = reinterpret_cast<uint8_t*>(s.data() + sz);
endian_store<int16_t, sizeof(int16_t), order::big>(
p, int16_t(byte_len)
);
}
s.append(std::begin(u), std::begin(u) + byte_len);
return s;
}
};
template <bool with_length = true>
class array_def {
public:
template <typename T>
auto operator()(T&& val) const {
return array_val<T> { std::forward<T>(val), with_length };
}
template <typename T, typename projection>
auto operator()(T&& val, projection proj) const {
if constexpr (is_optional<T>) {
using rv_type = std::invoke_result_t<
projection, typename boost::remove_cv_ref_t<T>::value_type
>;
if (val.has_value())
return (*this)(std::invoke(proj, *val));
return array_val<rv_type> { rv_type {}, false };
}
else {
const auto& av = std::invoke(proj, val);
return array_val<T> { av, true };
}
}
};
using utf8_def = array_def<true>;
constexpr auto utf8_ = utf8_def {};
constexpr auto binary_ = array_def<true> {}; // for now
constexpr auto verbatim_ = array_def<false> {};
template <typename T, typename U>
class composed_val : public encoder {
T _lhs; U _rhs;
public:
composed_val(T lhs, U rhs) :
_lhs(std::forward<T>(lhs)), _rhs(std::forward<U>(rhs))
{}
size_t byte_size() const {
return _lhs.byte_size() + _rhs.byte_size();
}
std::string& encode(std::string& s) const {
_lhs.encode(s);
return _rhs.encode(s);
}
};
template <
typename T, typename U,
std::enable_if_t<
std::is_base_of_v<encoder, std::decay_t<T>> &&
std::is_base_of_v<encoder, std::decay_t<U>>,
bool
> = true
>
inline auto operator&(T&& t, U&& u) {
return composed_val(std::forward<T>(t), std::forward<U>(u));
}
template <
typename T,
std::enable_if_t<std::is_base_of_v<encoder, std::decay_t<T>>, bool> = true
>
std::string& operator<<(std::string& s, T&& t) {
return t.encode(s);
}
} // end namespace basic
namespace prop {
namespace pp = async_mqtt5::prop;
template <typename T>
auto encoder_for_prop_value(const T& val) {
if constexpr (std::is_same_v<T, uint8_t>)
return basic::int_def<uint8_t>{}(val);
else if constexpr (std::is_same_v<T, uint16_t>)
return basic::int_def<uint16_t>{}(val);
else if constexpr (std::is_same_v<T, int32_t>)
return basic::int_def<basic::varint_t>{}(val);
else if constexpr (std::is_same_v<T, uint32_t>)
return basic::int_def<uint32_t>{}(val);
else if constexpr (std::is_same_v<T, std::string>)
return basic::utf8_def{}(val);
else if constexpr (is_pair<T>)
return encoder_for_prop_value(val.first) &
encoder_for_prop_value(val.second);
}
template <typename T, pp::property_type p, typename Enable = void>
class prop_val;
template <
typename T, pp::property_type p
>
class prop_val<
T, p,
std::enable_if_t<!is_vector<T> && is_optional<T>>
> : public basic::encoder {
// allows T to be reference type to std::optional
static inline boost::remove_cv_ref_t<T> nulltype;
T _val;
public:
prop_val(T val) : _val(val) {
static_assert(std::is_reference_v<T>);
}
prop_val() : _val(nulltype) {}
size_t byte_size() const {
if (!_val) return 0;
return 1 + encoder_for_prop_value(*_val).byte_size();
}
std::string& encode(std::string& s) const {
if (!_val)
return s;
s.push_back(p);
return encoder_for_prop_value(*_val).encode(s);
}
};
template <
typename T, pp::property_type p
>
class prop_val<
T, p,
std::enable_if_t<is_vector<T> || is_small_vector<T>>
> : public basic::encoder {
// allows T to be reference type to std::vector
static inline boost::remove_cv_ref_t<T> nulltype;
T _val;
public:
prop_val(T val) : _val(val) {
static_assert(std::is_reference_v<T>);
}
prop_val() : _val(nulltype) { }
size_t byte_size() const {
if (_val.empty()) return 0;
size_t total_size = 0;
for (const auto& elem : _val)
total_size += 1 + encoder_for_prop_value(elem).byte_size();
return total_size;
}
std::string& encode(std::string& s) const {
if (_val.empty())
return s;
for (const auto& elem: _val) {
s.push_back(p);
encoder_for_prop_value(elem).encode(s);
}
return s;
}
};
template <typename Props>
class props_val : public basic::encoder {
static inline std::decay_t<Props> nulltype;
template <pp::property_type P, typename T>
static auto to_prop_val(const T& val) {
return prop_val<const T&, P>(val);
}
template <pp::property_type ...Ps>
static auto to_prop_vals(const pp::properties<Ps...>& props) {
return std::make_tuple(
to_prop_val<Ps>(
props[std::integral_constant<pp::property_type, Ps> {}]
)...
);
}
template <typename Func>
auto apply_each(Func&& func) const {
return std::apply([&func](const auto&... props) {
return (std::invoke(func, props), ...);
}, _prop_vals);
}
decltype(to_prop_vals(std::declval<Props>())) _prop_vals;
bool _may_omit;
public:
props_val(Props val, bool may_omit) :
_prop_vals(to_prop_vals(val)), _may_omit(may_omit)
{
static_assert(std::is_reference_v<Props>);
}
props_val(bool may_omit) :
_prop_vals(to_prop_vals(nulltype)), _may_omit(may_omit)
{}
size_t byte_size() const {
size_t psize = props_size();
if (_may_omit && psize == 0) return 0;
return psize + basic::varlen_(psize).byte_size();
}
std::string& encode(std::string& s) const {
size_t psize = props_size();
if (_may_omit && psize == 0) return s;
basic::varlen_(psize).encode(s);
apply_each([&s](const auto& pv) { return pv.encode(s); });
return s;
}
private:
size_t props_size() const {
size_t retval = 0;
apply_each([&retval](const auto& pv) {
return retval += pv.byte_size();
});
return retval;
}
};
template <bool may_omit>
class props_def {
public:
template <typename T>
auto operator()(T&& prop_container) const {
if constexpr (is_optional<T>) {
if (prop_container.has_value())
return (*this)(*prop_container);
return props_val<
const typename boost::remove_cv_ref_t<T>::value_type&
>(true);
}
else {
return props_val<T> { prop_container, may_omit };
}
}
};
constexpr auto props_ = props_def<false> {};
constexpr auto props_may_omit_ = props_def<true> {};
} // end namespace prop
} // end namespace async_mqtt5::encoders
#endif // !ASYNC_MQTT5_BASE_ENCODERS_HPP

View File

@ -1,307 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_MESSAGE_DECODERS_HPP
#define ASYNC_MQTT5_MESSAGE_DECODERS_HPP
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/base_decoders.hpp>
namespace async_mqtt5::decoders {
using byte_citer = detail::byte_citer;
using fixed_header = std::tuple<
uint8_t, // control byte
uint32_t // remaining_length
>;
inline std::optional<fixed_header> decode_fixed_header(
byte_citer& it, const byte_citer last
) {
auto fixed_header_ = x3::byte_ >> basic::varint_;
return type_parse(it, last, fixed_header_);
}
using packet_id = uint16_t;
inline std::optional<packet_id> decode_packet_id(
byte_citer& it
) {
auto packet_id_ = x3::big_word;
return type_parse(it, it + sizeof(uint16_t), packet_id_);
}
using connect_message = std::tuple<
std::string, // client_id,
std::optional<std::string>, // user_name,
std::optional<std::string>, // password,
uint16_t, // keep_alive,
bool, // clean_start,
connect_props, // props,
std::optional<will> // will
>;
inline std::optional<connect_message> decode_connect(
uint32_t remain_length, byte_citer& it
) {
auto var_header_ =
basic::utf8_ >> // MQTT
x3::byte_ >> // (num 5)
x3::byte_ >> // conn_flags_
x3::big_word >> // keep_alive
prop::props_<connect_props>;
const byte_citer end = it + remain_length;
auto vh = type_parse(it, end, var_header_);
if (!vh)
return std::optional<connect_message>{};
auto& [mqtt_str, version, flags, keep_alive, cprops] = *vh;
if (mqtt_str != "MQTT" || version != 5)
return std::optional<connect_message>{};
bool has_will = (flags & 0b00000100);
bool has_uname = (flags & 0b10000000);
bool has_pwd = (flags & 0b01000000);
auto payload_ =
basic::utf8_ >> // client_id
basic::if_(has_will)[prop::props_<will_props>] >>
basic::if_(has_will)[basic::utf8_] >> // will topic
basic::if_(has_will)[basic::binary_] >> // will message
basic::if_(has_uname)[basic::utf8_] >> // username
basic::if_(has_pwd)[basic::utf8_]; // password
auto pload = type_parse(it, end, payload_);
if (!pload)
return std::optional<connect_message>{};
std::optional<will> w;
if (has_will)
w.emplace(
std::move(*std::get<2>(*pload)), // will_topic
std::move(*std::get<3>(*pload)), // will_message
qos_e((flags & 0b00011000) >> 3),
retain_e((flags & 0b00100000) >> 5),
std::move(*std::get<1>(*pload)) // will props
);
connect_message retval = {
std::move(std::get<0>(*pload)), // client_id
std::move(std::get<4>(*pload)), // user_name
std::move(std::get<5>(*pload)), // password
keep_alive,
flags & 0b00000010, // clean_start
std::move(cprops), // connect_props
std::move(w) // will
};
return std::optional<connect_message> { std::move(retval) };
}
using connack_message = std::tuple<
uint8_t, // session_present
uint8_t, // connect reason code
connack_props // props
>;
inline std::optional<connack_message> decode_connack(
uint32_t remain_length, byte_citer& it
) {
auto connack_ = basic::scope_limit_(remain_length)[
x3::byte_ >> x3::byte_ >> prop::props_<connack_props>
];
return type_parse(it, it + remain_length, connack_);
}
using publish_message = std::tuple<
std::string, // topic
std::optional<uint16_t>, // packet_id
uint8_t, // dup_e, qos_e, retain_e
publish_props, // publish props
std::string // payload
>;
inline std::optional<publish_message> decode_publish(
uint8_t control_byte, uint32_t remain_length, byte_citer& it
) {
uint8_t flags = control_byte & 0b1111;
auto qos = qos_e((flags >> 1) & 0b11);
auto publish_ = basic::scope_limit_(remain_length)[
basic::utf8_ >> basic::if_(qos != qos_e::at_most_once)[x3::big_word] >>
x3::attr(flags) >> prop::props_<publish_props> >> basic::verbatim_
];
return type_parse(it, it + remain_length, publish_);
}
using puback_message = std::tuple<
uint8_t, // puback reason code
puback_props // props
>;
inline std::optional<puback_message> decode_puback(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return puback_message {};
auto puback_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<puback_props>
];
return type_parse(it, it + remain_length, puback_);
}
using pubrec_message = std::tuple<
uint8_t, // puback reason code
pubrec_props // props
>;
inline std::optional<pubrec_message> decode_pubrec(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubrec_message {};
auto pubrec_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubrec_props>
];
return type_parse(it, it + remain_length, pubrec_);
}
using pubrel_message = std::tuple<
uint8_t, // puback reason code
pubrel_props // props
>;
inline std::optional<pubrel_message> decode_pubrel(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubrel_message {};
auto pubrel_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubrel_props>
];
return type_parse(it, it + remain_length, pubrel_);
}
using pubcomp_message = std::tuple<
uint8_t, // puback reason code
pubcomp_props // props
>;
inline std::optional<pubcomp_message> decode_pubcomp(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return pubcomp_message {};
auto pubcomp_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<pubcomp_props>
];
return type_parse(it, it + remain_length, pubcomp_);
}
using subscribe_message = std::tuple<
subscribe_props,
std::vector<std::tuple<std::string, uint8_t>> // topic filter with opts
>;
inline std::optional<subscribe_message> decode_subscribe(
uint32_t remain_length, byte_citer& it
) {
auto subscribe_ = basic::scope_limit_(remain_length)[
prop::props_<subscribe_props> >> +(basic::utf8_ >> x3::byte_)
];
return type_parse(it, it + remain_length, subscribe_);
}
using suback_message = std::tuple<
suback_props,
std::vector<uint8_t> // reason_codes
>;
inline std::optional<suback_message> decode_suback(
uint32_t remain_length, byte_citer& it
) {
auto suback_ = basic::scope_limit_(remain_length)[
prop::props_<suback_props> >> +x3::byte_
];
return type_parse(it, it + remain_length, suback_);
}
using unsubscribe_message = std::tuple<
unsubscribe_props,
std::vector<std::string> // topics
>;
inline std::optional<unsubscribe_message> decode_unsubscribe(
uint32_t remain_length, byte_citer& it
) {
auto unsubscribe_ = basic::scope_limit_(remain_length)[
prop::props_<unsubscribe_props> >> +basic::utf8_
];
return type_parse(it, it + remain_length, unsubscribe_);
}
using unsuback_message = std::tuple<
unsuback_props,
std::vector<uint8_t> // reason_codes
>;
inline std::optional<unsuback_message> decode_unsuback(
uint32_t remain_length, byte_citer& it
) {
auto unsuback_ = basic::scope_limit_(remain_length)[
prop::props_<unsuback_props> >> +x3::byte_
];
return type_parse(it, it + remain_length, unsuback_);
}
using disconnect_message = std::tuple<
uint8_t, // reason_code
disconnect_props
>;
inline std::optional<disconnect_message> decode_disconnect(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return disconnect_message {};
auto disconnect_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<disconnect_props>
];
return type_parse(it, it + remain_length, disconnect_);
}
using auth_message = std::tuple<
uint8_t, // reason_code
auth_props
>;
inline std::optional<auth_message> decode_auth(
uint32_t remain_length, byte_citer& it
) {
if (remain_length == 0)
return auth_message {};
auto auth_ = basic::scope_limit_(remain_length)[
x3::byte_ >> prop::props_<auth_props>
];
return type_parse(it, it + remain_length, auth_);
}
} // end namespace async_mqtt5::decoders
#endif // !ASYNC_MQTT5_MESSAGE_DECODERS_HPP

View File

@ -1,431 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
#define ASYNC_MQTT5_MESSAGE_ENCODERS_HPP
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/impl/codecs/base_encoders.hpp>
namespace async_mqtt5::encoders {
template <typename encoder>
std::string encode(const encoder& e) {
std::string s;
s.reserve(e.byte_size());
s << e;
return s;
}
inline std::string encode_connect(
std::string_view client_id,
std::optional<std::string_view> user_name,
std::optional<std::string_view> password,
uint16_t keep_alive, bool clean_start,
const connect_props& props,
const std::optional<will>& w
) {
auto packet_type_ =
basic::flag<4>(0b0001) |
basic::flag<4>(0);
auto conn_flags_ =
basic::flag<1>(user_name) |
basic::flag<1>(password) |
basic::flag<1>(w, &will::retain) |
basic::flag<2>(w, &will::qos) |
basic::flag<1>(w) |
basic::flag<1>(clean_start) |
basic::flag<1>(0);
auto var_header_ =
basic::utf8_("MQTT") &
basic::byte_(uint8_t(5)) &
conn_flags_ &
basic::int16_(keep_alive) &
prop::props_(props);
auto payload_ =
basic::utf8_(client_id) &
prop::props_(w) &
basic::utf8_(w, &will::topic) &
basic::binary_(w, &will::message) &
basic::utf8_(user_name) &
basic::utf8_(password);
auto message_body_ = var_header_ & payload_;
auto fixed_header_ =
packet_type_ &
basic::varlen_(message_body_.byte_size());
auto connect_message_ = fixed_header_ & message_body_;
return encode(connect_message_);
}
inline std::string encode_connack(
bool session_present,
uint8_t reason_code,
const connack_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0010) |
basic::flag<4>(0);
auto var_header_ =
basic::flag<1>(session_present) &
basic::byte_(reason_code) &
prop::props_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto connack_message_ = fixed_header_ & var_header_;
return encode(connack_message_);
}
inline std::string encode_publish(
uint16_t packet_id,
std::string_view topic_name,
std::string_view payload,
qos_e qos, retain_e retain, dup_e dup,
const publish_props& props
) {
std::optional<uint16_t> used_packet_id;
if (qos != qos_e::at_most_once) used_packet_id.emplace(packet_id);
auto packet_type_ =
basic::flag<4>(0b0011) |
basic::flag<1>(dup) |
basic::flag<2>(qos) |
basic::flag<1>(retain);
auto var_header_ =
basic::utf8_(topic_name) &
basic::int16_(used_packet_id) &
prop::props_(props);
auto message_body_ = var_header_ & basic::verbatim_(payload);
auto fixed_header_ =
packet_type_ &
basic::varlen_(message_body_.byte_size());
auto publish_message_ = fixed_header_ & message_body_;
return encode(publish_message_);
}
inline std::string encode_puback(
uint16_t packet_id,
uint8_t reason_code,
const puback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0100) |
basic::flag<4>(0);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto puback_message_ = fixed_header_ & var_header_;
return encode(puback_message_);
}
inline std::string encode_pubrec(
uint16_t packet_id,
uint8_t reason_code,
const pubrec_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0101) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubrec_message_ = fixed_header_ & var_header_;
return encode(pubrec_message_);
}
inline std::string encode_pubrel(
uint16_t packet_id,
uint8_t reason_code,
const pubrel_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0110) |
basic::flag<4>(0b0010);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubrel_message_ = fixed_header_ & var_header_;
return encode(pubrel_message_);
}
inline std::string encode_pubcomp(
uint16_t packet_id,
uint8_t reason_code,
const pubcomp_props& props
) {
auto packet_type_ =
basic::flag<4>(0b0111) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
basic::byte_(reason_code) &
prop::props_may_omit_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto pubcomp_message_ = fixed_header_ & var_header_;
return encode(pubcomp_message_);
}
inline std::string encode_subscribe(
uint16_t packet_id,
const std::vector<subscribe_topic>& topics,
const subscribe_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1000) |
basic::flag<4>(0b0010);
size_t payload_size = 0;
for (const auto& [topic_filter, _]: topics)
payload_size += basic::utf8_(topic_filter).byte_size() + 1;
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + payload_size) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + payload_size);
for (const auto& [topic_filter, sub_opts]: topics) {
auto opts_ =
basic::flag<2>(sub_opts.retain_handling) |
basic::flag<1>(sub_opts.retain_as_published) |
basic::flag<1>(sub_opts.no_local) |
basic::flag<2>(sub_opts.max_qos);
auto filter_ = basic::utf8_(topic_filter) & opts_;
s << filter_;
}
return s;
}
inline std::string encode_suback(
uint16_t packet_id,
const std::vector<uint8_t>& reason_codes,
const suback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1001) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + reason_codes.size());
for (auto reason_code: reason_codes)
s << basic::byte_(reason_code);
return s;
}
inline std::string encode_unsubscribe(
uint16_t packet_id,
const std::vector<std::string>& topics,
const unsubscribe_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1010) |
basic::flag<4>(0b0010);
size_t payload_size = 0;
for (const auto& topic: topics)
payload_size += basic::utf8_(topic).byte_size();
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + payload_size) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + payload_size);
for (const auto& topic: topics)
s << basic::utf8_(topic);
return s;
}
inline std::string encode_unsuback(
uint16_t packet_id,
const std::vector<uint8_t>& reason_codes,
const unsuback_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1011) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::int16_(packet_id) &
prop::props_(props);
auto message_ =
packet_type_ &
basic::varlen_(var_header_.byte_size() + reason_codes.size()) &
var_header_;
auto s = encode(message_);
s.reserve(s.size() + reason_codes.size());
for (auto reason_code: reason_codes)
s << basic::byte_(reason_code);
return s;
}
inline std::string encode_pingreq() {
auto packet_type_ =
basic::flag<4>(0b1100) |
basic::flag<4>(0);
auto remaining_len_ =
basic::byte_(uint8_t(0));
auto ping_req_ = packet_type_ & remaining_len_;
return encode(ping_req_);
}
inline std::string encode_pingresp() {
auto packet_type_ =
basic::flag<4>(0b1101) |
basic::flag<4>(0);
auto remaining_len_ =
basic::byte_(uint8_t(0));
auto ping_resp_ = packet_type_ & remaining_len_;
return encode(ping_resp_);
}
inline std::string encode_disconnect(
uint8_t reason_code,
const disconnect_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1110) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::byte_(reason_code) &
prop::props_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto disconnect_message_ = fixed_header_ & var_header_;
return encode(disconnect_message_);
}
inline std::string encode_auth(
uint8_t reason_code,
const auth_props& props
) {
auto packet_type_ =
basic::flag<4>(0b1111) |
basic::flag<4>(0b0000);
auto var_header_ =
basic::byte_(reason_code) &
prop::props_(props);
auto fixed_header_ =
packet_type_ &
basic::varlen_(var_header_.byte_size());
auto auth_message_ = fixed_header_ & var_header_;
return encode(auth_message_);
}
} // end namespace async_mqtt5::encoders
#endif // !ASYNC_MQTT5_MESSAGE_ENCODERS_HPP

View File

@ -1,451 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_CONNECT_OP_HPP
#define ASYNC_MQTT5_CONNECT_OP_HPP
#include <cstdint>
#include <memory>
#include <string>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/http/field.hpp>
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/beast/websocket/stream_base.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
template <typename Stream>
class connect_op {
static constexpr size_t min_packet_sz = 5;
struct on_connect {};
struct on_tls_handshake {};
struct on_ws_handshake {};
struct on_send_connect {};
struct on_fixed_header {};
struct on_read_packet {};
struct on_init_auth_data {};
struct on_auth_data {};
struct on_send_auth {};
struct on_complete_auth {};
Stream& _stream;
mqtt_ctx& _ctx;
using handler_type = asio::any_completion_handler<void (error_code)>;
handler_type _handler;
std::unique_ptr<std::string> _buffer_ptr;
asio::cancellation_state _cancellation_state;
using endpoint = asio::ip::tcp::endpoint;
using epoints = asio::ip::tcp::resolver::results_type;
public:
template <typename Handler>
connect_op(
Stream& stream, Handler&& handler, mqtt_ctx& ctx
) :
_stream(stream), _ctx(ctx),
_handler(std::forward<Handler>(handler)),
_cancellation_state(
asio::get_associated_cancellation_slot(_handler),
asio::enable_total_cancellation {},
asio::enable_total_cancellation {}
)
{}
connect_op(connect_op&&) = default;
connect_op(const connect_op&) = delete;
connect_op& operator=(connect_op&&) = default;
connect_op& operator=(const connect_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
void perform(
const epoints& eps, authority_path ap
) {
lowest_layer(_stream).async_connect(
*std::begin(eps),
asio::append(
asio::prepend(std::move(*this), on_connect {}),
*std::begin(eps), std::move(ap)
)
);
}
void operator()(
on_connect, error_code ec, endpoint ep, authority_path ap
) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
do_tls_handshake(std::move(ep), std::move(ap));
}
void do_tls_handshake(endpoint ep, authority_path ap) {
if constexpr (has_tls_handshake<Stream>) {
_stream.async_handshake(
tls_handshake_type<Stream>::client,
asio::append(
asio::prepend(std::move(*this), on_tls_handshake {}),
std::move(ep), std::move(ap)
)
);
}
else if constexpr (
has_tls_handshake<typename next_layer_type<Stream>::type>
) {
_stream.next_layer().async_handshake(
tls_handshake_type<typename next_layer_type<Stream>::type>::client,
asio::append(
asio::prepend(std::move(*this), on_tls_handshake {}),
std::move(ep), std::move(ap)
)
);
}
else
do_ws_handshake(std::move(ep), std::move(ap));
}
void operator()(
on_tls_handshake, error_code ec,
endpoint ep, authority_path ap
) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
do_ws_handshake(std::move(ep), std::move(ap));
}
void do_ws_handshake(endpoint, authority_path ap) {
if constexpr (has_ws_handshake<Stream>) {
using namespace boost::beast;
// We'll need to turn off read timeouts on the underlying stream
// because the websocket stream has its own timeout system.
// Set suggested timeout settings for the websocket
_stream.set_option(
websocket::stream_base::timeout::suggested(role_type::client)
);
_stream.binary(true);
// Set a decorator to change the User-Agent of the handshake
_stream.set_option(websocket::stream_base::decorator(
[](websocket::request_type& req) {
req.set(http::field::sec_websocket_protocol, "mqtt");
req.set(http::field::user_agent, "boost.mqtt");
})
);
_stream.async_handshake(
ap.host + ':' + ap.port, ap.path,
asio::prepend(std::move(*this), on_ws_handshake {})
);
}
else
(*this)(on_ws_handshake {}, error_code {});
}
void operator()(on_ws_handshake, error_code ec) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
auto auth_method = _ctx.authenticator.method();
if (!auth_method.empty()) {
_ctx.co_props[prop::authentication_method] = auth_method;
return _ctx.authenticator.async_auth(
auth_step_e::client_initial, "",
asio::prepend(std::move(*this), on_init_auth_data {})
);
}
send_connect();
}
void operator()(on_init_auth_data, error_code ec, std::string data) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(asio::error::try_again);
_ctx.co_props[prop::authentication_data] = std::move(data);
send_connect();
}
void send_connect() {
auto packet = control_packet<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_connect,
_ctx.creds.client_id,
_ctx.creds.username, _ctx.creds.password,
_ctx.keep_alive, false, _ctx.co_props, _ctx.will_msg
);
auto wire_data = packet.wire_data();
detail::async_write(
_stream, asio::buffer(wire_data),
asio::consign(
asio::prepend(std::move(*this), on_send_connect {}),
std::move(packet)
)
);
}
void operator()(on_send_connect, error_code ec, size_t) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
_buffer_ptr = std::make_unique<std::string>(min_packet_sz, char(0));
auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz);
asio::async_read(
_stream, buff, asio::transfer_all(),
asio::prepend(std::move(*this), on_fixed_header {})
);
}
void operator()(
on_fixed_header, error_code ec, size_t num_read
) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
auto code = control_code_e((*_buffer_ptr)[0] & 0b11110000);
if (code != control_code_e::auth && code != control_code_e::connack)
return complete(asio::error::try_again);
auto varlen_ptr = _buffer_ptr->cbegin() + 1;
auto varlen = decoders::type_parse(
varlen_ptr, _buffer_ptr->cend(), decoders::basic::varint_
);
if (!varlen)
return complete(asio::error::try_again);
auto varlen_sz = std::distance(_buffer_ptr->cbegin() + 1, varlen_ptr);
auto remain_len = *varlen -
std::distance(varlen_ptr, _buffer_ptr->cbegin() + num_read);
if (num_read + remain_len > _buffer_ptr->size())
_buffer_ptr->resize(num_read + remain_len);
auto buff = asio::buffer(_buffer_ptr->data() + num_read, remain_len);
auto first = _buffer_ptr->cbegin() + varlen_sz + 1;
auto last = first + *varlen;
asio::async_read(
_stream, buff, asio::transfer_all(),
asio::prepend(
asio::append(std::move(*this), code, first, last),
on_read_packet {}
)
);
}
void operator()(
on_read_packet, error_code ec, size_t, control_code_e code,
byte_citer first, byte_citer last
) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
if (code == control_code_e::connack)
return on_connack(first, last);
if (!_ctx.co_props[prop::authentication_method].has_value())
return complete(client::error::malformed_packet);
on_auth(first, last);
}
void on_connack(byte_citer first, byte_citer last) {
auto packet_length = static_cast<uint32_t>(std::distance(first, last));
auto rv = decoders::decode_connack(packet_length, first);
if (!rv.has_value())
return complete(client::error::malformed_packet);
const auto& [session_present, reason_code, ca_props] = *rv;
_ctx.ca_props = ca_props;
_ctx.state.session_present(session_present);
// Unexpected result handling:
// - If we don't have a Session State, and we get session_present = true,
// we must close the network connection (and restart with a clean start)
// - If we have a Session State, and we get session_present = false,
// we must discard our Session State
auto rc = to_reason_code<reason_codes::category::connack>(reason_code);
if (!rc.has_value()) // reason code not allowed in CONNACK
return complete(client::error::malformed_packet);
if (*rc)
return complete(asio::error::try_again);
if (_ctx.co_props[prop::authentication_method].has_value())
return _ctx.authenticator.async_auth(
auth_step_e::server_final,
ca_props[prop::authentication_data].value_or(""),
asio::prepend(std::move(*this), on_complete_auth {})
);
complete(error_code {});
}
void on_auth(byte_citer first, byte_citer last) {
auto packet_length = static_cast<uint32_t>(std::distance(first, last));
auto rv = decoders::decode_auth(packet_length, first);
if (!rv.has_value())
return complete(client::error::malformed_packet);
const auto& [reason_code, auth_props] = *rv;
auto rc = to_reason_code<reason_codes::category::auth>(reason_code);
if (
!rc.has_value() ||
auth_props[prop::authentication_method]
!= _ctx.co_props[prop::authentication_method]
)
return complete(client::error::malformed_packet);
_ctx.authenticator.async_auth(
auth_step_e::server_challenge,
auth_props[prop::authentication_data].value_or(""),
asio::prepend(std::move(*this), on_auth_data {})
);
}
void operator()(on_auth_data, error_code ec, std::string data) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(asio::error::try_again);
auth_props props;
props[prop::authentication_method] =
_ctx.co_props[prop::authentication_method];
props[prop::authentication_data] = std::move(data);
auto packet = control_packet<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_auth,
reason_codes::continue_authentication.value(), props
);
auto wire_data = packet.wire_data();
detail::async_write(
_stream, asio::buffer(wire_data),
asio::consign(
asio::prepend(std::move(*this), on_send_auth{}),
std::move(packet)
)
);
}
void operator()(on_send_auth, error_code ec, size_t) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(ec);
auto buff = asio::buffer(_buffer_ptr->data(), min_packet_sz);
asio::async_read(
_stream, buff, asio::transfer_all(),
asio::prepend(std::move(*this), on_fixed_header {})
);
}
void operator()(on_complete_auth, error_code ec, std::string) {
if (is_cancelled())
return complete(asio::error::operation_aborted);
if (ec)
return complete(asio::error::try_again);
complete(error_code {});
}
private:
bool is_cancelled() const {
return _cancellation_state.cancelled() != asio::cancellation_type::none;
}
void complete(error_code ec) {
_cancellation_state.slot().clear();
std::move(_handler)(ec);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_CONNECT_OP_HPP

View File

@ -1,319 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_DISCONNECT_OP_HPP
#define ASYNC_MQTT5_DISCONNECT_OP_HPP
#include <cstdint>
#include <memory>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/topic_validation.hpp>
#include <async_mqtt5/detail/utf8_mqtt.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <
typename ClientService,
typename DisconnectContext
>
class disconnect_op {
using client_service = ClientService;
struct on_disconnect {};
std::shared_ptr<client_service> _svc_ptr;
DisconnectContext _context;
using handler_type = cancellable_handler<
asio::any_completion_handler<void (error_code)>,
typename ClientService::executor_type
>;
handler_type _handler;
public:
template <typename Handler>
disconnect_op(
std::shared_ptr<client_service> svc_ptr,
DisconnectContext&& context, Handler&& handler
) :
_svc_ptr(std::move(svc_ptr)), _context(std::move(context)),
_handler(std::move(handler), _svc_ptr->get_executor())
{
auto slot = asio::get_associated_cancellation_slot(_handler);
if (slot.is_connected())
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
svc.cancel();
});
}
disconnect_op(disconnect_op&&) = default;
disconnect_op(const disconnect_op&) = delete;
disconnect_op& operator=(disconnect_op&&) = default;
disconnect_op& operator=(const disconnect_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
void perform() {
error_code ec = validate_disconnect(_context.props);
if (ec)
return complete_immediate(ec);
auto disconnect = control_packet<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_disconnect,
static_cast<uint8_t>(_context.reason_code), _context.props
);
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
.value_or(default_max_send_size);
if (disconnect.size() > max_packet_size)
// drop properties
return send_disconnect(control_packet<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_disconnect,
static_cast<uint8_t>(_context.reason_code), disconnect_props {}
));
send_disconnect(std::move(disconnect));
}
void send_disconnect(control_packet<allocator_type> disconnect) {
auto wire_data = disconnect.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::terminal,
asio::prepend(
std::move(*this),
on_disconnect {}, std::move(disconnect)
)
);
}
void operator()(
on_disconnect,
control_packet<allocator_type> disconnect, error_code ec
) {
// The connection must be closed even
// if we failed to send the DISCONNECT packet
// with Reason Code of 0x80 or greater.
if (
ec == asio::error::operation_aborted ||
ec == asio::error::no_recovery
)
return complete(asio::error::operation_aborted);
if (ec == asio::error::try_again) {
if (_context.terminal)
return send_disconnect(std::move(disconnect));
return complete(error_code {});
}
if (_context.terminal) {
_svc_ptr->cancel();
return complete(error_code {});
}
_svc_ptr->close_stream();
_svc_ptr->open_stream();
complete(error_code {});
}
private:
static error_code validate_disconnect(const disconnect_props& props) {
const auto& reason_string = props[prop::reason_string];
if (
reason_string &&
validate_mqtt_utf8(*reason_string) != validation_result::valid
)
return client::error::malformed_packet;
const auto& user_properties = props[prop::user_property];
for (const auto& user_property: user_properties)
if (!is_valid_string_pair(user_property))
return client::error::malformed_packet;
return error_code {};
}
void complete(error_code ec) {
_handler.complete(ec);
}
void complete_immediate(error_code ec) {
_handler.complete_immediate(ec);
}
};
template <typename ClientService, typename Handler>
class terminal_disconnect_op {
using client_service = ClientService;
static constexpr uint8_t seconds = 5;
std::shared_ptr<client_service> _svc_ptr;
std::unique_ptr<asio::steady_timer> _timer;
using handler_type = cancellable_handler<
Handler, typename ClientService::executor_type
>;
handler_type _handler;
public:
terminal_disconnect_op(
std::shared_ptr<client_service> svc_ptr,
Handler&& handler
) :
_svc_ptr(std::move(svc_ptr)),
_timer(new asio::steady_timer(_svc_ptr->get_executor())),
_handler(std::move(handler), _svc_ptr->get_executor())
{}
terminal_disconnect_op(terminal_disconnect_op&&) = default;
terminal_disconnect_op(const terminal_disconnect_op&) = delete;
terminal_disconnect_op& operator=(terminal_disconnect_op&&) = default;
terminal_disconnect_op& operator=(const terminal_disconnect_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type = asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename DisconnectContext>
void perform(DisconnectContext&& context) {
namespace asioex = boost::asio::experimental;
auto init_disconnect = [](
auto handler, disconnect_ctx ctx,
const std::shared_ptr<ClientService>& svc_ptr
) {
disconnect_op {
svc_ptr, std::move(ctx), std::move(handler)
}.perform();
};
_timer->expires_after(std::chrono::seconds(seconds));
auto timed_disconnect = asioex::make_parallel_group(
asio::async_initiate<const asio::deferred_t, void (error_code)>(
init_disconnect, asio::deferred,
std::forward<DisconnectContext>(context), _svc_ptr
),
_timer->async_wait(asio::deferred)
);
timed_disconnect.async_wait(
asioex::wait_for_one(), asio::prepend(std::move(*this))
);
}
void operator()(
std::array<std::size_t, 2> /* ord */,
error_code disconnect_ec, error_code /* timer_ec */
) {
_handler.complete(disconnect_ec);
}
};
template <typename ClientService, bool terminal>
class initiate_async_disconnect {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_disconnect(std::shared_ptr<ClientService> svc_ptr) :
_svc_ptr(std::move(svc_ptr))
{}
using executor_type = typename ClientService::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
}
template <typename Handler>
void operator()(
Handler&& handler,
disconnect_rc_e rc, const disconnect_props& props
) {
auto ctx = disconnect_ctx { rc, props, terminal };
if constexpr (terminal)
terminal_disconnect_op { _svc_ptr, std::move(handler) }
.perform(std::move(ctx));
else
disconnect_op { _svc_ptr, std::move(ctx), std::move(handler) }
.perform();
}
};
template <typename ClientService, typename CompletionToken>
decltype(auto) async_disconnect(
disconnect_rc_e reason_code, const disconnect_props& props,
const std::shared_ptr<ClientService>& svc_ptr,
CompletionToken&& token
) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
initiate_async_disconnect<ClientService, false>(svc_ptr), token,
reason_code, props
);
}
template <typename ClientService, typename CompletionToken>
decltype(auto) async_terminal_disconnect(
disconnect_rc_e reason_code, const disconnect_props& props,
const std::shared_ptr<ClientService>& svc_ptr,
CompletionToken&& token
) {
using Signature = void (error_code);
return asio::async_initiate<CompletionToken, Signature>(
initiate_async_disconnect<ClientService, true>(svc_ptr), token,
reason_code, props
);
}
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_DISCONNECT_HPP

View File

@ -1,230 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_ENDPOINTS_HPP
#define ASYNC_MQTT5_ENDPOINTS_HPP
#include <array>
#include <chrono>
#include <string>
#include <boost/asio/append.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/spirit/home/x3.hpp>
#include <async_mqtt5/types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
using epoints = asio::ip::tcp::resolver::results_type;
template <typename Owner, typename Handler>
class resolve_op {
struct on_resolve {};
Owner& _owner;
using handler_type = Handler;
handler_type _handler;
public:
resolve_op(Owner& owner, Handler&& handler) :
_owner(owner), _handler(std::move(handler))
{}
resolve_op(resolve_op&&) = default;
resolve_op(const resolve_op&) = delete;
resolve_op& operator=(resolve_op&&) = default;
resolve_op& operator=(const resolve_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
void perform() {
namespace asioex = boost::asio::experimental;
if (_owner._servers.empty())
return complete_post(asio::error::host_not_found, {}, {});
_owner._current_host++;
if (_owner._current_host + 1 > static_cast<int>(_owner._servers.size())) {
_owner._current_host = -1;
return complete_post(asio::error::try_again, {}, {});
}
authority_path ap = _owner._servers[_owner._current_host];
_owner._connect_timer.expires_after(std::chrono::seconds(5));
auto timed_resolve = asioex::make_parallel_group(
_owner._resolver.async_resolve(ap.host, ap.port, asio::deferred),
_owner._connect_timer.async_wait(asio::deferred)
);
timed_resolve.async_wait(
asioex::wait_for_one(),
asio::append(
asio::prepend(std::move(*this), on_resolve {}),
std::move(ap)
)
);
}
void operator()(
on_resolve, std::array<std::size_t, 2> ord,
error_code resolve_ec, epoints epts,
error_code timer_ec, authority_path ap
) {
if (
ord[0] == 0 && resolve_ec == asio::error::operation_aborted ||
ord[0] == 1 && timer_ec == asio::error::operation_aborted
)
return complete(asio::error::operation_aborted, {}, {});
if (!resolve_ec)
return complete(error_code {}, std::move(epts), std::move(ap));
perform();
}
private:
void complete(error_code ec, epoints eps, authority_path ap) {
std::move(_handler)(ec, std::move(eps), std::move(ap));
}
void complete_post(error_code ec, epoints eps, authority_path ap) {
asio::post(
_owner.get_executor(),
asio::prepend(
std::move(_handler), ec,
std::move(eps), std::move(ap)
)
);
}
};
class endpoints {
asio::ip::tcp::resolver _resolver;
asio::steady_timer& _connect_timer;
std::vector<authority_path> _servers;
int _current_host { -1 };
template <typename Owner, typename Handler>
friend class resolve_op;
template <typename T>
static constexpr auto to_(T& arg) {
return [&](auto& ctx) { arg = boost::spirit::x3::_attr(ctx); };
}
template <typename T, typename Parser>
static constexpr auto as_(Parser&& p){
return boost::spirit::x3::rule<struct _, T>{} = std::forward<Parser>(p);
}
public:
template <typename Executor>
endpoints(Executor ex, asio::steady_timer& timer) :
_resolver(ex), _connect_timer(timer)
{}
endpoints(const endpoints&) = delete;
endpoints& operator=(const endpoints&) = delete;
void clone_servers(const endpoints& other) {
_servers = other._servers;
}
using executor_type = asio::ip::tcp::resolver::executor_type;
// NOTE: asio::ip::basic_resolver returns executor by value
executor_type get_executor() noexcept {
return _resolver.get_executor();
}
template <typename CompletionToken>
decltype(auto) async_next_endpoint(CompletionToken&& token) {
using Signature = void (error_code, epoints, authority_path);
auto initiation = [](auto handler, endpoints& self) {
resolve_op { self, std::move(handler) }.perform();
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this)
);
}
void brokers(std::string hosts, uint16_t default_port) {
namespace x3 = boost::spirit::x3;
_servers.clear();
std::string host, port, path;
// loosely based on RFC 3986
auto unreserved_ = x3::char_("-a-zA-Z_0-9._~");
auto digit_ = x3::char_("0-9");
auto separator_ = x3::char_(',');
auto host_ = as_<std::string>(+unreserved_)[to_(host)];
auto port_ = as_<std::string>(':' >> +digit_)[to_(port)];
auto path_ = as_<std::string>(x3::char_('/') >> *unreserved_)[to_(path)];
auto uri_ = *x3::omit[x3::space] >> (host_ >> *port_ >> *path_) >>
(*x3::omit[x3::space] >> x3::omit[separator_ | x3::eoi]);
for (auto b = hosts.begin(); b != hosts.end(); ) {
host.clear(); port.clear(); path.clear();
if (phrase_parse(b, hosts.end(), uri_, x3::eps(false))) {
_servers.push_back({
std::move(host),
port.empty()
? std::to_string(default_port)
: std::move(port),
std::move(path)
});
}
else b = hosts.end();
}
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_ENDPOINTS_HPP

View File

@ -1,135 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_PING_OP_HPP
#define ASYNC_MQTT5_PING_OP_HPP
#include <limits>
#include <chrono>
#include <memory>
#include <boost/asio/cancellation_state.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <boost/asio/steady_timer.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Executor>
class ping_op {
public:
using executor_type = Executor;
private:
using client_service = ClientService;
struct on_timer {};
struct on_pingreq {};
std::shared_ptr<client_service> _svc_ptr;
executor_type _executor;
std::unique_ptr<asio::steady_timer> _ping_timer;
asio::cancellation_state _cancellation_state;
public:
ping_op(
std::shared_ptr<client_service> svc_ptr,
executor_type ex
) :
_svc_ptr(std::move(svc_ptr)), _executor(ex),
_ping_timer(new asio::steady_timer(_svc_ptr->get_executor())),
_cancellation_state(
_svc_ptr->_cancel_ping.slot(),
asio::enable_total_cancellation {},
asio::enable_total_cancellation {}
)
{}
ping_op(ping_op&&) noexcept = default;
ping_op(const ping_op&) = delete;
ping_op& operator=(ping_op&&) noexcept = default;
ping_op& operator=(const ping_op&) = delete;
using allocator_type = asio::recycling_allocator<void>;
allocator_type get_allocator() const noexcept {
return allocator_type {};
}
using cancellation_slot_type = asio::cancellation_slot;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _cancellation_state.slot();
}
executor_type get_executor() const noexcept {
return _executor;
}
void perform() {
_ping_timer->expires_after(compute_wait_time());
_ping_timer->async_wait(
asio::prepend(std::move(*this), on_timer {})
);
}
void operator()(on_timer, error_code) {
if (
_cancellation_state.cancelled() == asio::cancellation_type::terminal ||
!_svc_ptr->is_open()
)
return;
else if (_cancellation_state.cancelled() == asio::cancellation_type::total) {
_cancellation_state.clear();
return perform();
}
auto pingreq = control_packet<allocator_type>::of(
no_pid, get_allocator(), encoders::encode_pingreq
);
auto wire_data = pingreq.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::consign(
asio::prepend(std::move(*this), on_pingreq {}),
std::move(pingreq)
)
);
}
void operator()(on_pingreq, error_code ec) {
if (
_cancellation_state.cancelled() == asio::cancellation_type::terminal ||
ec == asio::error::no_recovery
)
return;
_cancellation_state.clear();
perform();
}
private:
duration compute_wait_time() const {
auto negotiated_ka = _svc_ptr->negotiated_keep_alive();
return negotiated_ka ?
std::chrono::seconds(negotiated_ka) :
duration(std::numeric_limits<duration::rep>::max());
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_PING_OP_HPP

View File

@ -1,219 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_PUBLISH_REC_OP_HPP
#define ASYNC_MQTT5_PUBLISH_REC_OP_HPP
#include <cstdint>
#include <string>
#include <memory>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/property_types.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
class publish_rec_op {
using client_service = ClientService;
struct on_puback {};
struct on_pubrec {};
struct on_pubrel {};
struct on_pubcomp {};
std::shared_ptr<client_service> _svc_ptr;
decoders::publish_message _message;
public:
explicit publish_rec_op(std::shared_ptr<client_service> svc_ptr) :
_svc_ptr(std::move(svc_ptr))
{}
publish_rec_op(publish_rec_op&&) noexcept = default;
publish_rec_op(const publish_rec_op&) = delete;
publish_rec_op& operator=(publish_rec_op&&) noexcept = default;
publish_rec_op& operator=(const publish_rec_op&) = delete;
using allocator_type = asio::recycling_allocator<void>;
allocator_type get_allocator() const noexcept {
return allocator_type {};
}
using executor_type = typename client_service::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
}
void perform(decoders::publish_message message) {
auto flags = std::get<2>(message);
auto qos_bits = (flags >> 1) & 0b11;
if (qos_bits == 0b11)
return on_malformed_packet(
"Malformed PUBLISH received: QoS bits set to 0b11"
);
auto qos = qos_e(qos_bits);
_message = std::move(message);
if (qos == qos_e::at_most_once)
return complete();
auto packet_id = std::get<1>(_message);
if (qos == qos_e::at_least_once) {
auto puback = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_puback, *packet_id,
uint8_t(0), puback_props {}
);
return send_puback(std::move(puback));
}
// qos == qos_e::exactly_once
auto pubrec = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubrec, *packet_id,
uint8_t(0), pubrec_props {}
);
return send_pubrec(std::move(pubrec));
}
void send_puback(control_packet<allocator_type> puback) {
auto wire_data = puback.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::consign(
asio::prepend(std::move(*this), on_puback {}),
std::move(puback)
)
);
}
void operator()(on_puback, error_code ec) {
if (ec)
return;
complete();
}
void send_pubrec(control_packet<allocator_type> pubrec) {
auto wire_data = pubrec.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::prepend(std::move(*this), on_pubrec {}, std::move(pubrec))
);
}
void operator()(
on_pubrec, control_packet<allocator_type> packet,
error_code ec
) {
if (ec)
return;
wait_pubrel(packet.packet_id());
}
void wait_pubrel(uint16_t packet_id) {
_svc_ptr->async_wait_reply(
control_code_e::pubrel, packet_id,
asio::prepend(std::move(*this), on_pubrel {}, packet_id)
);
}
void operator()(
on_pubrel, uint16_t packet_id,
error_code ec, byte_citer first, byte_citer last
) {
if (ec == asio::error::try_again) // "resend unanswered"
return wait_pubrel(packet_id);
if (ec)
return;
auto pubrel = decoders::decode_pubrel(static_cast<uint32_t>(std::distance(first, last)), first);
if (!pubrel.has_value()) {
on_malformed_packet("Malformed PUBREL received: cannot decode");
return wait_pubrel(packet_id);
}
auto& [reason_code, props] = *pubrel;
auto rc = to_reason_code<reason_codes::category::pubrel>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBREL received: invalid Reason Code");
return wait_pubrel(packet_id);
}
auto pubcomp = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubcomp, packet_id,
uint8_t(0), pubcomp_props{}
);
send_pubcomp(std::move(pubcomp));
}
void send_pubcomp(control_packet<allocator_type> pubcomp) {
auto wire_data = pubcomp.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubcomp))
);
}
void operator()(
on_pubcomp, control_packet<allocator_type> packet,
error_code ec
) {
if (ec == asio::error::try_again)
return wait_pubrel(packet.packet_id());
if (ec)
return;
complete();
}
private:
void on_malformed_packet(const std::string& reason) {
auto props = disconnect_props {};
props[prop::reason_string] = reason;
return async_disconnect(
disconnect_rc_e::malformed_packet, props,
_svc_ptr, asio::detached
);
}
void complete() {
/* auto rv = */_svc_ptr->channel_store(std::move(_message));
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_PUBLISH_REC_OP_HPP

View File

@ -1,508 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
#define ASYNC_MQTT5_PUBLISH_SEND_OP_HPP
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/cancellable_handler.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
#include <async_mqtt5/detail/topic_validation.hpp>
#include <async_mqtt5/detail/utf8_mqtt.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <qos_e qos_type>
using on_publish_signature = std::conditional_t<
qos_type == qos_e::at_most_once,
void (error_code),
std::conditional_t<
qos_type == qos_e::at_least_once,
void (error_code, reason_code, puback_props),
void (error_code, reason_code, pubcomp_props)
>
>;
template <qos_e qos_type>
using on_publish_props_type = std::conditional_t<
qos_type == qos_e::at_most_once,
void,
std::conditional_t<
qos_type == qos_e::at_least_once,
puback_props,
pubcomp_props
>
>;
template <typename ClientService, typename Handler, qos_e qos_type>
class publish_send_op {
using client_service = ClientService;
struct on_publish {};
struct on_puback {};
struct on_pubrec {};
struct on_pubrel {};
struct on_pubcomp {};
std::shared_ptr<client_service> _svc_ptr;
using handler_type = cancellable_handler<
Handler,
typename client_service::executor_type
>;
handler_type _handler;
serial_num_t _serial_num;
public:
publish_send_op(
std::shared_ptr<client_service> svc_ptr,
Handler&& handler
) :
_svc_ptr(std::move(svc_ptr)),
_handler(std::move(handler), _svc_ptr->get_executor())
{
auto slot = asio::get_associated_cancellation_slot(_handler);
if (slot.is_connected())
slot.assign([&svc = *_svc_ptr](asio::cancellation_type_t) {
svc.cancel();
});
}
publish_send_op(publish_send_op&&) = default;
publish_send_op(const publish_send_op&) = delete;
publish_send_op& operator=(publish_send_op&&) = default;
publish_send_op& operator=(const publish_send_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
void perform(
std::string topic, std::string payload,
retain_e retain, const publish_props& props
) {
uint16_t packet_id = 0;
if constexpr (qos_type != qos_e::at_most_once) {
packet_id = _svc_ptr->allocate_pid();
if (packet_id == 0)
return complete_immediate(client::error::pid_overrun, packet_id);
}
auto ec = validate_publish(topic, payload, retain, props);
if (ec)
return complete_immediate(ec, packet_id);
_serial_num = _svc_ptr->next_serial_num();
auto publish = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_publish, packet_id,
std::move(topic), std::move(payload),
qos_type, retain, dup_e::no, props
);
auto max_packet_size = _svc_ptr->connack_property(prop::maximum_packet_size)
.value_or(default_max_send_size);
if (publish.size() > max_packet_size)
return complete_immediate(client::error::packet_too_large, packet_id);
send_publish(std::move(publish));
}
void send_publish(control_packet<allocator_type> publish) {
auto wire_data = publish.wire_data();
_svc_ptr->async_send(
wire_data,
_serial_num,
send_flag::throttled * (qos_type != qos_e::at_most_once),
asio::prepend(std::move(*this), on_publish {}, std::move(publish))
);
}
void resend_publish(control_packet<allocator_type> publish) {
if (_handler.cancelled() != asio::cancellation_type_t::none)
return complete(
asio::error::operation_aborted, publish.packet_id()
);
send_publish(std::move(publish));
}
void operator()(
on_publish, control_packet<allocator_type> publish,
error_code ec
) {
if (ec == asio::error::try_again)
return resend_publish(std::move(publish));
if constexpr (qos_type == qos_e::at_most_once)
return complete(ec);
else {
auto packet_id = publish.packet_id();
if (ec)
return complete(ec, packet_id);
if constexpr (qos_type == qos_e::at_least_once)
_svc_ptr->async_wait_reply(
control_code_e::puback, packet_id,
asio::prepend(
std::move(*this), on_puback {}, std::move(publish)
)
);
else if constexpr (qos_type == qos_e::exactly_once)
_svc_ptr->async_wait_reply(
control_code_e::pubrec, packet_id,
asio::prepend(
std::move(*this), on_pubrec {}, std::move(publish)
)
);
}
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::at_least_once, bool> = true
>
void operator()(
on_puback, control_packet<allocator_type> publish,
error_code ec, byte_citer first, byte_citer last
) {
if (ec == asio::error::try_again) // "resend unanswered"
return resend_publish(std::move(publish.set_dup()));
uint16_t packet_id = publish.packet_id();
if (ec)
return complete(ec, packet_id);
auto puback = decoders::decode_puback(
static_cast<uint32_t>(std::distance(first, last)), first
);
if (!puback.has_value()) {
on_malformed_packet("Malformed PUBACK: cannot decode");
return resend_publish(std::move(publish.set_dup()));
}
auto& [reason_code, props] = *puback;
auto rc = to_reason_code<reason_codes::category::puback>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBACK: invalid Reason Code");
return resend_publish(std::move(publish.set_dup()));
}
complete(ec, packet_id, *rc, std::move(props));
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubrec, control_packet<allocator_type> publish,
error_code ec, byte_citer first, byte_citer last
) {
if (ec == asio::error::try_again) // "resend unanswered"
return resend_publish(std::move(publish.set_dup()));
uint16_t packet_id = publish.packet_id();
if (ec)
return complete(ec, packet_id);
auto pubrec = decoders::decode_pubrec(
static_cast<uint32_t>(std::distance(first, last)), first
);
if (!pubrec.has_value()) {
on_malformed_packet("Malformed PUBREC: cannot decode");
return resend_publish(std::move(publish.set_dup()));
}
auto& [reason_code, props] = *pubrec;
auto rc = to_reason_code<reason_codes::category::pubrec>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBREC: invalid Reason Code");
return resend_publish(std::move(publish.set_dup()));
}
if (*rc)
return complete(ec, packet_id, *rc);
auto pubrel = control_packet<allocator_type>::of(
with_pid, get_allocator(),
encoders::encode_pubrel, packet_id,
0, pubrel_props {}
);
send_pubrel(std::move(pubrel), false);
}
void send_pubrel(control_packet<allocator_type> pubrel, bool throttled) {
auto wire_data = pubrel.wire_data();
_svc_ptr->async_send(
wire_data,
_serial_num,
(send_flag::throttled * throttled) | send_flag::prioritized,
asio::prepend(std::move(*this), on_pubrel {}, std::move(pubrel))
);
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubrel, control_packet<allocator_type> pubrel, error_code ec
) {
if (ec == asio::error::try_again)
return send_pubrel(std::move(pubrel), true);
uint16_t packet_id = pubrel.packet_id();
if (ec)
return complete(ec, packet_id);
_svc_ptr->async_wait_reply(
control_code_e::pubcomp, packet_id,
asio::prepend(std::move(*this), on_pubcomp {}, std::move(pubrel))
);
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::exactly_once, bool> = true
>
void operator()(
on_pubcomp, control_packet<allocator_type> pubrel,
error_code ec,
byte_citer first, byte_citer last
) {
if (ec == asio::error::try_again) // "resend unanswered"
return send_pubrel(std::move(pubrel), true);
uint16_t packet_id = pubrel.packet_id();
if (ec)
return complete(ec, packet_id);
auto pubcomp = decoders::decode_pubcomp(
static_cast<uint32_t>(std::distance(first, last)), first
);
if (!pubcomp.has_value()) {
on_malformed_packet("Malformed PUBCOMP: cannot decode");
return send_pubrel(std::move(pubrel), true);
}
auto& [reason_code, props] = *pubcomp;
auto rc = to_reason_code<reason_codes::category::pubcomp>(reason_code);
if (!rc) {
on_malformed_packet("Malformed PUBCOMP: invalid Reason Code");
return send_pubrel(std::move(pubrel), true);
}
return complete(ec, pubrel.packet_id(), *rc);
}
private:
error_code validate_publish(
const std::string& topic, const std::string& payload,
retain_e retain, const publish_props& props
) const {
constexpr uint8_t default_retain_available = 1;
constexpr uint8_t default_maximum_qos = 2;
constexpr uint8_t default_payload_format_ind = 0;
auto topic_name_valid = props[prop::topic_alias].has_value() ?
validate_topic_alias_name(topic) == validation_result::valid :
validate_topic_name(topic) == validation_result::valid
;
if (!topic_name_valid)
return client::error::invalid_topic;
auto max_qos = _svc_ptr->connack_property(prop::maximum_qos)
.value_or(default_maximum_qos);
auto retain_available = _svc_ptr->connack_property(prop::retain_available)
.value_or(default_retain_available);
if (uint8_t(qos_type) > max_qos)
return client::error::qos_not_supported;
if (retain_available == 0 && retain == retain_e::yes)
return client::error::retain_not_available;
auto payload_format_ind = props[prop::payload_format_indicator]
.value_or(default_payload_format_ind);
if (
payload_format_ind == 1 &&
validate_mqtt_utf8(payload) != validation_result::valid
)
return client::error::malformed_packet;
return validate_props(props);
}
error_code validate_props(const publish_props& props) const {
constexpr uint16_t default_topic_alias_max = 0;
const auto& topic_alias = props[prop::topic_alias];
if (topic_alias) {
auto topic_alias_max = _svc_ptr->connack_property(prop::topic_alias_maximum)
.value_or(default_topic_alias_max);
if (topic_alias_max == 0 || *topic_alias > topic_alias_max)
return client::error::topic_alias_maximum_reached;
if (*topic_alias == 0 )
return client::error::malformed_packet;
}
const auto& response_topic = props[prop::response_topic];
if (
response_topic &&
validate_topic_name(*response_topic) != validation_result::valid
)
return client::error::malformed_packet;
const auto& user_properties = props[prop::user_property];
for (const auto& user_property: user_properties)
if (!is_valid_string_pair(user_property))
return client::error::malformed_packet;
if (!props[prop::subscription_identifier].empty())
return client::error::malformed_packet;
const auto& content_type = props[prop::content_type];
if (
content_type &&
validate_mqtt_utf8(*content_type) != validation_result::valid
)
return client::error::malformed_packet;
return error_code {};
}
void on_malformed_packet(const std::string& reason) {
auto props = disconnect_props {};
props[prop::reason_string] = reason;
async_disconnect(
disconnect_rc_e::malformed_packet, props, _svc_ptr,
asio::detached
);
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::at_most_once, bool> = true
>
void complete(error_code ec, uint16_t = 0) {
_handler.complete(ec);
}
template <
qos_e q = qos_type,
std::enable_if_t<q == qos_e::at_most_once, bool> = true
>
void complete_immediate(error_code ec, uint16_t) {
_handler.complete_immediate(ec);
}
template <
typename Props = on_publish_props_type<qos_type>,
std::enable_if_t<
std::is_same_v<Props, puback_props> ||
std::is_same_v<Props, pubcomp_props>,
bool
> = true
>
void complete(
error_code ec, uint16_t packet_id,
reason_code rc = reason_codes::empty, Props&& props = Props {}
) {
_svc_ptr->free_pid(packet_id, true);
_handler.complete(ec, rc, std::forward<Props>(props));
}
template <
typename Props = on_publish_props_type<qos_type>,
std::enable_if_t<
std::is_same_v<Props, puback_props> ||
std::is_same_v<Props, pubcomp_props>,
bool
> = true
>
void complete_immediate(error_code ec, uint16_t packet_id) {
if (packet_id != 0)
_svc_ptr->free_pid(packet_id, false);
_handler.complete_immediate(ec, reason_codes::empty, Props {});
}
};
template <typename ClientService, qos_e qos_type>
class initiate_async_publish {
std::shared_ptr<ClientService> _svc_ptr;
public:
explicit initiate_async_publish(std::shared_ptr<ClientService> svc_ptr) :
_svc_ptr(std::move(svc_ptr))
{}
using executor_type = typename ClientService::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
}
template <typename Handler>
void operator()(
Handler&& handler,
std::string topic, std::string payload,
retain_e retain, const publish_props& props
) {
detail::publish_send_op<ClientService, Handler, qos_type> {
_svc_ptr, std::move(handler)
}.perform(
std::move(topic), std::move(payload), retain, props
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_PUBLISH_SEND_OP_HPP

View File

@ -1,152 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_RE_AUTH_OP_hpp
#define ASYNC_MQTT5_RE_AUTH_OP_hpp
#include <memory>
#include <string>
#include <boost/asio/detached.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/reason_codes.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/any_authenticator.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
#include <async_mqtt5/impl/codecs/message_encoders.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService>
class re_auth_op {
using client_service = ClientService;
struct on_auth_data {};
std::shared_ptr<client_service> _svc_ptr;
any_authenticator& _auth;
public:
explicit re_auth_op(std::shared_ptr<client_service> svc_ptr) :
_svc_ptr(std::move(svc_ptr)),
_auth(_svc_ptr->_stream_context.mqtt_context().authenticator)
{}
re_auth_op(re_auth_op&&) noexcept = default;
re_auth_op(const re_auth_op&) = delete;
re_auth_op& operator=(re_auth_op&&) noexcept = default;
re_auth_op& operator=(const re_auth_op&) = delete;
using allocator_type = asio::recycling_allocator<void>;
allocator_type get_allocator() const noexcept {
return allocator_type {};
}
using executor_type = typename client_service::executor_type;
executor_type get_executor() const noexcept {
return _svc_ptr->get_executor();
}
void perform() {
if (_auth.method().empty())
return;
auto auth_step = auth_step_e::client_initial;
return _auth.async_auth(
auth_step, "",
asio::prepend(std::move(*this), on_auth_data {}, auth_step)
);
}
void perform(decoders::auth_message auth_message) {
if (_auth.method().empty())
return on_auth_fail(
"Unexpected AUTH received",
disconnect_rc_e::protocol_error
);
const auto& [rc, props] = auth_message;
auto auth_rc = to_reason_code<reason_codes::category::auth>(rc);
if (!auth_rc.has_value())
return on_auth_fail(
"Malformed AUTH received: bad reason code",
disconnect_rc_e::malformed_packet
);
const auto& server_auth_method = props[prop::authentication_method];
if (!server_auth_method || *server_auth_method != _auth.method())
return on_auth_fail(
"Malformed AUTH received: wrong authentication method",
disconnect_rc_e::protocol_error
);
auto auth_step = auth_rc == reason_codes::success ?
auth_step_e::server_final : auth_step_e::server_challenge;
auto data = props[prop::authentication_data].value_or("");
return _auth.async_auth(
auth_step, std::move(data),
asio::prepend(std::move(*this), on_auth_data {}, auth_step)
);
}
void operator()(
on_auth_data, auth_step_e auth_step, error_code ec, std::string data
) {
if (ec)
return on_auth_fail(
"Re-authentication: authentication fail",
disconnect_rc_e::unspecified_error
);
if (auth_step == auth_step_e::server_final)
return;
auth_props props;
props[prop::authentication_method] = _auth.method();
props[prop::authentication_data] = std::move(data);
auto rc = auth_step == auth_step_e::client_initial ?
reason_codes::reauthenticate : reason_codes::continue_authentication;
auto packet = control_packet<allocator_type>::of(
no_pid, get_allocator(),
encoders::encode_auth,
rc.value(), props
);
auto wire_data = packet.wire_data();
_svc_ptr->async_send(
wire_data,
no_serial, send_flag::none,
asio::consign(asio::detached, std::move(packet))
);
}
private:
void on_auth_fail(std::string message, disconnect_rc_e reason) {
auto props = disconnect_props {};
props[prop::reason_string] = std::move(message);
async_disconnect(reason, props, _svc_ptr, asio::detached);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_RE_AUTH_OP_HPP

View File

@ -1,158 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_READ_MESSAGE_OP_HPP
#define ASYNC_MQTT5_READ_MESSAGE_OP_HPP
#include <chrono>
#include <cstdint>
#include <memory>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
#include <async_mqtt5/impl/publish_rec_op.hpp>
#include <async_mqtt5/impl/re_auth_op.hpp>
#include <async_mqtt5/impl/codecs/message_decoders.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Executor>
class read_message_op {
public:
using executor_type = Executor;
private:
using client_service = ClientService;
struct on_message {};
struct on_disconnect {};
std::shared_ptr<client_service> _svc_ptr;
executor_type _executor;
public:
read_message_op(
std::shared_ptr<client_service> svc_ptr,
executor_type ex
) :
_svc_ptr(std::move(svc_ptr)), _executor(ex)
{}
read_message_op(read_message_op&&) noexcept = default;
read_message_op(const read_message_op&) = delete;
read_message_op& operator=(read_message_op&&) noexcept = default;
read_message_op& operator=(const read_message_op&) = delete;
using allocator_type = asio::recycling_allocator<void>;
allocator_type get_allocator() const noexcept {
return allocator_type {};
}
executor_type get_executor() const noexcept {
return _executor;
}
void perform() {
_svc_ptr->async_assemble(
asio::prepend(std::move(*this), on_message {})
);
}
void operator()(
on_message, error_code ec,
uint8_t control_code,
byte_citer first, byte_citer last
) {
if (ec == client::error::malformed_packet)
return on_malformed_packet(
"Malformed Packet received from the Server"
);
if (ec == asio::error::no_recovery)
return _svc_ptr->cancel();
if (ec == asio::error::operation_aborted)
return;
dispatch(control_code, first, last);
}
void operator()(on_disconnect, error_code ec) {
if (!ec)
perform();
}
private:
void dispatch(
uint8_t control_byte,
byte_citer first, byte_citer last
) {
auto code = control_code_e(control_byte & 0b11110000);
switch (code) {
case control_code_e::publish: {
auto msg = decoders::decode_publish(
control_byte, static_cast<uint32_t>(std::distance(first, last)), first
);
if (!msg.has_value())
return on_malformed_packet(
"Malformed PUBLISH received: cannot decode"
);
publish_rec_op { _svc_ptr }.perform(std::move(*msg));
}
break;
case control_code_e::disconnect: {
_svc_ptr->close_stream();
_svc_ptr->open_stream();
}
break;
case control_code_e::auth: {
auto rv = decoders::decode_auth(
static_cast<uint32_t>(std::distance(first, last)), first
);
if (!rv.has_value())
return on_malformed_packet(
"Malformed AUTH received: cannot decode"
);
re_auth_op { _svc_ptr }.perform(std::move(*rv));
}
break;
default:
assert(false);
}
perform();
}
void on_malformed_packet(const std::string& reason) {
auto props = disconnect_props {};
props[prop::reason_string] = reason;
auto svc_ptr = _svc_ptr; // copy before this is moved
async_disconnect(
disconnect_rc_e::malformed_packet, props, svc_ptr,
asio::prepend(std::move(*this), on_disconnect {})
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_READ_MESSAGE_OP_HPP

View File

@ -1,140 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_READ_OP_HPP
#define ASYNC_MQTT5_READ_OP_HPP
#include <array>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
namespace asioex = boost::asio::experimental;
template <typename Owner, typename Handler>
class read_op {
struct on_read {};
struct on_reconnect {};
Owner& _owner;
using handler_type = Handler;
handler_type _handler;
public:
read_op(Owner& owner, Handler&& handler) :
_owner(owner), _handler(std::move(handler))
{}
read_op(read_op&&) = default;
read_op(const read_op&) = delete;
read_op& operator=(read_op&&) = default;
read_op& operator=(const read_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
template <typename BufferType>
void perform(
const BufferType& buffer, duration wait_for
) {
auto stream_ptr = _owner._stream_ptr;
if (_owner.was_connected()) {
_owner._read_timer.expires_after(wait_for);
auto timed_read = asioex::make_parallel_group(
stream_ptr->async_read_some(buffer, asio::deferred),
_owner._read_timer.async_wait(asio::deferred)
);
timed_read.async_wait(
asioex::wait_for_one(),
asio::prepend(std::move(*this), on_read {}, stream_ptr)
);
}
else
asio::post(
_owner.get_executor(),
asio::prepend(
std::move(*this), on_read {}, stream_ptr,
std::array<size_t, 2> { 0, 1 },
asio::error::not_connected, 0, error_code {}
)
);
}
void operator()(
on_read, typename Owner::stream_ptr stream_ptr,
std::array<std::size_t, 2> ord, error_code read_ec, size_t bytes_read,
error_code
) {
if (!_owner.is_open())
return complete(asio::error::operation_aborted, bytes_read);
error_code ec = ord[0] == 1 ? asio::error::timed_out : read_ec;
bytes_read = ord[0] == 0 ? bytes_read : 0;
if (!ec)
return complete(ec, bytes_read);
// websocket returns operation_aborted if disconnected
if (should_reconnect(ec) || ec == asio::error::operation_aborted)
return _owner.async_reconnect(
stream_ptr, asio::prepend(std::move(*this), on_reconnect {})
);
return complete(asio::error::no_recovery, bytes_read);
}
void operator()(on_reconnect, error_code ec) {
if ((ec == asio::error::operation_aborted && _owner.is_open()) || !ec)
ec = asio::error::try_again;
return complete(ec, 0);
}
private:
void complete(error_code ec, size_t bytes_read) {
std::move(_handler)(ec, bytes_read);
}
static bool should_reconnect(error_code ec) {
using namespace asio::error;
// note: Win ERROR_SEM_TIMEOUT == Posix ENOLINK (Reserved)
return ec.value() == 1236L || /* Win ERROR_CONNECTION_ABORTED */
ec.value() == 121L || /* Win ERROR_SEM_TIMEOUT */
ec == connection_aborted || ec == not_connected ||
ec == timed_out || ec == connection_reset ||
ec == broken_pipe || ec == asio::error::eof;
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_READ_OP_HPP

View File

@ -1,239 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_RECONNECT_OP_HPP
#define ASYNC_MQTT5_RECONNECT_OP_HPP
#include <array>
#include <chrono>
#include <memory>
#include <string>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/associated_cancellation_slot.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_smallint.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/async_traits.hpp>
#include <async_mqtt5/impl/connect_op.hpp>
namespace async_mqtt5::detail {
class exponential_backoff {
int _curr_exp { 0 };
static constexpr int _base_mulptilier = 1000;
static constexpr int _max_exp = 4;
// sizeof(_generator) = 8
boost::random::rand48 _generator { uint32_t(std::time(0)) };
boost::random::uniform_smallint<> _distribution { -500, 500 };
public:
exponential_backoff() = default;
duration generate() {
int exponent = _curr_exp < _max_exp ? _curr_exp++ : _max_exp;
int base = 1 << exponent;
return std::chrono::milliseconds(
base * _base_mulptilier + _distribution(_generator) /* noise */
);
}
};
namespace asio = boost::asio;
template <typename Owner>
class reconnect_op {
struct on_locked {};
struct on_next_endpoint {};
struct on_connect {};
struct on_backoff {};
Owner& _owner;
using handler_type = asio::any_completion_handler<void (error_code)>;
handler_type _handler;
std::unique_ptr<std::string> _buffer_ptr;
exponential_backoff _generator;
using endpoint = asio::ip::tcp::endpoint;
using epoints = asio::ip::tcp::resolver::results_type;
public:
template <typename Handler>
reconnect_op(Owner& owner, Handler&& handler) :
_owner(owner), _handler(std::move(handler))
{}
reconnect_op(reconnect_op&&) = default;
reconnect_op(const reconnect_op&) = delete;
reconnect_op& operator=(reconnect_op&&) = default;
reconnect_op& operator=(const reconnect_op&) = delete;
using allocator_type = asio::associated_allocator_t<handler_type>;
allocator_type get_allocator() const noexcept {
return asio::get_associated_allocator(_handler);
}
using cancellation_slot_type =
asio::associated_cancellation_slot_t<handler_type>;
cancellation_slot_type get_cancellation_slot() const noexcept {
return asio::get_associated_cancellation_slot(_handler);
}
using executor_type = asio::associated_executor_t<handler_type>;
executor_type get_executor() const noexcept {
return asio::get_associated_executor(_handler);
}
void perform(typename Owner::stream_ptr s) {
_owner._conn_mtx.lock(
asio::prepend(std::move(*this), on_locked {}, s)
);
}
void operator()(on_locked, typename Owner::stream_ptr s, error_code ec) {
if (ec == asio::error::operation_aborted)
// cancelled without acquiring the lock (by calling client.cancel())
return std::move(_handler)(ec);
if (!_owner.is_open())
return complete(asio::error::operation_aborted);
if (s != _owner._stream_ptr)
return complete(asio::error::try_again);
do_reconnect();
}
void do_reconnect() {
_owner._endpoints.async_next_endpoint(
asio::prepend(std::move(*this), on_next_endpoint {})
);
}
void backoff_and_reconnect() {
_owner._connect_timer.expires_after(_generator.generate());
_owner._connect_timer.async_wait(
asio::prepend(std::move(*this), on_backoff {})
);
}
void operator()(on_backoff, error_code ec) {
if (ec == asio::error::operation_aborted || !_owner.is_open())
return complete(asio::error::operation_aborted);
do_reconnect();
}
void operator()(
on_next_endpoint, error_code ec,
epoints eps, authority_path ap
) {
namespace asioex = boost::asio::experimental;
// the three error codes below are the only possible codes
// that may be returned from async_next_endpont
if (ec == asio::error::operation_aborted || !_owner.is_open())
return complete(asio::error::operation_aborted);
if (ec == asio::error::try_again)
return backoff_and_reconnect();
if (ec == asio::error::host_not_found)
return complete(asio::error::no_recovery);
auto sptr = _owner.construct_and_open_next_layer();
if constexpr (has_tls_context<typename Owner::stream_context_type>)
setup_tls_sni(
ap, _owner._stream_context.tls_context(), *sptr
);
// wait max 5 seconds for the connect (handshake) op to finish
_owner._connect_timer.expires_after(std::chrono::seconds(5));
auto init_connect = [](
auto handler, typename Owner::stream_type& stream,
mqtt_ctx& context, const epoints& eps, authority_path ap
) {
connect_op { stream, std::move(handler), context }
.perform(eps, std::move(ap));
};
auto timed_connect = asioex::make_parallel_group(
asio::async_initiate<const asio::deferred_t, void (error_code)>(
init_connect, asio::deferred, std::ref(*sptr),
std::ref(_owner._stream_context.mqtt_context()),
eps, std::move(ap)
),
_owner._connect_timer.async_wait(asio::deferred)
);
timed_connect.async_wait(
asioex::wait_for_one(),
asio::prepend(std::move(*this), on_connect {}, std::move(sptr))
);
}
void operator()(
on_connect, typename Owner::stream_ptr sptr,
std::array<std::size_t, 2> ord,
error_code connect_ec, error_code timer_ec
) {
// connect_ec may be any of stream.async_connect() error codes
// plus access_denied, connection_refused and
// client::error::malformed_packet
if (
ord[0] == 0 && connect_ec == asio::error::operation_aborted ||
ord[0] == 1 && timer_ec == asio::error::operation_aborted ||
!_owner.is_open()
)
return complete(asio::error::operation_aborted);
// operation timed out so retry
if (ord[0] == 1)
return do_reconnect();
if (connect_ec == asio::error::access_denied)
return complete(asio::error::no_recovery);
// retry for any other stream.async_connect() error or
// connection_refused, client::error::malformed_packet
if (connect_ec)
return do_reconnect();
_owner.replace_next_layer(std::move(sptr));
complete(error_code {});
}
private:
void complete(error_code ec) {
_owner._conn_mtx.unlock();
std::move(_handler)(ec);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_RECONNECT_OP_HPP

View File

@ -1,246 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_REPLIES_HPP
#define ASYNC_MQTT5_REPLIES_HPP
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <boost/asio/any_completion_handler.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/async_result.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/prepend.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/detail/control_packet.hpp>
#include <async_mqtt5/detail/internal_types.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
class replies {
public:
using executor_type = asio::any_io_executor;
private:
using Signature = void (error_code, byte_citer, byte_citer);
static constexpr auto max_reply_time = std::chrono::seconds(20);
class reply_handler {
asio::any_completion_handler<Signature> _handler;
control_code_e _code;
uint16_t _packet_id;
std::chrono::time_point<std::chrono::system_clock> _ts;
public:
template <typename H>
reply_handler(control_code_e code, uint16_t pid, H&& handler) :
_handler(std::forward<H>(handler)), _code(code), _packet_id(pid),
_ts(std::chrono::system_clock::now())
{}
reply_handler(reply_handler&&) = default;
reply_handler(const reply_handler&) = delete;
reply_handler& operator=(reply_handler&&) = default;
reply_handler& operator=(const reply_handler&) = delete;
void complete(
error_code ec,
byte_citer first = byte_citer {}, byte_citer last = byte_citer {}
) {
asio::dispatch(asio::prepend(std::move(_handler), ec, first, last));
}
void complete_post(const executor_type& ex, error_code ec) {
asio::post(
ex,
asio::prepend(
std::move(_handler), ec, byte_citer {}, byte_citer {}
)
);
}
uint16_t packet_id() const noexcept {
return _packet_id;
}
control_code_e code() const noexcept {
return _code;
}
auto time() const noexcept {
return _ts;
}
};
executor_type _ex;
using handlers = std::vector<reply_handler>;
handlers _handlers;
struct fast_reply {
control_code_e code;
uint16_t packet_id;
std::unique_ptr<std::string> packet;
};
using fast_replies = std::vector<fast_reply>;
fast_replies _fast_replies;
public:
template <typename Executor>
explicit replies(Executor ex) : _ex(std::move(ex)) {}
replies(replies&&) = default;
replies(const replies&) = delete;
replies& operator=(replies&&) = default;
replies& operator=(const replies&) = delete;
template <typename CompletionToken>
decltype(auto) async_wait_reply(
control_code_e code, uint16_t packet_id, CompletionToken&& token
) {
auto dup_handler_ptr = find_handler(code, packet_id);
if (dup_handler_ptr != _handlers.end()) {
dup_handler_ptr->complete_post(_ex, asio::error::operation_aborted);
_handlers.erase(dup_handler_ptr);
}
auto freply = find_fast_reply(code, packet_id);
if (freply == _fast_replies.end()) {
auto initiation = [](
auto handler, replies& self,
control_code_e code, uint16_t packet_id
) {
self._handlers.emplace_back(
code, packet_id, std::move(handler)
);
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::ref(*this), code, packet_id
);
}
auto fdata = std::move(*freply);
_fast_replies.erase(freply);
auto initiation = [](
auto handler, std::unique_ptr<std::string> packet,
const executor_type& ex
) {
byte_citer first = packet->cbegin();
byte_citer last = packet->cend();
asio::post(
ex,
asio::consign(
asio::prepend(
std::move(handler), error_code {}, first, last
),
std::move(packet)
)
);
};
return asio::async_initiate<CompletionToken, Signature>(
initiation, token, std::move(fdata.packet), _ex
);
}
void dispatch(
error_code ec, control_code_e code, uint16_t packet_id,
byte_citer first, byte_citer last
) {
auto handler_ptr = find_handler(code, packet_id);
if (handler_ptr == _handlers.end()) {
_fast_replies.push_back({
code, packet_id,
std::make_unique<std::string>(first, last)
});
return;
}
auto handler = std::move(*handler_ptr);
_handlers.erase(handler_ptr);
handler.complete(ec, first, last);
}
void resend_unanswered() {
auto ua = std::move(_handlers);
for (auto& h : ua)
h.complete(asio::error::try_again);
}
void cancel_unanswered() {
auto ua = std::move(_handlers);
for (auto& h : ua)
h.complete_post(_ex, asio::error::operation_aborted);
}
bool any_expired() {
auto now = std::chrono::system_clock::now();
return std::any_of(
_handlers.begin(), _handlers.end(),
[now](const auto& h) {
return now - h.time() > max_reply_time;
}
);
}
void clear_fast_replies() {
_fast_replies.clear();
}
void clear_pending_pubrels() {
for (auto it = _handlers.begin(); it != _handlers.end();) {
if (it->code() == control_code_e::pubrel) {
it->complete(asio::error::operation_aborted);
it = _handlers.erase(it);
}
else
++it;
}
}
private:
handlers::iterator find_handler(control_code_e code, uint16_t packet_id) {
return std::find_if(
_handlers.begin(), _handlers.end(),
[code, packet_id](const auto& h) {
return h.code() == code && h.packet_id() == packet_id;
}
);
}
fast_replies::iterator find_fast_reply(
control_code_e code, uint16_t packet_id
) {
return std::find_if(
_fast_replies.begin(), _fast_replies.end(),
[code, packet_id](const auto& f) {
return f.code == code && f.packet_id == packet_id;
}
);
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_REPLIES_HPP

View File

@ -1,107 +0,0 @@
//
// Copyright (c) 2023-2024 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef ASYNC_MQTT5_SENTRY_OP_HPP
#define ASYNC_MQTT5_SENTRY_OP_HPP
#include <chrono>
#include <memory>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/prepend.hpp>
#include <boost/asio/recycling_allocator.hpp>
#include <boost/asio/steady_timer.hpp>
#include <async_mqtt5/error.hpp>
#include <async_mqtt5/types.hpp>
#include <async_mqtt5/impl/disconnect_op.hpp>
namespace async_mqtt5::detail {
namespace asio = boost::asio;
template <typename ClientService, typename Executor>
class sentry_op {
public:
using executor_type = Executor;
private:
using client_service = ClientService;
struct on_timer {};
struct on_disconnect {};
static constexpr auto check_interval = std::chrono::seconds(3);
std::shared_ptr<client_service> _svc_ptr;
executor_type _executor;
std::unique_ptr<asio::steady_timer> _sentry_timer;
public:
sentry_op(
std::shared_ptr<client_service> svc_ptr, executor_type ex
) :
_svc_ptr(std::move(svc_ptr)), _executor(ex),
_sentry_timer(new asio::steady_timer(_svc_ptr->get_executor()))
{}
sentry_op(sentry_op&&) noexcept = default;
sentry_op(const sentry_op&) = delete;
sentry_op& operator=(sentry_op&&) noexcept = default;
sentry_op& operator=(const sentry_op&) = delete;
using allocator_type = asio::recycling_allocator<void>;
allocator_type get_allocator() const noexcept {
return allocator_type {};
}
using cancellation_slot_type = asio::cancellation_slot;
cancellation_slot_type get_cancellation_slot() const noexcept {
return _svc_ptr->_cancel_sentry.slot();
}
executor_type get_executor() const noexcept {
return _executor;
}
void perform() {
_sentry_timer->expires_after(check_interval);
_sentry_timer->async_wait(
asio::prepend(std::move(*this), on_timer {})
);
}
void operator()(on_timer, error_code ec) {
if (ec == asio::error::operation_aborted || !_svc_ptr->is_open())
return;
if (_svc_ptr->_replies.any_expired()) {
auto props = disconnect_props {};
// TODO add what packet was expected?
props[prop::reason_string] = "No reply received within 20 seconds";
auto svc_ptr = _svc_ptr;
return async_disconnect(
disconnect_rc_e::unspecified_error, props, svc_ptr,
asio::prepend(std::move(*this), on_disconnect {})
);
}
perform();
}
void operator()(on_disconnect, error_code ec) {
if (!ec)
perform();
}
};
} // end namespace async_mqtt5::detail
#endif // !ASYNC_MQTT5_SENTRY_OP_HPP

Some files were not shown because too many files have changed in this diff Show More