diff --git a/libs/cppzmq/.clang-format b/libs/cppzmq/.clang-format new file mode 100644 index 0000000..28f3353 --- /dev/null +++ b/libs/cppzmq/.clang-format @@ -0,0 +1,53 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: false + IndentBraces: false + +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: true +BinPackArguments: true +BinPackParameters: false +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortFunctionsOnASingleLine: InlineOnly +AlwaysBreakTemplateDeclarations: false +ColumnLimit: 85 +MaxEmptyLinesToKeep: 2 +KeepEmptyLinesAtTheStartOfBlocks: false +ContinuationIndentWidth: 2 +PointerAlignment: Right +ReflowComments: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 + +SortIncludes: false + +FixNamespaceComments: false +BreakBeforeBinaryOperators: NonAssignment +SpaceAfterTemplateKeyword: false +AlignAfterOpenBracket: Align +AlignOperands: true +BreakConstructorInitializers: AfterColon +ConstructorInitializerAllOnOneLineOrOnePerLine: true +SpaceAfterCStyleCast: true +BreakBeforeTernaryOperators: true diff --git a/libs/cppzmq/.github/workflows/ci.yml b/libs/cppzmq/.github/workflows/ci.yml new file mode 100644 index 0000000..ccceb96 --- /dev/null +++ b/libs/cppzmq/.github/workflows/ci.yml @@ -0,0 +1,195 @@ +name: CI + +on: [push, pull_request] + +defaults: + run: + shell: bash + +jobs: + tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest"] + cppstd: ["98", "11", "20"] + cc: ["gcc-10"] + cxx: ["g++-10"] + drafts: ["ON"] + libzmq: ["4.3.4"] + libzmqbuild: ["cmake"] + include: + # older libzmq and gcc without draft + - os: "ubuntu-18.04" + cppstd: "11" + cc: "gcc-7" + cxx: "g++-7" + drafts: "OFF" + libzmq: "4.2.0" + libzmqbuild: "pkgconfig" + # gcc 4.8 + - os: "ubuntu-18.04" + cppstd: "11" + cc: "gcc-4.8" + cxx: "g++-4.8" + drafts: "ON" + libzmq: "4.3.4" + libzmqbuild: "cmake" + aptinstall: "gcc-4.8 g++-4.8" + # gcc 5 + - os: "ubuntu-18.04" + cppstd: "11" + cc: "gcc-5" + cxx: "g++-5" + drafts: "ON" + libzmq: "4.3.4" + libzmqbuild: "cmake" + aptinstall: "gcc-5 g++-5" + # without draft + - os: "ubuntu-latest" + cppstd: "20" + cc: "gcc-10" + cxx: "g++-10" + drafts: "OFF" + libzmq: "4.3.4" + libzmqbuild: "cmake" + # coverage (gcc version should match gcov version) + - os: "ubuntu-latest" + cppstd: "17" + cc: "gcc-9" + cxx: "g++-9" + drafts: "ON" + libzmq: "4.3.4" + libzmqbuild: "cmake" + coverage: "-DCOVERAGE=ON" + aptinstall: "lcov" + # clang + - os: "ubuntu-latest" + cppstd: "17" + cc: "clang-12" + cxx: "clang++-12" + drafts: "ON" + libzmq: "4.3.4" + libzmqbuild: "cmake" + # macos + - os: "macos-latest" + cppstd: "17" + cc: "clang" + cxx: "clang++" + drafts: "OFF" + libzmq: "4.3.4" + libzmqbuild: false + brewinstall: "zeromq" + # windows + - os: "windows-2019" + cppstd: "14" + cc: "msbuild" + cxx: "msbuild" + drafts: "ON" + libzmq: "4.3.4" + libzmqbuild: "cmake" + platform: "-Ax64" + - os: "windows-latest" + cppstd: "20" + cc: "msbuild" + cxx: "msbuild" + drafts: "ON" + libzmq: "4.3.4" + libzmqbuild: "cmake" + platform: "-Ax64" + + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + VERBOSE: 1 + THREADS: 2 + BUILDTYPE: "Debug" + + steps: + - uses: actions/checkout@v2 + + - name: install_deps + run: | + if [ ! -z "${{ matrix.aptinstall }}" ]; then + sudo apt install -y ${{ matrix.aptinstall }} + fi + if [ ! -z "${{ matrix.brewinstall }}" ]; then + brew install ${{ matrix.brewinstall }} + fi + + - name: get_libzmq + run: | + curl -L https://github.com/zeromq/libzmq/archive/v${{ matrix.libzmq }}.tar.gz \ + >zeromq.tar.gz + tar -xvzf zeromq.tar.gz + + - name: build_libzmq_cmake + if: ${{ matrix.libzmqbuild == 'cmake' }} + run: | + cmake -Hlibzmq-${{ matrix.libzmq }} -Blibzmq-build ${{ matrix.platform}} \ + -DWITH_PERF_TOOL=OFF \ + -DZMQ_BUILD_TESTS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_DRAFTS=${{ matrix.drafts }} + cmake --build libzmq-build --config ${BUILDTYPE} -j ${THREADS} + echo "LIBZMQ=${PWD}/libzmq-build" >> ${GITHUB_ENV} + + - name: build_libzmq_pkgconfig + if: ${{ matrix.libzmqbuild == 'pkgconfig' }} + working-directory: libzmq-${{ matrix.libzmq }} + run: | + ./autogen.sh && + ./configure --prefix=${PWD}/libzmq-build && + make -j ${THREADS} + make install + echo "LIBZMQ=${PWD}/libzmq-build" >> ${GITHUB_ENV} + + - name: build + env: + CMAKE_PREFIX_PATH: ${{ env.LIBZMQ }} + run: | + cmake -H. -Bbuild ${{ matrix.platform}} ${{ matrix.coverage }} \ + -DCMAKE_BUILD_TYPE=${BUILDTYPE} \ + -DENABLE_DRAFTS=${{ matrix.drafts }} \ + -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} + cmake --build build --config ${BUILDTYPE} -j ${THREADS} + echo "CPPZMQ=${PWD}/build" >> ${GITHUB_ENV} + + - name: test + # for unknown reason no tests are found and run on windows + # could be something to do with catch_discover_tests not working? + run: | + cd ${{ env.CPPZMQ }} + ctest -V -C ${BUILDTYPE} + + - name: demo + # probably need to install libzmq and cppzmq for this to work on windows + if: ${{ matrix.os == 'ubuntu*' }} + env: + CMAKE_PREFIX_PATH: ${{ env.LIBZMQ }}:${{ env.CPPZMQ }} + run: | + cd demo + cmake -H. -Bbuild ${{ matrix.platform}} \ + -DCMAKE_BUILD_TYPE=${BUILDTYPE} \ + -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} + cmake --build build --config ${BUILDTYPE} + cd build + ctest -V -C ${BUILDTYPE} + + - name: lcov + if: ${{ matrix.coverage && success() }} + run: | + lcov --capture --directory . --output-file coverage.info + lcov --remove coverage.info -o coverage_filtered.info \ + '/usr/include/*' \ + '/usr/local/include/*' \ + ${PWD}'/tests/*' \ + ${PWD}'/build/*' + # to generate local html: genhtml coverage_filtered.info --output-directory . + + - name: coveralls_upload + if: ${{ matrix.coverage && success() }} + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./coverage_filtered.info diff --git a/libs/cppzmq/.gitignore b/libs/cppzmq/.gitignore new file mode 100644 index 0000000..cb4c751 --- /dev/null +++ b/libs/cppzmq/.gitignore @@ -0,0 +1,5 @@ +# Vim tmp files +*.swp + +# Build directory +*build/ diff --git a/libs/cppzmq/CMakeLists.txt b/libs/cppzmq/CMakeLists.txt new file mode 100644 index 0000000..05001ad --- /dev/null +++ b/libs/cppzmq/CMakeLists.txt @@ -0,0 +1,112 @@ +cmake_minimum_required(VERSION 3.11) + +list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include (DetectCPPZMQVersion) + +project(cppzmq VERSION ${DETECTED_CPPZMQ_VERSION}) + +if (NOT TARGET libzmq AND NOT TARGET libzmq-static) + find_package(ZeroMQ QUIET) + + # libzmq autotools install: fallback to pkg-config + if(NOT ZeroMQ_FOUND) + message(STATUS "CMake libzmq package not found, trying again with pkg-config (normal install of zeromq)") + list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/libzmq-pkg-config) + find_package(ZeroMQ REQUIRED) + endif() + + # TODO "REQUIRED" above should already cause a fatal failure if not found, but this doesn't seem to work + if(NOT ZeroMQ_FOUND) + message(FATAL_ERROR "ZeroMQ was not found, neither as a CMake package nor via pkg-config") + endif() + + if (ZeroMQ_FOUND AND NOT (TARGET libzmq OR TARGET libzmq-static)) + message(FATAL_ERROR "ZeroMQ version not supported!") + endif() +endif() + +if (EXISTS "${CMAKE_SOURCE_DIR}/.git") + OPTION (ENABLE_DRAFTS "Build and install draft classes and methods" ON) +else () + OPTION (ENABLE_DRAFTS "Build and install draft classes and methods" OFF) +endif () +if (ENABLE_DRAFTS) + ADD_DEFINITIONS (-DZMQ_BUILD_DRAFT_API) + set (pkg_config_defines "-DZMQ_BUILD_DRAFT_API=1") +else (ENABLE_DRAFTS) + set (pkg_config_defines "") +endif (ENABLE_DRAFTS) + +message(STATUS "cppzmq v${cppzmq_VERSION}") + +set(CPPZMQ_HEADERS + zmq.hpp + zmq_addon.hpp +) + +foreach (target cppzmq cppzmq-static) + add_library(${target} INTERFACE) + target_include_directories(${target} INTERFACE $ + $) +endforeach() + +target_link_libraries(cppzmq INTERFACE libzmq) +target_link_libraries(cppzmq-static INTERFACE libzmq-static) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +install(TARGETS cppzmq cppzmq-static + EXPORT ${PROJECT_NAME}-targets) + +install(FILES ${CPPZMQ_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# GNUInstallDirs "DATADIR" wrong here; CMake search path wants "share". +set(CPPZMQ_CMAKECONFIG_INSTALL_DIR "share/cmake/${PROJECT_NAME}" CACHE STRING "install path for cppzmqConfig.cmake") + +configure_file(libzmq-pkg-config/FindZeroMQ.cmake + libzmq-pkg-config/FindZeroMQ.cmake + COPYONLY) + +export(EXPORT ${PROJECT_NAME}-targets + FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake") +configure_package_config_file(${PROJECT_NAME}Config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION ${CPPZMQ_CMAKECONFIG_INSTALL_DIR}) +# Workaround until ARCH_INDEPENDENT flag can be used with cmake 3.14. +# The ConigVersion.cmake file contains checks for the architecture is was +# generated on, which can cause problems for header only libraries +# used with e.g. the Conan package manager. Since it is header only we +# can/should omit those checks. +set(CPPZMQ_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) +set(CMAKE_SIZEOF_VOID_P "") # a simple unset is not sufficient +write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${CPPZMQ_VERSION} + COMPATIBILITY AnyNewerVersion) +set(CMAKE_SIZEOF_VOID_P ${CPPZMQ_SIZEOF_VOID_P}) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cppzmq.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/cppzmq.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cppzmq.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +install(EXPORT ${PROJECT_NAME}-targets + FILE ${PROJECT_NAME}Targets.cmake + DESTINATION ${CPPZMQ_CMAKECONFIG_INSTALL_DIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + DESTINATION ${CPPZMQ_CMAKECONFIG_INSTALL_DIR}) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libzmq-pkg-config/FindZeroMQ.cmake + DESTINATION ${CPPZMQ_CMAKECONFIG_INSTALL_DIR}/libzmq-pkg-config) + +option(CPPZMQ_BUILD_TESTS "Whether or not to build the tests" ON) + +if (CPPZMQ_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) + if (CMAKE_CXX_STANDARD AND NOT CMAKE_CXX_STANDARD EQUAL 98 AND CMAKE_CXX_STANDARD GREATER_EQUAL 11) + add_subdirectory(examples) + endif() +endif() diff --git a/libs/cppzmq/LICENSE b/libs/cppzmq/LICENSE new file mode 100644 index 0000000..ae98bd8 --- /dev/null +++ b/libs/cppzmq/LICENSE @@ -0,0 +1,17 @@ + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. diff --git a/libs/cppzmq/README.md b/libs/cppzmq/README.md new file mode 100644 index 0000000..e2bea0b --- /dev/null +++ b/libs/cppzmq/README.md @@ -0,0 +1,196 @@ +[![CI](https://github.com/zeromq/cppzmq/actions/workflows/ci.yml/badge.svg)](https://github.com/zeromq/cppzmq/actions) +[![Coverage Status](https://coveralls.io/repos/github/zeromq/cppzmq/badge.svg?branch=master)](https://coveralls.io/github/zeromq/cppzmq?branch=master) +[![License](https://img.shields.io/github/license/zeromq/cppzmq.svg)](https://github.com/zeromq/cppzmq/blob/master/LICENSE) + +Introduction & Design Goals +=========================== + +cppzmq is a C++ binding for libzmq. It has the following design goals: + - cppzmq maps the libzmq C API to C++ concepts. In particular: + - it is type-safe (the libzmq C API exposes various class-like concepts as void*) + - it provides exception-based error handling (the libzmq C API provides errno-based error handling) + - it provides RAII-style classes that automate resource management (the libzmq C API requires the user to take care to free resources explicitly) + - cppzmq is a light-weight, header-only binding. You only need to include the header file zmq.hpp (and maybe zmq_addon.hpp) to use it. + - zmq.hpp is meant to contain direct mappings of the abstractions provided by the libzmq C API, while zmq_addon.hpp provides additional higher-level abstractions. + +There are other C++ bindings for ZeroMQ with different design goals. In particular, none of the following bindings are header-only: + - [zmqpp](https://github.com/zeromq/zmqpp) is a high-level binding to libzmq. + - [czmqpp](https://github.com/zeromq/czmqpp) is a binding based on the high-level czmq API. + - [fbzmq](https://github.com/facebook/fbzmq) is a binding that integrates with Apache Thrift and provides higher-level abstractions in addition. It requires C++14. + +Supported platforms +=================== + + - Only a subset of the platforms that are supported by libzmq itself are supported. Some features already require a compiler supporting C++11. In the future, probably all features will require C++11. To build and run the tests, CMake and Catch are required. + - Any libzmq 4.x version is expected to work. DRAFT features may only work for the most recent tested version. Currently explicitly tested libzmq versions are + - 4.2.0 (without DRAFT API) + - 4.3.4 (with and without DRAFT API) + - Platforms with full support (i.e. CI executing build and tests) + - Ubuntu 18.04 x64 (with gcc 4.8.5, 5.5.0, 7.5.0) + - Ubuntu 20.04 x64 (with gcc 9.3.0, 10.3.0 and clang 12) + - Visual Studio 2017 x64 + - Visual Studio 2019 x64 + - macOS 10.15 (with clang 12, without DRAFT API) + - Additional platforms that are known to work: + - We have no current reports on additional platforms that are known to work yet. Please add your platform here. If CI can be provided for them with a cloud-based CI service working with GitHub, you are invited to add CI, and make it possible to be included in the list above. + - Additional platforms that probably work: + - Any platform supported by libzmq that provides a sufficiently recent gcc (4.8.1 or newer) or clang (3.4.1 or newer) + - Visual Studio 2012+ x86/x64 + +Examples +======== +These examples require at least C++11. +```c++ +#include + +int main() +{ + zmq::context_t ctx; + zmq::socket_t sock(ctx, zmq::socket_type::push); + sock.bind("inproc://test"); + sock.send(zmq::str_buffer("Hello, world"), zmq::send_flags::dontwait); +} +``` +This a more complex example where we send and receive multi-part messages over TCP with a wildcard port. +```c++ +#include +#include + +int main() +{ + zmq::context_t ctx; + zmq::socket_t sock1(ctx, zmq::socket_type::push); + zmq::socket_t sock2(ctx, zmq::socket_type::pull); + sock1.bind("tcp://127.0.0.1:*"); + const std::string last_endpoint = + sock1.get(zmq::sockopt::last_endpoint); + std::cout << "Connecting to " + << last_endpoint << std::endl; + sock2.connect(last_endpoint); + + std::array send_msgs = { + zmq::str_buffer("foo"), + zmq::str_buffer("bar!") + }; + if (!zmq::send_multipart(sock1, send_msgs)) + return 1; + + std::vector recv_msgs; + const auto ret = zmq::recv_multipart( + sock2, std::back_inserter(recv_msgs)); + if (!ret) + return 1; + std::cout << "Got " << *ret + << " messages" << std::endl; + return 0; +} +``` + +See the `examples` directory for more examples. When the project is compiled with tests enabled, each example gets compiled to an executable. + + +API Overview +============ + +For an extensive overview of the `zmq.hpp` API in use, see this [Tour of CPPZMQ by @brettviren](https://brettviren.github.io/cppzmq-tour/index.html). + +Bindings for libzmq in `zmq.hpp`: + +Types: +* class `zmq::context_t` +* enum `zmq::ctxopt` +* class `zmq::socket_t` +* class `zmq::socket_ref` +* enum `zmq::socket_type` +* enum `zmq::sockopt` +* enum `zmq::send_flags` +* enum `zmq::recv_flags` +* class `zmq::message_t` +* class `zmq::const_buffer` +* class `zmq::mutable_buffer` +* struct `zmq::recv_buffer_size` +* alias `zmq::send_result_t` +* alias `zmq::recv_result_t` +* alias `zmq::recv_buffer_result_t` +* class `zmq::error_t` +* class `zmq::monitor_t` +* struct `zmq_event_t`, +* alias `zmq::free_fn`, +* alias `zmq::pollitem_t`, +* alias `zmq::fd_t` +* class `zmq::poller_t` DRAFT +* enum `zmq::event_flags` DRAFT +* enum `zmq::poller_event` DRAFT + +Functions: +* `zmq::version` +* `zmq::poll` +* `zmq::proxy` +* `zmq::proxy_steerable` +* `zmq::buffer` +* `zmq::str_buffer` + +Extra high-level types and functions `zmq_addon.hpp`: + +Types: +* class `zmq::multipart_t` +* class `zmq::active_poller_t` DRAFT + +Functions: +* `zmq::recv_multipart` +* `zmq::send_multipart` +* `zmq::send_multipart_n` +* `zmq::encode` +* `zmq::decode` + +Compatibility Guidelines +======================== + +The users of cppzmq are expected to follow the guidelines below to ensure not to break when upgrading cppzmq to newer versions (non-exhaustive list): + +* Do not depend on any macros defined in cppzmq unless explicitly declared public here. + +The following macros may be used by consumers of cppzmq: `CPPZMQ_VERSION`, `CPPZMQ_VERSION_MAJOR`, `CPPZMQ_VERSION_MINOR`, `CPPZMQ_VERSION_PATCH`. + +Contribution policy +=================== + +The contribution policy is at: http://rfc.zeromq.org/spec:22 + +Build instructions +================== + +Build steps: + +1. Build [libzmq](https://github.com/zeromq/libzmq) via cmake. This does an out of source build and installs the build files + - download and unzip the lib, cd to directory + - mkdir build + - cd build + - cmake .. + - sudo make -j4 install + +2. Build cppzmq via cmake. This does an out of source build and installs the build files + - download and unzip the lib, cd to directory + - mkdir build + - cd build + - cmake .. + - sudo make -j4 install + +3. Build cppzmq via [vcpkg](https://github.com/Microsoft/vcpkg/). This does an out of source build and installs the build files + - git clone https://github.com/Microsoft/vcpkg.git + - cd vcpkg + - ./bootstrap-vcpkg.sh # bootstrap-vcpkg.bat for Powershell + - ./vcpkg integrate install + - ./vcpkg install cppzmq + +Using this: + +A cmake find package scripts is provided for you to easily include this library. +Add these lines in your CMakeLists.txt to include the headers and library files of +cpp zmq (which will also include libzmq for you). + +``` +#find cppzmq wrapper, installed by make of cppzmq +find_package(cppzmq) +target_link_libraries(*Your Project Name* cppzmq) +``` diff --git a/libs/cppzmq/cmake/DetectCPPZMQVersion.cmake b/libs/cppzmq/cmake/DetectCPPZMQVersion.cmake new file mode 100644 index 0000000..99ed9bf --- /dev/null +++ b/libs/cppzmq/cmake/DetectCPPZMQVersion.cmake @@ -0,0 +1,8 @@ + +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/zmq.hpp" _CPPZMQ_H_CONTENTS) +string(REGEX REPLACE ".*#define CPPZMQ_VERSION_MAJOR ([0-9]+).*" "\\1" DETECTED_CPPZMQ_VERSION_MAJOR "${_CPPZMQ_H_CONTENTS}") +string(REGEX REPLACE ".*#define CPPZMQ_VERSION_MINOR ([0-9]+).*" "\\1" DETECTED_CPPZMQ_VERSION_MINOR "${_CPPZMQ_H_CONTENTS}") +string(REGEX REPLACE ".*#define CPPZMQ_VERSION_PATCH ([0-9]+).*" "\\1" DETECTED_CPPZMQ_VERSION_PATCH "${_CPPZMQ_H_CONTENTS}") +set(DETECTED_CPPZMQ_VERSION "${DETECTED_CPPZMQ_VERSION_MAJOR}.${DETECTED_CPPZMQ_VERSION_MINOR}.${DETECTED_CPPZMQ_VERSION_PATCH}") + +message(STATUS "Detected CPPZMQ Version - ${DETECTED_CPPZMQ_VERSION}") diff --git a/libs/cppzmq/cppzmq.pc.in b/libs/cppzmq/cppzmq.pc.in new file mode 100644 index 0000000..3dc945d --- /dev/null +++ b/libs/cppzmq/cppzmq.pc.in @@ -0,0 +1,9 @@ +prefix="@CMAKE_INSTALL_PREFIX@" +includedir="@CMAKE_INSTALL_FULL_INCLUDEDIR@" + +Name: @PROJECT_NAME@ +Description: C++ binding for libzmq +URL: https://github.com/zeromq/cppzmq +Version: @PROJECT_VERSION@ +Requires: libzmq +Cflags: -I"${includedir}" @pkg_config_defines@ diff --git a/libs/cppzmq/cppzmqConfig.cmake.in b/libs/cppzmq/cppzmqConfig.cmake.in new file mode 100644 index 0000000..f596517 --- /dev/null +++ b/libs/cppzmq/cppzmqConfig.cmake.in @@ -0,0 +1,36 @@ +# cppzmq cmake module +# +# The following import targets are created +# +# :: +# +# cppzmq-static +# cppzmq +# +# This module sets the following variables in your project:: +# +# cppzmq_FOUND - true if cppzmq found on the system +# cppzmq_INCLUDE_DIR - the directory containing cppzmq headers +# cppzmq_LIBRARY - the ZeroMQ library for dynamic linking +# cppzmq_STATIC_LIBRARY - the ZeroMQ library for static linking + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_package(ZeroMQ QUIET) + +# libzmq autotools install: fallback to pkg-config +if(NOT ZeroMQ_FOUND) + list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/libzmq-pkg-config) + find_package(ZeroMQ REQUIRED) +endif() + +if(NOT ZeroMQ_FOUND) + message(FATAL_ERROR "ZeroMQ was NOT found!") +endif() + +if(NOT TARGET @PROJECT_NAME@) + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") + get_target_property(@PROJECT_NAME@_INCLUDE_DIR cppzmq INTERFACE_INCLUDE_DIRECTORIES) +endif() + diff --git a/libs/cppzmq/demo/CMakeLists.txt b/libs/cppzmq/demo/CMakeLists.txt new file mode 100644 index 0000000..1c90825 --- /dev/null +++ b/libs/cppzmq/demo/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(cppzmq-demo CXX) + +find_package(cppzmq) + +enable_testing() +add_executable( + demo + main.cpp + ) + +target_link_libraries( + demo + cppzmq + ) + +add_test( + NAME + demo + COMMAND + ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/demo + ) diff --git a/libs/cppzmq/demo/main.cpp b/libs/cppzmq/demo/main.cpp new file mode 100644 index 0000000..077fc98 --- /dev/null +++ b/libs/cppzmq/demo/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char **argv) +{ + zmq::context_t context; + return 0; +} diff --git a/libs/cppzmq/examples/CMakeLists.txt b/libs/cppzmq/examples/CMakeLists.txt new file mode 100644 index 0000000..1b47e03 --- /dev/null +++ b/libs/cppzmq/examples/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(cppzmq-examples CXX) + +# place binaries and libraries according to GNU standards + +include(GNUInstallDirs) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + +find_package(Threads) +find_package(cppzmq) + +add_executable( + pubsub_multithread_inproc + pubsub_multithread_inproc.cpp +) +target_link_libraries( + pubsub_multithread_inproc + PRIVATE cppzmq ${CMAKE_THREAD_LIBS_INIT} +) + +add_executable( + hello_world + hello_world.cpp +) +target_link_libraries( + hello_world + PRIVATE cppzmq ${CMAKE_THREAD_LIBS_INIT} +) + +add_executable( + multipart_messages + multipart_messages.cpp +) +target_link_libraries( + multipart_messages + PRIVATE cppzmq ${CMAKE_THREAD_LIBS_INIT} +) diff --git a/libs/cppzmq/examples/hello_world.cpp b/libs/cppzmq/examples/hello_world.cpp new file mode 100644 index 0000000..8719448 --- /dev/null +++ b/libs/cppzmq/examples/hello_world.cpp @@ -0,0 +1,9 @@ +#include + +int main() +{ + zmq::context_t ctx; + zmq::socket_t sock(ctx, zmq::socket_type::push); + sock.bind("inproc://test"); + sock.send(zmq::str_buffer("Hello, world"), zmq::send_flags::dontwait); +} diff --git a/libs/cppzmq/examples/multipart_messages.cpp b/libs/cppzmq/examples/multipart_messages.cpp new file mode 100644 index 0000000..1af8f8f --- /dev/null +++ b/libs/cppzmq/examples/multipart_messages.cpp @@ -0,0 +1,31 @@ +#include +#include + +int main() +{ + zmq::context_t ctx; + zmq::socket_t sock1(ctx, zmq::socket_type::push); + zmq::socket_t sock2(ctx, zmq::socket_type::pull); + sock1.bind("tcp://127.0.0.1:*"); + const std::string last_endpoint = + sock1.get(zmq::sockopt::last_endpoint); + std::cout << "Connecting to " + << last_endpoint << std::endl; + sock2.connect(last_endpoint); + + std::array send_msgs = { + zmq::str_buffer("foo"), + zmq::str_buffer("bar!") + }; + if (!zmq::send_multipart(sock1, send_msgs)) + return 1; + + std::vector recv_msgs; + const auto ret = zmq::recv_multipart( + sock2, std::back_inserter(recv_msgs)); + if (!ret) + return 1; + std::cout << "Got " << *ret + << " messages" << std::endl; + return 0; +} diff --git a/libs/cppzmq/examples/pubsub_multithread_inproc.cpp b/libs/cppzmq/examples/pubsub_multithread_inproc.cpp new file mode 100644 index 0000000..2727f7b --- /dev/null +++ b/libs/cppzmq/examples/pubsub_multithread_inproc.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +#include "zmq.hpp" +#include "zmq_addon.hpp" + +void PublisherThread(zmq::context_t *ctx) { + // Prepare publisher + zmq::socket_t publisher(*ctx, zmq::socket_type::pub); + publisher.bind("inproc://#1"); + + // Give the subscribers a chance to connect, so they don't lose any messages + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + while (true) { + // Write three messages, each with an envelope and content + publisher.send(zmq::str_buffer("A"), zmq::send_flags::sndmore); + publisher.send(zmq::str_buffer("Message in A envelope")); + publisher.send(zmq::str_buffer("B"), zmq::send_flags::sndmore); + publisher.send(zmq::str_buffer("Message in B envelope")); + publisher.send(zmq::str_buffer("C"), zmq::send_flags::sndmore); + publisher.send(zmq::str_buffer("Message in C envelope")); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +void SubscriberThread1(zmq::context_t *ctx) { + // Prepare subscriber + zmq::socket_t subscriber(*ctx, zmq::socket_type::sub); + subscriber.connect("inproc://#1"); + + // Thread2 opens "A" and "B" envelopes + subscriber.set(zmq::sockopt::subscribe, "A"); + subscriber.set(zmq::sockopt::subscribe, "B"); + + while (1) { + // Receive all parts of the message + std::vector recv_msgs; + zmq::recv_result_t result = + zmq::recv_multipart(subscriber, std::back_inserter(recv_msgs)); + assert(result && "recv failed"); + assert(*result == 2); + + std::cout << "Thread2: [" << recv_msgs[0].to_string() << "] " + << recv_msgs[1].to_string() << std::endl; + } +} + +void SubscriberThread2(zmq::context_t *ctx) { + // Prepare our context and subscriber + zmq::socket_t subscriber(*ctx, zmq::socket_type::sub); + subscriber.connect("inproc://#1"); + + // Thread3 opens ALL envelopes + subscriber.set(zmq::sockopt::subscribe, ""); + + while (1) { + // Receive all parts of the message + std::vector recv_msgs; + zmq::recv_result_t result = + zmq::recv_multipart(subscriber, std::back_inserter(recv_msgs)); + assert(result && "recv failed"); + assert(*result == 2); + + std::cout << "Thread3: [" << recv_msgs[0].to_string() << "] " + << recv_msgs[1].to_string() << std::endl; + } +} + +int main() { + /* + * No I/O threads are involved in passing messages using the inproc transport. + * Therefore, if you are using a ØMQ context for in-process messaging only you + * can initialise the context with zero I/O threads. + * + * Source: http://api.zeromq.org/4-3:zmq-inproc + */ + zmq::context_t ctx(0); + + auto thread1 = std::async(std::launch::async, PublisherThread, &ctx); + + // Give the publisher a chance to bind, since inproc requires it + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + auto thread2 = std::async(std::launch::async, SubscriberThread1, &ctx); + auto thread3 = std::async(std::launch::async, SubscriberThread2, &ctx); + thread1.wait(); + thread2.wait(); + thread3.wait(); + + /* + * Output: + * An infinite loop of a mix of: + * Thread2: [A] Message in A envelope + * Thread2: [B] Message in B envelope + * Thread3: [A] Message in A envelope + * Thread3: [B] Message in B envelope + * Thread3: [C] Message in C envelope + */ +} diff --git a/libs/cppzmq/libzmq-pkg-config/FindZeroMQ.cmake b/libs/cppzmq/libzmq-pkg-config/FindZeroMQ.cmake new file mode 100644 index 0000000..c07c0f6 --- /dev/null +++ b/libs/cppzmq/libzmq-pkg-config/FindZeroMQ.cmake @@ -0,0 +1,34 @@ +set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) +find_package(PkgConfig) +pkg_check_modules(PC_LIBZMQ QUIET libzmq) + +set(ZeroMQ_VERSION ${PC_LIBZMQ_VERSION}) + +find_path(ZeroMQ_INCLUDE_DIR zmq.h + PATHS ${ZeroMQ_DIR}/include + ${PC_LIBZMQ_INCLUDE_DIRS}) + +find_library(ZeroMQ_LIBRARY + NAMES zmq + PATHS ${ZeroMQ_DIR}/lib + ${PC_LIBZMQ_LIBDIR} + ${PC_LIBZMQ_LIBRARY_DIRS}) + +if(ZeroMQ_LIBRARY) + set(ZeroMQ_FOUND ON) +endif() + +set ( ZeroMQ_LIBRARIES ${ZeroMQ_LIBRARY} ) +set ( ZeroMQ_INCLUDE_DIRS ${ZeroMQ_INCLUDE_DIR} ) + +if(NOT TARGET libzmq) + add_library(libzmq UNKNOWN IMPORTED) + set_target_properties(libzmq PROPERTIES + IMPORTED_LOCATION ${ZeroMQ_LIBRARIES} + INTERFACE_INCLUDE_DIRECTORIES ${ZeroMQ_INCLUDE_DIRS}) +endif() + +include ( FindPackageHandleStandardArgs ) +# handle the QUIETLY and REQUIRED arguments and set ZMQ_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args ( ZeroMQ DEFAULT_MSG ZeroMQ_LIBRARIES ZeroMQ_INCLUDE_DIRS ) \ No newline at end of file diff --git a/libs/cppzmq/tests/CMakeLists.txt b/libs/cppzmq/tests/CMakeLists.txt new file mode 100644 index 0000000..6075df8 --- /dev/null +++ b/libs/cppzmq/tests/CMakeLists.txt @@ -0,0 +1,53 @@ +find_package(Threads) + +find_package(Catch2 QUIET) + +if (NOT Catch2_FOUND) + include(FetchContent) + + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v2.13.9) + + FetchContent_MakeAvailable(Catch2) + + list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) +endif() + +add_executable( + unit_tests + buffer.cpp + message.cpp + context.cpp + socket.cpp + socket_ref.cpp + poller.cpp + active_poller.cpp + multipart.cpp + recv_multipart.cpp + send_multipart.cpp + codec_multipart.cpp + monitor.cpp + utilities.cpp +) + +target_include_directories(unit_tests PUBLIC ${CATCH_MODULE_PATH}) +target_link_libraries( + unit_tests + PRIVATE Catch2::Catch2 + PRIVATE cppzmq + PRIVATE ${CMAKE_THREAD_LIBS_INIT} +) + +OPTION (COVERAGE "Enable gcda file generation needed by lcov" OFF) + +if (COVERAGE) + target_compile_options(unit_tests PRIVATE --coverage) + target_link_options(unit_tests PRIVATE --coverage) + message(STATUS "Coverage enabled") +endif() + +include(CTest) +include(Catch) +catch_discover_tests(unit_tests) diff --git a/libs/cppzmq/tests/active_poller.cpp b/libs/cppzmq/tests/active_poller.cpp new file mode 100644 index 0000000..224b371 --- /dev/null +++ b/libs/cppzmq/tests/active_poller.cpp @@ -0,0 +1,446 @@ +#include + +#include "testutil.hpp" + +#if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) && defined(ZMQ_BUILD_DRAFT_API) + +#include +#include + +TEST_CASE("create destroy", "[active_poller]") +{ + zmq::active_poller_t active_poller; + CHECK(active_poller.empty()); +} + +static_assert(!std::is_copy_constructible::value, + "active_poller_t should not be copy-constructible"); +static_assert(!std::is_copy_assignable::value, + "active_poller_t should not be copy-assignable"); + +static const zmq::active_poller_t::handler_type no_op_handler = + [](zmq::event_flags) {}; + +TEST_CASE("move construct empty", "[active_poller]") +{ + zmq::active_poller_t a; + CHECK(a.empty()); + zmq::active_poller_t b = std::move(a); + CHECK(b.empty()); + CHECK(0u == a.size()); + CHECK(0u == b.size()); +} + +TEST_CASE("move assign empty", "[active_poller]") +{ + zmq::active_poller_t a; + CHECK(a.empty()); + zmq::active_poller_t b; + CHECK(b.empty()); + b = std::move(a); + CHECK(0u == a.size()); + CHECK(0u == b.size()); + CHECK(a.empty()); + CHECK(b.empty()); +} + +TEST_CASE("move construct non empty", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + + zmq::active_poller_t a; + a.add(socket, zmq::event_flags::pollin, [](zmq::event_flags) {}); + CHECK_FALSE(a.empty()); + CHECK(1u == a.size()); + zmq::active_poller_t b = std::move(a); + CHECK(a.empty()); + CHECK(0u == a.size()); + CHECK_FALSE(b.empty()); + CHECK(1u == b.size()); +} + +TEST_CASE("move assign non empty", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + + zmq::active_poller_t a; + a.add(socket, zmq::event_flags::pollin, no_op_handler); + CHECK_FALSE(a.empty()); + CHECK(1u == a.size()); + zmq::active_poller_t b; + b = std::move(a); + CHECK(a.empty()); + CHECK(0u == a.size()); + CHECK_FALSE(b.empty()); + CHECK(1u == b.size()); +} + +TEST_CASE("add handler", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + CHECK_NOTHROW( + active_poller.add(socket, zmq::event_flags::pollin, no_op_handler)); +} + +TEST_CASE("add null handler fails", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + zmq::active_poller_t::handler_type handler; + CHECK_THROWS_AS(active_poller.add(socket, zmq::event_flags::pollin, handler), + std::invalid_argument); +} + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 3, 0) +// this behaviour was added by https://github.com/zeromq/libzmq/pull/3100 +TEST_CASE("add handler invalid events type", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + short invalid_events_type = 2 << 10; + CHECK_THROWS_AS( + active_poller.add(socket, static_cast(invalid_events_type), + no_op_handler), + zmq::error_t); + CHECK(active_poller.empty()); + CHECK(0u == active_poller.size()); +} +#endif + +TEST_CASE("add handler twice throws", "[active_poller]") +{ + common_server_client_setup s; + + CHECK(s.client.send(zmq::message_t{}, zmq::send_flags::none)); + + zmq::active_poller_t active_poller; + bool message_received = false; + active_poller.add( + s.server, zmq::event_flags::pollin, + [&message_received](zmq::event_flags) { message_received = true; }); + CHECK_THROWS_ZMQ_ERROR( + EINVAL, active_poller.add(s.server, zmq::event_flags::pollin, no_op_handler)); + CHECK(1 == active_poller.wait(std::chrono::milliseconds{-1})); + CHECK(message_received); // handler unmodified +} + +TEST_CASE("wait with no handlers throws", "[active_poller]") +{ + zmq::active_poller_t active_poller; + CHECK_THROWS_ZMQ_ERROR(EFAULT, + active_poller.wait(std::chrono::milliseconds{10})); +} + +TEST_CASE("remove unregistered throws", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + CHECK_THROWS_ZMQ_ERROR(EINVAL, active_poller.remove(socket)); +} + +TEST_CASE("remove registered empty", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + active_poller.add(socket, zmq::event_flags::pollin, no_op_handler); + CHECK_NOTHROW(active_poller.remove(socket)); +} + +TEST_CASE("remove registered non empty", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + active_poller.add(socket, zmq::event_flags::pollin, no_op_handler); + CHECK_NOTHROW(active_poller.remove(socket)); +} + +namespace +{ +struct server_client_setup : common_server_client_setup +{ + zmq::active_poller_t::handler_type handler = [&](zmq::event_flags e) { + events = e; + }; + + zmq::event_flags events = zmq::event_flags::none; +}; + +const std::string hi_str = "Hi"; + +} + +TEST_CASE("poll basic", "[active_poller]") +{ + server_client_setup s; + + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + + zmq::active_poller_t active_poller; + bool message_received = false; + zmq::active_poller_t::handler_type handler = + [&message_received](zmq::event_flags events) { + CHECK(zmq::event_flags::none != (events & zmq::event_flags::pollin)); + message_received = true; + }; + CHECK_NOTHROW(active_poller.add(s.server, zmq::event_flags::pollin, handler)); + CHECK(1 == active_poller.wait(std::chrono::milliseconds{-1})); + CHECK(message_received); +} + +/// \todo this contains multiple test cases that should be split up +TEST_CASE("client server", "[active_poller]") +{ + const std::string send_msg = hi_str; + + // Setup server and client + server_client_setup s; + + // Setup active_poller + zmq::active_poller_t active_poller; + zmq::event_flags events; + zmq::active_poller_t::handler_type handler = [&](zmq::event_flags e) { + if (zmq::event_flags::none != (e & zmq::event_flags::pollin)) { + zmq::message_t zmq_msg; + CHECK_NOTHROW(s.server.recv(zmq_msg)); // get message + std::string recv_msg(zmq_msg.data(), zmq_msg.size()); + CHECK(send_msg == recv_msg); + } else if (zmq::event_flags::none != (e & ~zmq::event_flags::pollout)) { + INFO("Unexpected event type " << static_cast(events)); + REQUIRE(false); + } + events = e; + }; + + CHECK_NOTHROW(active_poller.add(s.server, zmq::event_flags::pollin, handler)); + + // client sends message + CHECK_NOTHROW(s.client.send(zmq::message_t{send_msg}, zmq::send_flags::none)); + + CHECK(1 == active_poller.wait(std::chrono::milliseconds{-1})); + CHECK(events == zmq::event_flags::pollin); + + // Re-add server socket with pollout flag + CHECK_NOTHROW(active_poller.remove(s.server)); + CHECK_NOTHROW(active_poller.add( + s.server, zmq::event_flags::pollin | zmq::event_flags::pollout, handler)); + CHECK(1 == active_poller.wait(std::chrono::milliseconds{-1})); + CHECK(events == zmq::event_flags::pollout); +} + +TEST_CASE("add invalid socket throws", "[active_poller]") +{ + zmq::context_t context; + zmq::active_poller_t active_poller; + zmq::socket_t a{context, zmq::socket_type::router}; + zmq::socket_t b{std::move(a)}; + CHECK_THROWS_AS(active_poller.add(a, zmq::event_flags::pollin, no_op_handler), + zmq::error_t); +} + +TEST_CASE("remove invalid socket throws", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::active_poller_t active_poller; + CHECK_NOTHROW( + active_poller.add(socket, zmq::event_flags::pollin, no_op_handler)); + CHECK(1u == active_poller.size()); + std::vector sockets; + sockets.emplace_back(std::move(socket)); + CHECK_THROWS_AS(active_poller.remove(socket), zmq::error_t); + CHECK(1u == active_poller.size()); +} + +TEST_CASE("wait on added empty handler", "[active_poller]") +{ + server_client_setup s; + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + zmq::active_poller_t active_poller; + CHECK_NOTHROW( + active_poller.add(s.server, zmq::event_flags::pollin, no_op_handler)); + CHECK_NOTHROW(active_poller.wait(std::chrono::milliseconds{-1})); +} + +TEST_CASE("modify empty throws", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::push}; + zmq::active_poller_t active_poller; + CHECK_THROWS_AS(active_poller.modify(socket, zmq::event_flags::pollin), + zmq::error_t); +} + +TEST_CASE("modify invalid socket throws", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t a{context, zmq::socket_type::push}; + zmq::socket_t b{std::move(a)}; + zmq::active_poller_t active_poller; + CHECK_THROWS_AS(active_poller.modify(a, zmq::event_flags::pollin), + zmq::error_t); +} + +TEST_CASE("modify not added throws", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t a{context, zmq::socket_type::push}; + zmq::socket_t b{context, zmq::socket_type::push}; + zmq::active_poller_t active_poller; + CHECK_NOTHROW(active_poller.add(a, zmq::event_flags::pollin, no_op_handler)); + CHECK_THROWS_AS(active_poller.modify(b, zmq::event_flags::pollin), + zmq::error_t); +} + +TEST_CASE("modify simple", "[active_poller]") +{ + zmq::context_t context; + zmq::socket_t a{context, zmq::socket_type::push}; + zmq::active_poller_t active_poller; + CHECK_NOTHROW(active_poller.add(a, zmq::event_flags::pollin, no_op_handler)); + CHECK_NOTHROW( + active_poller.modify(a, zmq::event_flags::pollin | zmq::event_flags::pollout)); +} + +TEST_CASE("poll client server", "[active_poller]") +{ + // Setup server and client + server_client_setup s; + + // Setup active_poller + zmq::active_poller_t active_poller; + CHECK_NOTHROW(active_poller.add(s.server, zmq::event_flags::pollin, s.handler)); + + // client sends message + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + + // wait for message and verify events + CHECK_NOTHROW(active_poller.wait(std::chrono::milliseconds{500})); + CHECK(s.events == zmq::event_flags::pollin); + + // Modify server socket with pollout flag + CHECK_NOTHROW(active_poller.modify(s.server, zmq::event_flags::pollin + | zmq::event_flags::pollout)); + CHECK(1 == active_poller.wait(std::chrono::milliseconds{500})); + CHECK(s.events == (zmq::event_flags::pollin | zmq::event_flags::pollout)); +} + +TEST_CASE("wait one return", "[active_poller]") +{ + // Setup server and client + server_client_setup s; + + int count = 0; + + // Setup active_poller + zmq::active_poller_t active_poller; + CHECK_NOTHROW(active_poller.add(s.server, zmq::event_flags::pollin, + [&count](zmq::event_flags) { ++count; })); + + // client sends message + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + + // wait for message and verify events + CHECK(1 == active_poller.wait(std::chrono::milliseconds{500})); + CHECK(1u == count); +} + +TEST_CASE("wait on move constructed active_poller", "[active_poller]") +{ + server_client_setup s; + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + zmq::active_poller_t a; + CHECK_NOTHROW(a.add(s.server, zmq::event_flags::pollin, no_op_handler)); + zmq::active_poller_t b{std::move(a)}; + CHECK(1u == b.size()); + CHECK(0u == a.size()); + CHECK_THROWS_ZMQ_ERROR(EFAULT, a.wait(std::chrono::milliseconds{10})); + CHECK(b.wait(std::chrono::milliseconds{-1})); +} + +TEST_CASE("wait on move assigned active_poller", "[active_poller]") +{ + server_client_setup s; + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + zmq::active_poller_t a; + CHECK_NOTHROW(a.add(s.server, zmq::event_flags::pollin, no_op_handler)); + zmq::active_poller_t b; + b = {std::move(a)}; + CHECK(1u == b.size()); + CHECK(0u == a.size()); + CHECK_THROWS_ZMQ_ERROR(EFAULT, a.wait(std::chrono::milliseconds{10})); + CHECK(b.wait(std::chrono::milliseconds{-1})); +} + +TEST_CASE("received on move constructed active_poller", "[active_poller]") +{ + // Setup server and client + server_client_setup s; + int count = 0; + // Setup active_poller a + zmq::active_poller_t a; + CHECK_NOTHROW(a.add(s.server, zmq::event_flags::pollin, + [&count](zmq::event_flags) { ++count; })); + // client sends message + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + // wait for message and verify it is received + CHECK(1 == a.wait(std::chrono::milliseconds{500})); + CHECK(1u == count); + // Move construct active_poller b + zmq::active_poller_t b{std::move(a)}; + // client sends message again + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + // wait for message and verify it is received + CHECK(1 == b.wait(std::chrono::milliseconds{500})); + CHECK(2u == count); +} + + +TEST_CASE("remove from handler", "[active_poller]") +{ + constexpr size_t ITER_NO = 10; + + // Setup servers and clients + std::vector setup_list; + for (size_t i = 0; i < ITER_NO; ++i) + setup_list.emplace_back(server_client_setup{}); + + // Setup active_poller + zmq::active_poller_t active_poller; + int count = 0; + for (size_t i = 0; i < ITER_NO; ++i) { + CHECK_NOTHROW(active_poller.add( + setup_list[i].server, zmq::event_flags::pollin, + [&, i](zmq::event_flags events) { + CHECK(events == zmq::event_flags::pollin); + active_poller.remove(setup_list[ITER_NO - i - 1].server); + CHECK((ITER_NO - i - 1) == active_poller.size()); + })); + ++count; + } + CHECK(ITER_NO == active_poller.size()); + // Clients send messages + for (auto &s : setup_list) { + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + } + + // Wait for all servers to receive a message + for (auto &s : setup_list) { + zmq::pollitem_t items[] = {{s.server, 0, ZMQ_POLLIN, 0}}; + zmq::poll(&items[0], 1); + } + + // Fire all handlers in one wait + CHECK(ITER_NO == active_poller.wait(std::chrono::milliseconds{-1})); + CHECK(ITER_NO == count); +} + +#endif diff --git a/libs/cppzmq/tests/buffer.cpp b/libs/cppzmq/tests/buffer.cpp new file mode 100644 index 0000000..da8a514 --- /dev/null +++ b/libs/cppzmq/tests/buffer.cpp @@ -0,0 +1,306 @@ +#include +#include + +#ifdef ZMQ_CPP17 +static_assert(std::is_nothrow_swappable_v); +static_assert(std::is_nothrow_swappable_v); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_copyable_v); +#endif + +#ifdef ZMQ_CPP11 + +using BT = int16_t; + +TEST_CASE("buffer default ctor", "[buffer]") +{ + constexpr zmq::mutable_buffer mb; + constexpr zmq::const_buffer cb; + CHECK(mb.size() == 0); + CHECK(mb.data() == nullptr); + CHECK(cb.size() == 0); + CHECK(cb.data() == nullptr); +} + +TEST_CASE("buffer data ctor", "[buffer]") +{ + std::vector v(10); + zmq::const_buffer cb(v.data(), v.size() * sizeof(BT)); + CHECK(cb.size() == v.size() * sizeof(BT)); + CHECK(cb.data() == v.data()); + zmq::mutable_buffer mb(v.data(), v.size() * sizeof(BT)); + CHECK(mb.size() == v.size() * sizeof(BT)); + CHECK(mb.data() == v.data()); + zmq::const_buffer from_mut = mb; + CHECK(mb.size() == from_mut.size()); + CHECK(mb.data() == from_mut.data()); + const auto cmb = mb; + static_assert(std::is_same::value, ""); + + constexpr const void *cp = nullptr; + constexpr void *p = nullptr; + constexpr zmq::const_buffer cecb = zmq::buffer(p, 0); + constexpr zmq::mutable_buffer cemb = zmq::buffer(p, 0); + CHECK(cecb.data() == nullptr); + CHECK(cemb.data() == nullptr); +} + +TEST_CASE("const_buffer operator+", "[buffer]") +{ + std::vector v(10); + zmq::const_buffer cb(v.data(), v.size() * sizeof(BT)); + const size_t shift = 4; + auto shifted = cb + shift; + CHECK(shifted.size() == v.size() * sizeof(BT) - shift); + CHECK(shifted.data() == v.data() + shift / sizeof(BT)); + auto shifted2 = shift + cb; + CHECK(shifted.size() == shifted2.size()); + CHECK(shifted.data() == shifted2.data()); + auto cbinp = cb; + cbinp += shift; + CHECK(shifted.size() == cbinp.size()); + CHECK(shifted.data() == cbinp.data()); +} + +TEST_CASE("mutable_buffer operator+", "[buffer]") +{ + std::vector v(10); + zmq::mutable_buffer mb(v.data(), v.size() * sizeof(BT)); + const size_t shift = 4; + auto shifted = mb + shift; + CHECK(shifted.size() == v.size() * sizeof(BT) - shift); + CHECK(shifted.data() == v.data() + shift / sizeof(BT)); + auto shifted2 = shift + mb; + CHECK(shifted.size() == shifted2.size()); + CHECK(shifted.data() == shifted2.data()); + auto mbinp = mb; + mbinp += shift; + CHECK(shifted.size() == mbinp.size()); + CHECK(shifted.data() == mbinp.data()); +} + +TEST_CASE("mutable_buffer creation basic", "[buffer]") +{ + std::vector v(10); + zmq::mutable_buffer mb(v.data(), v.size() * sizeof(BT)); + zmq::mutable_buffer mb2 = zmq::buffer(v.data(), v.size() * sizeof(BT)); + CHECK(mb.data() == mb2.data()); + CHECK(mb.size() == mb2.size()); + zmq::mutable_buffer mb3 = zmq::buffer(mb); + CHECK(mb.data() == mb3.data()); + CHECK(mb.size() == mb3.size()); + zmq::mutable_buffer mb4 = zmq::buffer(mb, 10 * v.size() * sizeof(BT)); + CHECK(mb.data() == mb4.data()); + CHECK(mb.size() == mb4.size()); + zmq::mutable_buffer mb5 = zmq::buffer(mb, 4); + CHECK(mb.data() == mb5.data()); + CHECK(4 == mb5.size()); +} + +TEST_CASE("const_buffer creation basic", "[buffer]") +{ + const std::vector v(10); + zmq::const_buffer cb(v.data(), v.size() * sizeof(BT)); + zmq::const_buffer cb2 = zmq::buffer(v.data(), v.size() * sizeof(BT)); + CHECK(cb.data() == cb2.data()); + CHECK(cb.size() == cb2.size()); + zmq::const_buffer cb3 = zmq::buffer(cb); + CHECK(cb.data() == cb3.data()); + CHECK(cb.size() == cb3.size()); + zmq::const_buffer cb4 = zmq::buffer(cb, 10 * v.size() * sizeof(BT)); + CHECK(cb.data() == cb4.data()); + CHECK(cb.size() == cb4.size()); + zmq::const_buffer cb5 = zmq::buffer(cb, 4); + CHECK(cb.data() == cb5.data()); + CHECK(4 == cb5.size()); +} + +TEST_CASE("mutable_buffer creation C array", "[buffer]") +{ + BT d[10] = {}; + zmq::mutable_buffer b = zmq::buffer(d); + CHECK(b.size() == 10 * sizeof(BT)); + CHECK(b.data() == static_cast(d)); + zmq::const_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == static_cast(d)); +} + +TEST_CASE("const_buffer creation C array", "[buffer]") +{ + const BT d[10] = {}; + zmq::const_buffer b = zmq::buffer(d); + CHECK(b.size() == 10 * sizeof(BT)); + CHECK(b.data() == static_cast(d)); + zmq::const_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == static_cast(d)); +} + +TEST_CASE("mutable_buffer creation array", "[buffer]") +{ + std::array d = {}; + zmq::mutable_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(BT)); + CHECK(b.data() == d.data()); + zmq::mutable_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); +} + +TEST_CASE("const_buffer creation array", "[buffer]") +{ + const std::array d = {}; + zmq::const_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(BT)); + CHECK(b.data() == d.data()); + zmq::const_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); +} + +TEST_CASE("const_buffer creation array 2", "[buffer]") +{ + std::array d = {{}}; + zmq::const_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(BT)); + CHECK(b.data() == d.data()); + zmq::const_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); +} + +TEST_CASE("mutable_buffer creation vector", "[buffer]") +{ + std::vector d(10); + zmq::mutable_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(BT)); + CHECK(b.data() == d.data()); + zmq::mutable_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); + d.clear(); + b = zmq::buffer(d); + CHECK(b.size() == 0); + CHECK(b.data() == nullptr); +} + +TEST_CASE("const_buffer creation vector", "[buffer]") +{ + std::vector d(10); + zmq::const_buffer b = zmq::buffer(static_cast &>(d)); + CHECK(b.size() == d.size() * sizeof(BT)); + CHECK(b.data() == d.data()); + zmq::const_buffer b2 = zmq::buffer(static_cast &>(d), 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); + d.clear(); + b = zmq::buffer(static_cast &>(d)); + CHECK(b.size() == 0); + CHECK(b.data() == nullptr); +} + +TEST_CASE("const_buffer creation string", "[buffer]") +{ + const std::wstring d(10, L'a'); + zmq::const_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(wchar_t)); + CHECK(b.data() == d.data()); + zmq::const_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); +} + +TEST_CASE("mutable_buffer creation string", "[buffer]") +{ + std::wstring d(10, L'a'); + zmq::mutable_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(wchar_t)); + CHECK(b.data() == d.data()); + zmq::mutable_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); +} + +#if CPPZMQ_HAS_STRING_VIEW +TEST_CASE("const_buffer creation string_view", "[buffer]") +{ + std::wstring dstr(10, L'a'); + std::wstring_view d = dstr; + zmq::const_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(wchar_t)); + CHECK(b.data() == d.data()); + zmq::const_buffer b2 = zmq::buffer(d, 4); + CHECK(b2.size() == 4); + CHECK(b2.data() == d.data()); +} +#endif + +TEST_CASE("const_buffer creation with str_buffer", "[buffer]") +{ + const wchar_t wd[10] = {}; + zmq::const_buffer b = zmq::str_buffer(wd); + CHECK(b.size() == 9 * sizeof(wchar_t)); + CHECK(b.data() == static_cast(wd)); + + zmq::const_buffer b2_null = zmq::buffer("hello"); + constexpr zmq::const_buffer b2 = zmq::str_buffer("hello"); + CHECK(b2_null.size() == 6); + CHECK(b2.size() == 5); + CHECK(std::string(static_cast(b2.data()), b2.size()) == "hello"); +} + +TEST_CASE("const_buffer creation with zbuf string literal char", "[buffer]") +{ + using namespace zmq::literals; + constexpr zmq::const_buffer b = "hello"_zbuf; + CHECK(b.size() == 5); + CHECK(std::memcmp(b.data(), "hello", b.size()) == 0); +} + +TEST_CASE("const_buffer creation with zbuf string literal wchar_t", "[buffer]") +{ + using namespace zmq::literals; + constexpr zmq::const_buffer b = L"hello"_zbuf; + CHECK(b.size() == 5 * sizeof(wchar_t)); + CHECK(std::memcmp(b.data(), L"hello", b.size()) == 0); +} + +TEST_CASE("const_buffer creation with zbuf string literal char16_t", "[buffer]") +{ + using namespace zmq::literals; + constexpr zmq::const_buffer b = u"hello"_zbuf; + CHECK(b.size() == 5 * sizeof(char16_t)); + CHECK(std::memcmp(b.data(), u"hello", b.size()) == 0); +} + +TEST_CASE("const_buffer creation with zbuf string literal char32_t", "[buffer]") +{ + using namespace zmq::literals; + constexpr zmq::const_buffer b = U"hello"_zbuf; + CHECK(b.size() == 5 * sizeof(char32_t)); + CHECK(std::memcmp(b.data(), U"hello", b.size()) == 0); +} + +TEST_CASE("buffer of structs", "[buffer]") +{ + struct some_pod + { + int64_t val; + char arr[8]; + }; + struct some_non_pod + { + int64_t val; + char arr[8]; + std::vector s; // not trivially copyable + }; + static_assert(zmq::detail::is_pod_like::value, ""); + static_assert(!zmq::detail::is_pod_like::value, ""); + std::array d; + zmq::mutable_buffer b = zmq::buffer(d); + CHECK(b.size() == d.size() * sizeof(some_pod)); + CHECK(b.data() == d.data()); +} + +#endif diff --git a/libs/cppzmq/tests/codec_multipart.cpp b/libs/cppzmq/tests/codec_multipart.cpp new file mode 100644 index 0000000..a5cb1e8 --- /dev/null +++ b/libs/cppzmq/tests/codec_multipart.cpp @@ -0,0 +1,210 @@ +#include +#include + +#ifdef ZMQ_CPP11 + +TEST_CASE("multipart codec empty", "[codec_multipart]") +{ + using namespace zmq; + + multipart_t mmsg; + message_t msg = mmsg.encode(); + CHECK(msg.size() == 0); + + multipart_t mmsg2; + mmsg2.decode_append(msg); + CHECK(mmsg2.size() == 0); + +} + +TEST_CASE("multipart codec small", "[codec_multipart]") +{ + using namespace zmq; + + multipart_t mmsg; + mmsg.addstr("Hello World"); + message_t msg = mmsg.encode(); + CHECK(msg.size() == 1 + 11); // small size packing + + mmsg.addstr("Second frame"); + msg = mmsg.encode(); + CHECK(msg.size() == 1 + 11 + 1 + 12); + + multipart_t mmsg2; + mmsg2.decode_append(msg); + CHECK(mmsg2.size() == 2); + std::string part0 = mmsg2[0].to_string(); + CHECK(part0 == "Hello World"); + CHECK(mmsg2[1].to_string() == "Second frame"); +} + +TEST_CASE("multipart codec big", "[codec_multipart]") +{ + using namespace zmq; + + message_t big(495); // large size packing + big.data()[0] = 'X'; + + multipart_t mmsg; + mmsg.pushmem(big.data(), big.size()); + message_t msg = mmsg.encode(); + CHECK(msg.size() == 5 + 495); + CHECK(msg.data()[0] == std::numeric_limits::max()); + CHECK(msg.data()[5] == 'X'); + + CHECK(mmsg.size() == 1); + mmsg.decode_append(msg); + CHECK(mmsg.size() == 2); + CHECK(mmsg[0].data()[0] == 'X'); +} + +TEST_CASE("multipart codec decode bad data overflow", "[codec_multipart]") +{ + using namespace zmq; + + char bad_data[3] = {5, 'h', 'i'}; + message_t wrong_size(bad_data, 3); + CHECK(wrong_size.size() == 3); + CHECK(wrong_size.data()[0] == 5); + + CHECK_THROWS_AS( + multipart_t::decode(wrong_size), + std::out_of_range); +} + +TEST_CASE("multipart codec decode bad data extra data", "[codec_multipart]") +{ + using namespace zmq; + + char bad_data[3] = {1, 'h', 'i'}; + message_t wrong_size(bad_data, 3); + CHECK(wrong_size.size() == 3); + CHECK(wrong_size.data()[0] == 1); + + CHECK_THROWS_AS( + multipart_t::decode(wrong_size), + std::out_of_range); +} + + +// After exercising it, this test is disabled over concern of running +// on hosts which lack enough free memory to allow the absurdly large +// message part to be allocated. +#if 0 +TEST_CASE("multipart codec encode too big", "[codec_multipart]") +{ + using namespace zmq; + + const size_t too_big_size = 1L + std::numeric_limits::max(); + CHECK(too_big_size > std::numeric_limits::max()); + char* too_big_data = new char[too_big_size]; + multipart_t mmsg(too_big_data, too_big_size); + delete [] too_big_data; + + CHECK(mmsg.size() == 1); + CHECK(mmsg[0].size() > std::numeric_limits::max()); + + CHECK_THROWS_AS( + mmsg.encode(), + std::range_error); +} +#endif + +TEST_CASE("multipart codec free function with vector of message_t", "[codec_multipart]") +{ + using namespace zmq; + std::vector parts; + parts.emplace_back("Hello", 5); + parts.emplace_back("World",5); + auto msg = encode(parts); + CHECK(msg.size() == 1 + 5 + 1 + 5 ); + CHECK(msg.data()[0] == 5); + CHECK(msg.data()[1] == 'H'); + CHECK(msg.data()[6] == 5); + CHECK(msg.data()[7] == 'W'); + + std::vector parts2; + decode(msg, std::back_inserter(parts2)); + CHECK(parts.size() == 2); + CHECK(parts[0].size() == 5); + CHECK(parts[1].size() == 5); +} + +TEST_CASE("multipart codec free function with vector of const_buffer", "[codec_multipart]") +{ + using namespace zmq; + std::vector parts; + parts.emplace_back("Hello", 5); + parts.emplace_back("World",5); + auto msg = encode(parts); + CHECK(msg.size() == 1 + 5 + 1 + 5 ); + CHECK(msg.data()[0] == 5); + CHECK(msg.data()[1] == 'H'); + CHECK(msg.data()[6] == 5); + CHECK(msg.data()[7] == 'W'); + + std::vector parts2; + decode(msg, std::back_inserter(parts2)); + CHECK(parts.size() == 2); + CHECK(parts[0].size() == 5); + CHECK(parts[1].size() == 5); +} + +TEST_CASE("multipart codec free function with vector of mutable_buffer", "[codec_multipart]") +{ + using namespace zmq; + std::vector parts; + char hello[6] = "Hello"; + parts.emplace_back(hello, 5); + char world[6] = "World"; + parts.emplace_back(world,5); + auto msg = encode(parts); + CHECK(msg.size() == 1 + 5 + 1 + 5 ); + CHECK(msg.data()[0] == 5); + CHECK(msg.data()[1] == 'H'); + CHECK(msg.data()[6] == 5); + CHECK(msg.data()[7] == 'W'); + + std::vector parts2; + decode(msg, std::back_inserter(parts2)); + CHECK(parts.size() == 2); + CHECK(parts[0].size() == 5); + CHECK(parts[1].size() == 5); +} + +TEST_CASE("multipart codec free function with multipart_t", "[codec_multipart]") +{ + using namespace zmq; + multipart_t mmsg; + mmsg.addstr("Hello"); + mmsg.addstr("World"); + auto msg = encode(mmsg); + CHECK(msg.size() == 1 + 5 + 1 + 5); + CHECK(msg.data()[0] == 5); + CHECK(msg.data()[1] == 'H'); + CHECK(msg.data()[6] == 5); + CHECK(msg.data()[7] == 'W'); + + multipart_t mmsg2; + decode(msg, std::back_inserter(mmsg2)); + CHECK(mmsg2.size() == 2); + CHECK(mmsg2[0].size() == 5); + CHECK(mmsg2[1].size() == 5); +} + +TEST_CASE("multipart codec static method decode to multipart_t", "[codec_multipart]") +{ + using namespace zmq; + multipart_t mmsg; + mmsg.addstr("Hello"); + mmsg.addstr("World"); + auto msg = encode(mmsg); + + auto mmsg2 = multipart_t::decode(msg); + CHECK(mmsg2.size() == 2); + CHECK(mmsg2[0].size() == 5); + CHECK(mmsg2[1].size() == 5); +} + + +#endif diff --git a/libs/cppzmq/tests/context.cpp b/libs/cppzmq/tests/context.cpp new file mode 100644 index 0000000..ade5ade --- /dev/null +++ b/libs/cppzmq/tests/context.cpp @@ -0,0 +1,84 @@ +#include +#include + +#if (__cplusplus >= 201703L) +static_assert(std::is_nothrow_swappable::value, + "context_t should be nothrow swappable"); +#endif + +TEST_CASE("context construct default and destroy", "[context]") +{ + zmq::context_t context; +} + +TEST_CASE("context create, close and destroy", "[context]") +{ + zmq::context_t context; + context.close(); + CHECK(NULL == context.handle()); +} + +TEST_CASE("context shutdown", "[context]") +{ + zmq::context_t context; + context.shutdown(); + CHECK(NULL != context.handle()); + context.close(); + CHECK(NULL == context.handle()); +} + +TEST_CASE("context shutdown again", "[context]") +{ + zmq::context_t context; + context.shutdown(); + context.shutdown(); + CHECK(NULL != context.handle()); + context.close(); + CHECK(NULL == context.handle()); +} + +#ifdef ZMQ_CPP11 +TEST_CASE("context swap", "[context]") +{ + zmq::context_t context1; + zmq::context_t context2; + using std::swap; + swap(context1, context2); +} + +TEST_CASE("context - use socket after shutdown", "[context]") +{ + zmq::context_t context; + zmq::socket_t sock(context, zmq::socket_type::rep); + context.shutdown(); + try + { + sock.connect("inproc://test"); + zmq::message_t msg; + (void)sock.recv(msg, zmq::recv_flags::dontwait); + REQUIRE(false); + } + catch (const zmq::error_t& e) + { + REQUIRE(e.num() == ETERM); + } +} + +TEST_CASE("context set/get options", "[context]") +{ + zmq::context_t context; +#if defined(ZMQ_BLOCKY) && defined(ZMQ_IO_THREADS) + context.set(zmq::ctxopt::blocky, false); + context.set(zmq::ctxopt::io_threads, 5); + CHECK(context.get(zmq::ctxopt::io_threads) == 5); +#endif + + CHECK_THROWS_AS( + context.set(static_cast(-42), 5), + zmq::error_t); + + CHECK_THROWS_AS( + context.get(static_cast(-42)), + zmq::error_t); +} +#endif diff --git a/libs/cppzmq/tests/message.cpp b/libs/cppzmq/tests/message.cpp new file mode 100644 index 0000000..a841413 --- /dev/null +++ b/libs/cppzmq/tests/message.cpp @@ -0,0 +1,246 @@ +#define CATCH_CONFIG_MAIN +#include +#include + +#if defined(ZMQ_CPP11) +static_assert(!std::is_copy_constructible::value, + "message_t should not be copy-constructible"); +static_assert(!std::is_copy_assignable::value, + "message_t should not be copy-assignable"); +#endif +#if (__cplusplus >= 201703L) +static_assert(std::is_nothrow_swappable::value, + "message_t should be nothrow swappable"); +#endif + +TEST_CASE("message default constructed", "[message]") +{ + const zmq::message_t message; + CHECK(0u == message.size()); + CHECK(message.empty()); +} + +#ifdef ZMQ_CPP11 +TEST_CASE("message swap", "[message]") +{ + const std::string data = "foo"; + zmq::message_t message1; + zmq::message_t message2(data.data(), data.size()); + using std::swap; + swap(message1, message2); + CHECK(message1.size() == data.size()); + CHECK(message2.size() == 0); + swap(message1, message2); + CHECK(message1.size() == 0); + CHECK(message2.size() == data.size()); +} +#endif + +namespace +{ +const char *const data = "Hi"; +} + +TEST_CASE("message constructor with iterators", "[message]") +{ + const std::string hi(data); + const zmq::message_t hi_msg(hi.begin(), hi.end()); + CHECK(2u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 2)); +} + +TEST_CASE("message constructor with size", "[message]") +{ + const zmq::message_t msg(5); + CHECK(msg.size() == 5); +} + +TEST_CASE("message constructor with buffer and size", "[message]") +{ + const std::string hi(data); + const zmq::message_t hi_msg(hi.data(), hi.size()); + CHECK(2u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 2)); +} + +TEST_CASE("message constructor with char array", "[message]") +{ + const zmq::message_t hi_msg(data, strlen(data)); + CHECK(2u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 2)); +} + +#if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) +TEST_CASE("message constructor with container - deprecated", "[message]") +{ + zmq::message_t hi_msg("Hi"); // deprecated + REQUIRE(3u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 3)); +} + +TEST_CASE("message constructor with container of trivial data", "[message]") +{ + int buf[3] = {1, 2, 3}; + zmq::message_t msg(buf); + REQUIRE(sizeof(buf) == msg.size()); + CHECK(0 == memcmp(buf, msg.data(), msg.size())); +} + +TEST_CASE("message constructor with strings", "[message]") +{ + SECTION("string") + { + const std::string hi(data); + zmq::message_t hi_msg(hi); + CHECK(2u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 2)); + } +#if CPPZMQ_HAS_STRING_VIEW + SECTION("string_view") + { + const std::string_view hi(data); + zmq::message_t hi_msg(hi); + CHECK(2u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 2)); + } +#endif +} +#endif + +#ifdef ZMQ_HAS_RVALUE_REFS +TEST_CASE("message move constructor", "[message]") +{ + zmq::message_t hi_msg(zmq::message_t(data, strlen(data))); +} + +TEST_CASE("message assign move empty before", "[message]") +{ + zmq::message_t hi_msg; + hi_msg = zmq::message_t(data, strlen(data)); + CHECK(2u == hi_msg.size()); + CHECK(0 == memcmp(data, hi_msg.data(), 2)); +} + +TEST_CASE("message assign move empty after", "[message]") +{ + zmq::message_t hi_msg(data, strlen(data)); + CHECK(!hi_msg.empty()); + hi_msg = zmq::message_t(); + CHECK(0u == hi_msg.size()); + CHECK(hi_msg.empty()); +} + +TEST_CASE("message assign move empty before and after", "[message]") +{ + zmq::message_t hi_msg; + hi_msg = zmq::message_t(); + CHECK(0u == hi_msg.size()); +} +#endif + +TEST_CASE("message equality self", "[message]") +{ + const zmq::message_t hi_msg(data, strlen(data)); + CHECK(hi_msg == hi_msg); +} + +TEST_CASE("message equality equal", "[message]") +{ + const zmq::message_t hi_msg_a(data, strlen(data)); + const zmq::message_t hi_msg_b(data, strlen(data)); + CHECK(hi_msg_a == hi_msg_b); +} + +TEST_CASE("message equality equal empty", "[message]") +{ + const zmq::message_t msg_a; + const zmq::message_t msg_b; + CHECK(msg_a == msg_b); +} + +TEST_CASE("message equality non equal", "[message]") +{ + const zmq::message_t msg_a("Hi", 2); + const zmq::message_t msg_b("Hello", 5); + CHECK(msg_a != msg_b); +} + +TEST_CASE("message equality non equal rhs empty", "[message]") +{ + const zmq::message_t msg_a("Hi", 2); + const zmq::message_t msg_b; + CHECK(msg_a != msg_b); +} + +TEST_CASE("message equality non equal lhs empty", "[message]") +{ + const zmq::message_t msg_a; + const zmq::message_t msg_b("Hi", 2); + CHECK(msg_a != msg_b); +} + +TEST_CASE("message to string", "[message]") +{ + const zmq::message_t a; + const zmq::message_t b("Foo", 3); + CHECK(a.to_string() == ""); + CHECK(b.to_string() == "Foo"); +#if CPPZMQ_HAS_STRING_VIEW + CHECK(a.to_string_view() == ""); + CHECK(b.to_string_view() == "Foo"); +#endif + +#if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) + const zmq::message_t depr("Foo"); // deprecated + CHECK(depr.to_string() != "Foo"); + CHECK(depr.to_string() == std::string("Foo", 4)); +#endif +} + +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) +TEST_CASE("message routing id persists", "[message]") +{ + zmq::message_t msg; + msg.set_routing_id(123); + CHECK(123u == msg.routing_id()); +} + +TEST_CASE("message group persists", "[message]") +{ + zmq::message_t msg; + msg.set_group("mygroup"); + CHECK(std::string(msg.group()) == "mygroup"); +} +#endif + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 2, 0) +TEST_CASE("message is not shared", "[message]") +{ + zmq::message_t msg; + CHECK(msg.get(ZMQ_SHARED) == 0); +} + +TEST_CASE("message is shared", "[message]") +{ + size_t msg_sz = 1024; // large enough to be a type_lmsg + zmq::message_t msg1(msg_sz); + zmq::message_t msg2; + msg2.copy(msg1); + CHECK(msg1.get(ZMQ_SHARED) == 1); + CHECK(msg2.get(ZMQ_SHARED) == 1); + CHECK(msg1.size() == msg_sz); + CHECK(msg2.size() == msg_sz); +} + +TEST_CASE("message move is not shared", "[message]") +{ + size_t msg_sz = 1024; // large enough to be a type_lmsg + zmq::message_t msg1(msg_sz); + zmq::message_t msg2; + msg2.move(msg1); + CHECK(msg1.get(ZMQ_SHARED) == 0); + CHECK(msg2.get(ZMQ_SHARED) == 0); + CHECK(msg2.size() == msg_sz); + CHECK(msg1.size() == 0); +} +#endif diff --git a/libs/cppzmq/tests/monitor.cpp b/libs/cppzmq/tests/monitor.cpp new file mode 100644 index 0000000..09d5381 --- /dev/null +++ b/libs/cppzmq/tests/monitor.cpp @@ -0,0 +1,152 @@ +#include "testutil.hpp" + +#ifdef ZMQ_CPP11 +#include +#include +#include +#include + +class mock_monitor_t : public zmq::monitor_t +{ +public: + + void on_event_connected(const zmq_event_t &, const char *) ZMQ_OVERRIDE + { + ++connected; + ++total; + } + + int total{0}; + int connected{0}; +}; + +#endif + +TEST_CASE("monitor create destroy", "[monitor]") +{ + zmq::monitor_t monitor; +} + +#if defined(ZMQ_CPP11) +TEST_CASE("monitor move construct", "[monitor]") +{ + zmq::context_t ctx; + zmq::socket_t sock(ctx, ZMQ_DEALER); + SECTION("move ctor empty") { + zmq::monitor_t monitor1; + zmq::monitor_t monitor2 = std::move(monitor1); + } + SECTION("move ctor init") { + zmq::monitor_t monitor1; + monitor1.init(sock, "inproc://monitor-client"); + zmq::monitor_t monitor2 = std::move(monitor1); + } +} + +TEST_CASE("monitor move assign", "[monitor]") +{ + zmq::context_t ctx; + zmq::socket_t sock(ctx, ZMQ_DEALER); + SECTION("move assign empty") { + zmq::monitor_t monitor1; + zmq::monitor_t monitor2; + monitor1 = std::move(monitor2); + } + SECTION("move assign init") { + zmq::monitor_t monitor1; + monitor1.init(sock, "inproc://monitor-client"); + zmq::monitor_t monitor2; + monitor2 = std::move(monitor1); + } + SECTION("move assign init both") { + zmq::monitor_t monitor1; + monitor1.init(sock, "inproc://monitor-client"); + zmq::monitor_t monitor2; + zmq::socket_t sock2(ctx, ZMQ_DEALER); + monitor2.init(sock2, "inproc://monitor-client2"); + monitor2 = std::move(monitor1); + } +} + +TEST_CASE("monitor init event count", "[monitor]") +{ + common_server_client_setup s{false}; + mock_monitor_t monitor; + + const int expected_event_count = 1; + monitor.init(s.client, "inproc://foo"); + + CHECK_FALSE(monitor.check_event(0)); + s.init(); + + while (monitor.check_event(1000) && monitor.total < expected_event_count) { + } + CHECK(monitor.connected == 1); + CHECK(monitor.total == expected_event_count); +} + +TEST_CASE("monitor init abort", "[monitor]") +{ + class mock_monitor : public mock_monitor_t + { + public: + mock_monitor(std::function handle_connected) : + handle_connected{std::move(handle_connected)} + { + } + + void on_event_connected(const zmq_event_t &e, const char *m) ZMQ_OVERRIDE + { + mock_monitor_t::on_event_connected(e, m); + handle_connected(); + } + + std::function handle_connected; + }; + + common_server_client_setup s(false); + + std::mutex mutex; + std::condition_variable cond_var; + bool done{false}; + + mock_monitor monitor([&]() + { + std::lock_guard lock(mutex); + done = true; + cond_var.notify_one(); + }); + monitor.init(s.client, "inproc://foo"); + + auto thread = std::thread([&monitor] + { + while (monitor.check_event(-1)) { + } + }); + + s.init(); + { + std::unique_lock lock(mutex); + CHECK(cond_var.wait_for(lock, std::chrono::seconds(1), + [&done] { return done; })); + } + CHECK(monitor.connected == 1); + monitor.abort(); + thread.join(); +} + + +TEST_CASE("monitor from move assigned socket", "[monitor]") +{ + zmq::context_t ctx; + zmq::socket_t sock; + sock = std::move([&ctx] { + zmq::socket_t sock(ctx, ZMQ_DEALER); + return sock; + }()); + zmq::monitor_t monitor1; + monitor1.init(sock, "inproc://monitor-client"); + // On failure, this test might hang indefinitely instead of immediately + // failing +} +#endif diff --git a/libs/cppzmq/tests/multipart.cpp b/libs/cppzmq/tests/multipart.cpp new file mode 100644 index 0000000..7d92b97 --- /dev/null +++ b/libs/cppzmq/tests/multipart.cpp @@ -0,0 +1,212 @@ +#include +#include + +#ifdef ZMQ_HAS_RVALUE_REFS + +#ifdef ZMQ_CPP17 +static_assert(std::is_invocable::value, + "Can't multipart_t::send with socket_ref"); +static_assert(std::is_invocable::value, + "Can't multipart_t::recv with socket_ref"); +#endif +static_assert(std::is_constructible::value, + "Can't construct with socket_ref"); + +/// \todo split this up into separate test cases +/// +TEST_CASE("multipart legacy test", "[multipart]") +{ + using namespace zmq; + + bool ok = true; + (void) ok; + float num = 0; + (void) num; + std::string str = ""; + message_t msg; + + // Create two PAIR sockets and connect over inproc + context_t context(1); + socket_t output(context, ZMQ_PAIR); + socket_t input(context, ZMQ_PAIR); + output.bind("inproc://multipart.test"); + input.connect("inproc://multipart.test"); + + // Test send and receive of single-frame message + multipart_t multipart; + assert(multipart.empty()); + + multipart.push(message_t("Hello", 5)); + assert(multipart.size() == 1); + + ok = multipart.send(output); + assert(multipart.empty()); + assert(ok); + + ok = multipart.recv(input); + assert(multipart.size() == 1); + assert(ok); + + msg = multipart.pop(); + assert(multipart.empty()); + assert(std::string(msg.data(), msg.size()) == "Hello"); + + // Test send and receive of multi-frame message + multipart.addstr("A"); + multipart.addstr("BB"); + multipart.addstr("CCC"); + assert(multipart.size() == 3); + + multipart_t copy = multipart.clone(); + assert(copy.size() == 3); + + ok = copy.send(output); + assert(copy.empty()); + assert(ok); + + ok = copy.recv(input); + assert(copy.size() == 3); + assert(ok); + assert(copy.equal(&multipart)); + + // Test equality operators + assert(copy == multipart); + assert(multipart == copy); + + multipart.pop(); + + assert(copy != multipart); + assert(multipart != copy); + + multipart_t emptyMessage1 {}; + multipart_t emptyMessage2 {}; + + assert(emptyMessage1 == emptyMessage2); + assert(emptyMessage2 == emptyMessage1); + + multipart.clear(); + assert(multipart.empty()); + + // Test message frame manipulation + multipart.add(message_t("Frame5", 6)); + multipart.addstr("Frame6"); + multipart.addstr("Frame7"); + multipart.addtyp(8.0f); + multipart.addmem("Frame9", 6); + multipart.push(message_t("Frame4", 6)); + multipart.pushstr("Frame3"); + multipart.pushstr("Frame2"); + multipart.pushtyp(1.0f); + multipart.pushmem("Frame0", 6); + assert(multipart.size() == 10); + + const message_t &front_msg = multipart.front(); + assert(multipart.size() == 10); + assert(std::string(front_msg.data(), front_msg.size()) == "Frame0"); + + const message_t &back_msg = multipart.back(); + assert(multipart.size() == 10); + assert(std::string(back_msg.data(), back_msg.size()) == "Frame9"); + + msg = multipart.remove(); + assert(multipart.size() == 9); + assert(std::string(msg.data(), msg.size()) == "Frame9"); + + msg = multipart.pop(); + assert(multipart.size() == 8); + assert(std::string(msg.data(), msg.size()) == "Frame0"); + + num = multipart.poptyp(); + assert(multipart.size() == 7); + assert(num == 1.0f); + + str = multipart.popstr(); + assert(multipart.size() == 6); + assert(str == "Frame2"); + + str = multipart.popstr(); + assert(multipart.size() == 5); + assert(str == "Frame3"); + + str = multipart.popstr(); + assert(multipart.size() == 4); + assert(str == "Frame4"); + + str = multipart.popstr(); + assert(multipart.size() == 3); + assert(str == "Frame5"); + + str = multipart.popstr(); + assert(multipart.size() == 2); + assert(str == "Frame6"); + + str = multipart.popstr(); + assert(multipart.size() == 1); + assert(str == "Frame7"); + + num = multipart.poptyp(); + assert(multipart.empty()); + assert(num == 8.0f); + + // Test message constructors and concatenation + multipart_t head("One", 3); + head.addstr("Two"); + assert(head.size() == 2); + + multipart_t tail(std::string("One-hundred")); + tail.pushstr("Ninety-nine"); + assert(tail.size() == 2); + + multipart_t tmp(message_t("Fifty", 5)); + assert(tmp.size() == 1); + + multipart_t mid = multipart_t::create(49.0f); + mid.append(std::move(tmp)); + assert(mid.size() == 2); + assert(tmp.empty()); + + multipart_t merged(std::move(mid)); + merged.prepend(std::move(head)); + merged.append(std::move(tail)); + assert(merged.size() == 6); + assert(head.empty()); + assert(tail.empty()); + + ok = merged.send(output); + assert(merged.empty()); + assert(ok); + + multipart_t received(input); + assert(received.size() == 6); + + str = received.popstr(); + assert(received.size() == 5); + assert(str == "One"); + + str = received.popstr(); + assert(received.size() == 4); + assert(str == "Two"); + + num = received.poptyp(); + assert(received.size() == 3); + assert(num == 49.0f); + + str = received.popstr(); + assert(received.size() == 2); + assert(str == "Fifty"); + + str = received.popstr(); + assert(received.size() == 1); + assert(str == "Ninety-nine"); + + str = received.popstr(); + assert(received.empty()); + assert(str == "One-hundred"); +} +#endif diff --git a/libs/cppzmq/tests/poller.cpp b/libs/cppzmq/tests/poller.cpp new file mode 100644 index 0000000..81c42e6 --- /dev/null +++ b/libs/cppzmq/tests/poller.cpp @@ -0,0 +1,356 @@ +#include "testutil.hpp" + +#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) && defined(ZMQ_HAVE_POLLER) + +#include +#include + +#ifdef ZMQ_CPP17 +static_assert(std::is_nothrow_swappable_v>); +#endif +static_assert(sizeof(zmq_poller_event_t) == sizeof(zmq::poller_event<>), ""); +static_assert(sizeof(zmq_poller_event_t) == sizeof(zmq::poller_event), ""); +static_assert(sizeof(zmq_poller_event_t) == sizeof(zmq::poller_event), ""); +static_assert(alignof(zmq_poller_event_t) == alignof(zmq::poller_event<>), ""); +static_assert(alignof(zmq_poller_event_t) == alignof(zmq::poller_event), ""); + +static_assert(!std::is_copy_constructible>::value, + "poller_t should not be copy-constructible"); +static_assert(!std::is_copy_assignable>::value, + "poller_t should not be copy-assignable"); + +TEST_CASE("event flags", "[poller]") +{ + CHECK((zmq::event_flags::pollin | zmq::event_flags::pollout) + == static_cast(ZMQ_POLLIN | ZMQ_POLLOUT)); + CHECK((zmq::event_flags::pollin & zmq::event_flags::pollout) + == static_cast(ZMQ_POLLIN & ZMQ_POLLOUT)); + CHECK((zmq::event_flags::pollin ^ zmq::event_flags::pollout) + == static_cast(ZMQ_POLLIN ^ ZMQ_POLLOUT)); + CHECK(~zmq::event_flags::pollin == static_cast(~ZMQ_POLLIN)); +} + +TEST_CASE("poller create destroy", "[poller]") +{ + zmq::poller_t<> a; +#ifdef ZMQ_CPP17 // CTAD + zmq::poller_t b; + zmq::poller_event e; +#endif +} + +TEST_CASE("poller move construct empty", "[poller]") +{ + zmq::poller_t<> a; + zmq::poller_t<> b = std::move(a); +} + +TEST_CASE("poller move assign empty", "[poller]") +{ + zmq::poller_t<> a; + zmq::poller_t<> b; + b = std::move(a); +} + +TEST_CASE("poller swap", "[poller]") +{ + zmq::poller_t<> a; + zmq::poller_t<> b; + using std::swap; + swap(a, b); +} + +TEST_CASE("poller move construct non empty", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + + zmq::poller_t<> a; + a.add(socket, zmq::event_flags::pollin); + zmq::poller_t<> b = std::move(a); +} + +TEST_CASE("poller move assign non empty", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + + zmq::poller_t<> a; + a.add(socket, zmq::event_flags::pollin); + zmq::poller_t<> b; + b = std::move(a); +} + +TEST_CASE("poller add nullptr", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t poller; + CHECK_NOTHROW(poller.add(socket, zmq::event_flags::pollin, nullptr)); +} + +TEST_CASE("poller add non nullptr", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t poller; + int i; + CHECK_NOTHROW(poller.add(socket, zmq::event_flags::pollin, &i)); +} + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 3, 0) +// this behaviour was added by https://github.com/zeromq/libzmq/pull/3100 +TEST_CASE("poller add handler invalid events type", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t<> poller; + short invalid_events_type = 2 << 10; + CHECK_THROWS_AS( + poller.add(socket, static_cast(invalid_events_type)), + zmq::error_t); +} +#endif + +TEST_CASE("poller add handler twice throws", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t<> poller; + poller.add(socket, zmq::event_flags::pollin); + /// \todo the actual error code should be checked + CHECK_THROWS_AS(poller.add(socket, zmq::event_flags::pollin), + zmq::error_t); +} + +TEST_CASE("poller wait with no handlers throws", "[poller]") +{ + zmq::poller_t<> poller; + std::vector> events; + /// \todo the actual error code should be checked + CHECK_THROWS_AS(poller.wait_all(events, std::chrono::milliseconds{10}), + zmq::error_t); +} + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 3, 3) +TEST_CASE("poller add/remove size checks", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t<> poller; + CHECK(poller.size() == 0); + poller.add(socket, zmq::event_flags::pollin); + CHECK(poller.size() == 1); + CHECK_NOTHROW(poller.remove(socket)); + CHECK(poller.size() == 0); +} +#endif + +TEST_CASE("poller remove unregistered throws", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t<> poller; + /// \todo the actual error code should be checked + CHECK_THROWS_AS(poller.remove(socket), zmq::error_t); +} + +TEST_CASE("poller remove registered empty", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t<> poller; + poller.add(socket, zmq::event_flags::pollin); + CHECK_NOTHROW(poller.remove(socket)); +} + +TEST_CASE("poller remove registered non empty", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t poller; + int empty{}; + poller.add(socket, zmq::event_flags::pollin, &empty); + CHECK_NOTHROW(poller.remove(socket)); +} + +const std::string hi_str = "Hi"; + +TEST_CASE("poller poll basic", "[poller]") +{ + common_server_client_setup s; + + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + + zmq::poller_t poller; + std::vector> events{1}; + int i = 0; + CHECK_NOTHROW(poller.add(s.server, zmq::event_flags::pollin, &i)); + CHECK(1 == poller.wait_all(events, std::chrono::milliseconds{-1})); + CHECK(s.server == events[0].socket); + CHECK(&i == events[0].user_data); +} + +TEST_CASE("poller add invalid socket throws", "[poller]") +{ + zmq::context_t context; + zmq::poller_t<> poller; + zmq::socket_t a{context, zmq::socket_type::router}; + zmq::socket_t b{std::move(a)}; + CHECK_THROWS_AS(poller.add(a, zmq::event_flags::pollin), zmq::error_t); +} + +TEST_CASE("poller remove invalid socket throws", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::router}; + zmq::poller_t<> poller; + CHECK_NOTHROW(poller.add(socket, zmq::event_flags::pollin)); + std::vector sockets; + sockets.emplace_back(std::move(socket)); + CHECK_THROWS_AS(poller.remove(socket), zmq::error_t); + CHECK_NOTHROW(poller.remove(sockets[0])); +} + +TEST_CASE("poller modify empty throws", "[poller]") +{ + zmq::context_t context; + zmq::socket_t socket{context, zmq::socket_type::push}; + zmq::poller_t<> poller; + CHECK_THROWS_AS(poller.modify(socket, zmq::event_flags::pollin), + zmq::error_t); +} + +TEST_CASE("poller modify invalid socket throws", "[poller]") +{ + zmq::context_t context; + zmq::socket_t a{context, zmq::socket_type::push}; + zmq::socket_t b{std::move(a)}; + zmq::poller_t<> poller; + CHECK_THROWS_AS(poller.modify(a, zmq::event_flags::pollin), zmq::error_t); +} + +TEST_CASE("poller modify not added throws", "[poller]") +{ + zmq::context_t context; + zmq::socket_t a{context, zmq::socket_type::push}; + zmq::socket_t b{context, zmq::socket_type::push}; + zmq::poller_t<> poller; + CHECK_NOTHROW(poller.add(a, zmq::event_flags::pollin)); + CHECK_THROWS_AS(poller.modify(b, zmq::event_flags::pollin), zmq::error_t); +} + +TEST_CASE("poller modify simple", "[poller]") +{ + zmq::context_t context; + zmq::socket_t a{context, zmq::socket_type::push}; + zmq::poller_t<> poller; + CHECK_NOTHROW(poller.add(a, zmq::event_flags::pollin)); + CHECK_NOTHROW( + poller.modify(a, zmq::event_flags::pollin | zmq::event_flags::pollout)); +} + +TEST_CASE("poller poll client server", "[poller]") +{ + // Setup server and client + common_server_client_setup s; + + // Setup poller + zmq::poller_t poller; + CHECK_NOTHROW(poller.add(s.server, zmq::event_flags::pollin, &s.server)); + + // client sends message + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + + // wait for message and verify events + std::vector> events(1); + CHECK(1 == poller.wait_all(events, std::chrono::milliseconds{500})); + CHECK(zmq::event_flags::pollin == events[0].events); + + // Modify server socket with pollout flag + CHECK_NOTHROW( + poller.modify(s.server, zmq::event_flags::pollin | zmq::event_flags::pollout + )); + CHECK(1 == poller.wait_all(events, std::chrono::milliseconds{500})); + CHECK((zmq::event_flags::pollin | zmq::event_flags::pollout) == events[0].events) + ; +} + +TEST_CASE("poller wait one return", "[poller]") +{ + // Setup server and client + common_server_client_setup s; + + // Setup poller + zmq::poller_t<> poller; + CHECK_NOTHROW(poller.add(s.server, zmq::event_flags::pollin)); + + // client sends message + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + + // wait for message and verify events + std::vector> events(1); + CHECK(1 == poller.wait_all(events, std::chrono::milliseconds{500})); +} + +TEST_CASE("poller wait on move constructed", "[poller]") +{ + common_server_client_setup s; + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + zmq::poller_t<> a; + CHECK_NOTHROW(a.add(s.server, zmq::event_flags::pollin)); + zmq::poller_t<> b{std::move(a)}; + std::vector> events(1); + /// \todo the actual error code should be checked + CHECK_THROWS_AS(a.wait_all(events, std::chrono::milliseconds{10}), + zmq::error_t); + CHECK(1 == b.wait_all(events, std::chrono::milliseconds{-1})); +} + +TEST_CASE("poller wait on move assigned", "[poller]") +{ + common_server_client_setup s; + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + zmq::poller_t<> a; + CHECK_NOTHROW(a.add(s.server, zmq::event_flags::pollin)); + zmq::poller_t<> b; + b = {std::move(a)}; + /// \todo the TEST_CASE error code should be checked + std::vector> events(1); + CHECK_THROWS_AS(a.wait_all(events, std::chrono::milliseconds{10}), + zmq::error_t); + CHECK(1 == b.wait_all(events, std::chrono::milliseconds{-1})); +} + +TEST_CASE("poller remove from handler", "[poller]") +{ + constexpr size_t ITER_NO = 10; + + // Setup servers and clients + std::vector setup_list; + for (size_t i = 0; i < ITER_NO; ++i) + setup_list.emplace_back(common_server_client_setup{}); + + // Setup poller + zmq::poller_t<> poller; + for (size_t i = 0; i < ITER_NO; ++i) { + CHECK_NOTHROW(poller.add(setup_list[i].server, zmq::event_flags::pollin)); + } + // Clients send messages + for (auto &s : setup_list) { + CHECK_NOTHROW(s.client.send(zmq::message_t{hi_str}, zmq::send_flags::none)); + } + + // Wait for all servers to receive a message + for (auto &s : setup_list) { + zmq::pollitem_t items[] = {{s.server, 0, ZMQ_POLLIN, 0}}; + zmq::poll(&items[0], 1); + } + + // Fire all handlers in one wait + std::vector> events(ITER_NO); + CHECK(ITER_NO == poller.wait_all(events, std::chrono::milliseconds{-1})); +} + +#endif diff --git a/libs/cppzmq/tests/recv_multipart.cpp b/libs/cppzmq/tests/recv_multipart.cpp new file mode 100644 index 0000000..cf99855 --- /dev/null +++ b/libs/cppzmq/tests/recv_multipart.cpp @@ -0,0 +1,139 @@ +#include +#include +#ifdef ZMQ_CPP11 + +TEST_CASE("recv_multipart test", "[recv_multipart]") +{ + zmq::context_t context(1); + zmq::socket_t output(context, ZMQ_PAIR); + zmq::socket_t input(context, ZMQ_PAIR); + output.bind("inproc://multipart.test"); + input.connect("inproc://multipart.test"); + + SECTION("send 1 message") { + input.send(zmq::str_buffer("hello")); + + std::vector msgs; + auto ret = zmq::recv_multipart(output, std::back_inserter(msgs)); + REQUIRE(ret); + CHECK(*ret == 1); + REQUIRE(msgs.size() == 1); + CHECK(msgs[0].size() == 5); + } + SECTION("send 2 messages") { + input.send(zmq::str_buffer("hello"), zmq::send_flags::sndmore); + input.send(zmq::str_buffer("world!")); + + std::vector msgs; + auto ret = zmq::recv_multipart(output, std::back_inserter(msgs)); + REQUIRE(ret); + CHECK(*ret == 2); + REQUIRE(msgs.size() == 2); + CHECK(msgs[0].size() == 5); + CHECK(msgs[1].size() == 6); + } + SECTION("send no messages, dontwait") { + std::vector msgs; + auto ret = zmq::recv_multipart(output, std::back_inserter(msgs), + zmq::recv_flags::dontwait); + CHECK_FALSE(ret); + REQUIRE(msgs.size() == 0); + } + SECTION("send 1 partial message, dontwait") { + input.send(zmq::str_buffer("hello"), zmq::send_flags::sndmore); + + std::vector msgs; + auto ret = zmq::recv_multipart(output, std::back_inserter(msgs), + zmq::recv_flags::dontwait); + CHECK_FALSE(ret); + REQUIRE(msgs.size() == 0); + } + SECTION("recv with invalid socket") { + std::vector msgs; + CHECK_THROWS_AS( + zmq::recv_multipart(zmq::socket_ref(), std::back_inserter(msgs)), + zmq::error_t); + } +} + +TEST_CASE("recv_multipart_n test", "[recv_multipart]") +{ + zmq::context_t context(1); + zmq::socket_t output(context, ZMQ_PAIR); + zmq::socket_t input(context, ZMQ_PAIR); + output.bind("inproc://multipart.test"); + input.connect("inproc://multipart.test"); + + SECTION("send 1 message") { + input.send(zmq::str_buffer("hello")); + + std::array msgs; + auto ret = zmq::recv_multipart_n(output, msgs.data(), msgs.size()); + REQUIRE(ret); + CHECK(*ret == 1); + CHECK(msgs[0].size() == 5); + } + SECTION("send 1 message 2") { + input.send(zmq::str_buffer("hello")); + + std::array msgs; + auto ret = zmq::recv_multipart_n(output, msgs.data(), msgs.size()); + REQUIRE(ret); + CHECK(*ret == 1); + CHECK(msgs[0].size() == 5); + CHECK(msgs[1].size() == 0); + } + SECTION("send 2 messages, recv 1") { + input.send(zmq::str_buffer("hello"), zmq::send_flags::sndmore); + input.send(zmq::str_buffer("world!")); + + std::array msgs; + CHECK_THROWS_AS( + zmq::recv_multipart_n(output, msgs.data(), msgs.size()), + std::runtime_error); + } + SECTION("recv 0") { + input.send(zmq::str_buffer("hello"), zmq::send_flags::sndmore); + input.send(zmq::str_buffer("world!")); + + std::array msgs; + CHECK_THROWS_AS( + zmq::recv_multipart_n(output, msgs.data(), 0), + std::runtime_error); + } + SECTION("send 2 messages") { + input.send(zmq::str_buffer("hello"), zmq::send_flags::sndmore); + input.send(zmq::str_buffer("world!")); + + std::array msgs; + auto ret = zmq::recv_multipart_n(output, msgs.data(), msgs.size()); + REQUIRE(ret); + CHECK(*ret == 2); + CHECK(msgs[0].size() == 5); + CHECK(msgs[1].size() == 6); + } + SECTION("send no messages, dontwait") { + std::array msgs; + auto ret = zmq::recv_multipart_n(output, msgs.data(), msgs.size(), + zmq::recv_flags::dontwait); + CHECK_FALSE(ret); + REQUIRE(msgs[0].size() == 0); + } + SECTION("send 1 partial message, dontwait") { + input.send(zmq::str_buffer("hello"), zmq::send_flags::sndmore); + + std::array msgs; + auto ret = zmq::recv_multipart_n(output, msgs.data(), msgs.size(), + zmq::recv_flags::dontwait); + CHECK_FALSE(ret); + REQUIRE(msgs[0].size() == 0); + } + SECTION("recv with invalid socket") { + std::array msgs; + CHECK_THROWS_AS( + zmq::recv_multipart_n(zmq::socket_ref(), msgs.data(), msgs.size()), + zmq::error_t); + } +} + +#endif diff --git a/libs/cppzmq/tests/send_multipart.cpp b/libs/cppzmq/tests/send_multipart.cpp new file mode 100644 index 0000000..1f23823 --- /dev/null +++ b/libs/cppzmq/tests/send_multipart.cpp @@ -0,0 +1,121 @@ +#include +#include +#ifdef ZMQ_CPP11 + +#include + +TEST_CASE("send_multipart test", "[send_multipart]") +{ + zmq::context_t context(1); + zmq::socket_t output(context, ZMQ_PAIR); + zmq::socket_t input(context, ZMQ_PAIR); + output.bind("inproc://multipart.test"); + input.connect("inproc://multipart.test"); + + SECTION("send 0 messages") { + std::vector imsgs; + auto iret = zmq::send_multipart(input, imsgs); + REQUIRE(iret); + CHECK(*iret == 0); + } + SECTION("send 1 message") { + std::array imsgs = {zmq::message_t(3)}; + auto iret = zmq::send_multipart(input, imsgs); + REQUIRE(iret); + CHECK(*iret == 1); + + std::vector omsgs; + auto oret = zmq::recv_multipart(output, std::back_inserter(omsgs)); + REQUIRE(oret); + CHECK(*oret == 1); + REQUIRE(omsgs.size() == 1); + CHECK(omsgs[0].size() == 3); + } + SECTION("send 2 messages") { + std::array imsgs = {zmq::message_t(3), zmq::message_t(4)}; + auto iret = zmq::send_multipart(input, imsgs); + REQUIRE(iret); + CHECK(*iret == 2); + + std::vector omsgs; + auto oret = zmq::recv_multipart(output, std::back_inserter(omsgs)); + REQUIRE(oret); + CHECK(*oret == 2); + REQUIRE(omsgs.size() == 2); + CHECK(omsgs[0].size() == 3); + CHECK(omsgs[1].size() == 4); + } + SECTION("send 2 messages, const_buffer") { + std::array imsgs = {zmq::str_buffer("foo"), + zmq::str_buffer("bar!")}; + auto iret = zmq::send_multipart(input, imsgs); + REQUIRE(iret); + CHECK(*iret == 2); + + std::vector omsgs; + auto oret = zmq::recv_multipart(output, std::back_inserter(omsgs)); + REQUIRE(oret); + CHECK(*oret == 2); + REQUIRE(omsgs.size() == 2); + CHECK(omsgs[0].size() == 3); + CHECK(omsgs[1].size() == 4); + } + SECTION("send 2 messages, mutable_buffer") { + char buf[4] = {}; + std::array imsgs = { + zmq::buffer(buf, 3), zmq::buffer(buf)}; + auto iret = zmq::send_multipart(input, imsgs); + REQUIRE(iret); + CHECK(*iret == 2); + + std::vector omsgs; + auto oret = zmq::recv_multipart(output, std::back_inserter(omsgs)); + REQUIRE(oret); + CHECK(*oret == 2); + REQUIRE(omsgs.size() == 2); + CHECK(omsgs[0].size() == 3); + CHECK(omsgs[1].size() == 4); + } + SECTION("send 2 messages, dontwait") { + zmq::socket_t push(context, ZMQ_PUSH); + push.bind("inproc://multipart.test.push"); + + std::array imsgs = {zmq::message_t(3), zmq::message_t(4)}; + auto iret = zmq::send_multipart(push, imsgs, zmq::send_flags::dontwait); + REQUIRE_FALSE(iret); + } + SECTION("send, misc. containers") { + std::vector msgs_vec; + msgs_vec.emplace_back(3); + msgs_vec.emplace_back(4); + auto iret = zmq::send_multipart(input, msgs_vec); + REQUIRE(iret); + CHECK(*iret == 2); + + std::forward_list msgs_list; + msgs_list.emplace_front(4); + msgs_list.emplace_front(3); + iret = zmq::send_multipart(input, msgs_list); + REQUIRE(iret); + CHECK(*iret == 2); + + // init. list + const auto msgs_il = {zmq::str_buffer("foo"), zmq::str_buffer("bar!")}; + iret = zmq::send_multipart(input, msgs_il); + REQUIRE(iret); + CHECK(*iret == 2); + // rvalue + iret = zmq::send_multipart(input, + std::initializer_list{ + zmq::str_buffer("foo"), + zmq::str_buffer("bar!")}); + REQUIRE(iret); + CHECK(*iret == 2); + } + SECTION("send with invalid socket") { + std::vector msgs(1); + CHECK_THROWS_AS(zmq::send_multipart(zmq::socket_ref(), msgs), + zmq::error_t); + } +} +#endif diff --git a/libs/cppzmq/tests/socket.cpp b/libs/cppzmq/tests/socket.cpp new file mode 100644 index 0000000..cebee4b --- /dev/null +++ b/libs/cppzmq/tests/socket.cpp @@ -0,0 +1,700 @@ +#include +#include +#ifdef ZMQ_CPP11 +#include +#endif + +#if (__cplusplus >= 201703L) +static_assert(std::is_nothrow_swappable::value, + "socket_t should be nothrow swappable"); +#endif + +TEST_CASE("socket default ctor", "[socket]") +{ + zmq::socket_t socket; +} + +TEST_CASE("socket create destroy", "[socket]") +{ + zmq::context_t context; + zmq::socket_t socket(context, ZMQ_ROUTER); +} + +#ifdef ZMQ_CPP11 +TEST_CASE("socket create assign", "[socket]") +{ + zmq::context_t context; + zmq::socket_t socket(context, ZMQ_ROUTER); + CHECK(static_cast(socket)); + CHECK(socket.handle() != nullptr); + socket = {}; + CHECK(!static_cast(socket)); + CHECK(socket.handle() == nullptr); +} + +TEST_CASE("socket create by enum and destroy", "[socket]") +{ + zmq::context_t context; + zmq::socket_t socket(context, zmq::socket_type::router); +} + +TEST_CASE("socket swap", "[socket]") +{ + zmq::context_t context; + zmq::socket_t socket1(context, zmq::socket_type::router); + zmq::socket_t socket2(context, zmq::socket_type::dealer); + using std::swap; + swap(socket1, socket2); +} + +#ifdef ZMQ_CPP11 +TEST_CASE("socket options", "[socket]") +{ + zmq::context_t context; + zmq::socket_t socket(context, zmq::socket_type::router); + +#ifdef ZMQ_IMMEDIATE + socket.set(zmq::sockopt::immediate, 0); + socket.set(zmq::sockopt::immediate, false); + CHECK(socket.get(zmq::sockopt::immediate) == false); + // unit out of range + CHECK_THROWS_AS(socket.set(zmq::sockopt::immediate, 80), zmq::error_t); +#endif +#ifdef ZMQ_LINGER + socket.set(zmq::sockopt::linger, 55); + CHECK(socket.get(zmq::sockopt::linger) == 55); +#endif +#ifdef ZMQ_ROUTING_ID + const std::string id = "foobar"; + socket.set(zmq::sockopt::routing_id, "foobar"); + socket.set(zmq::sockopt::routing_id, zmq::buffer(id)); + socket.set(zmq::sockopt::routing_id, id); +#if CPPZMQ_HAS_STRING_VIEW + socket.set(zmq::sockopt::routing_id, std::string_view{id}); +#endif + + std::string id_ret(10, ' '); + auto size = socket.get(zmq::sockopt::routing_id, zmq::buffer(id_ret)); + id_ret.resize(size); + CHECK(id == id_ret); + auto stropt = socket.get(zmq::sockopt::routing_id); + CHECK(id == stropt); + + std::string id_ret_small(3, ' '); + // truncated + CHECK_THROWS_AS(socket.get(zmq::sockopt::routing_id, zmq::buffer(id_ret_small)), + zmq::error_t); +#endif +} + +template +void check_array_opt(T opt, + zmq::socket_t &sock, + std::string info, + bool set_only = false) +{ + const std::string val = "foobar"; + INFO("setting " + info); + sock.set(opt, val); + if (set_only) + return; + + INFO("getting " + info); + auto s = sock.get(opt); + CHECK(s == val); +} + +template +void check_array_opt_get(T opt, zmq::socket_t &sock, std::string info) +{ + INFO("getting " + info); + (void) sock.get(opt); +} + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 0, 0) +template void check_bin_z85(T opt, zmq::socket_t &sock, std::string str_val) +{ + std::vector bin_val(32); + const auto dret = zmq_z85_decode(bin_val.data(), str_val.c_str()); + CHECK(dret != nullptr); + + sock.set(opt, str_val); + sock.set(opt, zmq::buffer(bin_val)); + auto sv = sock.get(opt); + CHECK(sv == str_val); + + auto bv = sock.get(opt, 32); + REQUIRE(bv.size() == bin_val.size()); + CHECK(std::memcmp(bv.data(), bin_val.data(), bin_val.size()) == 0); +} +#endif + +TEST_CASE("socket check array options", "[socket]") +{ + zmq::context_t context; + zmq::socket_t router(context, zmq::socket_type::router); + zmq::socket_t xpub(context, zmq::socket_type::xpub); + zmq::socket_t sub(context, zmq::socket_type::sub); + +#ifdef ZMQ_BINDTODEVICE +// requires setting CAP_NET_RAW +//check_array_opt(zmq::sockopt::bindtodevice, router, "bindtodevice"); +#endif +#ifdef ZMQ_CONNECT_ROUTING_ID + check_array_opt(zmq::sockopt::connect_routing_id, router, "connect_routing_id", + true); +#endif +#ifdef ZMQ_LAST_ENDPOINT + check_array_opt_get(zmq::sockopt::last_endpoint, router, "last_endpoint"); +#endif +#ifdef ZMQ_METADATA + router.set(zmq::sockopt::metadata, zmq::str_buffer("X-foo:bar")); +#endif +#ifdef ZMQ_PLAIN_PASSWORD + check_array_opt(zmq::sockopt::plain_password, router, "plain_password"); +#endif +#ifdef ZMQ_PLAIN_USERNAME + check_array_opt(zmq::sockopt::plain_username, router, "plain_username"); +#endif +#ifdef ZMQ_ROUTING_ID + check_array_opt(zmq::sockopt::routing_id, router, "routing_id"); +#endif +#ifdef ZMQ_SOCKS_PROXY + check_array_opt(zmq::sockopt::socks_proxy, router, "socks_proxy"); +#endif +#ifdef ZMQ_SUBSCRIBE + check_array_opt(zmq::sockopt::subscribe, sub, "subscribe", true); +#endif +#ifdef ZMQ_UNSUBSCRIBE + check_array_opt(zmq::sockopt::unsubscribe, sub, "unsubscribe", true); +#endif +#ifdef ZMQ_XPUB_WELCOME_MSG + check_array_opt(zmq::sockopt::xpub_welcome_msg, xpub, "xpub_welcome_msg", true); +#endif +#ifdef ZMQ_ZAP_DOMAIN + check_array_opt(zmq::sockopt::zap_domain, router, "zap_domain"); +#endif + +// curve +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 0, 0) && defined(ZMQ_HAS_CAPABILITIES) + if (zmq_has("curve") == 1) { + const std::string spk = "rq:rM>}U?@Lns47E1%kR.o@n%FcmmsL/@{H8]yf7"; + const std::string ssk = "JTKVSB%%)wK0E.X)V>+}o?pNmC{O&4W4b!Ni{Lh6"; + const std::string cpk = "Yne@$w-vo= ZMQ_MAKE_VERSION(4, 1, 0) && defined(ZMQ_HAS_CAPABILITIES) + if (zmq_has("gssapi") == 1 && false) // TODO enable + { + zmq::socket_t gss_server(context, zmq::socket_type::router); + gss_server.set(zmq::sockopt::gssapi_server, true); + CHECK(gss_server.get(zmq::sockopt::gssapi_server) == 1); + gss_server.set(zmq::sockopt::gssapi_plaintext, false); + CHECK(gss_server.get(zmq::sockopt::gssapi_plaintext) == 0); + check_array_opt(zmq::sockopt::gssapi_principal, gss_server, + "gssapi_principal"); + + zmq::socket_t gss_client(context, zmq::socket_type::router); + CHECK(gss_client.get(zmq::sockopt::gssapi_server) == 0); + check_array_opt(zmq::sockopt::gssapi_principal, gss_client, + "gssapi_principal"); + check_array_opt(zmq::sockopt::gssapi_service_principal, gss_client, + "gssapi_service_principal"); + } +#endif +} + +template +void check_integral_opt(Opt opt, + zmq::socket_t &sock, + std::string info, + bool set_only = false) +{ + const T val = 1; + INFO("setting " + info); + sock.set(opt, val); + if (set_only) + return; + + INFO("getting " + info); + auto s = sock.get(opt); + CHECK(s == val); +} + +template +void check_integral_opt_get(Opt opt, zmq::socket_t &sock, std::string info) +{ + INFO("getting " + info); + (void) sock.get(opt); +} + +TEST_CASE("socket check integral options", "[socket]") +{ + zmq::context_t context; + zmq::socket_t router(context, zmq::socket_type::router); + zmq::socket_t xpub(context, zmq::socket_type::xpub); + zmq::socket_t req(context, zmq::socket_type::req); +#ifdef ZMQ_STREAM_NOTIFY + zmq::socket_t stream(context, zmq::socket_type::stream); +#endif + +#ifdef ZMQ_AFFINITY + check_integral_opt(zmq::sockopt::affinity, router, "affinity"); +#endif +#ifdef ZMQ_BACKLOG + check_integral_opt(zmq::sockopt::backlog, router, "backlog"); +#endif +#ifdef ZMQ_CONFLATE + check_integral_opt(zmq::sockopt::conflate, router, "conflate"); +#endif +#ifdef ZMQ_CONNECT_TIMEOUT + check_integral_opt(zmq::sockopt::connect_timeout, router, + "connect_timeout"); +#endif +#ifdef ZMQ_EVENTS + check_integral_opt_get(zmq::sockopt::events, router, "events"); +#endif +#ifdef ZMQ_FD + check_integral_opt_get(zmq::sockopt::fd, router, "fd"); +#endif +#ifdef ZMQ_HANDSHAKE_IVL + check_integral_opt(zmq::sockopt::handshake_ivl, router, "handshake_ivl"); +#endif +#ifdef ZMQ_HEARTBEAT_IVL + check_integral_opt(zmq::sockopt::heartbeat_ivl, router, "heartbeat_ivl"); +#endif +#ifdef ZMQ_HEARTBEAT_TIMEOUT + check_integral_opt(zmq::sockopt::heartbeat_timeout, router, + "heartbeat_timeout"); +#endif +#ifdef ZMQ_HEARTBEAT_TTL + router.set(zmq::sockopt::heartbeat_ttl, 100); + CHECK(router.get(zmq::sockopt::heartbeat_ttl) == 100); +#endif +#ifdef ZMQ_IMMEDIATE + check_integral_opt(zmq::sockopt::immediate, router, "immediate"); +#endif +#ifdef ZMQ_INVERT_MATCHING + check_integral_opt(zmq::sockopt::invert_matching, router, + "invert_matching"); +#endif +#ifdef ZMQ_IPV6 + check_integral_opt(zmq::sockopt::ipv6, router, "ipv6"); +#endif +#ifdef ZMQ_LINGER + check_integral_opt(zmq::sockopt::linger, router, "linger"); +#endif +#ifdef ZMQ_MAXMSGSIZE + check_integral_opt(zmq::sockopt::maxmsgsize, router, "maxmsgsize"); +#endif +#ifdef ZMQ_MECHANISM + check_integral_opt_get(zmq::sockopt::mechanism, router, "mechanism"); +#endif +#ifdef ZMQ_MULTICAST_HOPS + check_integral_opt(zmq::sockopt::multicast_hops, router, "multicast_hops"); +#endif +#ifdef ZMQ_MULTICAST_LOOP + check_integral_opt(zmq::sockopt::multicast_loop, router, "multicast_loop"); +#endif +#ifdef ZMQ_MULTICAST_MAXTPDU + check_integral_opt(zmq::sockopt::multicast_maxtpdu, router, + "multicast_maxtpdu"); +#endif +#ifdef ZMQ_PLAIN_SERVER + check_integral_opt(zmq::sockopt::plain_server, router, "plain_server"); +#endif +#ifdef ZMQ_USE_FD + check_integral_opt(zmq::sockopt::use_fd, router, "use_fd"); +#endif +#ifdef ZMQ_PROBE_ROUTER + check_integral_opt(zmq::sockopt::probe_router, router, "probe_router", + true); +#endif +#ifdef ZMQ_RATE + check_integral_opt(zmq::sockopt::rate, router, "rate"); +#endif +#ifdef ZMQ_RCVBUF + check_integral_opt(zmq::sockopt::rcvbuf, router, "rcvbuf"); +#endif +#ifdef ZMQ_RCVHWM + check_integral_opt(zmq::sockopt::rcvhwm, router, "rcvhwm"); +#endif +#ifdef ZMQ_RCVMORE + check_integral_opt_get(zmq::sockopt::rcvmore, router, "rcvmore"); +#endif +#ifdef ZMQ_RCVTIMEO + check_integral_opt(zmq::sockopt::rcvtimeo, router, "rcvtimeo"); +#endif +#ifdef ZMQ_RECONNECT_IVL + check_integral_opt(zmq::sockopt::reconnect_ivl, router, "reconnect_ivl"); +#endif +#ifdef ZMQ_RECONNECT_IVL_MAX + check_integral_opt(zmq::sockopt::reconnect_ivl_max, router, + "reconnect_ivl_max"); +#endif +#ifdef ZMQ_RECOVERY_IVL + check_integral_opt(zmq::sockopt::recovery_ivl, router, "recovery_ivl"); +#endif +#ifdef ZMQ_REQ_CORRELATE + check_integral_opt(zmq::sockopt::req_correlate, req, "req_correlate", true); +#endif +#ifdef ZMQ_REQ_RELAXED + check_integral_opt(zmq::sockopt::req_relaxed, req, "req_relaxed", true); +#endif +#ifdef ZMQ_ROUTER_HANDOVER + check_integral_opt(zmq::sockopt::router_handover, router, "router_handover", + true); +#endif +#ifdef ZMQ_ROUTER_MANDATORY + check_integral_opt(zmq::sockopt::router_mandatory, router, + "router_mandatory", true); +#endif +#ifdef ZMQ_ROUTER_NOTIFY + check_integral_opt(zmq::sockopt::router_notify, router, "router_notify"); +#endif +#ifdef ZMQ_SNDBUF + check_integral_opt(zmq::sockopt::sndbuf, router, "sndbuf"); +#endif +#ifdef ZMQ_SNDHWM + check_integral_opt(zmq::sockopt::sndhwm, router, "sndhwm"); +#endif +#ifdef ZMQ_SNDTIMEO + check_integral_opt(zmq::sockopt::sndtimeo, router, "sndtimeo"); +#endif +#ifdef ZMQ_STREAM_NOTIFY + check_integral_opt(zmq::sockopt::stream_notify, stream, "stream_notify", + true); +#endif +#ifdef ZMQ_TCP_KEEPALIVE + check_integral_opt(zmq::sockopt::tcp_keepalive, router, "tcp_keepalive"); +#endif +#ifdef ZMQ_TCP_KEEPALIVE_CNT + check_integral_opt(zmq::sockopt::tcp_keepalive_cnt, router, + "tcp_keepalive_cnt"); +#endif +#ifdef ZMQ_TCP_KEEPALIVE_IDLE + check_integral_opt(zmq::sockopt::tcp_keepalive_idle, router, + "tcp_keepalive_idle"); +#endif +#ifdef ZMQ_TCP_KEEPALIVE_INTVL + check_integral_opt(zmq::sockopt::tcp_keepalive_intvl, router, + "tcp_keepalive_intvl"); +#endif +#ifdef ZMQ_TCP_MAXRT + check_integral_opt(zmq::sockopt::tcp_maxrt, router, "tcp_maxrt"); +#endif +#ifdef ZMQ_THREAD_SAFE + check_integral_opt_get(zmq::sockopt::thread_safe, router, "thread_safe"); +#endif +#ifdef ZMQ_TOS + check_integral_opt(zmq::sockopt::tos, router, "tos"); +#endif +#ifdef ZMQ_TYPE + check_integral_opt_get(zmq::sockopt::type, router, "type"); +#ifdef ZMQ_CPP11 + check_integral_opt_get(zmq::sockopt::socket_type, router, "socket_type"); +#endif // ZMQ_CPP11 +#endif // ZMQ_TYPE + +#ifdef ZMQ_HAVE_VMCI +#ifdef ZMQ_VMCI_BUFFER_SIZE + check_integral_opt(zmq::sockopt::vmci_buffer_size, router, + "vmci_buffer_size"); +#endif +#ifdef ZMQ_VMCI_BUFFER_MIN_SIZE + check_integral_opt(zmq::sockopt::vmci_buffer_min_size, router, + "vmci_buffer_min_size"); +#endif +#ifdef ZMQ_VMCI_BUFFER_MAX_SIZE + check_integral_opt(zmq::sockopt::vmci_buffer_max_size, router, + "vmci_buffer_max_size"); +#endif +#ifdef ZMQ_VMCI_CONNECT_TIMEOUT + check_integral_opt(zmq::sockopt::vmci_connect_timeout, router, + "vmci_connect_timeout"); +#endif +#endif + +#ifdef ZMQ_XPUB_VERBOSE + check_integral_opt(zmq::sockopt::xpub_verbose, xpub, "xpub_verbose", true); +#endif +#ifdef ZMQ_XPUB_VERBOSER + check_integral_opt(zmq::sockopt::xpub_verboser, xpub, "xpub_verboser", + true); +#endif +#ifdef ZMQ_XPUB_MANUAL + check_integral_opt(zmq::sockopt::xpub_manual, xpub, "xpub_manual", true); +#endif +#ifdef ZMQ_XPUB_NODROP + check_integral_opt(zmq::sockopt::xpub_nodrop, xpub, "xpub_nodrop", true); +#endif +#ifdef ZMQ_ZAP_ENFORCE_DOMAIN + check_integral_opt(zmq::sockopt::zap_enforce_domain, router, + "zap_enforce_domain"); +#endif +} + +#endif + +TEST_CASE("socket flags", "[socket]") +{ + CHECK((zmq::recv_flags::dontwait | zmq::recv_flags::none) + == static_cast(ZMQ_DONTWAIT | 0)); + CHECK((zmq::recv_flags::dontwait & zmq::recv_flags::none) + == static_cast(ZMQ_DONTWAIT & 0)); + CHECK((zmq::recv_flags::dontwait ^ zmq::recv_flags::none) + == static_cast(ZMQ_DONTWAIT ^ 0)); + CHECK(~zmq::recv_flags::dontwait == static_cast(~ZMQ_DONTWAIT)); + + CHECK((zmq::send_flags::dontwait | zmq::send_flags::sndmore) + == static_cast(ZMQ_DONTWAIT | ZMQ_SNDMORE)); + CHECK((zmq::send_flags::dontwait & zmq::send_flags::sndmore) + == static_cast(ZMQ_DONTWAIT & ZMQ_SNDMORE)); + CHECK((zmq::send_flags::dontwait ^ zmq::send_flags::sndmore) + == static_cast(ZMQ_DONTWAIT ^ ZMQ_SNDMORE)); + CHECK(~zmq::send_flags::dontwait == static_cast(~ZMQ_DONTWAIT)); +} + +TEST_CASE("socket readme example", "[socket]") +{ + zmq::context_t ctx; + zmq::socket_t sock(ctx, zmq::socket_type::push); + sock.bind("inproc://test"); + sock.send(zmq::str_buffer("Hello, world"), zmq::send_flags::dontwait); +} +#endif + +TEST_CASE("socket sends and receives const buffer", "[socket]") +{ + zmq::context_t context; + zmq::socket_t sender(context, ZMQ_PAIR); + zmq::socket_t receiver(context, ZMQ_PAIR); + receiver.bind("inproc://test"); + sender.connect("inproc://test"); + const char *str = "Hi"; + +#ifdef ZMQ_CPP11 + CHECK(2 == *sender.send(zmq::buffer(str, 2))); + char buf[2]; + const auto res = receiver.recv(zmq::buffer(buf)); + CHECK(res); + CHECK(!res->truncated()); + CHECK(2 == res->size); +#else + CHECK(2 == sender.send(str, 2)); + char buf[2]; + CHECK(2 == receiver.recv(buf, 2)); +#endif + CHECK(0 == memcmp(buf, str, 2)); +} + +#ifdef ZMQ_CPP11 + +TEST_CASE("socket send none sndmore", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::router); + s.bind("inproc://test"); + + std::vector buf(4); + auto res = s.send(zmq::buffer(buf), zmq::send_flags::sndmore); + CHECK(res); + CHECK(*res == buf.size()); + res = s.send(zmq::buffer(buf)); + CHECK(res); + CHECK(*res == buf.size()); +} + +TEST_CASE("socket send dontwait", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::push); + s.bind("inproc://test"); + + std::vector buf(4); + auto res = s.send(zmq::buffer(buf), zmq::send_flags::dontwait); + CHECK(!res); + res = + s.send(zmq::buffer(buf), zmq::send_flags::dontwait | zmq::send_flags::sndmore); + CHECK(!res); + + zmq::message_t msg; + auto resm = s.send(msg, zmq::send_flags::dontwait); + CHECK(!resm); + CHECK(msg.size() == 0); +} + +TEST_CASE("socket send exception", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::pull); + s.bind("inproc://test"); + + std::vector buf(4); + CHECK_THROWS_AS(s.send(zmq::buffer(buf)), zmq::error_t); +} + +TEST_CASE("socket recv none", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::pair); + zmq::socket_t s2(context, zmq::socket_type::pair); + s2.bind("inproc://test"); + s.connect("inproc://test"); + + std::vector sbuf(4); + const auto res_send = s2.send(zmq::buffer(sbuf)); + CHECK(res_send); + CHECK(res_send.has_value()); + + std::vector buf(2); + const auto res = s.recv(zmq::buffer(buf)); + CHECK(res.has_value()); + CHECK(res->truncated()); + CHECK(res->untruncated_size == sbuf.size()); + CHECK(res->size == buf.size()); + + const auto res_send2 = s2.send(zmq::buffer(sbuf)); + CHECK(res_send2.has_value()); + std::vector buf2(10); + const auto res2 = s.recv(zmq::buffer(buf2)); + CHECK(res2.has_value()); + CHECK(!res2->truncated()); + CHECK(res2->untruncated_size == sbuf.size()); + CHECK(res2->size == sbuf.size()); +} + +TEST_CASE("socket send recv message_t", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::pair); + zmq::socket_t s2(context, zmq::socket_type::pair); + s2.bind("inproc://test"); + s.connect("inproc://test"); + + zmq::message_t smsg(10); + const auto res_send = s2.send(smsg, zmq::send_flags::none); + CHECK(res_send); + CHECK(*res_send == 10); + CHECK(smsg.size() == 0); + + zmq::message_t rmsg; + const auto res = s.recv(rmsg); + CHECK(res); + CHECK(*res == 10); + CHECK(res.value() == 10); + CHECK(rmsg.size() == *res); +} + +TEST_CASE("socket send recv message_t by pointer", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::pair); + zmq::socket_t s2(context, zmq::socket_type::pair); + s2.bind("inproc://test"); + s.connect("inproc://test"); + + zmq::message_t smsg(size_t{10}); + const auto res_send = s2.send(smsg, zmq::send_flags::none); + CHECK(res_send); + CHECK(*res_send == 10); + CHECK(smsg.size() == 0); + + zmq::message_t rmsg; + const bool res = s.recv(&rmsg); + CHECK(res); +} + +TEST_CASE("socket recv dontwait", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::pull); + s.bind("inproc://test"); + + std::vector buf(4); + constexpr auto flags = zmq::recv_flags::none | zmq::recv_flags::dontwait; + auto res = s.recv(zmq::buffer(buf), flags); + CHECK(!res); + + zmq::message_t msg; + auto resm = s.recv(msg, flags); + CHECK(!resm); + CHECK_THROWS_AS(resm.value(), std::exception); + CHECK(msg.size() == 0); +} + +TEST_CASE("socket recv exception", "[socket]") +{ + zmq::context_t context; + zmq::socket_t s(context, zmq::socket_type::push); + s.bind("inproc://test"); + + std::vector buf(4); + CHECK_THROWS_AS(s.recv(zmq::buffer(buf)), zmq::error_t); +} + +TEST_CASE("socket proxy", "[socket]") +{ + zmq::context_t context; + zmq::socket_t front(context, ZMQ_ROUTER); + zmq::socket_t back(context, ZMQ_ROUTER); + zmq::socket_t capture(context, ZMQ_DEALER); + front.bind("inproc://test1"); + back.bind("inproc://test2"); + capture.bind("inproc://test3"); + auto f = std::async(std::launch::async, [&]() { + auto s1 = std::move(front); + auto s2 = std::move(back); + auto s3 = std::move(capture); + try { + zmq::proxy(s1, s2, zmq::socket_ref(s3)); + } + catch (const zmq::error_t &e) { + return e.num() == ETERM; + } + return false; + }); + context.close(); + CHECK(f.get()); +} + +TEST_CASE("socket proxy steerable", "[socket]") +{ + zmq::context_t context; + zmq::socket_t front(context, ZMQ_ROUTER); + zmq::socket_t back(context, ZMQ_ROUTER); + zmq::socket_t control(context, ZMQ_SUB); + front.bind("inproc://test1"); + back.bind("inproc://test2"); + control.connect("inproc://test3"); + auto f = std::async(std::launch::async, [&]() { + auto s1 = std::move(front); + auto s2 = std::move(back); + auto s3 = std::move(control); + try { + zmq::proxy_steerable(s1, s2, zmq::socket_ref(), s3); + } + catch (const zmq::error_t &e) { + return e.num() == ETERM; + } + return false; + }); + context.close(); + CHECK(f.get()); +} +#endif diff --git a/libs/cppzmq/tests/socket_ref.cpp b/libs/cppzmq/tests/socket_ref.cpp new file mode 100644 index 0000000..7658658 --- /dev/null +++ b/libs/cppzmq/tests/socket_ref.cpp @@ -0,0 +1,118 @@ +#include +#include +#ifdef ZMQ_CPP11 + +#ifdef ZMQ_CPP17 +static_assert(std::is_nothrow_swappable_v); +#endif +static_assert(sizeof(zmq::socket_ref) == sizeof(void *), "size mismatch"); +static_assert(alignof(zmq::socket_ref) == alignof(void *), "alignment mismatch"); +static_assert(ZMQ_IS_TRIVIALLY_COPYABLE(zmq::socket_ref), + "needs to be trivially copyable"); + +TEST_CASE("socket_ref default init", "[socket_ref]") +{ + zmq::socket_ref sr; + CHECK(!sr); + CHECK(sr == nullptr); + CHECK(nullptr == sr); + CHECK(sr.handle() == nullptr); +} + +TEST_CASE("socket_ref create from nullptr", "[socket_ref]") +{ + zmq::socket_ref sr = nullptr; + CHECK(sr == nullptr); + CHECK(sr.handle() == nullptr); +} + +TEST_CASE("socket_ref create from handle", "[socket_ref]") +{ + void *np = nullptr; + zmq::socket_ref sr{zmq::from_handle, np}; + CHECK(sr == nullptr); + CHECK(sr.handle() == nullptr); +} + +TEST_CASE("socket_ref compare", "[socket_ref]") +{ + zmq::socket_ref sr1; + zmq::socket_ref sr2; + CHECK(sr1 == sr2); + CHECK(!(sr1 != sr2)); +} + +TEST_CASE("socket_ref compare from socket_t", "[socket_ref]") +{ + zmq::context_t context; + zmq::socket_t s1(context, zmq::socket_type::router); + zmq::socket_t s2(context, zmq::socket_type::dealer); + zmq::socket_ref sr1 = s1; + zmq::socket_ref sr2 = s2; + CHECK(sr1); + CHECK(sr2); + CHECK(sr1 == s1); + CHECK(sr2 == s2); + CHECK(sr1.handle() == s1.handle()); + CHECK(sr1 != sr2); + CHECK(sr1.handle() != sr2.handle()); + CHECK(sr1 != nullptr); + CHECK(nullptr != sr1); + CHECK(sr2 != nullptr); + const bool comp1 = (sr1 < sr2) != (sr1 >= sr2); + CHECK(comp1); + const bool comp2 = (sr1 > sr2) != (sr1 <= sr2); + CHECK(comp2); + std::hash hash; + CHECK(hash(sr1) != hash(sr2)); + CHECK(hash(sr1) == hash(s1)); +} + +TEST_CASE("socket_ref assignment", "[socket_ref]") +{ + zmq::context_t context; + zmq::socket_t s1(context, zmq::socket_type::router); + zmq::socket_t s2(context, zmq::socket_type::dealer); + zmq::socket_ref sr1 = s1; + zmq::socket_ref sr2 = s2; + sr1 = s2; + CHECK(sr1 == sr2); + CHECK(sr1.handle() == sr2.handle()); + sr1 = std::move(sr2); + CHECK(sr1 == sr2); + CHECK(sr1.handle() == sr2.handle()); + sr2 = nullptr; + CHECK(sr1 != sr2); + sr1 = nullptr; + CHECK(sr1 == sr2); +} + +TEST_CASE("socket_ref swap", "[socket_ref]") +{ + zmq::socket_ref sr1; + zmq::socket_ref sr2; + using std::swap; + swap(sr1, sr2); +} + +TEST_CASE("socket_ref type punning", "[socket_ref]") +{ + struct SVP + { + void *p; + } svp; + struct SSR + { + zmq::socket_ref sr; + } ssr; + + zmq::context_t context; + zmq::socket_t socket(context, zmq::socket_type::router); + CHECK(socket.handle() != nullptr); + svp.p = socket.handle(); + // static_cast to silence incorrect warning + std::memcpy(static_cast(&ssr), &svp, sizeof(ssr)); + CHECK(ssr.sr == socket); +} + +#endif diff --git a/libs/cppzmq/tests/testutil.hpp b/libs/cppzmq/tests/testutil.hpp new file mode 100644 index 0000000..6c371de --- /dev/null +++ b/libs/cppzmq/tests/testutil.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#if defined(ZMQ_CPP11) + +inline std::string bind_ip4_loopback(zmq::socket_t &socket) +{ + socket.bind("tcp://127.0.0.1:*"); + std::string endpoint(100, ' '); + endpoint.resize(socket.get(zmq::sockopt::last_endpoint, zmq::buffer(endpoint))); + return endpoint; +} + +struct common_server_client_setup +{ + common_server_client_setup(bool initialize = true) + { + if (initialize) + init(); + } + + void init() + { + endpoint = bind_ip4_loopback(server); + REQUIRE_NOTHROW(client.connect(endpoint)); + } + + zmq::context_t context; + zmq::socket_t server{context, zmq::socket_type::pair}; + zmq::socket_t client{context, zmq::socket_type::pair}; + std::string endpoint; +}; +#endif + +#define CHECK_THROWS_ZMQ_ERROR(ecode, expr) \ + do { \ + try { \ + expr; \ + CHECK(false); \ + } \ + catch (const zmq::error_t &ze) { \ + INFO(std::string("Unexpected error code: ") + ze.what()); \ + CHECK(ze.num() == ecode); \ + } \ + catch (const std::exception &ex) { \ + INFO(std::string("Unexpected exception: ") + ex.what()); \ + CHECK(false); \ + } \ + catch (...) { \ + CHECK(false); \ + } \ + } while (false) diff --git a/libs/cppzmq/tests/utilities.cpp b/libs/cppzmq/tests/utilities.cpp new file mode 100644 index 0000000..30238a6 --- /dev/null +++ b/libs/cppzmq/tests/utilities.cpp @@ -0,0 +1,110 @@ +#include +#include + +#if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) + +namespace test_ns +{ +struct T_nr +{ +}; + +struct T_mr +{ + void *begin() const noexcept { return nullptr; } + void *end() const noexcept { return nullptr; } +}; + +struct T_fr +{ +}; + +inline void *begin(const T_fr &) noexcept +{ + return nullptr; +} + +inline void *end(const T_fr &) noexcept +{ + return nullptr; +} + +struct T_mfr +{ + void *begin() const noexcept { return nullptr; } + void *end() const noexcept { return nullptr; } +}; + +inline void *begin(const T_mfr &) noexcept +{ + return nullptr; +} + +inline void *end(const T_mfr &) noexcept +{ + return nullptr; +} + +// types with associated namespace std +struct T_assoc_ns_nr : std::exception +{ +}; + +struct T_assoc_ns_mr : std::exception +{ + void *begin() const noexcept { return nullptr; } + void *end() const noexcept { return nullptr; } +}; + +struct T_assoc_ns_fr : std::exception +{ +}; + +inline void *begin(const T_assoc_ns_fr &) noexcept +{ + return nullptr; +} + +inline void *end(const T_assoc_ns_fr &) noexcept +{ + return nullptr; +} + +struct T_assoc_ns_mfr : std::exception +{ + void *begin() const noexcept { return nullptr; } + void *end() const noexcept { return nullptr; } +}; + +inline void *begin(const T_assoc_ns_mfr &) noexcept +{ + return nullptr; +} + +inline void *end(const T_assoc_ns_mfr &) noexcept +{ + return nullptr; +} +} // namespace test_ns + +TEST_CASE("range SFINAE", "[utilities]") +{ + CHECK(!zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range>::value); + + CHECK(!zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + + CHECK(!zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); + CHECK(zmq::detail::is_range::value); +} + +#endif diff --git a/libs/cppzmq/version.sh b/libs/cppzmq/version.sh new file mode 100755 index 0000000..f7d9eb6 --- /dev/null +++ b/libs/cppzmq/version.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# This script extracts the 0MQ version from zmq.hpp, which is the master +# location for this information. +# +if [ ! -f zmq.hpp ]; then + echo "version.sh: error: zmq.hpp does not exist" 1>&2 + exit 1 +fi +MAJOR=$(grep '^#define CPPZMQ_VERSION_MAJOR \+[0-9]\+' zmq.hpp) +MINOR=$(grep '^#define CPPZMQ_VERSION_MINOR \+[0-9]\+' zmq.hpp) +PATCH=$(grep '^#define CPPZMQ_VERSION_PATCH \+[0-9]\+' zmq.hpp) +if [ -z "$MAJOR" -o -z "$MINOR" -o -z "$PATCH" ]; then + echo "version.sh: error: could not extract version from zmq.hpp" 1>&2 + exit 1 +fi +MAJOR=$(echo $MAJOR | awk '{ print $3 }') +MINOR=$(echo $MINOR | awk '{ print $3 }') +PATCH=$(echo $PATCH | awk '{ print $3 }') +echo $MAJOR.$MINOR.$PATCH | tr -d '\n\r' + diff --git a/libs/cppzmq/zmq.hpp b/libs/cppzmq/zmq.hpp new file mode 100644 index 0000000..775c6f6 --- /dev/null +++ b/libs/cppzmq/zmq.hpp @@ -0,0 +1,2721 @@ +/* + Copyright (c) 2016-2017 ZeroMQ community + Copyright (c) 2009-2011 250bpm s.r.o. + Copyright (c) 2011 Botond Ballo + Copyright (c) 2007-2009 iMatix Corporation + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ + +#ifndef __ZMQ_HPP_INCLUDED__ +#define __ZMQ_HPP_INCLUDED__ + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +// included here for _HAS_CXX* macros +#include + +#if defined(_MSVC_LANG) +#define CPPZMQ_LANG _MSVC_LANG +#else +#define CPPZMQ_LANG __cplusplus +#endif +// overwrite if specific language macros indicate higher version +#if defined(_HAS_CXX14) && _HAS_CXX14 && CPPZMQ_LANG < 201402L +#undef CPPZMQ_LANG +#define CPPZMQ_LANG 201402L +#endif +#if defined(_HAS_CXX17) && _HAS_CXX17 && CPPZMQ_LANG < 201703L +#undef CPPZMQ_LANG +#define CPPZMQ_LANG 201703L +#endif + +// macros defined if has a specific standard or greater +#if CPPZMQ_LANG >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) +#define ZMQ_CPP11 +#endif +#if CPPZMQ_LANG >= 201402L +#define ZMQ_CPP14 +#endif +#if CPPZMQ_LANG >= 201703L +#define ZMQ_CPP17 +#endif + +#if defined(ZMQ_CPP14) && !defined(_MSC_VER) +#define ZMQ_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(_MSC_VER) +#define ZMQ_DEPRECATED(msg) __declspec(deprecated(msg)) +#elif defined(__GNUC__) +#define ZMQ_DEPRECATED(msg) __attribute__((deprecated(msg))) +#else +#define ZMQ_DEPRECATED(msg) +#endif + +#if defined(ZMQ_CPP17) +#define ZMQ_NODISCARD [[nodiscard]] +#else +#define ZMQ_NODISCARD +#endif + +#if defined(ZMQ_CPP11) +#define ZMQ_NOTHROW noexcept +#define ZMQ_EXPLICIT explicit +#define ZMQ_OVERRIDE override +#define ZMQ_NULLPTR nullptr +#define ZMQ_CONSTEXPR_FN constexpr +#define ZMQ_CONSTEXPR_VAR constexpr +#define ZMQ_CPP11_DEPRECATED(msg) ZMQ_DEPRECATED(msg) +#else +#define ZMQ_NOTHROW throw() +#define ZMQ_EXPLICIT +#define ZMQ_OVERRIDE +#define ZMQ_NULLPTR 0 +#define ZMQ_CONSTEXPR_FN +#define ZMQ_CONSTEXPR_VAR const +#define ZMQ_CPP11_DEPRECATED(msg) +#endif +#if defined(ZMQ_CPP14) && (!defined(_MSC_VER) || _MSC_VER > 1900) && (!defined(__GNUC__) || __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ > 3)) +#define ZMQ_EXTENDED_CONSTEXPR +#endif +#if defined(ZMQ_CPP17) +#define ZMQ_INLINE_VAR inline +#define ZMQ_CONSTEXPR_IF constexpr +#else +#define ZMQ_INLINE_VAR +#define ZMQ_CONSTEXPR_IF +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef ZMQ_CPP11 +#include +#include +#include +#include +#endif + +#if defined(__has_include) && defined(ZMQ_CPP17) +#define CPPZMQ_HAS_INCLUDE_CPP17(X) __has_include(X) +#else +#define CPPZMQ_HAS_INCLUDE_CPP17(X) 0 +#endif + +#if CPPZMQ_HAS_INCLUDE_CPP17() && !defined(CPPZMQ_HAS_OPTIONAL) +#define CPPZMQ_HAS_OPTIONAL 1 +#endif +#ifndef CPPZMQ_HAS_OPTIONAL +#define CPPZMQ_HAS_OPTIONAL 0 +#elif CPPZMQ_HAS_OPTIONAL +#include +#endif + +#if CPPZMQ_HAS_INCLUDE_CPP17() && !defined(CPPZMQ_HAS_STRING_VIEW) +#define CPPZMQ_HAS_STRING_VIEW 1 +#endif +#ifndef CPPZMQ_HAS_STRING_VIEW +#define CPPZMQ_HAS_STRING_VIEW 0 +#elif CPPZMQ_HAS_STRING_VIEW +#include +#endif + +/* Version macros for compile-time API version detection */ +#define CPPZMQ_VERSION_MAJOR 4 +#define CPPZMQ_VERSION_MINOR 9 +#define CPPZMQ_VERSION_PATCH 0 + +#define CPPZMQ_VERSION \ + ZMQ_MAKE_VERSION(CPPZMQ_VERSION_MAJOR, CPPZMQ_VERSION_MINOR, \ + CPPZMQ_VERSION_PATCH) + +// Detect whether the compiler supports C++11 rvalue references. +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2)) \ + && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION = delete +#elif defined(__clang__) +#if __has_feature(cxx_rvalue_references) +#define ZMQ_HAS_RVALUE_REFS +#endif + +#if __has_feature(cxx_deleted_functions) +#define ZMQ_DELETED_FUNCTION = delete +#else +#define ZMQ_DELETED_FUNCTION +#endif +#elif defined(_MSC_VER) && (_MSC_VER >= 1900) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION = delete +#elif defined(_MSC_VER) && (_MSC_VER >= 1600) +#define ZMQ_HAS_RVALUE_REFS +#define ZMQ_DELETED_FUNCTION +#else +#define ZMQ_DELETED_FUNCTION +#endif + +#if defined(ZMQ_CPP11) && !defined(__llvm__) && !defined(__INTEL_COMPILER) \ + && defined(__GNUC__) && __GNUC__ < 5 +#define ZMQ_CPP11_PARTIAL +#elif defined(__GLIBCXX__) && __GLIBCXX__ < 20160805 +//the date here is the last date of gcc 4.9.4, which +// effectively means libstdc++ from gcc 5.5 and higher won't trigger this branch +#define ZMQ_CPP11_PARTIAL +#endif + +#ifdef ZMQ_CPP11 +#ifdef ZMQ_CPP11_PARTIAL +#define ZMQ_IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) +#else +#include +#define ZMQ_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value +#endif +#endif + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 3, 0) +#define ZMQ_NEW_MONITOR_EVENT_LAYOUT +#endif + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) +#define ZMQ_HAS_PROXY_STEERABLE +/* Socket event data */ +typedef struct +{ + uint16_t event; // id of the event as bitfield + int32_t value; // value is either error code, fd or reconnect interval +} zmq_event_t; +#endif + +// Avoid using deprecated message receive function when possible +#if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0) +#define zmq_msg_recv(msg, socket, flags) zmq_recvmsg(socket, msg, flags) +#endif + + +// In order to prevent unused variable warnings when building in non-debug +// mode use this macro to make assertions. +#ifndef NDEBUG +#define ZMQ_ASSERT(expression) assert(expression) +#else +#define ZMQ_ASSERT(expression) (void) (expression) +#endif + +namespace zmq +{ +#ifdef ZMQ_CPP11 +namespace detail +{ +namespace ranges +{ +using std::begin; +using std::end; +template auto begin(T &&r) -> decltype(begin(std::forward(r))) +{ + return begin(std::forward(r)); +} +template auto end(T &&r) -> decltype(end(std::forward(r))) +{ + return end(std::forward(r)); +} +} // namespace ranges + +template using void_t = void; + +template +using iter_value_t = typename std::iterator_traits::value_type; + +template +using range_iter_t = decltype( + ranges::begin(std::declval::type &>())); + +template using range_value_t = iter_value_t>; + +template struct is_range : std::false_type +{ +}; + +template +struct is_range< + T, + void_t::type &>()) + == ranges::end(std::declval::type &>()))>> + : std::true_type +{ +}; + +} // namespace detail +#endif + +typedef zmq_free_fn free_fn; +typedef zmq_pollitem_t pollitem_t; + +// duplicate definition from libzmq 4.3.3 +#if defined _WIN32 +#if defined _WIN64 +typedef unsigned __int64 fd_t; +#else +typedef unsigned int fd_t; +#endif +#else +typedef int fd_t; +#endif + +class error_t : public std::exception +{ + public: + error_t() ZMQ_NOTHROW : errnum(zmq_errno()) {} + explicit error_t(int err) ZMQ_NOTHROW : errnum(err) {} + virtual const char *what() const ZMQ_NOTHROW ZMQ_OVERRIDE + { + return zmq_strerror(errnum); + } + int num() const ZMQ_NOTHROW { return errnum; } + + private: + int errnum; +}; + +namespace detail { +inline int poll(zmq_pollitem_t *items_, size_t nitems_, long timeout_) +{ + int rc = zmq_poll(items_, static_cast(nitems_), timeout_); + if (rc < 0) + throw error_t(); + return rc; +} +} + +#ifdef ZMQ_CPP11 +ZMQ_DEPRECATED("from 4.8.0, use poll taking std::chrono::duration instead of long") +inline int poll(zmq_pollitem_t *items_, size_t nitems_, long timeout_) +#else +inline int poll(zmq_pollitem_t *items_, size_t nitems_, long timeout_ = -1) +#endif +{ + return detail::poll(items_, nitems_, timeout_); +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int poll(zmq_pollitem_t const *items_, size_t nitems_, long timeout_ = -1) +{ + return detail::poll(const_cast(items_), nitems_, timeout_); +} + +#ifdef ZMQ_CPP11 +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int +poll(zmq_pollitem_t const *items, size_t nitems, std::chrono::milliseconds timeout) +{ + return detail::poll(const_cast(items), nitems, + static_cast(timeout.count())); +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int poll(std::vector const &items, + std::chrono::milliseconds timeout) +{ + return detail::poll(const_cast(items.data()), items.size(), + static_cast(timeout.count())); +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") +inline int poll(std::vector const &items, long timeout_ = -1) +{ + return detail::poll(const_cast(items.data()), items.size(), timeout_); +} + +inline int +poll(zmq_pollitem_t *items, size_t nitems, std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) +{ + return detail::poll(items, nitems, static_cast(timeout.count())); +} + +inline int poll(std::vector &items, + std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) +{ + return detail::poll(items.data(), items.size(), static_cast(timeout.count())); +} + +ZMQ_DEPRECATED("from 4.3.1, use poll taking std::chrono::duration instead of long") +inline int poll(std::vector &items, long timeout_) +{ + return detail::poll(items.data(), items.size(), timeout_); +} + +template +inline int poll(std::array &items, + std::chrono::milliseconds timeout = std::chrono::milliseconds{-1}) +{ + return detail::poll(items.data(), items.size(), static_cast(timeout.count())); +} +#endif + + +inline void version(int *major_, int *minor_, int *patch_) +{ + zmq_version(major_, minor_, patch_); +} + +#ifdef ZMQ_CPP11 +inline std::tuple version() +{ + std::tuple v; + zmq_version(&std::get<0>(v), &std::get<1>(v), &std::get<2>(v)); + return v; +} + +#if !defined(ZMQ_CPP11_PARTIAL) +namespace detail +{ +template struct is_char_type +{ + // true if character type for string literals in C++11 + static constexpr bool value = + std::is_same::value || std::is_same::value + || std::is_same::value || std::is_same::value; +}; +} +#endif + +#endif + +class message_t +{ + public: + message_t() ZMQ_NOTHROW + { + int rc = zmq_msg_init(&msg); + ZMQ_ASSERT(rc == 0); + } + + explicit message_t(size_t size_) + { + int rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + } + + template message_t(ForwardIter first, ForwardIter last) + { + typedef typename std::iterator_traits::value_type value_t; + + assert(std::distance(first, last) >= 0); + size_t const size_ = + static_cast(std::distance(first, last)) * sizeof(value_t); + int const rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + std::copy(first, last, data()); + } + + message_t(const void *data_, size_t size_) + { + int rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + if (size_) { + // this constructor allows (nullptr, 0), + // memcpy with a null pointer is UB + memcpy(data(), data_, size_); + } + } + + message_t(void *data_, size_t size_, free_fn *ffn_, void *hint_ = ZMQ_NULLPTR) + { + int rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); + if (rc != 0) + throw error_t(); + } + + // overload set of string-like types and generic containers +#if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) + // NOTE this constructor will include the null terminator + // when called with a string literal. + // An overload taking const char* can not be added because + // it would be preferred over this function and break compatiblity. + template< + class Char, + size_t N, + typename = typename std::enable_if::value>::type> + ZMQ_DEPRECATED("from 4.7.0, use constructors taking iterators, (pointer, size) " + "or strings instead") + explicit message_t(const Char (&data)[N]) : + message_t(detail::ranges::begin(data), detail::ranges::end(data)) + { + } + + template::value + && ZMQ_IS_TRIVIALLY_COPYABLE(detail::range_value_t) + && !detail::is_char_type>::value + && !std::is_same::value>::type> + explicit message_t(const Range &rng) : + message_t(detail::ranges::begin(rng), detail::ranges::end(rng)) + { + } + + explicit message_t(const std::string &str) : message_t(str.data(), str.size()) {} + +#if CPPZMQ_HAS_STRING_VIEW + explicit message_t(std::string_view str) : message_t(str.data(), str.size()) {} +#endif + +#endif + +#ifdef ZMQ_HAS_RVALUE_REFS + message_t(message_t &&rhs) ZMQ_NOTHROW : msg(rhs.msg) + { + int rc = zmq_msg_init(&rhs.msg); + ZMQ_ASSERT(rc == 0); + } + + message_t &operator=(message_t &&rhs) ZMQ_NOTHROW + { + std::swap(msg, rhs.msg); + return *this; + } +#endif + + ~message_t() ZMQ_NOTHROW + { + int rc = zmq_msg_close(&msg); + ZMQ_ASSERT(rc == 0); + } + + void rebuild() + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init(&msg); + ZMQ_ASSERT(rc == 0); + } + + void rebuild(size_t size_) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + } + + void rebuild(const void *data_, size_t size_) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_size(&msg, size_); + if (rc != 0) + throw error_t(); + memcpy(data(), data_, size_); + } + + void rebuild(const std::string &str) + { + rebuild(str.data(), str.size()); + } + + void rebuild(void *data_, size_t size_, free_fn *ffn_, void *hint_ = ZMQ_NULLPTR) + { + int rc = zmq_msg_close(&msg); + if (rc != 0) + throw error_t(); + rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); + if (rc != 0) + throw error_t(); + } + + ZMQ_DEPRECATED("from 4.3.1, use move taking non-const reference instead") + void move(message_t const *msg_) + { + int rc = zmq_msg_move(&msg, const_cast(msg_->handle())); + if (rc != 0) + throw error_t(); + } + + void move(message_t &msg_) + { + int rc = zmq_msg_move(&msg, msg_.handle()); + if (rc != 0) + throw error_t(); + } + + ZMQ_DEPRECATED("from 4.3.1, use copy taking non-const reference instead") + void copy(message_t const *msg_) + { + int rc = zmq_msg_copy(&msg, const_cast(msg_->handle())); + if (rc != 0) + throw error_t(); + } + + void copy(message_t &msg_) + { + int rc = zmq_msg_copy(&msg, msg_.handle()); + if (rc != 0) + throw error_t(); + } + + bool more() const ZMQ_NOTHROW + { + int rc = zmq_msg_more(const_cast(&msg)); + return rc != 0; + } + + void *data() ZMQ_NOTHROW { return zmq_msg_data(&msg); } + + const void *data() const ZMQ_NOTHROW + { + return zmq_msg_data(const_cast(&msg)); + } + + size_t size() const ZMQ_NOTHROW + { + return zmq_msg_size(const_cast(&msg)); + } + + ZMQ_NODISCARD bool empty() const ZMQ_NOTHROW { return size() == 0u; } + + template T *data() ZMQ_NOTHROW { return static_cast(data()); } + + template T const *data() const ZMQ_NOTHROW + { + return static_cast(data()); + } + + ZMQ_DEPRECATED("from 4.3.0, use operator== instead") + bool equal(const message_t *other) const ZMQ_NOTHROW { return *this == *other; } + + bool operator==(const message_t &other) const ZMQ_NOTHROW + { + const size_t my_size = size(); + return my_size == other.size() && 0 == memcmp(data(), other.data(), my_size); + } + + bool operator!=(const message_t &other) const ZMQ_NOTHROW + { + return !(*this == other); + } + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 2, 0) + int get(int property_) + { + int value = zmq_msg_get(&msg, property_); + if (value == -1) + throw error_t(); + return value; + } +#endif + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) + const char *gets(const char *property_) + { + const char *value = zmq_msg_gets(&msg, property_); + if (value == ZMQ_NULLPTR) + throw error_t(); + return value; + } +#endif + +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + uint32_t routing_id() const + { + return zmq_msg_routing_id(const_cast(&msg)); + } + + void set_routing_id(uint32_t routing_id) + { + int rc = zmq_msg_set_routing_id(&msg, routing_id); + if (rc != 0) + throw error_t(); + } + + const char *group() const + { + return zmq_msg_group(const_cast(&msg)); + } + + void set_group(const char *group) + { + int rc = zmq_msg_set_group(&msg, group); + if (rc != 0) + throw error_t(); + } +#endif + + // interpret message content as a string + std::string to_string() const + { + return std::string(static_cast(data()), size()); + } +#if CPPZMQ_HAS_STRING_VIEW + // interpret message content as a string + std::string_view to_string_view() const noexcept + { + return std::string_view(static_cast(data()), size()); + } +#endif + + /** Dump content to string for debugging. + * Ascii chars are readable, the rest is printed as hex. + * Probably ridiculously slow. + * Use to_string() or to_string_view() for + * interpreting the message as a string. + */ + std::string str() const + { + // Partly mutuated from the same method in zmq::multipart_t + std::stringstream os; + + const unsigned char *msg_data = this->data(); + unsigned char byte; + size_t size = this->size(); + int is_ascii[2] = {0, 0}; + + os << "zmq::message_t [size " << std::dec << std::setw(3) + << std::setfill('0') << size << "] ("; + // Totally arbitrary + if (size >= 1000) { + os << "... too big to print)"; + } else { + while (size--) { + byte = *msg_data++; + + is_ascii[1] = (byte >= 32 && byte < 127); + if (is_ascii[1] != is_ascii[0]) + os << " "; // Separate text/non text + + if (is_ascii[1]) { + os << byte; + } else { + os << std::hex << std::uppercase << std::setw(2) + << std::setfill('0') << static_cast(byte); + } + is_ascii[0] = is_ascii[1]; + } + os << ")"; + } + return os.str(); + } + + void swap(message_t &other) ZMQ_NOTHROW + { + // this assumes zmq::msg_t from libzmq is trivially relocatable + std::swap(msg, other.msg); + } + + ZMQ_NODISCARD zmq_msg_t *handle() ZMQ_NOTHROW { return &msg; } + ZMQ_NODISCARD const zmq_msg_t *handle() const ZMQ_NOTHROW { return &msg; } + + private: + // The underlying message + zmq_msg_t msg; + + // Disable implicit message copying, so that users won't use shared + // messages (less efficient) without being aware of the fact. + message_t(const message_t &) ZMQ_DELETED_FUNCTION; + void operator=(const message_t &) ZMQ_DELETED_FUNCTION; +}; + +inline void swap(message_t &a, message_t &b) ZMQ_NOTHROW +{ + a.swap(b); +} + +#ifdef ZMQ_CPP11 +enum class ctxopt +{ +#ifdef ZMQ_BLOCKY + blocky = ZMQ_BLOCKY, +#endif +#ifdef ZMQ_IO_THREADS + io_threads = ZMQ_IO_THREADS, +#endif +#ifdef ZMQ_THREAD_SCHED_POLICY + thread_sched_policy = ZMQ_THREAD_SCHED_POLICY, +#endif +#ifdef ZMQ_THREAD_PRIORITY + thread_priority = ZMQ_THREAD_PRIORITY, +#endif +#ifdef ZMQ_THREAD_AFFINITY_CPU_ADD + thread_affinity_cpu_add = ZMQ_THREAD_AFFINITY_CPU_ADD, +#endif +#ifdef ZMQ_THREAD_AFFINITY_CPU_REMOVE + thread_affinity_cpu_remove = ZMQ_THREAD_AFFINITY_CPU_REMOVE, +#endif +#ifdef ZMQ_THREAD_NAME_PREFIX + thread_name_prefix = ZMQ_THREAD_NAME_PREFIX, +#endif +#ifdef ZMQ_MAX_MSGSZ + max_msgsz = ZMQ_MAX_MSGSZ, +#endif +#ifdef ZMQ_ZERO_COPY_RECV + zero_copy_recv = ZMQ_ZERO_COPY_RECV, +#endif +#ifdef ZMQ_MAX_SOCKETS + max_sockets = ZMQ_MAX_SOCKETS, +#endif +#ifdef ZMQ_SOCKET_LIMIT + socket_limit = ZMQ_SOCKET_LIMIT, +#endif +#ifdef ZMQ_IPV6 + ipv6 = ZMQ_IPV6, +#endif +#ifdef ZMQ_MSG_T_SIZE + msg_t_size = ZMQ_MSG_T_SIZE +#endif +}; +#endif + +class context_t +{ + public: + context_t() + { + ptr = zmq_ctx_new(); + if (ptr == ZMQ_NULLPTR) + throw error_t(); + } + + + explicit context_t(int io_threads_, int max_sockets_ = ZMQ_MAX_SOCKETS_DFLT) + { + ptr = zmq_ctx_new(); + if (ptr == ZMQ_NULLPTR) + throw error_t(); + + int rc = zmq_ctx_set(ptr, ZMQ_IO_THREADS, io_threads_); + ZMQ_ASSERT(rc == 0); + + rc = zmq_ctx_set(ptr, ZMQ_MAX_SOCKETS, max_sockets_); + ZMQ_ASSERT(rc == 0); + } + +#ifdef ZMQ_HAS_RVALUE_REFS + context_t(context_t &&rhs) ZMQ_NOTHROW : ptr(rhs.ptr) { rhs.ptr = ZMQ_NULLPTR; } + context_t &operator=(context_t &&rhs) ZMQ_NOTHROW + { + close(); + std::swap(ptr, rhs.ptr); + return *this; + } +#endif + + ~context_t() ZMQ_NOTHROW { close(); } + + ZMQ_CPP11_DEPRECATED("from 4.7.0, use set taking zmq::ctxopt instead") + int setctxopt(int option_, int optval_) + { + int rc = zmq_ctx_set(ptr, option_, optval_); + ZMQ_ASSERT(rc == 0); + return rc; + } + + ZMQ_CPP11_DEPRECATED("from 4.7.0, use get taking zmq::ctxopt instead") + int getctxopt(int option_) { return zmq_ctx_get(ptr, option_); } + +#ifdef ZMQ_CPP11 + void set(ctxopt option, int optval) + { + int rc = zmq_ctx_set(ptr, static_cast(option), optval); + if (rc == -1) + throw error_t(); + } + + ZMQ_NODISCARD int get(ctxopt option) + { + int rc = zmq_ctx_get(ptr, static_cast(option)); + // some options have a default value of -1 + // which is unfortunate, and may result in errors + // that don't make sense + if (rc == -1) + throw error_t(); + return rc; + } +#endif + + // Terminates context (see also shutdown()). + void close() ZMQ_NOTHROW + { + if (ptr == ZMQ_NULLPTR) + return; + + int rc; + do { + rc = zmq_ctx_term(ptr); + } while (rc == -1 && errno == EINTR); + + ZMQ_ASSERT(rc == 0); + ptr = ZMQ_NULLPTR; + } + + // Shutdown context in preparation for termination (close()). + // Causes all blocking socket operations and any further + // socket operations to return with ETERM. + void shutdown() ZMQ_NOTHROW + { + if (ptr == ZMQ_NULLPTR) + return; + int rc = zmq_ctx_shutdown(ptr); + ZMQ_ASSERT(rc == 0); + } + + // Be careful with this, it's probably only useful for + // using the C api together with an existing C++ api. + // Normally you should never need to use this. + ZMQ_EXPLICIT operator void *() ZMQ_NOTHROW { return ptr; } + + ZMQ_EXPLICIT operator void const *() const ZMQ_NOTHROW { return ptr; } + + ZMQ_NODISCARD void *handle() ZMQ_NOTHROW { return ptr; } + + ZMQ_DEPRECATED("from 4.7.0, use handle() != nullptr instead") + operator bool() const ZMQ_NOTHROW { return ptr != ZMQ_NULLPTR; } + + void swap(context_t &other) ZMQ_NOTHROW { std::swap(ptr, other.ptr); } + + private: + void *ptr; + + context_t(const context_t &) ZMQ_DELETED_FUNCTION; + void operator=(const context_t &) ZMQ_DELETED_FUNCTION; +}; + +inline void swap(context_t &a, context_t &b) ZMQ_NOTHROW +{ + a.swap(b); +} + +#ifdef ZMQ_CPP11 + +struct recv_buffer_size +{ + size_t size; // number of bytes written to buffer + size_t untruncated_size; // untruncated message size in bytes + + ZMQ_NODISCARD bool truncated() const noexcept + { + return size != untruncated_size; + } +}; + +#if CPPZMQ_HAS_OPTIONAL + +using send_result_t = std::optional; +using recv_result_t = std::optional; +using recv_buffer_result_t = std::optional; + +#else + +namespace detail +{ +// A C++11 type emulating the most basic +// operations of std::optional for trivial types +template class trivial_optional +{ + public: + static_assert(std::is_trivial::value, "T must be trivial"); + using value_type = T; + + trivial_optional() = default; + trivial_optional(T value) noexcept : _value(value), _has_value(true) {} + + const T *operator->() const noexcept + { + assert(_has_value); + return &_value; + } + T *operator->() noexcept + { + assert(_has_value); + return &_value; + } + + const T &operator*() const noexcept + { + assert(_has_value); + return _value; + } + T &operator*() noexcept + { + assert(_has_value); + return _value; + } + + T &value() + { + if (!_has_value) + throw std::exception(); + return _value; + } + const T &value() const + { + if (!_has_value) + throw std::exception(); + return _value; + } + + explicit operator bool() const noexcept { return _has_value; } + bool has_value() const noexcept { return _has_value; } + + private: + T _value{}; + bool _has_value{false}; +}; +} // namespace detail + +using send_result_t = detail::trivial_optional; +using recv_result_t = detail::trivial_optional; +using recv_buffer_result_t = detail::trivial_optional; + +#endif + +namespace detail +{ +template constexpr T enum_bit_or(T a, T b) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(static_cast(a) | static_cast(b)); +} +template constexpr T enum_bit_and(T a, T b) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(static_cast(a) & static_cast(b)); +} +template constexpr T enum_bit_xor(T a, T b) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(static_cast(a) ^ static_cast(b)); +} +template constexpr T enum_bit_not(T a) noexcept +{ + static_assert(std::is_enum::value, "must be enum"); + using U = typename std::underlying_type::type; + return static_cast(~static_cast(a)); +} +} // namespace detail + +// partially satisfies named requirement BitmaskType +enum class send_flags : int +{ + none = 0, + dontwait = ZMQ_DONTWAIT, + sndmore = ZMQ_SNDMORE +}; + +constexpr send_flags operator|(send_flags a, send_flags b) noexcept +{ + return detail::enum_bit_or(a, b); +} +constexpr send_flags operator&(send_flags a, send_flags b) noexcept +{ + return detail::enum_bit_and(a, b); +} +constexpr send_flags operator^(send_flags a, send_flags b) noexcept +{ + return detail::enum_bit_xor(a, b); +} +constexpr send_flags operator~(send_flags a) noexcept +{ + return detail::enum_bit_not(a); +} + +// partially satisfies named requirement BitmaskType +enum class recv_flags : int +{ + none = 0, + dontwait = ZMQ_DONTWAIT +}; + +constexpr recv_flags operator|(recv_flags a, recv_flags b) noexcept +{ + return detail::enum_bit_or(a, b); +} +constexpr recv_flags operator&(recv_flags a, recv_flags b) noexcept +{ + return detail::enum_bit_and(a, b); +} +constexpr recv_flags operator^(recv_flags a, recv_flags b) noexcept +{ + return detail::enum_bit_xor(a, b); +} +constexpr recv_flags operator~(recv_flags a) noexcept +{ + return detail::enum_bit_not(a); +} + + +// mutable_buffer, const_buffer and buffer are based on +// the Networking TS specification, draft: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf + +class mutable_buffer +{ + public: + constexpr mutable_buffer() noexcept : _data(nullptr), _size(0) {} + constexpr mutable_buffer(void *p, size_t n) noexcept : _data(p), _size(n) + { +#ifdef ZMQ_EXTENDED_CONSTEXPR + assert(p != nullptr || n == 0); +#endif + } + + constexpr void *data() const noexcept { return _data; } + constexpr size_t size() const noexcept { return _size; } + mutable_buffer &operator+=(size_t n) noexcept + { + // (std::min) is a workaround for when a min macro is defined + const auto shift = (std::min)(n, _size); + _data = static_cast(_data) + shift; + _size -= shift; + return *this; + } + + private: + void *_data; + size_t _size; +}; + +inline mutable_buffer operator+(const mutable_buffer &mb, size_t n) noexcept +{ + return mutable_buffer(static_cast(mb.data()) + (std::min)(n, mb.size()), + mb.size() - (std::min)(n, mb.size())); +} +inline mutable_buffer operator+(size_t n, const mutable_buffer &mb) noexcept +{ + return mb + n; +} + +class const_buffer +{ + public: + constexpr const_buffer() noexcept : _data(nullptr), _size(0) {} + constexpr const_buffer(const void *p, size_t n) noexcept : _data(p), _size(n) + { +#ifdef ZMQ_EXTENDED_CONSTEXPR + assert(p != nullptr || n == 0); +#endif + } + constexpr const_buffer(const mutable_buffer &mb) noexcept : + _data(mb.data()), _size(mb.size()) + { + } + + constexpr const void *data() const noexcept { return _data; } + constexpr size_t size() const noexcept { return _size; } + const_buffer &operator+=(size_t n) noexcept + { + const auto shift = (std::min)(n, _size); + _data = static_cast(_data) + shift; + _size -= shift; + return *this; + } + + private: + const void *_data; + size_t _size; +}; + +inline const_buffer operator+(const const_buffer &cb, size_t n) noexcept +{ + return const_buffer(static_cast(cb.data()) + + (std::min)(n, cb.size()), + cb.size() - (std::min)(n, cb.size())); +} +inline const_buffer operator+(size_t n, const const_buffer &cb) noexcept +{ + return cb + n; +} + +// buffer creation + +constexpr mutable_buffer buffer(void *p, size_t n) noexcept +{ + return mutable_buffer(p, n); +} +constexpr const_buffer buffer(const void *p, size_t n) noexcept +{ + return const_buffer(p, n); +} +constexpr mutable_buffer buffer(const mutable_buffer &mb) noexcept +{ + return mb; +} +inline mutable_buffer buffer(const mutable_buffer &mb, size_t n) noexcept +{ + return mutable_buffer(mb.data(), (std::min)(mb.size(), n)); +} +constexpr const_buffer buffer(const const_buffer &cb) noexcept +{ + return cb; +} +inline const_buffer buffer(const const_buffer &cb, size_t n) noexcept +{ + return const_buffer(cb.data(), (std::min)(cb.size(), n)); +} + +namespace detail +{ +template struct is_buffer +{ + static constexpr bool value = + std::is_same::value || std::is_same::value; +}; + +template struct is_pod_like +{ + // NOTE: The networking draft N4771 section 16.11 requires + // T in the buffer functions below to be + // trivially copyable OR standard layout. + // Here we decide to be conservative and require both. + static constexpr bool value = + ZMQ_IS_TRIVIALLY_COPYABLE(T) && std::is_standard_layout::value; +}; + +template constexpr auto seq_size(const C &c) noexcept -> decltype(c.size()) +{ + return c.size(); +} +template +constexpr size_t seq_size(const T (&/*array*/)[N]) noexcept +{ + return N; +} + +template +auto buffer_contiguous_sequence(Seq &&seq) noexcept + -> decltype(buffer(std::addressof(*std::begin(seq)), size_t{})) +{ + using T = typename std::remove_cv< + typename std::remove_reference::type>::type; + static_assert(detail::is_pod_like::value, "T must be POD"); + + const auto size = seq_size(seq); + return buffer(size != 0u ? std::addressof(*std::begin(seq)) : nullptr, + size * sizeof(T)); +} +template +auto buffer_contiguous_sequence(Seq &&seq, size_t n_bytes) noexcept + -> decltype(buffer_contiguous_sequence(seq)) +{ + using T = typename std::remove_cv< + typename std::remove_reference::type>::type; + static_assert(detail::is_pod_like::value, "T must be POD"); + + const auto size = seq_size(seq); + return buffer(size != 0u ? std::addressof(*std::begin(seq)) : nullptr, + (std::min)(size * sizeof(T), n_bytes)); +} + +} // namespace detail + +// C array +template mutable_buffer buffer(T (&data)[N]) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(T (&data)[N], size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template const_buffer buffer(const T (&data)[N]) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const T (&data)[N], size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +// std::array +template mutable_buffer buffer(std::array &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(std::array &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(std::array &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(std::array &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(const std::array &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const std::array &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +// std::vector +template +mutable_buffer buffer(std::vector &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(std::vector &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(const std::vector &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const std::vector &data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +// std::basic_string +template +mutable_buffer buffer(std::basic_string &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +mutable_buffer buffer(std::basic_string &data, + size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +template +const_buffer buffer(const std::basic_string &data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(const std::basic_string &data, + size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} + +#if CPPZMQ_HAS_STRING_VIEW +// std::basic_string_view +template +const_buffer buffer(std::basic_string_view data) noexcept +{ + return detail::buffer_contiguous_sequence(data); +} +template +const_buffer buffer(std::basic_string_view data, size_t n_bytes) noexcept +{ + return detail::buffer_contiguous_sequence(data, n_bytes); +} +#endif + +// Buffer for a string literal (null terminated) +// where the buffer size excludes the terminating character. +// Equivalent to zmq::buffer(std::string_view("...")). +template +constexpr const_buffer str_buffer(const Char (&data)[N]) noexcept +{ + static_assert(detail::is_pod_like::value, "Char must be POD"); +#ifdef ZMQ_EXTENDED_CONSTEXPR + assert(data[N - 1] == Char{0}); +#endif + return const_buffer(static_cast(data), (N - 1) * sizeof(Char)); +} + +namespace literals +{ +constexpr const_buffer operator"" _zbuf(const char *str, size_t len) noexcept +{ + return const_buffer(str, len * sizeof(char)); +} +constexpr const_buffer operator"" _zbuf(const wchar_t *str, size_t len) noexcept +{ + return const_buffer(str, len * sizeof(wchar_t)); +} +constexpr const_buffer operator"" _zbuf(const char16_t *str, size_t len) noexcept +{ + return const_buffer(str, len * sizeof(char16_t)); +} +constexpr const_buffer operator"" _zbuf(const char32_t *str, size_t len) noexcept +{ + return const_buffer(str, len * sizeof(char32_t)); +} +} + +#ifdef ZMQ_CPP11 +enum class socket_type : int +{ + req = ZMQ_REQ, + rep = ZMQ_REP, + dealer = ZMQ_DEALER, + router = ZMQ_ROUTER, + pub = ZMQ_PUB, + sub = ZMQ_SUB, + xpub = ZMQ_XPUB, + xsub = ZMQ_XSUB, + push = ZMQ_PUSH, + pull = ZMQ_PULL, +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + server = ZMQ_SERVER, + client = ZMQ_CLIENT, + radio = ZMQ_RADIO, + dish = ZMQ_DISH, + gather = ZMQ_GATHER, + scatter = ZMQ_SCATTER, + dgram = ZMQ_DGRAM, +#endif +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 3, 3) + peer = ZMQ_PEER, + channel = ZMQ_CHANNEL, +#endif +#if ZMQ_VERSION_MAJOR >= 4 + stream = ZMQ_STREAM, +#endif + pair = ZMQ_PAIR +}; +#endif + +namespace sockopt +{ +// There are two types of options, +// integral type with known compiler time size (int, bool, int64_t, uint64_t) +// and arrays with dynamic size (strings, binary data). + +// BoolUnit: if true accepts values of type bool (but passed as T into libzmq) +template struct integral_option +{ +}; + +// NullTerm: +// 0: binary data +// 1: null-terminated string (`getsockopt` size includes null) +// 2: binary (size 32) or Z85 encoder string of size 41 (null included) +template struct array_option +{ +}; + +#define ZMQ_DEFINE_INTEGRAL_OPT(OPT, NAME, TYPE) \ + using NAME##_t = integral_option; \ + ZMQ_INLINE_VAR ZMQ_CONSTEXPR_VAR NAME##_t NAME {} +#define ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(OPT, NAME, TYPE) \ + using NAME##_t = integral_option; \ + ZMQ_INLINE_VAR ZMQ_CONSTEXPR_VAR NAME##_t NAME {} +#define ZMQ_DEFINE_ARRAY_OPT(OPT, NAME) \ + using NAME##_t = array_option; \ + ZMQ_INLINE_VAR ZMQ_CONSTEXPR_VAR NAME##_t NAME {} +#define ZMQ_DEFINE_ARRAY_OPT_BINARY(OPT, NAME) \ + using NAME##_t = array_option; \ + ZMQ_INLINE_VAR ZMQ_CONSTEXPR_VAR NAME##_t NAME {} +#define ZMQ_DEFINE_ARRAY_OPT_BIN_OR_Z85(OPT, NAME) \ + using NAME##_t = array_option; \ + ZMQ_INLINE_VAR ZMQ_CONSTEXPR_VAR NAME##_t NAME {} + +// deprecated, use zmq::fd_t +using cppzmq_fd_t = ::zmq::fd_t; + +#ifdef ZMQ_AFFINITY +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_AFFINITY, affinity, uint64_t); +#endif +#ifdef ZMQ_BACKLOG +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_BACKLOG, backlog, int); +#endif +#ifdef ZMQ_BINDTODEVICE +ZMQ_DEFINE_ARRAY_OPT_BINARY(ZMQ_BINDTODEVICE, bindtodevice); +#endif +#ifdef ZMQ_CONFLATE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_CONFLATE, conflate, int); +#endif +#ifdef ZMQ_CONNECT_ROUTING_ID +ZMQ_DEFINE_ARRAY_OPT(ZMQ_CONNECT_ROUTING_ID, connect_routing_id); +#endif +#ifdef ZMQ_CONNECT_TIMEOUT +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_CONNECT_TIMEOUT, connect_timeout, int); +#endif +#ifdef ZMQ_CURVE_PUBLICKEY +ZMQ_DEFINE_ARRAY_OPT_BIN_OR_Z85(ZMQ_CURVE_PUBLICKEY, curve_publickey); +#endif +#ifdef ZMQ_CURVE_SECRETKEY +ZMQ_DEFINE_ARRAY_OPT_BIN_OR_Z85(ZMQ_CURVE_SECRETKEY, curve_secretkey); +#endif +#ifdef ZMQ_CURVE_SERVER +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_CURVE_SERVER, curve_server, int); +#endif +#ifdef ZMQ_CURVE_SERVERKEY +ZMQ_DEFINE_ARRAY_OPT_BIN_OR_Z85(ZMQ_CURVE_SERVERKEY, curve_serverkey); +#endif +#ifdef ZMQ_EVENTS +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_EVENTS, events, int); +#endif +#ifdef ZMQ_FD +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_FD, fd, ::zmq::fd_t); +#endif +#ifdef ZMQ_GSSAPI_PLAINTEXT +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_GSSAPI_PLAINTEXT, gssapi_plaintext, int); +#endif +#ifdef ZMQ_GSSAPI_SERVER +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_GSSAPI_SERVER, gssapi_server, int); +#endif +#ifdef ZMQ_GSSAPI_SERVICE_PRINCIPAL +ZMQ_DEFINE_ARRAY_OPT(ZMQ_GSSAPI_SERVICE_PRINCIPAL, gssapi_service_principal); +#endif +#ifdef ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_GSSAPI_SERVICE_PRINCIPAL_NAMETYPE, + gssapi_service_principal_nametype, + int); +#endif +#ifdef ZMQ_GSSAPI_PRINCIPAL +ZMQ_DEFINE_ARRAY_OPT(ZMQ_GSSAPI_PRINCIPAL, gssapi_principal); +#endif +#ifdef ZMQ_GSSAPI_PRINCIPAL_NAMETYPE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_GSSAPI_PRINCIPAL_NAMETYPE, + gssapi_principal_nametype, + int); +#endif +#ifdef ZMQ_HANDSHAKE_IVL +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_HANDSHAKE_IVL, handshake_ivl, int); +#endif +#ifdef ZMQ_HEARTBEAT_IVL +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_HEARTBEAT_IVL, heartbeat_ivl, int); +#endif +#ifdef ZMQ_HEARTBEAT_TIMEOUT +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_HEARTBEAT_TIMEOUT, heartbeat_timeout, int); +#endif +#ifdef ZMQ_HEARTBEAT_TTL +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_HEARTBEAT_TTL, heartbeat_ttl, int); +#endif +#ifdef ZMQ_IMMEDIATE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_IMMEDIATE, immediate, int); +#endif +#ifdef ZMQ_INVERT_MATCHING +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_INVERT_MATCHING, invert_matching, int); +#endif +#ifdef ZMQ_IPV6 +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_IPV6, ipv6, int); +#endif +#ifdef ZMQ_LAST_ENDPOINT +ZMQ_DEFINE_ARRAY_OPT(ZMQ_LAST_ENDPOINT, last_endpoint); +#endif +#ifdef ZMQ_LINGER +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_LINGER, linger, int); +#endif +#ifdef ZMQ_MAXMSGSIZE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_MAXMSGSIZE, maxmsgsize, int64_t); +#endif +#ifdef ZMQ_MECHANISM +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_MECHANISM, mechanism, int); +#endif +#ifdef ZMQ_METADATA +ZMQ_DEFINE_ARRAY_OPT(ZMQ_METADATA, metadata); +#endif +#ifdef ZMQ_MULTICAST_HOPS +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_MULTICAST_HOPS, multicast_hops, int); +#endif +#ifdef ZMQ_MULTICAST_LOOP +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_MULTICAST_LOOP, multicast_loop, int); +#endif +#ifdef ZMQ_MULTICAST_MAXTPDU +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_MULTICAST_MAXTPDU, multicast_maxtpdu, int); +#endif +#ifdef ZMQ_ONLY_FIRST_SUBSCRIBE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_ONLY_FIRST_SUBSCRIBE, only_first_subscribe, int); +#endif +#ifdef ZMQ_PLAIN_SERVER +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_PLAIN_SERVER, plain_server, int); +#endif +#ifdef ZMQ_PLAIN_PASSWORD +ZMQ_DEFINE_ARRAY_OPT(ZMQ_PLAIN_PASSWORD, plain_password); +#endif +#ifdef ZMQ_PLAIN_USERNAME +ZMQ_DEFINE_ARRAY_OPT(ZMQ_PLAIN_USERNAME, plain_username); +#endif +#ifdef ZMQ_USE_FD +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_USE_FD, use_fd, int); +#endif +#ifdef ZMQ_PROBE_ROUTER +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_PROBE_ROUTER, probe_router, int); +#endif +#ifdef ZMQ_RATE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RATE, rate, int); +#endif +#ifdef ZMQ_RCVBUF +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RCVBUF, rcvbuf, int); +#endif +#ifdef ZMQ_RCVHWM +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RCVHWM, rcvhwm, int); +#endif +#ifdef ZMQ_RCVMORE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_RCVMORE, rcvmore, int); +#endif +#ifdef ZMQ_RCVTIMEO +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RCVTIMEO, rcvtimeo, int); +#endif +#ifdef ZMQ_RECONNECT_IVL +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RECONNECT_IVL, reconnect_ivl, int); +#endif +#ifdef ZMQ_RECONNECT_IVL_MAX +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RECONNECT_IVL_MAX, reconnect_ivl_max, int); +#endif +#ifdef ZMQ_RECOVERY_IVL +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_RECOVERY_IVL, recovery_ivl, int); +#endif +#ifdef ZMQ_REQ_CORRELATE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_REQ_CORRELATE, req_correlate, int); +#endif +#ifdef ZMQ_REQ_RELAXED +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_REQ_RELAXED, req_relaxed, int); +#endif +#ifdef ZMQ_ROUTER_HANDOVER +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_ROUTER_HANDOVER, router_handover, int); +#endif +#ifdef ZMQ_ROUTER_MANDATORY +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_ROUTER_MANDATORY, router_mandatory, int); +#endif +#ifdef ZMQ_ROUTER_NOTIFY +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_ROUTER_NOTIFY, router_notify, int); +#endif +#ifdef ZMQ_ROUTING_ID +ZMQ_DEFINE_ARRAY_OPT_BINARY(ZMQ_ROUTING_ID, routing_id); +#endif +#ifdef ZMQ_SNDBUF +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_SNDBUF, sndbuf, int); +#endif +#ifdef ZMQ_SNDHWM +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_SNDHWM, sndhwm, int); +#endif +#ifdef ZMQ_SNDTIMEO +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_SNDTIMEO, sndtimeo, int); +#endif +#ifdef ZMQ_SOCKS_PROXY +ZMQ_DEFINE_ARRAY_OPT(ZMQ_SOCKS_PROXY, socks_proxy); +#endif +#ifdef ZMQ_STREAM_NOTIFY +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_STREAM_NOTIFY, stream_notify, int); +#endif +#ifdef ZMQ_SUBSCRIBE +ZMQ_DEFINE_ARRAY_OPT(ZMQ_SUBSCRIBE, subscribe); +#endif +#ifdef ZMQ_TCP_KEEPALIVE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TCP_KEEPALIVE, tcp_keepalive, int); +#endif +#ifdef ZMQ_TCP_KEEPALIVE_CNT +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TCP_KEEPALIVE_CNT, tcp_keepalive_cnt, int); +#endif +#ifdef ZMQ_TCP_KEEPALIVE_IDLE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TCP_KEEPALIVE_IDLE, tcp_keepalive_idle, int); +#endif +#ifdef ZMQ_TCP_KEEPALIVE_INTVL +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TCP_KEEPALIVE_INTVL, tcp_keepalive_intvl, int); +#endif +#ifdef ZMQ_TCP_MAXRT +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TCP_MAXRT, tcp_maxrt, int); +#endif +#ifdef ZMQ_THREAD_SAFE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_THREAD_SAFE, thread_safe, int); +#endif +#ifdef ZMQ_TOS +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TOS, tos, int); +#endif +#ifdef ZMQ_TYPE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TYPE, type, int); +#ifdef ZMQ_CPP11 +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_TYPE, socket_type, socket_type); +#endif // ZMQ_CPP11 +#endif // ZMQ_TYPE +#ifdef ZMQ_UNSUBSCRIBE +ZMQ_DEFINE_ARRAY_OPT(ZMQ_UNSUBSCRIBE, unsubscribe); +#endif +#ifdef ZMQ_VMCI_BUFFER_SIZE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_VMCI_BUFFER_SIZE, vmci_buffer_size, uint64_t); +#endif +#ifdef ZMQ_VMCI_BUFFER_MIN_SIZE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_VMCI_BUFFER_MIN_SIZE, vmci_buffer_min_size, uint64_t); +#endif +#ifdef ZMQ_VMCI_BUFFER_MAX_SIZE +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_VMCI_BUFFER_MAX_SIZE, vmci_buffer_max_size, uint64_t); +#endif +#ifdef ZMQ_VMCI_CONNECT_TIMEOUT +ZMQ_DEFINE_INTEGRAL_OPT(ZMQ_VMCI_CONNECT_TIMEOUT, vmci_connect_timeout, int); +#endif +#ifdef ZMQ_XPUB_VERBOSE +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_XPUB_VERBOSE, xpub_verbose, int); +#endif +#ifdef ZMQ_XPUB_VERBOSER +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_XPUB_VERBOSER, xpub_verboser, int); +#endif +#ifdef ZMQ_XPUB_MANUAL +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_XPUB_MANUAL, xpub_manual, int); +#endif +#ifdef ZMQ_XPUB_NODROP +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_XPUB_NODROP, xpub_nodrop, int); +#endif +#ifdef ZMQ_XPUB_WELCOME_MSG +ZMQ_DEFINE_ARRAY_OPT(ZMQ_XPUB_WELCOME_MSG, xpub_welcome_msg); +#endif +#ifdef ZMQ_ZAP_ENFORCE_DOMAIN +ZMQ_DEFINE_INTEGRAL_BOOL_UNIT_OPT(ZMQ_ZAP_ENFORCE_DOMAIN, zap_enforce_domain, int); +#endif +#ifdef ZMQ_ZAP_DOMAIN +ZMQ_DEFINE_ARRAY_OPT(ZMQ_ZAP_DOMAIN, zap_domain); +#endif + +} // namespace sockopt +#endif // ZMQ_CPP11 + + +namespace detail +{ +class socket_base +{ + public: + socket_base() ZMQ_NOTHROW : _handle(ZMQ_NULLPTR) {} + ZMQ_EXPLICIT socket_base(void *handle) ZMQ_NOTHROW : _handle(handle) {} + + template + ZMQ_CPP11_DEPRECATED("from 4.7.0, use `set` taking option from zmq::sockopt") + void setsockopt(int option_, T const &optval) + { + setsockopt(option_, &optval, sizeof(T)); + } + + ZMQ_CPP11_DEPRECATED("from 4.7.0, use `set` taking option from zmq::sockopt") + void setsockopt(int option_, const void *optval_, size_t optvallen_) + { + int rc = zmq_setsockopt(_handle, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + ZMQ_CPP11_DEPRECATED("from 4.7.0, use `get` taking option from zmq::sockopt") + void getsockopt(int option_, void *optval_, size_t *optvallen_) const + { + int rc = zmq_getsockopt(_handle, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + template + ZMQ_CPP11_DEPRECATED("from 4.7.0, use `get` taking option from zmq::sockopt") + T getsockopt(int option_) const + { + T optval; + size_t optlen = sizeof(T); + getsockopt(option_, &optval, &optlen); + return optval; + } + +#ifdef ZMQ_CPP11 + // Set integral socket option, e.g. + // `socket.set(zmq::sockopt::linger, 0)` + template + void set(sockopt::integral_option, const T &val) + { + static_assert(std::is_integral::value, "T must be integral"); + set_option(Opt, &val, sizeof val); + } + + // Set integral socket option from boolean, e.g. + // `socket.set(zmq::sockopt::immediate, false)` + template + void set(sockopt::integral_option, bool val) + { + static_assert(std::is_integral::value, "T must be integral"); + T rep_val = val; + set_option(Opt, &rep_val, sizeof rep_val); + } + + // Set array socket option, e.g. + // `socket.set(zmq::sockopt::plain_username, "foo123")` + template + void set(sockopt::array_option, const char *buf) + { + set_option(Opt, buf, std::strlen(buf)); + } + + // Set array socket option, e.g. + // `socket.set(zmq::sockopt::routing_id, zmq::buffer(id))` + template + void set(sockopt::array_option, const_buffer buf) + { + set_option(Opt, buf.data(), buf.size()); + } + + // Set array socket option, e.g. + // `socket.set(zmq::sockopt::routing_id, id_str)` + template + void set(sockopt::array_option, const std::string &buf) + { + set_option(Opt, buf.data(), buf.size()); + } + +#if CPPZMQ_HAS_STRING_VIEW + // Set array socket option, e.g. + // `socket.set(zmq::sockopt::routing_id, id_str)` + template + void set(sockopt::array_option, std::string_view buf) + { + set_option(Opt, buf.data(), buf.size()); + } +#endif + + // Get scalar socket option, e.g. + // `auto opt = socket.get(zmq::sockopt::linger)` + template + ZMQ_NODISCARD T get(sockopt::integral_option) const + { + static_assert(std::is_scalar::value, "T must be scalar"); + T val; + size_t size = sizeof val; + get_option(Opt, &val, &size); + assert(size == sizeof val); + return val; + } + + // Get array socket option, writes to buf, returns option size in bytes, e.g. + // `size_t optsize = socket.get(zmq::sockopt::routing_id, zmq::buffer(id))` + template + ZMQ_NODISCARD size_t get(sockopt::array_option, + mutable_buffer buf) const + { + size_t size = buf.size(); + get_option(Opt, buf.data(), &size); + return size; + } + + // Get array socket option as string (initializes the string buffer size to init_size) e.g. + // `auto s = socket.get(zmq::sockopt::routing_id)` + // Note: removes the null character from null-terminated string options, + // i.e. the string size excludes the null character. + template + ZMQ_NODISCARD std::string get(sockopt::array_option, + size_t init_size = 1024) const + { + if ZMQ_CONSTEXPR_IF (NullTerm == 2) { + if (init_size == 1024) { + init_size = 41; // get as Z85 string + } + } + std::string str(init_size, '\0'); + size_t size = get(sockopt::array_option{}, buffer(str)); + if ZMQ_CONSTEXPR_IF (NullTerm == 1) { + if (size > 0) { + assert(str[size - 1] == '\0'); + --size; + } + } else if ZMQ_CONSTEXPR_IF (NullTerm == 2) { + assert(size == 32 || size == 41); + if (size == 41) { + assert(str[size - 1] == '\0'); + --size; + } + } + str.resize(size); + return str; + } +#endif + + void bind(std::string const &addr) { bind(addr.c_str()); } + + void bind(const char *addr_) + { + int rc = zmq_bind(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + void unbind(std::string const &addr) { unbind(addr.c_str()); } + + void unbind(const char *addr_) + { + int rc = zmq_unbind(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + void connect(std::string const &addr) { connect(addr.c_str()); } + + void connect(const char *addr_) + { + int rc = zmq_connect(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + void disconnect(std::string const &addr) { disconnect(addr.c_str()); } + + void disconnect(const char *addr_) + { + int rc = zmq_disconnect(_handle, addr_); + if (rc != 0) + throw error_t(); + } + + ZMQ_DEPRECATED("from 4.7.1, use handle() != nullptr or operator bool") + bool connected() const ZMQ_NOTHROW { return (_handle != ZMQ_NULLPTR); } + + ZMQ_CPP11_DEPRECATED("from 4.3.1, use send taking a const_buffer and send_flags") + size_t send(const void *buf_, size_t len_, int flags_ = 0) + { + int nbytes = zmq_send(_handle, buf_, len_, flags_); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return 0; + throw error_t(); + } + + ZMQ_CPP11_DEPRECATED("from 4.3.1, use send taking message_t and send_flags") + bool send(message_t &msg_, + int flags_ = 0) // default until removed + { + int nbytes = zmq_msg_send(msg_.handle(), _handle, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + + template + ZMQ_CPP11_DEPRECATED( + "from 4.4.1, use send taking message_t or buffer (for contiguous " + "ranges), and send_flags") + bool send(T first, T last, int flags_ = 0) + { + zmq::message_t msg(first, last); + int nbytes = zmq_msg_send(msg.handle(), _handle, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + +#ifdef ZMQ_HAS_RVALUE_REFS + ZMQ_CPP11_DEPRECATED("from 4.3.1, use send taking message_t and send_flags") + bool send(message_t &&msg_, + int flags_ = 0) // default until removed + { +#ifdef ZMQ_CPP11 + return send(msg_, static_cast(flags_)).has_value(); +#else + return send(msg_, flags_); +#endif + } +#endif + +#ifdef ZMQ_CPP11 + send_result_t send(const_buffer buf, send_flags flags = send_flags::none) + { + const int nbytes = + zmq_send(_handle, buf.data(), buf.size(), static_cast(flags)); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } + + send_result_t send(message_t &msg, send_flags flags) + { + int nbytes = zmq_msg_send(msg.handle(), _handle, static_cast(flags)); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } + + send_result_t send(message_t &&msg, send_flags flags) + { + return send(msg, flags); + } +#endif + + ZMQ_CPP11_DEPRECATED( + "from 4.3.1, use recv taking a mutable_buffer and recv_flags") + size_t recv(void *buf_, size_t len_, int flags_ = 0) + { + int nbytes = zmq_recv(_handle, buf_, len_, flags_); + if (nbytes >= 0) + return static_cast(nbytes); + if (zmq_errno() == EAGAIN) + return 0; + throw error_t(); + } + + ZMQ_CPP11_DEPRECATED( + "from 4.3.1, use recv taking a reference to message_t and recv_flags") + bool recv(message_t *msg_, int flags_ = 0) + { + int nbytes = zmq_msg_recv(msg_->handle(), _handle, flags_); + if (nbytes >= 0) + return true; + if (zmq_errno() == EAGAIN) + return false; + throw error_t(); + } + +#ifdef ZMQ_CPP11 + ZMQ_NODISCARD + recv_buffer_result_t recv(mutable_buffer buf, + recv_flags flags = recv_flags::none) + { + const int nbytes = + zmq_recv(_handle, buf.data(), buf.size(), static_cast(flags)); + if (nbytes >= 0) { + return recv_buffer_size{ + (std::min)(static_cast(nbytes), buf.size()), + static_cast(nbytes)}; + } + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } + + ZMQ_NODISCARD + recv_result_t recv(message_t &msg, recv_flags flags = recv_flags::none) + { + const int nbytes = + zmq_msg_recv(msg.handle(), _handle, static_cast(flags)); + if (nbytes >= 0) { + assert(msg.size() == static_cast(nbytes)); + return static_cast(nbytes); + } + if (zmq_errno() == EAGAIN) + return {}; + throw error_t(); + } +#endif + +#if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) + void join(const char *group) + { + int rc = zmq_join(_handle, group); + if (rc != 0) + throw error_t(); + } + + void leave(const char *group) + { + int rc = zmq_leave(_handle, group); + if (rc != 0) + throw error_t(); + } +#endif + + ZMQ_NODISCARD void *handle() ZMQ_NOTHROW { return _handle; } + ZMQ_NODISCARD const void *handle() const ZMQ_NOTHROW { return _handle; } + + ZMQ_EXPLICIT operator bool() const ZMQ_NOTHROW { return _handle != ZMQ_NULLPTR; } + // note: non-const operator bool can be removed once + // operator void* is removed from socket_t + ZMQ_EXPLICIT operator bool() ZMQ_NOTHROW { return _handle != ZMQ_NULLPTR; } + + protected: + void *_handle; + + private: + void set_option(int option_, const void *optval_, size_t optvallen_) + { + int rc = zmq_setsockopt(_handle, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } + + void get_option(int option_, void *optval_, size_t *optvallen_) const + { + int rc = zmq_getsockopt(_handle, option_, optval_, optvallen_); + if (rc != 0) + throw error_t(); + } +}; +} // namespace detail + +struct from_handle_t +{ + struct _private + { + }; // disabling use other than with from_handle + ZMQ_CONSTEXPR_FN ZMQ_EXPLICIT from_handle_t(_private /*p*/) ZMQ_NOTHROW {} +}; + +ZMQ_CONSTEXPR_VAR from_handle_t from_handle = + from_handle_t(from_handle_t::_private()); + +// A non-owning nullable reference to a socket. +// The reference is invalidated on socket close or destruction. +class socket_ref : public detail::socket_base +{ + public: + socket_ref() ZMQ_NOTHROW : detail::socket_base() {} +#ifdef ZMQ_CPP11 + socket_ref(std::nullptr_t) ZMQ_NOTHROW : detail::socket_base() {} +#endif + socket_ref(from_handle_t /*fh*/, void *handle) ZMQ_NOTHROW + : detail::socket_base(handle) + { + } +}; + +#ifdef ZMQ_CPP11 +inline bool operator==(socket_ref sr, std::nullptr_t /*p*/) ZMQ_NOTHROW +{ + return sr.handle() == nullptr; +} +inline bool operator==(std::nullptr_t /*p*/, socket_ref sr) ZMQ_NOTHROW +{ + return sr.handle() == nullptr; +} +inline bool operator!=(socket_ref sr, std::nullptr_t /*p*/) ZMQ_NOTHROW +{ + return !(sr == nullptr); +} +inline bool operator!=(std::nullptr_t /*p*/, socket_ref sr) ZMQ_NOTHROW +{ + return !(sr == nullptr); +} +#endif + +inline bool operator==(const detail::socket_base& a, const detail::socket_base& b) ZMQ_NOTHROW +{ + return std::equal_to()(a.handle(), b.handle()); +} +inline bool operator!=(const detail::socket_base& a, const detail::socket_base& b) ZMQ_NOTHROW +{ + return !(a == b); +} +inline bool operator<(const detail::socket_base& a, const detail::socket_base& b) ZMQ_NOTHROW +{ + return std::less()(a.handle(), b.handle()); +} +inline bool operator>(const detail::socket_base& a, const detail::socket_base& b) ZMQ_NOTHROW +{ + return b < a; +} +inline bool operator<=(const detail::socket_base& a, const detail::socket_base& b) ZMQ_NOTHROW +{ + return !(a > b); +} +inline bool operator>=(const detail::socket_base& a, const detail::socket_base& b) ZMQ_NOTHROW +{ + return !(a < b); +} + +} // namespace zmq + +#ifdef ZMQ_CPP11 +namespace std +{ +template<> struct hash +{ + size_t operator()(zmq::socket_ref sr) const ZMQ_NOTHROW + { + return hash()(sr.handle()); + } +}; +} // namespace std +#endif + +namespace zmq +{ +class socket_t : public detail::socket_base +{ + friend class monitor_t; + + public: + socket_t() ZMQ_NOTHROW : detail::socket_base(ZMQ_NULLPTR), ctxptr(ZMQ_NULLPTR) {} + + socket_t(context_t &context_, int type_) : + detail::socket_base(zmq_socket(context_.handle(), type_)), + ctxptr(context_.handle()) + { + if (_handle == ZMQ_NULLPTR) + throw error_t(); + } + +#ifdef ZMQ_CPP11 + socket_t(context_t &context_, socket_type type_) : + socket_t(context_, static_cast(type_)) + { + } +#endif + +#ifdef ZMQ_HAS_RVALUE_REFS + socket_t(socket_t &&rhs) ZMQ_NOTHROW : detail::socket_base(rhs._handle), + ctxptr(rhs.ctxptr) + { + rhs._handle = ZMQ_NULLPTR; + rhs.ctxptr = ZMQ_NULLPTR; + } + socket_t &operator=(socket_t &&rhs) ZMQ_NOTHROW + { + close(); + std::swap(_handle, rhs._handle); + std::swap(ctxptr, rhs.ctxptr); + return *this; + } +#endif + + ~socket_t() ZMQ_NOTHROW { close(); } + + operator void *() ZMQ_NOTHROW { return _handle; } + + operator void const *() const ZMQ_NOTHROW { return _handle; } + + void close() ZMQ_NOTHROW + { + if (_handle == ZMQ_NULLPTR) + // already closed + return; + int rc = zmq_close(_handle); + ZMQ_ASSERT(rc == 0); + _handle = ZMQ_NULLPTR; + ctxptr = ZMQ_NULLPTR; + } + + void swap(socket_t &other) ZMQ_NOTHROW + { + std::swap(_handle, other._handle); + std::swap(ctxptr, other.ctxptr); + } + + operator socket_ref() ZMQ_NOTHROW { return socket_ref(from_handle, _handle); } + + private: + void *ctxptr; + + socket_t(const socket_t &) ZMQ_DELETED_FUNCTION; + void operator=(const socket_t &) ZMQ_DELETED_FUNCTION; + + // used by monitor_t + socket_t(void *context_, int type_) : + detail::socket_base(zmq_socket(context_, type_)), ctxptr(context_) + { + if (_handle == ZMQ_NULLPTR) + throw error_t(); + if (ctxptr == ZMQ_NULLPTR) + throw error_t(); + } +}; + +inline void swap(socket_t &a, socket_t &b) ZMQ_NOTHROW +{ + a.swap(b); +} + +ZMQ_DEPRECATED("from 4.3.1, use proxy taking socket_t objects") +inline void proxy(void *frontend, void *backend, void *capture) +{ + int rc = zmq_proxy(frontend, backend, capture); + if (rc != 0) + throw error_t(); +} + +inline void +proxy(socket_ref frontend, socket_ref backend, socket_ref capture = socket_ref()) +{ + int rc = zmq_proxy(frontend.handle(), backend.handle(), capture.handle()); + if (rc != 0) + throw error_t(); +} + +#ifdef ZMQ_HAS_PROXY_STEERABLE +ZMQ_DEPRECATED("from 4.3.1, use proxy_steerable taking socket_t objects") +inline void +proxy_steerable(void *frontend, void *backend, void *capture, void *control) +{ + int rc = zmq_proxy_steerable(frontend, backend, capture, control); + if (rc != 0) + throw error_t(); +} + +inline void proxy_steerable(socket_ref frontend, + socket_ref backend, + socket_ref capture, + socket_ref control) +{ + int rc = zmq_proxy_steerable(frontend.handle(), backend.handle(), + capture.handle(), control.handle()); + if (rc != 0) + throw error_t(); +} +#endif + +class monitor_t +{ + public: + monitor_t() : _socket(), _monitor_socket() {} + + virtual ~monitor_t() { close(); } + +#ifdef ZMQ_HAS_RVALUE_REFS + monitor_t(monitor_t &&rhs) ZMQ_NOTHROW : _socket(), _monitor_socket() + { + std::swap(_socket, rhs._socket); + std::swap(_monitor_socket, rhs._monitor_socket); + } + + monitor_t &operator=(monitor_t &&rhs) ZMQ_NOTHROW + { + close(); + _socket = socket_ref(); + std::swap(_socket, rhs._socket); + std::swap(_monitor_socket, rhs._monitor_socket); + return *this; + } +#endif + + + void + monitor(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) + { + monitor(socket, addr.c_str(), events); + } + + void monitor(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) + { + init(socket, addr_, events); + while (true) { + check_event(-1); + } + } + + void init(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) + { + init(socket, addr.c_str(), events); + } + + void init(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) + { + int rc = zmq_socket_monitor(socket.handle(), addr_, events); + if (rc != 0) + throw error_t(); + + _socket = socket; + _monitor_socket = socket_t(socket.ctxptr, ZMQ_PAIR); + _monitor_socket.connect(addr_); + + on_monitor_started(); + } + + bool check_event(int timeout = 0) + { + assert(_monitor_socket); + + zmq::message_t eventMsg; + + zmq::pollitem_t items[] = { + {_monitor_socket.handle(), 0, ZMQ_POLLIN, 0}, + }; + + #ifdef ZMQ_CPP11 + zmq::poll(&items[0], 1, std::chrono::milliseconds(timeout)); + #else + zmq::poll(&items[0], 1, timeout); + #endif + + if (items[0].revents & ZMQ_POLLIN) { + int rc = zmq_msg_recv(eventMsg.handle(), _monitor_socket.handle(), 0); + if (rc == -1 && zmq_errno() == ETERM) + return false; + assert(rc != -1); + + } else { + return false; + } + +#if ZMQ_VERSION_MAJOR >= 4 + const char *data = static_cast(eventMsg.data()); + zmq_event_t msgEvent; + memcpy(&msgEvent.event, data, sizeof(uint16_t)); + data += sizeof(uint16_t); + memcpy(&msgEvent.value, data, sizeof(int32_t)); + zmq_event_t *event = &msgEvent; +#else + zmq_event_t *event = static_cast(eventMsg.data()); +#endif + +#ifdef ZMQ_NEW_MONITOR_EVENT_LAYOUT + zmq::message_t addrMsg; + int rc = zmq_msg_recv(addrMsg.handle(), _monitor_socket.handle(), 0); + if (rc == -1 && zmq_errno() == ETERM) { + return false; + } + + assert(rc != -1); + std::string address = addrMsg.to_string(); +#else + // Bit of a hack, but all events in the zmq_event_t union have the same layout so this will work for all event types. + std::string address = event->data.connected.addr; +#endif + +#ifdef ZMQ_EVENT_MONITOR_STOPPED + if (event->event == ZMQ_EVENT_MONITOR_STOPPED) { + return false; + } + +#endif + + switch (event->event) { + case ZMQ_EVENT_CONNECTED: + on_event_connected(*event, address.c_str()); + break; + case ZMQ_EVENT_CONNECT_DELAYED: + on_event_connect_delayed(*event, address.c_str()); + break; + case ZMQ_EVENT_CONNECT_RETRIED: + on_event_connect_retried(*event, address.c_str()); + break; + case ZMQ_EVENT_LISTENING: + on_event_listening(*event, address.c_str()); + break; + case ZMQ_EVENT_BIND_FAILED: + on_event_bind_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_ACCEPTED: + on_event_accepted(*event, address.c_str()); + break; + case ZMQ_EVENT_ACCEPT_FAILED: + on_event_accept_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_CLOSED: + on_event_closed(*event, address.c_str()); + break; + case ZMQ_EVENT_CLOSE_FAILED: + on_event_close_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_DISCONNECTED: + on_event_disconnected(*event, address.c_str()); + break; +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 3, 0) || (defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3)) + case ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL: + on_event_handshake_failed_no_detail(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL: + on_event_handshake_failed_protocol(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_FAILED_AUTH: + on_event_handshake_failed_auth(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_SUCCEEDED: + on_event_handshake_succeeded(*event, address.c_str()); + break; +#elif defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) + case ZMQ_EVENT_HANDSHAKE_FAILED: + on_event_handshake_failed(*event, address.c_str()); + break; + case ZMQ_EVENT_HANDSHAKE_SUCCEED: + on_event_handshake_succeed(*event, address.c_str()); + break; +#endif + default: + on_event_unknown(*event, address.c_str()); + break; + } + + return true; + } + +#ifdef ZMQ_EVENT_MONITOR_STOPPED + void abort() + { + if (_socket) + zmq_socket_monitor(_socket.handle(), ZMQ_NULLPTR, 0); + + _socket = socket_ref(); + } +#endif + virtual void on_monitor_started() {} + virtual void on_event_connected(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_connect_delayed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_connect_retried(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_listening(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_bind_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_accepted(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_accept_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_closed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_close_failed(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_disconnected(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + virtual void on_event_handshake_failed_no_detail(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_failed_protocol(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_failed_auth(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_succeeded(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } +#elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) + virtual void on_event_handshake_failed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } + virtual void on_event_handshake_succeed(const zmq_event_t &event_, + const char *addr_) + { + (void) event_; + (void) addr_; + } +#endif + virtual void on_event_unknown(const zmq_event_t &event_, const char *addr_) + { + (void) event_; + (void) addr_; + } + + private: + monitor_t(const monitor_t &) ZMQ_DELETED_FUNCTION; + void operator=(const monitor_t &) ZMQ_DELETED_FUNCTION; + + socket_ref _socket; + socket_t _monitor_socket; + + void close() ZMQ_NOTHROW + { + if (_socket) + zmq_socket_monitor(_socket.handle(), ZMQ_NULLPTR, 0); + _monitor_socket.close(); + } +}; + +#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) + +// polling events +enum class event_flags : short +{ + none = 0, + pollin = ZMQ_POLLIN, + pollout = ZMQ_POLLOUT, + pollerr = ZMQ_POLLERR, + pollpri = ZMQ_POLLPRI +}; + +constexpr event_flags operator|(event_flags a, event_flags b) noexcept +{ + return detail::enum_bit_or(a, b); +} +constexpr event_flags operator&(event_flags a, event_flags b) noexcept +{ + return detail::enum_bit_and(a, b); +} +constexpr event_flags operator^(event_flags a, event_flags b) noexcept +{ + return detail::enum_bit_xor(a, b); +} +constexpr event_flags operator~(event_flags a) noexcept +{ + return detail::enum_bit_not(a); +} + +struct no_user_data; + +// layout compatible with zmq_poller_event_t +template struct poller_event +{ + socket_ref socket; + ::zmq::fd_t fd; + T *user_data; + event_flags events; +}; + +template class poller_t +{ + public: + using event_type = poller_event; + + poller_t() : poller_ptr(zmq_poller_new()) + { + if (!poller_ptr) + throw error_t(); + } + + template< + typename Dummy = void, + typename = + typename std::enable_if::value, Dummy>::type> + void add(zmq::socket_ref socket, event_flags events, T *user_data) + { + add_impl(socket, events, user_data); + } + + void add(zmq::socket_ref socket, event_flags events) + { + add_impl(socket, events, nullptr); + } + + void remove(zmq::socket_ref socket) + { + if (0 != zmq_poller_remove(poller_ptr.get(), socket.handle())) { + throw error_t(); + } + } + + void modify(zmq::socket_ref socket, event_flags events) + { + if (0 + != zmq_poller_modify(poller_ptr.get(), socket.handle(), + static_cast(events))) { + throw error_t(); + } + } + + size_t wait_all(std::vector &poller_events, + const std::chrono::milliseconds timeout) + { + int rc = zmq_poller_wait_all( + poller_ptr.get(), + reinterpret_cast(poller_events.data()), + static_cast(poller_events.size()), + static_cast(timeout.count())); + if (rc > 0) + return static_cast(rc); + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) + if (zmq_errno() == EAGAIN) +#else + if (zmq_errno() == ETIMEDOUT) +#endif + return 0; + + throw error_t(); + } + +#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 3, 3) + size_t size() const noexcept + { + int rc = zmq_poller_size(const_cast(poller_ptr.get())); + ZMQ_ASSERT(rc >= 0); + return static_cast(std::max(rc, 0)); + } +#endif + + private: + struct destroy_poller_t + { + void operator()(void *ptr) noexcept + { + int rc = zmq_poller_destroy(&ptr); + ZMQ_ASSERT(rc == 0); + } + }; + + std::unique_ptr poller_ptr; + + void add_impl(zmq::socket_ref socket, event_flags events, T *user_data) + { + if (0 + != zmq_poller_add(poller_ptr.get(), socket.handle(), user_data, + static_cast(events))) { + throw error_t(); + } + } +}; +#endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) + +inline std::ostream &operator<<(std::ostream &os, const message_t &msg) +{ + return os << msg.str(); +} + +} // namespace zmq + +#endif // __ZMQ_HPP_INCLUDED__ diff --git a/libs/cppzmq/zmq_addon.hpp b/libs/cppzmq/zmq_addon.hpp new file mode 100644 index 0000000..958eec5 --- /dev/null +++ b/libs/cppzmq/zmq_addon.hpp @@ -0,0 +1,753 @@ +/* + Copyright (c) 2016-2017 ZeroMQ community + Copyright (c) 2016 VOCA AS / Harald Nøkland + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ + +#ifndef __ZMQ_ADDON_HPP_INCLUDED__ +#define __ZMQ_ADDON_HPP_INCLUDED__ + +#include "zmq.hpp" + +#include +#include +#include +#include +#ifdef ZMQ_CPP11 +#include +#include +#include +#endif + +namespace zmq +{ +#ifdef ZMQ_CPP11 + +namespace detail +{ +template +recv_result_t +recv_multipart_n(socket_ref s, OutputIt out, size_t n, recv_flags flags) +{ + size_t msg_count = 0; + message_t msg; + while (true) { + if ZMQ_CONSTEXPR_IF (CheckN) { + if (msg_count >= n) + throw std::runtime_error( + "Too many message parts in recv_multipart_n"); + } + if (!s.recv(msg, flags)) { + // zmq ensures atomic delivery of messages + assert(msg_count == 0); + return {}; + } + ++msg_count; + const bool more = msg.more(); + *out++ = std::move(msg); + if (!more) + break; + } + return msg_count; +} + +inline bool is_little_endian() +{ + const uint16_t i = 0x01; + return *reinterpret_cast(&i) == 0x01; +} + +inline void write_network_order(unsigned char *buf, const uint32_t value) +{ + if (is_little_endian()) { + ZMQ_CONSTEXPR_VAR uint32_t mask = (std::numeric_limits::max)(); + *buf++ = static_cast((value >> 24) & mask); + *buf++ = static_cast((value >> 16) & mask); + *buf++ = static_cast((value >> 8) & mask); + *buf++ = static_cast(value & mask); + } else { + std::memcpy(buf, &value, sizeof(value)); + } +} + +inline uint32_t read_u32_network_order(const unsigned char *buf) +{ + if (is_little_endian()) { + return (static_cast(buf[0]) << 24) + + (static_cast(buf[1]) << 16) + + (static_cast(buf[2]) << 8) + + static_cast(buf[3]); + } else { + uint32_t value; + std::memcpy(&value, buf, sizeof(value)); + return value; + } +} +} // namespace detail + +/* Receive a multipart message. + + Writes the zmq::message_t objects to OutputIterator out. + The out iterator must handle an unspecified number of writes, + e.g. by using std::back_inserter. + + Returns: the number of messages received or nullopt (on EAGAIN). + Throws: if recv throws. Any exceptions thrown + by the out iterator will be propagated and the message + may have been only partially received with pending + message parts. It is adviced to close this socket in that event. +*/ +template +ZMQ_NODISCARD recv_result_t recv_multipart(socket_ref s, + OutputIt out, + recv_flags flags = recv_flags::none) +{ + return detail::recv_multipart_n(s, std::move(out), 0, flags); +} + +/* Receive a multipart message. + + Writes at most n zmq::message_t objects to OutputIterator out. + If the number of message parts of the incoming message exceeds n + then an exception will be thrown. + + Returns: the number of messages received or nullopt (on EAGAIN). + Throws: if recv throws. Throws std::runtime_error if the number + of message parts exceeds n (exactly n messages will have been written + to out). Any exceptions thrown + by the out iterator will be propagated and the message + may have been only partially received with pending + message parts. It is adviced to close this socket in that event. +*/ +template +ZMQ_NODISCARD recv_result_t recv_multipart_n(socket_ref s, + OutputIt out, + size_t n, + recv_flags flags = recv_flags::none) +{ + return detail::recv_multipart_n(s, std::move(out), n, flags); +} + +/* Send a multipart message. + + The range must be a ForwardRange of zmq::message_t, + zmq::const_buffer or zmq::mutable_buffer. + The flags may be zmq::send_flags::sndmore if there are + more message parts to be sent after the call to this function. + + Returns: the number of messages sent (exactly msgs.size()) or nullopt (on EAGAIN). + Throws: if send throws. Any exceptions thrown + by the msgs range will be propagated and the message + may have been only partially sent. It is adviced to close this socket in that event. +*/ +template::value + && (std::is_same, message_t>::value + || detail::is_buffer>::value)>::type +#endif + > +send_result_t +send_multipart(socket_ref s, Range &&msgs, send_flags flags = send_flags::none) +{ + using std::begin; + using std::end; + auto it = begin(msgs); + const auto end_it = end(msgs); + size_t msg_count = 0; + while (it != end_it) { + const auto next = std::next(it); + const auto msg_flags = + flags | (next == end_it ? send_flags::none : send_flags::sndmore); + if (!s.send(*it, msg_flags)) { + // zmq ensures atomic delivery of messages + assert(it == begin(msgs)); + return {}; + } + ++msg_count; + it = next; + } + return msg_count; +} + +/* Encode a multipart message. + + The range must be a ForwardRange of zmq::message_t. A + zmq::multipart_t or STL container may be passed for encoding. + + Returns: a zmq::message_t holding the encoded multipart data. + + Throws: std::range_error is thrown if the size of any single part + can not fit in an unsigned 32 bit integer. + + The encoding is compatible with that used by the CZMQ function + zmsg_encode(), see https://rfc.zeromq.org/spec/50/. + Each part consists of a size followed by the data. + These are placed contiguously into the output message. A part of + size less than 255 bytes will have a single byte size value. + Larger parts will have a five byte size value with the first byte + set to 0xFF and the remaining four bytes holding the size of the + part's data. +*/ +template::value + && (std::is_same, message_t>::value + || detail::is_buffer>::value)>::type +#endif + > +message_t encode(const Range &parts) +{ + size_t mmsg_size = 0; + + // First pass check sizes + for (const auto &part : parts) { + const size_t part_size = part.size(); + if (part_size > (std::numeric_limits::max)()) { + // Size value must fit into uint32_t. + throw std::range_error("Invalid size, message part too large"); + } + const size_t count_size = + part_size < (std::numeric_limits::max)() ? 1 : 5; + mmsg_size += part_size + count_size; + } + + message_t encoded(mmsg_size); + unsigned char *buf = encoded.data(); + for (const auto &part : parts) { + const uint32_t part_size = static_cast(part.size()); + const unsigned char *part_data = + static_cast(part.data()); + + if (part_size < (std::numeric_limits::max)()) { + // small part + *buf++ = (unsigned char) part_size; + } else { + // big part + *buf++ = (std::numeric_limits::max)(); + detail::write_network_order(buf, part_size); + buf += sizeof(part_size); + } + std::memcpy(buf, part_data, part_size); + buf += part_size; + } + + assert(static_cast(buf - encoded.data()) == mmsg_size); + return encoded; +} + +/* Decode an encoded message to multiple parts. + + The given output iterator must be a ForwardIterator to a container + holding zmq::message_t such as a zmq::multipart_t or various STL + containers. + + Returns the ForwardIterator advanced once past the last decoded + part. + + Throws: a std::out_of_range is thrown if the encoded part sizes + lead to exceeding the message data bounds. + + The decoding assumes the message is encoded in the manner + performed by zmq::encode(), see https://rfc.zeromq.org/spec/50/. + */ +template OutputIt decode(const message_t &encoded, OutputIt out) +{ + const unsigned char *source = encoded.data(); + const unsigned char *const limit = source + encoded.size(); + + while (source < limit) { + size_t part_size = *source++; + if (part_size == (std::numeric_limits::max)()) { + if (static_cast(limit - source) < sizeof(uint32_t)) { + throw std::out_of_range( + "Malformed encoding, overflow in reading size"); + } + part_size = detail::read_u32_network_order(source); + // the part size is allowed to be less than 0xFF + source += sizeof(uint32_t); + } + + if (static_cast(limit - source) < part_size) { + throw std::out_of_range("Malformed encoding, overflow in reading part"); + } + *out = message_t(source, part_size); + ++out; + source += part_size; + } + + assert(source == limit); + return out; +} + +#endif + + +#ifdef ZMQ_HAS_RVALUE_REFS + +/* + This class handles multipart messaging. It is the C++ equivalent of zmsg.h, + which is part of CZMQ (the high-level C binding). Furthermore, it is a major + improvement compared to zmsg.hpp, which is part of the examples in the ØMQ + Guide. Unnecessary copying is avoided by using move semantics to efficiently + add/remove parts. +*/ +class multipart_t +{ + private: + std::deque m_parts; + + public: + typedef std::deque::value_type value_type; + + typedef std::deque::iterator iterator; + typedef std::deque::const_iterator const_iterator; + + typedef std::deque::reverse_iterator reverse_iterator; + typedef std::deque::const_reverse_iterator const_reverse_iterator; + + // Default constructor + multipart_t() {} + + // Construct from socket receive + multipart_t(socket_ref socket) { recv(socket); } + + // Construct from memory block + multipart_t(const void *src, size_t size) { addmem(src, size); } + + // Construct from string + multipart_t(const std::string &string) { addstr(string); } + + // Construct from message part + multipart_t(message_t &&message) { add(std::move(message)); } + + // Move constructor + multipart_t(multipart_t &&other) ZMQ_NOTHROW { m_parts = std::move(other.m_parts); } + + // Move assignment operator + multipart_t &operator=(multipart_t &&other) ZMQ_NOTHROW + { + m_parts = std::move(other.m_parts); + return *this; + } + + // Destructor + virtual ~multipart_t() { clear(); } + + message_t &operator[](size_t n) { return m_parts[n]; } + + const message_t &operator[](size_t n) const { return m_parts[n]; } + + message_t &at(size_t n) { return m_parts.at(n); } + + const message_t &at(size_t n) const { return m_parts.at(n); } + + iterator begin() { return m_parts.begin(); } + + const_iterator begin() const { return m_parts.begin(); } + + const_iterator cbegin() const { return m_parts.cbegin(); } + + reverse_iterator rbegin() { return m_parts.rbegin(); } + + const_reverse_iterator rbegin() const { return m_parts.rbegin(); } + + iterator end() { return m_parts.end(); } + + const_iterator end() const { return m_parts.end(); } + + const_iterator cend() const { return m_parts.cend(); } + + reverse_iterator rend() { return m_parts.rend(); } + + const_reverse_iterator rend() const { return m_parts.rend(); } + + // Delete all parts + void clear() { m_parts.clear(); } + + // Get number of parts + size_t size() const { return m_parts.size(); } + + // Check if number of parts is zero + bool empty() const { return m_parts.empty(); } + + // Receive multipart message from socket + bool recv(socket_ref socket, int flags = 0) + { + clear(); + bool more = true; + while (more) { + message_t message; +#ifdef ZMQ_CPP11 + if (!socket.recv(message, static_cast(flags))) + return false; +#else + if (!socket.recv(&message, flags)) + return false; +#endif + more = message.more(); + add(std::move(message)); + } + return true; + } + + // Send multipart message to socket + bool send(socket_ref socket, int flags = 0) + { + flags &= ~(ZMQ_SNDMORE); + bool more = size() > 0; + while (more) { + message_t message = pop(); + more = size() > 0; +#ifdef ZMQ_CPP11 + if (!socket.send(message, static_cast( + (more ? ZMQ_SNDMORE : 0) | flags))) + return false; +#else + if (!socket.send(message, (more ? ZMQ_SNDMORE : 0) | flags)) + return false; +#endif + } + clear(); + return true; + } + + // Concatenate other multipart to front + void prepend(multipart_t &&other) + { + while (!other.empty()) + push(other.remove()); + } + + // Concatenate other multipart to back + void append(multipart_t &&other) + { + while (!other.empty()) + add(other.pop()); + } + + // Push memory block to front + void pushmem(const void *src, size_t size) + { + m_parts.push_front(message_t(src, size)); + } + + // Push memory block to back + void addmem(const void *src, size_t size) + { + m_parts.push_back(message_t(src, size)); + } + + // Push string to front + void pushstr(const std::string &string) + { + m_parts.push_front(message_t(string.data(), string.size())); + } + + // Push string to back + void addstr(const std::string &string) + { + m_parts.push_back(message_t(string.data(), string.size())); + } + + // Push type (fixed-size) to front + template void pushtyp(const T &type) + { + static_assert(!std::is_same::value, + "Use pushstr() instead of pushtyp()"); + m_parts.push_front(message_t(&type, sizeof(type))); + } + + // Push type (fixed-size) to back + template void addtyp(const T &type) + { + static_assert(!std::is_same::value, + "Use addstr() instead of addtyp()"); + m_parts.push_back(message_t(&type, sizeof(type))); + } + + // Push message part to front + void push(message_t &&message) { m_parts.push_front(std::move(message)); } + + // Push message part to back + void add(message_t &&message) { m_parts.push_back(std::move(message)); } + + // Alias to allow std::back_inserter() + void push_back(message_t &&message) { m_parts.push_back(std::move(message)); } + + // Pop string from front + std::string popstr() + { + std::string string(m_parts.front().data(), m_parts.front().size()); + m_parts.pop_front(); + return string; + } + + // Pop type (fixed-size) from front + template T poptyp() + { + static_assert(!std::is_same::value, + "Use popstr() instead of poptyp()"); + if (sizeof(T) != m_parts.front().size()) + throw std::runtime_error( + "Invalid type, size does not match the message size"); + T type = *m_parts.front().data(); + m_parts.pop_front(); + return type; + } + + // Pop message part from front + message_t pop() + { + message_t message = std::move(m_parts.front()); + m_parts.pop_front(); + return message; + } + + // Pop message part from back + message_t remove() + { + message_t message = std::move(m_parts.back()); + m_parts.pop_back(); + return message; + } + + // get message part from front + const message_t &front() { return m_parts.front(); } + + // get message part from back + const message_t &back() { return m_parts.back(); } + + // Get pointer to a specific message part + const message_t *peek(size_t index) const { return &m_parts[index]; } + + // Get a string copy of a specific message part + std::string peekstr(size_t index) const + { + std::string string(m_parts[index].data(), m_parts[index].size()); + return string; + } + + // Peek type (fixed-size) from front + template T peektyp(size_t index) const + { + static_assert(!std::is_same::value, + "Use peekstr() instead of peektyp()"); + if (sizeof(T) != m_parts[index].size()) + throw std::runtime_error( + "Invalid type, size does not match the message size"); + T type = *m_parts[index].data(); + return type; + } + + // Create multipart from type (fixed-size) + template static multipart_t create(const T &type) + { + multipart_t multipart; + multipart.addtyp(type); + return multipart; + } + + // Copy multipart + multipart_t clone() const + { + multipart_t multipart; + for (size_t i = 0; i < size(); i++) + multipart.addmem(m_parts[i].data(), m_parts[i].size()); + return multipart; + } + + // Dump content to string + std::string str() const + { + std::stringstream ss; + for (size_t i = 0; i < m_parts.size(); i++) { + const unsigned char *data = m_parts[i].data(); + size_t size = m_parts[i].size(); + + // Dump the message as text or binary + bool isText = true; + for (size_t j = 0; j < size; j++) { + if (data[j] < 32 || data[j] > 127) { + isText = false; + break; + } + } + ss << "\n[" << std::dec << std::setw(3) << std::setfill('0') << size + << "] "; + if (size >= 1000) { + ss << "... (too big to print)"; + continue; + } + for (size_t j = 0; j < size; j++) { + if (isText) + ss << static_cast(data[j]); + else + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(data[j]); + } + } + return ss.str(); + } + + // Check if equal to other multipart + bool equal(const multipart_t *other) const ZMQ_NOTHROW + { + return *this == *other; + } + + bool operator==(const multipart_t &other) const ZMQ_NOTHROW + { + if (size() != other.size()) + return false; + for (size_t i = 0; i < size(); i++) + if (at(i) != other.at(i)) + return false; + return true; + } + + bool operator!=(const multipart_t &other) const ZMQ_NOTHROW + { + return !(*this == other); + } + +#ifdef ZMQ_CPP11 + + // Return single part message_t encoded from this multipart_t. + message_t encode() const { return zmq::encode(*this); } + + // Decode encoded message into multiple parts and append to self. + void decode_append(const message_t &encoded) + { + zmq::decode(encoded, std::back_inserter(*this)); + } + + // Return a new multipart_t containing the decoded message_t. + static multipart_t decode(const message_t &encoded) + { + multipart_t tmp; + zmq::decode(encoded, std::back_inserter(tmp)); + return tmp; + } + +#endif + + private: + // Disable implicit copying (moving is more efficient) + multipart_t(const multipart_t &other) ZMQ_DELETED_FUNCTION; + void operator=(const multipart_t &other) ZMQ_DELETED_FUNCTION; +}; // class multipart_t + +inline std::ostream &operator<<(std::ostream &os, const multipart_t &msg) +{ + return os << msg.str(); +} + +#endif // ZMQ_HAS_RVALUE_REFS + +#if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) +class active_poller_t +{ + public: + active_poller_t() = default; + ~active_poller_t() = default; + + active_poller_t(const active_poller_t &) = delete; + active_poller_t &operator=(const active_poller_t &) = delete; + + active_poller_t(active_poller_t &&src) = default; + active_poller_t &operator=(active_poller_t &&src) = default; + + using handler_type = std::function; + + void add(zmq::socket_ref socket, event_flags events, handler_type handler) + { + if (!handler) + throw std::invalid_argument("null handler in active_poller_t::add"); + auto ret = handlers.emplace( + socket, std::make_shared(std::move(handler))); + if (!ret.second) + throw error_t(EINVAL); // already added + try { + base_poller.add(socket, events, ret.first->second.get()); + need_rebuild = true; + } + catch (...) { + // rollback + handlers.erase(socket); + throw; + } + } + + void remove(zmq::socket_ref socket) + { + base_poller.remove(socket); + handlers.erase(socket); + need_rebuild = true; + } + + void modify(zmq::socket_ref socket, event_flags events) + { + base_poller.modify(socket, events); + } + + size_t wait(std::chrono::milliseconds timeout) + { + if (need_rebuild) { + poller_events.resize(handlers.size()); + poller_handlers.clear(); + poller_handlers.reserve(handlers.size()); + for (const auto &handler : handlers) { + poller_handlers.push_back(handler.second); + } + need_rebuild = false; + } + const auto count = base_poller.wait_all(poller_events, timeout); + std::for_each(poller_events.begin(), + poller_events.begin() + static_cast(count), + [](decltype(base_poller)::event_type &event) { + assert(event.user_data != nullptr); + (*event.user_data)(event.events); + }); + return count; + } + + ZMQ_NODISCARD bool empty() const noexcept { return handlers.empty(); } + + size_t size() const noexcept { return handlers.size(); } + + private: + bool need_rebuild{false}; + + poller_t base_poller{}; + std::unordered_map> handlers{}; + std::vector poller_events{}; + std::vector> poller_handlers{}; +}; // class active_poller_t +#endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) + + +} // namespace zmq + +#endif // __ZMQ_ADDON_HPP_INCLUDED__