commit 6198e1b53cfceb0a5649ef7b3bbfd006bc237ed5 Author: bing Date: Thu Apr 2 23:18:28 2026 +0800 init diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8aa6b1a --- /dev/null +++ b/.clang-format @@ -0,0 +1,47 @@ +--- +# BasedOnStyle: Google +AccessModifierOffset: -1 +ConstructorInitializerIndentWidth: 4 +AlignEscapedNewlinesLeft: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakTemplateDeclarations: true +AlwaysBreakBeforeMultilineStrings: true +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BinPackParameters: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerBinding: true +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 60 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerBindsToType: true +SpacesBeforeTrailingComments: 2 +Cpp11BracedListStyle: true +Standard: Auto +IndentWidth: 2 +TabWidth: 8 +UseTab: Never +BreakBeforeBraces: Attach +IndentFunctionDeclarationAfterType: true +SpacesInParentheses: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 4 +... + diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..863086d --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12 + +ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="3.28.1" + +# Optionally install the cmake for vcpkg +COPY ./reinstall-cmake.sh /tmp/ + +RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ + chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \ + fi \ + && rm -f /tmp/reinstall-cmake.sh + +# [Optional] Uncomment this section to install additional vcpkg ports. +# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " + +# [Optional] Uncomment this section to install additional packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends libssl-dev libpopt-dev clang-format clangd xmlto doxygen diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..bf839a0 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "C++", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/itsmechlark/features/rabbitmq-server:1": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "gcc -v", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.devcontainer/reinstall-cmake.sh b/.devcontainer/reinstall-cmake.sh new file mode 100644 index 0000000..408b81d --- /dev/null +++ b/.devcontainer/reinstall-cmake.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +set -e + +CMAKE_VERSION=${1:-"none"} + +if [ "${CMAKE_VERSION}" = "none" ]; then + echo "No CMake version specified, skipping CMake reinstallation" + exit 0 +fi + +# Cleanup temporary directory and associated files when exiting the script. +cleanup() { + EXIT_CODE=$? + set +e + if [[ -n "${TMP_DIR}" ]]; then + echo "Executing cleanup of tmp files" + rm -Rf "${TMP_DIR}" + fi + exit $EXIT_CODE +} +trap cleanup EXIT + + +echo "Installing CMake..." +apt-get -y purge --auto-remove cmake +mkdir -p /opt/cmake + +architecture=$(dpkg --print-architecture) +case "${architecture}" in + arm64) + ARCH=aarch64 ;; + amd64) + ARCH=x86_64 ;; + *) + echo "Unsupported architecture ${architecture}." + exit 1 + ;; +esac + +CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" +CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt" +TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX) + +echo "${TMP_DIR}" +cd "${TMP_DIR}" + +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O + +sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}" +sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license + +ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake +ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a465161 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Default for those who don't have core.autocrlf set +* text=auto + +# Things that should be treated like text (lines converted on checkout): +*.c text +*.h text +*.py text +*.cmake text +*.md text +# This is for the output of table_test +*.expected text +*.xml + +# Exceptions to the rule: +*.ac text eol=lf +*.am text eol=lf +*.m4 text eol=lf + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ae254d8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,76 @@ +name: CI + +on: + # Run the jobs on either push or a pull_request, but not both. + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - compiler: gcc + config: cmake + - compiler: clang + config: cmake + - compiler: clang + config: asan + - compiler: clang + config: tsan + - compiler: clang + config: format + - compiler: clang + config: scan-build + - compiler: clang + config: framing + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install Prerequisites + shell: bash + run: | + sudo apt update + sudo apt install -y ninja-build libpopt-dev + + - name: Configure Build & Test + shell: bash + env: + CC: ${{ matrix.compiler }} + run: ./travis.sh ${{ matrix.config }} + + build-macox: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install Prerequisites + shell: bash + run: brew install ninja popt + + - name: Configure Build & Test + shell: bash + run: ./travis.sh macos + + build-win32: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Configure Build & Test + shell: bash + run: | + cmake -E make_directory build/ + cmake -S . -B build/ -DBUILD_EXAMPLES=ON -DCMAKE_C_FLAGS=" /W4" + cmake --build build/ --config Debug --target INSTALL + ctest -V ./build/ diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000..221ddf0 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,32 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: [address, undefined, memory] + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'rabbitmq-c' + dry-run: false + language: c + sanitizer: ${{ matrix.sanitizer }} + - name: Run Fuzzers (${{ matrix.sanitizer }}) + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'rabbitmq-c' + dry-run: false + language: c + fuzz-seconds: 300 + sanitizer: ${{ matrix.sanitizer }} + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() && steps.build.outcome == 'success' + with: + name: ${{ matrix.sanitizer }}-artifacts + path: ./out/artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..51e97b7 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,50 @@ +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '18 10 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Install Prerequisites + shell: bash + run: sudo apt install -y ninja-build libpopt-dev + + - name: Build + shell: bash + run: | + mkdir $PWD/build + cmake -S . -B build -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_EXAMPLES=ON \ + -DBUILD_TOOLS=ON + cmake --build build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d43d521 --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +*.la +*.lo +*.o +.deps +.dirstamp +.libs +/aclocal.m4 +/autom4te.cache +/bin* +/build +/compile +/config.guess +/config.h +/config.h.in +/config.h.in~ +/config.log +/config.status +/config.sub +/configure +/cscope.* +/depcomp +/install-sh +/libtool +/ltmain.sh +/missing +/stamp-h1 +/test-suite.log +INSTALL +Makefile +Makefile.in +examples/amqp_bind +examples/amqp_connect_timeout +examples/amqp_consumer +examples/amqp_exchange_declare +examples/amqp_listen +examples/amqp_listenq +examples/amqp_producer +examples/amqp_rpc_sendstring_client +examples/amqp_sendstring +examples/amqp_unbind +examples/amqps_bind +examples/amqps_connect_timeout +examples/amqps_consumer +examples/amqps_exchange_declare +examples/amqps_listen +examples/amqps_listenq +examples/amqps_producer +examples/amqps_sendstring +examples/amqps_unbind +librabbitmq.pc +test-driver +tests/*.log +tests/*.trs +tests/test_hostcheck +tests/test_parse_url +tests/test_status_enum +tests/test_tables +tools/amqp-consume +tools/amqp-declare-queue +tools/amqp-delete-queue +tools/amqp-get +tools/amqp-publish +tools/doc/*.1 +tools/doc/*.7 +tools/doc/man-date.ent +.ycm_extra_conf.py? +.DS_Store + +# Ignore editor swap files +*~ +*.sw? +.#* +\#*# +.venv/ +.cache/ +compile_commands.json +codegen/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0f0c3df --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "travis/run-clang-format"] + path = travis/run-clang-format + url = https://github.com/Sarcasm/run-clang-format.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..bd0070c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Tony Garnock-Jones +The RabbitMQ team diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..72eb6f7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,276 @@ +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +cmake_minimum_required(VERSION 3.22...3.26) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +# Follow all steps below in order to calculate new ABI version when updating the library +# NOTE: THIS IS UNRELATED to the actual project version +# +# 1. If the library source code has changed at all since the last update, then increment revision +# 2. If any interfaces have been added, removed, or changed since the last update, increment current and set revision to 0. +# 3. If any interfaces have been added since the last public release, then increment age. +# 4. If any interfaces have been removed since the last public release, then set age to 0. + +set(RMQ_SOVERSION_CURRENT 11) +set(RMQ_SOVERSION_REVISION 0) +set(RMQ_SOVERSION_AGE 7) + +include(VersionFunctions) +get_library_version(RMQ_VERSION) +compute_soversion(${RMQ_SOVERSION_CURRENT} ${RMQ_SOVERSION_REVISION} ${RMQ_SOVERSION_AGE} RMQ_SOVERSION) + +project(rabbitmq-c + VERSION ${RMQ_VERSION} + DESCRIPTION "C RabbitMQ AMQP client library" + LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS ON) + +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) + +set(default_build_type "Release") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + +include(CheckSymbolExists) +include(CheckLibraryExists) +include(CMakeDependentOption) +include(CMakePushCheckState) +include(GNUInstallDirs) + +# Detect if we need to link against a socket library: +cmake_push_check_state() +if (WIN32) + # Always use WinSock2 on Windows + set(SOCKET_LIBRARIES ws2_32) +else () + # Is it in the default link? + check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" HAVE_GETADDRINFO) + if (NOT (HAVE_GETADDRINFO EQUAL 1)) + SET(CMAKE_REQUIRED_LIBRARIES "socket") + check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" HAVE_GETADDRINFO2) + if (HAVE_GETADDRINFO2 EQUAL 1) + set(SOCKET_LIBRARIES socket) + else () + SET(CMAKE_REQUIRED_LIBRARIES "socket;nsl") + check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" HAVE_GETADDRINFO3) + if (HAVE_GETADDRINFO3 EQUAL 1) + set(SOCKET_LIBRARIES socket nsl) + else () + message(FATAL_ERROR "Cannot find name resolution library (containing symbol getaddrinfo)") + endif () + endif () + endif () + + set(CMAKE_REQUIRED_LIBRARIES ${SOCKET_LIBRARIES}) + check_symbol_exists(socket "sys/types.h;sys/socket.h" HAVE_SOCKET) + if (NOT HAVE_SOCKET EQUAL 1) + set(CMAKE_REQUIRED_LIBRARIES socket ${SOCKET_LIBRARIES}) + check_symbol_exists(socket "sys/types.h;sys/socket.h" HAVE_SOCKET2) + if (HAVE_SOCKET2 EQUAL 1) + set(SOCKET_LIBRARIES socket ${SOCKET_LIBRARIES}) + else () + set(CMAKE_REQUIRED_LIBRARIES socket nsl ${SOCKET_LIBRARIES}) + check_symbol_exists(socket "sys/types.h;sys/socket.h" HAVE_SOCKET3) + if (HAVE_SOCKET3 EQUAL 1) + set(SOCKET_LIBRARIES socket nsl ${SOCKET_LIBRARIES}) + else () + message(FATAL_ERROR "Cannot find socket library (containing symbol socket)") + endif () + endif () + endif () +endif () +cmake_pop_check_state() + +cmake_push_check_state() +set(CMAKE_REQUIRED_LIBRARIES ${SOCKET_LIBRARIES}) +check_symbol_exists(poll poll.h HAVE_POLL) +if (NOT HAVE_POLL) + if (WIN32) + set(HAVE_SELECT 1) + else() + check_symbol_exists(select sys/select.h HAVE_SELECT) + endif() + if (NOT HAVE_SELECT) + message(FATAL_ERROR "rabbitmq-c requires poll() or select() to be available") + endif() +endif() +cmake_pop_check_state() + +check_library_exists(rt clock_gettime "time.h" CLOCK_GETTIME_NEEDS_LIBRT) +check_library_exists(rt posix_spawnp "spawn.h" POSIX_SPAWNP_NEEDS_LIBRT) +if (CLOCK_GETTIME_NEEDS_LIBRT OR POSIX_SPAWNP_NEEDS_LIBRT) + set(LIBRT rt) +endif() + +option(ENABLE_SSL_SUPPORT "Enable SSL support" ON) + +if (ENABLE_SSL_SUPPORT) + # Manually check OpenSSL version because BoringSSL doesn't support version checking via find_package + set(RMQ_OPENSSL_MIN_VERSION 1.1.1) + find_package(OpenSSL REQUIRED) + if(OPENSSL_VERSION) # Will be empty for BoringSSL + if(OPENSSL_VERSION VERSION_LESS RMQ_OPENSSL_MIN_VERSION) + MESSAGE(FATAL_ERROR "Found OpenSSL version ${OPENSSL_VERSION} but ${RMQ_OPENSSL_MIN_VERSION} or later is required") + endif() + endif() + + cmake_push_check_state() + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + cmake_pop_check_state() + + cmake_push_check_state() + set(CMAKE_REQUIRED_LIBRARIES OpenSSL::SSL) + check_symbol_exists(ENGINE_new openssl/engine.h HAS_OPENSSL_ENGINE) + cmake_pop_check_state() + + cmake_dependent_option(ENABLE_SSL_ENGINE_API "Enable support for deprecated OpenSSL ENGINE feature" ON "HAS_OPENSSL_ENGINE" OFF) +endif() + +if(PROJECT_IS_TOP_LEVEL) + include(CTest) +endif() + +option(BUILD_SHARED_LIBS "Build rabbitmq-c as a shared library" ON) +option(BUILD_STATIC_LIBS "Build rabbitmq-c as a static library" ON) +option(INSTALL_STATIC_LIBS "Install rabbitmq-c static library" ON) + +option(BUILD_EXAMPLES "Build Examples" OFF) +option(BUILD_TOOLS "Build Tools (requires POPT Library)" OFF) +cmake_dependent_option(BUILD_TOOLS_DOCS "Build man pages for tools (requires xmlto)" OFF "BUILD_TOOLS" OFF) +option(BUILD_API_DOCS "Build Doxygen API docs" OFF) +option(RUN_SYSTEM_TESTS "Run system tests (i.e. tests requiring an accessible RabbitMQ server instance on localhost)" OFF) +option(BUILD_OSSFUZZ "Build OSSFUZZ" OFF) + +if (NOT BUILD_SHARED_LIBS AND NOT BUILD_STATIC_LIBS) + message(FATAL_ERROR "One or both of BUILD_SHARED_LIBS or BUILD_STATIC_LIBS must be set to ON to build") +endif() + +set(targets_export_name rabbitmq-targets) + +add_subdirectory(librabbitmq) + +if(BUILD_EXAMPLES) + if(NOT BUILD_SHARED_LIBS) + message(FATAL_ERROR "Examples require -DBUILD_SHARED_LIBS=ON") + endif() + add_subdirectory(examples) +endif() + +if(BUILD_TOOLS) + find_package(POPT REQUIRED) + if(BUILD_TOOLS_DOCS) + find_package(XMLTO REQUIRED) + endif() + add_subdirectory(tools) +endif() + +if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING) + if (NOT BUILD_STATIC_LIBS) + message(FATAL_ERROR + "Tests can only be built against static libraries " + "(set BUILD_STATIC_LIBS=ON)") + endif () + add_subdirectory(tests) +endif () + +if(BUILD_OSSFUZZ) + if (NOT BUILD_STATIC_LIBS) + message(FATAL_ERROR "OSS-FUZZ can only be built against static libraries " "(set BUILD_STATIC_LIBS=ON)") + endif () + add_subdirectory(fuzz) +endif () + +if (BUILD_API_DOCS) + find_package(Doxygen REQUIRED) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile @ONLY) + + add_custom_target(docs + COMMAND ${DOXYGEN_EXECUTABLE} + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs + DEPENDS rabbitmq + COMMENT "Generating API documentation" + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in + ) +endif () + +foreach (lib ${SOCKET_LIBRARIES}) + set(libs_private "${libs_private} -l${lib}") +endforeach(lib) +set(libs_private "${libs_private} -l${LIBRT}") +if (ENABLE_SSL_SUPPORT) + set(libs_private "${libs_private} -lssl -lcrypto ${CMAKE_THREAD_LIBS_INIT}") +endif() + +set(prefix ${CMAKE_INSTALL_PREFIX}) +set(exec_prefix "\${prefix}") +cmake_path(APPEND libdir "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}") +cmake_path(APPEND includedir "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + +configure_file(cmake/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/librabbitmq/config.h) +configure_file(librabbitmq.pc.in ${CMAKE_CURRENT_BINARY_DIR}/librabbitmq.pc @ONLY) + +include(CMakePackageConfigHelpers) +set(RMQ_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/rabbitmq-c) +set(project_config "${CMAKE_CURRENT_BINARY_DIR}/rabbitmq-c-config.cmake") +set(version_config "${CMAKE_CURRENT_BINARY_DIR}/rabbitmq-c-config-version.cmake") + +write_basic_package_version_file( + "${version_config}" + VERSION ${RMQ_VERSION} + COMPATIBILITY SameMajorVersion) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/rabbitmq-c-config.cmake.in" + "${project_config}" + INSTALL_DESTINATION "${RMQ_CMAKE_DIR}") + + +if(BUILD_SHARED_LIBS) + list(APPEND INSTALL_TARGETS rabbitmq) +endif() +if(BUILD_STATIC_LIBS AND INSTALL_STATIC_LIBS) + list(APPEND INSTALL_TARGETS rabbitmq-static) +endif() + +export(TARGETS ${INSTALL_TARGETS} + NAMESPACE rabbitmq:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + +install(FILES ${project_config} ${version_config} + DESTINATION ${RMQ_CMAKE_DIR} + COMPONENT rabbitmq-c-development + ) + +install(EXPORT ${targets_export_name} + DESTINATION ${RMQ_CMAKE_DIR} + NAMESPACE rabbitmq:: + COMPONENT rabbitmq-c-development + ) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/librabbitmq.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + +if (BUILD_SHARED_LIBS) + message(STATUS "Building rabbitmq as a shared library - yes") +else () + message(STATUS "Building rabbitmq as a shared library - no") +endif () + +if (BUILD_STATIC_LIBS) + message(STATUS "Building rabbitmq as a static library - yes") +else () + message(STATUS "Building rabbitmq as a static library - no") +endif () diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..20c2e4d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +Contributing to rabbitmq-c +========================== + +Thanks for contributing to rabbitmq-c. I firmly believe that participation helps +make open source software great. With that there are a few things that can be +done to make our interaction a bit smoother. + +Please use the following guidelines when creating an issue or submitting a +pull request + +Creating an issue +----------------- +When submitting an issue its helpful to know the following + - What version of rabbitmq-c are you using? + - What operating system and version are you running on? + - What compiler and version are you running? + - + - If its a build system issue: which build system are you using ( + + +Submitting a pull-request +------------------------- +I love to get code contributions, a few things that can help out: + - Make sure your commits are rebased on the current master branch + - Please collapse your commits down to a couple logical commits + diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..cc3ba21 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,318 @@ +# Change Log +## v0.15.0 - 2024-11-19 +## Fixed +- Warning on MacOS about incompatible function declaration +- Logic when decoding AMQP data after bad data may cause crash (#837) +- Use SSL_get1_peer_certificate when compiling against OpenSSL 3+ + +## Changed +- Updates to various github actions. + +## Added +- Add amqp_publisher_confirm_wait function (#841) +- Add amqp_literal_bytes macro (#844) + +## v0.14.0 - 2024-03-18 +## Fixed +- Fix potential stackoverflow in decoding table and array +- Fix issue with Mach-O version (#758) +- Make dependency on OpenSSL in rabbitmq-c.cmake match what is built (#725) +- Fix pkg-config generation when CMAKE_INSTALL_DIR is absolute (#733) +- Fix issue with amqp_basic_publish blocking in non-blocking mode (#780) +- Fix SSL hostname check (#784) +- Fix bug in amqp-consume documentation #791 + +## Changed +- CMake minimum version is now 3.22 +- OpenSSL minimum version is now 1.1.1 +- Minimum TLS version supported is v1.2 (v1.3 is also supported). +- OpenSSL ENGINE APIs are conditionally enabled based on availability + +## Added +- Add option to read username/password from file in tools (#781) +- Add amqp_ssl_socket_enable_default_verify_paths API to allow loading from default certification paths +- rabbitmq-c can be compiled against BoringSSL (#814) + +## v0.13.0 - 2023-02-05 +## Fixed +- Fixed missing option to not install static library (#665) +- Missing pkgconfig version in v0.12.0 output (#755, #751) +- Correct return value from amqp_ssl_socket_set_key_buffer (#723) + +## Changed +- Remove OpenSSL code no longer needed when used with OpenSSL >= 1.1.0. (Fixed: #715, #737) + +## Added +- Integration with OSS-Fuzz (#736) + +## v0.12.0 - 2023-01-31 +## Changed +- rabbitmq-c now compiles as C99 +- CMake 3.12 is new minimum required version +- CMake -DBUILD_TESTS renamed to -DBUILD_TESTING +- CMake -DBUILD_EXAMPLES now defaults to OFF +- CMake -DBUILD_TOOLS now defaults to OFF +- Unix library version now matches the release version, SONAME remains the same. +- Modernized CMake scripts to better adopt modern standards +- Public headers have moved to rabbitmq-c/ directory +- Dropped support for MSVC older than VS 2010 +- Dropped support for OpenSSL v1.1.0 and older +- Minimum SSL version set to TLSv1.2 +- Updated to RabbitMQ framing to v3.8.19 + +## v0.11.0 - 2021-03-31 +## Added: +- rabbitmq-c-config.cmake is now generated (#611) +- rabbitmq-c can be compiled on Win32 using pthreads (#613) +- Add amqp_set_ssl_engine API to allow setting OpenSSL engine (#618) +- Add amqp_ssl_socket_set_key_engine API to allow setting OpenSSL engine (#618) +- Add support use of password-protected SSL keys (#661) + +## Changed +- Update OpenSSL library initialization to current best practices (#603) +- Updates to OpenSSL to support v3.0.0 (#654, 627) + +## Fixed: +- OpenSSL symbol clash when compiling on Win32 (#583) +- Restore correct non-blocking behavior using OpenSSL v1.1.1 (#586) +- Disable harmless secure CRT compile warnings on Win32 (#588) +- Fix unused parameter warnings on Win32 (#591) +- Fix invalid format in generated pkg-config file (#599) +- Fix invalid AMQP_STATUS_HEARTBEAT_TIMEOUT (#557) +- Fix incorrect port when using --server flag in CLI tools + +## v0.10.0 - 2019-12-01 +## Added: +- amqp_ssl_socket_get_context can be used to get the current OpenSSL CTX* + associated with a connection. + +## Changed: +- openssl: missing OpenSSL config is ignored as an OpenSSL init error (#523) +- AMQP_DEFAULT_MAX_CHANNELS is now set to 2047 to follow current default channel + limit in the RabbitMQ broker. (#513) + +## Fixed: +- add additional input validation to prevent integer overflow when parsing a + frame header. This addresses CVE-2019-18609. + + +## v0.9.0 - 2018-05-08 +### Added: +- amqp-publish: added support for specifying headers via the -H flag +- Add support for specifying timeout for amqp_login calls via + amqp_set_handshake_timeout +- Add support for specifying timeouts in RPC-style AMQP methods via + amqp_set_rpc_timeout +- Add define for `AMQP_DEFAULT_VHOST` +- Support for SSL SNI +- Support for OpenSSL v1.1.0 + +### Changed: +- rabbitmq-c now requires Windows Vista or better +- rabbitmq-c enables TCP keep-alive by default on platforms that support it +- dropped support for compiling rabbitmq-c without threading support +- OpenSSL is no longer un-intialized automatically by default. OpenSSL can be + explicitly initialized by calling amqp_initialize_ssl_library and + uninitialized by calling amqp_uninitialize_ssl_library. + +### Fixed: +- Correct bugs in processing of --url flag in tools (#364). +- Improve documentation on AMQP_SASL_METHOD_EXTERNAL (#349) +- Improve support for compiling under mingw-w64 +- Better support for handing SIGPIPE on Linux over SSL (#401) +- Improve publish performance on Linux by not specifying MSG_MORE on last part + of message. +- Fix connection logic where multiple hostnames won't be tried if connection to + doesn't fail immediately (#430) + +### Removed: +- autotools build system has been removed +- many duplicate amqps_* examples, they did not add a lot of value + + +## v0.8.0 - 2016-04-09 +### Added: +- SSL: peer certificate and hostname validation can now be controlled separately + using `amqp_ssl_socket_set_verify_peer` and + `amqp_ssl_socket_set_verify_hostname`. +- SSL: the desire SSL version range can now be specified using the + `amqp_ssl_socket_set_ssl_versions` function. +- Add flags to SSL examples on controlling hostname verification. + +### Changed: +- SSL: SSLv2, and SSLv3 have been disabled by default. +- SSL: OpenSSL hostname validation has been improved. +- Win32 debug information is built with /Z7 on MSVC to embed debug info instead + of using a .pdb + +### Fixed: +- Connection failure results in hang on Win32 (#297, #346) +- Rabbitmq-c may block when attempting to close an SSL socket (#313) +- amqp_parse_url does not correctly initialize default parameters (#319) +- x509 objects are leaked in verify_hostname (#323) +- TCP_NOPUSH doesn't work under cygwin (#335) + +### Deprecated +- SSL: `amqp_ssl_socket_set_verify` is being replaced by + `amqp_ssl_socket_set_verify_peer` and `amqp_ssl_socket_set_verify_hostname`. + +### Removed: +- OpenVMS build system and related files. +- Unmaintained PolarSSL, CyaSSL, and gnuTLS SSL backends + +## Changes since v0.7.0 (a.k.a., v0.7.1) +- `41fa9df` Autoconf: add missing files in build system +- `ef73c06` Win32: Use WSAEWOULDBLOCK instead of EWOULDBLOCK on Win32 +- `ceca348` CI: use travis-ci container based builds +- `393e2df` Lib: if channel_max is 0 use server's channel_max +- `ff47574` Lib: fix build on OpenBSD +- `8429496...0ac6430` CI: enable CI on Mac OS X in travis-ci + +## Changes since v0.6.0 (a.k.a., v0.7.0) +- `3379812` Tools: Add support for heartbeats +- `d7029db` CI: Add continuous integration on Win32 using Appveyor +- `a5f7ffb` Tests: only link against static libraries +- `a16ad45...9cf7a3b` Lib: add support for EXTERNAL SASL method +- `038a9ed` Lib: fix incorrect parameters to WSAPoll on Win32 +- `a240c69...14ae307` Lib: use non-blocking sockets internally +- `8d1d5cc`, `5498dc6` Lib: simplify timer/timeout logic +- `61fc4e1` Lib: add support for heartbeat checks in blocking send calls +- `f462c0f...3546a70` Lib: Fix warnings on Win32 +- `ba9d8ba...112a54d` Lib: Add support for RabbitMQ auth failure extension +- `fb8e318` Lib: allow calling functions to override client-properties +- `3ef3f5f` examples: replace usleep() with nanosleep() +- `9027a94` Lib: add AMQP_VERSION code +- `9ee1718` Lib: fix res maybe returned uninitialized in amqp_merge_capbilities +- `22a36db` Lib: Fix SSL_connection status check +- `abbefd4` Lib: Fix issues with c89 compatiblity +- `2bc1f9b...816cbfc` Lib: perf improvements when sending small messages by + hinting to the OS message boundaries. +- `be2e6dd...784a0e9` Lib: add select()-based timeout implementation +- `91db548...8d77b4c` CI: add ubsan, asan, and tsan CI builds + +## Changes since v0.5.2 (a.k.a., v0.6.0) +- `e1746f9` Tools: Enable support for SSL in tools. +- `9626dd5` Lib: ABI CHANGE: enable support for auto_delete, internal flags to + amqp_exchange_declare +- `ee54e27`, `656f833` Lib: check for double-close in SSL/TCP socket impl +- `cf2760d` Lib: allocate struct when method has no field. +- `513ad4a` Lib: add support for SANs in OpenSSL socket impl. +- `5348c69` Lib: add functions to get negotiated frame_max and heartbeat parms. + +## Changes since v0.5.1 (a.k.a., v0.5.2) +- `fcdf0f8` Autoconf: check for htonll as declaration in a header file +- `5790ec7` SSL: correctly report hostname verification errors. +- `d60c28c` Build: disable OpenSSL deprecation warnings on OSX +- `072191a` Lib: include platform, version and copyright in AMQP handshake +- `8b448c6` Examples: print message body in amqp[s]_listen[q] examples +- `7188e5d` Tools: Add flag to set prefetch for amqp-consume tool + +## Changes since v0.5.0 (a.k.a., v0.5.1) +### Enhancements: +- `a566929` SSL: Add support for wildcards in hostname verification (Mike + Steinert) +- `a78aa8a` Lib: Use poll(2) instead of select(2) for timeouts on sockets. +- `357bdb3` Lib: support for specifying frame and decoding pool sizes. (Mike + Stitt) +- `8956003` Lib: improve invalid frame detection code. + +### Bug fixes: +- `b852f84` Lib: Add missing amqp_get_server_properties() function. +- `7001e82` Lib: Add missing ssize_t on Win32 (emazv72) +- `c2ce2cb` Lib: Correctly specify WINVER on Win32 when unspecified. +- `fe844e4` CMake: specify -DHAVE_CONFIG_H in examples. +- `932de5f` Lib: correct time computation on Win32 (jestor) +- `3e83192` HPUX: use gethrtime on HP-UX for timers. +- `cb1b44e` HPUX: correct include location of sys/uio.h +- `8ce585d` Lib: incorrect OOM condition when 0-lenth exchange name is received. +- `c7716b8` CMake: correct htonll detection code on platforms defined with a + macro. +- `4dc4eda` Lib: remove unused assignment. +- `45302cf` Lib: remove range-check of channel-ids. + + +## Changes since v0.4.1 (a.k.a., v0.5.0): +### Major changes: +- Add amqp_get_broker_properties() function 5c7c40adc1 +- Remove distro-specific packaging a5749657ee +- Add -x flag to amqp-consume utilty 1d9c5291ff +- Add amqp_basic_nack() public API 9b168776fb +- Add delivery mode constants to amqp.h 5f291ea772 +- Add support for connection.blocked/connection.unblocked methods ccbc24d270 + +### Bug fixes: +- `f8c6cee749` Examples: Destroy amqp_envelope_t in consumer example +- `ac88db56d3` CMake: fix generation of librabbitmq.pc +- `d5b35afa40` CMake: fix missing POPT_INCLUDE_DIRS variable in tools/ +- `5ea6a0945a` build: provide independent locations for x64 libs +- `fac34656c0` Doc: documentation fixes +- `715901d675` Lib: Correct OpenSSL initialization under threaded conditions +- `ce64e57df8` Examples: Handle unexpected frames in amqp_consumer.c +- `bcda3e933d` CMake: Use GnuInstallDirs to generate install dirs +- `27245a4e15` Lib: correctly handle amqp_get_monotonic_timestamp on win32 +- `693662ef5b` Tools: honor --persistent flag in publish utility +- `01d9c3ca60` Doc: improve documentation in amqp_ssl_socket functions +- `02d5c58ae4` autoconf: correct librabbitmq.pc generation +- `1f4e0cc48b` Doc: improve documentation in amqp_tcp_socket functions + +## Changes since v0.4.0: +### Major changes: +- Removed distro-specific packaging d285d01 + +### Bug fixes: +- `a642602` FIX: destroy amqp_envelop_t object in consumer example +- `860dd71` FIX: correct generation of librabbitmq.pc under CMake +- `bdda7ab` FIX: amqp_socket_close() should not be exported from shlib +- `24f4131` FIX: Use correct buf/len vars when re-starting send() + +## Changes since v0.3.0: +### New Features/Enhancements: +- `amqp_login_with_properties()` function to connect to a broker sending a + properties table to the broker 21b124e #101 +- SSL support (Mike Steinert) 473c865 #17 +- `amqp_simple_wait_frame_noblock()` function variant to wait for a frame + with a timeout f8cfc72 #119 +- Allow memory to be released on a per-channel basis with + `amqp_maybe_release_buffers_on_channel()` 4a2d899 #5 +- Support for AMQP heartbeats while blocking in `amqp_simple_wait_frame*()` + and `amqp_basic_publish()` daa0e66 aca5dc1 +- `amqp_socket_open_noblock()` for a non-blocking socket connection + (Bogdan Padalko) 6ad770d +- `amqp_table_clone()` to do a deep-copy of an amqp_table_t 08af83a +- Add option to listen to multiple keys in `amqp_consume` tool (Brian Hammond) e6c256d +- Add contributed OpenVMS build system 448ab68 +- Higher level APIs for consuming messages 33ebeed #8 +- Doxygen-based API documentation. +- Many improvements to error-handling and reporting + +### Bug Fixes: +- `24ffaf8` FIX: autotools was broken when dependency-tracking was disabled +- `38e741b` FIX: CMake XmlTo not found warning +- `906f04f` FIX: htonll redeclared on Win32 v8 +- `8e41603` FIX: SIGPIPE not disabled on OS X/BSD #102 +- `872ea49` FIX: Header issues with amqp.h on Mingw on Win32 (yoniyoni) +- `0f1f75b` FIX: potential memory leak in amqp_new_connection +- `c9f6312` FIX: missing va_end in `amqp_login()`/`amqp_login_with_properties()` +- `7bb64e4` FIX: include amqp_tcp_socket.h in dpkg (Tim Stewart) +- `ba9d1f5` FIX: Report out of buffer space in `amqp_table_encode()` +- `9496e10` FIX: Remove `abort()` on invalid parameter in `amqp_send_frame()` +- `f209420` FIX: Remote `abort()` in `amqp_simple_wait_method()` +- `f027518` FIX: Return error on socket lib init error +- `0ae534a` FIX: Correctly handle 0 return val from `SSL_read()`/`SSL_write()` +- `22e41b8` FIX: Improve error handling in socket functions +- `33c2353` FIX: Set state->socket to NULL after `amqp_socket_close()` +- `c83e728` FIX: Incorrect error code returned +- `1a19879` FIX: redecl of int i in `amqp_tcp_socket_writev()` +- `7477449` FIX: incorrect bit-shift in `amqp_error_string2()` +- `2e37bb3` FIX: correctly handle `amqp_get_sockfd()` in `amqp_simple_wait_frame()` +- `52a459b` FIX: Don't delete state in `amqp_tune_connection()` on error +- `01e38dd` FIX: Correctly handle `mach_timebase_info()` failure +- `34bffb7` FIX: Correctly disable `SIGPIPE` on platforms with `SO_NOSIGPIPE` +- `3866433` FIX: Use correct number of bits in timer precision on MacOSX +- `b6a1dfe` FIX: Squash OpenSSL deprecated warnings on MacOSX (Bogdan Padalko) +- `7a217d5` FIX: Incorrect `assert()` in `wait_frame_inner()` +- `7942af3` FIX: Correctly handle 0-length table in `amqp_table_clone()` +- `157788e` FIX: Correctly handle 0-length strings in `amqp_basic_properties_clone()` +- `4eaf771` FIX: Correctly handle 0-length message body in `amqp_read_message()` +- `59f943b` FIX: Double-free SSL on connection failure +- `7a451a4` FIX: `amqp_open_socket()` not defined diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bff0c05 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2012-2021 Alan Antonuk + +Copyright (c) 2007-2012 VMware, Inc. + +Copyright (c) 2009-2010 VMware, Inc. and Tony Garnock-Jones + +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/README.md b/README.md new file mode 100644 index 0000000..7c02d52 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# RabbitMQ C AMQP client library + +![Build Status](https://github.com/alanxz/rabbitmq-c/actions/workflows/ci.yml/badge.svg) + +[![Coverage Status](https://coveralls.io/repos/github/alanxz/rabbitmq-c/badge.svg?branch=master)](https://coveralls.io/github/alanxz/rabbitmq-c?branch=master) + +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/rabbitmq-c.svg)](https://oss-fuzz-build-logs.storage.googleapis.com/index.html#rabbitmq-c) + +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7001/badge)](https://www.bestpractices.dev/projects/7001) + +## Introduction + +This is a C-language AMQP client library for use with v2.0+ of the +[RabbitMQ](http://www.rabbitmq.com/) broker. + + - + +Announcements regarding the library are periodically made on the +rabbitmq-c-users and cross-posted to rabbitmq-users. + + - + - + +## Latest Stable Version + +The latest stable release of rabbitmq-c can be found at: + + - + +## Documentation + +API documentation for v0.8.0+ can viewed from: + + + +## Getting started + +### Building and installing + +#### Prereqs: +- [CMake v3.22 or better](http://www.cmake.org/) +- A C compiler (GCC 4.4+, clang, and MSVC are test. Other compilers may also + work) +- *Optionally* [OpenSSL](http://www.openssl.org/) v1.1.1+ to enable support for + connecting to RabbitMQ over SSL/TLS +- *Optionally* [POpt](http://freecode.com/projects/popt) to build some handy + command-line tools. +- *Optionally* [XmlTo](https://fedorahosted.org/xmlto/) to build man pages for + the handy command-line tools +- *Optionally* [Doxygen](http://www.stack.nl/~dimitri/doxygen/) to build + developer API documentation. + +After downloading and extracting the source from a tarball to a directory +([see above](#latest-stable-version)), the commands to build rabbitmq-c on most +systems are: + + mkdir build && cd build + cmake .. + cmake --build . [--config Release] + +The --config Release flag should be used in multi-configuration generators e.g., +Visual Studio or XCode. + +It is also possible to point the CMake GUI tool at the CMakeLists.txt in the root of +the source tree and generate build projects or IDE workspace + +Installing the library and optionally specifying a prefix can be done with: + + cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. + cmake --build . [--config Release] --target install + +More information on CMake can be found on its FAQ (http://www.cmake.org/Wiki/CMake_FAQ) + +Other interesting flags that can be passed to CMake: + +* `BUILD_EXAMPLES=ON/OFF` toggles building the examples. OFF by default. +* `BUILD_SHARED_LIBS=ON/OFF` toggles building rabbitmq-c as a shared library. + ON by default. +* `BUILD_STATIC_LIBS=ON/OFF` toggles building rabbitmq-c as a static library. + ON by default. +* `BUILD_TESTING=ON/OFF` toggles building test code. ON by default. +* `BUILD_TOOLS=ON/OFF` toggles building the command line tools. By default + this is ON if the build system can find the POpt header and library. +* `BUILD_TOOLS_DOCS=ON/OFF` toggles building the man pages for the command line + tools. By default this is ON if BUILD_TOOLS is ON and the build system can + find the XmlTo utility. +* `ENABLE_SSL_SUPPORT=ON/OFF` toggles building rabbitmq-c with SSL support. By + default this is ON if the OpenSSL headers and library can be found. +* `BUILD_API_DOCS=ON/OFF` - toggles building the Doxygen API documentation, by + default this is OFF +* `RUN_SYSTEM_TESTS=ON/OFF` toggles building the system tests (i.e. tests requiring + an accessible RabbitMQ server instance on localhost), by default this is OFF + +## Building RabbitMQ - Using vcpkg + +You can download and install RabbitMQ using the [vcpkg](https://github.com/Microsoft/vcpkg) +dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install librabbitmq + +The RabbitMQ port in vcpkg is kept up to date by Microsoft team members and +community contributors. If the version is out of date, +please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +## Running the examples + +Arrange for a RabbitMQ or other AMQP server to be running on +`localhost` at TCP port number 5672. + +In one terminal, run + + ./examples/amqp_listen localhost 5672 amq.direct test + +In another terminal, + + ./examples/amqp_sendstring localhost 5672 amq.direct test "hello world" + +You should see output similar to the following in the listener's +terminal window: + + Delivery 1, exchange amq.direct routingkey test + Content-type: text/plain + ---- + 00000000: 68 65 6C 6C 6F 20 77 6F : 72 6C 64 hello world + 0000000B: + +## Writing applications using `librabbitmq` + +Please see the `examples` directory for short examples of the use of +the `librabbitmq` library. + +### Threading + +You cannot share a socket, an `amqp_connection_state_t`, or a channel +between threads using `librabbitmq`. The `librabbitmq` library is +built with event-driven, single-threaded applications in mind, and +does not yet cater to any of the requirements of `pthread`ed +applications. + +Your applications instead should open an AMQP connection (and an +associated socket, of course) per thread. If your program needs to +access an AMQP connection or any of its channels from more than one +thread, it is entirely responsible for designing and implementing an +appropriate locking scheme. It will generally be much simpler to have +a connection exclusive to each thread that needs AMQP service. + +### License & Copyright + +Portions created by Alan Antonuk are Copyright (c) 2012-2021 +Alan Antonuk. All Rights Reserved. + +Portions created by VMware are Copyright (c) 2007-2012 VMware, Inc. +All Rights Reserved. + +Portions created by Tony Garnock-Jones are Copyright (c) 2009-2010 +VMware, Inc. and Tony Garnock-Jones. All Rights Reserved. + +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. \ No newline at end of file diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..1f378ea --- /dev/null +++ b/THANKS @@ -0,0 +1,8 @@ +Thank-you to the following people for their contributions to the +codebase: + + - Scott Brooks / Epic Advertising + + - Frank Gönninger + + - Daniel Schauenberg diff --git a/cmake/COPYING-CMAKE-SCRIPTS b/cmake/COPYING-CMAKE-SCRIPTS new file mode 100644 index 0000000..53b6b71 --- /dev/null +++ b/cmake/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmake/FindPOPT.cmake b/cmake/FindPOPT.cmake new file mode 100644 index 0000000..e315f72 --- /dev/null +++ b/cmake/FindPOPT.cmake @@ -0,0 +1,48 @@ +# - Try to find the popt options processing library +# The module will set the following variables +# +# POPT_FOUND - System has popt +# POPT_INCLUDE_DIR - The popt include directory +# POPT_LIBRARY - The libraries needed to use popt + +# use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls + +find_package(PkgConfig QUIET) +if (PKG_CONFIG_FOUND) + pkg_search_module(PC_POPT QUIET popt) +endif() + +# Find the include directories +find_path(POPT_INCLUDE_DIR + NAMES popt.h + HINTS + ${PC_POPT_INCLUDEDIR} + ${PC_POPT_INCLUDE_DIRS} + DOC "Path containing the popt.h include file" + ) + +find_library(POPT_LIBRARY + NAMES popt + HINTS + ${PC_POPT_LIBRARYDIR} + ${PC_POPT_LIBRARY_DIRS} + DOC "popt library path" + ) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(POPT + REQUIRED_VARS POPT_INCLUDE_DIR POPT_LIBRARY + VERSION_VAR PC_POPT_VERSION) + +mark_as_advanced(POPT_INCLUDE_DIR POPT_LIBRARY) + +if(POPT_FOUND AND NOT TARGET popt::popt) + add_library(popt::popt UNKNOWN IMPORTED) + set_target_properties(popt::popt PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${POPT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${POPT_INCLUDE_DIR}" + ) +endif() diff --git a/cmake/FindXMLTO.cmake b/cmake/FindXMLTO.cmake new file mode 100644 index 0000000..d2d4d63 --- /dev/null +++ b/cmake/FindXMLTO.cmake @@ -0,0 +1,98 @@ +# - Convert XML docBook files to various formats +# This will convert XML docBook files to various formats like: +# man html txt dvi ps pdf +# macro XMLTO(outfiles infiles... MODES modes...) + +find_program ( XMLTO_EXECUTABLE + NAMES xmlto + DOC "path to the xmlto docbook xslt frontend" +) + + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(XMLTO + REQUIRED_VARS XMLTO_EXECUTABLE) + +mark_as_advanced( XMLTO_EXECUTABLE ) + +macro ( _XMLTO_FILE outfiles mode) + #special settings + set ( XMLTO_FILEEXT_man 1 ) + set ( XMLTO_MODE_html xhtml-nochunks ) + + if ( NOT XMLTO_MODE_${mode}) + set ( XMLTO_MODE_${mode} ${mode} ) + endif ( NOT XMLTO_MODE_${mode} ) + if ( NOT XMLTO_FILEEXT_${mode} ) + set ( XMLTO_FILEEXT_${mode} ${mode} ) + endif ( NOT XMLTO_FILEEXT_${mode} ) + + foreach ( dbFile ${ARGN} ) + #TODO: set XMLTO_FILEEXT_man to value from + if ( "${mode}" STREQUAL "man" ) + file ( READ "${dbFile}" _DB_FILE_CONTENTS ) + string ( REGEX MATCH "[^<]*" XMLTO_FILEEXT_${mode} "${_DB_FILE_CONTENTS}" ) + string ( REGEX REPLACE "^" "" XMLTO_FILEEXT_${mode} "${XMLTO_FILEEXT_${mode}}" ) + string ( REGEX REPLACE "[[:space:]]" "" XMLTO_FILEEXT_${mode} "${XMLTO_FILEEXT_${mode}}" ) + endif ( "${mode}" STREQUAL "man" ) + + get_filename_component ( dbFilePath ${CMAKE_CURRENT_BINARY_DIR}/${dbFile} PATH ) + get_filename_component ( dbFileWE ${dbFile} NAME_WE ) + get_filename_component ( dbFileAbsWE ${dbFilePath}/${dbFileWE} ABSOLUTE ) + + add_custom_command ( + OUTPUT ${dbFileAbsWE}.${XMLTO_FILEEXT_${mode}} + COMMAND ${XMLTO_EXECUTABLE} ${XMLTO_COMMAND_ARGS} -o ${dbFilePath} + ${XMLTO_MODE_${mode}} "${CMAKE_CURRENT_SOURCE_DIR}/${dbFile}" + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${dbFile} + DEPENDS ${XMLTO_DEPENDS} + VERBATIM + ) + + set ( ${outfiles} + ${${outfiles}} + ${dbFileAbsWE}.${XMLTO_FILEEXT_${mode}} + ) + endforeach ( dbFile ) +endmacro ( _XMLTO_FILE outfiles ) + +macro ( XMLTO ) + set ( XMLTO_MODES ) + set ( XMLTO_FILES ) + set ( XMLTO_HAS_MODES false ) + set ( XMLTO_ADD_DEFAULT false ) + foreach ( arg ${ARGN} ) + if ( ${arg} STREQUAL "MODES" ) + set ( XMLTO_HAS_MODES true ) + elseif ( ${arg} STREQUAL "ALL" ) + set ( XMLTO_ADD_DEFAULT true ) + else ( ${arg} STREQUAL "MODES" ) + if ( XMLTO_HAS_MODES ) + set ( XMLTO_MODES ${XMLTO_MODES} ${arg} ) + else ( XMLTO_HAS_MODES ) + set ( XMLTO_FILES ${XMLTO_FILES} ${arg} ) + endif ( XMLTO_HAS_MODES ) + endif ( ${arg} STREQUAL "MODES" ) + endforeach ( arg ${ARGN} ) + if ( NOT XMLTO_MODES ) + set ( XMLTO_MODES html ) + endif ( NOT XMLTO_MODES ) + + foreach ( mode ${XMLTO_MODES} ) + _xmlto_file ( XMLTO_FILES_${mode} ${mode} ${XMLTO_FILES} ) + if ( XMLTO_ADD_DEFAULT ) + add_custom_target ( ${mode} ALL + DEPENDS ${XMLTO_FILES_${mode}} + VERBATIM + ) + else ( XMLTO_ADD_DEFAULT ) + add_custom_target ( ${mode} + DEPENDS ${XMLTO_FILES_${mode}} + VERBATIM + ) + endif ( XMLTO_ADD_DEFAULT ) + endforeach ( mode ) + + set ( XMLTO_MODES ) + set ( XMLTO_FILES ) +endmacro ( XMLTO ) diff --git a/cmake/VersionFunctions.cmake b/cmake/VersionFunctions.cmake new file mode 100644 index 0000000..cedf710 --- /dev/null +++ b/cmake/VersionFunctions.cmake @@ -0,0 +1,20 @@ +function(get_library_version VERSION_ARG) + file(STRINGS include/rabbitmq-c/amqp.h _API_VERSION_MAJOR REGEX "^#define AMQP_VERSION_MAJOR [0-9]+$") + file(STRINGS include/rabbitmq-c/amqp.h _API_VERSION_MINOR REGEX "^#define AMQP_VERSION_MINOR [0-9]+$") + file(STRINGS include/rabbitmq-c/amqp.h _API_VERSION_PATCH REGEX "^#define AMQP_VERSION_PATCH [0-9]+$") + + string(REGEX MATCH "[0-9]+" _API_VERSION_MAJOR ${_API_VERSION_MAJOR}) + string(REGEX MATCH "[0-9]+" _API_VERSION_MINOR ${_API_VERSION_MINOR}) + string(REGEX MATCH "[0-9]+" _API_VERSION_PATCH ${_API_VERSION_PATCH}) + + # VERSION to match what is in autotools + set(${VERSION_ARG} ${_API_VERSION_MAJOR}.${_API_VERSION_MINOR}.${_API_VERSION_PATCH} PARENT_SCOPE) +endfunction() + +function(compute_soversion CURRENT REVISION AGE SOVERSION) + math(EXPR MAJOR "${CURRENT} - ${AGE}") + math(EXPR MINOR "${AGE}") + math(EXPR PATCH "${REVISION}") + + set(${SOVERSION} ${MAJOR} PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/cmake/config.h.in b/cmake/config.h.in new file mode 100644 index 0000000..d90e3b6 --- /dev/null +++ b/cmake/config.h.in @@ -0,0 +1,12 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#cmakedefine HAVE_SELECT + +#cmakedefine HAVE_POLL + +#define AMQ_PLATFORM "@CMAKE_SYSTEM_NAME@" + +#cmakedefine ENABLE_SSL_ENGINE_API + +#endif /* CONFIG_H */ diff --git a/cmake/rabbitmq-c-config.cmake.in b/cmake/rabbitmq-c-config.cmake.in new file mode 100644 index 0000000..6f774ef --- /dev/null +++ b/cmake/rabbitmq-c-config.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +set(RMQ_USES_OPENSSL @ENABLE_SSL_SUPPORT@) + +include(CMakeFindDependencyMacro) + +if (RMQ_USES_OPENSSL) + find_dependency(OpenSSL REQUIRED) + if(OPENSSL_VERSION) + if(OPENSSL_VERSION VERSION_LESS RMQ_OPENSSL_MIN_VERSION) + MESSAGE(FATAL_ERROR "Found OpenSSL version @OPENSSL_VERSION@ but @RMQ_OPENSSL_MIN_VERSION@ or later is required") + endif() + endif() +endif () + +include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) +check_required_components(rabbitmq-c) diff --git a/coverity/model.c b/coverity/model.c new file mode 100644 index 0000000..dc1ad98 --- /dev/null +++ b/coverity/model.c @@ -0,0 +1,17 @@ +/* Functions to help coverity do static analysis on rabbitmq-c */ + +typedef struct { +} amqp_rpc_reply_t; + +/* librabbitmq/amqp_private.h */ +void amqp_abort(const char* fmt, ...) { __coverity_panic__(); } + +/* tools/common.h */ +void die(const char* fmt, ...) { __coverity_panic__(); } +void die_errno(int err, const char* fmt, ...) { __coverity_panic__(); } +void die_amqp_error(int err, const char* fmt, ...) { __coverity_panic__(); } +void die_rpc(amqp_rpc_reply_t r, const char* fmt, ...) { __coverity_panic__(); } + +/* examples/utils.h */ +void die_on_amqp_error(amqp_rpc_reply_t* r) { __coverity_panic__(); } +void die_on_error(int r) { __coverity_panic__(); } diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 0000000..3cb9526 --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,316 @@ +# Doxyfile 1.8.4 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = rabbitmq-c +PROJECT_NUMBER = @VERSION@ +PROJECT_BRIEF = "C AMQP Client library for RabbitMQ" +PROJECT_LOGO = +OUTPUT_DIRECTORY = . +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 2 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = YES +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/README.md \ + @CMAKE_CURRENT_SOURCE_DIR@/ChangeLog.md \ + @CMAKE_CURRENT_SOURCE_DIR@/librabbitmq \ + @CMAKE_CURRENT_SOURCE_DIR@/docs +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h \ + *.md +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = amqp_private.h \ + config.h +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@ \ + @CMAKE_CURRENT_SOURCE_DIR@/examples +EXAMPLE_PATTERNS = *.c \ + *.md +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = README.md +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = amqp_ +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://www.mathjax.org/mathjax +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/librabbitmq \ + @CMAKE_CURRENT_BINARY_DIR@/librabbitmq +INCLUDE_FILE_PATTERNS = +PREDEFINED = AMQP_BEGIN_DECLS= \ + AMQP_END_DECLS= \ + AMQP_EXPORT= \ + AMQP_CALL= \ + AMQP_DEPRECATED(x)=x +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..3592dd4 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +add_library(examples-common OBJECT) +target_sources(examples-common PRIVATE + utils.h + utils.c) +if(WIN32) + target_sources(examples-common PRIVATE win32/platform_utils.c) +else() + target_sources(examples-common PRIVATE unix/platform_utils.c) +endif() +target_link_libraries(examples-common PRIVATE rabbitmq::rabbitmq) + +add_executable(amqp_sendstring amqp_sendstring.c) +target_link_libraries(amqp_sendstring PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_rpc_sendstring_client amqp_rpc_sendstring_client.c) +target_link_libraries(amqp_rpc_sendstring_client PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_exchange_declare amqp_exchange_declare.c) +target_link_libraries(amqp_exchange_declare PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_listen amqp_listen.c) +target_link_libraries(amqp_listen PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_producer amqp_producer.c) +target_link_libraries(amqp_producer PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_confirm_select amqp_confirm_select.c) +target_link_libraries(amqp_confirm_select PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_connect_timeout amqp_connect_timeout.c) +target_link_libraries(amqp_connect_timeout PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_consumer amqp_consumer.c) +target_link_libraries(amqp_consumer PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_unbind amqp_unbind.c) +target_link_libraries(amqp_unbind PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_bind amqp_bind.c) +target_link_libraries(amqp_bind PRIVATE examples-common rabbitmq::rabbitmq) + +add_executable(amqp_listenq amqp_listenq.c) +target_link_libraries(amqp_listenq PRIVATE examples-common rabbitmq::rabbitmq) + +if (ENABLE_SSL_SUPPORT) + add_executable(amqp_ssl_connect amqp_ssl_connect.c) + target_link_libraries(amqp_ssl_connect PRIVATE examples-common rabbitmq::rabbitmq) +endif (ENABLE_SSL_SUPPORT) diff --git a/examples/amqp_bind.c b/examples/amqp_bind.c new file mode 100644 index 0000000..8f05552 --- /dev/null +++ b/examples/amqp_bind.c @@ -0,0 +1,63 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *exchange; + char const *bindingkey; + char const *queue; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 6) { + fprintf(stderr, "Usage: amqp_bind host port exchange bindingkey queue\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = argv[3]; + bindingkey = argv[4]; + queue = argv[5]; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + amqp_queue_bind(conn, 1, amqp_cstring_bytes(queue), + amqp_cstring_bytes(exchange), amqp_cstring_bytes(bindingkey), + amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Unbinding"); + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + return 0; +} diff --git a/examples/amqp_confirm_select.c b/examples/amqp_confirm_select.c new file mode 100644 index 0000000..952a76d --- /dev/null +++ b/examples/amqp_confirm_select.c @@ -0,0 +1,188 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +#if ((defined(_WIN32)) || (defined(__MINGW32__)) || (defined(__MINGW64__))) +#ifndef WINVER +#define WINVER 0x0502 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif + +#define SUMMARY_EVERY_US 5000 + +static void send_batch(amqp_connection_state_t conn, amqp_bytes_t queue_name, + int rate_limit, int message_count) { + uint64_t start_time = now_microseconds(); + int i; + int sent = 0; + int previous_sent = 0; + uint64_t previous_report_time = start_time; + uint64_t next_summary_time = start_time + SUMMARY_EVERY_US; + + char message[256]; + amqp_bytes_t message_bytes; + + for (i = 0; i < (int)sizeof(message); i++) { + message[i] = i & 0xff; + } + + message_bytes.len = sizeof(message); + message_bytes.bytes = message; + + for (i = 0; i < message_count; i++) { + uint64_t now = now_microseconds(); + + die_on_error(amqp_basic_publish(conn, 1, amqp_literal_bytes("amq.direct"), + queue_name, 0, 0, NULL, message_bytes), + "Publishing"); + sent++; + if (now > next_summary_time) { + int countOverInterval = sent - previous_sent; + double intervalRate = + countOverInterval / ((now - previous_report_time) / 1000000.0); + printf("%d ms: Sent %d - %d since last report (%d Hz)\n", + (int)(now - start_time) / 1000, sent, countOverInterval, + (int)intervalRate); + + previous_sent = sent; + previous_report_time = now; + next_summary_time += SUMMARY_EVERY_US; + } + + while (((i * 1000000.0) / (now - start_time)) > rate_limit) { + microsleep(2000); + now = now_microseconds(); + } + } + + { + uint64_t stop_time = now_microseconds(); + int total_delta = (int)(stop_time - start_time); + + printf("PRODUCER - Message count: %d\n", message_count); + printf("Total time, milliseconds: %d\n", total_delta / 1000); + printf("Overall messages-per-second: %g\n", + (message_count / (total_delta / 1000000.0))); + } +} + +#define CONSUME_TIMEOUT_USEC 100 +#define WAITING_TIMEOUT_USEC (30 * 1000) +void wait_for_acks(amqp_connection_state_t conn) { + uint64_t start_time = now_microseconds(); + struct timeval timeout = {0, CONSUME_TIMEOUT_USEC}; + uint64_t now = 0; + amqp_publisher_confirm_t result = {}; + + for (;;) { + amqp_rpc_reply_t ret; + + now = now_microseconds(); + + if (now > start_time + WAITING_TIMEOUT_USEC) { + return; + } + + amqp_maybe_release_buffers(conn); + ret = amqp_publisher_confirm_wait(conn, &timeout, &result); + + if (AMQP_RESPONSE_LIBRARY_EXCEPTION == ret.reply_type) { + if (AMQP_STATUS_UNEXPECTED_STATE == ret.library_error) { + fprintf(stderr, "An unexpected method was received\n"); + return; + } else if (AMQP_STATUS_TIMEOUT == ret.library_error) { + // Timeout means you're done; no publisher confirms were waiting! + return; + } else { + die_on_amqp_error(ret, "Waiting for publisher confirmation"); + } + } + + switch (result.method) { + case AMQP_BASIC_ACK_METHOD: + fprintf(stderr, "Got an ACK!\n"); + fprintf(stderr, "Here's the ACK:\n"); + fprintf(stderr, "\tdelivery_tag: «%" PRIu64 "»\n", + result.payload.ack.delivery_tag); + fprintf(stderr, "\tmultiple: «%d»\n", result.payload.ack.multiple); + break; + case AMQP_BASIC_NACK_METHOD: + fprintf(stderr, "NACK\n"); + break; + case AMQP_BASIC_REJECT_METHOD: + fprintf(stderr, "REJECT\n"); + break; + default: + fprintf(stderr, "Unexpected method «%s» is.\n", + amqp_method_name(result.method)); + }; + } +} + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + int rate_limit; + int message_count; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 5) { + fprintf(stderr, + "Usage: amqp_producer host port rate_limit message_count\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + rate_limit = atoi(argv[3]); + message_count = atoi(argv[4]); + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + // Enable confirm_select + amqp_confirm_select(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Enable confirm-select"); + + send_batch(conn, amqp_literal_bytes("test queue"), rate_limit, message_count); + + wait_for_acks(conn); + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + return 0; +} diff --git a/examples/amqp_connect_timeout.c b/examples/amqp_connect_timeout.c new file mode 100644 index 0000000..a662f1a --- /dev/null +++ b/examples/amqp_connect_timeout.c @@ -0,0 +1,79 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port; + amqp_socket_t *socket; + amqp_connection_state_t conn; + struct timeval tval; + struct timeval *tv; + + if (argc < 3) { + fprintf(stderr, + "Usage: amqp_connect_timeout host port [timeout_sec " + "[timeout_usec=0]]\n"); + return 1; + } + + if (argc > 3) { + tv = &tval; + + tv->tv_sec = atoi(argv[3]); + + if (argc > 4) { + tv->tv_usec = atoi(argv[4]); + } else { + tv->tv_usec = 0; + } + + } else { + tv = NULL; + } + + hostname = argv[1]; + port = atoi(argv[2]); + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + + if (!socket) { + die("creating TCP socket"); + } + + die_on_error(amqp_socket_open_noblock(socket, hostname, port, tv), + "opening TCP socket"); + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + + printf("Done\n"); + return 0; +} diff --git a/examples/amqp_consumer.c b/examples/amqp_consumer.c new file mode 100644 index 0000000..6eee9a9 --- /dev/null +++ b/examples/amqp_consumer.c @@ -0,0 +1,185 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include + +#include "utils.h" + +#define SUMMARY_EVERY_US 1000000 + +static void run(amqp_connection_state_t conn) { + uint64_t start_time = now_microseconds(); + int received = 0; + int previous_received = 0; + uint64_t previous_report_time = start_time; + uint64_t next_summary_time = start_time + SUMMARY_EVERY_US; + + amqp_frame_t frame; + + uint64_t now; + + for (;;) { + amqp_rpc_reply_t ret; + amqp_envelope_t envelope; + + now = now_microseconds(); + if (now > next_summary_time) { + int countOverInterval = received - previous_received; + double intervalRate = + countOverInterval / ((now - previous_report_time) / 1000000.0); + printf("%d ms: Received %d - %d since last report (%d Hz)\n", + (int)(now - start_time) / 1000, received, countOverInterval, + (int)intervalRate); + + previous_received = received; + previous_report_time = now; + next_summary_time += SUMMARY_EVERY_US; + } + + amqp_maybe_release_buffers(conn); + ret = amqp_consume_message(conn, &envelope, NULL, 0); + + if (AMQP_RESPONSE_NORMAL != ret.reply_type) { + if (AMQP_RESPONSE_LIBRARY_EXCEPTION == ret.reply_type && + AMQP_STATUS_UNEXPECTED_STATE == ret.library_error) { + if (AMQP_STATUS_OK != amqp_simple_wait_frame(conn, &frame)) { + return; + } + + if (AMQP_FRAME_METHOD == frame.frame_type) { + switch (frame.payload.method.id) { + case AMQP_BASIC_ACK_METHOD: + /* if we've turned publisher confirms on, and we've published a + * message here is a message being confirmed. + */ + break; + case AMQP_BASIC_RETURN_METHOD: + /* if a published message couldn't be routed and the mandatory + * flag was set this is what would be returned. The message then + * needs to be read. + */ + { + amqp_message_t message; + ret = amqp_read_message(conn, frame.channel, &message, 0); + if (AMQP_RESPONSE_NORMAL != ret.reply_type) { + return; + } + + amqp_destroy_message(&message); + } + + break; + + case AMQP_CHANNEL_CLOSE_METHOD: + /* a channel.close method happens when a channel exception occurs, + * this can happen by publishing to an exchange that doesn't exist + * for example. + * + * In this case you would need to open another channel redeclare + * any queues that were declared auto-delete, and restart any + * consumers that were attached to the previous channel. + */ + return; + + case AMQP_CONNECTION_CLOSE_METHOD: + /* a connection.close method happens when a connection exception + * occurs, this can happen by trying to use a channel that isn't + * open for example. + * + * In this case the whole connection must be restarted. + */ + return; + + default: + fprintf(stderr, "An unexpected method was received %u\n", + frame.payload.method.id); + return; + } + } + } + + } else { + amqp_destroy_envelope(&envelope); + } + + received++; + } +} + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *exchange; + char const *bindingkey; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + amqp_bytes_t queuename; + + if (argc < 3) { + fprintf(stderr, "Usage: amqp_consumer host port\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = "amq.direct"; /* argv[3]; */ + bindingkey = "test queue"; /* argv[4]; */ + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + { + amqp_queue_declare_ok_t *r = amqp_queue_declare( + conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Declaring queue"); + queuename = amqp_bytes_malloc_dup(r->queue); + if (queuename.bytes == NULL) { + fprintf(stderr, "Out of memory while copying queue name"); + return 1; + } + } + + amqp_queue_bind(conn, 1, queuename, amqp_cstring_bytes(exchange), + amqp_cstring_bytes(bindingkey), amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Binding queue"); + + amqp_basic_consume(conn, 1, queuename, amqp_empty_bytes, 0, 1, 0, + amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Consuming"); + + run(conn); + + amqp_bytes_free(queuename); + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + + return 0; +} diff --git a/examples/amqp_exchange_declare.c b/examples/amqp_exchange_declare.c new file mode 100644 index 0000000..4751a7f --- /dev/null +++ b/examples/amqp_exchange_declare.c @@ -0,0 +1,62 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *exchange; + char const *exchangetype; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 5) { + fprintf(stderr, + "Usage: amqp_exchange_declare host port exchange exchangetype\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = argv[3]; + exchangetype = argv[4]; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + amqp_exchange_declare(conn, 1, amqp_cstring_bytes(exchange), + amqp_cstring_bytes(exchangetype), 0, 0, 0, 0, + amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Declaring exchange"); + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + return 0; +} diff --git a/examples/amqp_listen.c b/examples/amqp_listen.c new file mode 100644 index 0000000..b468c80 --- /dev/null +++ b/examples/amqp_listen.c @@ -0,0 +1,113 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *exchange; + char const *bindingkey; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + amqp_bytes_t queuename; + + if (argc < 5) { + fprintf(stderr, "Usage: amqp_listen host port exchange bindingkey\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = argv[3]; + bindingkey = argv[4]; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + { + amqp_queue_declare_ok_t *r = amqp_queue_declare( + conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Declaring queue"); + queuename = amqp_bytes_malloc_dup(r->queue); + if (queuename.bytes == NULL) { + fprintf(stderr, "Out of memory while copying queue name"); + return 1; + } + } + + amqp_queue_bind(conn, 1, queuename, amqp_cstring_bytes(exchange), + amqp_cstring_bytes(bindingkey), amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Binding queue"); + + amqp_basic_consume(conn, 1, queuename, amqp_empty_bytes, 0, 1, 0, + amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Consuming"); + + { + for (;;) { + amqp_rpc_reply_t res; + amqp_envelope_t envelope; + + amqp_maybe_release_buffers(conn); + + res = amqp_consume_message(conn, &envelope, NULL, 0); + + if (AMQP_RESPONSE_NORMAL != res.reply_type) { + break; + } + + printf("Delivery %u, exchange %.*s routingkey %.*s\n", + (unsigned)envelope.delivery_tag, (int)envelope.exchange.len, + (char *)envelope.exchange.bytes, (int)envelope.routing_key.len, + (char *)envelope.routing_key.bytes); + + if (envelope.message.properties._flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { + printf("Content-type: %.*s\n", + (int)envelope.message.properties.content_type.len, + (char *)envelope.message.properties.content_type.bytes); + } + printf("----\n"); + + amqp_dump(envelope.message.body.bytes, envelope.message.body.len); + + amqp_destroy_envelope(&envelope); + } + } + + amqp_bytes_free(queuename); + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + + return 0; +} diff --git a/examples/amqp_listenq.c b/examples/amqp_listenq.c new file mode 100644 index 0000000..6cad892 --- /dev/null +++ b/examples/amqp_listenq.c @@ -0,0 +1,90 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *queuename; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 4) { + fprintf(stderr, "Usage: amqp_listenq host port queuename\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + queuename = argv[3]; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + amqp_basic_consume(conn, 1, amqp_cstring_bytes(queuename), amqp_empty_bytes, + 0, 0, 0, amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Consuming"); + + for (;;) { + amqp_rpc_reply_t res; + amqp_envelope_t envelope; + + amqp_maybe_release_buffers(conn); + + res = amqp_consume_message(conn, &envelope, NULL, 0); + + if (AMQP_RESPONSE_NORMAL != res.reply_type) { + break; + } + + printf("Delivery %u, exchange %.*s routingkey %.*s\n", + (unsigned)envelope.delivery_tag, (int)envelope.exchange.len, + (char *)envelope.exchange.bytes, (int)envelope.routing_key.len, + (char *)envelope.routing_key.bytes); + + if (envelope.message.properties._flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { + printf("Content-type: %.*s\n", + (int)envelope.message.properties.content_type.len, + (char *)envelope.message.properties.content_type.bytes); + } + printf("----\n"); + + amqp_dump(envelope.message.body.bytes, envelope.message.body.len); + + amqp_destroy_envelope(&envelope); + } + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + + return 0; +} diff --git a/examples/amqp_producer.c b/examples/amqp_producer.c new file mode 100644 index 0000000..da41b53 --- /dev/null +++ b/examples/amqp_producer.c @@ -0,0 +1,117 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +#define SUMMARY_EVERY_US 1000000 + +static void send_batch(amqp_connection_state_t conn, amqp_bytes_t queue_name, + int rate_limit, int message_count) { + uint64_t start_time = now_microseconds(); + int i; + int sent = 0; + int previous_sent = 0; + uint64_t previous_report_time = start_time; + uint64_t next_summary_time = start_time + SUMMARY_EVERY_US; + + char message[256]; + amqp_bytes_t message_bytes; + + for (i = 0; i < (int)sizeof(message); i++) { + message[i] = i & 0xff; + } + + message_bytes.len = sizeof(message); + message_bytes.bytes = message; + + for (i = 0; i < message_count; i++) { + uint64_t now = now_microseconds(); + + die_on_error(amqp_basic_publish(conn, 1, amqp_literal_bytes("amq.direct"), + queue_name, 0, 0, NULL, message_bytes), + "Publishing"); + sent++; + if (now > next_summary_time) { + int countOverInterval = sent - previous_sent; + double intervalRate = + countOverInterval / ((now - previous_report_time) / 1000000.0); + printf("%d ms: Sent %d - %d since last report (%d Hz)\n", + (int)(now - start_time) / 1000, sent, countOverInterval, + (int)intervalRate); + + previous_sent = sent; + previous_report_time = now; + next_summary_time += SUMMARY_EVERY_US; + } + + while (((i * 1000000.0) / (now - start_time)) > rate_limit) { + microsleep(2000); + now = now_microseconds(); + } + } + + { + uint64_t stop_time = now_microseconds(); + int total_delta = (int)(stop_time - start_time); + + printf("PRODUCER - Message count: %d\n", message_count); + printf("Total time, milliseconds: %d\n", total_delta / 1000); + printf("Overall messages-per-second: %g\n", + (message_count / (total_delta / 1000000.0))); + } +} + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + int rate_limit; + int message_count; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 5) { + fprintf(stderr, + "Usage: amqp_producer host port rate_limit message_count\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + rate_limit = atoi(argv[3]); + message_count = atoi(argv[4]); + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + send_batch(conn, amqp_literal_bytes("test queue"), rate_limit, message_count); + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + return 0; +} diff --git a/examples/amqp_rpc_sendstring_client.c b/examples/amqp_rpc_sendstring_client.c new file mode 100644 index 0000000..97cfa77 --- /dev/null +++ b/examples/amqp_rpc_sendstring_client.c @@ -0,0 +1,211 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include + +#include "utils.h" + +int main(int argc, char *argv[]) { + char const *hostname; + int port, status; + char const *exchange; + char const *routingkey; + char const *messagebody; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + amqp_bytes_t reply_to_queue; + + if (argc < 6) { /* minimum number of mandatory arguments */ + fprintf(stderr, + "usage:\namqp_rpc_sendstring_client host port exchange routingkey " + "messagebody\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = argv[3]; + routingkey = argv[4]; + messagebody = argv[5]; + + /* + establish a channel that is used to connect RabbitMQ server + */ + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + /* + create private reply_to queue + */ + + { + amqp_queue_declare_ok_t *r = amqp_queue_declare( + conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Declaring queue"); + reply_to_queue = amqp_bytes_malloc_dup(r->queue); + if (reply_to_queue.bytes == NULL) { + fprintf(stderr, "Out of memory while copying queue name"); + return 1; + } + } + + /* + send the message + */ + + { + /* + set properties + */ + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | + AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_REPLY_TO_FLAG | + AMQP_BASIC_CORRELATION_ID_FLAG; + props.content_type = amqp_literal_bytes("text/plain"); + props.delivery_mode = 2; /* persistent delivery mode */ + props.reply_to = amqp_bytes_malloc_dup(reply_to_queue); + if (props.reply_to.bytes == NULL) { + fprintf(stderr, "Out of memory while copying queue name"); + return 1; + } + props.correlation_id = amqp_literal_bytes("1"); + + /* + publish + */ + die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes(exchange), + amqp_cstring_bytes(routingkey), 0, 0, + &props, amqp_cstring_bytes(messagebody)), + "Publishing"); + + amqp_bytes_free(props.reply_to); + } + + /* + wait an answer + */ + + { + amqp_basic_consume(conn, 1, reply_to_queue, amqp_empty_bytes, 0, 1, 0, + amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Consuming"); + amqp_bytes_free(reply_to_queue); + + { + amqp_frame_t frame; + int result; + + amqp_basic_deliver_t *d; + amqp_basic_properties_t *p; + size_t body_target; + size_t body_received; + + for (;;) { + amqp_maybe_release_buffers(conn); + result = amqp_simple_wait_frame(conn, &frame); + printf("Result: %d\n", result); + if (result < 0) { + break; + } + + printf("Frame type: %u channel: %u\n", frame.frame_type, frame.channel); + if (frame.frame_type != AMQP_FRAME_METHOD) { + continue; + } + + printf("Method: %s\n", amqp_method_name(frame.payload.method.id)); + if (frame.payload.method.id != AMQP_BASIC_DELIVER_METHOD) { + continue; + } + + d = (amqp_basic_deliver_t *)frame.payload.method.decoded; + printf("Delivery: %u exchange: %.*s routingkey: %.*s\n", + (unsigned)d->delivery_tag, (int)d->exchange.len, + (char *)d->exchange.bytes, (int)d->routing_key.len, + (char *)d->routing_key.bytes); + + result = amqp_simple_wait_frame(conn, &frame); + if (result < 0) { + break; + } + + if (frame.frame_type != AMQP_FRAME_HEADER) { + fprintf(stderr, "Expected header!"); + abort(); + } + p = (amqp_basic_properties_t *)frame.payload.properties.decoded; + if (p->_flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { + printf("Content-type: %.*s\n", (int)p->content_type.len, + (char *)p->content_type.bytes); + } + printf("----\n"); + + body_target = (size_t)frame.payload.properties.body_size; + body_received = 0; + + while (body_received < body_target) { + result = amqp_simple_wait_frame(conn, &frame); + if (result < 0) { + break; + } + + if (frame.frame_type != AMQP_FRAME_BODY) { + fprintf(stderr, "Expected body!"); + abort(); + } + + body_received += frame.payload.body_fragment.len; + assert(body_received <= body_target); + + amqp_dump(frame.payload.body_fragment.bytes, + frame.payload.body_fragment.len); + } + + if (body_received != body_target) { + /* Can only happen when amqp_simple_wait_frame returns <= 0 */ + /* We break here to close the connection */ + break; + } + + /* everything was fine, we can quit now because we received the reply */ + break; + } + } + } + + /* + closing + */ + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + + return 0; +} diff --git a/examples/amqp_sendstring.c b/examples/amqp_sendstring.c new file mode 100644 index 0000000..bfcaea5 --- /dev/null +++ b/examples/amqp_sendstring.c @@ -0,0 +1,71 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *exchange; + char const *routingkey; + char const *messagebody; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 6) { + fprintf( + stderr, + "Usage: amqp_sendstring host port exchange routingkey messagebody\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = argv[3]; + routingkey = argv[4]; + messagebody = argv[5]; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + { + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG; + props.content_type = amqp_literal_bytes("text/plain"); + props.delivery_mode = 2; /* persistent delivery mode */ + die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes(exchange), + amqp_cstring_bytes(routingkey), 0, 0, + &props, amqp_cstring_bytes(messagebody)), + "Publishing"); + } + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + return 0; +} diff --git a/examples/amqp_ssl_connect.c b/examples/amqp_ssl_connect.c new file mode 100644 index 0000000..2d4d7c3 --- /dev/null +++ b/examples/amqp_ssl_connect.c @@ -0,0 +1,102 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port; + int timeout; + amqp_socket_t *socket; + amqp_connection_state_t conn; + struct timeval tval; + struct timeval *tv; + + if (argc < 3) { + fprintf(stderr, + "Usage: amqp_ssl_connect host port timeout_sec " + "[cacert.pem [engine engine_ID] [verifypeer] [verifyhostname] " + "[key.pem cert.pem]]\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + + timeout = atoi(argv[3]); + if (timeout > 0) { + tv = &tval; + + tv->tv_sec = timeout; + tv->tv_usec = 0; + } else { + tv = NULL; + } + + conn = amqp_new_connection(); + + socket = amqp_ssl_socket_new(conn); + if (!socket) { + die("creating SSL/TLS socket"); + } + + amqp_ssl_socket_set_verify_peer(socket, 0); + amqp_ssl_socket_set_verify_hostname(socket, 0); + + if (argc > 5) { + int nextarg = 5; + die_on_error(amqp_ssl_socket_set_cacert(socket, argv[4]), + "setting CA certificate"); + if (argc > nextarg && !strcmp("engine", argv[nextarg])) { + amqp_set_ssl_engine(argv[++nextarg]); + nextarg++; + } + if (argc > nextarg && !strcmp("verifypeer", argv[nextarg])) { + amqp_ssl_socket_set_verify_peer(socket, 1); + nextarg++; + } + if (argc > nextarg && !strcmp("verifyhostname", argv[nextarg])) { + amqp_ssl_socket_set_verify_hostname(socket, 1); + nextarg++; + } + if (argc > nextarg + 1) { + die_on_error( + amqp_ssl_socket_set_key(socket, argv[nextarg + 1], argv[nextarg]), + "setting client key"); + } + } + + die_on_error(amqp_socket_open_noblock(socket, hostname, port, tv), + "opening SSL/TLS connection"); + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + die_on_error(amqp_uninitialize_ssl_library(), "Uninitializing SSL library"); + + printf("Done\n"); + return 0; +} diff --git a/examples/amqp_unbind.c b/examples/amqp_unbind.c new file mode 100644 index 0000000..78d3d19 --- /dev/null +++ b/examples/amqp_unbind.c @@ -0,0 +1,63 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +int main(int argc, char const *const *argv) { + char const *hostname; + int port, status; + char const *exchange; + char const *bindingkey; + char const *queue; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + if (argc < 6) { + fprintf(stderr, "Usage: amqp_unbind host port exchange bindingkey queue\n"); + return 1; + } + + hostname = argv[1]; + port = atoi(argv[2]); + exchange = argv[3]; + bindingkey = argv[4]; + queue = argv[5]; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket"); + } + + status = amqp_socket_open(socket, hostname, port); + if (status) { + die("opening TCP socket"); + } + + die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"), + "Logging in"); + amqp_channel_open(conn, 1); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); + + amqp_queue_unbind(conn, 1, amqp_cstring_bytes(queue), + amqp_cstring_bytes(exchange), + amqp_cstring_bytes(bindingkey), amqp_empty_table); + die_on_amqp_error(amqp_get_rpc_reply(conn), "Unbinding"); + + die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), + "Closing channel"); + die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "Closing connection"); + die_on_error(amqp_destroy_connection(conn), "Ending connection"); + return 0; +} diff --git a/examples/unix/platform_utils.c b/examples/unix/platform_utils.c new file mode 100644 index 0000000..f9e4960 --- /dev/null +++ b/examples/unix/platform_utils.c @@ -0,0 +1,20 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +uint64_t now_microseconds(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec; +} + +void microsleep(int usec) { + struct timespec req; + req.tv_sec = 0; + req.tv_nsec = 1000 * usec; + nanosleep(&req, NULL); +} diff --git a/examples/utils.c b/examples/utils.c new file mode 100644 index 0000000..1ff8455 --- /dev/null +++ b/examples/utils.c @@ -0,0 +1,156 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "utils.h" + +void die(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +void die_on_error(int x, char const *context) { + if (x < 0) { + fprintf(stderr, "%s: %s\n", context, amqp_error_string2(x)); + exit(1); + } +} + +void die_on_amqp_error(amqp_rpc_reply_t x, char const *context) { + switch (x.reply_type) { + case AMQP_RESPONSE_NORMAL: + return; + + case AMQP_RESPONSE_NONE: + fprintf(stderr, "%s: missing RPC reply type!\n", context); + break; + + case AMQP_RESPONSE_LIBRARY_EXCEPTION: + fprintf(stderr, "%s: %s\n", context, amqp_error_string2(x.library_error)); + break; + + case AMQP_RESPONSE_SERVER_EXCEPTION: + switch (x.reply.id) { + case AMQP_CONNECTION_CLOSE_METHOD: { + amqp_connection_close_t *m = + (amqp_connection_close_t *)x.reply.decoded; + fprintf(stderr, "%s: server connection error %uh, message: %.*s\n", + context, m->reply_code, (int)m->reply_text.len, + (char *)m->reply_text.bytes); + break; + } + case AMQP_CHANNEL_CLOSE_METHOD: { + amqp_channel_close_t *m = (amqp_channel_close_t *)x.reply.decoded; + fprintf(stderr, "%s: server channel error %uh, message: %.*s\n", + context, m->reply_code, (int)m->reply_text.len, + (char *)m->reply_text.bytes); + break; + } + default: + fprintf(stderr, "%s: unknown server error, method id 0x%08X\n", + context, x.reply.id); + break; + } + break; + } + + exit(1); +} + +static void dump_row(long count, int numinrow, int *chs) { + int i; + + printf("%08lX:", count - numinrow); + + if (numinrow > 0) { + for (i = 0; i < numinrow; i++) { + if (i == 8) { + printf(" :"); + } + printf(" %02X", chs[i]); + } + for (i = numinrow; i < 16; i++) { + if (i == 8) { + printf(" :"); + } + printf(" "); + } + printf(" "); + for (i = 0; i < numinrow; i++) { + if (isprint(chs[i])) { + printf("%c", chs[i]); + } else { + printf("."); + } + } + } + printf("\n"); +} + +static int rows_eq(int *a, int *b) { + int i; + + for (i = 0; i < 16; i++) + if (a[i] != b[i]) { + return 0; + } + + return 1; +} + +void amqp_dump(void const *buffer, size_t len) { + unsigned char *buf = (unsigned char *)buffer; + long count = 0; + int numinrow = 0; + int chs[16]; + int oldchs[16] = {0}; + int showed_dots = 0; + size_t i; + + for (i = 0; i < len; i++) { + int ch = buf[i]; + + if (numinrow == 16) { + int j; + + if (rows_eq(oldchs, chs)) { + if (!showed_dots) { + showed_dots = 1; + printf( + " .. .. .. .. .. .. .. .. : .. .. .. .. .. .. .. ..\n"); + } + } else { + showed_dots = 0; + dump_row(count, numinrow, chs); + } + + for (j = 0; j < 16; j++) { + oldchs[j] = chs[j]; + } + + numinrow = 0; + } + + count++; + chs[numinrow++] = ch; + } + + dump_row(count, numinrow, chs); + + if (numinrow != 0) { + printf("%08lX:\n", count); + } +} diff --git a/examples/utils.h b/examples/utils.h new file mode 100644 index 0000000..51a7157 --- /dev/null +++ b/examples/utils.h @@ -0,0 +1,16 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef librabbitmq_examples_utils_h +#define librabbitmq_examples_utils_h + +void die(const char *fmt, ...); +extern void die_on_error(int x, char const *context); +extern void die_on_amqp_error(amqp_rpc_reply_t x, char const *context); + +extern void amqp_dump(void const *buffer, size_t len); + +extern uint64_t now_microseconds(void); +extern void microsleep(int usec); + +#endif diff --git a/examples/win32/platform_utils.c b/examples/win32/platform_utils.c new file mode 100644 index 0000000..59c1956 --- /dev/null +++ b/examples/win32/platform_utils.c @@ -0,0 +1,15 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include + +#include + +uint64_t now_microseconds(void) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + return (((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime) / + 10; +} + +void microsleep(int usec) { Sleep(usec / 1000); } diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 0000000..06d9e78 --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,20 @@ +include_directories( + ${LIBRABBITMQ_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR}/../librabbitmq/ + ${CMAKE_CURRENT_SOURCE_DIR}/../librabbitmq/) + +add_definitions(-DHAVE_CONFIG_H) +add_definitions(-DAMQP_STATIC) + +SET(CMAKE_EXE_LINKER_FLAGS "${LIB_FUZZING_ENGINE}") + +if(BUILD_OSSFUZZ) + add_executable(fuzz_url fuzz_url.c) + target_link_libraries(fuzz_url rabbitmq-static) + + add_executable(fuzz_table fuzz_table.c) + target_link_libraries(fuzz_table rabbitmq-static) + + add_executable(fuzz_server fuzz_server.c) + target_link_libraries(fuzz_server rabbitmq-static) +endif () diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..815fa07 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,27 @@ +### OSS-Fuzz in House + +#### Export Flags +``` +export CC=clang +export CXX=clang++ +export CFLAGS=-fsanitize=fuzzer-no-link,address +export LIB_FUZZING_ENGINE=-fsanitize=fuzzer +export LDFLAGS=-fsanitize=address +``` + +#### Build cmake Fuzzer +``` +cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_OSSFUZZ=ON \ +-DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX \ +-DCMAKE_C_FLAGS=$CFLAGS -DCMAKE_EXE_LINKER_FLAGS=$CFLAGS \ +-DLIB_FUZZING_ENGINE=$LIB_FUZZING_ENGINE \ +../ +``` + +#### Run Fuzzer +``` +mkdir coverage +./fuzz/fuzz_url coverage/ ../fuzz/input/ +./fuzz/fuzz_table coverage/ ../fuzz/input/ +./fuzz/fuzz_server coverage/ ../fuzz/input/ +``` diff --git a/fuzz/fuzz_server.c b/fuzz/fuzz_server.c new file mode 100644 index 0000000..458689e --- /dev/null +++ b/fuzz/fuzz_server.c @@ -0,0 +1,152 @@ +// Copyright 2007 - 2022, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct Fuzzer { + int socket; + uint16_t port; + pthread_t thread; + + uint64_t size; + uint8_t *buffer; +}; +typedef struct Fuzzer Fuzzer; + +#define PORT 5672 +#define kMinInputLength 8 +#define kMaxInputLength 1024 + +void client(Fuzzer *fuzzer); + +void fuzzinit(Fuzzer *fuzzer) { + struct sockaddr_in server_addr; + int res; + fuzzer->socket = socket(AF_INET, SOCK_STREAM, 0); + if (fuzzer->socket == -1) { + fprintf(stderr, "socket failed %s\n", strerror(errno)); + exit(1); + } + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(fuzzer->port); + server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + res = setsockopt(fuzzer->socket, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + if (res) { + fprintf(stderr, "setsockopt failed: %s\n", strerror(errno)); + exit(1); + } + + res = bind(fuzzer->socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); + if (res) { + fprintf(stderr, "bind failed: %s\n", strerror(errno)); + exit(1); + } + res = listen(fuzzer->socket, 1); + if (res) { + fprintf(stderr, "listen failed: %s\n", strerror(errno)); + exit(1); + } +} + +void *Server(void *args) { + Fuzzer *fuzzer = (Fuzzer *)args; + + int client; + int res; + char clientData[10240]; + + client = accept(fuzzer->socket, NULL, NULL); + if (client == -1) { + fprintf(stderr, "accept failed: %s\n", strerror(errno)); + exit(1); + } + + res = recv(client, clientData, sizeof(clientData), 0); + if (res == -1) { + fprintf(stderr, "recv failed: %s\n", strerror(errno)); + exit(1); + } + res = send(client, fuzzer->buffer, fuzzer->size, 0); + if (res == -1) { + fprintf(stderr, "send failed: %s\n", strerror(errno)); + exit(1); + } + + res = shutdown(client, SHUT_RDWR); + close(client); + return NULL; +} + +void clean(Fuzzer *fuzzer) { + shutdown(fuzzer->socket, SHUT_RDWR); + close(fuzzer->socket); + free(fuzzer->buffer); + free(fuzzer); +} + +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + + if (size < kMinInputLength || size > kMaxInputLength) { + return 0; + } + + Fuzzer *fuzzer = (Fuzzer *)malloc(sizeof(Fuzzer)); + fuzzer->port = PORT; + + fuzzer->size = size; + fuzzer->buffer = malloc(fuzzer->size); + memcpy(fuzzer->buffer, data, size); + + fuzzinit(fuzzer); + + pthread_create(&fuzzer->thread, NULL, Server, fuzzer); + + client(fuzzer); + + pthread_join(fuzzer->thread, NULL); + + clean(fuzzer); + + return 0; +} + +void client(Fuzzer *fuzzer) { + char const *hostname; + int status; + amqp_socket_t *socket = NULL; + amqp_connection_state_t conn; + + hostname = "127.0.0.1"; + + conn = amqp_new_connection(); + + socket = amqp_tcp_socket_new(conn); + if (!socket) { + exit(1); + } + + status = amqp_socket_open(socket, hostname, fuzzer->port); + if (status != AMQP_STATUS_OK) { + int sav_errno = errno; + fprintf(stderr, "amqp_socket_open failed: %s\n", amqp_error_string2(status)); + fprintf(stderr, "amqp_socket_open errno: %d: %s\n", sav_errno, strerror(sav_errno)); + exit(1); + } + + amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + + amqp_destroy_connection(conn); +} diff --git a/fuzz/fuzz_table.c b/fuzz/fuzz_table.c new file mode 100644 index 0000000..34a75ea --- /dev/null +++ b/fuzz/fuzz_table.c @@ -0,0 +1,32 @@ +// Copyright 2007 - 2022, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include +#include +#include +#include + +#include + +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + + int unused_result; + amqp_pool_t pool; + + init_amqp_pool(&pool, 4096); + { + amqp_table_t decoded; + size_t decoding_offset = 0; + amqp_bytes_t decoding_bytes; + decoding_bytes.len = size; + decoding_bytes.bytes = (uint8_t *)data; + + unused_result = + amqp_decode_table(decoding_bytes, &pool, &decoded, &decoding_offset); + } + empty_amqp_pool(&pool); + return 0; +} diff --git a/fuzz/fuzz_url.c b/fuzz/fuzz_url.c new file mode 100644 index 0000000..250a3a4 --- /dev/null +++ b/fuzz/fuzz_url.c @@ -0,0 +1,24 @@ +// Copyright 2007 - 2022, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include +#include + +#include + +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + // amqp_parse_url expects null-terminated string that it can modify, + // LLVMFuzzer expects that data will not be modified and won't necessarily + // null terminate the string, so do that here. + char* in = malloc(size + 1); + memcpy(in, data, size); + in[size] = '\0'; + + struct amqp_connection_info ci; + amqp_parse_url(in, &ci); + free(in); + return 0; +} diff --git a/fuzz/input/fuzz_server.raw b/fuzz/input/fuzz_server.raw new file mode 100644 index 0000000..4f2ca96 Binary files /dev/null and b/fuzz/input/fuzz_server.raw differ diff --git a/fuzz/input/fuzz_table.raw b/fuzz/input/fuzz_table.raw new file mode 100644 index 0000000..421d520 Binary files /dev/null and b/fuzz/input/fuzz_table.raw differ diff --git a/fuzz/input/fuzz_url.raw b/fuzz/input/fuzz_url.raw new file mode 100644 index 0000000..34a284a Binary files /dev/null and b/fuzz/input/fuzz_url.raw differ diff --git a/include/amqp.h b/include/amqp.h new file mode 100644 index 0000000..eadf966 --- /dev/null +++ b/include/amqp.h @@ -0,0 +1,15 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_H +#define AMQP_H + +#ifdef _MSC_VER +# pragma message("warning: amqp.h is deprecated, use rabbitmq-c/amqp.h instead.") +#else +# warning "amqp.h is deprecated, use rabbitmq-c/amqp.h instead." +#endif + +#include + +#endif /* AMQP_H */ diff --git a/include/amqp_framing.h b/include/amqp_framing.h new file mode 100644 index 0000000..cf914d3 --- /dev/null +++ b/include/amqp_framing.h @@ -0,0 +1,16 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/** @file amqp_framing.h */ +#ifndef AMQP_FRAMING_H +#define AMQP_FRAMING_H + +#ifdef _MSC_VER +# pragma message("warning: amqp_framing.h is deprecated, use rabbitmq-c/framing.h instead.") +#else +# warning "amqp_framing.h is deprecated, use rabbitmq-c/framing.h instead." +#endif + +#include + +#endif /* AMQP_FRAMING_H */ diff --git a/include/amqp_ssl_socket.h b/include/amqp_ssl_socket.h new file mode 100644 index 0000000..c42888b --- /dev/null +++ b/include/amqp_ssl_socket.h @@ -0,0 +1,17 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/** \file */ + +#ifndef AMQP_SSL_H +#define AMQP_SSL_H + +#ifdef _MSC_VER +# pragma message("warning: amqp_ssl_socket.h is deprecated, use rabbitmq-c/ssl_socket.h instead.") +#else +# warning "amqp_ssl_socket.h is deprecated, use rabbitmq-c/ssl_socket.h instead." +#endif + +#include + +#endif /* AMQP_SSL_H */ diff --git a/include/amqp_tcp_socket.h b/include/amqp_tcp_socket.h new file mode 100644 index 0000000..0ce7669 --- /dev/null +++ b/include/amqp_tcp_socket.h @@ -0,0 +1,15 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_TCP_SOCKET_H +#define AMQP_TCP_SOCKET_H + +#ifdef _MSC_VER +# pragma message("warning: amqp_tcp_socket.h is deprecated, use rabbitmq-c/tcp_socket.h instead.") +#else +# warning "amqp_tcp_socket.h is deprecated, use rabbitmq-c/tcp_socket.h instead." +#endif + +#include + +#endif /* AMQP_TCP_SOCKET_H */ diff --git a/include/rabbitmq-c/amqp.h b/include/rabbitmq-c/amqp.h new file mode 100644 index 0000000..9c6e0a1 --- /dev/null +++ b/include/rabbitmq-c/amqp.h @@ -0,0 +1,2517 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/** \file */ + +#include + +#ifndef RABBITMQ_C_RABBITMQ_C_H +#define RABBITMQ_C_RABBITMQ_C_H + +/** \cond HIDE_FROM_DOXYGEN */ + +#ifdef __cplusplus +#define AMQP_BEGIN_DECLS extern "C" { +#define AMQP_END_DECLS } +#else +#define AMQP_BEGIN_DECLS +#define AMQP_END_DECLS +#endif + +/* + * \internal + * AMQP_CALL - calling convension (used on Win32) + */ +#ifdef _WIN32 +#define AMQP_CALL __cdecl +#else +#define AMQP_CALL +#endif + +/* Define ssize_t on Win32/64 platforms + See: http://lists.cs.uiuc.edu/pipermail/llvmdev/2010-April/030649.html for + details + */ +#if !defined(_W64) +#if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +#define _W64 __w64 +#else +#define _W64 +#endif +#endif + +#if defined(_MSC_VER) || (defined(__BORLANDC__) && (__BORLANDC__ <= 0x0564)) +#ifdef _WIN64 +typedef __int64 ssize_t; +#else +typedef _W64 int ssize_t; +#endif +#endif + +#if defined(_WIN32) && defined(__MINGW32__) +#include +#endif + +/** \endcond */ + +#include +#include + +struct timeval; + +AMQP_BEGIN_DECLS + +/** + * \def AMQP_VERSION_MAJOR + * + * Major library version number compile-time constant + * + * The major version is incremented when backwards incompatible API changes + * are made. + * + * \sa AMQP_VERSION, AMQP_VERSION_STRING + * + * \since v0.4.0 + */ + +/** + * \def AMQP_VERSION_MINOR + * + * Minor library version number compile-time constant + * + * The minor version is incremented when new APIs are added. Existing APIs + * are left alone. + * + * \sa AMQP_VERSION, AMQP_VERSION_STRING + * + * \since v0.4.0 + */ + +/** + * \def AMQP_VERSION_PATCH + * + * Patch library version number compile-time constant + * + * The patch version is incremented when library code changes, but the API + * is not changed. + * + * \sa AMQP_VERSION, AMQP_VERSION_STRING + * + * \since v0.4.0 + */ + +/** + * \def AMQP_VERSION_IS_RELEASE + * + * Version constant set to 1 for tagged release, 0 otherwise + * + * NOTE: versions that are not tagged releases are not guaranteed to be API/ABI + * compatible with older releases, and may change commit-to-commit. + * + * \sa AMQP_VERSION, AMQP_VERSION_STRING + * + * \since v0.4.0 + */ +/* + * Developer note: when changing these, be sure to update SOVERSION constants + * in CMakeLists.txt and configure.ac + */ + +#define AMQP_VERSION_MAJOR 0 +#define AMQP_VERSION_MINOR 16 +#define AMQP_VERSION_PATCH 0 +#define AMQP_VERSION_IS_RELEASE 0 + +/** + * \def AMQP_VERSION_CODE + * + * Helper macro to geneate a packed version code suitable for + * comparison with AMQP_VERSION. + * + * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, + * AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION + * + * \since v0.6.1 + */ +#define AMQP_VERSION_CODE(major, minor, patch, release) \ + ((major << 24) | (minor << 16) | (patch << 8) | (release)) + +/** + * \def AMQP_VERSION + * + * Packed version number + * + * AMQP_VERSION is a 4-byte unsigned integer with the most significant byte + * set to AMQP_VERSION_MAJOR, the second most significant byte set to + * AMQP_VERSION_MINOR, third most significant byte set to AMQP_VERSION_PATCH, + * and the lowest byte set to AMQP_VERSION_IS_RELEASE. + * + * For example version 2.3.4 which is released version would be encoded as + * 0x02030401 + * + * \sa amqp_version_number() AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, + * AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE, AMQP_VERSION_CODE + * + * \since v0.4.0 + */ +#define AMQP_VERSION \ + AMQP_VERSION_CODE(AMQP_VERSION_MAJOR, AMQP_VERSION_MINOR, \ + AMQP_VERSION_PATCH, AMQP_VERSION_IS_RELEASE) + +/** \cond HIDE_FROM_DOXYGEN */ +#define AMQ_STRINGIFY(s) AMQ_STRINGIFY_HELPER(s) +#define AMQ_STRINGIFY_HELPER(s) #s + +#define AMQ_VERSION_STRING \ + AMQ_STRINGIFY(AMQP_VERSION_MAJOR) \ + "." AMQ_STRINGIFY(AMQP_VERSION_MINOR) "." AMQ_STRINGIFY(AMQP_VERSION_PATCH) +/** \endcond */ + +/** + * \def AMQP_VERSION_STRING + * + * Version string compile-time constant + * + * Non-released versions of the library will have "-pre" appended to the + * version string + * + * \sa amqp_version() + * + * \since v0.4.0 + */ +#if AMQP_VERSION_IS_RELEASE +#define AMQP_VERSION_STRING AMQ_VERSION_STRING +#else +#define AMQP_VERSION_STRING AMQ_VERSION_STRING "-pre" +#endif + +/** + * Returns the rabbitmq-c version as a packed integer. + * + * See \ref AMQP_VERSION + * + * \return packed 32-bit integer representing version of library at runtime + * + * \sa AMQP_VERSION, amqp_version() + * + * \since v0.4.0 + */ +AMQP_EXPORT +uint32_t AMQP_CALL amqp_version_number(void); + +/** + * Returns the rabbitmq-c version as a string. + * + * See \ref AMQP_VERSION_STRING + * + * \return a statically allocated string describing the version of rabbitmq-c. + * + * \sa amqp_version_number(), AMQP_VERSION_STRING, AMQP_VERSION + * + * \since v0.1 + */ +AMQP_EXPORT +char const *AMQP_CALL amqp_version(void); + +/** + * \def AMQP_DEFAULT_FRAME_SIZE + * + * Default frame size (128Kb) + * + * \sa amqp_login(), amqp_login_with_properties() + * + * \since v0.4.0 + */ +#define AMQP_DEFAULT_FRAME_SIZE 131072 + +/** + * \def AMQP_DEFAULT_MAX_CHANNELS + * + * Default maximum number of channels (2047, RabbitMQ default limit of 2048, + * minus 1 for channel 0). RabbitMQ set a default limit of 2048 channels per + * connection in v3.7.5 to prevent broken clients from leaking too many + * channels. + * + * \sa amqp_login(), amqp_login_with_properties() + * + * \since v0.4.0 + */ +#define AMQP_DEFAULT_MAX_CHANNELS 2047 + +/** + * \def AMQP_DEFAULT_HEARTBEAT + * + * Default heartbeat interval (0, heartbeat disabled) + * + * \sa amqp_login(), amqp_login_with_properties() + * + * \since v0.4.0 + */ +#define AMQP_DEFAULT_HEARTBEAT 0 + +/** + * \def AMQP_DEFAULT_VHOST + * + * Default RabbitMQ vhost: "/" + * + * \sa amqp_login(), amqp_login_with_properties() + * + * \since v0.9.0 + */ +#define AMQP_DEFAULT_VHOST "/" + +/** + * boolean type 0 = false, true otherwise + * + * \since v0.1 + */ +typedef int amqp_boolean_t; + +/** + * Method number + * + * \since v0.1 + */ +typedef uint32_t amqp_method_number_t; + +/** + * Bitmask for flags + * + * \since v0.1 + */ +typedef uint32_t amqp_flags_t; + +/** + * Channel type + * + * \since v0.1 + */ +typedef uint16_t amqp_channel_t; + +/** + * Buffer descriptor + * + * \since v0.1 + */ +typedef struct amqp_bytes_t_ { + size_t len; /**< length of the buffer in bytes */ + void *bytes; /**< pointer to the beginning of the buffer */ +} amqp_bytes_t; + +/** + * Decimal data type + * + * \since v0.1 + */ +typedef struct amqp_decimal_t_ { + uint8_t decimals; /**< the location of the decimal point */ + uint32_t value; /**< the value before the decimal point is applied */ +} amqp_decimal_t; + +/** + * AMQP field table + * + * An AMQP field table is a set of key-value pairs. + * A key is a UTF-8 encoded string up to 128 bytes long, and are not null + * terminated. + * A value can be one of several different datatypes. \sa + * amqp_field_value_kind_t + * + * \sa amqp_table_entry_t + * + * \since v0.1 + */ +typedef struct amqp_table_t_ { + int num_entries; /**< length of entries array */ + struct amqp_table_entry_t_ *entries; /**< an array of table entries */ +} amqp_table_t; + +/** + * An AMQP Field Array + * + * A repeated set of field values, all must be of the same type + * + * \since v0.1 + */ +typedef struct amqp_array_t_ { + int num_entries; /**< Number of entries in the table */ + struct amqp_field_value_t_ *entries; /**< linked list of field values */ +} amqp_array_t; + +/* + 0-9 0-9-1 Qpid/Rabbit Type Remarks +--------------------------------------------------------------------------- + t t Boolean + b b Signed 8-bit + B Unsigned 8-bit + U s Signed 16-bit (A1) + u Unsigned 16-bit + I I I Signed 32-bit + i Unsigned 32-bit + L l Signed 64-bit (B) + l Unsigned 64-bit + f f 32-bit float + d d 64-bit float + D D D Decimal + s Short string (A2) + S S S Long string + A Nested Array + T T T Timestamp (u64) + F F F Nested Table + V V V Void + x Byte array + +Remarks: + + A1, A2: Notice how the types **CONFLICT** here. In Qpid and Rabbit, + 's' means a signed 16-bit integer; in 0-9-1, it means a + short string. + + B: Notice how the signednesses **CONFLICT** here. In Qpid and Rabbit, + 'l' means a signed 64-bit integer; in 0-9-1, it means an unsigned + 64-bit integer. + +I'm going with the Qpid/Rabbit types, where there's a conflict, and +the 0-9-1 types otherwise. 0-8 is a subset of 0-9, which is a subset +of the other two, so this will work for both 0-8 and 0-9-1 branches of +the code. +*/ + +/** + * A field table value + * + * \since v0.1 + */ +typedef struct amqp_field_value_t_ { + uint8_t kind; /**< the type of the entry /sa amqp_field_value_kind_t */ + union { + amqp_boolean_t boolean; /**< boolean type AMQP_FIELD_KIND_BOOLEAN */ + int8_t i8; /**< int8_t type AMQP_FIELD_KIND_I8 */ + uint8_t u8; /**< uint8_t type AMQP_FIELD_KIND_U8 */ + int16_t i16; /**< int16_t type AMQP_FIELD_KIND_I16 */ + uint16_t u16; /**< uint16_t type AMQP_FIELD_KIND_U16 */ + int32_t i32; /**< int32_t type AMQP_FIELD_KIND_I32 */ + uint32_t u32; /**< uint32_t type AMQP_FIELD_KIND_U32 */ + int64_t i64; /**< int64_t type AMQP_FIELD_KIND_I64 */ + uint64_t u64; /**< uint64_t type AMQP_FIELD_KIND_U64, + AMQP_FIELD_KIND_TIMESTAMP */ + float f32; /**< float type AMQP_FIELD_KIND_F32 */ + double f64; /**< double type AMQP_FIELD_KIND_F64 */ + amqp_decimal_t decimal; /**< amqp_decimal_t AMQP_FIELD_KIND_DECIMAL */ + amqp_bytes_t bytes; /**< amqp_bytes_t type AMQP_FIELD_KIND_UTF8, + AMQP_FIELD_KIND_BYTES */ + amqp_table_t table; /**< amqp_table_t type AMQP_FIELD_KIND_TABLE */ + amqp_array_t array; /**< amqp_array_t type AMQP_FIELD_KIND_ARRAY */ + } value; /**< a union of the value */ +} amqp_field_value_t; + +/** + * An entry in a field-table + * + * \sa amqp_table_encode(), amqp_table_decode(), amqp_table_clone() + * + * \since v0.1 + */ +typedef struct amqp_table_entry_t_ { + amqp_bytes_t key; /**< the table entry key. Its a null-terminated UTF-8 + * string, with a maximum size of 128 bytes */ + amqp_field_value_t value; /**< the table entry values */ +} amqp_table_entry_t; + +/** + * Field value types + * + * \since v0.1 + */ +typedef enum { + AMQP_FIELD_KIND_BOOLEAN = + 't', /**< boolean type. 0 = false, 1 = true @see amqp_boolean_t */ + AMQP_FIELD_KIND_I8 = 'b', /**< 8-bit signed integer, datatype: int8_t */ + AMQP_FIELD_KIND_U8 = 'B', /**< 8-bit unsigned integer, datatype: uint8_t */ + AMQP_FIELD_KIND_I16 = 's', /**< 16-bit signed integer, datatype: int16_t */ + AMQP_FIELD_KIND_U16 = 'u', /**< 16-bit unsigned integer, datatype: uint16_t */ + AMQP_FIELD_KIND_I32 = 'I', /**< 32-bit signed integer, datatype: int32_t */ + AMQP_FIELD_KIND_U32 = 'i', /**< 32-bit unsigned integer, datatype: uint32_t */ + AMQP_FIELD_KIND_I64 = 'l', /**< 64-bit signed integer, datatype: int64_t */ + AMQP_FIELD_KIND_U64 = 'L', /**< 64-bit unsigned integer, datatype: uint64_t */ + AMQP_FIELD_KIND_F32 = + 'f', /**< single-precision floating point value, datatype: float */ + AMQP_FIELD_KIND_F64 = + 'd', /**< double-precision floating point value, datatype: double */ + AMQP_FIELD_KIND_DECIMAL = + 'D', /**< amqp-decimal value, datatype: amqp_decimal_t */ + AMQP_FIELD_KIND_UTF8 = 'S', /**< UTF-8 null-terminated character string, + datatype: amqp_bytes_t */ + AMQP_FIELD_KIND_ARRAY = 'A', /**< field array (repeated values of another + datatype. datatype: amqp_array_t */ + AMQP_FIELD_KIND_TIMESTAMP = 'T', /**< 64-bit timestamp. datatype uint64_t */ + AMQP_FIELD_KIND_TABLE = 'F', /**< field table. encapsulates a table inside a + table entry. datatype: amqp_table_t */ + AMQP_FIELD_KIND_VOID = 'V', /**< empty entry */ + AMQP_FIELD_KIND_BYTES = + 'x' /**< unformatted byte string, datatype: amqp_bytes_t */ +} amqp_field_value_kind_t; + +/** + * A list of allocation blocks + * + * \since v0.1 + */ +typedef struct amqp_pool_blocklist_t_ { + int num_blocks; /**< Number of blocks in the block list */ + void **blocklist; /**< Array of memory blocks */ +} amqp_pool_blocklist_t; + +/** + * A memory pool + * + * \since v0.1 + */ +typedef struct amqp_pool_t_ { + size_t pagesize; /**< the size of the page in bytes. Allocations less than or + * equal to this size are allocated in the pages block list. + * Allocations greater than this are allocated in their own + * own block in the large_blocks block list */ + + amqp_pool_blocklist_t pages; /**< blocks that are the size of pagesize */ + amqp_pool_blocklist_t large_blocks; /**< allocations larger than the pagesize + */ + + int next_page; /**< an index to the next unused page block */ + char *alloc_block; /**< pointer to the current allocation block */ + size_t alloc_used; /**< number of bytes in the current allocation block that + has been used */ +} amqp_pool_t; + +/** + * An amqp method + * + * \since v0.1 + */ +typedef struct amqp_method_t_ { + amqp_method_number_t id; /**< the method id number */ + void *decoded; /**< pointer to the decoded method, + * cast to the appropriate type to use */ +} amqp_method_t; + +/** + * An AMQP frame + * + * \since v0.1 + */ +typedef struct amqp_frame_t_ { + uint8_t frame_type; /**< frame type. The types: + * - AMQP_FRAME_METHOD - use the method union member + * - AMQP_FRAME_HEADER - use the properties union member + * - AMQP_FRAME_BODY - use the body_fragment union member + */ + amqp_channel_t channel; /**< the channel the frame was received on */ + union { + amqp_method_t method; /**< a method, use if frame_type == AMQP_FRAME_METHOD + */ + struct { + uint16_t class_id; /**< the class for the properties */ + uint64_t body_size; /**< size of the body in bytes */ + void *decoded; /**< the decoded properties */ + amqp_bytes_t raw; /**< amqp-encoded properties structure */ + } properties; /**< message header, a.k.a., properties, + use if frame_type == AMQP_FRAME_HEADER */ + amqp_bytes_t body_fragment; /**< a body fragment, use if frame_type == + AMQP_FRAME_BODY */ + struct { + uint8_t transport_high; /**< @internal first byte of handshake */ + uint8_t transport_low; /**< @internal second byte of handshake */ + uint8_t protocol_version_major; /**< @internal third byte of handshake */ + uint8_t protocol_version_minor; /**< @internal fourth byte of handshake */ + } protocol_header; /**< Used only when doing the initial handshake with the + broker, don't use otherwise */ + } payload; /**< the payload of the frame */ +} amqp_frame_t; + +/** + * Response type + * + * \since v0.1 + */ +typedef enum amqp_response_type_enum_ { + AMQP_RESPONSE_NONE = 0, /**< the library got an EOF from the socket */ + AMQP_RESPONSE_NORMAL, /**< response normal, the RPC completed successfully */ + AMQP_RESPONSE_LIBRARY_EXCEPTION, /**< library error, an error occurred in the + library, examine the library_error */ + AMQP_RESPONSE_SERVER_EXCEPTION /**< server exception, the broker returned an + error, check replay */ +} amqp_response_type_enum; + +/** + * Reply from a RPC method on the broker + * + * \since v0.1 + */ +typedef struct amqp_rpc_reply_t_ { + amqp_response_type_enum reply_type; /**< the reply type: + * - AMQP_RESPONSE_NORMAL - the RPC + * completed successfully + * - AMQP_RESPONSE_SERVER_EXCEPTION - the + * broker returned + * an exception, check the reply field + * - AMQP_RESPONSE_LIBRARY_EXCEPTION - the + * library + * encountered an error, check the + * library_error field + */ + amqp_method_t reply; /**< in case of AMQP_RESPONSE_SERVER_EXCEPTION this + * field will be set to the method returned from the + * broker */ + int library_error; /**< in case of AMQP_RESPONSE_LIBRARY_EXCEPTION this + * field will be set to an error code. An error + * string can be retrieved using amqp_error_string */ +} amqp_rpc_reply_t; + +/** + * SASL method type + * + * \since v0.1 + */ +typedef enum amqp_sasl_method_enum_ { + AMQP_SASL_METHOD_UNDEFINED = -1, /**< Invalid SASL method */ + AMQP_SASL_METHOD_PLAIN = + 0, /**< the PLAIN SASL method for authentication to the broker */ + AMQP_SASL_METHOD_EXTERNAL = + 1 /**< the EXTERNAL SASL method for authentication to the broker */ +} amqp_sasl_method_enum; + +/** + * connection state object + * + * \since v0.1 + */ +typedef struct amqp_connection_state_t_ *amqp_connection_state_t; + +/** + * Socket object + * + * \since v0.4.0 + */ +typedef struct amqp_socket_t_ amqp_socket_t; + +/** + * Status codes + * + * \since v0.4.0 + */ +/* NOTE: When updating this enum, update the strings in librabbitmq/amqp_api.c + */ +typedef enum amqp_status_enum_ { + AMQP_STATUS_OK = 0x0, /**< Operation successful */ + AMQP_STATUS_NO_MEMORY = -0x0001, /**< Memory allocation + failed */ + AMQP_STATUS_BAD_AMQP_DATA = -0x0002, /**< Incorrect or corrupt + data was received from + the broker. This is a + protocol error. */ + AMQP_STATUS_UNKNOWN_CLASS = -0x0003, /**< An unknown AMQP class + was received. This is + a protocol error. */ + AMQP_STATUS_UNKNOWN_METHOD = -0x0004, /**< An unknown AMQP method + was received. This is + a protocol error. */ + AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED = -0x0005, /**< Unable to resolve the + * hostname */ + AMQP_STATUS_INCOMPATIBLE_AMQP_VERSION = -0x0006, /**< The broker advertised + an incompaible AMQP + version */ + AMQP_STATUS_CONNECTION_CLOSED = -0x0007, /**< The connection to the + broker has been closed + */ + AMQP_STATUS_BAD_URL = -0x0008, /**< malformed AMQP URL */ + AMQP_STATUS_SOCKET_ERROR = -0x0009, /**< A socket error + occurred */ + AMQP_STATUS_INVALID_PARAMETER = -0x000A, /**< An invalid parameter + was passed into the + function */ + AMQP_STATUS_TABLE_TOO_BIG = -0x000B, /**< The amqp_table_t object + cannot be serialized + because the output + buffer is too small */ + AMQP_STATUS_WRONG_METHOD = -0x000C, /**< The wrong method was + received */ + AMQP_STATUS_TIMEOUT = -0x000D, /**< Operation timed out */ + AMQP_STATUS_TIMER_FAILURE = -0x000E, /**< The underlying system + timer facility failed */ + AMQP_STATUS_HEARTBEAT_TIMEOUT = -0x000F, /**< Timed out waiting for + heartbeat */ + AMQP_STATUS_UNEXPECTED_STATE = -0x0010, /**< Unexpected protocol + state */ + AMQP_STATUS_SOCKET_CLOSED = -0x0011, /**< Underlying socket is + closed */ + AMQP_STATUS_SOCKET_INUSE = -0x0012, /**< Underlying socket is + already open */ + AMQP_STATUS_BROKER_UNSUPPORTED_SASL_METHOD = -0x0013, /**< Broker does not + support the requested + SASL mechanism */ + AMQP_STATUS_UNSUPPORTED = -0x0014, /**< Parameter is unsupported + in this version */ + _AMQP_STATUS_NEXT_VALUE = -0x0015, /**< Internal value */ + + AMQP_STATUS_TCP_ERROR = -0x0100, /**< A generic TCP error + occurred */ + AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR = -0x0101, /**< An error occurred trying + to initialize the + socket library*/ + _AMQP_STATUS_TCP_NEXT_VALUE = -0x0102, /**< Internal value */ + + AMQP_STATUS_SSL_ERROR = -0x0200, /**< A generic SSL error + occurred. */ + AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED = -0x0201, /**< SSL validation of + hostname against + peer certificate + failed */ + AMQP_STATUS_SSL_PEER_VERIFY_FAILED = -0x0202, /**< SSL validation of peer + certificate failed. */ + AMQP_STATUS_SSL_CONNECTION_FAILED = -0x0203, /**< SSL handshake failed. */ + AMQP_STATUS_SSL_SET_ENGINE_FAILED = -0x0204, /**< SSL setting engine failed */ + AMQP_STATUS_SSL_UNIMPLEMENTED = -0x0205, /**< SSL API is not implemented. */ + _AMQP_STATUS_SSL_NEXT_VALUE = -0x0206 /**< Internal value */ +} amqp_status_enum; + +/** + * AMQP delivery modes. + * Use these values for the #amqp_basic_properties_t::delivery_mode field. + * + * \since v0.5 + */ +typedef enum { + AMQP_DELIVERY_NONPERSISTENT = 1, /**< Non-persistent message */ + AMQP_DELIVERY_PERSISTENT = 2 /**< Persistent message */ +} amqp_delivery_mode_enum; + +AMQP_END_DECLS + +#include + +AMQP_BEGIN_DECLS + +/** + * Empty bytes structure + * + * \since v0.2 + */ +AMQP_EXPORT extern const amqp_bytes_t amqp_empty_bytes; + +/** + * Empty table structure + * + * \since v0.2 + */ +AMQP_EXPORT extern const amqp_table_t amqp_empty_table; + +/** + * Empty table array structure + * + * \since v0.2 + */ +AMQP_EXPORT extern const amqp_array_t amqp_empty_array; + +/* Compatibility macros for the above, to avoid the need to update + code written against earlier versions of librabbitmq. */ + +/** + * \def AMQP_EMPTY_BYTES + * + * Deprecated, use \ref amqp_empty_bytes instead + * + * \deprecated use \ref amqp_empty_bytes instead + * + * \since v0.1 + */ +#define AMQP_EMPTY_BYTES amqp_empty_bytes + +/** + * \def AMQP_EMPTY_TABLE + * + * Deprecated, use \ref amqp_empty_table instead + * + * \deprecated use \ref amqp_empty_table instead + * + * \since v0.1 + */ +#define AMQP_EMPTY_TABLE amqp_empty_table + +/** + * \def AMQP_EMPTY_ARRAY + * + * Deprecated, use \ref amqp_empty_array instead + * + * \deprecated use \ref amqp_empty_array instead + * + * \since v0.1 + */ +#define AMQP_EMPTY_ARRAY amqp_empty_array + +/** + * Initializes an amqp_pool_t memory allocation pool for use + * + * Readies an allocation pool for use. An amqp_pool_t + * must be initialized before use + * + * \param [in] pool the amqp_pool_t structure to initialize. + * Calling this function on a pool a pool that has + * already been initialized will result in undefined + * behavior + * \param [in] pagesize the unit size that the pool will allocate + * memory chunks in. Anything allocated against the pool + * with a requested size will be carved out of a block + * this size. Allocations larger than this will be + * allocated individually + * + * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(), + * amqp_pool_alloc_bytes(), amqp_pool_t + * + * \since v0.1 + */ +AMQP_EXPORT +void AMQP_CALL init_amqp_pool(amqp_pool_t *pool, size_t pagesize); + +/** + * Recycles an amqp_pool_t memory allocation pool + * + * Recycles the space allocate by the pool + * + * This invalidates all allocations made against the pool before this call is + * made, any use of any allocations made before recycle_amqp_pool() is called + * will result in undefined behavior. + * + * Note: this may or may not release memory, to force memory to be released + * call empty_amqp_pool(). + * + * \param [in] pool the amqp_pool_t to recycle + * + * \sa recycle_amqp_pool(), empty_amqp_pool(), amqp_pool_alloc(), + * amqp_pool_alloc_bytes() + * + * \since v0.1 + * + */ +AMQP_EXPORT +void AMQP_CALL recycle_amqp_pool(amqp_pool_t *pool); + +/** + * Empties an amqp memory pool + * + * Releases all memory associated with an allocation pool + * + * \param [in] pool the amqp_pool_t to empty + * + * \since v0.1 + */ +AMQP_EXPORT +void AMQP_CALL empty_amqp_pool(amqp_pool_t *pool); + +/** + * Allocates a block of memory from an amqp_pool_t memory pool + * + * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is + * requested, a NULL pointer will be returned. + * + * \param [in] pool the allocation pool to allocate the memory from + * \param [in] amount the size of the allocation in bytes. + * \return a pointer to the memory block, or NULL if the allocation cannot + * be satisfied. + * + * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(), + * amqp_pool_alloc_bytes() + * + * \since v0.1 + */ +AMQP_EXPORT +void *AMQP_CALL amqp_pool_alloc(amqp_pool_t *pool, size_t amount); + +/** + * Allocates a block of memory from an amqp_pool_t to an amqp_bytes_t + * + * Memory will be aligned on a 8-byte boundary. If a 0-length allocation is + * requested, output.bytes = NULL. + * + * \param [in] pool the allocation pool to allocate the memory from + * \param [in] amount the size of the allocation in bytes + * \param [in] output the location to store the pointer. On success + * output.bytes will be set to the beginning of the buffer + * output.len will be set to amount + * On error output.bytes will be set to NULL and output.len + * set to 0 + * + * \sa init_amqp_pool(), recycle_amqp_pool(), empty_amqp_pool(), + * amqp_pool_alloc() + * + * \since v0.1 + */ +AMQP_EXPORT +void AMQP_CALL amqp_pool_alloc_bytes(amqp_pool_t *pool, size_t amount, + amqp_bytes_t *output); + +/** + * Wraps a c string literal in an amqp_bytes_t + * + * Takes a string literal, calculates its length and creates an + * amqp_bytes_t that points to it. The string literal is not duplicated. + * + * For a given input str, The amqp_bytes_t output.bytes is the + * same as str, output.len is the length of the string literal not including + * the \0 terminator + * + * \param [in] str the c string literal to wrap + * \return an amqp_bytes_t that describes the string literal + * + * \since v0.15 + */ +#define amqp_literal_bytes(str) (amqp_bytes_t){sizeof(str) - 1, (void *)str} + +/** + * Wraps a c string in an amqp_bytes_t + * + * Takes a string, calculates its length and creates an + * amqp_bytes_t that points to it. The string is not duplicated. + * + * For a given input cstr, The amqp_bytes_t output.bytes is the + * same as cstr, output.len is the length of the string not including + * the \0 terminator + * + * This function uses strlen() internally so cstr must be properly + * terminated + * + * \param [in] cstr the c string to wrap + * \return an amqp_bytes_t that describes the string + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_bytes_t AMQP_CALL amqp_cstring_bytes(char const *cstr); + +/** + * Wraps a string of bytes in an amqp_bytes_t + * + * Takes a string of bytes and its length and creates an + * amqp_bytes_t that points to it. The input is not duplicated. + * + * \param [in] ptr the string of bytes to wrap + * \param [in] length the length of the string + * \return an amqp_bytes_t that describes the string + * + * \since v0.16 + */ +#define amqp_bytes_from_buffer(ptr, length) (amqp_bytes_t){length, (void *)ptr} + +/** + * Duplicates an amqp_bytes_t buffer. + * + * The buffer is cloned and the contents copied. + * + * The memory associated with the output is allocated + * with amqp_bytes_malloc() and should be freed with + * amqp_bytes_free() + * + * \param [in] src + * \return a clone of the src + * + * \sa amqp_bytes_free(), amqp_bytes_malloc() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_bytes_t AMQP_CALL amqp_bytes_malloc_dup(amqp_bytes_t src); + +/** + * Allocates a amqp_bytes_t buffer + * + * Creates an amqp_bytes_t buffer of the specified amount, the buffer should be + * freed using amqp_bytes_free() + * + * \param [in] amount the size of the buffer in bytes + * \returns an amqp_bytes_t with amount bytes allocated. + * output.bytes will be set to NULL on error + * + * \sa amqp_bytes_free(), amqp_bytes_malloc_dup() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_bytes_t AMQP_CALL amqp_bytes_malloc(size_t amount); + +/** + * Frees an amqp_bytes_t buffer + * + * Frees a buffer allocated with amqp_bytes_malloc() or amqp_bytes_malloc_dup() + * + * Calling amqp_bytes_free on buffers not allocated with one + * of those two functions will result in undefined behavior + * + * \param [in] bytes the buffer to free + * + * \sa amqp_bytes_malloc(), amqp_bytes_malloc_dup() + * + * \since v0.1 + */ +AMQP_EXPORT +void AMQP_CALL amqp_bytes_free(amqp_bytes_t bytes); + +/** + * Allocate and initialize a new amqp_connection_state_t object + * + * amqp_connection_state_t objects created with this function + * should be freed with amqp_destroy_connection() + * + * \returns an opaque pointer on success, NULL or 0 on failure. + * + * \sa amqp_destroy_connection() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_connection_state_t AMQP_CALL amqp_new_connection(void); + +/** + * Get the underlying socket descriptor for the connection + * + * \warning Use the socket returned from this function carefully, incorrect use + * of the socket outside of the library will lead to undefined behavior. + * Additionally rabbitmq-c may use the socket differently version-to-version, + * what may work in one version, may break in the next version. Be sure to + * thoroughly test any applications that use the socket returned by this + * function especially when using a newer version of rabbitmq-c + * + * \param [in] state the connection object + * \returns the socket descriptor if one has been set, -1 otherwise + * + * \sa amqp_tcp_socket_new(), amqp_ssl_socket_new(), amqp_socket_open() + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_get_sockfd(amqp_connection_state_t state); + +/** + * Deprecated, use amqp_tcp_socket_new() or amqp_ssl_socket_new() + * + * \deprecated Use amqp_tcp_socket_new() or amqp_ssl_socket_new() + * + * Sets the socket descriptor associated with the connection. The socket + * should be connected to a broker, and should not be read to or written from + * before calling this function. A socket descriptor can be created and opened + * using amqp_open_socket() + * + * \param [in] state the connection object + * \param [in] sockfd the socket + * + * \sa amqp_open_socket(), amqp_tcp_socket_new(), amqp_ssl_socket_new() + * + * \since v0.1 + */ +AMQP_DEPRECATED_EXPORT void AMQP_CALL + amqp_set_sockfd(amqp_connection_state_t state, int sockfd); + +/** + * Tune client side parameters + * + * \warning This function may call abort() if the connection is in a certain + * state. As such it should probably not be called code outside the library. + * connection parameters should be specified when calling amqp_login() or + * amqp_login_with_properties() + * + * This function changes channel_max, frame_max, and heartbeat parameters, on + * the client side only. It does not try to renegotiate these parameters with + * the broker. Using this function will lead to unexpected results. + * + * \param [in] state the connection object + * \param [in] channel_max the maximum number of channels. + * The largest this can be is 65535 + * \param [in] frame_max the maximum size of an frame. + * The smallest this can be is 4096 + * The largest this can be is 2147483647 + * Unless you know what you're doing the recommended + * size is 131072 or 128KB + * \param [in] heartbeat the number of seconds between heartbeats + * + * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. + * Possible error codes include: + * - AMQP_STATUS_NO_MEMORY memory allocation failed. + * - AMQP_STATUS_TIMER_FAILURE the underlying system timer indicated it + * failed. + * + * \sa amqp_login(), amqp_login_with_properties() + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_tune_connection(amqp_connection_state_t state, + int channel_max, int frame_max, + int heartbeat); + +/** + * Get the maximum number of channels the connection can handle + * + * The maximum number of channels is set when connection negotiation takes + * place in amqp_login() or amqp_login_with_properties(). + * + * \param [in] state the connection object + * \return the maximum number of channels. 0 if there is no limit + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_get_channel_max(amqp_connection_state_t state); + +/** + * Get the maximum size of an frame the connection can handle + * + * The maximum size of an frame is set when connection negotiation takes + * place in amqp_login() or amqp_login_with_properties(). + * + * \param [in] state the connection object + * \return the maximum size of an frame. + * + * \since v0.6 + */ +AMQP_EXPORT +int AMQP_CALL amqp_get_frame_max(amqp_connection_state_t state); + +/** + * Get the number of seconds between heartbeats of the connection + * + * The number of seconds between heartbeats is set when connection + * negotiation takes place in amqp_login() or amqp_login_with_properties(). + * + * \param [in] state the connection object + * \return the number of seconds between heartbeats. + * + * \since v0.6 + */ +AMQP_EXPORT +int AMQP_CALL amqp_get_heartbeat(amqp_connection_state_t state); + +/** + * Destroys an amqp_connection_state_t object + * + * Destroys a amqp_connection_state_t object that was created with + * amqp_new_connection(). If the connection with the broker is open, it will be + * implicitly closed with a reply code of 200 (success). Any memory that + * would be freed with amqp_maybe_release_buffers() or + * amqp_maybe_release_buffers_on_channel() will be freed, and use of that + * memory will caused undefined behavior. + * + * \param [in] state the connection object + * \return AMQP_STATUS_OK on success. amqp_status_enum value failure + * + * \sa amqp_new_connection() + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_destroy_connection(amqp_connection_state_t state); + +/** + * Process incoming data + * + * \warning This is a low-level function intended for those who want to + * have greater control over input and output over the socket from the + * broker. Correctly using this function requires in-depth knowledge of AMQP + * and rabbitmq-c. + * + * For a given buffer of data received from the broker, decode the first + * frame in the buffer. If more than one frame is contained in the input buffer + * the return value will be less than the received_data size, the caller should + * adjust received_data buffer descriptor to point to the beginning of the + * buffer + the return value. + * + * \param [in] state the connection object + * \param [in] received_data a buffer of data received from the broker. The + * function will return the number of bytes of the buffer it used. The + * function copies these bytes to an internal buffer: this part of the buffer + * may be reused after this function successfully completes. + * \param [in,out] decoded_frame caller should pass in a pointer to an + * amqp_frame_t struct. If there is enough data in received_data for a + * complete frame, decoded_frame->frame_type will be set to something OTHER + * than 0. decoded_frame may contain members pointing to memory owned by + * the state object. This memory can be recycled with + * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel(). + * \return number of bytes consumed from received_data or 0 if a 0-length + * buffer was passed. A negative return value indicates failure. Possible + * errors: + * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely + * in an indeterminate state making recovery unlikely. Client should note the + * error and terminate the application + * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection + * should be shutdown immediately + * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the + * broker. This is likely a protocol error and the connection should be + * shutdown immediately + * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class + * was received from the broker. This is likely a protocol error and the + * connection should be shutdown immediately + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_handle_input(amqp_connection_state_t state, + amqp_bytes_t received_data, + amqp_frame_t *decoded_frame); + +/** + * Check to see if connection memory can be released + * + * \deprecated This function is deprecated in favor of + * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel() + * + * Checks the state of an amqp_connection_state_t object to see if + * amqp_release_buffers() can be called successfully. + * + * \param [in] state the connection object + * \returns TRUE if the buffers can be released FALSE otherwise + * + * \sa amqp_release_buffers() amqp_maybe_release_buffers() + * amqp_maybe_release_buffers_on_channel() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_boolean_t AMQP_CALL amqp_release_buffers_ok(amqp_connection_state_t state); + +/** + * Release amqp_connection_state_t owned memory + * + * \deprecated This function is deprecated in favor of + * amqp_maybe_release_buffers() or amqp_maybe_release_buffers_on_channel() + * + * \warning caller should ensure amqp_release_buffers_ok() returns true before + * calling this function. Failure to do so may result in abort() being called. + * + * Release memory owned by the amqp_connection_state_t for reuse by the + * library. Use of any memory returned by the library before this function is + * called will result in undefined behavior. + * + * \note internally rabbitmq-c tries to reuse memory when possible. As a result + * its possible calling this function may not have a noticeable effect on + * memory usage. + * + * \param [in] state the connection object + * + * \sa amqp_release_buffers_ok() amqp_maybe_release_buffers() + * amqp_maybe_release_buffers_on_channel() + * + * \since v0.1 + */ +AMQP_EXPORT +void AMQP_CALL amqp_release_buffers(amqp_connection_state_t state); + +/** + * Release amqp_connection_state_t owned memory + * + * Release memory owned by the amqp_connection_state_t object related to any + * channel, allowing reuse by the library. Use of any memory returned by the + * library before this function is called with result in undefined behavior. + * + * \note internally rabbitmq-c tries to reuse memory when possible. As a result + * its possible calling this function may not have a noticeable effect on + * memory usage. + * + * \param [in] state the connection object + * + * \sa amqp_maybe_release_buffers_on_channel() + * + * \since v0.1 + */ +AMQP_EXPORT +void AMQP_CALL amqp_maybe_release_buffers(amqp_connection_state_t state); + +/** + * Release amqp_connection_state_t owned memory related to a channel + * + * Release memory owned by the amqp_connection_state_t object related to the + * specified channel, allowing reuse by the library. Use of any memory returned + * the library for a specific channel will result in undefined behavior. + * + * \note internally rabbitmq-c tries to reuse memory when possible. As a result + * its possible calling this function may not have a noticeable effect on + * memory usage. + * + * \param [in] state the connection object + * \param [in] channel the channel specifier for which memory should be + * released. Note that the library does not care about the state of the + * channel when calling this function + * + * \sa amqp_maybe_release_buffers() + * + * \since v0.4.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_maybe_release_buffers_on_channel( + amqp_connection_state_t state, amqp_channel_t channel); + +/** + * Send a frame to the broker + * + * \param [in] state the connection object + * \param [in] frame the frame to send to the broker + * \return AMQP_STATUS_OK on success, an amqp_status_enum value on error. + * Possible error codes: + * - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or + * properties was too large to fit in a single AMQP frame, or the + * method contains an invalid value. The frame was not sent. + * - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is + * too large to fit in a single AMQP frame. Frame was not sent. + * - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in + * - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in + * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame + * was sent + * - AMQP_STATUS_SOCKET_ERROR + * - AMQP_STATUS_SSL_ERROR + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_send_frame(amqp_connection_state_t state, + amqp_frame_t const *frame); + +/** + * Compare two table entries + * + * Works just like strcmp(), comparing two the table keys, datatype, then values + * + * \param [in] entry1 the entry on the left + * \param [in] entry2 the entry on the right + * \return 0 if entries are equal, 0 < if left is greater, 0 > if right is + * greater + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_table_entry_cmp(void const *entry1, void const *entry2); + +/** + * Open a socket to a remote host + * + * \deprecated This function is deprecated in favor of amqp_socket_open() + * + * Looks up the hostname, then attempts to open a socket to the host using + * the specified portnumber. It also sets various options on the socket to + * improve performance and correctness. + * + * \param [in] hostname this can be a hostname or IP address. + * Both IPv4 and IPv6 are acceptable + * \param [in] portnumber the port to connect on. RabbitMQ brokers + * listen on port 5672, and 5671 for SSL + * \return a positive value indicates success and is the sockfd. A negative + * value (see amqp_status_enum)is returned on failure. Possible error codes: + * - AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR Initialization of underlying socket + * library failed. + * - AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED hostname lookup failed. + * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. errno or + * WSAGetLastError() may return more useful information. + * + * \note IPv6 support was added in v0.3 + * + * \sa amqp_socket_open() amqp_set_sockfd() + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_open_socket(char const *hostname, int portnumber); + +/** + * Send initial AMQP header to the broker + * + * \warning this is a low level function intended for those who want to + * interact with the broker at a very low level. Use of this function without + * understanding what it does will result in AMQP protocol errors. + * + * This function sends the AMQP protocol header to the broker. + * + * \param [in] state the connection object + * \return AMQP_STATUS_OK on success, a negative value on failure. Possible + * error codes: + * - AMQP_STATUS_CONNECTION_CLOSED the connection to the broker was closed. + * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. It is likely the + * underlying socket has been closed. errno or WSAGetLastError() may provide + * further information. + * - AMQP_STATUS_SSL_ERROR a SSL error occurred. The connection to the broker + * was closed. + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_send_header(amqp_connection_state_t state); + +/** + * Checks to see if there are any incoming frames ready to be read + * + * Checks to see if there are any amqp_frame_t objects buffered by the + * amqp_connection_state_t object. Having one or more frames buffered means + * that amqp_simple_wait_frame() or amqp_simple_wait_frame_noblock() will + * return a frame without potentially blocking on a read() call. + * + * \param [in] state the connection object + * \return TRUE if there are frames enqueued, FALSE otherwise + * + * \sa amqp_simple_wait_frame() amqp_simple_wait_frame_noblock() + * amqp_data_in_buffer() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_boolean_t AMQP_CALL amqp_frames_enqueued(amqp_connection_state_t state); + +/** + * Read a single amqp_frame_t + * + * Waits for the next amqp_frame_t frame to be read from the broker. + * This function has the potential to block for a long time in the case of + * waiting for a basic.deliver method frame from the broker. + * + * The library may buffer frames. When an amqp_connection_state_t object + * has frames buffered calling amqp_simple_wait_frame() will return an + * amqp_frame_t without entering a blocking read(). You can test to see if + * an amqp_connection_state_t object has frames buffered by calling the + * amqp_frames_enqueued() function. + * + * The library has a socket read buffer. When there is data in an + * amqp_connection_state_t read buffer, amqp_simple_wait_frame() may return an + * amqp_frame_t without entering a blocking read(). You can test to see if an + * amqp_connection_state_t object has data in its read buffer by calling the + * amqp_data_in_buffer() function. + * + * \param [in] state the connection object + * \param [out] decoded_frame the frame + * \return AMQP_STATUS_OK on success, an amqp_status_enum value + * is returned otherwise. Possible errors include: + * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely + * in an indeterminate state making recovery unlikely. Client should note the + * error and terminate the application + * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection + * should be shutdown immediately + * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the + * broker. This is likely a protocol error and the connection should be + * shutdown immediately + * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class + * was received from the broker. This is likely a protocol error and the + * connection should be shutdown immediately + * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat + * from the broker. The connection has been closed. + * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. + * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has + * been closed + * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has + * been closed. + * + * \sa amqp_simple_wait_frame_noblock() amqp_frames_enqueued() + * amqp_data_in_buffer() + * + * \note as of v0.4.0 this function will no longer return heartbeat frames + * when enabled by specifying a non-zero heartbeat value in amqp_login(). + * Heartbeating is handled internally by the library. + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_simple_wait_frame(amqp_connection_state_t state, + amqp_frame_t *decoded_frame); + +/** + * Read a single amqp_frame_t with a timeout. + * + * Waits for the next amqp_frame_t frame to be read from the broker, up to + * a timespan specified by tv. The function will return AMQP_STATUS_TIMEOUT + * if the timeout is reached. The tv value is not modified by the function. + * + * If a 0 timeval is specified, the function behaves as if its non-blocking: it + * will test to see if a frame can be read from the broker, and return + * immediately. + * + * If NULL is passed in for tv, the function will behave like + * amqp_simple_wait_frame() and block until a frame is received from the broker + * + * The library may buffer frames. When an amqp_connection_state_t object + * has frames buffered calling amqp_simple_wait_frame_noblock() will return an + * amqp_frame_t without entering a blocking read(). You can test to see if an + * amqp_connection_state_t object has frames buffered by calling the + * amqp_frames_enqueued() function. + * + * The library has a socket read buffer. When there is data in an + * amqp_connection_state_t read buffer, amqp_simple_wait_frame_noblock() may + * return + * an amqp_frame_t without entering a blocking read(). You can test to see if an + * amqp_connection_state_t object has data in its read buffer by calling the + * amqp_data_in_buffer() function. + * + * \note This function does not return heartbeat frames. When enabled, + * heartbeating is handled internally by the library. + * + * \param [in,out] state the connection object + * \param [out] decoded_frame the frame + * \param [in] tv the maximum time to wait for a frame to be read. Setting + * tv->tv_sec = 0 and tv->tv_usec = 0 will do a non-blocking read. Specifying + * NULL for tv will make the function block until a frame is read. + * \return AMQP_STATUS_OK on success. An amqp_status_enum value is returned + * otherwise. Possible errors include: + * - AMQP_STATUS_TIMEOUT the timeout was reached while waiting for a frame + * from the broker. + * - AMQP_STATUS_INVALID_PARAMETER the tv parameter contains an invalid value. + * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely + * in an indeterminate state making recovery unlikely. Client should note the + * error and terminate the application + * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection + * should be shutdown immediately + * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the + * broker. This is likely a protocol error and the connection should be + * shutdown immediately + * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class + * was received from the broker. This is likely a protocol error and the + * connection should be shutdown immediately + * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat + * from the broker. The connection has been closed. + * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. + * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has + * been closed + * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has + * been closed. + * + * \sa amqp_simple_wait_frame() amqp_frames_enqueued() amqp_data_in_buffer() + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_simple_wait_frame_noblock(amqp_connection_state_t state, + amqp_frame_t *decoded_frame, + const struct timeval *tv); + +/** + * Waits for a specific method from the broker + * + * \warning You probably don't want to use this function. If this function + * doesn't receive exactly the frame requested it closes the whole connection. + * + * Waits for a single method on a channel from the broker. + * If a frame is received that does not match expected_channel + * or expected_method the program will abort + * + * \param [in] state the connection object + * \param [in] expected_channel the channel that the method should be delivered + * on + * \param [in] expected_method the method to wait for + * \param [out] output the method + * \returns AMQP_STATUS_OK on success. An amqp_status_enum value is returned + * otherwise. Possible errors include: + * - AMQP_STATUS_WRONG_METHOD a frame containing the wrong method, wrong frame + * type or wrong channel was received. The connection is closed. + * - AMQP_STATUS_NO_MEMORY failure in allocating memory. The library is likely + * in an indeterminate state making recovery unlikely. Client should note the + * error and terminate the application + * - AMQP_STATUS_BAD_AMQP_DATA bad AMQP data was received. The connection + * should be shutdown immediately + * - AMQP_STATUS_UNKNOWN_METHOD: an unknown method was received from the + * broker. This is likely a protocol error and the connection should be + * shutdown immediately + * - AMQP_STATUS_UNKNOWN_CLASS: a properties frame with an unknown class + * was received from the broker. This is likely a protocol error and the + * connection should be shutdown immediately + * - AMQP_STATUS_HEARTBEAT_TIMEOUT timed out while waiting for heartbeat + * from the broker. The connection has been closed. + * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. + * - AMQP_STATUS_SOCKET_ERROR a socket error occurred. The connection has + * been closed + * - AMQP_STATUS_SSL_ERROR a SSL socket error occurred. The connection has + * been closed. + * + * \since v0.1 + */ + +AMQP_EXPORT +int AMQP_CALL amqp_simple_wait_method(amqp_connection_state_t state, + amqp_channel_t expected_channel, + amqp_method_number_t expected_method, + amqp_method_t *output); + +/** + * Sends a method to the broker + * + * This is a thin wrapper around amqp_send_frame(), providing a way to send + * a method to the broker on a specified channel. + * + * \param [in] state the connection object + * \param [in] channel the channel object + * \param [in] id the method number + * \param [in] decoded the method object + * \returns AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. + * Possible errors include: + * - AMQP_STATUS_BAD_AMQP_DATA the serialized form of the method or + * properties was too large to fit in a single AMQP frame, or the + * method contains an invalid value. The frame was not sent. + * - AMQP_STATUS_TABLE_TOO_BIG the serialized form of an amqp_table_t is + * too large to fit in a single AMQP frame. Frame was not sent. + * - AMQP_STATUS_UNKNOWN_METHOD an invalid method type was passed in + * - AMQP_STATUS_UNKNOWN_CLASS an invalid properties type was passed in + * - AMQP_STATUS_TIMER_FAILURE system timer indicated failure. The frame + * was sent + * - AMQP_STATUS_SOCKET_ERROR + * - AMQP_STATUS_SSL_ERROR + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_send_method(amqp_connection_state_t state, + amqp_channel_t channel, amqp_method_number_t id, + void *decoded); + +/** + * Sends a method to the broker and waits for a method response + * + * \param [in] state the connection object + * \param [in] channel the channel object + * \param [in] request_id the method number of the request + * \param [in] expected_reply_ids a 0 terminated array of expected response + * method numbers + * \param [in] decoded_request_method the method to be sent to the broker + * \return a amqp_rpc_reply_t: + * - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully + * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an + * exception: + * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception + * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details + * of the exception. The client should amqp_send_method() a + * amqp_channel_close_ok_t. The channel must be re-opened before it + * can be used again. Any resources associated with the channel + * (auto-delete exchanges, auto-delete queues, consumers) are invalid + * and must be recreated before attempting to use them again. + * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception + * occurred, cast r.reply.decoded to amqp_connection_close_t* to see + * details of the exception. The client amqp_send_method() a + * amqp_connection_close_ok_t and disconnect from the broker. + * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred + * within the library. Examine r.library_error and compare it against + * amqp_status_enum values to determine the error. + * + * \sa amqp_simple_rpc_decoded() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_simple_rpc( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_method_number_t request_id, amqp_method_number_t *expected_reply_ids, + void *decoded_request_method); + +/** + * Sends a method to the broker and waits for a method response + * + * \param [in] state the connection object + * \param [in] channel the channel object + * \param [in] request_id the method number of the request + * \param [in] reply_id the method number expected in response + * \param [in] decoded_request_method the request method + * \return a pointer to the method returned from the broker, or NULL on error. + * On error amqp_get_rpc_reply() will return an amqp_rpc_reply_t with + * details on the error that occurred. + * + * \since v0.1 + */ +AMQP_EXPORT +void *AMQP_CALL amqp_simple_rpc_decoded(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_method_number_t request_id, + amqp_method_number_t reply_id, + void *decoded_request_method); + +/** + * Get the last global amqp_rpc_reply + * + * The API methods corresponding to most synchronous AMQP methods + * return a pointer to the decoded method result. Upon error, they + * return NULL, and we need some way of discovering what, if anything, + * went wrong. amqp_get_rpc_reply() returns the most recent + * amqp_rpc_reply_t instance corresponding to such an API operation + * for the given connection. + * + * Only use it for operations that do not themselves return + * amqp_rpc_reply_t; operations that do return amqp_rpc_reply_t + * generally do NOT update this per-connection-global amqp_rpc_reply_t + * instance. + * + * \param [in] state the connection object + * \return the most recent amqp_rpc_reply_t: + * - r.reply_type == AMQP_RESPONSE_NORMAL. RPC completed successfully + * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an + * exception: + * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception + * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details + * of the exception. The client should amqp_send_method() a + * amqp_channel_close_ok_t. The channel must be re-opened before it + * can be used again. Any resources associated with the channel + * (auto-delete exchanges, auto-delete queues, consumers) are invalid + * and must be recreated before attempting to use them again. + * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception + * occurred, cast r.reply.decoded to amqp_connection_close_t* to see + * details of the exception. The client amqp_send_method() a + * amqp_connection_close_ok_t and disconnect from the broker. + * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. An exception occurred + * within the library. Examine r.library_error and compare it against + * amqp_status_enum values to determine the error. + * + * \sa amqp_simple_rpc_decoded() + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_get_rpc_reply(amqp_connection_state_t state); + +/** + * Login to the broker + * + * After using amqp_open_socket and amqp_set_sockfd, call + * amqp_login to complete connecting to the broker + * + * \param [in] state the connection object + * \param [in] vhost the virtual host to connect to on the broker. The default + * on most brokers is "/" + * \param [in] channel_max the limit for number of channels for the connection. + * 0 means no limit, and is a good default + * (AMQP_DEFAULT_MAX_CHANNELS) + * Note that the maximum number of channels the protocol supports + * is 65535 (2^16, with the 0-channel reserved). The server can + * set a lower channel_max and then the client will use the lowest + * of the two + * \param [in] frame_max the maximum size of an AMQP frame on the wire to + * request of the broker for this connection. 4096 is the minimum + * size, 2^31-1 is the maximum, a good default is 131072 (128KB), + * or AMQP_DEFAULT_FRAME_SIZE + * \param [in] heartbeat the number of seconds between heartbeat frames to + * request of the broker. A value of 0 disables heartbeats. + * Note rabbitmq-c only has partial support for heartbeats, as of + * v0.4.0 they are only serviced during amqp_basic_publish() and + * amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock() + * \param [in] sasl_method the SASL method to authenticate with the broker. + * followed by the authentication information. The following SASL + * methods are implemented: + * - AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument + * should be followed by two arguments in this order: + * const char* username, and const char* password. + * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL + * argument should be followed one argument: + * const char* identity. + * \return amqp_rpc_reply_t indicating success or failure. + * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully + * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors + * from the broker when logging in will be represented by the broker closing + * the socket. In this case r.library_error will be set to + * AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of + * error conditions including: invalid vhost, authentication failure. + * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an + * exception: + * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception + * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details + * of the exception. The client should amqp_send_method() a + * amqp_channel_close_ok_t. The channel must be re-opened before it + * can be used again. Any resources associated with the channel + * (auto-delete exchanges, auto-delete queues, consumers) are invalid + * and must be recreated before attempting to use them again. + * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception + * occurred, cast r.reply.decoded to amqp_connection_close_t* to see + * details of the exception. The client amqp_send_method() a + * amqp_connection_close_ok_t and disconnect from the broker. + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_login(amqp_connection_state_t state, + char const *vhost, int channel_max, + int frame_max, int heartbeat, + amqp_sasl_method_enum sasl_method, ...); + +/** + * Login to the broker passing a properties table + * + * This function is similar to amqp_login() and differs in that it provides a + * way to pass client properties to the broker. This is commonly used to + * negotiate newer protocol features as they are supported by the broker. + * + * \param [in] state the connection object + * \param [in] vhost the virtual host to connect to on the broker. The default + * on most brokers is "/" + * \param [in] channel_max the limit for the number of channels for the + * connection. + * 0 means no limit, and is a good default + * (AMQP_DEFAULT_MAX_CHANNELS) + * Note that the maximum number of channels the protocol supports + * is 65535 (2^16, with the 0-channel reserved). The server can + * set a lower channel_max and then the client will use the lowest + * of the two + * \param [in] frame_max the maximum size of an AMQP frame ont he wire to + * request of the broker for this connection. 4096 is the minimum + * size, 2^31-1 is the maximum, a good default is 131072 (128KB), + * or AMQP_DEFAULT_FRAME_SIZE + * \param [in] heartbeat the number of seconds between heartbeat frame to + * request of the broker. A value of 0 disables heartbeats. + * Note rabbitmq-c only has partial support for hearts, as of + * v0.4.0 heartbeats are only serviced during amqp_basic_publish(), + * and amqp_simple_wait_frame()/amqp_simple_wait_frame_noblock() + * \param [in] properties a table of properties to send the broker. + * \param [in] sasl_method the SASL method to authenticate with the broker + * followed by the authentication information. The following SASL + * methods are implemented: + * - AMQP_SASL_METHOD_PLAIN, the AMQP_SASL_METHOD_PLAIN argument + * should be followed by two arguments in this order: + * const char* username, and const char* password. + * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL + * argument should be followed one argument: + * const char* identity. + * \return amqp_rpc_reply_t indicating success or failure. + * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully + * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors + * from the broker when logging in will be represented by the broker closing + * the socket. In this case r.library_error will be set to + * AMQP_STATUS_CONNECTION_CLOSED. This error can represent a number of + * error conditions including: invalid vhost, authentication failure. + * - r.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. The broker returned an + * exception: + * - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception + * occurred, cast r.reply.decoded to amqp_channel_close_t* to see details + * of the exception. The client should amqp_send_method() a + * amqp_channel_close_ok_t. The channel must be re-opened before it + * can be used again. Any resources associated with the channel + * (auto-delete exchanges, auto-delete queues, consumers) are invalid + * and must be recreated before attempting to use them again. + * - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception + * occurred, cast r.reply.decoded to amqp_connection_close_t* to see + * details of the exception. The client amqp_send_method() a + * amqp_connection_close_ok_t and disconnect from the broker. + * + * \since v0.4.0 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_login_with_properties( + amqp_connection_state_t state, char const *vhost, int channel_max, + int frame_max, int heartbeat, const amqp_table_t *properties, + amqp_sasl_method_enum sasl_method, ...); + +struct amqp_basic_properties_t_; + +/** + * Publish a message to the broker + * + * Publish a message on an exchange with a routing key. + * + * Note that at the AMQ protocol level basic.publish is an async method: + * this means error conditions that occur on the broker (such as publishing to + * a non-existent exchange) will not be reflected in the return value of this + * function. + * + * \param [in] state the connection object + * \param [in] channel the channel identifier + * \param [in] exchange the exchange on the broker to publish to + * \param [in] routing_key the routing key to use when publishing the message + * \param [in] mandatory indicate to the broker that the message MUST be routed + * to a queue. If the broker cannot do this it should respond with + * a basic.return method. + * \param [in] immediate indicate to the broker that the message MUST be + * delivered to a consumer immediately. If the broker cannot do this + * it should respond with a basic.return method. + * \param [in] properties the properties associated with the message + * \param [in] body the message body + * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. Note + * that basic.publish is an async method, the return value from this + * function only indicates that the message data was successfully + * transmitted to the broker. It does not indicate failures that occur + * on the broker, such as publishing to a non-existent exchange. + * Possible error values: + * - AMQP_STATUS_TIMER_FAILURE: system timer facility returned an error + * the message was not sent. + * - AMQP_STATUS_HEARTBEAT_TIMEOUT: connection timed out waiting for a + * heartbeat from the broker. The message was not sent. + * - AMQP_STATUS_NO_MEMORY: memory allocation failed. The message was + * not sent. + * - AMQP_STATUS_TABLE_TOO_BIG: a table in the properties was too large + * to fit in a single frame. Message was not sent. + * - AMQP_STATUS_CONNECTION_CLOSED: the connection was closed. + * - AMQP_STATUS_SSL_ERROR: a SSL error occurred. + * - AMQP_STATUS_TCP_ERROR: a TCP error occurred. errno or + * WSAGetLastError() may provide more information + * + * Note: this function does heartbeat processing as of v0.4.0 + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_basic_publish( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_boolean_t mandatory, + amqp_boolean_t immediate, struct amqp_basic_properties_t_ const *properties, + amqp_bytes_t body); + +/** + * Closes an channel + * + * \param [in] state the connection object + * \param [in] channel the channel identifier + * \param [in] code the reason for closing the channel, AMQP_REPLY_SUCCESS is a + * good default + * \return amqp_rpc_reply_t indicating success or failure + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_channel_close(amqp_connection_state_t state, + amqp_channel_t channel, int code); + +/** + * Closes the entire connection + * + * Implicitly closes all channels and informs the broker the connection + * is being closed, after receiving acknowledgment from the broker it closes + * the socket. + * + * \param [in] state the connection object + * \param [in] code the reason code for closing the connection. + * AMQP_REPLY_SUCCESS is a good default. + * \return amqp_rpc_reply_t indicating the result + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_connection_close(amqp_connection_state_t state, + int code); + +/** + * Acknowledges a message + * + * Does a basic.ack on a received message + * + * \param [in] state the connection object + * \param [in] channel the channel identifier + * \param [in] delivery_tag the delivery tag of the message to be ack'd + * \param [in] multiple if true, ack all messages up to this delivery tag, if + * false ack only this delivery tag + * \return 0 on success, 0 > on failing to send the ack to the broker. + * this will not indicate failure if something goes wrong on the + * broker + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_basic_ack(amqp_connection_state_t state, + amqp_channel_t channel, uint64_t delivery_tag, + amqp_boolean_t multiple); + +/** + * Do a basic.get + * + * Synchonously polls the broker for a message in a queue, and + * retrieves the message if a message is in the queue. + * + * \param [in] state the connection object + * \param [in] channel the channel identifier to use + * \param [in] queue the queue name to retrieve from + * \param [in] no_ack if true the message is automatically ack'ed + * if false amqp_basic_ack should be called once the message + * retrieved has been processed + * \return amqp_rpc_reply indicating success or failure + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_basic_get(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_bytes_t queue, + amqp_boolean_t no_ack); + +/** + * Do a basic.reject + * + * Actively reject a message that has been delivered + * + * \param [in] state the connection object + * \param [in] channel the channel identifier + * \param [in] delivery_tag the delivery tag of the message to reject + * \param [in] requeue indicate to the broker whether it should requeue the + * message or just discard it. + * \return 0 on success, 0 > on failing to send the reject method to the broker. + * This will not indicate failure if something goes wrong on the + * broker. + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_basic_reject(amqp_connection_state_t state, + amqp_channel_t channel, uint64_t delivery_tag, + amqp_boolean_t requeue); + +/** + * Do a basic.nack + * + * Actively reject a message, this has the same effect as amqp_basic_reject() + * however, amqp_basic_nack() can negatively acknowledge multiple messages with + * one call much like amqp_basic_ack() can acknowledge mutliple messages with + * one call. + * + * \param [in] state the connection object + * \param [in] channel the channel identifier + * \param [in] delivery_tag the delivery tag of the message to reject + * \param [in] multiple if set to 1 negatively acknowledge all unacknowledged + * messages on this channel. + * \param [in] requeue indicate to the broker whether it should requeue the + * message or dead-letter it. + * \return AMQP_STATUS_OK on success, an amqp_status_enum value otherwise. + * + * \since v0.5.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_basic_nack(amqp_connection_state_t state, + amqp_channel_t channel, uint64_t delivery_tag, + amqp_boolean_t multiple, amqp_boolean_t requeue); +/** + * Check to see if there is data left in the receive buffer + * + * Can be used to see if there is data still in the buffer, if so + * calling amqp_simple_wait_frame will not immediately enter a + * blocking read. + * + * \param [in] state the connection object + * \return true if there is data in the recieve buffer, false otherwise + * + * \since v0.1 + */ +AMQP_EXPORT +amqp_boolean_t AMQP_CALL amqp_data_in_buffer(amqp_connection_state_t state); + +/** + * Get the error string for the given error code. + * + * \deprecated This function has been deprecated in favor of + * \ref amqp_error_string2() which returns statically allocated + * string which do not need to be freed by the caller. + * + * The returned string resides on the heap; the caller is responsible + * for freeing it. + * + * \param [in] err return error code + * \return the error string + * + * \since v0.1 + */ +AMQP_DEPRECATED_EXPORT char *AMQP_CALL amqp_error_string(int err); + +/** + * Get the error string for the given error code. + * + * Get an error string associated with an error code. The string is statically + * allocated and does not need to be freed + * + * \param [in] err the error code + * \return the error string + * + * \since v0.4.0 + */ +AMQP_EXPORT +const char *AMQP_CALL amqp_error_string2(int err); + +/** + * Deserialize an amqp_table_t from AMQP wireformat + * + * This is an internal function and is not typically used by + * client applications + * + * \param [in] encoded the buffer containing the serialized data + * \param [in] pool memory pool used to allocate the table entries from + * \param [in] output the amqp_table_t structure to fill in. Any existing + * entries will be erased + * \param [in,out] offset The offset into the encoded buffer to start + * reading the serialized table. It will be updated + * by this function to end of the table + * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure + * Possible error codes: + * - AMQP_STATUS_NO_MEMORY out of memory + * - AMQP_STATUS_BAD_AMQP_DATA invalid wireformat + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_decode_table(amqp_bytes_t encoded, amqp_pool_t *pool, + amqp_table_t *output, size_t *offset); + +/** + * Serializes an amqp_table_t to the AMQP wireformat + * + * This is an internal function and is not typically used by + * client applications + * + * \param [in] encoded the buffer where to serialize the table to + * \param [in] input the amqp_table_t to serialize + * \param [in,out] offset The offset into the encoded buffer to start + * writing the serialized table. It will be updated + * by this function to where writing left off + * \return AMQP_STATUS_OK on success, an amqp_status_enum value on failure + * Possible error codes: + * - AMQP_STATUS_TABLE_TOO_BIG the serialized form is too large for the + * buffer + * - AMQP_STATUS_BAD_AMQP_DATA invalid table + * + * \since v0.1 + */ +AMQP_EXPORT +int AMQP_CALL amqp_encode_table(amqp_bytes_t encoded, amqp_table_t *input, + size_t *offset); + +/** + * Create a deep-copy of an amqp_table_t object + * + * Creates a deep-copy of an amqp_table_t object, using the provided pool + * object to allocate the necessary memory. This memory can be freed later by + * call recycle_amqp_pool(), or empty_amqp_pool() + * + * \param [in] original the table to copy + * \param [in,out] clone the table to copy to + * \param [in] pool the initialized memory pool to do allocations for the table + * from + * \return AMQP_STATUS_OK on success, amqp_status_enum value on failure. + * Possible error values: + * - AMQP_STATUS_NO_MEMORY - memory allocation failure. + * - AMQP_STATUS_INVALID_PARAMETER - invalid table (e.g., no key name) + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_table_clone(const amqp_table_t *original, + amqp_table_t *clone, amqp_pool_t *pool); + +/** + * A message object + * + * \since v0.4.0 + */ +typedef struct amqp_message_t_ { + amqp_basic_properties_t properties; /**< message properties */ + amqp_bytes_t body; /**< message body */ + amqp_pool_t pool; /**< pool used to allocate properties */ +} amqp_message_t; + +/** + * Reads the next message on a channel + * + * Reads a complete message (header + body) on a specified channel. This + * function is intended to be used with amqp_basic_get() or when an + * AMQP_BASIC_DELIVERY_METHOD method is received. + * + * \param [in,out] state the connection object + * \param [in] channel the channel on which to read the message from + * \param [in,out] message a pointer to a amqp_message_t object. Caller should + * call amqp_message_destroy() when it is done using the + * fields in the message object. The caller is responsible for + * allocating/destroying the amqp_message_t object itself. + * \param [in] flags pass in 0. Currently unused. + * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL on + * success. + * + * \since v0.4.0 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_read_message(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_message_t *message, + int flags); + +/** + * Frees memory associated with a amqp_message_t allocated in amqp_read_message + * + * \param [in] message + * + * \since v0.4.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_destroy_message(amqp_message_t *message); + +/** + * Envelope object + * + * \since v0.4.0 + */ +typedef struct amqp_envelope_t_ { + amqp_channel_t channel; /**< channel message was delivered on */ + amqp_bytes_t consumer_tag; /**< the consumer tag the message was delivered to + */ + uint64_t delivery_tag; /**< the messages delivery tag */ + amqp_boolean_t redelivered; /**< flag indicating whether this message is being + redelivered */ + amqp_bytes_t exchange; /**< exchange this message was published to */ + amqp_bytes_t routing_key; /**< the routing key this message was published with + */ + amqp_message_t message; /**< the message */ +} amqp_envelope_t; + +/** + * Wait for and consume a message + * + * Waits for a basic.deliver method on any channel, upon receipt of + * basic.deliver it reads that message, and returns. If any other method is + * received before basic.deliver, this function will return an amqp_rpc_reply_t + * with ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, and + * ret.library_error == AMQP_STATUS_UNEXPECTED_STATE. The caller should then + * call amqp_simple_wait_frame() to read this frame and take appropriate action. + * + * This function should be used after starting a consumer with the + * amqp_basic_consume() function + * + * \param [in,out] state the connection object + * \param [in,out] envelope a pointer to a amqp_envelope_t object. Caller + * should call #amqp_destroy_envelope() when it is done using + * the fields in the envelope object. The caller is responsible + * for allocating/destroying the amqp_envelope_t object itself. + * \param [in] timeout a timeout to wait for a message delivery. Passing in + * NULL will result in blocking behavior. + * \param [in] flags pass in 0. Currently unused. + * \returns a amqp_rpc_reply_t object. ret.reply_type == AMQP_RESPONSE_NORMAL + * on success. If ret.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION, + * and ret.library_error == AMQP_STATUS_UNEXPECTED_STATE, a frame other + * than AMQP_BASIC_DELIVER_METHOD was received, the caller should call + * amqp_simple_wait_frame() to read this frame and take appropriate + * action. + * + * \since v0.4.0 + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_consume_message(amqp_connection_state_t state, + amqp_envelope_t *envelope, + const struct timeval *timeout, + int flags); + +/** + * Frees memory associated with a amqp_envelope_t allocated in + * amqp_consume_message() + * + * \param [in] envelope + * + * \since v0.4.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_destroy_envelope(amqp_envelope_t *envelope); + +/** + * Parameters used to connect to the RabbitMQ broker + * + * \since v0.2 + */ +struct amqp_connection_info { + char *user; /**< the username to authenticate with the broker, default on most + broker is 'guest' */ + char *password; /**< the password to authenticate with the broker, default on + most brokers is 'guest' */ + char *host; /**< the hostname of the broker */ + char *vhost; /**< the virtual host on the broker to connect to, a good default + is "/" */ + int port; /**< the port that the broker is listening on, default on most + brokers is 5672 */ + amqp_boolean_t ssl; +}; + +/** + * Initialze an amqp_connection_info to default values + * + * The default values are: + * - user: "guest" + * - password: "guest" + * - host: "localhost" + * - vhost: "/" + * - port: 5672 + * + * \param [out] parsed the connection info to set defaults on + * + * \since v0.2 + */ +AMQP_EXPORT +void AMQP_CALL + amqp_default_connection_info(struct amqp_connection_info *parsed); + +/** + * Parse a connection URL + * + * An amqp connection url takes the form: + * + * amqp://[$USERNAME[:$PASSWORD]\@]$HOST[:$PORT]/[$VHOST] + * + * Examples: + * amqp://guest:guest\@localhost:5672// + * amqp://guest:guest\@localhost/myvhost + * + * Any missing parts of the URL will be set to the defaults specified in + * amqp_default_connection_info. For amqps: URLs the default port will be set + * to 5671 instead of 5672 for non-SSL URLs. + * + * \note This function modifies url parameter. + * + * \param [in] url URI to parse, note that this parameter is modified by the + * function. + * \param [out] parsed the connection info gleaned from the URI. The char* + * members will point to parts of the url input parameter. + * Memory management will depend on how the url is allocated. + * \returns AMQP_STATUS_OK on success, AMQP_STATUS_BAD_URL on failure + * + * \since v0.2 + */ +AMQP_EXPORT +int AMQP_CALL amqp_parse_url(char *url, struct amqp_connection_info *parsed); + +/* socket API */ + +/** + * Open a socket connection. + * + * This function opens a socket connection returned from amqp_tcp_socket_new() + * or amqp_ssl_socket_new(). This function should be called after setting + * socket options and prior to assigning the socket to an AMQP connection with + * amqp_set_socket(). + * + * \param [in,out] self A socket object. + * \param [in] host Connect to this host. + * \param [in] port Connect on this remote port. + * + * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_socket_open(amqp_socket_t *self, const char *host, int port); + +/** + * Open a socket connection. + * + * This function opens a socket connection returned from amqp_tcp_socket_new() + * or amqp_ssl_socket_new(). This function should be called after setting + * socket options and prior to assigning the socket to an AMQP connection with + * amqp_set_socket(). + * + * \param [in,out] self A socket object. + * \param [in] host Connect to this host. + * \param [in] port Connect on this remote port. + * \param [in] timeout Max allowed time to spent on opening. If NULL - run in + * blocking mode + * + * \return AMQP_STATUS_OK on success, an amqp_status_enum on failure. + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_socket_open_noblock(amqp_socket_t *self, const char *host, + int port, const struct timeval *timeout); + +/** + * Get the socket descriptor in use by a socket object. + * + * Retrieve the underlying socket descriptor. This function can be used to + * perform low-level socket operations that aren't supported by the socket + * interface. Use with caution! + * + * \param [in,out] self A socket object. + * + * \return The underlying socket descriptor, or -1 if there is no socket + * descriptor associated with + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_socket_get_sockfd(amqp_socket_t *self); + +/** + * Get the socket object associated with a amqp_connection_state_t + * + * \param [in] state the connection object to get the socket from + * \return a pointer to the socket object, or NULL if one has not been assigned + * + * \since v0.4.0 + */ +AMQP_EXPORT +amqp_socket_t *AMQP_CALL amqp_get_socket(amqp_connection_state_t state); + +/** + * Get the broker properties table + * + * \param [in] state the connection object + * \return a pointer to an amqp_table_t containing the properties advertised + * by the broker on connection. The connection object owns the table, it + * should not be modified. + * + * \since v0.5.0 + */ +AMQP_EXPORT +amqp_table_t *AMQP_CALL + amqp_get_server_properties(amqp_connection_state_t state); + +/** + * Get the client properties table + * + * Get the properties that were passed to the broker on connection. + * + * \param [in] state the connection object + * \return a pointer to an amqp_table_t containing the properties advertised + * by the client on connection. The connection object owns the table, it + * should not be modified. + * + * \since v0.7.0 + */ +AMQP_EXPORT +amqp_table_t *AMQP_CALL + amqp_get_client_properties(amqp_connection_state_t state); + +/** + * Get the login handshake timeout. + * + * amqp_login and amqp_login_with_properties perform the login handshake with + * the broker. This function returns the timeout associated with completing + * this operation from the client side. This value can be set by using the + * amqp_set_handshake_timeout. + * + * Note that the RabbitMQ broker has configurable timeout for completing the + * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12 + * seconds. + * + * \param [in] state the connection object + * \return a struct timeval representing the current login timeout for the state + * object. A NULL value represents an infinite timeout. The memory returned is + * owned by the connection object. + * + * \since v0.9.0 + */ +AMQP_EXPORT +struct timeval *AMQP_CALL + amqp_get_handshake_timeout(amqp_connection_state_t state); + +/** + * Set the login handshake timeout. + * + * amqp_login and amqp_login_with_properties perform the login handshake with + * the broker. This function sets the timeout associated with completing this + * operation from the client side. + * + * The timeout must be set before amqp_login or amqp_login_with_properties is + * called to change from the default timeout. + * + * Note that the RabbitMQ broker has a configurable timeout for completing the + * login handshake, the default is 10 seconds. rabbitmq-c has a default of 12 + * seconds. + * + * \param [in] state the connection object + * \param [in] timeout a struct timeval* representing new login timeout for the + * state object. NULL represents an infinite timeout. The value of timeout is + * copied internally, the caller is responsible for ownership of the passed in + * pointer, it does not need to remain valid after this function is called. + * \return AMQP_STATUS_OK on success. + * + * \since v0.9.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_set_handshake_timeout(amqp_connection_state_t state, + const struct timeval *timeout); + +/** + * Get the RPC timeout + * + * Gets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare). + * This timeout may be changed at any time by calling \amqp_set_rpc_timeout + * function with a new timeout. The timeout applies individually to each RPC + * that is made. + * + * The default value is NULL, or an infinite timeout. + * + * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT, + * and the connection will be closed. + * + *\warning RPC-timeouts are an advanced feature intended to be used to detect + * dead connections quickly when the rabbitmq-c implementation of heartbeats + * does not work. Do not use RPC timeouts unless you understand the implications + * of doing so. + * + * \param [in] state the connection object + * \return a struct timeval representing the current RPC timeout for the state + * object. A NULL value represents an infinite timeout. The memory returned is + * owned by the connection object. + * + * \since v0.9.0 + */ +AMQP_EXPORT +struct timeval *AMQP_CALL amqp_get_rpc_timeout(amqp_connection_state_t state); + +/** + * Set the RPC timeout + * + * Sets the timeout for any RPC-style AMQP command (e.g., amqp_queue_declare). + * This timeout may be changed at any time by calling this function with a new + * timeout. The timeout applies individually to each RPC that is made. + * + * The default value is NULL, or an infinite timeout. + * + * When an RPC times out, the function will return an error AMQP_STATUS_TIMEOUT, + * and the connection will be closed. + * + *\warning RPC-timeouts are an advanced feature intended to be used to detect + * dead connections quickly when the rabbitmq-c implementation of heartbeats + * does not work. Do not use RPC timeouts unless you understand the implications + * of doing so. + * + * \param [in] state the connection object + * \param [in] timeout a struct timeval* representing new RPC timeout for the + * state object. NULL represents an infinite timeout. The value of timeout is + * copied internally, the caller is responsible for ownership of the passed + * pointer, it does not need to remain valid after this function is called. + * \return AMQP_STATUS_SUCCESS on success. + * + * \since v0.9.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_set_rpc_timeout(amqp_connection_state_t state, + const struct timeval *timeout); + +/** + * Possible payload permutations for publisher confirms. + **/ +typedef union amqp_publisher_confirm_payload_t_ { + amqp_basic_ack_t ack; /* basic.ack */ + amqp_basic_nack_t nack; /* basic.nack */ + amqp_basic_reject_t reject; /* basic.reject */ +} amqp_publisher_confirm_payload_t; + +/** + * Return information from publisher confirm wait + **/ +typedef struct amqp_publisher_confirm_t_ { + amqp_publisher_confirm_payload_t payload; /* The response payload; check the `method` value to see which value you should use in the union */ + amqp_channel_t channel; /* The channel where the confirmation was received */ + amqp_method_number_t method; /* The method which was received */ +} amqp_publisher_confirm_t; + +/** + * amqp_publisher_confirm_wait + * + * Wait for a publisher confirm when one or more channel is in select mode. + * If the response has a `reply_type` of `AMQP_RESPONSE_LIBRARY_EXCEPTION` _and_ + * the `library_error` is `AMQP_STATUS_UNEXPECTED_STATE`, then the frame + * received was not an ack. + * + * In the event that there are no publisher confirms received during the + * allotted time, `reply_type` will be `AMQP_RESPONSE_LIBRARY_EXCEPTION` + * and the `library_error` will be `AMQP_STATUS_TIMEOUT`. + * + * When a publisher confirm is received, `reply_type` will equal + * `AMQP_RESPONSE_NORMAL`, and the `result` out parameter will + * contain all of the information you need: + * + * - The `channel` will identify which channel the publisher confirm was received on + * - The `method` will tell you whether this is an `ack`, `nack`, or `reject` + * - The `payload` is a union, and based on the `method` it will use one of `amqp_basic_ack_t`, `amqp_basic_nack_t`, or `amqp_basic_reject_t` + * + * \param [in] state connection state + * \param [in] timeout when waiting for the frame. Passing NULL will result in + * blocking behavior + * \param [out] The result of the publisher confirm wait. + */ +AMQP_EXPORT +amqp_rpc_reply_t AMQP_CALL amqp_publisher_confirm_wait( + amqp_connection_state_t state, const struct timeval *timeout, + amqp_publisher_confirm_t *result); + +AMQP_END_DECLS + +#endif /* RABBITMQ_C_RABBITMQ_C_H */ diff --git a/include/rabbitmq-c/framing.h b/include/rabbitmq-c/framing.h new file mode 100644 index 0000000..9f2a7ed --- /dev/null +++ b/include/rabbitmq-c/framing.h @@ -0,0 +1,1150 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/* Generated code. Do not edit. Edit and re-run codegen.py instead. */ + +/** @file rabbitmq-c/framing.h */ +#ifndef RABBITMQ_C_FRAMING_H +#define RABBITMQ_C_FRAMING_H + +#include +#include + +AMQP_BEGIN_DECLS + +#define AMQP_PROTOCOL_VERSION_MAJOR 0 /**< AMQP protocol version major */ +#define AMQP_PROTOCOL_VERSION_MINOR 9 /**< AMQP protocol version minor */ +#define AMQP_PROTOCOL_VERSION_REVISION 1 /**< AMQP protocol version revision \ + */ +#define AMQP_PROTOCOL_PORT 5672 /**< Default AMQP Port */ +#define AMQP_FRAME_METHOD 1 /**< Constant: FRAME-METHOD */ +#define AMQP_FRAME_HEADER 2 /**< Constant: FRAME-HEADER */ +#define AMQP_FRAME_BODY 3 /**< Constant: FRAME-BODY */ +#define AMQP_FRAME_HEARTBEAT 8 /**< Constant: FRAME-HEARTBEAT */ +#define AMQP_FRAME_MIN_SIZE 4096 /**< Constant: FRAME-MIN-SIZE */ +#define AMQP_FRAME_END 206 /**< Constant: FRAME-END */ +#define AMQP_REPLY_SUCCESS 200 /**< Constant: REPLY-SUCCESS */ +#define AMQP_CONTENT_TOO_LARGE 311 /**< Constant: CONTENT-TOO-LARGE */ +#define AMQP_NO_ROUTE 312 /**< Constant: NO-ROUTE */ +#define AMQP_NO_CONSUMERS 313 /**< Constant: NO-CONSUMERS */ +#define AMQP_ACCESS_REFUSED 403 /**< Constant: ACCESS-REFUSED */ +#define AMQP_NOT_FOUND 404 /**< Constant: NOT-FOUND */ +#define AMQP_RESOURCE_LOCKED 405 /**< Constant: RESOURCE-LOCKED */ +#define AMQP_PRECONDITION_FAILED 406 /**< Constant: PRECONDITION-FAILED */ +#define AMQP_CONNECTION_FORCED 320 /**< Constant: CONNECTION-FORCED */ +#define AMQP_INVALID_PATH 402 /**< Constant: INVALID-PATH */ +#define AMQP_FRAME_ERROR 501 /**< Constant: FRAME-ERROR */ +#define AMQP_SYNTAX_ERROR 502 /**< Constant: SYNTAX-ERROR */ +#define AMQP_COMMAND_INVALID 503 /**< Constant: COMMAND-INVALID */ +#define AMQP_CHANNEL_ERROR 504 /**< Constant: CHANNEL-ERROR */ +#define AMQP_UNEXPECTED_FRAME 505 /**< Constant: UNEXPECTED-FRAME */ +#define AMQP_RESOURCE_ERROR 506 /**< Constant: RESOURCE-ERROR */ +#define AMQP_NOT_ALLOWED 530 /**< Constant: NOT-ALLOWED */ +#define AMQP_NOT_IMPLEMENTED 540 /**< Constant: NOT-IMPLEMENTED */ +#define AMQP_INTERNAL_ERROR 541 /**< Constant: INTERNAL-ERROR */ + +/* Function prototypes. */ + +/** + * Get constant name string from constant + * + * @param [in] constantNumber constant to get the name of + * @returns string describing the constant. String is managed by + * the library and should not be free()'d by the program + */ +AMQP_EXPORT +char const *AMQP_CALL amqp_constant_name(int constantNumber); + +/** + * Checks to see if a constant is a hard error + * + * A hard error occurs when something severe enough + * happens that the connection must be closed. + * + * @param [in] constantNumber the error constant + * @returns true if its a hard error, false otherwise + */ +AMQP_EXPORT +amqp_boolean_t AMQP_CALL amqp_constant_is_hard_error(int constantNumber); + +/** + * Get method name string from method number + * + * @param [in] methodNumber the method number + * @returns method name string. String is managed by the library + * and should not be freed()'d by the program + */ +AMQP_EXPORT +char const *AMQP_CALL amqp_method_name(amqp_method_number_t methodNumber); + +/** + * Check whether a method has content + * + * A method that has content will receive the method frame + * a properties frame, then 1 to N body frames + * + * @param [in] methodNumber the method number + * @returns true if method has content, false otherwise + */ +AMQP_EXPORT +amqp_boolean_t AMQP_CALL + amqp_method_has_content(amqp_method_number_t methodNumber); + +/** + * Decodes a method from AMQP wireformat + * + * @param [in] methodNumber the method number for the decoded parameter + * @param [in] pool the memory pool to allocate the decoded method from + * @param [in] encoded the encoded byte string buffer + * @param [out] decoded pointer to the decoded method struct + * @returns 0 on success, an error code otherwise + */ +AMQP_EXPORT +int AMQP_CALL amqp_decode_method(amqp_method_number_t methodNumber, + amqp_pool_t *pool, amqp_bytes_t encoded, + void **decoded); + +/** + * Decodes a header frame properties structure from AMQP wireformat + * + * @param [in] class_id the class id for the decoded parameter + * @param [in] pool the memory pool to allocate the decoded properties from + * @param [in] encoded the encoded byte string buffer + * @param [out] decoded pointer to the decoded properties struct + * @returns 0 on success, an error code otherwise + */ +AMQP_EXPORT +int AMQP_CALL amqp_decode_properties(uint16_t class_id, amqp_pool_t *pool, + amqp_bytes_t encoded, void **decoded); + +/** + * Encodes a method structure in AMQP wireformat + * + * @param [in] methodNumber the method number for the decoded parameter + * @param [in] decoded the method structure (e.g., amqp_connection_start_t) + * @param [in] encoded an allocated byte buffer for the encoded method + * structure to be written to. If the buffer isn't large enough + * to hold the encoded method, an error code will be returned. + * @returns 0 on success, an error code otherwise. + */ +AMQP_EXPORT +int AMQP_CALL amqp_encode_method(amqp_method_number_t methodNumber, + void *decoded, amqp_bytes_t encoded); + +/** + * Encodes a properties structure in AMQP wireformat + * + * @param [in] class_id the class id for the decoded parameter + * @param [in] decoded the properties structure (e.g., amqp_basic_properties_t) + * @param [in] encoded an allocated byte buffer for the encoded properties to + * written to. If the buffer isn't large enough to hold the encoded method, an + * an error code will be returned + * @returns 0 on success, an error code otherwise. + */ +AMQP_EXPORT +int AMQP_CALL amqp_encode_properties(uint16_t class_id, void *decoded, + amqp_bytes_t encoded); + +/* Method field records. */ + +#define AMQP_CONNECTION_START_METHOD \ + ((amqp_method_number_t)0x000A000A) /**< connection.start method id @internal \ + 10, 10; 655370 */ +/** connection.start method fields */ +typedef struct amqp_connection_start_t_ { + uint8_t version_major; /**< version-major */ + uint8_t version_minor; /**< version-minor */ + amqp_table_t server_properties; /**< server-properties */ + amqp_bytes_t mechanisms; /**< mechanisms */ + amqp_bytes_t locales; /**< locales */ +} amqp_connection_start_t; + +#define AMQP_CONNECTION_START_OK_METHOD \ + ((amqp_method_number_t)0x000A000B) /**< connection.start-ok method id \ + @internal 10, 11; 655371 */ +/** connection.start-ok method fields */ +typedef struct amqp_connection_start_ok_t_ { + amqp_table_t client_properties; /**< client-properties */ + amqp_bytes_t mechanism; /**< mechanism */ + amqp_bytes_t response; /**< response */ + amqp_bytes_t locale; /**< locale */ +} amqp_connection_start_ok_t; + +#define AMQP_CONNECTION_SECURE_METHOD \ + ((amqp_method_number_t)0x000A0014) /**< connection.secure method id \ + @internal 10, 20; 655380 */ +/** connection.secure method fields */ +typedef struct amqp_connection_secure_t_ { + amqp_bytes_t challenge; /**< challenge */ +} amqp_connection_secure_t; + +#define AMQP_CONNECTION_SECURE_OK_METHOD \ + ((amqp_method_number_t)0x000A0015) /**< connection.secure-ok method id \ + @internal 10, 21; 655381 */ +/** connection.secure-ok method fields */ +typedef struct amqp_connection_secure_ok_t_ { + amqp_bytes_t response; /**< response */ +} amqp_connection_secure_ok_t; + +#define AMQP_CONNECTION_TUNE_METHOD \ + ((amqp_method_number_t)0x000A001E) /**< connection.tune method id @internal \ + 10, 30; 655390 */ +/** connection.tune method fields */ +typedef struct amqp_connection_tune_t_ { + uint16_t channel_max; /**< channel-max */ + uint32_t frame_max; /**< frame-max */ + uint16_t heartbeat; /**< heartbeat */ +} amqp_connection_tune_t; + +#define AMQP_CONNECTION_TUNE_OK_METHOD \ + ((amqp_method_number_t)0x000A001F) /**< connection.tune-ok method id \ + @internal 10, 31; 655391 */ +/** connection.tune-ok method fields */ +typedef struct amqp_connection_tune_ok_t_ { + uint16_t channel_max; /**< channel-max */ + uint32_t frame_max; /**< frame-max */ + uint16_t heartbeat; /**< heartbeat */ +} amqp_connection_tune_ok_t; + +#define AMQP_CONNECTION_OPEN_METHOD \ + ((amqp_method_number_t)0x000A0028) /**< connection.open method id @internal \ + 10, 40; 655400 */ +/** connection.open method fields */ +typedef struct amqp_connection_open_t_ { + amqp_bytes_t virtual_host; /**< virtual-host */ + amqp_bytes_t capabilities; /**< capabilities */ + amqp_boolean_t insist; /**< insist */ +} amqp_connection_open_t; + +#define AMQP_CONNECTION_OPEN_OK_METHOD \ + ((amqp_method_number_t)0x000A0029) /**< connection.open-ok method id \ + @internal 10, 41; 655401 */ +/** connection.open-ok method fields */ +typedef struct amqp_connection_open_ok_t_ { + amqp_bytes_t known_hosts; /**< known-hosts */ +} amqp_connection_open_ok_t; + +#define AMQP_CONNECTION_CLOSE_METHOD \ + ((amqp_method_number_t)0x000A0032) /**< connection.close method id @internal \ + 10, 50; 655410 */ +/** connection.close method fields */ +typedef struct amqp_connection_close_t_ { + uint16_t reply_code; /**< reply-code */ + amqp_bytes_t reply_text; /**< reply-text */ + uint16_t class_id; /**< class-id */ + uint16_t method_id; /**< method-id */ +} amqp_connection_close_t; + +#define AMQP_CONNECTION_CLOSE_OK_METHOD \ + ((amqp_method_number_t)0x000A0033) /**< connection.close-ok method id \ + @internal 10, 51; 655411 */ +/** connection.close-ok method fields */ +typedef struct amqp_connection_close_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_connection_close_ok_t; + +#define AMQP_CONNECTION_BLOCKED_METHOD \ + ((amqp_method_number_t)0x000A003C) /**< connection.blocked method id \ + @internal 10, 60; 655420 */ +/** connection.blocked method fields */ +typedef struct amqp_connection_blocked_t_ { + amqp_bytes_t reason; /**< reason */ +} amqp_connection_blocked_t; + +#define AMQP_CONNECTION_UNBLOCKED_METHOD \ + ((amqp_method_number_t)0x000A003D) /**< connection.unblocked method id \ + @internal 10, 61; 655421 */ +/** connection.unblocked method fields */ +typedef struct amqp_connection_unblocked_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_connection_unblocked_t; + +#define AMQP_CONNECTION_UPDATE_SECRET_METHOD \ + ((amqp_method_number_t)0x000A0046) /**< connection.update-secret method id \ + @internal 10, 70; 655430 */ +/** connection.update-secret method fields */ +typedef struct amqp_connection_update_secret_t_ { + amqp_bytes_t new_secret; /**< new-secret */ + amqp_bytes_t reason; /**< reason */ +} amqp_connection_update_secret_t; + +#define AMQP_CONNECTION_UPDATE_SECRET_OK_METHOD \ + ((amqp_method_number_t)0x000A0047) /**< connection.update-secret-ok method \ + id @internal 10, 71; 655431 */ +/** connection.update-secret-ok method fields */ +typedef struct amqp_connection_update_secret_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_connection_update_secret_ok_t; + +#define AMQP_CHANNEL_OPEN_METHOD \ + ((amqp_method_number_t)0x0014000A) /**< channel.open method id @internal 20, \ + 10; 1310730 */ +/** channel.open method fields */ +typedef struct amqp_channel_open_t_ { + amqp_bytes_t out_of_band; /**< out-of-band */ +} amqp_channel_open_t; + +#define AMQP_CHANNEL_OPEN_OK_METHOD \ + ((amqp_method_number_t)0x0014000B) /**< channel.open-ok method id @internal \ + 20, 11; 1310731 */ +/** channel.open-ok method fields */ +typedef struct amqp_channel_open_ok_t_ { + amqp_bytes_t channel_id; /**< channel-id */ +} amqp_channel_open_ok_t; + +#define AMQP_CHANNEL_FLOW_METHOD \ + ((amqp_method_number_t)0x00140014) /**< channel.flow method id @internal 20, \ + 20; 1310740 */ +/** channel.flow method fields */ +typedef struct amqp_channel_flow_t_ { + amqp_boolean_t active; /**< active */ +} amqp_channel_flow_t; + +#define AMQP_CHANNEL_FLOW_OK_METHOD \ + ((amqp_method_number_t)0x00140015) /**< channel.flow-ok method id @internal \ + 20, 21; 1310741 */ +/** channel.flow-ok method fields */ +typedef struct amqp_channel_flow_ok_t_ { + amqp_boolean_t active; /**< active */ +} amqp_channel_flow_ok_t; + +#define AMQP_CHANNEL_CLOSE_METHOD \ + ((amqp_method_number_t)0x00140028) /**< channel.close method id @internal \ + 20, 40; 1310760 */ +/** channel.close method fields */ +typedef struct amqp_channel_close_t_ { + uint16_t reply_code; /**< reply-code */ + amqp_bytes_t reply_text; /**< reply-text */ + uint16_t class_id; /**< class-id */ + uint16_t method_id; /**< method-id */ +} amqp_channel_close_t; + +#define AMQP_CHANNEL_CLOSE_OK_METHOD \ + ((amqp_method_number_t)0x00140029) /**< channel.close-ok method id @internal \ + 20, 41; 1310761 */ +/** channel.close-ok method fields */ +typedef struct amqp_channel_close_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_channel_close_ok_t; + +#define AMQP_ACCESS_REQUEST_METHOD \ + ((amqp_method_number_t)0x001E000A) /**< access.request method id @internal \ + 30, 10; 1966090 */ +/** access.request method fields */ +typedef struct amqp_access_request_t_ { + amqp_bytes_t realm; /**< realm */ + amqp_boolean_t exclusive; /**< exclusive */ + amqp_boolean_t passive; /**< passive */ + amqp_boolean_t active; /**< active */ + amqp_boolean_t write; /**< write */ + amqp_boolean_t read; /**< read */ +} amqp_access_request_t; + +#define AMQP_ACCESS_REQUEST_OK_METHOD \ + ((amqp_method_number_t)0x001E000B) /**< access.request-ok method id \ + @internal 30, 11; 1966091 */ +/** access.request-ok method fields */ +typedef struct amqp_access_request_ok_t_ { + uint16_t ticket; /**< ticket */ +} amqp_access_request_ok_t; + +#define AMQP_EXCHANGE_DECLARE_METHOD \ + ((amqp_method_number_t)0x0028000A) /**< exchange.declare method id @internal \ + 40, 10; 2621450 */ +/** exchange.declare method fields */ +typedef struct amqp_exchange_declare_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t type; /**< type */ + amqp_boolean_t passive; /**< passive */ + amqp_boolean_t durable; /**< durable */ + amqp_boolean_t auto_delete; /**< auto-delete */ + amqp_boolean_t internal; /**< internal */ + amqp_boolean_t nowait; /**< nowait */ + amqp_table_t arguments; /**< arguments */ +} amqp_exchange_declare_t; + +#define AMQP_EXCHANGE_DECLARE_OK_METHOD \ + ((amqp_method_number_t)0x0028000B) /**< exchange.declare-ok method id \ + @internal 40, 11; 2621451 */ +/** exchange.declare-ok method fields */ +typedef struct amqp_exchange_declare_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_exchange_declare_ok_t; + +#define AMQP_EXCHANGE_DELETE_METHOD \ + ((amqp_method_number_t)0x00280014) /**< exchange.delete method id @internal \ + 40, 20; 2621460 */ +/** exchange.delete method fields */ +typedef struct amqp_exchange_delete_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t exchange; /**< exchange */ + amqp_boolean_t if_unused; /**< if-unused */ + amqp_boolean_t nowait; /**< nowait */ +} amqp_exchange_delete_t; + +#define AMQP_EXCHANGE_DELETE_OK_METHOD \ + ((amqp_method_number_t)0x00280015) /**< exchange.delete-ok method id \ + @internal 40, 21; 2621461 */ +/** exchange.delete-ok method fields */ +typedef struct amqp_exchange_delete_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_exchange_delete_ok_t; + +#define AMQP_EXCHANGE_BIND_METHOD \ + ((amqp_method_number_t)0x0028001E) /**< exchange.bind method id @internal \ + 40, 30; 2621470 */ +/** exchange.bind method fields */ +typedef struct amqp_exchange_bind_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t destination; /**< destination */ + amqp_bytes_t source; /**< source */ + amqp_bytes_t routing_key; /**< routing-key */ + amqp_boolean_t nowait; /**< nowait */ + amqp_table_t arguments; /**< arguments */ +} amqp_exchange_bind_t; + +#define AMQP_EXCHANGE_BIND_OK_METHOD \ + ((amqp_method_number_t)0x0028001F) /**< exchange.bind-ok method id @internal \ + 40, 31; 2621471 */ +/** exchange.bind-ok method fields */ +typedef struct amqp_exchange_bind_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_exchange_bind_ok_t; + +#define AMQP_EXCHANGE_UNBIND_METHOD \ + ((amqp_method_number_t)0x00280028) /**< exchange.unbind method id @internal \ + 40, 40; 2621480 */ +/** exchange.unbind method fields */ +typedef struct amqp_exchange_unbind_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t destination; /**< destination */ + amqp_bytes_t source; /**< source */ + amqp_bytes_t routing_key; /**< routing-key */ + amqp_boolean_t nowait; /**< nowait */ + amqp_table_t arguments; /**< arguments */ +} amqp_exchange_unbind_t; + +#define AMQP_EXCHANGE_UNBIND_OK_METHOD \ + ((amqp_method_number_t)0x00280033) /**< exchange.unbind-ok method id \ + @internal 40, 51; 2621491 */ +/** exchange.unbind-ok method fields */ +typedef struct amqp_exchange_unbind_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_exchange_unbind_ok_t; + +#define AMQP_QUEUE_DECLARE_METHOD \ + ((amqp_method_number_t)0x0032000A) /**< queue.declare method id @internal \ + 50, 10; 3276810 */ +/** queue.declare method fields */ +typedef struct amqp_queue_declare_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_boolean_t passive; /**< passive */ + amqp_boolean_t durable; /**< durable */ + amqp_boolean_t exclusive; /**< exclusive */ + amqp_boolean_t auto_delete; /**< auto-delete */ + amqp_boolean_t nowait; /**< nowait */ + amqp_table_t arguments; /**< arguments */ +} amqp_queue_declare_t; + +#define AMQP_QUEUE_DECLARE_OK_METHOD \ + ((amqp_method_number_t)0x0032000B) /**< queue.declare-ok method id @internal \ + 50, 11; 3276811 */ +/** queue.declare-ok method fields */ +typedef struct amqp_queue_declare_ok_t_ { + amqp_bytes_t queue; /**< queue */ + uint32_t message_count; /**< message-count */ + uint32_t consumer_count; /**< consumer-count */ +} amqp_queue_declare_ok_t; + +#define AMQP_QUEUE_BIND_METHOD \ + ((amqp_method_number_t)0x00320014) /**< queue.bind method id @internal 50, \ + 20; 3276820 */ +/** queue.bind method fields */ +typedef struct amqp_queue_bind_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t routing_key; /**< routing-key */ + amqp_boolean_t nowait; /**< nowait */ + amqp_table_t arguments; /**< arguments */ +} amqp_queue_bind_t; + +#define AMQP_QUEUE_BIND_OK_METHOD \ + ((amqp_method_number_t)0x00320015) /**< queue.bind-ok method id @internal \ + 50, 21; 3276821 */ +/** queue.bind-ok method fields */ +typedef struct amqp_queue_bind_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_queue_bind_ok_t; + +#define AMQP_QUEUE_PURGE_METHOD \ + ((amqp_method_number_t)0x0032001E) /**< queue.purge method id @internal 50, \ + 30; 3276830 */ +/** queue.purge method fields */ +typedef struct amqp_queue_purge_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_boolean_t nowait; /**< nowait */ +} amqp_queue_purge_t; + +#define AMQP_QUEUE_PURGE_OK_METHOD \ + ((amqp_method_number_t)0x0032001F) /**< queue.purge-ok method id @internal \ + 50, 31; 3276831 */ +/** queue.purge-ok method fields */ +typedef struct amqp_queue_purge_ok_t_ { + uint32_t message_count; /**< message-count */ +} amqp_queue_purge_ok_t; + +#define AMQP_QUEUE_DELETE_METHOD \ + ((amqp_method_number_t)0x00320028) /**< queue.delete method id @internal 50, \ + 40; 3276840 */ +/** queue.delete method fields */ +typedef struct amqp_queue_delete_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_boolean_t if_unused; /**< if-unused */ + amqp_boolean_t if_empty; /**< if-empty */ + amqp_boolean_t nowait; /**< nowait */ +} amqp_queue_delete_t; + +#define AMQP_QUEUE_DELETE_OK_METHOD \ + ((amqp_method_number_t)0x00320029) /**< queue.delete-ok method id @internal \ + 50, 41; 3276841 */ +/** queue.delete-ok method fields */ +typedef struct amqp_queue_delete_ok_t_ { + uint32_t message_count; /**< message-count */ +} amqp_queue_delete_ok_t; + +#define AMQP_QUEUE_UNBIND_METHOD \ + ((amqp_method_number_t)0x00320032) /**< queue.unbind method id @internal 50, \ + 50; 3276850 */ +/** queue.unbind method fields */ +typedef struct amqp_queue_unbind_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t routing_key; /**< routing-key */ + amqp_table_t arguments; /**< arguments */ +} amqp_queue_unbind_t; + +#define AMQP_QUEUE_UNBIND_OK_METHOD \ + ((amqp_method_number_t)0x00320033) /**< queue.unbind-ok method id @internal \ + 50, 51; 3276851 */ +/** queue.unbind-ok method fields */ +typedef struct amqp_queue_unbind_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_queue_unbind_ok_t; + +#define AMQP_BASIC_QOS_METHOD \ + ((amqp_method_number_t)0x003C000A) /**< basic.qos method id @internal 60, \ + 10; 3932170 */ +/** basic.qos method fields */ +typedef struct amqp_basic_qos_t_ { + uint32_t prefetch_size; /**< prefetch-size */ + uint16_t prefetch_count; /**< prefetch-count */ + amqp_boolean_t global; /**< global */ +} amqp_basic_qos_t; + +#define AMQP_BASIC_QOS_OK_METHOD \ + ((amqp_method_number_t)0x003C000B) /**< basic.qos-ok method id @internal 60, \ + 11; 3932171 */ +/** basic.qos-ok method fields */ +typedef struct amqp_basic_qos_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_basic_qos_ok_t; + +#define AMQP_BASIC_CONSUME_METHOD \ + ((amqp_method_number_t)0x003C0014) /**< basic.consume method id @internal \ + 60, 20; 3932180 */ +/** basic.consume method fields */ +typedef struct amqp_basic_consume_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_bytes_t consumer_tag; /**< consumer-tag */ + amqp_boolean_t no_local; /**< no-local */ + amqp_boolean_t no_ack; /**< no-ack */ + amqp_boolean_t exclusive; /**< exclusive */ + amqp_boolean_t nowait; /**< nowait */ + amqp_table_t arguments; /**< arguments */ +} amqp_basic_consume_t; + +#define AMQP_BASIC_CONSUME_OK_METHOD \ + ((amqp_method_number_t)0x003C0015) /**< basic.consume-ok method id @internal \ + 60, 21; 3932181 */ +/** basic.consume-ok method fields */ +typedef struct amqp_basic_consume_ok_t_ { + amqp_bytes_t consumer_tag; /**< consumer-tag */ +} amqp_basic_consume_ok_t; + +#define AMQP_BASIC_CANCEL_METHOD \ + ((amqp_method_number_t)0x003C001E) /**< basic.cancel method id @internal 60, \ + 30; 3932190 */ +/** basic.cancel method fields */ +typedef struct amqp_basic_cancel_t_ { + amqp_bytes_t consumer_tag; /**< consumer-tag */ + amqp_boolean_t nowait; /**< nowait */ +} amqp_basic_cancel_t; + +#define AMQP_BASIC_CANCEL_OK_METHOD \ + ((amqp_method_number_t)0x003C001F) /**< basic.cancel-ok method id @internal \ + 60, 31; 3932191 */ +/** basic.cancel-ok method fields */ +typedef struct amqp_basic_cancel_ok_t_ { + amqp_bytes_t consumer_tag; /**< consumer-tag */ +} amqp_basic_cancel_ok_t; + +#define AMQP_BASIC_PUBLISH_METHOD \ + ((amqp_method_number_t)0x003C0028) /**< basic.publish method id @internal \ + 60, 40; 3932200 */ +/** basic.publish method fields */ +typedef struct amqp_basic_publish_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t routing_key; /**< routing-key */ + amqp_boolean_t mandatory; /**< mandatory */ + amqp_boolean_t immediate; /**< immediate */ +} amqp_basic_publish_t; + +#define AMQP_BASIC_RETURN_METHOD \ + ((amqp_method_number_t)0x003C0032) /**< basic.return method id @internal 60, \ + 50; 3932210 */ +/** basic.return method fields */ +typedef struct amqp_basic_return_t_ { + uint16_t reply_code; /**< reply-code */ + amqp_bytes_t reply_text; /**< reply-text */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t routing_key; /**< routing-key */ +} amqp_basic_return_t; + +#define AMQP_BASIC_DELIVER_METHOD \ + ((amqp_method_number_t)0x003C003C) /**< basic.deliver method id @internal \ + 60, 60; 3932220 */ +/** basic.deliver method fields */ +typedef struct amqp_basic_deliver_t_ { + amqp_bytes_t consumer_tag; /**< consumer-tag */ + uint64_t delivery_tag; /**< delivery-tag */ + amqp_boolean_t redelivered; /**< redelivered */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t routing_key; /**< routing-key */ +} amqp_basic_deliver_t; + +#define AMQP_BASIC_GET_METHOD \ + ((amqp_method_number_t)0x003C0046) /**< basic.get method id @internal 60, \ + 70; 3932230 */ +/** basic.get method fields */ +typedef struct amqp_basic_get_t_ { + uint16_t ticket; /**< ticket */ + amqp_bytes_t queue; /**< queue */ + amqp_boolean_t no_ack; /**< no-ack */ +} amqp_basic_get_t; + +#define AMQP_BASIC_GET_OK_METHOD \ + ((amqp_method_number_t)0x003C0047) /**< basic.get-ok method id @internal 60, \ + 71; 3932231 */ +/** basic.get-ok method fields */ +typedef struct amqp_basic_get_ok_t_ { + uint64_t delivery_tag; /**< delivery-tag */ + amqp_boolean_t redelivered; /**< redelivered */ + amqp_bytes_t exchange; /**< exchange */ + amqp_bytes_t routing_key; /**< routing-key */ + uint32_t message_count; /**< message-count */ +} amqp_basic_get_ok_t; + +#define AMQP_BASIC_GET_EMPTY_METHOD \ + ((amqp_method_number_t)0x003C0048) /**< basic.get-empty method id @internal \ + 60, 72; 3932232 */ +/** basic.get-empty method fields */ +typedef struct amqp_basic_get_empty_t_ { + amqp_bytes_t cluster_id; /**< cluster-id */ +} amqp_basic_get_empty_t; + +#define AMQP_BASIC_ACK_METHOD \ + ((amqp_method_number_t)0x003C0050) /**< basic.ack method id @internal 60, \ + 80; 3932240 */ +/** basic.ack method fields */ +typedef struct amqp_basic_ack_t_ { + uint64_t delivery_tag; /**< delivery-tag */ + amqp_boolean_t multiple; /**< multiple */ +} amqp_basic_ack_t; + +#define AMQP_BASIC_REJECT_METHOD \ + ((amqp_method_number_t)0x003C005A) /**< basic.reject method id @internal 60, \ + 90; 3932250 */ +/** basic.reject method fields */ +typedef struct amqp_basic_reject_t_ { + uint64_t delivery_tag; /**< delivery-tag */ + amqp_boolean_t requeue; /**< requeue */ +} amqp_basic_reject_t; + +#define AMQP_BASIC_RECOVER_ASYNC_METHOD \ + ((amqp_method_number_t)0x003C0064) /**< basic.recover-async method id \ + @internal 60, 100; 3932260 */ +/** basic.recover-async method fields */ +typedef struct amqp_basic_recover_async_t_ { + amqp_boolean_t requeue; /**< requeue */ +} amqp_basic_recover_async_t; + +#define AMQP_BASIC_RECOVER_METHOD \ + ((amqp_method_number_t)0x003C006E) /**< basic.recover method id @internal \ + 60, 110; 3932270 */ +/** basic.recover method fields */ +typedef struct amqp_basic_recover_t_ { + amqp_boolean_t requeue; /**< requeue */ +} amqp_basic_recover_t; + +#define AMQP_BASIC_RECOVER_OK_METHOD \ + ((amqp_method_number_t)0x003C006F) /**< basic.recover-ok method id @internal \ + 60, 111; 3932271 */ +/** basic.recover-ok method fields */ +typedef struct amqp_basic_recover_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_basic_recover_ok_t; + +#define AMQP_BASIC_NACK_METHOD \ + ((amqp_method_number_t)0x003C0078) /**< basic.nack method id @internal 60, \ + 120; 3932280 */ +/** basic.nack method fields */ +typedef struct amqp_basic_nack_t_ { + uint64_t delivery_tag; /**< delivery-tag */ + amqp_boolean_t multiple; /**< multiple */ + amqp_boolean_t requeue; /**< requeue */ +} amqp_basic_nack_t; + +#define AMQP_TX_SELECT_METHOD \ + ((amqp_method_number_t)0x005A000A) /**< tx.select method id @internal 90, \ + 10; 5898250 */ +/** tx.select method fields */ +typedef struct amqp_tx_select_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_select_t; + +#define AMQP_TX_SELECT_OK_METHOD \ + ((amqp_method_number_t)0x005A000B) /**< tx.select-ok method id @internal 90, \ + 11; 5898251 */ +/** tx.select-ok method fields */ +typedef struct amqp_tx_select_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_select_ok_t; + +#define AMQP_TX_COMMIT_METHOD \ + ((amqp_method_number_t)0x005A0014) /**< tx.commit method id @internal 90, \ + 20; 5898260 */ +/** tx.commit method fields */ +typedef struct amqp_tx_commit_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_commit_t; + +#define AMQP_TX_COMMIT_OK_METHOD \ + ((amqp_method_number_t)0x005A0015) /**< tx.commit-ok method id @internal 90, \ + 21; 5898261 */ +/** tx.commit-ok method fields */ +typedef struct amqp_tx_commit_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_commit_ok_t; + +#define AMQP_TX_ROLLBACK_METHOD \ + ((amqp_method_number_t)0x005A001E) /**< tx.rollback method id @internal 90, \ + 30; 5898270 */ +/** tx.rollback method fields */ +typedef struct amqp_tx_rollback_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_rollback_t; + +#define AMQP_TX_ROLLBACK_OK_METHOD \ + ((amqp_method_number_t)0x005A001F) /**< tx.rollback-ok method id @internal \ + 90, 31; 5898271 */ +/** tx.rollback-ok method fields */ +typedef struct amqp_tx_rollback_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_rollback_ok_t; + +#define AMQP_CONFIRM_SELECT_METHOD \ + ((amqp_method_number_t)0x0055000A) /**< confirm.select method id @internal \ + 85, 10; 5570570 */ +/** confirm.select method fields */ +typedef struct amqp_confirm_select_t_ { + amqp_boolean_t nowait; /**< nowait */ +} amqp_confirm_select_t; + +#define AMQP_CONFIRM_SELECT_OK_METHOD \ + ((amqp_method_number_t)0x0055000B) /**< confirm.select-ok method id \ + @internal 85, 11; 5570571 */ +/** confirm.select-ok method fields */ +typedef struct amqp_confirm_select_ok_t_ { + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_confirm_select_ok_t; + +/* Class property records. */ +#define AMQP_CONNECTION_CLASS (0x000A) /**< connection class id @internal 10 \ + */ +/** connection class properties */ +typedef struct amqp_connection_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_connection_properties_t; + +#define AMQP_CHANNEL_CLASS (0x0014) /**< channel class id @internal 20 */ +/** channel class properties */ +typedef struct amqp_channel_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_channel_properties_t; + +#define AMQP_ACCESS_CLASS (0x001E) /**< access class id @internal 30 */ +/** access class properties */ +typedef struct amqp_access_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_access_properties_t; + +#define AMQP_EXCHANGE_CLASS (0x0028) /**< exchange class id @internal 40 */ +/** exchange class properties */ +typedef struct amqp_exchange_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_exchange_properties_t; + +#define AMQP_QUEUE_CLASS (0x0032) /**< queue class id @internal 50 */ +/** queue class properties */ +typedef struct amqp_queue_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_queue_properties_t; + +#define AMQP_BASIC_CLASS (0x003C) /**< basic class id @internal 60 */ +#define AMQP_BASIC_CONTENT_TYPE_FLAG \ + (1 << 15) /**< basic.content-type property flag */ +#define AMQP_BASIC_CONTENT_ENCODING_FLAG \ + (1 << 14) /**< basic.content-encoding property flag */ +#define AMQP_BASIC_HEADERS_FLAG (1 << 13) /**< basic.headers property flag */ +#define AMQP_BASIC_DELIVERY_MODE_FLAG \ + (1 << 12) /**< basic.delivery-mode property flag */ +#define AMQP_BASIC_PRIORITY_FLAG (1 << 11) /**< basic.priority property flag \ + */ +#define AMQP_BASIC_CORRELATION_ID_FLAG \ + (1 << 10) /**< basic.correlation-id property flag */ +#define AMQP_BASIC_REPLY_TO_FLAG (1 << 9) /**< basic.reply-to property flag */ +#define AMQP_BASIC_EXPIRATION_FLAG \ + (1 << 8) /**< basic.expiration property flag */ +#define AMQP_BASIC_MESSAGE_ID_FLAG \ + (1 << 7) /**< basic.message-id property flag */ +#define AMQP_BASIC_TIMESTAMP_FLAG (1 << 6) /**< basic.timestamp property flag \ + */ +#define AMQP_BASIC_TYPE_FLAG (1 << 5) /**< basic.type property flag */ +#define AMQP_BASIC_USER_ID_FLAG (1 << 4) /**< basic.user-id property flag */ +#define AMQP_BASIC_APP_ID_FLAG (1 << 3) /**< basic.app-id property flag */ +#define AMQP_BASIC_CLUSTER_ID_FLAG \ + (1 << 2) /**< basic.cluster-id property flag */ +/** basic class properties */ +typedef struct amqp_basic_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + amqp_bytes_t content_type; /**< content-type */ + amqp_bytes_t content_encoding; /**< content-encoding */ + amqp_table_t headers; /**< headers */ + uint8_t delivery_mode; /**< delivery-mode */ + uint8_t priority; /**< priority */ + amqp_bytes_t correlation_id; /**< correlation-id */ + amqp_bytes_t reply_to; /**< reply-to */ + amqp_bytes_t expiration; /**< expiration */ + amqp_bytes_t message_id; /**< message-id */ + uint64_t timestamp; /**< timestamp */ + amqp_bytes_t type; /**< type */ + amqp_bytes_t user_id; /**< user-id */ + amqp_bytes_t app_id; /**< app-id */ + amqp_bytes_t cluster_id; /**< cluster-id */ +} amqp_basic_properties_t; + +#define AMQP_TX_CLASS (0x005A) /**< tx class id @internal 90 */ +/** tx class properties */ +typedef struct amqp_tx_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_tx_properties_t; + +#define AMQP_CONFIRM_CLASS (0x0055) /**< confirm class id @internal 85 */ +/** confirm class properties */ +typedef struct amqp_confirm_properties_t_ { + amqp_flags_t _flags; /**< bit-mask of set fields */ + char dummy; /**< Dummy field to avoid empty struct */ +} amqp_confirm_properties_t; + +/* API functions for methods */ + +/** + * amqp_connection_update_secret + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] new_secret new_secret + * @param [in] reason reason + * @returns amqp_connection_update_secret_ok_t + */ +AMQP_EXPORT +amqp_connection_update_secret_ok_t *AMQP_CALL amqp_connection_update_secret( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t new_secret, amqp_bytes_t reason); +/** + * amqp_channel_open + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_channel_open_ok_t + */ +AMQP_EXPORT +amqp_channel_open_ok_t *AMQP_CALL + amqp_channel_open(amqp_connection_state_t state, amqp_channel_t channel); +/** + * amqp_channel_flow + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] active active + * @returns amqp_channel_flow_ok_t + */ +AMQP_EXPORT +amqp_channel_flow_ok_t *AMQP_CALL + amqp_channel_flow(amqp_connection_state_t state, amqp_channel_t channel, + amqp_boolean_t active); +/** + * amqp_exchange_declare + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] exchange exchange + * @param [in] type type + * @param [in] passive passive + * @param [in] durable durable + * @param [in] auto_delete auto_delete + * @param [in] internal internal + * @param [in] arguments arguments + * @returns amqp_exchange_declare_ok_t + */ +AMQP_EXPORT +amqp_exchange_declare_ok_t *AMQP_CALL amqp_exchange_declare( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t exchange, amqp_bytes_t type, amqp_boolean_t passive, + amqp_boolean_t durable, amqp_boolean_t auto_delete, amqp_boolean_t internal, + amqp_table_t arguments); +/** + * amqp_exchange_delete + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] exchange exchange + * @param [in] if_unused if_unused + * @returns amqp_exchange_delete_ok_t + */ +AMQP_EXPORT +amqp_exchange_delete_ok_t *AMQP_CALL + amqp_exchange_delete(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t exchange, amqp_boolean_t if_unused); +/** + * amqp_exchange_bind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] destination destination + * @param [in] source source + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_exchange_bind_ok_t + */ +AMQP_EXPORT +amqp_exchange_bind_ok_t *AMQP_CALL + amqp_exchange_bind(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t destination, amqp_bytes_t source, + amqp_bytes_t routing_key, amqp_table_t arguments); +/** + * amqp_exchange_unbind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] destination destination + * @param [in] source source + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_exchange_unbind_ok_t + */ +AMQP_EXPORT +amqp_exchange_unbind_ok_t *AMQP_CALL + amqp_exchange_unbind(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t destination, amqp_bytes_t source, + amqp_bytes_t routing_key, amqp_table_t arguments); +/** + * amqp_queue_declare + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] passive passive + * @param [in] durable durable + * @param [in] exclusive exclusive + * @param [in] auto_delete auto_delete + * @param [in] arguments arguments + * @returns amqp_queue_declare_ok_t + */ +AMQP_EXPORT +amqp_queue_declare_ok_t *AMQP_CALL amqp_queue_declare( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_boolean_t passive, amqp_boolean_t durable, amqp_boolean_t exclusive, + amqp_boolean_t auto_delete, amqp_table_t arguments); +/** + * amqp_queue_bind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] exchange exchange + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_queue_bind_ok_t + */ +AMQP_EXPORT +amqp_queue_bind_ok_t *AMQP_CALL amqp_queue_bind( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments); +/** + * amqp_queue_purge + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @returns amqp_queue_purge_ok_t + */ +AMQP_EXPORT +amqp_queue_purge_ok_t *AMQP_CALL amqp_queue_purge(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_bytes_t queue); +/** + * amqp_queue_delete + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] if_unused if_unused + * @param [in] if_empty if_empty + * @returns amqp_queue_delete_ok_t + */ +AMQP_EXPORT +amqp_queue_delete_ok_t *AMQP_CALL amqp_queue_delete( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_boolean_t if_unused, amqp_boolean_t if_empty); +/** + * amqp_queue_unbind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] exchange exchange + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_queue_unbind_ok_t + */ +AMQP_EXPORT +amqp_queue_unbind_ok_t *AMQP_CALL amqp_queue_unbind( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments); +/** + * amqp_basic_qos + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] prefetch_size prefetch_size + * @param [in] prefetch_count prefetch_count + * @param [in] global global + * @returns amqp_basic_qos_ok_t + */ +AMQP_EXPORT +amqp_basic_qos_ok_t *AMQP_CALL amqp_basic_qos(amqp_connection_state_t state, + amqp_channel_t channel, + uint32_t prefetch_size, + uint16_t prefetch_count, + amqp_boolean_t global); +/** + * amqp_basic_consume + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] consumer_tag consumer_tag + * @param [in] no_local no_local + * @param [in] no_ack no_ack + * @param [in] exclusive exclusive + * @param [in] arguments arguments + * @returns amqp_basic_consume_ok_t + */ +AMQP_EXPORT +amqp_basic_consume_ok_t *AMQP_CALL amqp_basic_consume( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_bytes_t consumer_tag, amqp_boolean_t no_local, amqp_boolean_t no_ack, + amqp_boolean_t exclusive, amqp_table_t arguments); +/** + * amqp_basic_cancel + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] consumer_tag consumer_tag + * @returns amqp_basic_cancel_ok_t + */ +AMQP_EXPORT +amqp_basic_cancel_ok_t *AMQP_CALL + amqp_basic_cancel(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t consumer_tag); +/** + * amqp_basic_recover + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] requeue requeue + * @returns amqp_basic_recover_ok_t + */ +AMQP_EXPORT +amqp_basic_recover_ok_t *AMQP_CALL + amqp_basic_recover(amqp_connection_state_t state, amqp_channel_t channel, + amqp_boolean_t requeue); +/** + * amqp_tx_select + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_tx_select_ok_t + */ +AMQP_EXPORT +amqp_tx_select_ok_t *AMQP_CALL amqp_tx_select(amqp_connection_state_t state, + amqp_channel_t channel); +/** + * amqp_tx_commit + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_tx_commit_ok_t + */ +AMQP_EXPORT +amqp_tx_commit_ok_t *AMQP_CALL amqp_tx_commit(amqp_connection_state_t state, + amqp_channel_t channel); +/** + * amqp_tx_rollback + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_tx_rollback_ok_t + */ +AMQP_EXPORT +amqp_tx_rollback_ok_t *AMQP_CALL amqp_tx_rollback(amqp_connection_state_t state, + amqp_channel_t channel); +/** + * amqp_confirm_select + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_confirm_select_ok_t + */ +AMQP_EXPORT +amqp_confirm_select_ok_t *AMQP_CALL + amqp_confirm_select(amqp_connection_state_t state, amqp_channel_t channel); + +AMQP_END_DECLS + +#endif /* RABBITMQ_C_FRAMING_H */ diff --git a/include/rabbitmq-c/ssl_socket.h b/include/rabbitmq-c/ssl_socket.h new file mode 100644 index 0000000..77ed1ef --- /dev/null +++ b/include/rabbitmq-c/ssl_socket.h @@ -0,0 +1,305 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/** \file */ + +#ifndef RABBITMQ_C_SSL_SOCKET_H +#define RABBITMQ_C_SSL_SOCKET_H + +#include +#include + +AMQP_BEGIN_DECLS + +/** + * Create a new SSL/TLS socket object. + * + * The returned socket object is owned by the \ref amqp_connection_state_t + * object and will be destroyed when the state object is destroyed or a new + * socket object is created. + * + * If the socket object creation fails, the \ref amqp_connection_state_t object + * will not be changed. + * + * The object returned by this function can be retrieved from the + * amqp_connection_state_t object later using the amqp_get_socket() function. + * + * Calling this function may result in the underlying SSL library being + * initialized. + * \sa amqp_set_initialize_ssl_library() + * + * \param [in,out] state The connection object that owns the SSL/TLS socket + * \return A new socket object or NULL if an error occurred. + * + * \since v0.4.0 + */ +AMQP_EXPORT +amqp_socket_t *AMQP_CALL amqp_ssl_socket_new(amqp_connection_state_t state); + +/** + * Get the internal OpenSSL context. Caveat emptor. + * + * \param [in,out] self An SSL/TLS socket object. + * + * \return A pointer to the internal OpenSSL context. This should be cast to + * SSL_CTX*. + * + * \since v0.9.0 + */ +AMQP_EXPORT +void *AMQP_CALL amqp_ssl_socket_get_context(amqp_socket_t *self); + +/** + * Enable loading of the CA certificates from the default location. + * + * \param [in,out] self An SSL/TLS socket object. + * + * \return \ref AMQP_STATUS_OK on success an \ref amqp_status_enum value on + * failure. + * + * \since v0.14.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_ssl_socket_enable_default_verify_paths(amqp_socket_t *self); + +/** + * Set the CA certificate. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] cacert Path to the CA cert file in PEM format. + * + * \return \ref AMQP_STATUS_OK on success an \ref amqp_status_enum value on + * failure. + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_ssl_socket_set_cacert(amqp_socket_t *self, + const char *cacert); + +/** + * Set the password of key in PEM format. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] passwd The password of key in PEM format. + * + * \since v0.11.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_ssl_socket_set_key_passwd(amqp_socket_t *self, + const char *passwd); + +/** + * Set the client key. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] cert Path to the client certificate in PEM foramt. + * \param [in] key Path to the client key in PEM format. + * + * \return \ref AMQP_STATUS_OK on success an \ref amqp_status_enum value on + * failure. + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_ssl_socket_set_key(amqp_socket_t *self, const char *cert, + const char *key); + +/** + * Set the client key use the engine. + * + * This function requires amqp_set_ssl_engine() has been called. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] cert Path to the client certificate in PEM foramt. + * \param [in] the key ID. + * + * \return \ref AMQP_STATUS_OK on success an \ref amqp_status_enum value on + * failure. May return \ref AMQP_STATUS_SSL_UNIMPLEMENTED if OpenSSL does + * not support the ENGINE API. + * + * \since v0.11.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_ssl_socket_set_key_engine(amqp_socket_t *self, + const char *cert, const char *key); + +/** + * Set the client key from a buffer. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] cert Path to the client certificate in PEM foramt. + * \param [in] key A buffer containing client key in PEM format. + * \param [in] n The length of the buffer. + * + * \return \ref AMQP_STATUS_OK on success an \ref amqp_status_enum value on + * failure. + * + * \since v0.4.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_ssl_socket_set_key_buffer(amqp_socket_t *self, + const char *cert, const void *key, + size_t n); + +/** + * Enable or disable peer verification. + * + * \deprecated use \amqp_ssl_socket_set_verify_peer and + * \amqp_ssl_socket_set_verify_hostname instead. + * + * If peer verification is enabled then the common name in the server + * certificate must match the server name. Peer verification is enabled by + * default. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] verify Enable or disable peer verification. + * + * \since v0.4.0 + */ +AMQP_DEPRECATED_EXPORT void AMQP_CALL + amqp_ssl_socket_set_verify(amqp_socket_t *self, amqp_boolean_t verify); + +/** + * Enable or disable peer verification. + * + * Peer verification validates the certificate chain that is sent by the broker. + * Hostname validation is controlled by \amqp_ssl_socket_set_verify_peer. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] verify enable or disable peer validation + * + * \since v0.8.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_ssl_socket_set_verify_peer(amqp_socket_t *self, + amqp_boolean_t verify); + +/** + * Enable or disable hostname verification. + * + * Hostname verification checks the broker cert for a CN or SAN that matches the + * hostname that amqp_socket_open() is presented. Peer verification is + * controlled by \amqp_ssl_socket_set_verify_peer + * + * \since v0.8.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_ssl_socket_set_verify_hostname(amqp_socket_t *self, + amqp_boolean_t verify); + +typedef enum { + AMQP_TLSv1 = 1, + AMQP_TLSv1_1 = 2, + AMQP_TLSv1_2 = 3, + AMQP_TLSv1_3 = 4, + AMQP_TLSvLATEST = 0xFFFF +} amqp_tls_version_t; + +/** + * Set min and max TLS versions. + * + * Set the oldest and newest acceptable TLS versions that are acceptable when + * connecting to the broker. Set min == max to restrict to just that + * version. + * + * As of v0.14.0 the defaults are TLS v1.2 and TLS v1.3. TLS v1.1 and lower are + * no longer supported. + * + * \param [in,out] self An SSL/TLS socket object. + * \param [in] min the minimum acceptable TLS version + * \param [in] max the maxmium acceptable TLS version + * \returns AMQP_STATUS_OK on success, AMQP_STATUS_UNSUPPORTED if OpenSSL does + * not support the requested TLS version, AMQP_STATUS_INVALID_PARAMETER if an + * invalid combination of parameters is passed. + * + * \since v0.8.0 + */ +AMQP_EXPORT +int AMQP_CALL amqp_ssl_socket_set_ssl_versions(amqp_socket_t *self, + amqp_tls_version_t min, + amqp_tls_version_t max); + +/** + * Sets whether rabbitmq-c will initialize OpenSSL. + * + * \deprecated Since v0.13.0 this is a no-op. OpenSSL automatically manages + * library initialization and uninitialization. + * + * OpenSSL requires a one-time initialization across a whole program, this sets + * whether or not rabbitmq-c will initialize the SSL library when the first call + * to amqp_ssl_socket_new() is made. You should call this function with + * do_init = 0 if the underlying SSL library is initialized somewhere else + * the program. + * + * Failing to initialize or double initialization of the SSL library will + * result in undefined behavior + * + * By default rabbitmq-c will initialize the underlying SSL library. + * + * NOTE: calling this function after the first socket has been opened with + * amqp_open_socket() will not have any effect. + * + * \param [in] do_initialize If 0 rabbitmq-c will not initialize the SSL + * library, otherwise rabbitmq-c will initialize the + * SSL library + * + * \since v0.4.0 + */ +AMQP_DEPRECATED_EXPORT +void AMQP_CALL amqp_set_initialize_ssl_library(amqp_boolean_t do_initialize); + +/** + * Initialize the underlying SSL/TLS library. + * + * \deprecated Since v0.13.0 this is a no-op. OpenSSL automatically manages + * library initialization and uninitialization. + * + * The OpenSSL library requires a one-time initialization across the whole + * program. + * + * This function unconditionally initializes OpenSSL so that rabbitmq-c may + * use it. + * + * This function is thread-safe, and may be called more than once. + * + * \return AMQP_STATUS_OK on success. + * + * \since v0.9.0 + */ +AMQP_DEPRECATED_EXPORT +int AMQP_CALL amqp_initialize_ssl_library(void); + +/** + * Set the engine for underlying SSL/TLS library. + * + * This function is thread-safe, and may be called more than once. + * + * This function requires amqp_initialize_ssl_library() or amqp_ssl_socket_new() + * has been called. + * + * \param [in] engine the engine ID + * \return AMQP_STATUS_OK on success. May return \ref AMQP_STATUS_SSL_UNIMPLEMENTED + * if OpenSSL does not support the ENGINE API. + * + * \since v0.11.0 + */ +AMQP_EXPORT +int amqp_set_ssl_engine(const char *engine); + +/** + * Uninitialize the underlying SSL/TLS library. + * + * \deprecated Since v0.13.0 this is a no-op. OpenSSL automatically manages + * library initialization and uninitialization. + * + * \return AMQP_STATUS_OK on success. + * + * \since v0.9.0 + */ +AMQP_DEPRECATED_EXPORT +int AMQP_CALL amqp_uninitialize_ssl_library(void); + +AMQP_END_DECLS + +#endif /* RABBITMQ_C_SSL_SOCKET_H */ diff --git a/include/rabbitmq-c/tcp_socket.h b/include/rabbitmq-c/tcp_socket.h new file mode 100644 index 0000000..10f0408 --- /dev/null +++ b/include/rabbitmq-c/tcp_socket.h @@ -0,0 +1,47 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/** \file */ + +/** + * A TCP socket connection. + */ + +#ifndef RABBITMQ_C_TCP_SOCKET_H +#define RABBITMQ_C_TCP_SOCKET_H + +#include +#include + +AMQP_BEGIN_DECLS + +/** + * Create a new TCP socket. + * + * Call amqp_connection_close() to release socket resources. + * + * \return A new socket object or NULL if an error occurred. + * + * \since v0.4.0 + */ +AMQP_EXPORT +amqp_socket_t *AMQP_CALL amqp_tcp_socket_new(amqp_connection_state_t state); + +/** + * Assign an open file descriptor to a socket object. + * + * This function must not be used in conjunction with amqp_socket_open(), i.e. + * the socket connection should already be open(2) when this function is + * called. + * + * \param [in,out] self A TCP socket object. + * \param [in] sockfd An open socket descriptor. + * + * \since v0.4.0 + */ +AMQP_EXPORT +void AMQP_CALL amqp_tcp_socket_set_sockfd(amqp_socket_t *self, int sockfd); + +AMQP_END_DECLS + +#endif /* RABBITMQ_C_TCP_SOCKET_H */ diff --git a/librabbitmq.pc.in b/librabbitmq.pc.in new file mode 100644 index 0000000..711d01d --- /dev/null +++ b/librabbitmq.pc.in @@ -0,0 +1,13 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: rabbitmq-c +Description: An AMQP 0-9-1 client library +Version: @RMQ_VERSION@ +URL: https://github.com/alanxz/rabbitmq-c +Requires.private: @requires_private@ +Libs: -L${libdir} -lrabbitmq +Libs.private: @libs_private@ +Cflags: -I${includedir} diff --git a/librabbitmq/CMakeLists.txt b/librabbitmq/CMakeLists.txt new file mode 100644 index 0000000..c7983ae --- /dev/null +++ b/librabbitmq/CMakeLists.txt @@ -0,0 +1,190 @@ +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +if (ENABLE_SSL_SUPPORT) + SET(AMQP_SSL_SOCKET_SHIM_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../include/amqp_ssl_socket.h) + set(AMQP_SSL_SOCKET_H_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../include/rabbitmq-c/ssl_socket.h) + + set(AMQP_SSL_SRCS + amqp_openssl.c + amqp_openssl_bio.c + amqp_openssl_bio.h + ) + set(AMQP_SSL_LIBS OpenSSL::SSL) + if (APPLE) + # Apple has deprecated OpenSSL in 10.7+. This disables that warning. + set_source_files_properties(${AMQP_SSL_SRCS} + PROPERTIES COMPILE_FLAGS -Wno-deprecated-declarations) + endif() + + if (WIN32 AND NOT CMAKE_USE_PTHREADS_INIT) + set(AMQP_SSL_SRCS ${AMQP_SSL_SRCS} win32/threads.h win32/threads.c) + set(SSL_INCLUDE_DIRS win32) + else() + set(AMQP_SSL_SRCS ${AMQP_SSL_SRCS} unix/threads.h) + set(SSL_INCLUDE_DIRS unix) + endif() +endif() + +set(PUBLIC_INCLUDE_DIRS + $ + $ + $ +) + +set(PRIVATE_INCLUDE_DIRS + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${SSL_INCLUDE_DIRS} +) + +set(RMQ_SOURCES + ../include/amqp.h + ../include/amqp_framing.h + ${AMQP_SSL_SOCKET_SHIM_PATH} + ../include/amqp_tcp_socket.h + ../include/rabbitmq-c/amqp.h + ../include/rabbitmq-c/framing.h + ${AMQP_SSL_SOCKET_H_PATH} + ../include/rabbitmq-c/tcp_socket.h + amqp_api.c + amqp_connection.c + amqp_consumer.c + amqp_framing.c + amqp_mem.c + ${AMQP_SSL_SRCS} + amqp_private.h + amqp_socket.c + amqp_socket.h + amqp_table.c + amqp_table.h + amqp_tcp_socket.c + amqp_time.c + amqp_time.h + amqp_url.c +) + +set(RMQ_LIBRARIES ${AMQP_SSL_LIBS} ${SOCKET_LIBRARIES} ${LIBRT} ${CMAKE_THREAD_LIBS_INIT}) + +if(BUILD_SHARED_LIBS) + if (NOT APPLE) + set(CMAKE_INSTALL_RPATH $ORIGIN) + endif() + + add_library(rabbitmq SHARED) + set(RMQ_GEN_EXPORT_TARGET rabbitmq) + + target_sources(rabbitmq PRIVATE ${RMQ_SOURCES}) + + target_include_directories(rabbitmq + PUBLIC ${PUBLIC_INCLUDE_DIRS} + PRIVATE ${PRIVATE_INCLUDE_DIRS} + ) + + target_compile_definitions(rabbitmq PRIVATE -DHAVE_CONFIG_H) + + target_link_libraries(rabbitmq PRIVATE ${RMQ_LIBRARIES}) + + set_target_properties(rabbitmq PROPERTIES + VERSION ${RMQ_VERSION} + SOVERSION ${RMQ_SOVERSION} + ) + + if (APPLE) + set_target_properties(rabbitmq PROPERTIES + MACHO_CURRENT_VERSION ${RMQ_SOVERSION}.${RMQ_SOVERSION_AGE}.${RMQ_SOVERSION_REVISION} + MACHO_COMPATIBILITY_VERSION ${RMQ_SOVERSION} + ) + endif() + + if (WIN32) + set_target_properties(rabbitmq PROPERTIES OUTPUT_NAME rabbitmq.${RMQ_SOVERSION}) + endif() + + install(TARGETS rabbitmq EXPORT "${targets_export_name}" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT rabbitmq-c-runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT rabbitmq-c-runtime + NAMELINK_COMPONENT rabbitmq-c-development + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT rabbitmq-c-development + ) + + add_library(rabbitmq::rabbitmq ALIAS rabbitmq) +endif() + +if(BUILD_STATIC_LIBS) + add_library(rabbitmq-static STATIC) + + target_sources(rabbitmq-static PRIVATE ${RMQ_SOURCES}) + if (NOT BUILD_SHARED_LIBS) + set(RMQ_GEN_EXPORT_TARGET rabbitmq-static) + endif() + + target_include_directories(rabbitmq-static + PUBLIC ${PUBLIC_INCLUDE_DIRS} + PRIVATE ${PRIVATE_INCLUDE_DIRS} + ) + + target_compile_definitions(rabbitmq-static + PUBLIC -DAMQP_STATIC + PRIVATE -DHAVE_CONFIG_H + ) + + target_link_libraries(rabbitmq-static PRIVATE ${RMQ_LIBRARIES}) + + set_target_properties(rabbitmq-static PROPERTIES + VERSION ${RMQ_VERSION} + SOVERSION ${RMQ_SOVERSION} + ) + + if (APPLE) + set_target_properties(rabbitmq-static PROPERTIES + MACHO_CURRENT_VERSION ${RMQ_SOVERSION}.${RMQ_SOVERSION_AGE}.${RMQ_SOVERSION_REVISION} + MACHO_COMPATIBILITY_VERSION ${RMQ_SOVERSION} + ) + endif() + + if (WIN32) + set_target_properties(rabbitmq-static PROPERTIES OUTPUT_NAME librabbitmq.${RMQ_SOVERSION}) + else() + set_target_properties(rabbitmq-static PROPERTIES OUTPUT_NAME rabbitmq) + endif() + + if(INSTALL_STATIC_LIBS) + install(TARGETS rabbitmq-static EXPORT "${targets_export_name}" + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT rabbitmq-c-development + ) + endif() + + add_library(rabbitmq::rabbitmq-static ALIAS rabbitmq-static) +endif() + +include(GenerateExportHeader) +generate_export_header(${RMQ_GEN_EXPORT_TARGET} + BASE_NAME AMQP + EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/../include/rabbitmq-c/export.h + STATIC_DEFINE AMQP_STATIC + INCLUDE_GUARD_NAME RABBITMQ_C_EXPORT_H +) + +install(FILES + ../include/amqp.h + ../include/amqp_framing.h + ../include/amqp_tcp_socket.h + ${AMQP_SSL_SOCKET_SHIM_PATH} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT rabbitmq-c-development +) + +install(FILES + ../include/rabbitmq-c/amqp.h + ../include/rabbitmq-c/framing.h + ../include/rabbitmq-c/tcp_socket.h + ${AMQP_SSL_SOCKET_H_PATH} + ${CMAKE_CURRENT_BINARY_DIR}/../include/rabbitmq-c/export.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rabbitmq-c + COMPONENT rabbitmq-c-development +) diff --git a/librabbitmq/amqp_api.c b/librabbitmq/amqp_api.c new file mode 100644 index 0000000..7b70a19 --- /dev/null +++ b/librabbitmq/amqp_api.c @@ -0,0 +1,421 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef _MSC_VER +/* MSVC complains about sprintf being deprecated in favor of sprintf_s */ +#define _CRT_SECURE_NO_WARNINGS +/* MSVC complains about strdup being deprecated in favor of _strdup */ +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#include "amqp_private.h" +#include "amqp_time.h" +#include +#include +#include +#include +#include + +#define ERROR_MASK (0x00FF) +#define ERROR_CATEGORY_MASK (0xFF00) + +enum error_category_enum_ { EC_base = 0, EC_tcp = 1, EC_ssl = 2 }; + +static const char *base_error_strings[] = { + /* AMQP_STATUS_OK 0x0 */ + "operation completed successfully", + /* AMQP_STATUS_NO_MEMORY -0x0001 */ + "could not allocate memory", + /* AMQP_STATUS_BAD_AQMP_DATA -0x0002 */ + "invalid AMQP data", + /* AMQP_STATUS_UNKNOWN_CLASS -0x0003 */ + "unknown AMQP class id", + /* AMQP_STATUS_UNKNOWN_METHOD -0x0004 */ + "unknown AMQP method id", + /* AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED -0x0005 */ + "hostname lookup failed", + /* AMQP_STATUS_INCOMPATIBLE_AMQP_VERSION -0x0006 */ + "incompatible AMQP version", + /* AMQP_STATUS_CONNECTION_CLOSED -0x0007 */ + "connection closed unexpectedly", + /* AMQP_STATUS_BAD_AMQP_URL -0x0008 */ + "could not parse AMQP URL", + /* AMQP_STATUS_SOCKET_ERROR -0x0009 */ + "a socket error occurred", + /* AMQP_STATUS_INVALID_PARAMETER -0x000A */ + "invalid parameter", + /* AMQP_STATUS_TABLE_TOO_BIG -0x000B */ + "table too large for buffer", + /* AMQP_STATUS_WRONG_METHOD -0x000C */ + "unexpected method received", + /* AMQP_STATUS_TIMEOUT -0x000D */ + "request timed out", + /* AMQP_STATUS_TIMER_FAILED -0x000E */ + "system timer has failed", + /* AMQP_STATUS_HEARTBEAT_TIMEOUT -0x000F */ + "heartbeat timeout, connection closed", + /* AMQP_STATUS_UNEXPECTED STATE -0x0010 */ + "unexpected protocol state", + /* AMQP_STATUS_SOCKET_CLOSED -0x0011 */ + "socket is closed", + /* AMQP_STATUS_SOCKET_INUSE -0x0012 */ + "socket already open", + /* AMQP_STATUS_BROKER_UNSUPPORTED_SASL_METHOD -0x00013 */ + "unsupported sasl method requested", + /* AMQP_STATUS_UNSUPPORTED -0x0014 */ + "parameter value is unsupported"}; + +static const char *tcp_error_strings[] = { + /* AMQP_STATUS_TCP_ERROR -0x0100 */ + "a socket error occurred", + /* AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR -0x0101 */ + "socket library initialization failed"}; + +static const char *ssl_error_strings[] = { + /* AMQP_STATUS_SSL_ERROR -0x0200 */ + "a SSL error occurred", + /* AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED -0x0201 */ + "SSL hostname verification failed", + /* AMQP_STATUS_SSL_PEER_VERIFY_FAILED -0x0202 */ + "SSL peer cert verification failed", + /* AMQP_STATUS_SSL_CONNECTION_FAILED -0x0203 */ + "SSL handshake failed", + /* AMQP_STATUS_SSL_SET_ENGINE_FAILED -0x0204 */ + "SSL setting engine failed", + /* AMQP_STATUS_SSL_UNIMPLEMENTED -0x0204 */ + "SSL API is not implemented"}; + +static const char *unknown_error_string = "(unknown error)"; + +const char *amqp_error_string2(int code) { + const char *error_string; + size_t category = (((-code) & ERROR_CATEGORY_MASK) >> 8); + size_t error = (-code) & ERROR_MASK; + + switch (category) { + case EC_base: + if (error < (sizeof(base_error_strings) / sizeof(char *))) { + error_string = base_error_strings[error]; + } else { + error_string = unknown_error_string; + } + break; + + case EC_tcp: + if (error < (sizeof(tcp_error_strings) / sizeof(char *))) { + error_string = tcp_error_strings[error]; + } else { + error_string = unknown_error_string; + } + break; + + case EC_ssl: + if (error < (sizeof(ssl_error_strings) / sizeof(char *))) { + error_string = ssl_error_strings[error]; + } else { + error_string = unknown_error_string; + } + + break; + + default: + error_string = unknown_error_string; + break; + } + + return error_string; +} + +char *amqp_error_string(int code) { + /* Previously sometimes clients had to flip the sign on a return value from a + * function to get the correct error code. Now, all error codes are negative. + * To keep people's legacy code running correctly, we map all error codes to + * negative values. + * + * This is only done with this deprecated function. + */ + if (code > 0) { + code = -code; + } + return strdup(amqp_error_string2(code)); +} + +void amqp_abort(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + abort(); +} + +const amqp_bytes_t amqp_empty_bytes = {0, NULL}; +const amqp_table_t amqp_empty_table = {0, NULL}; +const amqp_array_t amqp_empty_array = {0, NULL}; + +int amqp_basic_publish(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t exchange, amqp_bytes_t routing_key, + amqp_boolean_t mandatory, amqp_boolean_t immediate, + amqp_basic_properties_t const *properties, + amqp_bytes_t body) { + amqp_frame_t f; + size_t body_offset; + size_t usable_body_payload_size = + state->frame_max - (HEADER_SIZE + FOOTER_SIZE); + int res; + int flagz; + + amqp_basic_publish_t m; + amqp_basic_properties_t default_properties; + + m.exchange = exchange; + m.routing_key = routing_key; + m.mandatory = mandatory; + m.immediate = immediate; + m.ticket = 0; + + /* TODO(alanxz): this heartbeat check is happening in the wrong place, it + * should really be done in amqp_try_send/writev */ + res = amqp_time_has_past(state->next_recv_heartbeat); + if (AMQP_STATUS_TIMER_FAILURE == res) { + return res; + } else if (AMQP_STATUS_TIMEOUT == res) { + res = amqp_try_recv(state); + if (AMQP_STATUS_TIMEOUT == res) { + return AMQP_STATUS_HEARTBEAT_TIMEOUT; + } else if (AMQP_STATUS_OK != res) { + return res; + } + } + + res = amqp_send_method_inner(state, channel, AMQP_BASIC_PUBLISH_METHOD, &m, + AMQP_SF_MORE, amqp_time_infinite()); + if (res < 0) { + return res; + } + + if (properties == NULL) { + memset(&default_properties, 0, sizeof(default_properties)); + properties = &default_properties; + } + + f.frame_type = AMQP_FRAME_HEADER; + f.channel = channel; + f.payload.properties.class_id = AMQP_BASIC_CLASS; + f.payload.properties.body_size = body.len; + f.payload.properties.decoded = (void *)properties; + + if (body.len > 0) { + flagz = AMQP_SF_MORE; + } else { + flagz = AMQP_SF_NONE; + } + res = amqp_send_frame_inner(state, &f, flagz, amqp_time_infinite()); + if (res < 0) { + return res; + } + + body_offset = 0; + while (body_offset < body.len) { + size_t remaining = body.len - body_offset; + + if (remaining == 0) { + break; + } + + f.frame_type = AMQP_FRAME_BODY; + f.channel = channel; + f.payload.body_fragment.bytes = amqp_offset(body.bytes, body_offset); + if (remaining >= usable_body_payload_size) { + f.payload.body_fragment.len = usable_body_payload_size; + flagz = AMQP_SF_MORE; + } else { + f.payload.body_fragment.len = remaining; + flagz = AMQP_SF_NONE; + } + + body_offset += f.payload.body_fragment.len; + res = amqp_send_frame_inner(state, &f, flagz, amqp_time_infinite()); + if (res < 0) { + return res; + } + } + + return AMQP_STATUS_OK; +} + +amqp_rpc_reply_t amqp_channel_close(amqp_connection_state_t state, + amqp_channel_t channel, int code) { + char codestr[13]; + amqp_method_number_t replies[2] = {AMQP_CHANNEL_CLOSE_OK_METHOD, 0}; + amqp_channel_close_t req; + + if (code < 0 || code > UINT16_MAX) { + return amqp_rpc_reply_error(AMQP_STATUS_INVALID_PARAMETER); + } + + req.reply_code = (uint16_t)code; + req.reply_text.bytes = codestr; + req.reply_text.len = sprintf(codestr, "%d", code); + req.class_id = 0; + req.method_id = 0; + + return amqp_simple_rpc(state, channel, AMQP_CHANNEL_CLOSE_METHOD, replies, + &req); +} + +amqp_rpc_reply_t amqp_connection_close(amqp_connection_state_t state, + int code) { + char codestr[13]; + amqp_method_number_t replies[2] = {AMQP_CONNECTION_CLOSE_OK_METHOD, 0}; + amqp_channel_close_t req; + + if (code < 0 || code > UINT16_MAX) { + return amqp_rpc_reply_error(AMQP_STATUS_INVALID_PARAMETER); + } + + req.reply_code = (uint16_t)code; + req.reply_text.bytes = codestr; + req.reply_text.len = sprintf(codestr, "%d", code); + req.class_id = 0; + req.method_id = 0; + + return amqp_simple_rpc(state, 0, AMQP_CONNECTION_CLOSE_METHOD, replies, &req); +} + +int amqp_basic_ack(amqp_connection_state_t state, amqp_channel_t channel, + uint64_t delivery_tag, amqp_boolean_t multiple) { + amqp_basic_ack_t m; + m.delivery_tag = delivery_tag; + m.multiple = multiple; + return amqp_send_method(state, channel, AMQP_BASIC_ACK_METHOD, &m); +} + +amqp_rpc_reply_t amqp_basic_get(amqp_connection_state_t state, + amqp_channel_t channel, amqp_bytes_t queue, + amqp_boolean_t no_ack) { + amqp_method_number_t replies[] = {AMQP_BASIC_GET_OK_METHOD, + AMQP_BASIC_GET_EMPTY_METHOD, 0}; + amqp_basic_get_t req; + req.ticket = 0; + req.queue = queue; + req.no_ack = no_ack; + + state->most_recent_api_result = + amqp_simple_rpc(state, channel, AMQP_BASIC_GET_METHOD, replies, &req); + return state->most_recent_api_result; +} + +int amqp_basic_reject(amqp_connection_state_t state, amqp_channel_t channel, + uint64_t delivery_tag, amqp_boolean_t requeue) { + amqp_basic_reject_t req; + req.delivery_tag = delivery_tag; + req.requeue = requeue; + return amqp_send_method(state, channel, AMQP_BASIC_REJECT_METHOD, &req); +} + +int amqp_basic_nack(amqp_connection_state_t state, amqp_channel_t channel, + uint64_t delivery_tag, amqp_boolean_t multiple, + amqp_boolean_t requeue) { + amqp_basic_nack_t req; + req.delivery_tag = delivery_tag; + req.multiple = multiple; + req.requeue = requeue; + return amqp_send_method(state, channel, AMQP_BASIC_NACK_METHOD, &req); +} + +struct timeval *amqp_get_handshake_timeout(amqp_connection_state_t state) { + return state->handshake_timeout; +} + +int amqp_set_handshake_timeout(amqp_connection_state_t state, + const struct timeval *timeout) { + if (timeout) { + if (timeout->tv_sec < 0 || timeout->tv_usec < 0) { + return AMQP_STATUS_INVALID_PARAMETER; + } + state->internal_handshake_timeout = *timeout; + state->handshake_timeout = &state->internal_handshake_timeout; + } else { + state->handshake_timeout = NULL; + } + + return AMQP_STATUS_OK; +} + +struct timeval *amqp_get_rpc_timeout(amqp_connection_state_t state) { + return state->rpc_timeout; +} + +int amqp_set_rpc_timeout(amqp_connection_state_t state, + const struct timeval *timeout) { + if (timeout) { + if (timeout->tv_sec < 0 || timeout->tv_usec < 0) { + return AMQP_STATUS_INVALID_PARAMETER; + } + state->rpc_timeout = &state->internal_rpc_timeout; + *state->rpc_timeout = *timeout; + } else { + state->rpc_timeout = NULL; + } + return AMQP_STATUS_OK; +} + +amqp_rpc_reply_t amqp_publisher_confirm_wait(amqp_connection_state_t state, + const struct timeval *timeout, + amqp_publisher_confirm_t *result) { + int res; + amqp_frame_t frame; + amqp_rpc_reply_t ret; + + memset(&ret, 0x0, sizeof(ret)); + memset(result, 0x0, sizeof(amqp_publisher_confirm_t)); + + res = amqp_simple_wait_frame_noblock(state, &frame, timeout); + + if (AMQP_STATUS_OK != res) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = res; + return ret; + } else if (AMQP_FRAME_METHOD != frame.frame_type || + (AMQP_BASIC_ACK_METHOD != frame.payload.method.id && + AMQP_BASIC_NACK_METHOD != frame.payload.method.id && + AMQP_BASIC_REJECT_METHOD != frame.payload.method.id)) { + amqp_put_back_frame(state, &frame); + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_UNEXPECTED_STATE; + return ret; + } + + switch (frame.payload.method.id) { + case AMQP_BASIC_ACK_METHOD: + memcpy(&(result->payload.ack), frame.payload.method.decoded, + sizeof(amqp_basic_ack_t)); + break; + + case AMQP_BASIC_NACK_METHOD: + memcpy(&(result->payload.nack), frame.payload.method.decoded, + sizeof(amqp_basic_nack_t)); + break; + + case AMQP_BASIC_REJECT_METHOD: + memcpy(&(result->payload.reject), frame.payload.method.decoded, + sizeof(amqp_basic_reject_t)); + break; + + default: + amqp_put_back_frame(state, &frame); + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_UNSUPPORTED; + return ret; + } + result->method = frame.payload.method.id; + result->channel = frame.channel; + ret.reply_type = AMQP_RESPONSE_NORMAL; + + return ret; +} diff --git a/librabbitmq/amqp_connection.c b/librabbitmq/amqp_connection.c new file mode 100644 index 0000000..56ab8a8 --- /dev/null +++ b/librabbitmq/amqp_connection.c @@ -0,0 +1,571 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "amqp_private.h" +#include "amqp_time.h" +#include "rabbitmq-c/tcp_socket.h" +#include +#include +#include +#include +#include + +#ifndef AMQP_INITIAL_FRAME_POOL_PAGE_SIZE +#define AMQP_INITIAL_FRAME_POOL_PAGE_SIZE 65536 +#endif + +#ifndef AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE +#define AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE 131072 +#endif + +#ifndef AMQP_DEFAULT_LOGIN_TIMEOUT_SEC +#define AMQP_DEFAULT_LOGIN_TIMEOUT_SEC 12 +#endif + +#define ENFORCE_STATE(statevec, statenum) \ + { \ + amqp_connection_state_t _check_state = (statevec); \ + amqp_connection_state_enum _wanted_state = (statenum); \ + if (_check_state->state != _wanted_state) \ + amqp_abort( \ + "Programming error: invalid AMQP connection state: expected %d, " \ + "got %d", \ + _wanted_state, _check_state->state); \ + } + +amqp_connection_state_t amqp_new_connection(void) { + int res; + amqp_connection_state_t state = (amqp_connection_state_t)calloc( + 1, sizeof(struct amqp_connection_state_t_)); + + if (state == NULL) { + return NULL; + } + + res = amqp_tune_connection(state, 0, AMQP_INITIAL_FRAME_POOL_PAGE_SIZE, 0); + if (0 != res) { + goto out_nomem; + } + + state->inbound_buffer.bytes = state->header_buffer; + state->inbound_buffer.len = sizeof(state->header_buffer); + + state->state = CONNECTION_STATE_INITIAL; + /* the server protocol version response is 8 bytes, which conveniently + is also the minimum frame size */ + state->target_size = 8; + + state->sock_inbound_buffer.len = AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE; + state->sock_inbound_buffer.bytes = + malloc(AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE); + if (state->sock_inbound_buffer.bytes == NULL) { + goto out_nomem; + } + + init_amqp_pool(&state->properties_pool, 512); + + /* Use address of the internal_handshake_timeout object by default. */ + state->internal_handshake_timeout.tv_sec = AMQP_DEFAULT_LOGIN_TIMEOUT_SEC; + state->internal_handshake_timeout.tv_usec = 0; + state->handshake_timeout = &state->internal_handshake_timeout; + + return state; + +out_nomem: + free(state->sock_inbound_buffer.bytes); + free(state); + return NULL; +} + +int amqp_get_sockfd(amqp_connection_state_t state) { + return state->socket ? amqp_socket_get_sockfd(state->socket) : -1; +} + +void amqp_set_sockfd(amqp_connection_state_t state, int sockfd) { + amqp_socket_t *socket = amqp_tcp_socket_new(state); + if (!socket) { + amqp_abort("%s", strerror(errno)); + } + amqp_tcp_socket_set_sockfd(socket, sockfd); +} + +void amqp_set_socket(amqp_connection_state_t state, amqp_socket_t *socket) { + amqp_socket_delete(state->socket); + state->socket = socket; +} + +amqp_socket_t *amqp_get_socket(amqp_connection_state_t state) { + return state->socket; +} + +int amqp_tune_connection(amqp_connection_state_t state, int channel_max, + int frame_max, int heartbeat) { + void *newbuf; + int res; + + ENFORCE_STATE(state, CONNECTION_STATE_IDLE); + + state->channel_max = channel_max; + state->frame_max = frame_max; + + state->heartbeat = heartbeat; + if (0 > state->heartbeat) { + state->heartbeat = 0; + } + + res = amqp_time_s_from_now(&state->next_send_heartbeat, + amqp_heartbeat_send(state)); + if (AMQP_STATUS_OK != res) { + return res; + } + res = amqp_time_s_from_now(&state->next_recv_heartbeat, + amqp_heartbeat_recv(state)); + if (AMQP_STATUS_OK != res) { + return res; + } + + state->outbound_buffer.len = frame_max; + newbuf = realloc(state->outbound_buffer.bytes, frame_max); + if (newbuf == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + state->outbound_buffer.bytes = newbuf; + + return AMQP_STATUS_OK; +} + +int amqp_get_channel_max(amqp_connection_state_t state) { + return state->channel_max; +} + +int amqp_get_frame_max(amqp_connection_state_t state) { + return state->frame_max; +} + +int amqp_get_heartbeat(amqp_connection_state_t state) { + return state->heartbeat; +} + +int amqp_destroy_connection(amqp_connection_state_t state) { + int status = AMQP_STATUS_OK; + if (state) { + int i; + for (i = 0; i < POOL_TABLE_SIZE; ++i) { + amqp_pool_table_entry_t *entry = state->pool_table[i]; + while (NULL != entry) { + amqp_pool_table_entry_t *todelete = entry; + empty_amqp_pool(&entry->pool); + entry = entry->next; + free(todelete); + } + } + + free(state->outbound_buffer.bytes); + free(state->sock_inbound_buffer.bytes); + amqp_socket_delete(state->socket); + empty_amqp_pool(&state->properties_pool); + free(state); + } + return status; +} + +static void return_to_idle(amqp_connection_state_t state) { + state->inbound_buffer.len = sizeof(state->header_buffer); + state->inbound_buffer.bytes = state->header_buffer; + state->inbound_offset = 0; + state->target_size = HEADER_SIZE; + state->state = CONNECTION_STATE_IDLE; +} + +static size_t consume_data(amqp_connection_state_t state, + amqp_bytes_t *received_data) { + /* how much data is available and will fit? */ + size_t bytes_consumed = state->target_size - state->inbound_offset; + if (received_data->len < bytes_consumed) { + bytes_consumed = received_data->len; + } + + memcpy(amqp_offset(state->inbound_buffer.bytes, state->inbound_offset), + received_data->bytes, bytes_consumed); + state->inbound_offset += bytes_consumed; + received_data->bytes = amqp_offset(received_data->bytes, bytes_consumed); + received_data->len -= bytes_consumed; + + return bytes_consumed; +} + +int amqp_handle_input(amqp_connection_state_t state, amqp_bytes_t received_data, + amqp_frame_t *decoded_frame) { + size_t bytes_consumed; + void *raw_frame; + + /* Returning frame_type of zero indicates either insufficient input, + or a complete, ignored frame was read. */ + decoded_frame->frame_type = 0; + + if (received_data.len == 0) { + return AMQP_STATUS_OK; + } + + if (state->state == CONNECTION_STATE_IDLE) { + state->state = CONNECTION_STATE_HEADER; + } + + bytes_consumed = consume_data(state, &received_data); + + /* do we have target_size data yet? if not, return with the + expectation that more will arrive */ + if (state->inbound_offset < state->target_size) { + return (int)bytes_consumed; + } + + raw_frame = state->inbound_buffer.bytes; + + switch (state->state) { + case CONNECTION_STATE_INITIAL: + /* check for a protocol header from the server */ + if (memcmp(raw_frame, "AMQP", 4) == 0) { + decoded_frame->frame_type = AMQP_PSEUDOFRAME_PROTOCOL_HEADER; + decoded_frame->channel = 0; + + decoded_frame->payload.protocol_header.transport_high = + amqp_d8(amqp_offset(raw_frame, 4)); + decoded_frame->payload.protocol_header.transport_low = + amqp_d8(amqp_offset(raw_frame, 5)); + decoded_frame->payload.protocol_header.protocol_version_major = + amqp_d8(amqp_offset(raw_frame, 6)); + decoded_frame->payload.protocol_header.protocol_version_minor = + amqp_d8(amqp_offset(raw_frame, 7)); + + return_to_idle(state); + return (int)bytes_consumed; + } + + /* it's not a protocol header; fall through to process it as a + regular frame header */ + + case CONNECTION_STATE_HEADER: { + amqp_channel_t channel; + amqp_pool_t *channel_pool; + uint32_t frame_size; + + channel = amqp_d16(amqp_offset(raw_frame, 1)); + + /* frame length is 3 bytes in */ + frame_size = amqp_d32(amqp_offset(raw_frame, 3)); + /* To prevent the target_size calculation below from overflowing, check + * that the stated frame_size is smaller than a signed 32-bit. Given + * the library only allows configuring frame_max as an int32_t, and + * frame_size is uint32_t, the math below is safe from overflow. */ + if (frame_size >= INT32_MAX) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + frame_size = frame_size + HEADER_SIZE + FOOTER_SIZE; + if ((size_t)state->frame_max < frame_size) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + channel_pool = amqp_get_or_create_channel_pool(state, channel); + if (NULL == channel_pool) { + return AMQP_STATUS_NO_MEMORY; + } + + amqp_pool_alloc_bytes(channel_pool, frame_size, &state->inbound_buffer); + if (NULL == state->inbound_buffer.bytes) { + return AMQP_STATUS_NO_MEMORY; + } + memcpy(state->inbound_buffer.bytes, state->header_buffer, HEADER_SIZE); + raw_frame = state->inbound_buffer.bytes; + + state->state = CONNECTION_STATE_BODY; + state->target_size = frame_size; + bytes_consumed += consume_data(state, &received_data); + + /* do we have target_size data yet? if not, return with the + expectation that more will arrive */ + if (state->inbound_offset < state->target_size) { + return (int)bytes_consumed; + } + } + /* fall through to process body */ + + case CONNECTION_STATE_BODY: { + amqp_bytes_t encoded; + int res; + amqp_pool_t *channel_pool; + + /* Check frame end marker (footer) */ + if (amqp_d8(amqp_offset(raw_frame, state->target_size - 1)) != + AMQP_FRAME_END) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + decoded_frame->frame_type = amqp_d8(amqp_offset(raw_frame, 0)); + decoded_frame->channel = amqp_d16(amqp_offset(raw_frame, 1)); + + channel_pool = + amqp_get_or_create_channel_pool(state, decoded_frame->channel); + if (NULL == channel_pool) { + return AMQP_STATUS_NO_MEMORY; + } + + switch (decoded_frame->frame_type) { + case AMQP_FRAME_METHOD: + decoded_frame->payload.method.id = + amqp_d32(amqp_offset(raw_frame, HEADER_SIZE)); + encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 4); + encoded.len = state->target_size - HEADER_SIZE - 4 - FOOTER_SIZE; + + res = amqp_decode_method(decoded_frame->payload.method.id, + channel_pool, encoded, + &decoded_frame->payload.method.decoded); + if (res < 0) { + return res; + } + + break; + + case AMQP_FRAME_HEADER: + decoded_frame->payload.properties.class_id = + amqp_d16(amqp_offset(raw_frame, HEADER_SIZE)); + /* unused 2-byte weight field goes here */ + decoded_frame->payload.properties.body_size = + amqp_d64(amqp_offset(raw_frame, HEADER_SIZE + 4)); + encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 12); + encoded.len = state->target_size - HEADER_SIZE - 12 - FOOTER_SIZE; + decoded_frame->payload.properties.raw = encoded; + + res = amqp_decode_properties( + decoded_frame->payload.properties.class_id, channel_pool, encoded, + &decoded_frame->payload.properties.decoded); + if (res < 0) { + return res; + } + + break; + + case AMQP_FRAME_BODY: + decoded_frame->payload.body_fragment.len = + state->target_size - HEADER_SIZE - FOOTER_SIZE; + decoded_frame->payload.body_fragment.bytes = + amqp_offset(raw_frame, HEADER_SIZE); + break; + + case AMQP_FRAME_HEARTBEAT: + break; + + default: + /* Ignore the frame */ + decoded_frame->frame_type = 0; + break; + } + + return_to_idle(state); + return (int)bytes_consumed; + } + + default: + amqp_abort("Internal error: invalid amqp_connection_state_t->state %d", + state->state); + } +} + +amqp_boolean_t amqp_release_buffers_ok(amqp_connection_state_t state) { + return (state->state == CONNECTION_STATE_IDLE); +} + +void amqp_release_buffers(amqp_connection_state_t state) { + int i; + ENFORCE_STATE(state, CONNECTION_STATE_IDLE); + + for (i = 0; i < POOL_TABLE_SIZE; ++i) { + amqp_pool_table_entry_t *entry = state->pool_table[i]; + + for (; NULL != entry; entry = entry->next) { + amqp_maybe_release_buffers_on_channel(state, entry->channel); + } + } +} + +void amqp_maybe_release_buffers(amqp_connection_state_t state) { + if (amqp_release_buffers_ok(state)) { + amqp_release_buffers(state); + } +} + +void amqp_maybe_release_buffers_on_channel(amqp_connection_state_t state, + amqp_channel_t channel) { + amqp_link_t *queued_link; + amqp_pool_t *pool; + if (CONNECTION_STATE_IDLE != state->state) { + return; + } + + queued_link = state->first_queued_frame; + + while (NULL != queued_link) { + amqp_frame_t *frame = queued_link->data; + if (channel == frame->channel) { + return; + } + + queued_link = queued_link->next; + } + + pool = amqp_get_channel_pool(state, channel); + + if (pool != NULL) { + recycle_amqp_pool(pool); + } +} + +static int amqp_frame_to_bytes(const amqp_frame_t *frame, amqp_bytes_t buffer, + amqp_bytes_t *encoded) { + void *out_frame = buffer.bytes; + size_t out_frame_len; + int res; + + amqp_e8(frame->frame_type, amqp_offset(out_frame, 0)); + amqp_e16(frame->channel, amqp_offset(out_frame, 1)); + + switch (frame->frame_type) { + case AMQP_FRAME_BODY: { + const amqp_bytes_t *body = &frame->payload.body_fragment; + + memcpy(amqp_offset(out_frame, HEADER_SIZE), body->bytes, body->len); + + out_frame_len = body->len; + break; + } + case AMQP_FRAME_METHOD: { + amqp_bytes_t method_encoded; + + amqp_e32(frame->payload.method.id, amqp_offset(out_frame, HEADER_SIZE)); + + method_encoded.bytes = amqp_offset(out_frame, HEADER_SIZE + 4); + method_encoded.len = buffer.len - HEADER_SIZE - 4 - FOOTER_SIZE; + + res = amqp_encode_method(frame->payload.method.id, + frame->payload.method.decoded, method_encoded); + if (res < 0) { + return res; + } + + out_frame_len = res + 4; + break; + } + + case AMQP_FRAME_HEADER: { + amqp_bytes_t properties_encoded; + + amqp_e16(frame->payload.properties.class_id, + amqp_offset(out_frame, HEADER_SIZE)); + amqp_e16(0, amqp_offset(out_frame, HEADER_SIZE + 2)); /* "weight" */ + amqp_e64(frame->payload.properties.body_size, + amqp_offset(out_frame, HEADER_SIZE + 4)); + + properties_encoded.bytes = amqp_offset(out_frame, HEADER_SIZE + 12); + properties_encoded.len = buffer.len - HEADER_SIZE - 12 - FOOTER_SIZE; + + res = amqp_encode_properties(frame->payload.properties.class_id, + frame->payload.properties.decoded, + properties_encoded); + if (res < 0) { + return res; + } + + out_frame_len = res + 12; + break; + } + + case AMQP_FRAME_HEARTBEAT: + out_frame_len = 0; + break; + + default: + return AMQP_STATUS_INVALID_PARAMETER; + } + + amqp_e32((uint32_t)out_frame_len, amqp_offset(out_frame, 3)); + amqp_e8(AMQP_FRAME_END, amqp_offset(out_frame, HEADER_SIZE + out_frame_len)); + + encoded->bytes = out_frame; + encoded->len = out_frame_len + HEADER_SIZE + FOOTER_SIZE; + + return AMQP_STATUS_OK; +} + +int amqp_send_frame(amqp_connection_state_t state, const amqp_frame_t *frame) { + return amqp_send_frame_inner(state, frame, AMQP_SF_NONE, + amqp_time_infinite()); +} + +int amqp_send_frame_inner(amqp_connection_state_t state, + const amqp_frame_t *frame, int flags, + amqp_time_t deadline) { + int res; + ssize_t sent; + amqp_bytes_t encoded; + amqp_time_t next_timeout; + + /* TODO: if the AMQP_SF_MORE socket optimization can be shown to work + * correctly, then this could be un-done so that body-frames are sent as 3 + * send calls, getting rid of the copy of the body content, some testing + * would need to be done to see if this would actually a win for performance. + * */ + res = amqp_frame_to_bytes(frame, state->outbound_buffer, &encoded); + if (AMQP_STATUS_OK != res) { + return res; + } + +start_send: + + next_timeout = amqp_time_first(deadline, state->next_recv_heartbeat); + + sent = amqp_try_send(state, encoded.bytes, encoded.len, next_timeout, flags); + if (0 > sent) { + return (int)sent; + } + + /* A partial send has occurred, because of a heartbeat timeout (so try recv + * something) or common timeout (so return AMQP_STATUS_TIMEOUT) */ + if ((ssize_t)encoded.len != sent) { + if (amqp_time_equal(next_timeout, deadline)) { + /* timeout of method was received, so return from method*/ + return AMQP_STATUS_TIMEOUT; + } + + res = amqp_try_recv(state); + + if (AMQP_STATUS_TIMEOUT == res) { + return AMQP_STATUS_HEARTBEAT_TIMEOUT; + } else if (AMQP_STATUS_OK != res) { + return res; + } + + encoded.bytes = (uint8_t *)encoded.bytes + sent; + encoded.len -= sent; + goto start_send; + } + + res = amqp_time_s_from_now(&state->next_send_heartbeat, + amqp_heartbeat_send(state)); + return res; +} + +amqp_table_t *amqp_get_server_properties(amqp_connection_state_t state) { + return &state->server_properties; +} + +amqp_table_t *amqp_get_client_properties(amqp_connection_state_t state) { + return &state->client_properties; +} diff --git a/librabbitmq/amqp_consumer.c b/librabbitmq/amqp_consumer.c new file mode 100644 index 0000000..f2ea617 --- /dev/null +++ b/librabbitmq/amqp_consumer.c @@ -0,0 +1,282 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_private.h" +#include "amqp_socket.h" +#include "rabbitmq-c/amqp.h" + +#include +#include + +static int amqp_basic_properties_clone(amqp_basic_properties_t *original, + amqp_basic_properties_t *clone, + amqp_pool_t *pool) { + memset(clone, 0, sizeof(*clone)); + clone->_flags = original->_flags; + +#define CLONE_BYTES_POOL(original, clone, pool) \ + if (0 == original.len) { \ + clone = amqp_empty_bytes; \ + } else { \ + amqp_pool_alloc_bytes(pool, original.len, &clone); \ + if (NULL == clone.bytes) { \ + return AMQP_STATUS_NO_MEMORY; \ + } \ + memcpy(clone.bytes, original.bytes, clone.len); \ + } + + if (clone->_flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { + CLONE_BYTES_POOL(original->content_type, clone->content_type, pool) + } + + if (clone->_flags & AMQP_BASIC_CONTENT_ENCODING_FLAG) { + CLONE_BYTES_POOL(original->content_encoding, clone->content_encoding, pool) + } + + if (clone->_flags & AMQP_BASIC_HEADERS_FLAG) { + int res = amqp_table_clone(&original->headers, &clone->headers, pool); + if (AMQP_STATUS_OK != res) { + return res; + } + } + + if (clone->_flags & AMQP_BASIC_DELIVERY_MODE_FLAG) { + clone->delivery_mode = original->delivery_mode; + } + + if (clone->_flags & AMQP_BASIC_PRIORITY_FLAG) { + clone->priority = original->priority; + } + + if (clone->_flags & AMQP_BASIC_CORRELATION_ID_FLAG) { + CLONE_BYTES_POOL(original->correlation_id, clone->correlation_id, pool) + } + + if (clone->_flags & AMQP_BASIC_REPLY_TO_FLAG) { + CLONE_BYTES_POOL(original->reply_to, clone->reply_to, pool) + } + + if (clone->_flags & AMQP_BASIC_EXPIRATION_FLAG) { + CLONE_BYTES_POOL(original->expiration, clone->expiration, pool) + } + + if (clone->_flags & AMQP_BASIC_MESSAGE_ID_FLAG) { + CLONE_BYTES_POOL(original->message_id, clone->message_id, pool) + } + + if (clone->_flags & AMQP_BASIC_TIMESTAMP_FLAG) { + clone->timestamp = original->timestamp; + } + + if (clone->_flags & AMQP_BASIC_TYPE_FLAG) { + CLONE_BYTES_POOL(original->type, clone->type, pool) + } + + if (clone->_flags & AMQP_BASIC_USER_ID_FLAG) { + CLONE_BYTES_POOL(original->user_id, clone->user_id, pool) + } + + if (clone->_flags & AMQP_BASIC_APP_ID_FLAG) { + CLONE_BYTES_POOL(original->app_id, clone->app_id, pool) + } + + if (clone->_flags & AMQP_BASIC_CLUSTER_ID_FLAG) { + CLONE_BYTES_POOL(original->cluster_id, clone->cluster_id, pool) + } + + return AMQP_STATUS_OK; +#undef CLONE_BYTES_POOL +} + +void amqp_destroy_message(amqp_message_t *message) { + empty_amqp_pool(&message->pool); + amqp_bytes_free(message->body); +} + +void amqp_destroy_envelope(amqp_envelope_t *envelope) { + amqp_destroy_message(&envelope->message); + amqp_bytes_free(envelope->routing_key); + amqp_bytes_free(envelope->exchange); + amqp_bytes_free(envelope->consumer_tag); +} + +static int amqp_bytes_malloc_dup_failed(amqp_bytes_t bytes) { + if (bytes.len != 0 && bytes.bytes == NULL) { + return 1; + } + return 0; +} + +amqp_rpc_reply_t amqp_consume_message(amqp_connection_state_t state, + amqp_envelope_t *envelope, + const struct timeval *timeout, + AMQP_UNUSED int flags) { + int res; + amqp_frame_t frame; + amqp_basic_deliver_t *delivery_method; + amqp_rpc_reply_t ret; + + memset(&ret, 0, sizeof(ret)); + memset(envelope, 0, sizeof(*envelope)); + + res = amqp_simple_wait_frame_noblock(state, &frame, timeout); + if (AMQP_STATUS_OK != res) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = res; + goto error_out1; + } + + if (AMQP_FRAME_METHOD != frame.frame_type || + AMQP_BASIC_DELIVER_METHOD != frame.payload.method.id) { + amqp_put_back_frame(state, &frame); + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_UNEXPECTED_STATE; + goto error_out1; + } + + delivery_method = frame.payload.method.decoded; + + envelope->channel = frame.channel; + envelope->consumer_tag = amqp_bytes_malloc_dup(delivery_method->consumer_tag); + envelope->delivery_tag = delivery_method->delivery_tag; + envelope->redelivered = delivery_method->redelivered; + envelope->exchange = amqp_bytes_malloc_dup(delivery_method->exchange); + envelope->routing_key = amqp_bytes_malloc_dup(delivery_method->routing_key); + + if (amqp_bytes_malloc_dup_failed(envelope->consumer_tag) || + amqp_bytes_malloc_dup_failed(envelope->exchange) || + amqp_bytes_malloc_dup_failed(envelope->routing_key)) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_NO_MEMORY; + goto error_out2; + } + + ret = amqp_read_message(state, envelope->channel, &envelope->message, 0); + if (AMQP_RESPONSE_NORMAL != ret.reply_type) { + goto error_out2; + } + + ret.reply_type = AMQP_RESPONSE_NORMAL; + return ret; + +error_out2: + amqp_bytes_free(envelope->routing_key); + amqp_bytes_free(envelope->exchange); + amqp_bytes_free(envelope->consumer_tag); +error_out1: + return ret; +} + +amqp_rpc_reply_t amqp_read_message(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_message_t *message, + AMQP_UNUSED int flags) { + amqp_frame_t frame; + amqp_rpc_reply_t ret; + + size_t body_read; + char *body_read_ptr; + int res; + + memset(&ret, 0, sizeof(ret)); + memset(message, 0, sizeof(*message)); + + res = amqp_simple_wait_frame_on_channel(state, channel, &frame); + if (AMQP_STATUS_OK != res) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = res; + + goto error_out1; + } + + if (AMQP_FRAME_HEADER != frame.frame_type) { + if (AMQP_FRAME_METHOD == frame.frame_type && + (AMQP_CHANNEL_CLOSE_METHOD == frame.payload.method.id || + AMQP_CONNECTION_CLOSE_METHOD == frame.payload.method.id)) { + + ret.reply_type = AMQP_RESPONSE_SERVER_EXCEPTION; + ret.reply = frame.payload.method; + + } else { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_UNEXPECTED_STATE; + + amqp_put_back_frame(state, &frame); + } + goto error_out1; + } + + init_amqp_pool(&message->pool, 4096); + res = amqp_basic_properties_clone(frame.payload.properties.decoded, + &message->properties, &message->pool); + + if (AMQP_STATUS_OK != res) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = res; + goto error_out3; + } + + if (0 == frame.payload.properties.body_size) { + message->body = amqp_empty_bytes; + } else { + if (SIZE_MAX < frame.payload.properties.body_size) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_NO_MEMORY; + goto error_out1; + } + message->body = + amqp_bytes_malloc((size_t)frame.payload.properties.body_size); + if (NULL == message->body.bytes) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_NO_MEMORY; + goto error_out1; + } + } + + body_read = 0; + body_read_ptr = message->body.bytes; + + while (body_read < message->body.len) { + res = amqp_simple_wait_frame_on_channel(state, channel, &frame); + if (AMQP_STATUS_OK != res) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = res; + goto error_out2; + } + if (AMQP_FRAME_BODY != frame.frame_type) { + if (AMQP_FRAME_METHOD == frame.frame_type && + (AMQP_CHANNEL_CLOSE_METHOD == frame.payload.method.id || + AMQP_CONNECTION_CLOSE_METHOD == frame.payload.method.id)) { + + ret.reply_type = AMQP_RESPONSE_SERVER_EXCEPTION; + ret.reply = frame.payload.method; + } else { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_BAD_AMQP_DATA; + } + goto error_out2; + } + + if (body_read + frame.payload.body_fragment.len > message->body.len) { + ret.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + ret.library_error = AMQP_STATUS_BAD_AMQP_DATA; + goto error_out2; + } + + memcpy(body_read_ptr, frame.payload.body_fragment.bytes, + frame.payload.body_fragment.len); + + body_read += frame.payload.body_fragment.len; + body_read_ptr += frame.payload.body_fragment.len; + } + + ret.reply_type = AMQP_RESPONSE_NORMAL; + return ret; + +error_out2: + amqp_bytes_free(message->body); +error_out3: + empty_amqp_pool(&message->pool); +error_out1: + return ret; +} diff --git a/librabbitmq/amqp_framing.c b/librabbitmq/amqp_framing.c new file mode 100644 index 0000000..35f026d --- /dev/null +++ b/librabbitmq/amqp_framing.c @@ -0,0 +1,2919 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/* Generated code. Do not edit. Edit and re-run codegen.py instead. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amqp_private.h" +#include +#include +#include +#include + +char const *amqp_constant_name(int constantNumber) { + switch (constantNumber) { + case AMQP_FRAME_METHOD: + return "AMQP_FRAME_METHOD"; + case AMQP_FRAME_HEADER: + return "AMQP_FRAME_HEADER"; + case AMQP_FRAME_BODY: + return "AMQP_FRAME_BODY"; + case AMQP_FRAME_HEARTBEAT: + return "AMQP_FRAME_HEARTBEAT"; + case AMQP_FRAME_MIN_SIZE: + return "AMQP_FRAME_MIN_SIZE"; + case AMQP_FRAME_END: + return "AMQP_FRAME_END"; + case AMQP_REPLY_SUCCESS: + return "AMQP_REPLY_SUCCESS"; + case AMQP_CONTENT_TOO_LARGE: + return "AMQP_CONTENT_TOO_LARGE"; + case AMQP_NO_ROUTE: + return "AMQP_NO_ROUTE"; + case AMQP_NO_CONSUMERS: + return "AMQP_NO_CONSUMERS"; + case AMQP_ACCESS_REFUSED: + return "AMQP_ACCESS_REFUSED"; + case AMQP_NOT_FOUND: + return "AMQP_NOT_FOUND"; + case AMQP_RESOURCE_LOCKED: + return "AMQP_RESOURCE_LOCKED"; + case AMQP_PRECONDITION_FAILED: + return "AMQP_PRECONDITION_FAILED"; + case AMQP_CONNECTION_FORCED: + return "AMQP_CONNECTION_FORCED"; + case AMQP_INVALID_PATH: + return "AMQP_INVALID_PATH"; + case AMQP_FRAME_ERROR: + return "AMQP_FRAME_ERROR"; + case AMQP_SYNTAX_ERROR: + return "AMQP_SYNTAX_ERROR"; + case AMQP_COMMAND_INVALID: + return "AMQP_COMMAND_INVALID"; + case AMQP_CHANNEL_ERROR: + return "AMQP_CHANNEL_ERROR"; + case AMQP_UNEXPECTED_FRAME: + return "AMQP_UNEXPECTED_FRAME"; + case AMQP_RESOURCE_ERROR: + return "AMQP_RESOURCE_ERROR"; + case AMQP_NOT_ALLOWED: + return "AMQP_NOT_ALLOWED"; + case AMQP_NOT_IMPLEMENTED: + return "AMQP_NOT_IMPLEMENTED"; + case AMQP_INTERNAL_ERROR: + return "AMQP_INTERNAL_ERROR"; + default: + return "(unknown)"; + } +} + +amqp_boolean_t amqp_constant_is_hard_error(int constantNumber) { + switch (constantNumber) { + case AMQP_CONNECTION_FORCED: + return 1; + case AMQP_INVALID_PATH: + return 1; + case AMQP_FRAME_ERROR: + return 1; + case AMQP_SYNTAX_ERROR: + return 1; + case AMQP_COMMAND_INVALID: + return 1; + case AMQP_CHANNEL_ERROR: + return 1; + case AMQP_UNEXPECTED_FRAME: + return 1; + case AMQP_RESOURCE_ERROR: + return 1; + case AMQP_NOT_ALLOWED: + return 1; + case AMQP_NOT_IMPLEMENTED: + return 1; + case AMQP_INTERNAL_ERROR: + return 1; + default: + return 0; + } +} + +char const *amqp_method_name(amqp_method_number_t methodNumber) { + switch (methodNumber) { + case AMQP_CONNECTION_START_METHOD: + return "AMQP_CONNECTION_START_METHOD"; + case AMQP_CONNECTION_START_OK_METHOD: + return "AMQP_CONNECTION_START_OK_METHOD"; + case AMQP_CONNECTION_SECURE_METHOD: + return "AMQP_CONNECTION_SECURE_METHOD"; + case AMQP_CONNECTION_SECURE_OK_METHOD: + return "AMQP_CONNECTION_SECURE_OK_METHOD"; + case AMQP_CONNECTION_TUNE_METHOD: + return "AMQP_CONNECTION_TUNE_METHOD"; + case AMQP_CONNECTION_TUNE_OK_METHOD: + return "AMQP_CONNECTION_TUNE_OK_METHOD"; + case AMQP_CONNECTION_OPEN_METHOD: + return "AMQP_CONNECTION_OPEN_METHOD"; + case AMQP_CONNECTION_OPEN_OK_METHOD: + return "AMQP_CONNECTION_OPEN_OK_METHOD"; + case AMQP_CONNECTION_CLOSE_METHOD: + return "AMQP_CONNECTION_CLOSE_METHOD"; + case AMQP_CONNECTION_CLOSE_OK_METHOD: + return "AMQP_CONNECTION_CLOSE_OK_METHOD"; + case AMQP_CONNECTION_BLOCKED_METHOD: + return "AMQP_CONNECTION_BLOCKED_METHOD"; + case AMQP_CONNECTION_UNBLOCKED_METHOD: + return "AMQP_CONNECTION_UNBLOCKED_METHOD"; + case AMQP_CONNECTION_UPDATE_SECRET_METHOD: + return "AMQP_CONNECTION_UPDATE_SECRET_METHOD"; + case AMQP_CONNECTION_UPDATE_SECRET_OK_METHOD: + return "AMQP_CONNECTION_UPDATE_SECRET_OK_METHOD"; + case AMQP_CHANNEL_OPEN_METHOD: + return "AMQP_CHANNEL_OPEN_METHOD"; + case AMQP_CHANNEL_OPEN_OK_METHOD: + return "AMQP_CHANNEL_OPEN_OK_METHOD"; + case AMQP_CHANNEL_FLOW_METHOD: + return "AMQP_CHANNEL_FLOW_METHOD"; + case AMQP_CHANNEL_FLOW_OK_METHOD: + return "AMQP_CHANNEL_FLOW_OK_METHOD"; + case AMQP_CHANNEL_CLOSE_METHOD: + return "AMQP_CHANNEL_CLOSE_METHOD"; + case AMQP_CHANNEL_CLOSE_OK_METHOD: + return "AMQP_CHANNEL_CLOSE_OK_METHOD"; + case AMQP_ACCESS_REQUEST_METHOD: + return "AMQP_ACCESS_REQUEST_METHOD"; + case AMQP_ACCESS_REQUEST_OK_METHOD: + return "AMQP_ACCESS_REQUEST_OK_METHOD"; + case AMQP_EXCHANGE_DECLARE_METHOD: + return "AMQP_EXCHANGE_DECLARE_METHOD"; + case AMQP_EXCHANGE_DECLARE_OK_METHOD: + return "AMQP_EXCHANGE_DECLARE_OK_METHOD"; + case AMQP_EXCHANGE_DELETE_METHOD: + return "AMQP_EXCHANGE_DELETE_METHOD"; + case AMQP_EXCHANGE_DELETE_OK_METHOD: + return "AMQP_EXCHANGE_DELETE_OK_METHOD"; + case AMQP_EXCHANGE_BIND_METHOD: + return "AMQP_EXCHANGE_BIND_METHOD"; + case AMQP_EXCHANGE_BIND_OK_METHOD: + return "AMQP_EXCHANGE_BIND_OK_METHOD"; + case AMQP_EXCHANGE_UNBIND_METHOD: + return "AMQP_EXCHANGE_UNBIND_METHOD"; + case AMQP_EXCHANGE_UNBIND_OK_METHOD: + return "AMQP_EXCHANGE_UNBIND_OK_METHOD"; + case AMQP_QUEUE_DECLARE_METHOD: + return "AMQP_QUEUE_DECLARE_METHOD"; + case AMQP_QUEUE_DECLARE_OK_METHOD: + return "AMQP_QUEUE_DECLARE_OK_METHOD"; + case AMQP_QUEUE_BIND_METHOD: + return "AMQP_QUEUE_BIND_METHOD"; + case AMQP_QUEUE_BIND_OK_METHOD: + return "AMQP_QUEUE_BIND_OK_METHOD"; + case AMQP_QUEUE_PURGE_METHOD: + return "AMQP_QUEUE_PURGE_METHOD"; + case AMQP_QUEUE_PURGE_OK_METHOD: + return "AMQP_QUEUE_PURGE_OK_METHOD"; + case AMQP_QUEUE_DELETE_METHOD: + return "AMQP_QUEUE_DELETE_METHOD"; + case AMQP_QUEUE_DELETE_OK_METHOD: + return "AMQP_QUEUE_DELETE_OK_METHOD"; + case AMQP_QUEUE_UNBIND_METHOD: + return "AMQP_QUEUE_UNBIND_METHOD"; + case AMQP_QUEUE_UNBIND_OK_METHOD: + return "AMQP_QUEUE_UNBIND_OK_METHOD"; + case AMQP_BASIC_QOS_METHOD: + return "AMQP_BASIC_QOS_METHOD"; + case AMQP_BASIC_QOS_OK_METHOD: + return "AMQP_BASIC_QOS_OK_METHOD"; + case AMQP_BASIC_CONSUME_METHOD: + return "AMQP_BASIC_CONSUME_METHOD"; + case AMQP_BASIC_CONSUME_OK_METHOD: + return "AMQP_BASIC_CONSUME_OK_METHOD"; + case AMQP_BASIC_CANCEL_METHOD: + return "AMQP_BASIC_CANCEL_METHOD"; + case AMQP_BASIC_CANCEL_OK_METHOD: + return "AMQP_BASIC_CANCEL_OK_METHOD"; + case AMQP_BASIC_PUBLISH_METHOD: + return "AMQP_BASIC_PUBLISH_METHOD"; + case AMQP_BASIC_RETURN_METHOD: + return "AMQP_BASIC_RETURN_METHOD"; + case AMQP_BASIC_DELIVER_METHOD: + return "AMQP_BASIC_DELIVER_METHOD"; + case AMQP_BASIC_GET_METHOD: + return "AMQP_BASIC_GET_METHOD"; + case AMQP_BASIC_GET_OK_METHOD: + return "AMQP_BASIC_GET_OK_METHOD"; + case AMQP_BASIC_GET_EMPTY_METHOD: + return "AMQP_BASIC_GET_EMPTY_METHOD"; + case AMQP_BASIC_ACK_METHOD: + return "AMQP_BASIC_ACK_METHOD"; + case AMQP_BASIC_REJECT_METHOD: + return "AMQP_BASIC_REJECT_METHOD"; + case AMQP_BASIC_RECOVER_ASYNC_METHOD: + return "AMQP_BASIC_RECOVER_ASYNC_METHOD"; + case AMQP_BASIC_RECOVER_METHOD: + return "AMQP_BASIC_RECOVER_METHOD"; + case AMQP_BASIC_RECOVER_OK_METHOD: + return "AMQP_BASIC_RECOVER_OK_METHOD"; + case AMQP_BASIC_NACK_METHOD: + return "AMQP_BASIC_NACK_METHOD"; + case AMQP_TX_SELECT_METHOD: + return "AMQP_TX_SELECT_METHOD"; + case AMQP_TX_SELECT_OK_METHOD: + return "AMQP_TX_SELECT_OK_METHOD"; + case AMQP_TX_COMMIT_METHOD: + return "AMQP_TX_COMMIT_METHOD"; + case AMQP_TX_COMMIT_OK_METHOD: + return "AMQP_TX_COMMIT_OK_METHOD"; + case AMQP_TX_ROLLBACK_METHOD: + return "AMQP_TX_ROLLBACK_METHOD"; + case AMQP_TX_ROLLBACK_OK_METHOD: + return "AMQP_TX_ROLLBACK_OK_METHOD"; + case AMQP_CONFIRM_SELECT_METHOD: + return "AMQP_CONFIRM_SELECT_METHOD"; + case AMQP_CONFIRM_SELECT_OK_METHOD: + return "AMQP_CONFIRM_SELECT_OK_METHOD"; + default: + return NULL; + } +} + +amqp_boolean_t amqp_method_has_content(amqp_method_number_t methodNumber) { + switch (methodNumber) { + case AMQP_BASIC_PUBLISH_METHOD: + return 1; + case AMQP_BASIC_RETURN_METHOD: + return 1; + case AMQP_BASIC_DELIVER_METHOD: + return 1; + case AMQP_BASIC_GET_OK_METHOD: + return 1; + default: + return 0; + } +} + +int amqp_decode_method(amqp_method_number_t methodNumber, amqp_pool_t *pool, + amqp_bytes_t encoded, void **decoded) { + size_t offset = 0; + uint8_t bit_buffer; + + switch (methodNumber) { + case AMQP_CONNECTION_START_METHOD: { + amqp_connection_start_t *m = (amqp_connection_start_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_start_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_8(encoded, &offset, &m->version_major)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &m->version_minor)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = + amqp_decode_table(encoded, pool, &(m->server_properties), &offset); + if (res < 0) return res; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->mechanisms, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->locales, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_START_OK_METHOD: { + amqp_connection_start_ok_t *m = + (amqp_connection_start_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_start_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + int res = + amqp_decode_table(encoded, pool, &(m->client_properties), &offset); + if (res < 0) return res; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->mechanism, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->response, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->locale, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_SECURE_METHOD: { + amqp_connection_secure_t *m = (amqp_connection_secure_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_secure_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->challenge, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_SECURE_OK_METHOD: { + amqp_connection_secure_ok_t *m = + (amqp_connection_secure_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_secure_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->response, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_TUNE_METHOD: { + amqp_connection_tune_t *m = (amqp_connection_tune_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_tune_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->channel_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_32(encoded, &offset, &m->frame_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_16(encoded, &offset, &m->heartbeat)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_CONNECTION_TUNE_OK_METHOD: { + amqp_connection_tune_ok_t *m = + (amqp_connection_tune_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_tune_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->channel_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_32(encoded, &offset, &m->frame_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_16(encoded, &offset, &m->heartbeat)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_CONNECTION_OPEN_METHOD: { + amqp_connection_open_t *m = (amqp_connection_open_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_open_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->virtual_host, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->capabilities, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->insist = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_CONNECTION_OPEN_OK_METHOD: { + amqp_connection_open_ok_t *m = + (amqp_connection_open_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_open_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->known_hosts, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_CLOSE_METHOD: { + amqp_connection_close_t *m = (amqp_connection_close_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_close_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->reply_code)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->reply_text, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_16(encoded, &offset, &m->class_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_16(encoded, &offset, &m->method_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_CONNECTION_CLOSE_OK_METHOD: { + amqp_connection_close_ok_t *m = + (amqp_connection_close_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_close_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_BLOCKED_METHOD: { + amqp_connection_blocked_t *m = + (amqp_connection_blocked_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_blocked_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->reason, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_UNBLOCKED_METHOD: { + amqp_connection_unblocked_t *m = + (amqp_connection_unblocked_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_unblocked_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_UPDATE_SECRET_METHOD: { + amqp_connection_update_secret_t *m = + (amqp_connection_update_secret_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_update_secret_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->new_secret, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->reason, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CONNECTION_UPDATE_SECRET_OK_METHOD: { + amqp_connection_update_secret_ok_t *m = + (amqp_connection_update_secret_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_update_secret_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_CHANNEL_OPEN_METHOD: { + amqp_channel_open_t *m = (amqp_channel_open_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_open_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->out_of_band, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CHANNEL_OPEN_OK_METHOD: { + amqp_channel_open_ok_t *m = (amqp_channel_open_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_open_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint32_t len; + if (!amqp_decode_32(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->channel_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_CHANNEL_FLOW_METHOD: { + amqp_channel_flow_t *m = (amqp_channel_flow_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_flow_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->active = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_CHANNEL_FLOW_OK_METHOD: { + amqp_channel_flow_ok_t *m = (amqp_channel_flow_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_flow_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->active = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_CHANNEL_CLOSE_METHOD: { + amqp_channel_close_t *m = (amqp_channel_close_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_close_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->reply_code)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->reply_text, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_16(encoded, &offset, &m->class_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_16(encoded, &offset, &m->method_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_CHANNEL_CLOSE_OK_METHOD: { + amqp_channel_close_ok_t *m = (amqp_channel_close_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_close_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_ACCESS_REQUEST_METHOD: { + amqp_access_request_t *m = (amqp_access_request_t *)amqp_pool_alloc( + pool, sizeof(amqp_access_request_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->realm, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->exclusive = (bit_buffer & (1 << 0)) ? 1 : 0; + m->passive = (bit_buffer & (1 << 1)) ? 1 : 0; + m->active = (bit_buffer & (1 << 2)) ? 1 : 0; + m->write = (bit_buffer & (1 << 3)) ? 1 : 0; + m->read = (bit_buffer & (1 << 4)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_ACCESS_REQUEST_OK_METHOD: { + amqp_access_request_ok_t *m = (amqp_access_request_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_access_request_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_DECLARE_METHOD: { + amqp_exchange_declare_t *m = (amqp_exchange_declare_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_declare_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->type, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->passive = (bit_buffer & (1 << 0)) ? 1 : 0; + m->durable = (bit_buffer & (1 << 1)) ? 1 : 0; + m->auto_delete = (bit_buffer & (1 << 2)) ? 1 : 0; + m->internal = (bit_buffer & (1 << 3)) ? 1 : 0; + m->nowait = (bit_buffer & (1 << 4)) ? 1 : 0; + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_DECLARE_OK_METHOD: { + amqp_exchange_declare_ok_t *m = + (amqp_exchange_declare_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_declare_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_DELETE_METHOD: { + amqp_exchange_delete_t *m = (amqp_exchange_delete_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_delete_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->if_unused = (bit_buffer & (1 << 0)) ? 1 : 0; + m->nowait = (bit_buffer & (1 << 1)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_DELETE_OK_METHOD: { + amqp_exchange_delete_ok_t *m = + (amqp_exchange_delete_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_delete_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_BIND_METHOD: { + amqp_exchange_bind_t *m = (amqp_exchange_bind_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_bind_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->destination, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->source, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->nowait = (bit_buffer & (1 << 0)) ? 1 : 0; + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_BIND_OK_METHOD: { + amqp_exchange_bind_ok_t *m = (amqp_exchange_bind_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_bind_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_UNBIND_METHOD: { + amqp_exchange_unbind_t *m = (amqp_exchange_unbind_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_unbind_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->destination, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->source, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->nowait = (bit_buffer & (1 << 0)) ? 1 : 0; + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_EXCHANGE_UNBIND_OK_METHOD: { + amqp_exchange_unbind_ok_t *m = + (amqp_exchange_unbind_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_unbind_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_QUEUE_DECLARE_METHOD: { + amqp_queue_declare_t *m = (amqp_queue_declare_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_declare_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->passive = (bit_buffer & (1 << 0)) ? 1 : 0; + m->durable = (bit_buffer & (1 << 1)) ? 1 : 0; + m->exclusive = (bit_buffer & (1 << 2)) ? 1 : 0; + m->auto_delete = (bit_buffer & (1 << 3)) ? 1 : 0; + m->nowait = (bit_buffer & (1 << 4)) ? 1 : 0; + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_QUEUE_DECLARE_OK_METHOD: { + amqp_queue_declare_ok_t *m = (amqp_queue_declare_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_declare_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_32(encoded, &offset, &m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_32(encoded, &offset, &m->consumer_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_QUEUE_BIND_METHOD: { + amqp_queue_bind_t *m = + (amqp_queue_bind_t *)amqp_pool_alloc(pool, sizeof(amqp_queue_bind_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->nowait = (bit_buffer & (1 << 0)) ? 1 : 0; + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_QUEUE_BIND_OK_METHOD: { + amqp_queue_bind_ok_t *m = (amqp_queue_bind_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_bind_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_QUEUE_PURGE_METHOD: { + amqp_queue_purge_t *m = (amqp_queue_purge_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_purge_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->nowait = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_QUEUE_PURGE_OK_METHOD: { + amqp_queue_purge_ok_t *m = (amqp_queue_purge_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_purge_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_32(encoded, &offset, &m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_QUEUE_DELETE_METHOD: { + amqp_queue_delete_t *m = (amqp_queue_delete_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_delete_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->if_unused = (bit_buffer & (1 << 0)) ? 1 : 0; + m->if_empty = (bit_buffer & (1 << 1)) ? 1 : 0; + m->nowait = (bit_buffer & (1 << 2)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_QUEUE_DELETE_OK_METHOD: { + amqp_queue_delete_ok_t *m = (amqp_queue_delete_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_delete_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_32(encoded, &offset, &m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_QUEUE_UNBIND_METHOD: { + amqp_queue_unbind_t *m = (amqp_queue_unbind_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_unbind_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_QUEUE_UNBIND_OK_METHOD: { + amqp_queue_unbind_ok_t *m = (amqp_queue_unbind_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_unbind_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_QOS_METHOD: { + amqp_basic_qos_t *m = + (amqp_basic_qos_t *)amqp_pool_alloc(pool, sizeof(amqp_basic_qos_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_32(encoded, &offset, &m->prefetch_size)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_16(encoded, &offset, &m->prefetch_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->global = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_QOS_OK_METHOD: { + amqp_basic_qos_ok_t *m = (amqp_basic_qos_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_qos_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_CONSUME_METHOD: { + amqp_basic_consume_t *m = (amqp_basic_consume_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_consume_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->consumer_tag, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->no_local = (bit_buffer & (1 << 0)) ? 1 : 0; + m->no_ack = (bit_buffer & (1 << 1)) ? 1 : 0; + m->exclusive = (bit_buffer & (1 << 2)) ? 1 : 0; + m->nowait = (bit_buffer & (1 << 3)) ? 1 : 0; + { + int res = amqp_decode_table(encoded, pool, &(m->arguments), &offset); + if (res < 0) return res; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_CONSUME_OK_METHOD: { + amqp_basic_consume_ok_t *m = (amqp_basic_consume_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_consume_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->consumer_tag, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_CANCEL_METHOD: { + amqp_basic_cancel_t *m = (amqp_basic_cancel_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_cancel_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->consumer_tag, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->nowait = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_CANCEL_OK_METHOD: { + amqp_basic_cancel_ok_t *m = (amqp_basic_cancel_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_cancel_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->consumer_tag, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_PUBLISH_METHOD: { + amqp_basic_publish_t *m = (amqp_basic_publish_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_publish_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->mandatory = (bit_buffer & (1 << 0)) ? 1 : 0; + m->immediate = (bit_buffer & (1 << 1)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_RETURN_METHOD: { + amqp_basic_return_t *m = (amqp_basic_return_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_return_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->reply_code)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->reply_text, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_DELIVER_METHOD: { + amqp_basic_deliver_t *m = (amqp_basic_deliver_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_deliver_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->consumer_tag, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_64(encoded, &offset, &m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->redelivered = (bit_buffer & (1 << 0)) ? 1 : 0; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_GET_METHOD: { + amqp_basic_get_t *m = + (amqp_basic_get_t *)amqp_pool_alloc(pool, sizeof(amqp_basic_get_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_16(encoded, &offset, &m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->queue, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->no_ack = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_GET_OK_METHOD: { + amqp_basic_get_ok_t *m = (amqp_basic_get_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_get_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_64(encoded, &offset, &m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->redelivered = (bit_buffer & (1 << 0)) ? 1 : 0; + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->exchange, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->routing_key, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (!amqp_decode_32(encoded, &offset, &m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + *decoded = m; + return 0; + } + case AMQP_BASIC_GET_EMPTY_METHOD: { + amqp_basic_get_empty_t *m = (amqp_basic_get_empty_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_get_empty_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &m->cluster_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_ACK_METHOD: { + amqp_basic_ack_t *m = + (amqp_basic_ack_t *)amqp_pool_alloc(pool, sizeof(amqp_basic_ack_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_64(encoded, &offset, &m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->multiple = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_REJECT_METHOD: { + amqp_basic_reject_t *m = (amqp_basic_reject_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_reject_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_64(encoded, &offset, &m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->requeue = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_RECOVER_ASYNC_METHOD: { + amqp_basic_recover_async_t *m = + (amqp_basic_recover_async_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_recover_async_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->requeue = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_RECOVER_METHOD: { + amqp_basic_recover_t *m = (amqp_basic_recover_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_recover_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->requeue = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_BASIC_RECOVER_OK_METHOD: { + amqp_basic_recover_ok_t *m = (amqp_basic_recover_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_recover_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_BASIC_NACK_METHOD: { + amqp_basic_nack_t *m = + (amqp_basic_nack_t *)amqp_pool_alloc(pool, sizeof(amqp_basic_nack_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_64(encoded, &offset, &m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->multiple = (bit_buffer & (1 << 0)) ? 1 : 0; + m->requeue = (bit_buffer & (1 << 1)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_TX_SELECT_METHOD: { + amqp_tx_select_t *m = + (amqp_tx_select_t *)amqp_pool_alloc(pool, sizeof(amqp_tx_select_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_TX_SELECT_OK_METHOD: { + amqp_tx_select_ok_t *m = (amqp_tx_select_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_tx_select_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_TX_COMMIT_METHOD: { + amqp_tx_commit_t *m = + (amqp_tx_commit_t *)amqp_pool_alloc(pool, sizeof(amqp_tx_commit_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_TX_COMMIT_OK_METHOD: { + amqp_tx_commit_ok_t *m = (amqp_tx_commit_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_tx_commit_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_TX_ROLLBACK_METHOD: { + amqp_tx_rollback_t *m = (amqp_tx_rollback_t *)amqp_pool_alloc( + pool, sizeof(amqp_tx_rollback_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_TX_ROLLBACK_OK_METHOD: { + amqp_tx_rollback_ok_t *m = (amqp_tx_rollback_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_tx_rollback_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + case AMQP_CONFIRM_SELECT_METHOD: { + amqp_confirm_select_t *m = (amqp_confirm_select_t *)amqp_pool_alloc( + pool, sizeof(amqp_confirm_select_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + if (!amqp_decode_8(encoded, &offset, &bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + m->nowait = (bit_buffer & (1 << 0)) ? 1 : 0; + *decoded = m; + return 0; + } + case AMQP_CONFIRM_SELECT_OK_METHOD: { + amqp_confirm_select_ok_t *m = (amqp_confirm_select_ok_t *)amqp_pool_alloc( + pool, sizeof(amqp_confirm_select_ok_t)); + if (m == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + *decoded = m; + return 0; + } + default: + return AMQP_STATUS_UNKNOWN_METHOD; + } +} + +int amqp_decode_properties(uint16_t class_id, amqp_pool_t *pool, + amqp_bytes_t encoded, void **decoded) { + size_t offset = 0; + + amqp_flags_t flags = 0; + int flagword_index = 0; + uint16_t partial_flags; + + do { + if (!amqp_decode_16(encoded, &offset, &partial_flags)) + return AMQP_STATUS_BAD_AMQP_DATA; + flags |= (partial_flags << (flagword_index * 16)); + flagword_index++; + } while (partial_flags & 1); + + switch (class_id) { + case 10: { + amqp_connection_properties_t *p = + (amqp_connection_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_connection_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + case 20: { + amqp_channel_properties_t *p = + (amqp_channel_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_channel_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + case 30: { + amqp_access_properties_t *p = (amqp_access_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_access_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + case 40: { + amqp_exchange_properties_t *p = + (amqp_exchange_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_exchange_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + case 50: { + amqp_queue_properties_t *p = (amqp_queue_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_queue_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + case 60: { + amqp_basic_properties_t *p = (amqp_basic_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_basic_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + if (flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->content_type, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_CONTENT_ENCODING_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->content_encoding, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_HEADERS_FLAG) { + { + int res = amqp_decode_table(encoded, pool, &(p->headers), &offset); + if (res < 0) return res; + } + } + if (flags & AMQP_BASIC_DELIVERY_MODE_FLAG) { + if (!amqp_decode_8(encoded, &offset, &p->delivery_mode)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_PRIORITY_FLAG) { + if (!amqp_decode_8(encoded, &offset, &p->priority)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_CORRELATION_ID_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->correlation_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_REPLY_TO_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->reply_to, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_EXPIRATION_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->expiration, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_MESSAGE_ID_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->message_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_TIMESTAMP_FLAG) { + if (!amqp_decode_64(encoded, &offset, &p->timestamp)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_TYPE_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->type, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_USER_ID_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->user_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_APP_ID_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->app_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + if (flags & AMQP_BASIC_CLUSTER_ID_FLAG) { + { + uint8_t len; + if (!amqp_decode_8(encoded, &offset, &len) || + !amqp_decode_bytes(encoded, &offset, &p->cluster_id, len)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + *decoded = p; + return 0; + } + case 90: { + amqp_tx_properties_t *p = (amqp_tx_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_tx_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + case 85: { + amqp_confirm_properties_t *p = + (amqp_confirm_properties_t *)amqp_pool_alloc( + pool, sizeof(amqp_confirm_properties_t)); + if (p == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + p->_flags = flags; + *decoded = p; + return 0; + } + default: + return AMQP_STATUS_UNKNOWN_CLASS; + } +} + +int amqp_encode_method(amqp_method_number_t methodNumber, void *decoded, + amqp_bytes_t encoded) { + size_t offset = 0; + uint8_t bit_buffer; + + switch (methodNumber) { + case AMQP_CONNECTION_START_METHOD: { + amqp_connection_start_t *m = (amqp_connection_start_t *)decoded; + if (!amqp_encode_8(encoded, &offset, m->version_major)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_8(encoded, &offset, m->version_minor)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->server_properties), &offset); + if (res < 0) return res; + } + if (UINT32_MAX < m->mechanisms.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->mechanisms.len) || + !amqp_encode_bytes(encoded, &offset, m->mechanisms)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT32_MAX < m->locales.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->locales.len) || + !amqp_encode_bytes(encoded, &offset, m->locales)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_START_OK_METHOD: { + amqp_connection_start_ok_t *m = (amqp_connection_start_ok_t *)decoded; + { + int res = amqp_encode_table(encoded, &(m->client_properties), &offset); + if (res < 0) return res; + } + if (UINT8_MAX < m->mechanism.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->mechanism.len) || + !amqp_encode_bytes(encoded, &offset, m->mechanism)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT32_MAX < m->response.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->response.len) || + !amqp_encode_bytes(encoded, &offset, m->response)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->locale.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->locale.len) || + !amqp_encode_bytes(encoded, &offset, m->locale)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_SECURE_METHOD: { + amqp_connection_secure_t *m = (amqp_connection_secure_t *)decoded; + if (UINT32_MAX < m->challenge.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->challenge.len) || + !amqp_encode_bytes(encoded, &offset, m->challenge)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_SECURE_OK_METHOD: { + amqp_connection_secure_ok_t *m = (amqp_connection_secure_ok_t *)decoded; + if (UINT32_MAX < m->response.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->response.len) || + !amqp_encode_bytes(encoded, &offset, m->response)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_TUNE_METHOD: { + amqp_connection_tune_t *m = (amqp_connection_tune_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->channel_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_32(encoded, &offset, m->frame_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->heartbeat)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_TUNE_OK_METHOD: { + amqp_connection_tune_ok_t *m = (amqp_connection_tune_ok_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->channel_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_32(encoded, &offset, m->frame_max)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->heartbeat)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_OPEN_METHOD: { + amqp_connection_open_t *m = (amqp_connection_open_t *)decoded; + if (UINT8_MAX < m->virtual_host.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->virtual_host.len) || + !amqp_encode_bytes(encoded, &offset, m->virtual_host)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->capabilities.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->capabilities.len) || + !amqp_encode_bytes(encoded, &offset, m->capabilities)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->insist) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_OPEN_OK_METHOD: { + amqp_connection_open_ok_t *m = (amqp_connection_open_ok_t *)decoded; + if (UINT8_MAX < m->known_hosts.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->known_hosts.len) || + !amqp_encode_bytes(encoded, &offset, m->known_hosts)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_CLOSE_METHOD: { + amqp_connection_close_t *m = (amqp_connection_close_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->reply_code)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->reply_text.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->reply_text.len) || + !amqp_encode_bytes(encoded, &offset, m->reply_text)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->class_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->method_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_CLOSE_OK_METHOD: { + return (int)offset; + } + case AMQP_CONNECTION_BLOCKED_METHOD: { + amqp_connection_blocked_t *m = (amqp_connection_blocked_t *)decoded; + if (UINT8_MAX < m->reason.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->reason.len) || + !amqp_encode_bytes(encoded, &offset, m->reason)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_UNBLOCKED_METHOD: { + return (int)offset; + } + case AMQP_CONNECTION_UPDATE_SECRET_METHOD: { + amqp_connection_update_secret_t *m = + (amqp_connection_update_secret_t *)decoded; + if (UINT32_MAX < m->new_secret.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->new_secret.len) || + !amqp_encode_bytes(encoded, &offset, m->new_secret)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->reason.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->reason.len) || + !amqp_encode_bytes(encoded, &offset, m->reason)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONNECTION_UPDATE_SECRET_OK_METHOD: { + return (int)offset; + } + case AMQP_CHANNEL_OPEN_METHOD: { + amqp_channel_open_t *m = (amqp_channel_open_t *)decoded; + if (UINT8_MAX < m->out_of_band.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->out_of_band.len) || + !amqp_encode_bytes(encoded, &offset, m->out_of_band)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CHANNEL_OPEN_OK_METHOD: { + amqp_channel_open_ok_t *m = (amqp_channel_open_ok_t *)decoded; + if (UINT32_MAX < m->channel_id.len || + !amqp_encode_32(encoded, &offset, (uint32_t)m->channel_id.len) || + !amqp_encode_bytes(encoded, &offset, m->channel_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CHANNEL_FLOW_METHOD: { + amqp_channel_flow_t *m = (amqp_channel_flow_t *)decoded; + bit_buffer = 0; + if (m->active) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CHANNEL_FLOW_OK_METHOD: { + amqp_channel_flow_ok_t *m = (amqp_channel_flow_ok_t *)decoded; + bit_buffer = 0; + if (m->active) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CHANNEL_CLOSE_METHOD: { + amqp_channel_close_t *m = (amqp_channel_close_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->reply_code)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->reply_text.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->reply_text.len) || + !amqp_encode_bytes(encoded, &offset, m->reply_text)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->class_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->method_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CHANNEL_CLOSE_OK_METHOD: { + return (int)offset; + } + case AMQP_ACCESS_REQUEST_METHOD: { + amqp_access_request_t *m = (amqp_access_request_t *)decoded; + if (UINT8_MAX < m->realm.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->realm.len) || + !amqp_encode_bytes(encoded, &offset, m->realm)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->exclusive) bit_buffer |= (1 << 0); + if (m->passive) bit_buffer |= (1 << 1); + if (m->active) bit_buffer |= (1 << 2); + if (m->write) bit_buffer |= (1 << 3); + if (m->read) bit_buffer |= (1 << 4); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_ACCESS_REQUEST_OK_METHOD: { + amqp_access_request_ok_t *m = (amqp_access_request_ok_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_EXCHANGE_DECLARE_METHOD: { + amqp_exchange_declare_t *m = (amqp_exchange_declare_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->type.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->type.len) || + !amqp_encode_bytes(encoded, &offset, m->type)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->passive) bit_buffer |= (1 << 0); + if (m->durable) bit_buffer |= (1 << 1); + if (m->auto_delete) bit_buffer |= (1 << 2); + if (m->internal) bit_buffer |= (1 << 3); + if (m->nowait) bit_buffer |= (1 << 4); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_EXCHANGE_DECLARE_OK_METHOD: { + return (int)offset; + } + case AMQP_EXCHANGE_DELETE_METHOD: { + amqp_exchange_delete_t *m = (amqp_exchange_delete_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->if_unused) bit_buffer |= (1 << 0); + if (m->nowait) bit_buffer |= (1 << 1); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_EXCHANGE_DELETE_OK_METHOD: { + return (int)offset; + } + case AMQP_EXCHANGE_BIND_METHOD: { + amqp_exchange_bind_t *m = (amqp_exchange_bind_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->destination.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->destination.len) || + !amqp_encode_bytes(encoded, &offset, m->destination)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->source.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->source.len) || + !amqp_encode_bytes(encoded, &offset, m->source)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->nowait) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_EXCHANGE_BIND_OK_METHOD: { + return (int)offset; + } + case AMQP_EXCHANGE_UNBIND_METHOD: { + amqp_exchange_unbind_t *m = (amqp_exchange_unbind_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->destination.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->destination.len) || + !amqp_encode_bytes(encoded, &offset, m->destination)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->source.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->source.len) || + !amqp_encode_bytes(encoded, &offset, m->source)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->nowait) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_EXCHANGE_UNBIND_OK_METHOD: { + return (int)offset; + } + case AMQP_QUEUE_DECLARE_METHOD: { + amqp_queue_declare_t *m = (amqp_queue_declare_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->passive) bit_buffer |= (1 << 0); + if (m->durable) bit_buffer |= (1 << 1); + if (m->exclusive) bit_buffer |= (1 << 2); + if (m->auto_delete) bit_buffer |= (1 << 3); + if (m->nowait) bit_buffer |= (1 << 4); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_QUEUE_DECLARE_OK_METHOD: { + amqp_queue_declare_ok_t *m = (amqp_queue_declare_ok_t *)decoded; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_32(encoded, &offset, m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_32(encoded, &offset, m->consumer_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_QUEUE_BIND_METHOD: { + amqp_queue_bind_t *m = (amqp_queue_bind_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->nowait) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_QUEUE_BIND_OK_METHOD: { + return (int)offset; + } + case AMQP_QUEUE_PURGE_METHOD: { + amqp_queue_purge_t *m = (amqp_queue_purge_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->nowait) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_QUEUE_PURGE_OK_METHOD: { + amqp_queue_purge_ok_t *m = (amqp_queue_purge_ok_t *)decoded; + if (!amqp_encode_32(encoded, &offset, m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_QUEUE_DELETE_METHOD: { + amqp_queue_delete_t *m = (amqp_queue_delete_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->if_unused) bit_buffer |= (1 << 0); + if (m->if_empty) bit_buffer |= (1 << 1); + if (m->nowait) bit_buffer |= (1 << 2); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_QUEUE_DELETE_OK_METHOD: { + amqp_queue_delete_ok_t *m = (amqp_queue_delete_ok_t *)decoded; + if (!amqp_encode_32(encoded, &offset, m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_QUEUE_UNBIND_METHOD: { + amqp_queue_unbind_t *m = (amqp_queue_unbind_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_QUEUE_UNBIND_OK_METHOD: { + return (int)offset; + } + case AMQP_BASIC_QOS_METHOD: { + amqp_basic_qos_t *m = (amqp_basic_qos_t *)decoded; + if (!amqp_encode_32(encoded, &offset, m->prefetch_size)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_16(encoded, &offset, m->prefetch_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->global) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_QOS_OK_METHOD: { + return (int)offset; + } + case AMQP_BASIC_CONSUME_METHOD: { + amqp_basic_consume_t *m = (amqp_basic_consume_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->consumer_tag.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->consumer_tag.len) || + !amqp_encode_bytes(encoded, &offset, m->consumer_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->no_local) bit_buffer |= (1 << 0); + if (m->no_ack) bit_buffer |= (1 << 1); + if (m->exclusive) bit_buffer |= (1 << 2); + if (m->nowait) bit_buffer |= (1 << 3); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + { + int res = amqp_encode_table(encoded, &(m->arguments), &offset); + if (res < 0) return res; + } + return (int)offset; + } + case AMQP_BASIC_CONSUME_OK_METHOD: { + amqp_basic_consume_ok_t *m = (amqp_basic_consume_ok_t *)decoded; + if (UINT8_MAX < m->consumer_tag.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->consumer_tag.len) || + !amqp_encode_bytes(encoded, &offset, m->consumer_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_CANCEL_METHOD: { + amqp_basic_cancel_t *m = (amqp_basic_cancel_t *)decoded; + if (UINT8_MAX < m->consumer_tag.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->consumer_tag.len) || + !amqp_encode_bytes(encoded, &offset, m->consumer_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->nowait) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_CANCEL_OK_METHOD: { + amqp_basic_cancel_ok_t *m = (amqp_basic_cancel_ok_t *)decoded; + if (UINT8_MAX < m->consumer_tag.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->consumer_tag.len) || + !amqp_encode_bytes(encoded, &offset, m->consumer_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_PUBLISH_METHOD: { + amqp_basic_publish_t *m = (amqp_basic_publish_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->mandatory) bit_buffer |= (1 << 0); + if (m->immediate) bit_buffer |= (1 << 1); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_RETURN_METHOD: { + amqp_basic_return_t *m = (amqp_basic_return_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->reply_code)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->reply_text.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->reply_text.len) || + !amqp_encode_bytes(encoded, &offset, m->reply_text)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_DELIVER_METHOD: { + amqp_basic_deliver_t *m = (amqp_basic_deliver_t *)decoded; + if (UINT8_MAX < m->consumer_tag.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->consumer_tag.len) || + !amqp_encode_bytes(encoded, &offset, m->consumer_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_64(encoded, &offset, m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->redelivered) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_GET_METHOD: { + amqp_basic_get_t *m = (amqp_basic_get_t *)decoded; + if (!amqp_encode_16(encoded, &offset, m->ticket)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->queue.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->queue.len) || + !amqp_encode_bytes(encoded, &offset, m->queue)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->no_ack) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_GET_OK_METHOD: { + amqp_basic_get_ok_t *m = (amqp_basic_get_ok_t *)decoded; + if (!amqp_encode_64(encoded, &offset, m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->redelivered) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->exchange.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->exchange.len) || + !amqp_encode_bytes(encoded, &offset, m->exchange)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (UINT8_MAX < m->routing_key.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->routing_key.len) || + !amqp_encode_bytes(encoded, &offset, m->routing_key)) + return AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_encode_32(encoded, &offset, m->message_count)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_GET_EMPTY_METHOD: { + amqp_basic_get_empty_t *m = (amqp_basic_get_empty_t *)decoded; + if (UINT8_MAX < m->cluster_id.len || + !amqp_encode_8(encoded, &offset, (uint8_t)m->cluster_id.len) || + !amqp_encode_bytes(encoded, &offset, m->cluster_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_ACK_METHOD: { + amqp_basic_ack_t *m = (amqp_basic_ack_t *)decoded; + if (!amqp_encode_64(encoded, &offset, m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->multiple) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_REJECT_METHOD: { + amqp_basic_reject_t *m = (amqp_basic_reject_t *)decoded; + if (!amqp_encode_64(encoded, &offset, m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->requeue) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_RECOVER_ASYNC_METHOD: { + amqp_basic_recover_async_t *m = (amqp_basic_recover_async_t *)decoded; + bit_buffer = 0; + if (m->requeue) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_RECOVER_METHOD: { + amqp_basic_recover_t *m = (amqp_basic_recover_t *)decoded; + bit_buffer = 0; + if (m->requeue) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_BASIC_RECOVER_OK_METHOD: { + return (int)offset; + } + case AMQP_BASIC_NACK_METHOD: { + amqp_basic_nack_t *m = (amqp_basic_nack_t *)decoded; + if (!amqp_encode_64(encoded, &offset, m->delivery_tag)) + return AMQP_STATUS_BAD_AMQP_DATA; + bit_buffer = 0; + if (m->multiple) bit_buffer |= (1 << 0); + if (m->requeue) bit_buffer |= (1 << 1); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_TX_SELECT_METHOD: { + return (int)offset; + } + case AMQP_TX_SELECT_OK_METHOD: { + return (int)offset; + } + case AMQP_TX_COMMIT_METHOD: { + return (int)offset; + } + case AMQP_TX_COMMIT_OK_METHOD: { + return (int)offset; + } + case AMQP_TX_ROLLBACK_METHOD: { + return (int)offset; + } + case AMQP_TX_ROLLBACK_OK_METHOD: { + return (int)offset; + } + case AMQP_CONFIRM_SELECT_METHOD: { + amqp_confirm_select_t *m = (amqp_confirm_select_t *)decoded; + bit_buffer = 0; + if (m->nowait) bit_buffer |= (1 << 0); + if (!amqp_encode_8(encoded, &offset, bit_buffer)) + return AMQP_STATUS_BAD_AMQP_DATA; + return (int)offset; + } + case AMQP_CONFIRM_SELECT_OK_METHOD: { + return (int)offset; + } + default: + return AMQP_STATUS_UNKNOWN_METHOD; + } +} + +int amqp_encode_properties(uint16_t class_id, void *decoded, + amqp_bytes_t encoded) { + size_t offset = 0; + + /* Cheat, and get the flags out generically, relying on the + similarity of structure between classes */ + amqp_flags_t flags = *(amqp_flags_t *)decoded; /* cheating! */ + + { + /* We take a copy of flags to avoid destroying it, as it is used + in the autogenerated code below. */ + amqp_flags_t remaining_flags = flags; + do { + amqp_flags_t remainder = remaining_flags >> 16; + uint16_t partial_flags = remaining_flags & 0xFFFE; + if (remainder != 0) { + partial_flags |= 1; + } + if (!amqp_encode_16(encoded, &offset, partial_flags)) + return AMQP_STATUS_BAD_AMQP_DATA; + remaining_flags = remainder; + } while (remaining_flags != 0); + } + + switch (class_id) { + case 10: { + return (int)offset; + } + case 20: { + return (int)offset; + } + case 30: { + return (int)offset; + } + case 40: { + return (int)offset; + } + case 50: { + return (int)offset; + } + case 60: { + amqp_basic_properties_t *p = (amqp_basic_properties_t *)decoded; + if (flags & AMQP_BASIC_CONTENT_TYPE_FLAG) { + if (UINT8_MAX < p->content_type.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->content_type.len) || + !amqp_encode_bytes(encoded, &offset, p->content_type)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_CONTENT_ENCODING_FLAG) { + if (UINT8_MAX < p->content_encoding.len || + !amqp_encode_8(encoded, &offset, + (uint8_t)p->content_encoding.len) || + !amqp_encode_bytes(encoded, &offset, p->content_encoding)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_HEADERS_FLAG) { + { + int res = amqp_encode_table(encoded, &(p->headers), &offset); + if (res < 0) return res; + } + } + if (flags & AMQP_BASIC_DELIVERY_MODE_FLAG) { + if (!amqp_encode_8(encoded, &offset, p->delivery_mode)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_PRIORITY_FLAG) { + if (!amqp_encode_8(encoded, &offset, p->priority)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_CORRELATION_ID_FLAG) { + if (UINT8_MAX < p->correlation_id.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->correlation_id.len) || + !amqp_encode_bytes(encoded, &offset, p->correlation_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_REPLY_TO_FLAG) { + if (UINT8_MAX < p->reply_to.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->reply_to.len) || + !amqp_encode_bytes(encoded, &offset, p->reply_to)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_EXPIRATION_FLAG) { + if (UINT8_MAX < p->expiration.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->expiration.len) || + !amqp_encode_bytes(encoded, &offset, p->expiration)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_MESSAGE_ID_FLAG) { + if (UINT8_MAX < p->message_id.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->message_id.len) || + !amqp_encode_bytes(encoded, &offset, p->message_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_TIMESTAMP_FLAG) { + if (!amqp_encode_64(encoded, &offset, p->timestamp)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_TYPE_FLAG) { + if (UINT8_MAX < p->type.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->type.len) || + !amqp_encode_bytes(encoded, &offset, p->type)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_USER_ID_FLAG) { + if (UINT8_MAX < p->user_id.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->user_id.len) || + !amqp_encode_bytes(encoded, &offset, p->user_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_APP_ID_FLAG) { + if (UINT8_MAX < p->app_id.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->app_id.len) || + !amqp_encode_bytes(encoded, &offset, p->app_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + if (flags & AMQP_BASIC_CLUSTER_ID_FLAG) { + if (UINT8_MAX < p->cluster_id.len || + !amqp_encode_8(encoded, &offset, (uint8_t)p->cluster_id.len) || + !amqp_encode_bytes(encoded, &offset, p->cluster_id)) + return AMQP_STATUS_BAD_AMQP_DATA; + } + return (int)offset; + } + case 90: { + return (int)offset; + } + case 85: { + return (int)offset; + } + default: + return AMQP_STATUS_UNKNOWN_CLASS; + } +} + +/** + * amqp_connection_update_secret + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] new_secret new_secret + * @param [in] reason reason + * @returns amqp_connection_update_secret_ok_t + */ +AMQP_EXPORT +amqp_connection_update_secret_ok_t *AMQP_CALL amqp_connection_update_secret( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t new_secret, amqp_bytes_t reason) { + amqp_connection_update_secret_t req; + req.new_secret = new_secret; + req.reason = reason; + + return amqp_simple_rpc_decoded(state, channel, + AMQP_CONNECTION_UPDATE_SECRET_METHOD, + AMQP_CONNECTION_UPDATE_SECRET_OK_METHOD, &req); +} + +/** + * amqp_channel_open + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_channel_open_ok_t + */ +AMQP_EXPORT +amqp_channel_open_ok_t *AMQP_CALL + amqp_channel_open(amqp_connection_state_t state, amqp_channel_t channel) { + amqp_channel_open_t req; + req.out_of_band = amqp_empty_bytes; + + return amqp_simple_rpc_decoded(state, channel, AMQP_CHANNEL_OPEN_METHOD, + AMQP_CHANNEL_OPEN_OK_METHOD, &req); +} + +/** + * amqp_channel_flow + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] active active + * @returns amqp_channel_flow_ok_t + */ +AMQP_EXPORT +amqp_channel_flow_ok_t *AMQP_CALL + amqp_channel_flow(amqp_connection_state_t state, amqp_channel_t channel, + amqp_boolean_t active) { + amqp_channel_flow_t req; + req.active = active; + + return amqp_simple_rpc_decoded(state, channel, AMQP_CHANNEL_FLOW_METHOD, + AMQP_CHANNEL_FLOW_OK_METHOD, &req); +} + +/** + * amqp_exchange_declare + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] exchange exchange + * @param [in] type type + * @param [in] passive passive + * @param [in] durable durable + * @param [in] auto_delete auto_delete + * @param [in] internal internal + * @param [in] arguments arguments + * @returns amqp_exchange_declare_ok_t + */ +AMQP_EXPORT +amqp_exchange_declare_ok_t *AMQP_CALL amqp_exchange_declare( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t exchange, amqp_bytes_t type, amqp_boolean_t passive, + amqp_boolean_t durable, amqp_boolean_t auto_delete, amqp_boolean_t internal, + amqp_table_t arguments) { + amqp_exchange_declare_t req; + req.ticket = 0; + req.exchange = exchange; + req.type = type; + req.passive = passive; + req.durable = durable; + req.auto_delete = auto_delete; + req.internal = internal; + req.nowait = 0; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_EXCHANGE_DECLARE_METHOD, + AMQP_EXCHANGE_DECLARE_OK_METHOD, &req); +} + +/** + * amqp_exchange_delete + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] exchange exchange + * @param [in] if_unused if_unused + * @returns amqp_exchange_delete_ok_t + */ +AMQP_EXPORT +amqp_exchange_delete_ok_t *AMQP_CALL + amqp_exchange_delete(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t exchange, amqp_boolean_t if_unused) { + amqp_exchange_delete_t req; + req.ticket = 0; + req.exchange = exchange; + req.if_unused = if_unused; + req.nowait = 0; + + return amqp_simple_rpc_decoded(state, channel, AMQP_EXCHANGE_DELETE_METHOD, + AMQP_EXCHANGE_DELETE_OK_METHOD, &req); +} + +/** + * amqp_exchange_bind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] destination destination + * @param [in] source source + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_exchange_bind_ok_t + */ +AMQP_EXPORT +amqp_exchange_bind_ok_t *AMQP_CALL + amqp_exchange_bind(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t destination, amqp_bytes_t source, + amqp_bytes_t routing_key, amqp_table_t arguments) { + amqp_exchange_bind_t req; + req.ticket = 0; + req.destination = destination; + req.source = source; + req.routing_key = routing_key; + req.nowait = 0; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_EXCHANGE_BIND_METHOD, + AMQP_EXCHANGE_BIND_OK_METHOD, &req); +} + +/** + * amqp_exchange_unbind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] destination destination + * @param [in] source source + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_exchange_unbind_ok_t + */ +AMQP_EXPORT +amqp_exchange_unbind_ok_t *AMQP_CALL + amqp_exchange_unbind(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t destination, amqp_bytes_t source, + amqp_bytes_t routing_key, amqp_table_t arguments) { + amqp_exchange_unbind_t req; + req.ticket = 0; + req.destination = destination; + req.source = source; + req.routing_key = routing_key; + req.nowait = 0; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_EXCHANGE_UNBIND_METHOD, + AMQP_EXCHANGE_UNBIND_OK_METHOD, &req); +} + +/** + * amqp_queue_declare + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] passive passive + * @param [in] durable durable + * @param [in] exclusive exclusive + * @param [in] auto_delete auto_delete + * @param [in] arguments arguments + * @returns amqp_queue_declare_ok_t + */ +AMQP_EXPORT +amqp_queue_declare_ok_t *AMQP_CALL amqp_queue_declare( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_boolean_t passive, amqp_boolean_t durable, amqp_boolean_t exclusive, + amqp_boolean_t auto_delete, amqp_table_t arguments) { + amqp_queue_declare_t req; + req.ticket = 0; + req.queue = queue; + req.passive = passive; + req.durable = durable; + req.exclusive = exclusive; + req.auto_delete = auto_delete; + req.nowait = 0; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_QUEUE_DECLARE_METHOD, + AMQP_QUEUE_DECLARE_OK_METHOD, &req); +} + +/** + * amqp_queue_bind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] exchange exchange + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_queue_bind_ok_t + */ +AMQP_EXPORT +amqp_queue_bind_ok_t *AMQP_CALL amqp_queue_bind( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments) { + amqp_queue_bind_t req; + req.ticket = 0; + req.queue = queue; + req.exchange = exchange; + req.routing_key = routing_key; + req.nowait = 0; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_QUEUE_BIND_METHOD, + AMQP_QUEUE_BIND_OK_METHOD, &req); +} + +/** + * amqp_queue_purge + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @returns amqp_queue_purge_ok_t + */ +AMQP_EXPORT +amqp_queue_purge_ok_t *AMQP_CALL amqp_queue_purge(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_bytes_t queue) { + amqp_queue_purge_t req; + req.ticket = 0; + req.queue = queue; + req.nowait = 0; + + return amqp_simple_rpc_decoded(state, channel, AMQP_QUEUE_PURGE_METHOD, + AMQP_QUEUE_PURGE_OK_METHOD, &req); +} + +/** + * amqp_queue_delete + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] if_unused if_unused + * @param [in] if_empty if_empty + * @returns amqp_queue_delete_ok_t + */ +AMQP_EXPORT +amqp_queue_delete_ok_t *AMQP_CALL amqp_queue_delete( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_boolean_t if_unused, amqp_boolean_t if_empty) { + amqp_queue_delete_t req; + req.ticket = 0; + req.queue = queue; + req.if_unused = if_unused; + req.if_empty = if_empty; + req.nowait = 0; + + return amqp_simple_rpc_decoded(state, channel, AMQP_QUEUE_DELETE_METHOD, + AMQP_QUEUE_DELETE_OK_METHOD, &req); +} + +/** + * amqp_queue_unbind + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] exchange exchange + * @param [in] routing_key routing_key + * @param [in] arguments arguments + * @returns amqp_queue_unbind_ok_t + */ +AMQP_EXPORT +amqp_queue_unbind_ok_t *AMQP_CALL amqp_queue_unbind( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_bytes_t exchange, amqp_bytes_t routing_key, amqp_table_t arguments) { + amqp_queue_unbind_t req; + req.ticket = 0; + req.queue = queue; + req.exchange = exchange; + req.routing_key = routing_key; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_QUEUE_UNBIND_METHOD, + AMQP_QUEUE_UNBIND_OK_METHOD, &req); +} + +/** + * amqp_basic_qos + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] prefetch_size prefetch_size + * @param [in] prefetch_count prefetch_count + * @param [in] global global + * @returns amqp_basic_qos_ok_t + */ +AMQP_EXPORT +amqp_basic_qos_ok_t *AMQP_CALL amqp_basic_qos(amqp_connection_state_t state, + amqp_channel_t channel, + uint32_t prefetch_size, + uint16_t prefetch_count, + amqp_boolean_t global) { + amqp_basic_qos_t req; + req.prefetch_size = prefetch_size; + req.prefetch_count = prefetch_count; + req.global = global; + + return amqp_simple_rpc_decoded(state, channel, AMQP_BASIC_QOS_METHOD, + AMQP_BASIC_QOS_OK_METHOD, &req); +} + +/** + * amqp_basic_consume + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] queue queue + * @param [in] consumer_tag consumer_tag + * @param [in] no_local no_local + * @param [in] no_ack no_ack + * @param [in] exclusive exclusive + * @param [in] arguments arguments + * @returns amqp_basic_consume_ok_t + */ +AMQP_EXPORT +amqp_basic_consume_ok_t *AMQP_CALL amqp_basic_consume( + amqp_connection_state_t state, amqp_channel_t channel, amqp_bytes_t queue, + amqp_bytes_t consumer_tag, amqp_boolean_t no_local, amqp_boolean_t no_ack, + amqp_boolean_t exclusive, amqp_table_t arguments) { + amqp_basic_consume_t req; + req.ticket = 0; + req.queue = queue; + req.consumer_tag = consumer_tag; + req.no_local = no_local; + req.no_ack = no_ack; + req.exclusive = exclusive; + req.nowait = 0; + req.arguments = arguments; + + return amqp_simple_rpc_decoded(state, channel, AMQP_BASIC_CONSUME_METHOD, + AMQP_BASIC_CONSUME_OK_METHOD, &req); +} + +/** + * amqp_basic_cancel + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] consumer_tag consumer_tag + * @returns amqp_basic_cancel_ok_t + */ +AMQP_EXPORT +amqp_basic_cancel_ok_t *AMQP_CALL + amqp_basic_cancel(amqp_connection_state_t state, amqp_channel_t channel, + amqp_bytes_t consumer_tag) { + amqp_basic_cancel_t req; + req.consumer_tag = consumer_tag; + req.nowait = 0; + + return amqp_simple_rpc_decoded(state, channel, AMQP_BASIC_CANCEL_METHOD, + AMQP_BASIC_CANCEL_OK_METHOD, &req); +} + +/** + * amqp_basic_recover + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @param [in] requeue requeue + * @returns amqp_basic_recover_ok_t + */ +AMQP_EXPORT +amqp_basic_recover_ok_t *AMQP_CALL + amqp_basic_recover(amqp_connection_state_t state, amqp_channel_t channel, + amqp_boolean_t requeue) { + amqp_basic_recover_t req; + req.requeue = requeue; + + return amqp_simple_rpc_decoded(state, channel, AMQP_BASIC_RECOVER_METHOD, + AMQP_BASIC_RECOVER_OK_METHOD, &req); +} + +/** + * amqp_tx_select + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_tx_select_ok_t + */ +AMQP_EXPORT +amqp_tx_select_ok_t *AMQP_CALL amqp_tx_select(amqp_connection_state_t state, + amqp_channel_t channel) { + amqp_tx_select_t req; + + return amqp_simple_rpc_decoded(state, channel, AMQP_TX_SELECT_METHOD, + AMQP_TX_SELECT_OK_METHOD, &req); +} + +/** + * amqp_tx_commit + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_tx_commit_ok_t + */ +AMQP_EXPORT +amqp_tx_commit_ok_t *AMQP_CALL amqp_tx_commit(amqp_connection_state_t state, + amqp_channel_t channel) { + amqp_tx_commit_t req; + + return amqp_simple_rpc_decoded(state, channel, AMQP_TX_COMMIT_METHOD, + AMQP_TX_COMMIT_OK_METHOD, &req); +} + +/** + * amqp_tx_rollback + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_tx_rollback_ok_t + */ +AMQP_EXPORT +amqp_tx_rollback_ok_t *AMQP_CALL amqp_tx_rollback(amqp_connection_state_t state, + amqp_channel_t channel) { + amqp_tx_rollback_t req; + + return amqp_simple_rpc_decoded(state, channel, AMQP_TX_ROLLBACK_METHOD, + AMQP_TX_ROLLBACK_OK_METHOD, &req); +} + +/** + * amqp_confirm_select + * + * @param [in] state connection state + * @param [in] channel the channel to do the RPC on + * @returns amqp_confirm_select_ok_t + */ +AMQP_EXPORT +amqp_confirm_select_ok_t *AMQP_CALL + amqp_confirm_select(amqp_connection_state_t state, amqp_channel_t channel) { + amqp_confirm_select_t req; + req.nowait = 0; + + return amqp_simple_rpc_decoded(state, channel, AMQP_CONFIRM_SELECT_METHOD, + AMQP_CONFIRM_SELECT_OK_METHOD, &req); +} diff --git a/librabbitmq/amqp_mem.c b/librabbitmq/amqp_mem.c new file mode 100644 index 0000000..d03591c --- /dev/null +++ b/librabbitmq/amqp_mem.c @@ -0,0 +1,210 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amqp_private.h" +#include +#include +#include +#include +#include +#include + +char const *amqp_version(void) { return AMQP_VERSION_STRING; } + +uint32_t amqp_version_number(void) { return AMQP_VERSION; } + +void init_amqp_pool(amqp_pool_t *pool, size_t pagesize) { + pool->pagesize = pagesize ? pagesize : 4096; + + pool->pages.num_blocks = 0; + pool->pages.blocklist = NULL; + + pool->large_blocks.num_blocks = 0; + pool->large_blocks.blocklist = NULL; + + pool->next_page = 0; + pool->alloc_block = NULL; + pool->alloc_used = 0; +} + +static void empty_blocklist(amqp_pool_blocklist_t *x) { + int i; + + if (x->blocklist != NULL) { + for (i = 0; i < x->num_blocks; i++) { + free(x->blocklist[i]); + } + free(x->blocklist); + } + x->num_blocks = 0; + x->blocklist = NULL; +} + +void recycle_amqp_pool(amqp_pool_t *pool) { + empty_blocklist(&pool->large_blocks); + pool->next_page = 0; + pool->alloc_block = NULL; + pool->alloc_used = 0; +} + +void empty_amqp_pool(amqp_pool_t *pool) { + recycle_amqp_pool(pool); + empty_blocklist(&pool->pages); +} + +/* Returns 1 on success, 0 on failure */ +static int record_pool_block(amqp_pool_blocklist_t *x, void *block) { + size_t blocklistlength = sizeof(void *) * (x->num_blocks + 1); + + if (x->blocklist == NULL) { + x->blocklist = malloc(blocklistlength); + if (x->blocklist == NULL) { + return 0; + } + } else { + void *newbl = realloc(x->blocklist, blocklistlength); + if (newbl == NULL) { + return 0; + } + x->blocklist = newbl; + } + + x->blocklist[x->num_blocks] = block; + x->num_blocks++; + return 1; +} + +void *amqp_pool_alloc(amqp_pool_t *pool, size_t amount) { + if (amount == 0) { + return NULL; + } + + amount = (amount + 7) & (~7); /* round up to nearest 8-byte boundary */ + + if (amount > pool->pagesize) { + void *result = calloc(1, amount); + if (result == NULL) { + return NULL; + } + if (!record_pool_block(&pool->large_blocks, result)) { + free(result); + return NULL; + } + return result; + } + + if (pool->alloc_block != NULL) { + assert(pool->alloc_used <= pool->pagesize); + + if (pool->alloc_used + amount <= pool->pagesize) { + void *result = pool->alloc_block + pool->alloc_used; + pool->alloc_used += amount; + return result; + } + } + + if (pool->next_page >= pool->pages.num_blocks) { + pool->alloc_block = calloc(1, pool->pagesize); + if (pool->alloc_block == NULL) { + return NULL; + } + if (!record_pool_block(&pool->pages, pool->alloc_block)) { + return NULL; + } + pool->next_page = pool->pages.num_blocks; + } else { + pool->alloc_block = pool->pages.blocklist[pool->next_page]; + pool->next_page++; + } + + pool->alloc_used = amount; + + return pool->alloc_block; +} + +void amqp_pool_alloc_bytes(amqp_pool_t *pool, size_t amount, + amqp_bytes_t *output) { + output->len = amount; + output->bytes = amqp_pool_alloc(pool, amount); +} + +amqp_bytes_t amqp_cstring_bytes(char const *cstr) { + amqp_bytes_t result; + result.len = strlen(cstr); + result.bytes = (void *)cstr; + return result; +} + +amqp_bytes_t amqp_bytes_malloc_dup(amqp_bytes_t src) { + amqp_bytes_t result; + result.len = src.len; + result.bytes = malloc(src.len); + if (result.bytes != NULL) { + memcpy(result.bytes, src.bytes, src.len); + } + return result; +} + +amqp_bytes_t amqp_bytes_malloc(size_t amount) { + amqp_bytes_t result; + result.len = amount; + result.bytes = malloc(amount); /* will return NULL if it fails */ + return result; +} + +void amqp_bytes_free(amqp_bytes_t bytes) { free(bytes.bytes); } + +amqp_pool_t *amqp_get_or_create_channel_pool(amqp_connection_state_t state, + amqp_channel_t channel) { + amqp_pool_table_entry_t *entry; + size_t index = channel % POOL_TABLE_SIZE; + + entry = state->pool_table[index]; + + for (; NULL != entry; entry = entry->next) { + if (channel == entry->channel) { + return &entry->pool; + } + } + + entry = malloc(sizeof(amqp_pool_table_entry_t)); + if (NULL == entry) { + return NULL; + } + + entry->channel = channel; + entry->next = state->pool_table[index]; + state->pool_table[index] = entry; + + init_amqp_pool(&entry->pool, state->frame_max); + + return &entry->pool; +} + +amqp_pool_t *amqp_get_channel_pool(amqp_connection_state_t state, + amqp_channel_t channel) { + amqp_pool_table_entry_t *entry; + size_t index = channel % POOL_TABLE_SIZE; + + entry = state->pool_table[index]; + + for (; NULL != entry; entry = entry->next) { + if (channel == entry->channel) { + return &entry->pool; + } + } + + return NULL; +} + +int amqp_bytes_equal(amqp_bytes_t r, amqp_bytes_t l) { + if (r.len == l.len && + (r.bytes == l.bytes || 0 == memcmp(r.bytes, l.bytes, r.len))) { + return 1; + } + return 0; +} diff --git a/librabbitmq/amqp_openssl.c b/librabbitmq/amqp_openssl.c new file mode 100644 index 0000000..ac37b3f --- /dev/null +++ b/librabbitmq/amqp_openssl.c @@ -0,0 +1,669 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +// Use OpenSSL v1.1.1 API. +#define OPENSSL_API_COMPAT 10101 + +#include "amqp_openssl_bio.h" +#include "amqp_private.h" +#include "amqp_socket.h" +#include "amqp_time.h" +#include "rabbitmq-c/ssl_socket.h" +#include "threads.h" + +#include +#include +#include +#include +#ifdef ENABLE_SSL_ENGINE_API +#include +#endif +#include +#include +#include +#include +#include +#include + +static int initialize_ssl_and_increment_connections(void); +static int decrement_ssl_connections(void); + +static pthread_mutex_t openssl_init_mutex = PTHREAD_MUTEX_INITIALIZER; +static amqp_boolean_t openssl_bio_initialized = 0; +static int openssl_connections = 0; +#ifdef ENABLE_SSL_ENGINE_API +static ENGINE *openssl_engine = NULL; +#endif + +#define CHECK_SUCCESS(condition) \ + do { \ + int check_success_ret = (condition); \ + if (check_success_ret) { \ + amqp_abort("Check %s failed <%d>: %s", #condition, check_success_ret, \ + strerror(check_success_ret)); \ + } \ + } while (0) + +struct amqp_ssl_socket_t { + const struct amqp_socket_class_t *klass; + SSL_CTX *ctx; + int sockfd; + SSL *ssl; + amqp_boolean_t verify_peer; + amqp_boolean_t verify_hostname; + int internal_error; +}; + +static ssize_t amqp_ssl_socket_send(void *base, const void *buf, size_t len, + AMQP_UNUSED int flags) { + struct amqp_ssl_socket_t *self = (struct amqp_ssl_socket_t *)base; + int res; + if (-1 == self->sockfd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + + /* SSL_write takes an int for length of buffer, protect against len being + * larger than larger than what SSL_write can take */ + if (len > INT_MAX) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + ERR_clear_error(); + self->internal_error = 0; + + /* This will only return on error, or once the whole buffer has been + * written to the SSL stream. See SSL_MODE_ENABLE_PARTIAL_WRITE */ + res = SSL_write(self->ssl, buf, (int)len); + if (0 >= res) { + self->internal_error = SSL_get_error(self->ssl, res); + /* TODO: Close connection if it isn't already? */ + /* TODO: Possibly be more intelligent in reporting WHAT went wrong */ + switch (self->internal_error) { + case SSL_ERROR_WANT_READ: + res = AMQP_PRIVATE_STATUS_SOCKET_NEEDREAD; + break; + case SSL_ERROR_WANT_WRITE: + res = AMQP_PRIVATE_STATUS_SOCKET_NEEDWRITE; + break; + case SSL_ERROR_ZERO_RETURN: + res = AMQP_STATUS_CONNECTION_CLOSED; + break; + default: + res = AMQP_STATUS_SSL_ERROR; + break; + } + } else { + self->internal_error = 0; + } + + return (ssize_t)res; +} + +static ssize_t amqp_ssl_socket_recv(void *base, void *buf, size_t len, + AMQP_UNUSED int flags) { + struct amqp_ssl_socket_t *self = (struct amqp_ssl_socket_t *)base; + int received; + if (-1 == self->sockfd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + + /* SSL_read takes an int for length of buffer, protect against len being + * larger than larger than what SSL_read can take */ + if (len > INT_MAX) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + ERR_clear_error(); + self->internal_error = 0; + + received = SSL_read(self->ssl, buf, (int)len); + if (0 >= received) { + self->internal_error = SSL_get_error(self->ssl, received); + switch (self->internal_error) { + case SSL_ERROR_WANT_READ: + received = AMQP_PRIVATE_STATUS_SOCKET_NEEDREAD; + break; + case SSL_ERROR_WANT_WRITE: + received = AMQP_PRIVATE_STATUS_SOCKET_NEEDWRITE; + break; + case SSL_ERROR_ZERO_RETURN: + received = AMQP_STATUS_CONNECTION_CLOSED; + break; + default: + received = AMQP_STATUS_SSL_ERROR; + break; + } + } + + return (ssize_t)received; +} + +static int amqp_ssl_socket_open(void *base, const char *host, int port, + const struct timeval *timeout) { + struct amqp_ssl_socket_t *self = (struct amqp_ssl_socket_t *)base; + long result; + int status; + amqp_time_t deadline; + X509 *cert; + BIO *bio; + if (-1 != self->sockfd) { + return AMQP_STATUS_SOCKET_INUSE; + } + ERR_clear_error(); + + self->ssl = SSL_new(self->ctx); + if (!self->ssl) { + self->internal_error = ERR_peek_error(); + status = AMQP_STATUS_SSL_ERROR; + goto exit; + } + + status = amqp_time_from_now(&deadline, timeout); + if (AMQP_STATUS_OK != status) { + return status; + } + + self->sockfd = amqp_open_socket_inner(host, port, deadline); + if (0 > self->sockfd) { + status = self->sockfd; + self->internal_error = amqp_os_socket_error(); + self->sockfd = -1; + goto error_out1; + } + + bio = BIO_new(amqp_openssl_bio()); + if (!bio) { + status = AMQP_STATUS_NO_MEMORY; + goto error_out2; + } + + BIO_set_fd(bio, self->sockfd, BIO_NOCLOSE); + SSL_set_bio(self->ssl, bio, bio); + + status = SSL_set_tlsext_host_name(self->ssl, host); + if (!status) { + self->internal_error = SSL_get_error(self->ssl, status); + status = AMQP_STATUS_SSL_ERROR; + goto error_out2; + } + +start_connect: + status = SSL_connect(self->ssl); + if (status != 1) { + self->internal_error = SSL_get_error(self->ssl, status); + switch (self->internal_error) { + case SSL_ERROR_WANT_READ: + status = amqp_poll(self->sockfd, AMQP_SF_POLLIN, deadline); + break; + case SSL_ERROR_WANT_WRITE: + status = amqp_poll(self->sockfd, AMQP_SF_POLLOUT, deadline); + break; + default: + status = AMQP_STATUS_SSL_CONNECTION_FAILED; + } + if (AMQP_STATUS_OK == status) { + goto start_connect; + } + goto error_out2; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + cert = SSL_get_peer_certificate(self->ssl); +#else + cert = SSL_get1_peer_certificate(self->ssl); +#endif + + if (self->verify_peer) { + if (!cert) { + self->internal_error = 0; + status = AMQP_STATUS_SSL_PEER_VERIFY_FAILED; + goto error_out3; + } + + result = SSL_get_verify_result(self->ssl); + if (X509_V_OK != result) { + self->internal_error = result; + status = AMQP_STATUS_SSL_PEER_VERIFY_FAILED; + goto error_out4; + } + } + if (self->verify_hostname) { + if (!cert) { + self->internal_error = 0; + status = AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED; + goto error_out3; + } + + if (1 != X509_check_host(cert, host, strlen(host), + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS, NULL)) { + self->internal_error = 0; + status = AMQP_STATUS_SSL_HOSTNAME_VERIFY_FAILED; + goto error_out4; + } + } + + X509_free(cert); + self->internal_error = 0; + status = AMQP_STATUS_OK; + +exit: + return status; + +error_out4: + X509_free(cert); +error_out3: + SSL_shutdown(self->ssl); +error_out2: + amqp_os_socket_close(self->sockfd); + self->sockfd = -1; +error_out1: + SSL_free(self->ssl); + self->ssl = NULL; + goto exit; +} + +static int amqp_ssl_socket_close(void *base, amqp_socket_close_enum force) { + struct amqp_ssl_socket_t *self = (struct amqp_ssl_socket_t *)base; + + if (-1 == self->sockfd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + + if (AMQP_SC_NONE == force) { + /* don't try too hard to shutdown the connection */ + SSL_shutdown(self->ssl); + } + + SSL_free(self->ssl); + self->ssl = NULL; + + if (amqp_os_socket_close(self->sockfd)) { + return AMQP_STATUS_SOCKET_ERROR; + } + self->sockfd = -1; + + return AMQP_STATUS_OK; +} + +static int amqp_ssl_socket_get_sockfd(void *base) { + struct amqp_ssl_socket_t *self = (struct amqp_ssl_socket_t *)base; + return self->sockfd; +} + +static void amqp_ssl_socket_delete(void *base) { + struct amqp_ssl_socket_t *self = (struct amqp_ssl_socket_t *)base; + + if (self) { + amqp_ssl_socket_close(self, AMQP_SC_NONE); + + SSL_CTX_free(self->ctx); + free(self); + } + decrement_ssl_connections(); +} + +static const struct amqp_socket_class_t amqp_ssl_socket_class = { + amqp_ssl_socket_send, /* send */ + amqp_ssl_socket_recv, /* recv */ + amqp_ssl_socket_open, /* open */ + amqp_ssl_socket_close, /* close */ + amqp_ssl_socket_get_sockfd, /* get_sockfd */ + amqp_ssl_socket_delete /* delete */ +}; + +amqp_socket_t *amqp_ssl_socket_new(amqp_connection_state_t state) { + struct amqp_ssl_socket_t *self = calloc(1, sizeof(*self)); + int status; + if (!self) { + return NULL; + } + + self->sockfd = -1; + self->klass = &amqp_ssl_socket_class; + self->verify_peer = 1; + self->verify_hostname = 1; + + status = initialize_ssl_and_increment_connections(); + if (status) { + goto error; + } + + self->ctx = SSL_CTX_new(TLS_client_method()); + if (!self->ctx) { + goto error; + } + status = amqp_ssl_socket_set_ssl_versions((amqp_socket_t *)self, AMQP_TLSv1_2, + AMQP_TLSvLATEST); + if (status != AMQP_STATUS_OK) { + goto error; + } + + SSL_CTX_set_mode(self->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + /* OpenSSL v1.1.1 turns this on by default, which makes the non-blocking + * logic not behave as expected, so turn this back off */ + SSL_CTX_clear_mode(self->ctx, SSL_MODE_AUTO_RETRY); + + amqp_set_socket(state, (amqp_socket_t *)self); + + return (amqp_socket_t *)self; +error: + amqp_ssl_socket_delete((amqp_socket_t *)self); + return NULL; +} + +void *amqp_ssl_socket_get_context(amqp_socket_t *base) { + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + return ((struct amqp_ssl_socket_t *)base)->ctx; +} + +int amqp_ssl_socket_enable_default_verify_paths(amqp_socket_t *base) { + int status; + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + status = SSL_CTX_set_default_verify_paths(self->ctx); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + return AMQP_STATUS_OK; +} + +int amqp_ssl_socket_set_cacert(amqp_socket_t *base, const char *cacert) { + int status; + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + status = SSL_CTX_load_verify_locations(self->ctx, cacert, NULL); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + return AMQP_STATUS_OK; +} + +int amqp_ssl_socket_set_key(amqp_socket_t *base, const char *cert, + const char *key) { + int status; + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + status = SSL_CTX_use_certificate_chain_file(self->ctx, cert); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + status = SSL_CTX_use_PrivateKey_file(self->ctx, key, SSL_FILETYPE_PEM); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + return AMQP_STATUS_OK; +} + +int amqp_ssl_socket_set_key_engine(amqp_socket_t *base, const char *cert, + const char *key) { +#ifdef ENABLE_SSL_ENGINE_API + int status; + struct amqp_ssl_socket_t *self; + EVP_PKEY *pkey = NULL; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + status = SSL_CTX_use_certificate_chain_file(self->ctx, cert); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + + pkey = ENGINE_load_private_key(openssl_engine, key, NULL, NULL); + if (pkey == NULL) { + return AMQP_STATUS_SSL_ERROR; + } + + status = SSL_CTX_use_PrivateKey(self->ctx, pkey); + EVP_PKEY_free(pkey); + + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + return AMQP_STATUS_OK; +#else + return AMQP_STATUS_SSL_UNIMPLEMENTED; +#endif +} + +static int password_cb(AMQP_UNUSED char *buffer, AMQP_UNUSED int length, + AMQP_UNUSED int rwflag, AMQP_UNUSED void *user_data) { + amqp_abort("rabbitmq-c does not support password protected keys"); +} + +int amqp_ssl_socket_set_key_buffer(amqp_socket_t *base, const char *cert, + const void *key, size_t n) { + int status = AMQP_STATUS_OK; + BIO *buf = NULL; + RSA *rsa = NULL; + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + if (n > INT_MAX) { + return AMQP_STATUS_INVALID_PARAMETER; + } + self = (struct amqp_ssl_socket_t *)base; + status = SSL_CTX_use_certificate_chain_file(self->ctx, cert); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + buf = BIO_new_mem_buf((void *)key, (int)n); + if (!buf) { + goto error; + } + rsa = PEM_read_bio_RSAPrivateKey(buf, NULL, password_cb, NULL); + if (!rsa) { + goto error; + } + status = SSL_CTX_use_RSAPrivateKey(self->ctx, rsa); + if (1 != status) { + goto error; + } + status = AMQP_STATUS_OK; +exit: + BIO_vfree(buf); + RSA_free(rsa); + return status; +error: + status = AMQP_STATUS_SSL_ERROR; + goto exit; +} + +int amqp_ssl_socket_set_cert(amqp_socket_t *base, const char *cert) { + int status; + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + status = SSL_CTX_use_certificate_chain_file(self->ctx, cert); + if (1 != status) { + return AMQP_STATUS_SSL_ERROR; + } + return AMQP_STATUS_OK; +} + +void amqp_ssl_socket_set_key_passwd(amqp_socket_t *base, const char *passwd) { + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + SSL_CTX_set_default_passwd_cb_userdata(self->ctx, (void *)passwd); +} + +void amqp_ssl_socket_set_verify(amqp_socket_t *base, amqp_boolean_t verify) { + amqp_ssl_socket_set_verify_peer(base, verify); + amqp_ssl_socket_set_verify_hostname(base, verify); +} + +void amqp_ssl_socket_set_verify_peer(amqp_socket_t *base, + amqp_boolean_t verify) { + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + self->verify_peer = verify; +} + +void amqp_ssl_socket_set_verify_hostname(amqp_socket_t *base, + amqp_boolean_t verify) { + struct amqp_ssl_socket_t *self; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + self->verify_hostname = verify; +} + +static int get_tls_version(amqp_tls_version_t ver, int *tls_version) { + switch (ver) { + case AMQP_TLSv1_2: + *tls_version = TLS1_2_VERSION; + break; + case AMQP_TLSv1_3: + case AMQP_TLSvLATEST: + *tls_version = TLS1_3_VERSION; + break; + default: + return AMQP_STATUS_UNSUPPORTED; + } + return AMQP_STATUS_OK; +} + +int amqp_ssl_socket_set_ssl_versions(amqp_socket_t *base, + amqp_tls_version_t min, + amqp_tls_version_t max) { + struct amqp_ssl_socket_t *self; + int min_ver; + int max_ver; + int status; + if (base->klass != &amqp_ssl_socket_class) { + amqp_abort("<%p> is not of type amqp_ssl_socket_t", base); + } + self = (struct amqp_ssl_socket_t *)base; + + if (max < min) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + status = get_tls_version(min, &min_ver); + if (status != AMQP_STATUS_OK) { + return status; + } + + status = get_tls_version(max, &max_ver); + if (status != AMQP_STATUS_OK) { + return status; + } + + if (!SSL_CTX_set_min_proto_version(self->ctx, min_ver)) { + return AMQP_STATUS_INVALID_PARAMETER; + } + if (!SSL_CTX_set_max_proto_version(self->ctx, max_ver)) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + return AMQP_STATUS_OK; +} + +void amqp_set_initialize_ssl_library(amqp_boolean_t do_initialize) { + (void)do_initialize; + return; +} + +int amqp_initialize_ssl_library(void) { return AMQP_STATUS_OK; } + +int amqp_set_ssl_engine(const char *engine) { +#ifdef ENABLE_SSL_ENGINE_API + int status = AMQP_STATUS_OK; + CHECK_SUCCESS(pthread_mutex_lock(&openssl_init_mutex)); + + if (openssl_engine != NULL) { + ENGINE_free(openssl_engine); + openssl_engine = NULL; + } + + if (engine == NULL) { + goto out; + } + + ENGINE_load_builtin_engines(); + openssl_engine = ENGINE_by_id(engine); + if (openssl_engine == NULL) { + status = AMQP_STATUS_SSL_SET_ENGINE_FAILED; + goto out; + } + + if (ENGINE_set_default(openssl_engine, ENGINE_METHOD_ALL) == 0) { + ENGINE_free(openssl_engine); + openssl_engine = NULL; + status = AMQP_STATUS_SSL_SET_ENGINE_FAILED; + goto out; + } + +out: + CHECK_SUCCESS(pthread_mutex_unlock(&openssl_init_mutex)); + return status; +#else + return AMQP_STATUS_SSL_UNIMPLEMENTED; +#endif +} + +static int initialize_ssl_and_increment_connections(void) { + int status; + CHECK_SUCCESS(pthread_mutex_lock(&openssl_init_mutex)); + + if (!openssl_bio_initialized) { + status = amqp_openssl_bio_init(); + if (status) { + goto exit; + } + openssl_bio_initialized = 1; + } + + openssl_connections += 1; + status = AMQP_STATUS_OK; +exit: + CHECK_SUCCESS(pthread_mutex_unlock(&openssl_init_mutex)); + return status; +} + +static int decrement_ssl_connections(void) { + CHECK_SUCCESS(pthread_mutex_lock(&openssl_init_mutex)); + + if (openssl_connections > 0) { + openssl_connections--; + } + + if (openssl_connections == 0) { + amqp_openssl_bio_destroy(); + openssl_bio_initialized = 0; + } + + CHECK_SUCCESS(pthread_mutex_unlock(&openssl_init_mutex)); + return AMQP_STATUS_OK; +} + +int amqp_uninitialize_ssl_library(void) { return AMQP_STATUS_OK; } diff --git a/librabbitmq/amqp_openssl_bio.c b/librabbitmq/amqp_openssl_bio.c new file mode 100644 index 0000000..2846197 --- /dev/null +++ b/librabbitmq/amqp_openssl_bio.c @@ -0,0 +1,155 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_openssl_bio.h" +#include "amqp_socket.h" + +#include +#include +#if ((defined(_WIN32)) || (defined(__MINGW32__)) || (defined(__MINGW64__))) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#endif + +#ifdef MSG_NOSIGNAL +#define AMQP_USE_AMQP_BIO +#endif + +static int amqp_ssl_bio_initialized = 0; + +#ifdef AMQP_USE_AMQP_BIO + +static BIO_METHOD *amqp_bio_method; + +static int amqp_openssl_bio_should_retry(int res) { + if (res == -1) { + int err = amqp_os_socket_error(); + if ( +#ifdef EWOULDBLOCK + err == EWOULDBLOCK || +#endif +#ifdef WSAEWOULDBLOCK + err == WSAEWOULDBLOCK || +#endif +#ifdef ENOTCONN + err == ENOTCONN || +#endif +#ifdef EINTR + err == EINTR || +#endif +#ifdef EAGAIN + err == EAGAIN || +#endif +#ifdef EPROTO + err == EPROTO || +#endif +#ifdef EINPROGRESS + err == EINPROGRESS || +#endif +#ifdef EALREADY + err == EALREADY || +#endif + 0) { + return 1; + } + } + return 0; +} + +static int amqp_openssl_bio_write(BIO *b, const char *in, int inl) { + int flags = 0; + int fd; + int res; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + BIO_get_fd(b, &fd); + res = send(fd, in, inl, flags); + + BIO_clear_retry_flags(b); + if (res <= 0 && amqp_openssl_bio_should_retry(res)) { + BIO_set_retry_write(b); + } + + return res; +} + +static int amqp_openssl_bio_read(BIO *b, char *out, int outl) { + int flags = 0; + int fd; + int res; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + BIO_get_fd(b, &fd); + res = recv(fd, out, outl, flags); + + BIO_clear_retry_flags(b); + if (res <= 0 && amqp_openssl_bio_should_retry(res)) { + BIO_set_retry_read(b); + } + + return res; +} +#endif /* AMQP_USE_AMQP_BIO */ + +int amqp_openssl_bio_init(void) { + assert(!amqp_ssl_bio_initialized); +#ifdef AMQP_USE_AMQP_BIO + if (!(amqp_bio_method = BIO_meth_new(BIO_TYPE_SOCKET, "amqp_bio_method"))) { + return AMQP_STATUS_NO_MEMORY; + } +#ifdef OPENSSL_IS_BORINGSSL + BIO_meth_set_create(amqp_bio_method, BIO_s_socket()->create); + BIO_meth_set_destroy(amqp_bio_method, BIO_s_socket()->destroy); + BIO_meth_set_ctrl(amqp_bio_method, BIO_s_socket()->ctrl); + BIO_meth_set_read(amqp_bio_method, BIO_s_socket()->bread); + BIO_meth_set_write(amqp_bio_method, BIO_s_socket()->bwrite); + BIO_meth_set_gets(amqp_bio_method, BIO_s_socket()->bgets); + BIO_meth_set_puts(amqp_bio_method, BIO_s_socket()->bputs); +#else + BIO_meth_set_create(amqp_bio_method, BIO_meth_get_create(BIO_s_socket())); + BIO_meth_set_destroy(amqp_bio_method, BIO_meth_get_destroy(BIO_s_socket())); + BIO_meth_set_ctrl(amqp_bio_method, BIO_meth_get_ctrl(BIO_s_socket())); + BIO_meth_set_callback_ctrl(amqp_bio_method, + BIO_meth_get_callback_ctrl(BIO_s_socket())); + BIO_meth_set_read(amqp_bio_method, BIO_meth_get_read(BIO_s_socket())); + BIO_meth_set_write(amqp_bio_method, BIO_meth_get_write(BIO_s_socket())); + BIO_meth_set_gets(amqp_bio_method, BIO_meth_get_gets(BIO_s_socket())); + BIO_meth_set_puts(amqp_bio_method, BIO_meth_get_puts(BIO_s_socket())); +#endif + + BIO_meth_set_write(amqp_bio_method, amqp_openssl_bio_write); + BIO_meth_set_read(amqp_bio_method, amqp_openssl_bio_read); +#endif + + amqp_ssl_bio_initialized = 1; + return AMQP_STATUS_OK; +} + +void amqp_openssl_bio_destroy(void) { + assert(amqp_ssl_bio_initialized); +#ifdef AMQP_USE_AMQP_BIO + BIO_meth_free(amqp_bio_method); + amqp_bio_method = NULL; +#endif + amqp_ssl_bio_initialized = 0; +} + +BIO_METHOD_PTR amqp_openssl_bio(void) { + assert(amqp_ssl_bio_initialized); +#ifdef AMQP_USE_AMQP_BIO + return amqp_bio_method; +#else + return BIO_s_socket(); +#endif +} diff --git a/librabbitmq/amqp_openssl_bio.h b/librabbitmq/amqp_openssl_bio.h new file mode 100644 index 0000000..28e484c --- /dev/null +++ b/librabbitmq/amqp_openssl_bio.h @@ -0,0 +1,20 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_OPENSSL_BIO +#define AMQP_OPENSSL_BIO + +// Use OpenSSL v1.1.1 API. +#define OPENSSL_API_COMPAT 10101 + +#include + +int amqp_openssl_bio_init(void); + +void amqp_openssl_bio_destroy(void); + +typedef const BIO_METHOD *BIO_METHOD_PTR; + +BIO_METHOD_PTR amqp_openssl_bio(void); + +#endif /* ifndef AMQP_OPENSSL_BIO */ diff --git a/librabbitmq/amqp_private.h b/librabbitmq/amqp_private.h new file mode 100644 index 0000000..77a6904 --- /dev/null +++ b/librabbitmq/amqp_private.h @@ -0,0 +1,337 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef librabbitmq_amqp_private_h +#define librabbitmq_amqp_private_h + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define AMQ_COPYRIGHT \ + "Copyright (c) 2007-2014 VMWare Inc, Tony Garnock-Jones," \ + " and Alan Antonuk." + +#include "rabbitmq-c/amqp.h" +#include "rabbitmq-c/framing.h" +#include + +#if ((defined(_WIN32)) || (defined(__MINGW32__)) || (defined(__MINGW64__))) +#ifndef WINVER +/* WINVER 0x0502 is WinXP SP2+, Windows Server 2003 SP1+ + * See: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx#macros_for_conditional_declarations + */ +#define WINVER 0x0502 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#endif + +/* GCC attributes */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +#define AMQP_NORETURN __attribute__((__noreturn__)) +#define AMQP_UNUSED __attribute__((__unused__)) +#elif defined(_MSC_VER) +#define AMQP_NORETURN __declspec(noreturn) +#define AMQP_UNUSED __pragma(warning(suppress : 4100)) +#else +#define AMQP_NORETURN +#define AMQP_UNUSED +#endif + +#if (defined(_MSC_VER) && (_MSC_VER <= 1800)) || \ + (defined(__BORLANDC__) && (__BORLANDC__ <= 0x0564)) +#define inline __inline +#endif + +char *amqp_os_error_string(int err); + +#include "amqp_socket.h" +#include "amqp_time.h" + +/* + * Connection states: XXX FIX THIS + * + * - CONNECTION_STATE_INITIAL: The initial state, when we cannot be + * sure if the next thing we will get is the first AMQP frame, or a + * protocol header from the server. + * + * - CONNECTION_STATE_IDLE: The normal state between + * frames. Connections may only be reconfigured, and the + * connection's pools recycled, when in this state. Whenever we're + * in this state, the inbound_buffer's bytes pointer must be NULL; + * any other state, and it must point to a block of memory allocated + * from the frame_pool. + * + * - CONNECTION_STATE_HEADER: Some bytes of an incoming frame have + * been seen, but not a complete frame header's worth. + * + * - CONNECTION_STATE_BODY: A complete frame header has been seen, but + * the frame is not yet complete. When it is completed, it will be + * returned, and the connection will return to IDLE state. + * + */ +typedef enum amqp_connection_state_enum_ { + CONNECTION_STATE_IDLE = 0, + CONNECTION_STATE_INITIAL, + CONNECTION_STATE_HEADER, + CONNECTION_STATE_BODY +} amqp_connection_state_enum; + +typedef enum amqp_status_private_enum_ { + /* 0x00xx -> AMQP_STATUS_*/ + /* 0x01xx -> AMQP_STATUS_TCP_* */ + /* 0x02xx -> AMQP_STATUS_SSL_* */ + AMQP_PRIVATE_STATUS_SOCKET_NEEDREAD = -0x1301, + AMQP_PRIVATE_STATUS_SOCKET_NEEDWRITE = -0x1302 +} amqp_status_private_enum; + +/* 7 bytes up front, then payload, then 1 byte footer */ +#define HEADER_SIZE 7 +#define FOOTER_SIZE 1 + +#define AMQP_PSEUDOFRAME_PROTOCOL_HEADER 'A' + +typedef struct amqp_link_t_ { + struct amqp_link_t_ *next; + void *data; +} amqp_link_t; + +#define POOL_TABLE_SIZE 16 + +typedef struct amqp_pool_table_entry_t_ { + struct amqp_pool_table_entry_t_ *next; + amqp_pool_t pool; + amqp_channel_t channel; +} amqp_pool_table_entry_t; + +struct amqp_connection_state_t_ { + amqp_pool_table_entry_t *pool_table[POOL_TABLE_SIZE]; + + amqp_connection_state_enum state; + + int channel_max; + int frame_max; + + /* Heartbeat interval in seconds. If this is <= 0, then heartbeats are not + * enabled, and next_recv_heartbeat and next_send_heartbeat are set to + * infinite */ + int heartbeat; + amqp_time_t next_recv_heartbeat; + amqp_time_t next_send_heartbeat; + + /* buffer for holding frame headers. Allows us to delay allocating + * the raw frame buffer until the type, channel, and size are all known + */ + char header_buffer[HEADER_SIZE + 1]; + amqp_bytes_t inbound_buffer; + + size_t inbound_offset; + size_t target_size; + + amqp_bytes_t outbound_buffer; + + amqp_socket_t *socket; + + amqp_bytes_t sock_inbound_buffer; + size_t sock_inbound_offset; + size_t sock_inbound_limit; + + amqp_link_t *first_queued_frame; + amqp_link_t *last_queued_frame; + + amqp_rpc_reply_t most_recent_api_result; + + amqp_table_t server_properties; + amqp_table_t client_properties; + amqp_pool_t properties_pool; + + struct timeval *handshake_timeout; + struct timeval internal_handshake_timeout; + struct timeval *rpc_timeout; + struct timeval internal_rpc_timeout; +}; + +amqp_pool_t *amqp_get_or_create_channel_pool(amqp_connection_state_t connection, + amqp_channel_t channel); +amqp_pool_t *amqp_get_channel_pool(amqp_connection_state_t state, + amqp_channel_t channel); + +static inline int amqp_heartbeat_send(amqp_connection_state_t state) { + return state->heartbeat; +} + +static inline int amqp_heartbeat_recv(amqp_connection_state_t state) { + return 2 * state->heartbeat; +} + +int amqp_try_recv(amqp_connection_state_t state); + +static inline void *amqp_offset(void *data, size_t offset) { + return (char *)data + offset; +} + +/* This macro defines the encoding and decoding functions associated with a + simple type. */ + +#define DECLARE_CODEC_BASE_TYPE(bits) \ + \ + static inline int amqp_encode_##bits(amqp_bytes_t encoded, size_t *offset, \ + uint##bits##_t input) { \ + size_t o = *offset; \ + if ((*offset = o + bits / 8) <= encoded.len) { \ + amqp_e##bits(input, amqp_offset(encoded.bytes, o)); \ + return 1; \ + } \ + return 0; \ + } \ + \ + static inline int amqp_decode_##bits(amqp_bytes_t encoded, size_t *offset, \ + uint##bits##_t *output) { \ + size_t o = *offset; \ + if ((*offset = o + bits / 8) <= encoded.len) { \ + *output = amqp_d##bits(amqp_offset(encoded.bytes, o)); \ + return 1; \ + } \ + return 0; \ + } + +static inline int is_bigendian(void) { + union { + uint32_t i; + char c[4]; + } bint = {0x01020304}; + return bint.c[0] == 1; +} + +static inline void amqp_e8(uint8_t val, void *data) { + memcpy(data, &val, sizeof(val)); +} + +static inline uint8_t amqp_d8(void *data) { + uint8_t val; + memcpy(&val, data, sizeof(val)); + return val; +} + +static inline void amqp_e16(uint16_t val, void *data) { + if (!is_bigendian()) { + val = ((val & 0xFF00u) >> 8u) | ((val & 0x00FFu) << 8u); + } + memcpy(data, &val, sizeof(val)); +} + +static inline uint16_t amqp_d16(void *data) { + uint16_t val; + memcpy(&val, data, sizeof(val)); + if (!is_bigendian()) { + val = ((val & 0xFF00u) >> 8u) | ((val & 0x00FFu) << 8u); + } + return val; +} + +static inline void amqp_e32(uint32_t val, void *data) { + if (!is_bigendian()) { + val = ((val & 0xFF000000u) >> 24u) | ((val & 0x00FF0000u) >> 8u) | + ((val & 0x0000FF00u) << 8u) | ((val & 0x000000FFu) << 24u); + } + memcpy(data, &val, sizeof(val)); +} + +static inline uint32_t amqp_d32(void *data) { + uint32_t val; + memcpy(&val, data, sizeof(val)); + if (!is_bigendian()) { + val = ((val & 0xFF000000u) >> 24u) | ((val & 0x00FF0000u) >> 8u) | + ((val & 0x0000FF00u) << 8u) | ((val & 0x000000FFu) << 24u); + } + return val; +} + +static inline void amqp_e64(uint64_t val, void *data) { + if (!is_bigendian()) { + val = ((val & 0xFF00000000000000u) >> 56u) | + ((val & 0x00FF000000000000u) >> 40u) | + ((val & 0x0000FF0000000000u) >> 24u) | + ((val & 0x000000FF00000000u) >> 8u) | + ((val & 0x00000000FF000000u) << 8u) | + ((val & 0x0000000000FF0000u) << 24u) | + ((val & 0x000000000000FF00u) << 40u) | + ((val & 0x00000000000000FFu) << 56u); + } + memcpy(data, &val, sizeof(val)); +} + +static inline uint64_t amqp_d64(void *data) { + uint64_t val; + memcpy(&val, data, sizeof(val)); + if (!is_bigendian()) { + val = ((val & 0xFF00000000000000u) >> 56u) | + ((val & 0x00FF000000000000u) >> 40u) | + ((val & 0x0000FF0000000000u) >> 24u) | + ((val & 0x000000FF00000000u) >> 8u) | + ((val & 0x00000000FF000000u) << 8u) | + ((val & 0x0000000000FF0000u) << 24u) | + ((val & 0x000000000000FF00u) << 40u) | + ((val & 0x00000000000000FFu) << 56u); + } + return val; +} + +DECLARE_CODEC_BASE_TYPE(8) +DECLARE_CODEC_BASE_TYPE(16) +DECLARE_CODEC_BASE_TYPE(32) +DECLARE_CODEC_BASE_TYPE(64) + +static inline int amqp_encode_bytes(amqp_bytes_t encoded, size_t *offset, + amqp_bytes_t input) { + size_t o = *offset; + /* The memcpy below has undefined behavior if the input is NULL. It is valid + * for a 0-length amqp_bytes_t to have .bytes == NULL. Thus we should check + * before encoding. + */ + if (input.len == 0) { + return 1; + } + if ((*offset = o + input.len) <= encoded.len) { + memcpy(amqp_offset(encoded.bytes, o), input.bytes, input.len); + return 1; + } else { + return 0; + } +} + +static inline int amqp_decode_bytes(amqp_bytes_t encoded, size_t *offset, + amqp_bytes_t *output, size_t len) { + size_t o = *offset; + if ((*offset = o + len) <= encoded.len) { + output->bytes = amqp_offset(encoded.bytes, o); + output->len = len; + return 1; + } else { + return 0; + } +} + +AMQP_NORETURN +void amqp_abort(const char *fmt, ...); + +int amqp_bytes_equal(amqp_bytes_t r, amqp_bytes_t l); + +static inline amqp_rpc_reply_t amqp_rpc_reply_error(amqp_status_enum status) { + amqp_rpc_reply_t reply; + reply.reply_type = AMQP_RESPONSE_LIBRARY_EXCEPTION; + reply.library_error = status; + return reply; +} + +int amqp_send_frame_inner(amqp_connection_state_t state, + const amqp_frame_t *frame, int flags, + amqp_time_t deadline); +#endif diff --git a/librabbitmq/amqp_socket.c b/librabbitmq/amqp_socket.c new file mode 100644 index 0000000..108de9c --- /dev/null +++ b/librabbitmq/amqp_socket.c @@ -0,0 +1,1469 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "amqp_private.h" +#include "amqp_socket.h" +#include "amqp_table.h" +#include "amqp_time.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#if ((defined(_WIN32)) || (defined(__MINGW32__)) || (defined(__MINGW64__))) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#else +#include +/* On older BSD types.h must come before net includes */ +#include +#include +#ifdef HAVE_SELECT +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_POLL +#include +#endif +#include +#endif + +static int amqp_id_in_reply_list(amqp_method_number_t expected, + amqp_method_number_t *list); + +static int amqp_os_socket_init(void) { +#ifdef _WIN32 + static int called_wsastartup = 0; + if (!called_wsastartup) { + WSADATA data; + int res = WSAStartup(0x0202, &data); + if (res) { + return AMQP_STATUS_TCP_SOCKETLIB_INIT_ERROR; + } + + called_wsastartup = 1; + } + return AMQP_STATUS_OK; + +#else + return AMQP_STATUS_OK; +#endif +} + +int amqp_os_socket_error(void) { +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +int amqp_os_socket_close(int sockfd) { +#ifdef _WIN32 + return closesocket(sockfd); +#else + return close(sockfd); +#endif +} + +ssize_t amqp_socket_send(amqp_socket_t *self, const void *buf, size_t len, + int flags) { + assert(self); + assert(self->klass->send); + return self->klass->send(self, buf, len, flags); +} + +ssize_t amqp_socket_recv(amqp_socket_t *self, void *buf, size_t len, + int flags) { + assert(self); + assert(self->klass->recv); + return self->klass->recv(self, buf, len, flags); +} + +int amqp_socket_open(amqp_socket_t *self, const char *host, int port) { + assert(self); + assert(self->klass->open); + return self->klass->open(self, host, port, NULL); +} + +int amqp_socket_open_noblock(amqp_socket_t *self, const char *host, int port, + const struct timeval *timeout) { + assert(self); + assert(self->klass->open); + return self->klass->open(self, host, port, timeout); +} + +int amqp_socket_close(amqp_socket_t *self, amqp_socket_close_enum force) { + assert(self); + assert(self->klass->close); + return self->klass->close(self, force); +} + +void amqp_socket_delete(amqp_socket_t *self) { + if (self) { + assert(self->klass->delete); + self->klass->delete (self); + } +} + +int amqp_socket_get_sockfd(amqp_socket_t *self) { + assert(self); + assert(self->klass->get_sockfd); + return self->klass->get_sockfd(self); +} + +int amqp_poll(int fd, int event, amqp_time_t deadline) { +#ifdef HAVE_POLL + struct pollfd pfd; + int res; + int timeout_ms; + + /* Function should only ever be called with one of these two */ + assert(event == AMQP_SF_POLLIN || event == AMQP_SF_POLLOUT); + +start_poll: + pfd.fd = fd; + switch (event) { + case AMQP_SF_POLLIN: + pfd.events = POLLIN; + break; + case AMQP_SF_POLLOUT: + pfd.events = POLLOUT; + break; + } + + timeout_ms = amqp_time_ms_until(deadline); + if (-1 > timeout_ms) { + return timeout_ms; + } + + res = poll(&pfd, 1, timeout_ms); + + if (0 < res) { + /* TODO: optimize this a bit by returning the AMQP_STATUS_SOCKET_ERROR or + * equivalent when pdf.revent is POLLHUP or POLLERR, so an extra syscall + * doesn't need to be made. */ + return AMQP_STATUS_OK; + } else if (0 == res) { + return AMQP_STATUS_TIMEOUT; + } else { + switch (amqp_os_socket_error()) { + case EINTR: + goto start_poll; + default: + return AMQP_STATUS_SOCKET_ERROR; + } + } +#elif defined(HAVE_SELECT) + fd_set fds; + fd_set exceptfds; + fd_set *exceptfdsp; + int res; + struct timeval tv; + struct timeval *tvp; + + assert((0 != (event & AMQP_SF_POLLIN)) || (0 != (event & AMQP_SF_POLLOUT))); +#ifndef _WIN32 + /* On Win32 connect() failure is indicated through the exceptfds, it does not + * make any sense to allow POLLERR on any other platform or condition */ + assert(0 == (event & AMQP_SF_POLLERR)); +#endif + +start_select: + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (event & AMQP_SF_POLLERR) { + FD_ZERO(&exceptfds); + FD_SET(fd, &exceptfds); + exceptfdsp = &exceptfds; + } else { + exceptfdsp = NULL; + } + + res = amqp_time_tv_until(deadline, &tv, &tvp); + if (res != AMQP_STATUS_OK) { + return res; + } + + if (event & AMQP_SF_POLLIN) { + res = select(fd + 1, &fds, NULL, exceptfdsp, tvp); + } else if (event & AMQP_SF_POLLOUT) { + res = select(fd + 1, NULL, &fds, exceptfdsp, tvp); + } + + if (0 < res) { + return AMQP_STATUS_OK; + } else if (0 == res) { + return AMQP_STATUS_TIMEOUT; + } else { + switch (amqp_os_socket_error()) { + case EINTR: + goto start_select; + default: + return AMQP_STATUS_SOCKET_ERROR; + } + } +#else +#error "poll() or select() is needed to compile rabbitmq-c" +#endif +} + +static ssize_t do_poll(amqp_connection_state_t state, ssize_t res, + amqp_time_t deadline) { + int fd = amqp_get_sockfd(state); + if (-1 == fd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + switch (res) { + case AMQP_PRIVATE_STATUS_SOCKET_NEEDREAD: + res = amqp_poll(fd, AMQP_SF_POLLIN, deadline); + break; + case AMQP_PRIVATE_STATUS_SOCKET_NEEDWRITE: + res = amqp_poll(fd, AMQP_SF_POLLOUT, deadline); + break; + } + return res; +} + +ssize_t amqp_try_send(amqp_connection_state_t state, const void *buf, + size_t len, amqp_time_t deadline, int flags) { + ssize_t res; + void *buf_left = (void *)buf; + /* Assume that len is not going to be larger than ssize_t can hold. */ + ssize_t len_left = (size_t)len; + +start_send: + res = amqp_socket_send(state->socket, buf_left, len_left, flags); + + if (res > 0) { + len_left -= res; + buf_left = (char *)buf_left + res; + if (0 == len_left) { + return (ssize_t)len; + } + goto start_send; + } + res = do_poll(state, res, deadline); + if (AMQP_STATUS_OK == res) { + goto start_send; + } + if (AMQP_STATUS_TIMEOUT == res) { + return (ssize_t)len - len_left; + } + return res; +} + +int amqp_open_socket(char const *hostname, int portnumber) { + return amqp_open_socket_inner(hostname, portnumber, amqp_time_infinite()); +} + +int amqp_open_socket_noblock(char const *hostname, int portnumber, + const struct timeval *timeout) { + amqp_time_t deadline; + int res = amqp_time_from_now(&deadline, timeout); + if (AMQP_STATUS_OK != res) { + return res; + } + return amqp_open_socket_inner(hostname, portnumber, deadline); +} + +#ifdef _WIN32 +static int connect_socket(struct addrinfo *addr, amqp_time_t deadline) { + int one = 1; + SOCKET sockfd; + int last_error; + + /* + * This cast is to squash warnings on Win64, see: + * http://stackoverflow.com/questions/1953639/is-it-safe-to-cast-socket-to-int-under-win64 + */ + + sockfd = (int)socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (INVALID_SOCKET == sockfd) { + return AMQP_STATUS_SOCKET_ERROR; + } + + /* Set the socket to be non-blocking */ + if (SOCKET_ERROR == ioctlsocket(sockfd, FIONBIO, &one)) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + /* Disable nagle */ + if (SOCKET_ERROR == setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, + (const char *)&one, sizeof(one))) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + /* Enable TCP keepalives */ + if (SOCKET_ERROR == setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, + (const char *)&one, sizeof(one))) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + if (SOCKET_ERROR != connect(sockfd, addr->ai_addr, (int)addr->ai_addrlen)) { + return (int)sockfd; + } + + if (WSAEWOULDBLOCK != WSAGetLastError()) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + last_error = + amqp_poll((int)sockfd, AMQP_SF_POLLOUT | AMQP_SF_POLLERR, deadline); + if (AMQP_STATUS_OK != last_error) { + goto err; + } + + { + int result; + int result_len = sizeof(result); + + if (SOCKET_ERROR == getsockopt(sockfd, SOL_SOCKET, SO_ERROR, + (char *)&result, &result_len) || + result != 0) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + } + + return (int)sockfd; + +err: + closesocket(sockfd); + return last_error; +} +#else +static int connect_socket(struct addrinfo *addr, amqp_time_t deadline) { + int one = 1; + int sockfd; + int flags; + int last_error; + + sockfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (-1 == sockfd) { + return AMQP_STATUS_SOCKET_ERROR; + } + + /* Enable CLOEXEC on socket */ + flags = fcntl(sockfd, F_GETFD); + if (flags == -1 || fcntl(sockfd, F_SETFD, (long)(flags | FD_CLOEXEC)) == -1) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + /* Set the socket as non-blocking */ + flags = fcntl(sockfd, F_GETFL); + if (flags == -1 || fcntl(sockfd, F_SETFL, (long)(flags | O_NONBLOCK)) == -1) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + +#ifdef SO_NOSIGPIPE + /* Turn off SIGPIPE on platforms that support it, BSD, MacOSX */ + if (0 != setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one))) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } +#endif /* SO_NOSIGPIPE */ + + /* Disable nagle */ + if (0 != setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one))) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + /* Enable TCP keepalives */ + if (0 != setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one))) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + if (0 == connect(sockfd, addr->ai_addr, addr->ai_addrlen)) { + return sockfd; + } + + if (EINPROGRESS != errno) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + + last_error = amqp_poll(sockfd, AMQP_SF_POLLOUT, deadline); + if (AMQP_STATUS_OK != last_error) { + goto err; + } + + { + int result; + socklen_t result_len = sizeof(result); + + if (-1 == getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &result, &result_len) || + result != 0) { + last_error = AMQP_STATUS_SOCKET_ERROR; + goto err; + } + } + + return sockfd; + +err: + close(sockfd); + return last_error; +} +#endif + +int amqp_open_socket_inner(char const *hostname, int portnumber, + amqp_time_t deadline) { + struct addrinfo hint; + struct addrinfo *address_list; + struct addrinfo *addr; + char portnumber_string[33]; + int sockfd = -1; + int last_error; + + last_error = amqp_os_socket_init(); + if (AMQP_STATUS_OK != last_error) { + return last_error; + } + + memset(&hint, 0, sizeof(hint)); + hint.ai_family = PF_UNSPEC; /* PF_INET or PF_INET6 */ + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + + (void)sprintf(portnumber_string, "%d", portnumber); + + last_error = getaddrinfo(hostname, portnumber_string, &hint, &address_list); + if (0 != last_error) { + return AMQP_STATUS_HOSTNAME_RESOLUTION_FAILED; + } + + for (addr = address_list; addr; addr = addr->ai_next) { + sockfd = connect_socket(addr, deadline); + + if (sockfd >= 0) { + last_error = AMQP_STATUS_OK; + break; + } else if (sockfd == AMQP_STATUS_TIMEOUT) { + last_error = sockfd; + break; + } + } + + freeaddrinfo(address_list); + if (last_error != AMQP_STATUS_OK || sockfd == -1) { + return last_error; + } + return sockfd; +} + +static int send_header_inner(amqp_connection_state_t state, + amqp_time_t deadline) { + ssize_t res; + static const uint8_t header[8] = {'A', + 'M', + 'Q', + 'P', + 0, + AMQP_PROTOCOL_VERSION_MAJOR, + AMQP_PROTOCOL_VERSION_MINOR, + AMQP_PROTOCOL_VERSION_REVISION}; + res = amqp_try_send(state, header, sizeof(header), deadline, AMQP_SF_NONE); + if (sizeof(header) == res) { + return AMQP_STATUS_OK; + } + return (int)res; +} + +int amqp_send_header(amqp_connection_state_t state) { + return send_header_inner(state, amqp_time_infinite()); +} + +static amqp_bytes_t sasl_method_name(amqp_sasl_method_enum method) { + amqp_bytes_t res; + + switch (method) { + case AMQP_SASL_METHOD_PLAIN: + res = amqp_literal_bytes("PLAIN"); + break; + case AMQP_SASL_METHOD_EXTERNAL: + res = amqp_literal_bytes("EXTERNAL"); + break; + + default: + amqp_abort("Invalid SASL method: %d", (int)method); + } + + return res; +} + +static int bytes_equal(amqp_bytes_t l, amqp_bytes_t r) { + if (l.len == r.len) { + if (l.bytes && r.bytes) { + if (0 == memcmp(l.bytes, r.bytes, l.len)) { + return 1; + } + } + } + return 0; +} + +int sasl_mechanism_in_list(amqp_bytes_t mechanisms, + amqp_sasl_method_enum method) { + amqp_bytes_t mechanism; + amqp_bytes_t supported_mechanism; + uint8_t *start; + uint8_t *end; + uint8_t *current; + + assert(NULL != mechanisms.bytes); + + mechanism = sasl_method_name(method); + + start = (uint8_t *)mechanisms.bytes; + current = start; + end = start + mechanisms.len; + + for (; current != end; start = current + 1) { + /* HACK: SASL states that we should be parsing this string as a UTF-8 + * string, which we're plainly not doing here. At this point its not worth + * dragging an entire UTF-8 parser for this one case, and this should work + * most of the time */ + current = memchr(start, ' ', end - start); + if (NULL == current) { + current = end; + } + supported_mechanism.bytes = start; + supported_mechanism.len = current - start; + if (bytes_equal(mechanism, supported_mechanism)) { + return 1; + } + } + + return 0; +} + +static amqp_bytes_t sasl_response(amqp_pool_t *pool, + amqp_sasl_method_enum method, va_list args) { + amqp_bytes_t response; + + switch (method) { + case AMQP_SASL_METHOD_PLAIN: { + char *username = va_arg(args, char *); + size_t username_len = strlen(username); + char *password = va_arg(args, char *); + size_t password_len = strlen(password); + char *response_buf; + + amqp_pool_alloc_bytes(pool, strlen(username) + strlen(password) + 2, + &response); + if (response.bytes == NULL) + /* We never request a zero-length block, because of the +2 + above, so a NULL here really is ENOMEM. */ + { + return response; + } + + response_buf = response.bytes; + response_buf[0] = 0; + memcpy(response_buf + 1, username, username_len); + response_buf[username_len + 1] = 0; + memcpy(response_buf + username_len + 2, password, password_len); + break; + } + case AMQP_SASL_METHOD_EXTERNAL: { + char *identity = va_arg(args, char *); + size_t identity_len = strlen(identity); + + amqp_pool_alloc_bytes(pool, identity_len, &response); + if (response.bytes == NULL) { + return response; + } + + memcpy(response.bytes, identity, identity_len); + break; + } + default: + amqp_abort("Invalid SASL method: %d", (int)method); + } + + return response; +} + +amqp_boolean_t amqp_frames_enqueued(amqp_connection_state_t state) { + return (state->first_queued_frame != NULL); +} + +/* + * Check to see if we have data in our buffer. If this returns 1, we + * will avoid an immediate blocking read in amqp_simple_wait_frame. + */ +amqp_boolean_t amqp_data_in_buffer(amqp_connection_state_t state) { + return (state->sock_inbound_offset < state->sock_inbound_limit); +} + +static int consume_one_frame(amqp_connection_state_t state, + amqp_frame_t *decoded_frame) { + int res; + + amqp_bytes_t buffer; + buffer.len = state->sock_inbound_limit - state->sock_inbound_offset; + buffer.bytes = + ((char *)state->sock_inbound_buffer.bytes) + state->sock_inbound_offset; + + res = amqp_handle_input(state, buffer, decoded_frame); + if (res < 0) { + return res; + } + + state->sock_inbound_offset += res; + + return AMQP_STATUS_OK; +} + +static int recv_with_timeout(amqp_connection_state_t state, + amqp_time_t timeout) { + ssize_t res; + int fd; + +start_recv: + res = amqp_socket_recv(state->socket, state->sock_inbound_buffer.bytes, + state->sock_inbound_buffer.len, 0); + + if (res < 0) { + fd = amqp_get_sockfd(state); + if (-1 == fd) { + return AMQP_STATUS_CONNECTION_CLOSED; + } + switch (res) { + default: + return (int)res; + case AMQP_PRIVATE_STATUS_SOCKET_NEEDREAD: + res = amqp_poll(fd, AMQP_SF_POLLIN, timeout); + break; + case AMQP_PRIVATE_STATUS_SOCKET_NEEDWRITE: + res = amqp_poll(fd, AMQP_SF_POLLOUT, timeout); + break; + } + if (AMQP_STATUS_OK == res) { + goto start_recv; + } + return (int)res; + } + + state->sock_inbound_limit = res; + state->sock_inbound_offset = 0; + + res = amqp_time_s_from_now(&state->next_recv_heartbeat, + amqp_heartbeat_recv(state)); + if (AMQP_STATUS_OK != res) { + return (int)res; + } + return AMQP_STATUS_OK; +} + +int amqp_try_recv(amqp_connection_state_t state) { + amqp_time_t timeout; + int res; + + while (amqp_data_in_buffer(state)) { + amqp_frame_t frame; + res = consume_one_frame(state, &frame); + + if (AMQP_STATUS_OK != res) { + return res; + } + + if (frame.frame_type != 0) { + amqp_pool_t *channel_pool; + amqp_frame_t *frame_copy; + amqp_link_t *link; + + channel_pool = amqp_get_or_create_channel_pool(state, frame.channel); + if (NULL == channel_pool) { + return AMQP_STATUS_NO_MEMORY; + } + + frame_copy = amqp_pool_alloc(channel_pool, sizeof(amqp_frame_t)); + link = amqp_pool_alloc(channel_pool, sizeof(amqp_link_t)); + + if (frame_copy == NULL || link == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + + *frame_copy = frame; + + link->next = NULL; + link->data = frame_copy; + + if (state->last_queued_frame == NULL) { + state->first_queued_frame = link; + } else { + state->last_queued_frame->next = link; + } + state->last_queued_frame = link; + } + } + res = amqp_time_from_now(&timeout, &(struct timeval){0}); + if (AMQP_STATUS_OK != res) { + return res; + } + + return recv_with_timeout(state, timeout); +} + +static int wait_frame_inner(amqp_connection_state_t state, + amqp_frame_t *decoded_frame, + amqp_time_t timeout_deadline) { + amqp_time_t deadline; + int res; + + for (;;) { + while (amqp_data_in_buffer(state)) { + res = consume_one_frame(state, decoded_frame); + + if (AMQP_STATUS_OK != res) { + return res; + } + + if (AMQP_FRAME_HEARTBEAT == decoded_frame->frame_type) { + amqp_maybe_release_buffers_on_channel(state, 0); + continue; + } + + if (decoded_frame->frame_type != 0) { + /* Complete frame was read. Return it. */ + return AMQP_STATUS_OK; + } + } + + beginrecv: + res = amqp_time_has_past(state->next_send_heartbeat); + if (AMQP_STATUS_TIMER_FAILURE == res) { + return res; + } else if (AMQP_STATUS_TIMEOUT == res) { + amqp_frame_t heartbeat; + heartbeat.channel = 0; + heartbeat.frame_type = AMQP_FRAME_HEARTBEAT; + + res = amqp_send_frame(state, &heartbeat); + if (AMQP_STATUS_OK != res) { + return res; + } + } + deadline = amqp_time_first(timeout_deadline, + amqp_time_first(state->next_recv_heartbeat, + state->next_send_heartbeat)); + + /* TODO this needs to wait for a _frame_ and not anything written from the + * socket */ + res = recv_with_timeout(state, deadline); + + if (AMQP_STATUS_TIMEOUT == res) { + if (amqp_time_equal(deadline, state->next_recv_heartbeat)) { + amqp_socket_close(state->socket, AMQP_SC_FORCE); + return AMQP_STATUS_HEARTBEAT_TIMEOUT; + } else if (amqp_time_equal(deadline, timeout_deadline)) { + return AMQP_STATUS_TIMEOUT; + } else if (amqp_time_equal(deadline, state->next_send_heartbeat)) { + /* send heartbeat happens before we do recv_with_timeout */ + goto beginrecv; + } else { + amqp_abort("Internal error: unable to determine timeout reason"); + } + } else if (AMQP_STATUS_OK != res) { + return res; + } + } +} + +static amqp_link_t *amqp_create_link_for_frame(amqp_connection_state_t state, + amqp_frame_t *frame) { + amqp_link_t *link; + amqp_frame_t *frame_copy; + + amqp_pool_t *channel_pool = + amqp_get_or_create_channel_pool(state, frame->channel); + + if (NULL == channel_pool) { + return NULL; + } + + link = amqp_pool_alloc(channel_pool, sizeof(amqp_link_t)); + frame_copy = amqp_pool_alloc(channel_pool, sizeof(amqp_frame_t)); + + if (NULL == link || NULL == frame_copy) { + return NULL; + } + + *frame_copy = *frame; + link->data = frame_copy; + + return link; +} + +int amqp_queue_frame(amqp_connection_state_t state, amqp_frame_t *frame) { + amqp_link_t *link = amqp_create_link_for_frame(state, frame); + if (NULL == link) { + return AMQP_STATUS_NO_MEMORY; + } + + if (NULL == state->first_queued_frame) { + state->first_queued_frame = link; + } else { + state->last_queued_frame->next = link; + } + + link->next = NULL; + state->last_queued_frame = link; + + return AMQP_STATUS_OK; +} + +int amqp_put_back_frame(amqp_connection_state_t state, amqp_frame_t *frame) { + amqp_link_t *link = amqp_create_link_for_frame(state, frame); + if (NULL == link) { + return AMQP_STATUS_NO_MEMORY; + } + + if (NULL == state->first_queued_frame) { + state->first_queued_frame = link; + state->last_queued_frame = link; + link->next = NULL; + } else { + link->next = state->first_queued_frame; + state->first_queued_frame = link; + } + + return AMQP_STATUS_OK; +} + +int amqp_simple_wait_frame_on_channel(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_frame_t *decoded_frame) { + amqp_frame_t *frame_ptr; + amqp_link_t *cur; + int res; + + for (cur = state->first_queued_frame; NULL != cur; cur = cur->next) { + frame_ptr = cur->data; + + if (channel == frame_ptr->channel) { + state->first_queued_frame = cur->next; + if (NULL == state->first_queued_frame) { + state->last_queued_frame = NULL; + } + + *decoded_frame = *frame_ptr; + + return AMQP_STATUS_OK; + } + } + + for (;;) { + res = wait_frame_inner(state, decoded_frame, amqp_time_infinite()); + + if (AMQP_STATUS_OK != res) { + return res; + } + + if (channel == decoded_frame->channel) { + return AMQP_STATUS_OK; + } else { + res = amqp_queue_frame(state, decoded_frame); + if (res != AMQP_STATUS_OK) { + return res; + } + } + } +} + +int amqp_simple_wait_frame(amqp_connection_state_t state, + amqp_frame_t *decoded_frame) { + return amqp_simple_wait_frame_noblock(state, decoded_frame, NULL); +} + +int amqp_simple_wait_frame_noblock(amqp_connection_state_t state, + amqp_frame_t *decoded_frame, + const struct timeval *timeout) { + amqp_time_t deadline; + + int res = amqp_time_from_now(&deadline, timeout); + if (AMQP_STATUS_OK != res) { + return res; + } + + if (state->first_queued_frame != NULL) { + amqp_frame_t *f = (amqp_frame_t *)state->first_queued_frame->data; + state->first_queued_frame = state->first_queued_frame->next; + if (state->first_queued_frame == NULL) { + state->last_queued_frame = NULL; + } + *decoded_frame = *f; + return AMQP_STATUS_OK; + } else { + return wait_frame_inner(state, decoded_frame, deadline); + } +} + +static int amqp_simple_wait_method_list(amqp_connection_state_t state, + amqp_channel_t expected_channel, + amqp_method_number_t *expected_methods, + amqp_time_t deadline, + amqp_method_t *output) { + amqp_frame_t frame; + struct timeval tv; + struct timeval *tvp; + + int res = amqp_time_tv_until(deadline, &tv, &tvp); + if (res != AMQP_STATUS_OK) { + return res; + } + + res = amqp_simple_wait_frame_noblock(state, &frame, tvp); + if (AMQP_STATUS_OK != res) { + return res; + } + + if (AMQP_FRAME_METHOD != frame.frame_type || + expected_channel != frame.channel || + !amqp_id_in_reply_list(frame.payload.method.id, expected_methods)) { + return AMQP_STATUS_WRONG_METHOD; + } + *output = frame.payload.method; + return AMQP_STATUS_OK; +} + +static int simple_wait_method_inner(amqp_connection_state_t state, + amqp_channel_t expected_channel, + amqp_method_number_t expected_method, + amqp_time_t deadline, + amqp_method_t *output) { + amqp_method_number_t expected_methods[2]; + expected_methods[0] = expected_method; + expected_methods[1] = 0; + return amqp_simple_wait_method_list(state, expected_channel, expected_methods, + deadline, output); +} + +int amqp_simple_wait_method(amqp_connection_state_t state, + amqp_channel_t expected_channel, + amqp_method_number_t expected_method, + amqp_method_t *output) { + return simple_wait_method_inner(state, expected_channel, expected_method, + amqp_time_infinite(), output); +} + +int amqp_send_method(amqp_connection_state_t state, amqp_channel_t channel, + amqp_method_number_t id, void *decoded) { + return amqp_send_method_inner(state, channel, id, decoded, AMQP_SF_NONE, + amqp_time_infinite()); +} + +int amqp_send_method_inner(amqp_connection_state_t state, + amqp_channel_t channel, amqp_method_number_t id, + void *decoded, int flags, amqp_time_t deadline) { + amqp_frame_t frame; + + frame.frame_type = AMQP_FRAME_METHOD; + frame.channel = channel; + frame.payload.method.id = id; + frame.payload.method.decoded = decoded; + return amqp_send_frame_inner(state, &frame, flags, deadline); +} + +static int amqp_id_in_reply_list(amqp_method_number_t expected, + amqp_method_number_t *list) { + while (*list != 0) { + if (*list == expected) { + return 1; + } + list++; + } + return 0; +} + +static amqp_rpc_reply_t simple_rpc_inner( + amqp_connection_state_t state, amqp_channel_t channel, + amqp_method_number_t request_id, amqp_method_number_t *expected_reply_ids, + void *decoded_request_method, amqp_time_t deadline) { + int status; + amqp_rpc_reply_t result; + + memset(&result, 0, sizeof(result)); + + status = amqp_send_method(state, channel, request_id, decoded_request_method); + if (status < 0) { + return amqp_rpc_reply_error(status); + } + + { + amqp_frame_t frame; + + retry: + status = wait_frame_inner(state, &frame, deadline); + if (status != AMQP_STATUS_OK) { + if (status == AMQP_STATUS_TIMEOUT) { + amqp_socket_close(state->socket, AMQP_SC_FORCE); + } + return amqp_rpc_reply_error(status); + } + + /* + * We store the frame for later processing unless it's something + * that directly affects us here, namely a method frame that is + * either + * - on the channel we want, and of the expected type, or + * - on the channel we want, and a channel.close frame, or + * - on channel zero, and a connection.close frame. + */ + if (!((frame.frame_type == AMQP_FRAME_METHOD) && + (((frame.channel == channel) && + (amqp_id_in_reply_list(frame.payload.method.id, + expected_reply_ids) || + (frame.payload.method.id == AMQP_CHANNEL_CLOSE_METHOD))) || + ((frame.channel == 0) && + (frame.payload.method.id == AMQP_CONNECTION_CLOSE_METHOD))))) { + amqp_pool_t *channel_pool; + amqp_frame_t *frame_copy; + amqp_link_t *link; + + channel_pool = amqp_get_or_create_channel_pool(state, frame.channel); + if (NULL == channel_pool) { + return amqp_rpc_reply_error(AMQP_STATUS_NO_MEMORY); + } + + frame_copy = amqp_pool_alloc(channel_pool, sizeof(amqp_frame_t)); + link = amqp_pool_alloc(channel_pool, sizeof(amqp_link_t)); + + if (frame_copy == NULL || link == NULL) { + return amqp_rpc_reply_error(AMQP_STATUS_NO_MEMORY); + } + + *frame_copy = frame; + + link->next = NULL; + link->data = frame_copy; + + if (state->last_queued_frame == NULL) { + state->first_queued_frame = link; + } else { + state->last_queued_frame->next = link; + } + state->last_queued_frame = link; + + goto retry; + } + + result.reply_type = + (amqp_id_in_reply_list(frame.payload.method.id, expected_reply_ids)) + ? AMQP_RESPONSE_NORMAL + : AMQP_RESPONSE_SERVER_EXCEPTION; + + result.reply = frame.payload.method; + return result; + } +} + +amqp_rpc_reply_t amqp_simple_rpc(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_method_number_t request_id, + amqp_method_number_t *expected_reply_ids, + void *decoded_request_method) { + amqp_time_t deadline; + int res; + + res = amqp_time_from_now(&deadline, state->rpc_timeout); + if (res != AMQP_STATUS_OK) { + return amqp_rpc_reply_error(res); + } + + return simple_rpc_inner(state, channel, request_id, expected_reply_ids, + decoded_request_method, deadline); +} + +void *amqp_simple_rpc_decoded(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_method_number_t request_id, + amqp_method_number_t reply_id, + void *decoded_request_method) { + amqp_time_t deadline; + int res; + amqp_method_number_t replies[2]; + + res = amqp_time_from_now(&deadline, state->rpc_timeout); + if (res != AMQP_STATUS_OK) { + state->most_recent_api_result = amqp_rpc_reply_error(res); + return NULL; + } + + replies[0] = reply_id; + replies[1] = 0; + + state->most_recent_api_result = simple_rpc_inner( + state, channel, request_id, replies, decoded_request_method, deadline); + + if (state->most_recent_api_result.reply_type == AMQP_RESPONSE_NORMAL) { + return state->most_recent_api_result.reply.decoded; + } else { + return NULL; + } +} + +amqp_rpc_reply_t amqp_get_rpc_reply(amqp_connection_state_t state) { + return state->most_recent_api_result; +} + +/* + * Merge base and add tables. If the two tables contain an entry with the same + * key, the entry from the add table takes precedence. For entries that are both + * tables with the same key, the table is recursively merged. + */ +int amqp_merge_capabilities(const amqp_table_t *base, const amqp_table_t *add, + amqp_table_t *result, amqp_pool_t *pool) { + int i; + int res; + amqp_pool_t temp_pool; + amqp_table_t temp_result; + assert(base != NULL); + assert(result != NULL); + assert(pool != NULL); + + if (NULL == add) { + return amqp_table_clone(base, result, pool); + } + + init_amqp_pool(&temp_pool, 4096); + temp_result.num_entries = 0; + temp_result.entries = + amqp_pool_alloc(&temp_pool, sizeof(amqp_table_entry_t) * + (base->num_entries + add->num_entries)); + if (NULL == temp_result.entries) { + res = AMQP_STATUS_NO_MEMORY; + goto error_out; + } + for (i = 0; i < base->num_entries; ++i) { + temp_result.entries[temp_result.num_entries] = base->entries[i]; + temp_result.num_entries++; + } + for (i = 0; i < add->num_entries; ++i) { + amqp_table_entry_t *e = + amqp_table_get_entry_by_key(&temp_result, add->entries[i].key); + if (NULL != e) { + if (AMQP_FIELD_KIND_TABLE == add->entries[i].value.kind && + AMQP_FIELD_KIND_TABLE == e->value.kind) { + amqp_table_entry_t *be = + amqp_table_get_entry_by_key(base, add->entries[i].key); + + res = amqp_merge_capabilities(&be->value.value.table, + &add->entries[i].value.value.table, + &e->value.value.table, &temp_pool); + if (AMQP_STATUS_OK != res) { + goto error_out; + } + } else { + e->value = add->entries[i].value; + } + } else { + temp_result.entries[temp_result.num_entries] = add->entries[i]; + temp_result.num_entries++; + } + } + res = amqp_table_clone(&temp_result, result, pool); +error_out: + empty_amqp_pool(&temp_pool); + return res; +} + +static amqp_rpc_reply_t amqp_login_inner(amqp_connection_state_t state, + char const *vhost, int channel_max, + int frame_max, int heartbeat, + const amqp_table_t *client_properties, + const struct timeval *timeout, + amqp_sasl_method_enum sasl_method, + va_list vl) { + int res; + amqp_method_t method; + + uint16_t client_channel_max; + uint32_t client_frame_max; + uint16_t client_heartbeat; + + uint16_t server_channel_max; + uint32_t server_frame_max; + uint16_t server_heartbeat; + + amqp_rpc_reply_t result; + amqp_time_t deadline; + + if (channel_max < 0 || channel_max > UINT16_MAX) { + return amqp_rpc_reply_error(AMQP_STATUS_INVALID_PARAMETER); + } + client_channel_max = (uint16_t)channel_max; + + if (frame_max < 0) { + return amqp_rpc_reply_error(AMQP_STATUS_INVALID_PARAMETER); + } + client_frame_max = (uint32_t)frame_max; + + if (heartbeat < 0 || heartbeat > UINT16_MAX) { + return amqp_rpc_reply_error(AMQP_STATUS_INVALID_PARAMETER); + } + client_heartbeat = (uint16_t)heartbeat; + + res = amqp_time_from_now(&deadline, timeout); + if (AMQP_STATUS_OK != res) { + goto error_res; + } + + res = send_header_inner(state, deadline); + if (AMQP_STATUS_OK != res) { + goto error_res; + } + + res = simple_wait_method_inner(state, 0, AMQP_CONNECTION_START_METHOD, + deadline, &method); + if (AMQP_STATUS_OK != res) { + goto error_res; + } + + { + amqp_connection_start_t *s = (amqp_connection_start_t *)method.decoded; + if ((s->version_major != AMQP_PROTOCOL_VERSION_MAJOR) || + (s->version_minor != AMQP_PROTOCOL_VERSION_MINOR)) { + res = AMQP_STATUS_INCOMPATIBLE_AMQP_VERSION; + goto error_res; + } + + res = amqp_table_clone(&s->server_properties, &state->server_properties, + &state->properties_pool); + + if (AMQP_STATUS_OK != res) { + goto error_res; + } + + /* TODO: check that our chosen SASL mechanism is in the list of + acceptable mechanisms. Or even let the application choose from + the list! */ + if (!sasl_mechanism_in_list(s->mechanisms, sasl_method)) { + res = AMQP_STATUS_BROKER_UNSUPPORTED_SASL_METHOD; + goto error_res; + } + } + + { + amqp_table_entry_t default_properties[6]; + amqp_table_t default_table; + amqp_table_entry_t client_capabilities[2]; + amqp_table_t client_capabilities_table; + amqp_connection_start_ok_t s; + amqp_pool_t *channel_pool; + amqp_bytes_t response_bytes; + + channel_pool = amqp_get_or_create_channel_pool(state, 0); + if (NULL == channel_pool) { + res = AMQP_STATUS_NO_MEMORY; + goto error_res; + } + + response_bytes = sasl_response(channel_pool, sasl_method, vl); + if (response_bytes.bytes == NULL) { + res = AMQP_STATUS_NO_MEMORY; + goto error_res; + } + + client_capabilities[0] = + amqp_table_construct_bool_entry("authentication_failure_close", 1); + client_capabilities[1] = + amqp_table_construct_bool_entry("exchange_exchange_bindings", 1); + + client_capabilities_table.entries = client_capabilities; + client_capabilities_table.num_entries = + sizeof(client_capabilities) / sizeof(amqp_table_entry_t); + + default_properties[0] = + amqp_table_construct_utf8_entry("product", "rabbitmq-c"); + default_properties[1] = + amqp_table_construct_utf8_entry("version", AMQP_VERSION_STRING); + default_properties[2] = + amqp_table_construct_utf8_entry("platform", AMQ_PLATFORM); + default_properties[3] = + amqp_table_construct_utf8_entry("copyright", AMQ_COPYRIGHT); + default_properties[4] = amqp_table_construct_utf8_entry( + "information", "See https://github.com/alanxz/rabbitmq-c"); + default_properties[5] = amqp_table_construct_table_entry( + "capabilities", &client_capabilities_table); + + default_table.entries = default_properties; + default_table.num_entries = + sizeof(default_properties) / sizeof(amqp_table_entry_t); + + res = amqp_merge_capabilities(&default_table, client_properties, + &state->client_properties, channel_pool); + if (AMQP_STATUS_OK != res) { + goto error_res; + } + + s.client_properties = state->client_properties; + s.mechanism = sasl_method_name(sasl_method); + s.response = response_bytes; + s.locale = amqp_literal_bytes("en_US"); + + res = amqp_send_method_inner(state, 0, AMQP_CONNECTION_START_OK_METHOD, &s, + AMQP_SF_NONE, deadline); + if (res < 0) { + goto error_res; + } + } + + amqp_release_buffers(state); + + { + amqp_method_number_t expected[] = {AMQP_CONNECTION_TUNE_METHOD, + AMQP_CONNECTION_CLOSE_METHOD, 0}; + + res = amqp_simple_wait_method_list(state, 0, expected, deadline, &method); + if (AMQP_STATUS_OK != res) { + goto error_res; + } + } + + if (AMQP_CONNECTION_CLOSE_METHOD == method.id) { + result.reply_type = AMQP_RESPONSE_SERVER_EXCEPTION; + result.reply = method; + result.library_error = 0; + goto out; + } + + { + amqp_connection_tune_t *s = (amqp_connection_tune_t *)method.decoded; + server_channel_max = s->channel_max; + server_frame_max = s->frame_max; + server_heartbeat = s->heartbeat; + } + + if (server_channel_max != 0 && + (server_channel_max < client_channel_max || client_channel_max == 0)) { + client_channel_max = server_channel_max; + } else if (server_channel_max == 0 && client_channel_max == 0) { + client_channel_max = UINT16_MAX; + } + + if (server_frame_max != 0 && server_frame_max < client_frame_max) { + client_frame_max = server_frame_max; + } + + if (server_heartbeat != 0 && server_heartbeat < client_heartbeat) { + client_heartbeat = server_heartbeat; + } + + res = amqp_tune_connection(state, client_channel_max, client_frame_max, + client_heartbeat); + if (res < 0) { + goto error_res; + } + + { + amqp_connection_tune_ok_t s; + s.frame_max = client_frame_max; + s.channel_max = client_channel_max; + s.heartbeat = client_heartbeat; + + res = amqp_send_method_inner(state, 0, AMQP_CONNECTION_TUNE_OK_METHOD, &s, + AMQP_SF_NONE, deadline); + if (res < 0) { + goto error_res; + } + } + + amqp_release_buffers(state); + + { + amqp_method_number_t replies[] = {AMQP_CONNECTION_OPEN_OK_METHOD, 0}; + amqp_connection_open_t s; + s.virtual_host = amqp_cstring_bytes(vhost); + s.capabilities = amqp_empty_bytes; + s.insist = 1; + + result = simple_rpc_inner(state, 0, AMQP_CONNECTION_OPEN_METHOD, replies, + &s, deadline); + if (result.reply_type != AMQP_RESPONSE_NORMAL) { + goto out; + } + } + + result.reply_type = AMQP_RESPONSE_NORMAL; + result.reply.id = 0; + result.reply.decoded = NULL; + result.library_error = 0; + amqp_maybe_release_buffers(state); + +out: + return result; + +error_res: + amqp_socket_close(state->socket, AMQP_SC_FORCE); + result = amqp_rpc_reply_error(res); + + goto out; +} + +amqp_rpc_reply_t amqp_login(amqp_connection_state_t state, char const *vhost, + int channel_max, int frame_max, int heartbeat, + amqp_sasl_method_enum sasl_method, ...) { + + va_list vl; + amqp_rpc_reply_t ret; + + va_start(vl, sasl_method); + + ret = amqp_login_inner(state, vhost, channel_max, frame_max, heartbeat, + &amqp_empty_table, state->handshake_timeout, + sasl_method, vl); + + va_end(vl); + + return ret; +} + +amqp_rpc_reply_t amqp_login_with_properties( + amqp_connection_state_t state, char const *vhost, int channel_max, + int frame_max, int heartbeat, const amqp_table_t *client_properties, + amqp_sasl_method_enum sasl_method, ...) { + va_list vl; + amqp_rpc_reply_t ret; + + va_start(vl, sasl_method); + + ret = amqp_login_inner(state, vhost, channel_max, frame_max, heartbeat, + client_properties, state->handshake_timeout, + sasl_method, vl); + + va_end(vl); + + return ret; +} diff --git a/librabbitmq/amqp_socket.h b/librabbitmq/amqp_socket.h new file mode 100644 index 0000000..9a28c85 --- /dev/null +++ b/librabbitmq/amqp_socket.h @@ -0,0 +1,166 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/** + * An abstract socket interface. + */ + +#ifndef AMQP_SOCKET_H +#define AMQP_SOCKET_H + +#include "amqp_private.h" +#include "amqp_time.h" + +AMQP_BEGIN_DECLS + +typedef enum { + AMQP_SF_NONE = 0, + AMQP_SF_MORE = 1, + AMQP_SF_POLLIN = 2, + AMQP_SF_POLLOUT = 4, + AMQP_SF_POLLERR = 8 +} amqp_socket_flag_enum; + +typedef enum { AMQP_SC_NONE = 0, AMQP_SC_FORCE = 1 } amqp_socket_close_enum; + +int amqp_os_socket_error(void); + +int amqp_os_socket_close(int sockfd); + +/* Socket callbacks. */ +typedef ssize_t (*amqp_socket_send_fn)(void *, const void *, size_t, int); +typedef ssize_t (*amqp_socket_recv_fn)(void *, void *, size_t, int); +typedef int (*amqp_socket_open_fn)(void *, const char *, int, + const struct timeval *); +typedef int (*amqp_socket_close_fn)(void *, amqp_socket_close_enum); +typedef int (*amqp_socket_get_sockfd_fn)(void *); +typedef void (*amqp_socket_delete_fn)(void *); + +/** V-table for amqp_socket_t */ +struct amqp_socket_class_t { + amqp_socket_send_fn send; + amqp_socket_recv_fn recv; + amqp_socket_open_fn open; + amqp_socket_close_fn close; + amqp_socket_get_sockfd_fn get_sockfd; + amqp_socket_delete_fn delete; +}; + +/** Abstract base class for amqp_socket_t */ +struct amqp_socket_t_ { + const struct amqp_socket_class_t *klass; +}; + +/** + * Set set the socket object for a connection + * + * This assigns a socket object to the connection, closing and deleting any + * existing socket + * + * \param [in] state The connection object to add the socket to + * \param [in] socket The socket object to assign to the connection + */ +void amqp_set_socket(amqp_connection_state_t state, amqp_socket_t *socket); + +/** + * Send a message from a socket. + * + * This function wraps send(2) functionality. + * + * This function will only return on error, or when all of the bytes in buf + * have been sent, or when an error occurs. + * + * \param [in,out] self A socket object. + * \param [in] buf A buffer to read from. + * \param [in] len The number of bytes in \e buf. + * \param [in] + * + * \return AMQP_STATUS_OK on success. amqp_status_enum value otherwise + */ +ssize_t amqp_socket_send(amqp_socket_t *self, const void *buf, size_t len, + int flags); + +ssize_t amqp_try_send(amqp_connection_state_t state, const void *buf, + size_t len, amqp_time_t deadline, int flags); + +/** + * Receive a message from a socket. + * + * This function wraps recv(2) functionality. + * + * \param [in,out] self A socket object. + * \param [out] buf A buffer to write to. + * \param [in] len The number of bytes at \e buf. + * \param [in] flags Receive flags, implementation specific. + * + * \return The number of bytes received, or < 0 on error (\ref amqp_status_enum) + */ +ssize_t amqp_socket_recv(amqp_socket_t *self, void *buf, size_t len, int flags); + +/** + * Close a socket connection and free resources. + * + * This function closes a socket connection and releases any resources used by + * the object. After calling this function the specified socket should no + * longer be referenced. + * + * \param [in,out] self A socket object. + * \param [in] force, if set, just close the socket, don't attempt a TLS + * shutdown. + * + * \return Zero upon success, non-zero otherwise. + */ +int amqp_socket_close(amqp_socket_t *self, amqp_socket_close_enum force); + +/** + * Destroy a socket object + * + * \param [in] self the socket object to delete + */ +void amqp_socket_delete(amqp_socket_t *self); + +/** + * Open a socket connection. + * + * This function opens a socket connection returned from amqp_tcp_socket_new() + * or amqp_ssl_socket_new(). This function should be called after setting + * socket options and prior to assigning the socket to an AMQP connection with + * amqp_set_socket(). + * + * \param [in] host Connect to this host. + * \param [in] port Connect on this remote port. + * \param [in] timeout Max allowed time to spent on opening. If NULL - run in + * blocking mode + * + * \return File descriptor upon success, non-zero negative error code otherwise. + */ +int amqp_open_socket_noblock(char const *hostname, int portnumber, + const struct timeval *timeout); + +int amqp_open_socket_inner(char const *hostname, int portnumber, + amqp_time_t deadline); + +/* Wait up to dealline for fd to become readable or writeable depending on + * event (AMQP_SF_POLLIN, AMQP_SF_POLLOUT) */ +int amqp_poll(int fd, int event, amqp_time_t deadline); + +int amqp_send_method_inner(amqp_connection_state_t state, + amqp_channel_t channel, amqp_method_number_t id, + void *decoded, int flags, amqp_time_t deadline); + +int amqp_queue_frame(amqp_connection_state_t state, amqp_frame_t *frame); + +int amqp_put_back_frame(amqp_connection_state_t state, amqp_frame_t *frame); + +int amqp_simple_wait_frame_on_channel(amqp_connection_state_t state, + amqp_channel_t channel, + amqp_frame_t *decoded_frame); + +int sasl_mechanism_in_list(amqp_bytes_t mechanisms, + amqp_sasl_method_enum method); + +int amqp_merge_capabilities(const amqp_table_t *base, const amqp_table_t *add, + amqp_table_t *result, amqp_pool_t *pool); +AMQP_END_DECLS + +#endif /* AMQP_SOCKET_H */ diff --git a/librabbitmq/amqp_table.c b/librabbitmq/amqp_table.c new file mode 100644 index 0000000..e900ab2 --- /dev/null +++ b/librabbitmq/amqp_table.c @@ -0,0 +1,652 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amqp_private.h" +#include "amqp_table.h" +#include +#include +#include +#include +#include + +#define INITIAL_ARRAY_SIZE 16 +#define INITIAL_TABLE_SIZE 16 +#define TABLE_DEPTH_LIMIT 100 + +static int amqp_decode_field_value(amqp_bytes_t encoded, amqp_pool_t *pool, + amqp_field_value_t *entry, size_t *offset, + int depth); + +static int amqp_encode_field_value(amqp_bytes_t encoded, + amqp_field_value_t *entry, size_t *offset); + +/*---------------------------------------------------------------------------*/ + +static int amqp_decode_array(amqp_bytes_t encoded, amqp_pool_t *pool, + amqp_array_t *output, size_t *offset, int depth) { + uint32_t arraysize; + int num_entries = 0; + int allocated_entries = INITIAL_ARRAY_SIZE; + amqp_field_value_t *entries; + size_t limit; + int res; + + if (!amqp_decode_32(encoded, offset, &arraysize)) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + if (arraysize + *offset > encoded.len) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + entries = malloc(allocated_entries * sizeof(amqp_field_value_t)); + if (entries == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + + limit = *offset + arraysize; + while (*offset < limit) { + if (num_entries >= allocated_entries) { + void *newentries; + allocated_entries = allocated_entries * 2; + newentries = + realloc(entries, allocated_entries * sizeof(amqp_field_value_t)); + res = AMQP_STATUS_NO_MEMORY; + if (newentries == NULL) { + goto out; + } + + entries = newentries; + } + + res = amqp_decode_field_value(encoded, pool, &entries[num_entries], offset, + depth); + if (res < 0) { + goto out; + } + + num_entries++; + } + + output->num_entries = num_entries; + output->entries = + amqp_pool_alloc(pool, num_entries * sizeof(amqp_field_value_t)); + /* NULL is legitimate if we requested a zero-length block. */ + if (output->entries == NULL) { + if (num_entries == 0) { + res = AMQP_STATUS_OK; + } else { + res = AMQP_STATUS_NO_MEMORY; + } + goto out; + } + + memcpy(output->entries, entries, num_entries * sizeof(amqp_field_value_t)); + res = AMQP_STATUS_OK; + +out: + free(entries); + return res; +} + +static int amqp_decode_table_internal(amqp_bytes_t encoded, amqp_pool_t *pool, + amqp_table_t *output, size_t *offset, + int depth) { + uint32_t tablesize; + int num_entries = 0; + amqp_table_entry_t *entries; + int allocated_entries = INITIAL_TABLE_SIZE; + size_t limit; + int res; + + if (!amqp_decode_32(encoded, offset, &tablesize)) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + if (tablesize + *offset > encoded.len) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + entries = malloc(allocated_entries * sizeof(amqp_table_entry_t)); + if (entries == NULL) { + return AMQP_STATUS_NO_MEMORY; + } + + limit = *offset + tablesize; + while (*offset < limit) { + uint8_t keylen; + + res = AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_8(encoded, offset, &keylen)) { + goto out; + } + + if (num_entries >= allocated_entries) { + void *newentries; + allocated_entries = allocated_entries * 2; + newentries = + realloc(entries, allocated_entries * sizeof(amqp_table_entry_t)); + res = AMQP_STATUS_NO_MEMORY; + if (newentries == NULL) { + goto out; + } + + entries = newentries; + } + + res = AMQP_STATUS_BAD_AMQP_DATA; + if (!amqp_decode_bytes(encoded, offset, &entries[num_entries].key, + keylen)) { + goto out; + } + + res = amqp_decode_field_value(encoded, pool, &entries[num_entries].value, + offset, depth); + if (res < 0) { + goto out; + } + + num_entries++; + } + + output->num_entries = num_entries; + output->entries = + amqp_pool_alloc(pool, num_entries * sizeof(amqp_table_entry_t)); + /* NULL is legitimate if we requested a zero-length block. */ + if (output->entries == NULL) { + if (num_entries == 0) { + res = AMQP_STATUS_OK; + } else { + res = AMQP_STATUS_NO_MEMORY; + } + goto out; + } + + memcpy(output->entries, entries, num_entries * sizeof(amqp_table_entry_t)); + res = AMQP_STATUS_OK; + +out: + free(entries); + return res; +} + +int amqp_decode_table(amqp_bytes_t encoded, amqp_pool_t *pool, + amqp_table_t *output, size_t *offset) { + return amqp_decode_table_internal(encoded, pool, output, offset, 0); +} + +static int amqp_decode_field_value(amqp_bytes_t encoded, amqp_pool_t *pool, + amqp_field_value_t *entry, size_t *offset, + int depth) { + int res = AMQP_STATUS_BAD_AMQP_DATA; + + if (depth > TABLE_DEPTH_LIMIT) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + + if (!amqp_decode_8(encoded, offset, &entry->kind)) { + goto out; + } + +#define TRIVIAL_FIELD_DECODER(bits) \ + if (!amqp_decode_##bits(encoded, offset, &entry->value.u##bits)) goto out; \ + break +#define SIMPLE_FIELD_DECODER(bits, dest, how) \ + { \ + uint##bits##_t val; \ + if (!amqp_decode_##bits(encoded, offset, &val)) goto out; \ + entry->value.dest = how; \ + } \ + break + + switch (entry->kind) { + case AMQP_FIELD_KIND_BOOLEAN: + SIMPLE_FIELD_DECODER(8, boolean, val ? 1 : 0); + + case AMQP_FIELD_KIND_I8: + SIMPLE_FIELD_DECODER(8, i8, (int8_t)val); + case AMQP_FIELD_KIND_U8: + TRIVIAL_FIELD_DECODER(8); + + case AMQP_FIELD_KIND_I16: + SIMPLE_FIELD_DECODER(16, i16, (int16_t)val); + case AMQP_FIELD_KIND_U16: + TRIVIAL_FIELD_DECODER(16); + + case AMQP_FIELD_KIND_I32: + SIMPLE_FIELD_DECODER(32, i32, (int32_t)val); + case AMQP_FIELD_KIND_U32: + TRIVIAL_FIELD_DECODER(32); + + case AMQP_FIELD_KIND_I64: + SIMPLE_FIELD_DECODER(64, i64, (int64_t)val); + case AMQP_FIELD_KIND_U64: + TRIVIAL_FIELD_DECODER(64); + + case AMQP_FIELD_KIND_F32: + TRIVIAL_FIELD_DECODER(32); + /* and by punning, f32 magically gets the right value...! */ + + case AMQP_FIELD_KIND_F64: + TRIVIAL_FIELD_DECODER(64); + /* and by punning, f64 magically gets the right value...! */ + + case AMQP_FIELD_KIND_DECIMAL: + if (!amqp_decode_8(encoded, offset, &entry->value.decimal.decimals) || + !amqp_decode_32(encoded, offset, &entry->value.decimal.value)) { + goto out; + } + break; + + case AMQP_FIELD_KIND_UTF8: + /* AMQP_FIELD_KIND_UTF8 and AMQP_FIELD_KIND_BYTES have the + same implementation, but different interpretations. */ + /* fall through */ + case AMQP_FIELD_KIND_BYTES: { + uint32_t len; + if (!amqp_decode_32(encoded, offset, &len) || + !amqp_decode_bytes(encoded, offset, &entry->value.bytes, len)) { + goto out; + } + break; + } + + case AMQP_FIELD_KIND_ARRAY: + res = amqp_decode_array(encoded, pool, &(entry->value.array), offset, + depth + 1); + goto out; + + case AMQP_FIELD_KIND_TIMESTAMP: + TRIVIAL_FIELD_DECODER(64); + + case AMQP_FIELD_KIND_TABLE: + res = amqp_decode_table_internal(encoded, pool, &(entry->value.table), + offset, depth + 1); + goto out; + + case AMQP_FIELD_KIND_VOID: + break; + + default: + goto out; + } + + res = AMQP_STATUS_OK; + +out: + return res; +} + +/*---------------------------------------------------------------------------*/ + +static int amqp_encode_array(amqp_bytes_t encoded, amqp_array_t *input, + size_t *offset) { + size_t start = *offset; + int i, res; + + *offset += 4; /* size of the array gets filled in later on */ + + for (i = 0; i < input->num_entries; i++) { + res = amqp_encode_field_value(encoded, &input->entries[i], offset); + if (res < 0) { + goto out; + } + } + + if (!amqp_encode_32(encoded, &start, (uint32_t)(*offset - start - 4))) { + res = AMQP_STATUS_TABLE_TOO_BIG; + goto out; + } + + res = AMQP_STATUS_OK; + +out: + return res; +} + +int amqp_encode_table(amqp_bytes_t encoded, amqp_table_t *input, + size_t *offset) { + size_t start = *offset; + int i, res; + + *offset += 4; /* size of the table gets filled in later on */ + + for (i = 0; i < input->num_entries; i++) { + if (!amqp_encode_8(encoded, offset, (uint8_t)input->entries[i].key.len)) { + res = AMQP_STATUS_TABLE_TOO_BIG; + goto out; + } + + if (!amqp_encode_bytes(encoded, offset, input->entries[i].key)) { + res = AMQP_STATUS_TABLE_TOO_BIG; + goto out; + } + + res = amqp_encode_field_value(encoded, &input->entries[i].value, offset); + if (res < 0) { + goto out; + } + } + + if (!amqp_encode_32(encoded, &start, (uint32_t)(*offset - start - 4))) { + res = AMQP_STATUS_TABLE_TOO_BIG; + goto out; + } + + res = AMQP_STATUS_OK; + +out: + return res; +} + +static int amqp_encode_field_value(amqp_bytes_t encoded, + amqp_field_value_t *entry, size_t *offset) { + int res = AMQP_STATUS_BAD_AMQP_DATA; + + if (!amqp_encode_8(encoded, offset, entry->kind)) { + goto out; + } + +#define FIELD_ENCODER(bits, val) \ + if (!amqp_encode_##bits(encoded, offset, val)) { \ + res = AMQP_STATUS_TABLE_TOO_BIG; \ + goto out; \ + } \ + break + + switch (entry->kind) { + case AMQP_FIELD_KIND_BOOLEAN: + FIELD_ENCODER(8, entry->value.boolean ? 1 : 0); + + case AMQP_FIELD_KIND_I8: + FIELD_ENCODER(8, entry->value.i8); + case AMQP_FIELD_KIND_U8: + FIELD_ENCODER(8, entry->value.u8); + + case AMQP_FIELD_KIND_I16: + FIELD_ENCODER(16, entry->value.i16); + case AMQP_FIELD_KIND_U16: + FIELD_ENCODER(16, entry->value.u16); + + case AMQP_FIELD_KIND_I32: + FIELD_ENCODER(32, entry->value.i32); + case AMQP_FIELD_KIND_U32: + FIELD_ENCODER(32, entry->value.u32); + + case AMQP_FIELD_KIND_I64: + FIELD_ENCODER(64, entry->value.i64); + case AMQP_FIELD_KIND_U64: + FIELD_ENCODER(64, entry->value.u64); + + case AMQP_FIELD_KIND_F32: + /* by punning, u32 magically gets the right value...! */ + FIELD_ENCODER(32, entry->value.u32); + + case AMQP_FIELD_KIND_F64: + /* by punning, u64 magically gets the right value...! */ + FIELD_ENCODER(64, entry->value.u64); + + case AMQP_FIELD_KIND_DECIMAL: + if (!amqp_encode_8(encoded, offset, entry->value.decimal.decimals) || + !amqp_encode_32(encoded, offset, entry->value.decimal.value)) { + res = AMQP_STATUS_TABLE_TOO_BIG; + goto out; + } + break; + + case AMQP_FIELD_KIND_UTF8: + /* AMQP_FIELD_KIND_UTF8 and AMQP_FIELD_KIND_BYTES have the + same implementation, but different interpretations. */ + /* fall through */ + case AMQP_FIELD_KIND_BYTES: + if (!amqp_encode_32(encoded, offset, (uint32_t)entry->value.bytes.len) || + !amqp_encode_bytes(encoded, offset, entry->value.bytes)) { + res = AMQP_STATUS_TABLE_TOO_BIG; + goto out; + } + break; + + case AMQP_FIELD_KIND_ARRAY: + res = amqp_encode_array(encoded, &entry->value.array, offset); + goto out; + + case AMQP_FIELD_KIND_TIMESTAMP: + FIELD_ENCODER(64, entry->value.u64); + + case AMQP_FIELD_KIND_TABLE: + res = amqp_encode_table(encoded, &entry->value.table, offset); + goto out; + + case AMQP_FIELD_KIND_VOID: + break; + + default: + res = AMQP_STATUS_INVALID_PARAMETER; + goto out; + } + + res = AMQP_STATUS_OK; + +out: + return res; +} + +/*---------------------------------------------------------------------------*/ + +int amqp_table_entry_cmp(void const *entry1, void const *entry2) { + amqp_table_entry_t const *p1 = (amqp_table_entry_t const *)entry1; + amqp_table_entry_t const *p2 = (amqp_table_entry_t const *)entry2; + + int d; + size_t minlen; + + minlen = p1->key.len; + if (p2->key.len < minlen) { + minlen = p2->key.len; + } + + d = memcmp(p1->key.bytes, p2->key.bytes, minlen); + if (d != 0) { + return d; + } + + return (int)p1->key.len - (int)p2->key.len; +} + +static int amqp_field_value_clone(const amqp_field_value_t *original, + amqp_field_value_t *clone, + amqp_pool_t *pool) { + int i; + int res; + clone->kind = original->kind; + + switch (clone->kind) { + case AMQP_FIELD_KIND_BOOLEAN: + clone->value.boolean = original->value.boolean; + break; + + case AMQP_FIELD_KIND_I8: + clone->value.i8 = original->value.i8; + break; + + case AMQP_FIELD_KIND_U8: + clone->value.u8 = original->value.u8; + break; + + case AMQP_FIELD_KIND_I16: + clone->value.i16 = original->value.i16; + break; + + case AMQP_FIELD_KIND_U16: + clone->value.u16 = original->value.u16; + break; + + case AMQP_FIELD_KIND_I32: + clone->value.i32 = original->value.i32; + break; + + case AMQP_FIELD_KIND_U32: + clone->value.u32 = original->value.u32; + break; + + case AMQP_FIELD_KIND_I64: + clone->value.i64 = original->value.i64; + break; + + case AMQP_FIELD_KIND_U64: + case AMQP_FIELD_KIND_TIMESTAMP: + clone->value.u64 = original->value.u64; + break; + + case AMQP_FIELD_KIND_F32: + clone->value.f32 = original->value.f32; + break; + + case AMQP_FIELD_KIND_F64: + clone->value.f64 = original->value.f64; + break; + + case AMQP_FIELD_KIND_DECIMAL: + clone->value.decimal = original->value.decimal; + break; + + case AMQP_FIELD_KIND_UTF8: + case AMQP_FIELD_KIND_BYTES: + if (0 == original->value.bytes.len) { + clone->value.bytes = amqp_empty_bytes; + } else { + amqp_pool_alloc_bytes(pool, original->value.bytes.len, + &clone->value.bytes); + if (NULL == clone->value.bytes.bytes) { + return AMQP_STATUS_NO_MEMORY; + } + memcpy(clone->value.bytes.bytes, original->value.bytes.bytes, + clone->value.bytes.len); + } + break; + + case AMQP_FIELD_KIND_ARRAY: + if (0 == original->value.array.entries) { + clone->value.array = amqp_empty_array; + } else { + clone->value.array.num_entries = original->value.array.num_entries; + clone->value.array.entries = amqp_pool_alloc( + pool, clone->value.array.num_entries * sizeof(amqp_field_value_t)); + if (NULL == clone->value.array.entries) { + return AMQP_STATUS_NO_MEMORY; + } + + for (i = 0; i < clone->value.array.num_entries; ++i) { + res = amqp_field_value_clone(&original->value.array.entries[i], + &clone->value.array.entries[i], pool); + if (AMQP_STATUS_OK != res) { + return res; + } + } + } + break; + + case AMQP_FIELD_KIND_TABLE: + return amqp_table_clone(&original->value.table, &clone->value.table, + pool); + + case AMQP_FIELD_KIND_VOID: + break; + + default: + return AMQP_STATUS_INVALID_PARAMETER; + } + + return AMQP_STATUS_OK; +} + +static int amqp_table_entry_clone(const amqp_table_entry_t *original, + amqp_table_entry_t *clone, + amqp_pool_t *pool) { + if (0 == original->key.len) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + amqp_pool_alloc_bytes(pool, original->key.len, &clone->key); + if (NULL == clone->key.bytes) { + return AMQP_STATUS_NO_MEMORY; + } + + memcpy(clone->key.bytes, original->key.bytes, clone->key.len); + + return amqp_field_value_clone(&original->value, &clone->value, pool); +} + +int amqp_table_clone(const amqp_table_t *original, amqp_table_t *clone, + amqp_pool_t *pool) { + int i; + int res; + clone->num_entries = original->num_entries; + if (0 == clone->num_entries) { + *clone = amqp_empty_table; + return AMQP_STATUS_OK; + } + + clone->entries = + amqp_pool_alloc(pool, clone->num_entries * sizeof(amqp_table_entry_t)); + + if (NULL == clone->entries) { + return AMQP_STATUS_NO_MEMORY; + } + + for (i = 0; i < clone->num_entries; ++i) { + res = + amqp_table_entry_clone(&original->entries[i], &clone->entries[i], pool); + if (AMQP_STATUS_OK != res) { + goto error_out1; + } + } + + return AMQP_STATUS_OK; + +error_out1: + return res; +} + +amqp_table_entry_t amqp_table_construct_utf8_entry(const char *key, + const char *value) { + amqp_table_entry_t ret; + ret.key = amqp_cstring_bytes(key); + ret.value.kind = AMQP_FIELD_KIND_UTF8; + ret.value.value.bytes = amqp_cstring_bytes(value); + return ret; +} + +amqp_table_entry_t amqp_table_construct_table_entry(const char *key, + const amqp_table_t *value) { + amqp_table_entry_t ret; + ret.key = amqp_cstring_bytes(key); + ret.value.kind = AMQP_FIELD_KIND_TABLE; + ret.value.value.table = *value; + return ret; +} + +amqp_table_entry_t amqp_table_construct_bool_entry(const char *key, + const int value) { + amqp_table_entry_t ret; + ret.key = amqp_cstring_bytes(key); + ret.value.kind = AMQP_FIELD_KIND_BOOLEAN; + ret.value.value.boolean = value; + return ret; +} + +amqp_table_entry_t *amqp_table_get_entry_by_key(const amqp_table_t *table, + const amqp_bytes_t key) { + int i; + assert(table != NULL); + for (i = 0; i < table->num_entries; ++i) { + if (amqp_bytes_equal(table->entries[i].key, key)) { + return &table->entries[i]; + } + } + return NULL; +} diff --git a/librabbitmq/amqp_table.h b/librabbitmq/amqp_table.h new file mode 100644 index 0000000..6b1de86 --- /dev/null +++ b/librabbitmq/amqp_table.h @@ -0,0 +1,56 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_TABLE_H +#define AMQP_TABLE_H + +#include "amqp_private.h" +#include "rabbitmq-c/amqp.h" + +/** + * Initializes a table entry with utf-8 string type value. + * + * \param [in] key the table entry key. The string must remain valid for the + * life of the resulting amqp_table_entry_t. + * \param [in] value the string value. The string must remain valid for the life + * of the resulting amqp_table_entry_t. + * \returns An initialized table entry. + */ +amqp_table_entry_t amqp_table_construct_utf8_entry(const char *key, + const char *value); + +/** + * Initializes a table entry with table type value. + * + * \param [in] key the table entry key. The string must remain value for the + * life of the resulting amqp_table_entry_t. + * \param [in] value the amqp_table_t value. The table must remain valid for the + * life of the resulting amqp_table_entry_t. + * \returns An initialized table entry. + */ +amqp_table_entry_t amqp_table_construct_table_entry(const char *key, + const amqp_table_t *value); + +/** + * Initializes a table entry with boolean type value. + * + * \param [in] key the table entry key. The string must remain value for the + * life of the resulting amqp_table_entry_t. + * \param [in] value the boolean value. 0 means false, any other value is true. + * \returns An initialized table entry. + */ +amqp_table_entry_t amqp_table_construct_bool_entry(const char *key, + const int value); + +/** + * Searches a table for an entry with a matching key. + * + * \param [in] table the table to search. + * \param [in] key the string to search with. + * \returns a pointer to the table entry in the table if a matching key can be + * found, NULL otherwise. + */ +amqp_table_entry_t *amqp_table_get_entry_by_key(const amqp_table_t *table, + const amqp_bytes_t key); + +#endif /* AMQP_TABLE_H */ diff --git a/librabbitmq/amqp_tcp_socket.c b/librabbitmq/amqp_tcp_socket.c new file mode 100644 index 0000000..1a88bc4 --- /dev/null +++ b/librabbitmq/amqp_tcp_socket.c @@ -0,0 +1,219 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amqp_private.h" +#include "rabbitmq-c/tcp_socket.h" + +#include +#if ((defined(_WIN32)) || (defined(__MINGW32__)) || (defined(__MINGW64__))) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#include +#endif +#include +#include + +struct amqp_tcp_socket_t { + const struct amqp_socket_class_t *klass; + int sockfd; + int internal_error; + int state; +}; + +static ssize_t amqp_tcp_socket_send(void *base, const void *buf, size_t len, + int flags) { + struct amqp_tcp_socket_t *self = (struct amqp_tcp_socket_t *)base; + ssize_t res; + int flagz = 0; + + if (-1 == self->sockfd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + +#ifdef MSG_NOSIGNAL + flagz |= MSG_NOSIGNAL; +#endif + +#if defined(MSG_MORE) + if (flags & AMQP_SF_MORE) { + flagz |= MSG_MORE; + } +/* Cygwin defines TCP_NOPUSH, but trying to use it will return not + * implemented. Disable it here. */ +#elif defined(TCP_NOPUSH) && !defined(__CYGWIN__) + if (flags & AMQP_SF_MORE && !(self->state & AMQP_SF_MORE)) { + int one = 1; + res = setsockopt(self->sockfd, IPPROTO_TCP, TCP_NOPUSH, &one, sizeof(one)); + if (0 != res) { + self->internal_error = res; + return AMQP_STATUS_SOCKET_ERROR; + } + self->state |= AMQP_SF_MORE; + } else if (!(flags & AMQP_SF_MORE) && self->state & AMQP_SF_MORE) { + int zero = 0; + res = + setsockopt(self->sockfd, IPPROTO_TCP, TCP_NOPUSH, &zero, sizeof(&zero)); + if (0 != res) { + self->internal_error = res; + res = AMQP_STATUS_SOCKET_ERROR; + } else { + self->state &= ~AMQP_SF_MORE; + } + } +#endif + +start: +#ifdef _WIN32 + res = send(self->sockfd, buf, (int)len, flagz); +#else + res = send(self->sockfd, buf, len, flagz); +#endif + + if (res < 0) { + self->internal_error = amqp_os_socket_error(); + switch (self->internal_error) { + case EINTR: + goto start; +#ifdef _WIN32 + case WSAEWOULDBLOCK: +#else + case EWOULDBLOCK: +#endif +#if defined(EAGAIN) && EAGAIN != EWOULDBLOCK + case EAGAIN: +#endif + res = AMQP_PRIVATE_STATUS_SOCKET_NEEDWRITE; + break; + default: + res = AMQP_STATUS_SOCKET_ERROR; + } + } else { + self->internal_error = 0; + } + + return res; +} + +static ssize_t amqp_tcp_socket_recv(void *base, void *buf, size_t len, + int flags) { + struct amqp_tcp_socket_t *self = (struct amqp_tcp_socket_t *)base; + ssize_t ret; + if (-1 == self->sockfd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + +start: +#ifdef _WIN32 + ret = recv(self->sockfd, buf, (int)len, flags); +#else + ret = recv(self->sockfd, buf, len, flags); +#endif + + if (0 > ret) { + self->internal_error = amqp_os_socket_error(); + switch (self->internal_error) { + case EINTR: + goto start; +#ifdef _WIN32 + case WSAEWOULDBLOCK: +#else + case EWOULDBLOCK: +#endif +#if defined(EAGAIN) && EAGAIN != EWOULDBLOCK + case EAGAIN: +#endif + ret = AMQP_PRIVATE_STATUS_SOCKET_NEEDREAD; + break; + default: + ret = AMQP_STATUS_SOCKET_ERROR; + } + } else if (0 == ret) { + ret = AMQP_STATUS_CONNECTION_CLOSED; + } + + return ret; +} + +static int amqp_tcp_socket_open(void *base, const char *host, int port, + const struct timeval *timeout) { + struct amqp_tcp_socket_t *self = (struct amqp_tcp_socket_t *)base; + if (-1 != self->sockfd) { + return AMQP_STATUS_SOCKET_INUSE; + } + self->sockfd = amqp_open_socket_noblock(host, port, timeout); + if (0 > self->sockfd) { + int err = self->sockfd; + self->sockfd = -1; + return err; + } + return AMQP_STATUS_OK; +} + +static int amqp_tcp_socket_close(void *base, + AMQP_UNUSED amqp_socket_close_enum force) { + struct amqp_tcp_socket_t *self = (struct amqp_tcp_socket_t *)base; + if (-1 == self->sockfd) { + return AMQP_STATUS_SOCKET_CLOSED; + } + + if (amqp_os_socket_close(self->sockfd)) { + return AMQP_STATUS_SOCKET_ERROR; + } + self->sockfd = -1; + + return AMQP_STATUS_OK; +} + +static int amqp_tcp_socket_get_sockfd(void *base) { + struct amqp_tcp_socket_t *self = (struct amqp_tcp_socket_t *)base; + return self->sockfd; +} + +static void amqp_tcp_socket_delete(void *base) { + struct amqp_tcp_socket_t *self = (struct amqp_tcp_socket_t *)base; + + if (self) { + amqp_tcp_socket_close(self, AMQP_SC_NONE); + free(self); + } +} + +static const struct amqp_socket_class_t amqp_tcp_socket_class = { + amqp_tcp_socket_send, /* send */ + amqp_tcp_socket_recv, /* recv */ + amqp_tcp_socket_open, /* open */ + amqp_tcp_socket_close, /* close */ + amqp_tcp_socket_get_sockfd, /* get_sockfd */ + amqp_tcp_socket_delete /* delete */ +}; + +amqp_socket_t *amqp_tcp_socket_new(amqp_connection_state_t state) { + struct amqp_tcp_socket_t *self = calloc(1, sizeof(*self)); + if (!self) { + return NULL; + } + self->klass = &amqp_tcp_socket_class; + self->sockfd = -1; + + amqp_set_socket(state, (amqp_socket_t *)self); + + return (amqp_socket_t *)self; +} + +void amqp_tcp_socket_set_sockfd(amqp_socket_t *base, int sockfd) { + struct amqp_tcp_socket_t *self; + if (base->klass != &amqp_tcp_socket_class) { + amqp_abort("<%p> is not of type amqp_tcp_socket_t", base); + } + self = (struct amqp_tcp_socket_t *)base; + self->sockfd = sockfd; +} diff --git a/librabbitmq/amqp_time.c b/librabbitmq/amqp_time.c new file mode 100644 index 0000000..f506b08 --- /dev/null +++ b/librabbitmq/amqp_time.c @@ -0,0 +1,236 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_time.h" +#include "rabbitmq-c/amqp.h" +#include +#include +#include + +#if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || \ + defined(__MINGW32__) || defined(__MINGW64__)) +#define AMQP_WIN_TIMER_API +#elif (defined(machintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) +#define AMQP_MAC_TIMER_API +#else +#define AMQP_POSIX_TIMER_API +#endif + +#ifdef AMQP_WIN_TIMER_API +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +uint64_t amqp_get_monotonic_timestamp(void) { + static double NS_PER_COUNT = 0; + LARGE_INTEGER perf_count; + + if (0 == NS_PER_COUNT) { + LARGE_INTEGER perf_frequency; + if (!QueryPerformanceFrequency(&perf_frequency)) { + return 0; + } + NS_PER_COUNT = (double)AMQP_NS_PER_S / perf_frequency.QuadPart; + } + + if (!QueryPerformanceCounter(&perf_count)) { + return 0; + } + + return (uint64_t)(perf_count.QuadPart * NS_PER_COUNT); +} +#endif /* AMQP_WIN_TIMER_API */ + +#ifdef AMQP_MAC_TIMER_API +#include + +uint64_t amqp_get_monotonic_timestamp(void) { + static mach_timebase_info_data_t s_timebase = {0, 0}; + uint64_t timestamp; + + timestamp = mach_absolute_time(); + + if (s_timebase.denom == 0) { + mach_timebase_info(&s_timebase); + if (0 == s_timebase.denom) { + return 0; + } + } + + timestamp *= (uint64_t)s_timebase.numer; + timestamp /= (uint64_t)s_timebase.denom; + + return timestamp; +} +#endif /* AMQP_MAC_TIMER_API */ + +#ifdef AMQP_POSIX_TIMER_API +#include + +uint64_t amqp_get_monotonic_timestamp(void) { +#ifdef __hpux + return (uint64_t)gethrtime(); +#else + struct timespec tp; + if (-1 == clock_gettime(CLOCK_MONOTONIC, &tp)) { + return 0; + } + + return ((uint64_t)tp.tv_sec * AMQP_NS_PER_S + (uint64_t)tp.tv_nsec); +#endif +} +#endif /* AMQP_POSIX_TIMER_API */ + +int amqp_time_from_now(amqp_time_t *time, const struct timeval *timeout) { + uint64_t now_ns; + uint64_t delta_ns; + + assert(NULL != time); + + if (NULL == timeout) { + *time = amqp_time_infinite(); + return AMQP_STATUS_OK; + } + + if (timeout->tv_sec < 0 || timeout->tv_usec < 0) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + delta_ns = (uint64_t)timeout->tv_sec * AMQP_NS_PER_S + + (uint64_t)timeout->tv_usec * AMQP_NS_PER_US; + + now_ns = amqp_get_monotonic_timestamp(); + if (0 == now_ns) { + return AMQP_STATUS_TIMER_FAILURE; + } + + time->time_point_ns = now_ns + delta_ns; + if (now_ns > time->time_point_ns || delta_ns > time->time_point_ns) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + return AMQP_STATUS_OK; +} + +int amqp_time_s_from_now(amqp_time_t *time, int seconds) { + uint64_t now_ns; + uint64_t delta_ns; + assert(NULL != time); + + if (0 >= seconds) { + *time = amqp_time_infinite(); + return AMQP_STATUS_OK; + } + + now_ns = amqp_get_monotonic_timestamp(); + if (0 == now_ns) { + return AMQP_STATUS_TIMER_FAILURE; + } + + delta_ns = (uint64_t)seconds * AMQP_NS_PER_S; + time->time_point_ns = now_ns + delta_ns; + if (now_ns > time->time_point_ns || delta_ns > time->time_point_ns) { + return AMQP_STATUS_INVALID_PARAMETER; + } + + return AMQP_STATUS_OK; +} + +amqp_time_t amqp_time_infinite(void) { + amqp_time_t time; + time.time_point_ns = UINT64_MAX; + return time; +} + +int amqp_time_ms_until(amqp_time_t time) { + uint64_t now_ns; + uint64_t delta_ns; + int left_ms; + + if (UINT64_MAX == time.time_point_ns) { + return -1; + } + if (0 == time.time_point_ns) { + return 0; + } + + now_ns = amqp_get_monotonic_timestamp(); + if (0 == now_ns) { + return AMQP_STATUS_TIMER_FAILURE; + } + + if (now_ns >= time.time_point_ns) { + return 0; + } + + delta_ns = time.time_point_ns - now_ns; + left_ms = (int)(delta_ns / AMQP_NS_PER_MS); + + return left_ms; +} + +int amqp_time_tv_until(amqp_time_t time, struct timeval *in, + struct timeval **out) { + uint64_t now_ns; + uint64_t delta_ns; + + assert(in != NULL); + if (UINT64_MAX == time.time_point_ns) { + *out = NULL; + return AMQP_STATUS_OK; + } + if (0 == time.time_point_ns) { + in->tv_sec = 0; + in->tv_usec = 0; + *out = in; + return AMQP_STATUS_OK; + } + + now_ns = amqp_get_monotonic_timestamp(); + if (0 == now_ns) { + return AMQP_STATUS_TIMER_FAILURE; + } + + if (now_ns >= time.time_point_ns) { + in->tv_sec = 0; + in->tv_usec = 0; + *out = in; + return AMQP_STATUS_OK; + } + + delta_ns = time.time_point_ns - now_ns; + in->tv_sec = (int)(delta_ns / AMQP_NS_PER_S); + in->tv_usec = (int)((delta_ns % AMQP_NS_PER_S) / AMQP_NS_PER_US); + *out = in; + + return AMQP_STATUS_OK; +} + +int amqp_time_has_past(amqp_time_t time) { + uint64_t now_ns; + if (UINT64_MAX == time.time_point_ns) { + return AMQP_STATUS_OK; + } + + now_ns = amqp_get_monotonic_timestamp(); + if (0 == now_ns) { + return AMQP_STATUS_TIMER_FAILURE; + } + + if (now_ns > time.time_point_ns) { + return AMQP_STATUS_TIMEOUT; + } + return AMQP_STATUS_OK; +} + +amqp_time_t amqp_time_first(amqp_time_t l, amqp_time_t r) { + if (l.time_point_ns < r.time_point_ns) { + return l; + } + return r; +} + +int amqp_time_equal(amqp_time_t l, amqp_time_t r) { + return l.time_point_ns == r.time_point_ns; +} diff --git a/librabbitmq/amqp_time.h b/librabbitmq/amqp_time.h new file mode 100644 index 0000000..7823796 --- /dev/null +++ b/librabbitmq/amqp_time.h @@ -0,0 +1,109 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_TIMER_H +#define AMQP_TIMER_H + +#include + +#if ((defined(_WIN32)) || (defined(__MINGW32__)) || (defined(__MINGW64__))) +#ifndef WINVER +#define WINVER 0x0502 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#endif + +#define AMQP_MS_PER_S 1000 +#define AMQP_US_PER_MS 1000 +#define AMQP_NS_PER_S 1000000000 +#define AMQP_NS_PER_MS 1000000 +#define AMQP_NS_PER_US 1000 + +/* This represents a point in time in reference to a monotonic clock. + * + * The internal representation is ns, relative to the monotonic clock. + * + * There are two 'special' values: + * - 0: means 'this instant', its meant for polls with a 0-timeout, or + * non-blocking option + * - UINT64_MAX: means 'at infinity', its mean for polls with an infinite + * timeout + */ +typedef struct amqp_time_t_ { + uint64_t time_point_ns; +} amqp_time_t; + +/* Gets a monotonic timestamp. This will return 0 if the underlying call to the + * system fails. + */ +uint64_t amqp_get_monotonic_timestamp(void); + +/* Get a amqp_time_t that is timeout from now. + * If timeout is NULL, an amqp_time_infinite() is created. + * + * Returns AMQP_STATUS_OK on success. + * AMQP_STATUS_INVALID_PARAMETER if timeout is invalid + * AMQP_STATUS_TIMER_FAILURE if the underlying call to get the current timestamp + * fails. + */ +int amqp_time_from_now(amqp_time_t *time, const struct timeval *timeout); + +/* Get a amqp_time_t that is seconds from now. + * If seconds <= 0, then amqp_time_infinite() is created. + * + * Returns AMQP_STATUS_OK on success. + * AMQP_STATUS_TIMER_FAILURE if the underlying call to get the current timestamp + * fails. + */ +int amqp_time_s_from_now(amqp_time_t *time, int seconds); + +/* Create an infinite amqp_time_t */ +amqp_time_t amqp_time_infinite(void); + +/* Gets the number of ms until the amqp_time_t, suitable for the timeout + * parameter in poll(). + * + * -1 will be returned for amqp_time_infinite values. + * 0 will be returned for amqp_time_immediate values. + * AMQP_STATUS_TIMEOUT will be returned if time was in the past. + * AMQP_STATUS_TIMER_FAILURE will be returned if the underlying call to get the + * current timestamp fails. + */ +int amqp_time_ms_until(amqp_time_t time); + +/* Gets a timeval filled in with the time until amqp_time_t. Suitable for the + * parameter in select(). + * + * The in parameter specifies a storage location for *out. + * If time is an inf timeout, then *out = NULL. + * If time is a 0-timeout or the timer has expired, then *out = {0, 0} + * Otherwise *out is set to the time left on the time. + * + * AMQP_STATUS_OK will be returned if successfully filled. + * AMQP_STATUS_TIMER_FAILURE is returned when the underlying call to get the + * current timestamp fails. + */ +int amqp_time_tv_until(amqp_time_t time, struct timeval *in, + struct timeval **out); + +/* Test whether current time is past the provided time. + * + * TODO: this isn't a great interface to use. Fix this. + * + * Return AMQP_STATUS_OK if time has not past + * Return AMQP_STATUS_TIMEOUT if time has past + * Return AMQP_STATUS_TIMER_FAILURE if the underlying call to get the current + * timestamp fails. + */ +int amqp_time_has_past(amqp_time_t time); + +/* Return the time value that happens first */ +amqp_time_t amqp_time_first(amqp_time_t l, amqp_time_t r); + +int amqp_time_equal(amqp_time_t l, amqp_time_t r); +#endif /* AMQP_TIMER_H */ diff --git a/librabbitmq/amqp_url.c b/librabbitmq/amqp_url.c new file mode 100644 index 0000000..db997b9 --- /dev/null +++ b/librabbitmq/amqp_url.c @@ -0,0 +1,188 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "amqp_private.h" +#include +#include +#include +#include +#include + +void amqp_default_connection_info(struct amqp_connection_info *ci) { + /* Apply defaults */ + ci->user = "guest"; + ci->password = "guest"; + ci->host = "localhost"; + ci->port = 5672; + ci->vhost = "/"; + ci->ssl = 0; +} + +/* Scan for the next delimiter, handling percent-encodings on the way. */ +static char find_delim(char **pp, int colon_and_at_sign_are_delims) { + char *from = *pp; + char *to = from; + + for (;;) { + char ch = *from++; + + switch (ch) { + case ':': + case '@': + if (!colon_and_at_sign_are_delims) { + *to++ = ch; + break; + } + + /* fall through */ + case 0: + case '/': + case '?': + case '#': + case '[': + case ']': + *to = 0; + *pp = from; + return ch; + + case '%': { + unsigned int val; + int chars; + int res = sscanf(from, "%2x%n", &val, &chars); + + if (res == EOF || res < 1 || chars != 2 || val > CHAR_MAX) + /* Return a surprising delimiter to + force an error. */ + { + return '%'; + } + + *to++ = (char)val; + from += 2; + break; + } + + default: + *to++ = ch; + break; + } + } +} + +/* Parse an AMQP URL into its component parts. */ +int amqp_parse_url(char *url, struct amqp_connection_info *parsed) { + int res = AMQP_STATUS_BAD_URL; + char delim; + char *start; + char *host; + char *port = NULL; + + amqp_default_connection_info(parsed); + + /* check the prefix */ + if (!strncmp(url, "amqp://", 7)) { + /* do nothing */ + } else if (!strncmp(url, "amqps://", 8)) { + parsed->port = 5671; + parsed->ssl = 1; + } else { + goto out; + } + + host = start = url += (parsed->ssl ? 8 : 7); + delim = find_delim(&url, 1); + + if (delim == ':') { + /* The colon could be introducing the port or the + password part of the userinfo. We don't know yet, + so stash the preceding component. */ + port = start = url; + delim = find_delim(&url, 1); + } + + if (delim == '@') { + /* What might have been the host and port were in fact + the username and password */ + parsed->user = host; + if (port) { + parsed->password = port; + } + + port = NULL; + host = start = url; + delim = find_delim(&url, 1); + } + + if (delim == '[') { + /* IPv6 address. The bracket should be the first + character in the host. */ + if (host != start || *host != 0) { + goto out; + } + + start = url; + delim = find_delim(&url, 0); + + if (delim != ']') { + goto out; + } + + parsed->host = start; + start = url; + delim = find_delim(&url, 1); + + /* Closing bracket should be the last character in the + host. */ + if (*start != 0) { + goto out; + } + } else { + /* If we haven't seen the host yet, this is it. */ + if (*host != 0) { + parsed->host = host; + } + } + + if (delim == ':') { + port = url; + delim = find_delim(&url, 1); + } + + if (port) { + char *end; + long portnum = strtol(port, &end, 10); + + if (port == end || *end != 0 || portnum < 0 || portnum > 65535) { + goto out; + } + + parsed->port = portnum; + } + + if (delim == '/') { + start = url; + delim = find_delim(&url, 1); + + if (delim != 0) { + goto out; + } + + parsed->vhost = start; + res = AMQP_STATUS_OK; + } else if (delim == 0) { + res = AMQP_STATUS_OK; + } + + /* Any other delimiter is bad, and we will return AMQP_STATUS_BAD_AMQP_URL. */ + +out: + return res; +} diff --git a/librabbitmq/codegen.py b/librabbitmq/codegen.py new file mode 100644 index 0000000..42bbe44 --- /dev/null +++ b/librabbitmq/codegen.py @@ -0,0 +1,692 @@ +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + + +from amqp_codegen import * +import string +import re + + +class Emitter(object): + """An object the trivially emits generated code lines. + + This largely exists to be wrapped by more sophisticated emitter + classes. + """ + + def __init__(self, prefix): + self.prefix = prefix + + def emit(self, line): + """Emit a line of generated code.""" + print(self.prefix + line) + + +class BitDecoder(object): + """An emitter object that keeps track of the state involved in + decoding the AMQP bit type.""" + + def __init__(self, emitter): + self.emitter = emitter + self.bit = 0 + + def emit(self, line): + self.bit = 0 + self.emitter.emit(line) + + def decode_bit(self, lvalue): + """Generate code to decode a value of the AMQP bit type into + the given lvalue.""" + if self.bit == 0: + self.emitter.emit("if (!amqp_decode_8(encoded, &offset, &bit_buffer)) return AMQP_STATUS_BAD_AMQP_DATA;") + + self.emitter.emit("%s = (bit_buffer & (1 << %d)) ? 1 : 0;" + % (lvalue, self.bit)) + self.bit += 1 + if self.bit == 8: + self.bit = 0 + + +class BitEncoder(object): + """An emitter object that keeps track of the state involved in + encoding the AMQP bit type.""" + + def __init__(self, emitter): + self.emitter = emitter + self.bit = 0 + + def flush(self): + """Flush the state associated with AMQP bit types.""" + if self.bit: + self.emitter.emit("if (!amqp_encode_8(encoded, &offset, bit_buffer)) return AMQP_STATUS_BAD_AMQP_DATA;") + self.bit = 0 + + def emit(self, line): + self.flush() + self.emitter.emit(line) + + def encode_bit(self, value): + """Generate code to encode a value of the AMQP bit type from + the given value.""" + if self.bit == 0: + self.emitter.emit("bit_buffer = 0;") + + self.emitter.emit("if (%s) bit_buffer |= (1 << %d);" + % (value, self.bit)) + self.bit += 1 + if self.bit == 8: + self.flush() + + +class SimpleType(object): + """A AMQP type that corresponds to a simple scalar C value of a + certain width.""" + + def __init__(self, bits): + self.bits = bits + self.ctype = "uint%d_t" % (bits,) + + def decode(self, emitter, lvalue): + emitter.emit("if (!amqp_decode_%d(encoded, &offset, &%s)) return AMQP_STATUS_BAD_AMQP_DATA;" % (self.bits, lvalue)) + + def encode(self, emitter, value): + emitter.emit("if (!amqp_encode_%d(encoded, &offset, %s)) return AMQP_STATUS_BAD_AMQP_DATA;" % (self.bits, value)) + + def literal(self, value): + return value + +class StrType(object): + """The AMQP shortstr or longstr types.""" + + def __init__(self, lenbits): + self.lenbits = lenbits + self.ctype = "amqp_bytes_t" + + def decode(self, emitter, lvalue): + emitter.emit("{") + emitter.emit(" uint%d_t len;" % (self.lenbits,)) + emitter.emit(" if (!amqp_decode_%d(encoded, &offset, &len)" % (self.lenbits,)) + emitter.emit(" || !amqp_decode_bytes(encoded, &offset, &%s, len))" % (lvalue,)) + emitter.emit(" return AMQP_STATUS_BAD_AMQP_DATA;") + emitter.emit("}") + + def encode(self, emitter, value): + emitter.emit("if (UINT%d_MAX < %s.len" % (self.lenbits, value)) + emitter.emit(" || !amqp_encode_%d(encoded, &offset, (uint%d_t)%s.len)" % + (self.lenbits, self.lenbits, value)) + emitter.emit(" || !amqp_encode_bytes(encoded, &offset, %s))" % (value,)) + emitter.emit(" return AMQP_STATUS_BAD_AMQP_DATA;") + + def literal(self, value): + if value != '': + raise NotImplementedError() + + return "amqp_empty_bytes" + +class BitType(object): + """The AMQP bit type.""" + + def __init__(self): + self.ctype = "amqp_boolean_t" + + def decode(self, emitter, lvalue): + emitter.decode_bit(lvalue) + + def encode(self, emitter, value): + emitter.encode_bit(value) + + def literal(self, value): + return {True: 1, False: 0}[value] + +class TableType(object): + """The AMQP table type.""" + + def __init__(self): + self.ctype = "amqp_table_t" + + def decode(self, emitter, lvalue): + emitter.emit("{") + emitter.emit(" int res = amqp_decode_table(encoded, pool, &(%s), &offset);" % (lvalue,)) + emitter.emit(" if (res < 0) return res;") + emitter.emit("}") + + def encode(self, emitter, value): + emitter.emit("{") + emitter.emit(" int res = amqp_encode_table(encoded, &(%s), &offset);" % (value,)) + emitter.emit(" if (res < 0) return res;") + emitter.emit("}") + + def literal(self, value): + raise NotImplementedError() + +types = { + 'octet': SimpleType(8), + 'short': SimpleType(16), + 'long': SimpleType(32), + 'longlong': SimpleType(64), + 'shortstr': StrType(8), + 'longstr': StrType(32), + 'bit': BitType(), + 'table': TableType(), + 'timestamp': SimpleType(64), +} + +def typeFor(spec, f): + """Get a representation of the AMQP type of a field.""" + return types[spec.resolveDomain(f.domain)] + +def c_ize(s): + s = s.replace('-', '_') + s = s.replace(' ', '_') + return s + +# When generating API functions corresponding to synchronous methods, +# we need some information that isn't in the protocol def: Some +# methods should not be exposed, indicated here by a False value. +# Some methods should be exposed but certain fields should not be +# exposed as parameters. +apiMethodInfo = { + "amqp_connection_start": False, # application code should not use this + "amqp_connection_secure": False, # application code should not use this + "amqp_connection_tune": False, # application code should not use this + "amqp_connection_open": False, # application code should not use this + "amqp_connection_close": False, # needs special handling + "amqp_channel_open": ["out_of_band"], + "amqp_channel_close": False, # needs special handling + "amqp_access_request": False, # huh? + "amqp_basic_get": False, # get-ok has content +} + +# When generating API functions corresponding to synchronous methods, +# some fields should be suppressed everywhere. This dict names those +# fields, and the fixed values to use for them. +apiMethodsSuppressArgs = {"ticket": 0, "nowait": False} + +AmqpMethod.defName = lambda m: cConstantName(c_ize(m.klass.name) + '_' + c_ize(m.name) + "_method") +AmqpMethod.fullName = lambda m: "amqp_%s_%s" % (c_ize(m.klass.name), c_ize(m.name)) +AmqpMethod.structName = lambda m: m.fullName() + "_t" + +AmqpClass.structName = lambda c: "amqp_" + c_ize(c.name) + "_properties_t" + +def methodApiPrototype(m): + fn = m.fullName() + info = apiMethodInfo.get(fn, []) + + docs = "/**\n * %s\n *\n" % (fn) + docs += " * @param [in] state connection state\n" + docs += " * @param [in] channel the channel to do the RPC on\n" + + args = [] + for f in m.arguments: + n = c_ize(f.name) + if n in apiMethodsSuppressArgs or n in info: + continue + + args.append(", ") + args.append(typeFor(m.klass.spec, f).ctype) + args.append(" ") + args.append(n) + docs += " * @param [in] %s %s\n" % (n, n) + + docs += " * @returns %s_ok_t\n" % (fn) + docs += " */\n" + + return "%sAMQP_EXPORT\n%s_ok_t *\nAMQP_CALL %s(amqp_connection_state_t state, amqp_channel_t channel%s)" % (docs, fn, fn, ''.join(args)) + +AmqpMethod.apiPrototype = methodApiPrototype + +def cConstantName(s): + return 'AMQP_' + '_'.join(re.split('[- ]', s.upper())) + +def cFlagName(c, f): + return cConstantName(c.name + '_' + f.name) + '_FLAG' + +def genErl(spec): + def fieldTempList(fields): + return '[' + ', '.join(['F' + str(f.index) for f in fields]) + ']' + + def fieldMapList(fields): + return ', '.join([c_ize(f.name) + " = F" + str(f.index) for f in fields]) + + def genLookupMethodName(m): + print(' case %s: return "%s";' % (m.defName(), m.defName())) + + def genDecodeMethodFields(m): + print(" case %s: {" % (m.defName(),)) + print(" %s *m = (%s *) amqp_pool_alloc(pool, sizeof(%s));" % \ + (m.structName(), m.structName(), m.structName())) + print(" if (m == NULL) { return AMQP_STATUS_NO_MEMORY; }") + + emitter = BitDecoder(Emitter(" ")) + for f in m.arguments: + typeFor(spec, f).decode(emitter, "m->"+c_ize(f.name)) + + print(" *decoded = m;") + print(" return 0;") + print(" }") + + def genDecodeProperties(c): + print(" case %d: {" % (c.index,)) + print(" %s *p = (%s *) amqp_pool_alloc(pool, sizeof(%s));" % \ + (c.structName(), c.structName(), c.structName())) + print(" if (p == NULL) { return AMQP_STATUS_NO_MEMORY; }") + print(" p->_flags = flags;") + + emitter = Emitter(" ") + for f in c.fields: + emitter.emit("if (flags & %s) {" % (cFlagName(c, f),)) + typeFor(spec, f).decode(emitter, "p->"+c_ize(f.name)) + emitter.emit("}") + + print(" *decoded = p;") + print(" return 0;") + print(" }") + + def genEncodeMethodFields(m): + print(" case %s: {" % (m.defName(),)) + if m.arguments: + print(" %s *m = (%s *) decoded;" % (m.structName(), m.structName())) + + emitter = BitEncoder(Emitter(" ")) + for f in m.arguments: + typeFor(spec, f).encode(emitter, "m->"+c_ize(f.name)) + emitter.flush() + + print(" return (int)offset;") + print(" }") + + def genEncodeProperties(c): + print(" case %d: {" % (c.index,)) + if c.fields: + print(" %s *p = (%s *) decoded;" % (c.structName(), c.structName())) + + emitter = Emitter(" ") + for f in c.fields: + emitter.emit(" if (flags & %s) {" % (cFlagName(c, f),)) + typeFor(spec, f).encode(emitter, "p->"+c_ize(f.name)) + emitter.emit("}") + + print(" return (int)offset;") + print(" }") + + methods = spec.allMethods() + + print("""// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + + /* Generated code. Do not edit. Edit and re-run codegen.py instead. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amqp_private.h" +#include +#include +#include +#include +""") + + print(""" +char const *amqp_constant_name(int constantNumber) { + switch (constantNumber) {""") + for (c,_,_) in spec.constants: + print(" case %s: return \"%s\";" % (cConstantName(c), cConstantName(c))) + print(""" default: return "(unknown)"; + } +}""") + + print(""" +amqp_boolean_t amqp_constant_is_hard_error(int constantNumber) { + switch (constantNumber) {""") + for (c,_,cls) in spec.constants: + if cls == 'hard-error': + print(" case %s: return 1;" % (cConstantName(c),)) + print(""" default: return 0; + } +}""") + + print(""" +char const *amqp_method_name(amqp_method_number_t methodNumber) { + switch (methodNumber) {""") + for m in methods: genLookupMethodName(m) + print(""" default: return NULL; + } +}""") + + print(""" +amqp_boolean_t amqp_method_has_content(amqp_method_number_t methodNumber) { + switch (methodNumber) {""") + for m in methods: + if m.hasContent: + print(' case %s: return 1;' % (m.defName())) + print(""" default: return 0; + } +}""") + + print(""" +int amqp_decode_method(amqp_method_number_t methodNumber, + amqp_pool_t *pool, + amqp_bytes_t encoded, + void **decoded) +{ + size_t offset = 0; + uint8_t bit_buffer; + + switch (methodNumber) {""") + for m in methods: genDecodeMethodFields(m) + print(""" default: return AMQP_STATUS_UNKNOWN_METHOD; + } +}""") + + print(""" +int amqp_decode_properties(uint16_t class_id, + amqp_pool_t *pool, + amqp_bytes_t encoded, + void **decoded) +{ + size_t offset = 0; + + amqp_flags_t flags = 0; + int flagword_index = 0; + uint16_t partial_flags; + + do { + if (!amqp_decode_16(encoded, &offset, &partial_flags)) + return AMQP_STATUS_BAD_AMQP_DATA; + flags |= (partial_flags << (flagword_index * 16)); + flagword_index++; + } while (partial_flags & 1); + + switch (class_id) {""") + for c in spec.allClasses(): genDecodeProperties(c) + print(""" default: return AMQP_STATUS_UNKNOWN_CLASS; + } +}""") + + print(""" +int amqp_encode_method(amqp_method_number_t methodNumber, + void *decoded, + amqp_bytes_t encoded) +{ + size_t offset = 0; + uint8_t bit_buffer; + + switch (methodNumber) {""") + for m in methods: genEncodeMethodFields(m) + print(""" default: return AMQP_STATUS_UNKNOWN_METHOD; + } +}""") + + print(""" +int amqp_encode_properties(uint16_t class_id, + void *decoded, + amqp_bytes_t encoded) +{ + size_t offset = 0; + + /* Cheat, and get the flags out generically, relying on the + similarity of structure between classes */ + amqp_flags_t flags = * (amqp_flags_t *) decoded; /* cheating! */ + + { + /* We take a copy of flags to avoid destroying it, as it is used + in the autogenerated code below. */ + amqp_flags_t remaining_flags = flags; + do { + amqp_flags_t remainder = remaining_flags >> 16; + uint16_t partial_flags = remaining_flags & 0xFFFE; + if (remainder != 0) { partial_flags |= 1; } + if (!amqp_encode_16(encoded, &offset, partial_flags)) + return AMQP_STATUS_BAD_AMQP_DATA; + remaining_flags = remainder; + } while (remaining_flags != 0); + } + + switch (class_id) {""") + for c in spec.allClasses(): genEncodeProperties(c) + print(""" default: return AMQP_STATUS_UNKNOWN_CLASS; + } +}""") + + for m in methods: + if not m.isSynchronous: + continue + + info = apiMethodInfo.get(m.fullName(), []) + if info is False: + continue + + print("") + print(m.apiPrototype()) + print("{") + print(" %s req;" % (m.structName(),)) + + for f in m.arguments: + n = c_ize(f.name) + + val = apiMethodsSuppressArgs.get(n) + if val is None and n in info: + val = f.defaultvalue + + if val is None: + val = n + else: + val = typeFor(spec, f).literal(val) + + + print(" req.%s = %s;" % (n, val)) + + reply = cConstantName(c_ize(m.klass.name) + '_' + c_ize(m.name) + + "_ok_method") + print(""" + return amqp_simple_rpc_decoded(state, channel, %s, %s, &req); +} +""" % (m.defName(), reply)) + +def genHrl(spec): + def fieldDeclList(fields): + if fields: + return ''.join([" %s %s; /**< %s */\n" % (typeFor(spec, f).ctype, + c_ize(f.name), f.name) + for f in fields]) + else: + return " char dummy; /**< Dummy field to avoid empty struct */\n" + + def propDeclList(fields): + return ''.join([" %s %s;\n" % (typeFor(spec, f).ctype, c_ize(f.name)) + for f in fields + if spec.resolveDomain(f.domain) != 'bit']) + + methods = spec.allMethods() + + print("""// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + + /* Generated code. Do not edit. Edit and re-run codegen.py instead. */ + +/** @file rabbitmq-c/framing.h */ +#ifndef RABBITMQ_C_FRAMING_H +#define RABBITMQ_C_FRAMING_H + +#include +#include + +AMQP_BEGIN_DECLS +""") + print("#define AMQP_PROTOCOL_VERSION_MAJOR %d /**< AMQP protocol version major */" % (spec.major)) + print("#define AMQP_PROTOCOL_VERSION_MINOR %d /**< AMQP protocol version minor */" % (spec.minor)) + print("#define AMQP_PROTOCOL_VERSION_REVISION %d /**< AMQP protocol version revision */" % (spec.revision)) + print("#define AMQP_PROTOCOL_PORT %d /**< Default AMQP Port */" % (spec.port)) + + for (c,v,cls) in spec.constants: + print("#define %s %s /**< Constant: %s */" % (cConstantName(c), v, c)) + print("") + + print("""/* Function prototypes. */ + +/** + * Get constant name string from constant + * + * @param [in] constantNumber constant to get the name of + * @returns string describing the constant. String is managed by + * the library and should not be free()'d by the program + */ +AMQP_EXPORT +char const * +AMQP_CALL amqp_constant_name(int constantNumber); + +/** + * Checks to see if a constant is a hard error + * + * A hard error occurs when something severe enough + * happens that the connection must be closed. + * + * @param [in] constantNumber the error constant + * @returns true if its a hard error, false otherwise + */ +AMQP_EXPORT +amqp_boolean_t +AMQP_CALL amqp_constant_is_hard_error(int constantNumber); + +/** + * Get method name string from method number + * + * @param [in] methodNumber the method number + * @returns method name string. String is managed by the library + * and should not be freed()'d by the program + */ +AMQP_EXPORT +char const * +AMQP_CALL amqp_method_name(amqp_method_number_t methodNumber); + +/** + * Check whether a method has content + * + * A method that has content will receive the method frame + * a properties frame, then 1 to N body frames + * + * @param [in] methodNumber the method number + * @returns true if method has content, false otherwise + */ +AMQP_EXPORT +amqp_boolean_t +AMQP_CALL amqp_method_has_content(amqp_method_number_t methodNumber); + +/** + * Decodes a method from AMQP wireformat + * + * @param [in] methodNumber the method number for the decoded parameter + * @param [in] pool the memory pool to allocate the decoded method from + * @param [in] encoded the encoded byte string buffer + * @param [out] decoded pointer to the decoded method struct + * @returns 0 on success, an error code otherwise + */ +AMQP_EXPORT +int +AMQP_CALL amqp_decode_method(amqp_method_number_t methodNumber, + amqp_pool_t *pool, + amqp_bytes_t encoded, + void **decoded); + +/** + * Decodes a header frame properties structure from AMQP wireformat + * + * @param [in] class_id the class id for the decoded parameter + * @param [in] pool the memory pool to allocate the decoded properties from + * @param [in] encoded the encoded byte string buffer + * @param [out] decoded pointer to the decoded properties struct + * @returns 0 on success, an error code otherwise + */ +AMQP_EXPORT +int +AMQP_CALL amqp_decode_properties(uint16_t class_id, + amqp_pool_t *pool, + amqp_bytes_t encoded, + void **decoded); + +/** + * Encodes a method structure in AMQP wireformat + * + * @param [in] methodNumber the method number for the decoded parameter + * @param [in] decoded the method structure (e.g., amqp_connection_start_t) + * @param [in] encoded an allocated byte buffer for the encoded method + * structure to be written to. If the buffer isn't large enough + * to hold the encoded method, an error code will be returned. + * @returns 0 on success, an error code otherwise. + */ +AMQP_EXPORT +int +AMQP_CALL amqp_encode_method(amqp_method_number_t methodNumber, + void *decoded, + amqp_bytes_t encoded); + +/** + * Encodes a properties structure in AMQP wireformat + * + * @param [in] class_id the class id for the decoded parameter + * @param [in] decoded the properties structure (e.g., amqp_basic_properties_t) + * @param [in] encoded an allocated byte buffer for the encoded properties to written to. + * If the buffer isn't large enough to hold the encoded method, an + * an error code will be returned + * @returns 0 on success, an error code otherwise. + */ +AMQP_EXPORT +int +AMQP_CALL amqp_encode_properties(uint16_t class_id, + void *decoded, + amqp_bytes_t encoded); +""") + + print("/* Method field records. */\n") + for m in methods: + methodid = m.klass.index << 16 | m.index + print("#define %s ((amqp_method_number_t) 0x%.08X) /**< %s.%s method id @internal %d, %d; %d */" % \ + (m.defName(), + methodid, + m.klass.name, + m.name, + m.klass.index, + m.index, + methodid)) + print("/** %s.%s method fields */\ntypedef struct %s_ {\n%s} %s;\n" % \ + (m.klass.name, m.name, m.structName(), fieldDeclList(m.arguments), m.structName())) + + print("/* Class property records. */") + for c in spec.allClasses(): + print("#define %s (0x%.04X) /**< %s class id @internal %d */" % \ + (cConstantName(c.name + "_class"), c.index, c.name, c.index)) + index = 0 + for f in c.fields: + if index % 16 == 15: + index = index + 1 + shortnum = index // 16 + partialindex = 15 - (index % 16) + bitindex = shortnum * 16 + partialindex + print('#define %s (1 << %d) /**< %s.%s property flag */' % (cFlagName(c, f), bitindex, c.name, f.name)) + index = index + 1 + print("/** %s class properties */\ntypedef struct %s_ {\n amqp_flags_t _flags; /**< bit-mask of set fields */\n%s} %s;\n" % \ + (c.name, + c.structName(), + fieldDeclList(c.fields), + c.structName())) + + print("/* API functions for methods */\n") + + for m in methods: + if m.isSynchronous and apiMethodInfo.get(m.fullName()) is not False: + print("%s;" % (m.apiPrototype(),)) + + print(""" +AMQP_END_DECLS + +#endif /* RABBITMQ_C_FRAMING_H */""") + +def generateErl(specPath): + genErl(AmqpSpec(specPath)) + +def generateHrl(specPath): + genHrl(AmqpSpec(specPath)) + +if __name__ == "__main__": + do_main(generateHrl, generateErl) diff --git a/librabbitmq/unix/threads.h b/librabbitmq/unix/threads.h new file mode 100644 index 0000000..4abd0c0 --- /dev/null +++ b/librabbitmq/unix/threads.h @@ -0,0 +1,9 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_THREADS_H +#define AMQP_THREADS_H + +#include + +#endif /* AMQP_THREADS_H */ diff --git a/librabbitmq/win32/threads.c b/librabbitmq/win32/threads.c new file mode 100644 index 0000000..050af9b --- /dev/null +++ b/librabbitmq/win32/threads.c @@ -0,0 +1,37 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "threads.h" + +#include + +DWORD pthread_self(void) { return GetCurrentThreadId(); } + +int pthread_mutex_init(pthread_mutex_t *mutex, void *attr) { + if (!mutex) { + return 1; + } + InitializeSRWLock(mutex); + return 0; +} + +int pthread_mutex_lock(pthread_mutex_t *mutex) { + if (!mutex) { + return 1; + } + AcquireSRWLockExclusive(mutex); + return 0; +} + +int pthread_mutex_unlock(pthread_mutex_t *mutex) { + if (!mutex) { + return 1; + } + ReleaseSRWLockExclusive(mutex); + return 0; +} + +int pthread_mutex_destroy(pthread_mutex_t *mutex) { + /* SRW's do not require destruction. */ + return 0; +} diff --git a/librabbitmq/win32/threads.h b/librabbitmq/win32/threads.h new file mode 100644 index 0000000..513356c --- /dev/null +++ b/librabbitmq/win32/threads.h @@ -0,0 +1,29 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifndef AMQP_THREAD_H +#define AMQP_THREAD_H + +#if !defined(WINVER) || defined(__MINGW32__) || defined(__MINGW64__) +#ifdef WINVER +#undef WINVER +#endif +/* Windows Vista or newer */ +#define WINVER 0x0600 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +typedef SRWLOCK pthread_mutex_t; +#define PTHREAD_MUTEX_INITIALIZER SRWLOCK_INIT; + +DWORD pthread_self(void); + +int pthread_mutex_init(pthread_mutex_t *, void *attr); +int pthread_mutex_lock(pthread_mutex_t *); +int pthread_mutex_unlock(pthread_mutex_t *); +int pthread_mutex_destroy(pthread_mutex_t *); + +#endif /* AMQP_THREAD_H */ diff --git a/regenerate_framing.sh b/regenerate_framing.sh new file mode 100644 index 0000000..fa6e5de --- /dev/null +++ b/regenerate_framing.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +set -e + +RMQ_VERSION=3.8.19 +DATA=${PWD}/codegen/rabbitmq-server-${RMQ_VERSION}/deps/rabbitmq_codegen/amqp-rabbitmq-0.9.1.json +export PYTHONPATH=${PWD}/codegen/rabbitmq-server-${RMQ_VERSION}/deps/rabbitmq_codegen + +rm -rf codegen +mkdir codegen + +wget -c https://github.com/rabbitmq/rabbitmq-server/releases/download/v${RMQ_VERSION}/rabbitmq-server-${RMQ_VERSION}.tar.xz -O - | tar -xJ -C codegen + +python librabbitmq/codegen.py header ${DATA} include/rabbitmq-c/framing.h +python librabbitmq/codegen.py body ${DATA} librabbitmq/amqp_framing.c + +clang-format -i include/rabbitmq-c/framing.h librabbitmq/amqp_framing.c \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8c0aee0 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +include_directories( + ${LIBRABBITMQ_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR}/../librabbitmq/ + ${CMAKE_CURRENT_SOURCE_DIR}/../librabbitmq/) + +add_definitions(-DHAVE_CONFIG_H) +add_definitions(-DAMQP_STATIC) + +add_executable(test_parse_url test_parse_url.c) +target_link_libraries(test_parse_url rabbitmq-static) +add_test(parse_url test_parse_url) + +add_executable(test_tables test_tables.c) +target_link_libraries(test_tables rabbitmq-static) +add_test(tables test_tables) +configure_file(test_tables.expected ${CMAKE_CURRENT_BINARY_DIR}/tests/test_tables.expected COPYONLY) + +add_executable(test_status_enum + test_status_enum.c) +target_link_libraries(test_status_enum rabbitmq-static) +add_test(status_enum test_status_enum) + +add_executable(test_basic + test_basic.c) +target_link_libraries(test_basic rabbitmq-static) + +if (RUN_SYSTEM_TESTS) + if (NOT APPLE) + add_test(basic test_basic) + endif() +endif(RUN_SYSTEM_TESTS) + +add_executable(test_sasl_mechanism test_sasl_mechanism.c) +target_link_libraries(test_sasl_mechanism rabbitmq-static) +add_test(sasl_mechanism test_sasl_mechanism) + +add_executable(test_merge_capabilities test_merge_capabilities.c) +target_link_libraries(test_merge_capabilities rabbitmq-static) +add_test(merge_capabilities test_merge_capabilities) + diff --git a/tests/test_basic.c b/tests/test_basic.c new file mode 100644 index 0000000..514c7ce --- /dev/null +++ b/tests/test_basic.c @@ -0,0 +1,194 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_time.h" +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +static const int fixed_channel_id = 1; +static const char test_queue_name[] = "test_queue"; + +amqp_connection_state_t setup_connection_and_channel(void) { + amqp_connection_state_t connection_state_ = amqp_new_connection(); + + amqp_socket_t *socket = amqp_tcp_socket_new(connection_state_); + assert(socket); + + int rc = amqp_socket_open(socket, "localhost", AMQP_PROTOCOL_PORT); + assert(rc == AMQP_STATUS_OK); + + amqp_rpc_reply_t rpc_reply = amqp_login( + connection_state_, "/", 1, AMQP_DEFAULT_FRAME_SIZE, + AMQP_DEFAULT_HEARTBEAT, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + assert(rpc_reply.reply_type == AMQP_RESPONSE_NORMAL); + + amqp_channel_open_ok_t *res = + amqp_channel_open(connection_state_, fixed_channel_id); + assert(res != NULL); + + return connection_state_; +} + +void close_and_destroy_connection(amqp_connection_state_t connection_state_) { + amqp_rpc_reply_t rpc_reply = + amqp_connection_close(connection_state_, AMQP_REPLY_SUCCESS); + assert(rpc_reply.reply_type == AMQP_RESPONSE_NORMAL); + + int rc = amqp_destroy_connection(connection_state_); + assert(rc == AMQP_STATUS_OK); +} + +void basic_publish(amqp_connection_state_t connectionState_, + const char *message_) { + amqp_bytes_t message_bytes = amqp_cstring_bytes(message_); + + amqp_basic_properties_t properties; + properties._flags = 0; + + properties._flags |= AMQP_BASIC_DELIVERY_MODE_FLAG; + properties.delivery_mode = AMQP_DELIVERY_NONPERSISTENT; + + int retval = amqp_basic_publish( + connectionState_, fixed_channel_id, amqp_cstring_bytes(""), + amqp_cstring_bytes(test_queue_name), + /* mandatory=*/1, + /* immediate=*/0, /* RabbitMQ 3.x does not support the "immediate" flag + according to + https://www.rabbitmq.com/specification.html */ + &properties, message_bytes); + + assert(retval == 0); +} + +void queue_declare(amqp_connection_state_t connection_state_, + const char *queue_name_) { + amqp_queue_declare_ok_t *res = amqp_queue_declare( + connection_state_, fixed_channel_id, amqp_cstring_bytes(queue_name_), + /*passive*/ 0, + /*durable*/ 0, + /*exclusive*/ 0, + /*auto_delete*/ 1, amqp_empty_table); + assert(res != NULL); +} + +char *basic_get(amqp_connection_state_t connection_state_, + const char *queue_name_, uint64_t *out_body_size_) { + amqp_rpc_reply_t rpc_reply; + amqp_time_t deadline; + struct timeval timeout = {5, 0}; + int time_rc = amqp_time_from_now(&deadline, &timeout); + assert(time_rc == AMQP_STATUS_OK); + + do { + rpc_reply = amqp_basic_get(connection_state_, fixed_channel_id, + amqp_cstring_bytes(queue_name_), /*no_ack*/ 1); + } while (rpc_reply.reply_type == AMQP_RESPONSE_NORMAL && + rpc_reply.reply.id == AMQP_BASIC_GET_EMPTY_METHOD && + amqp_time_has_past(deadline) == AMQP_STATUS_OK); + + assert(rpc_reply.reply_type == AMQP_RESPONSE_NORMAL); + assert(rpc_reply.reply.id == AMQP_BASIC_GET_OK_METHOD); + + amqp_message_t message; + rpc_reply = + amqp_read_message(connection_state_, fixed_channel_id, &message, 0); + assert(rpc_reply.reply_type == AMQP_RESPONSE_NORMAL); + + char *body = malloc(message.body.len); + if (body == NULL) { + return NULL; + } + memcpy(body, message.body.bytes, message.body.len); + *out_body_size_ = message.body.len; + amqp_destroy_message(&message); + + return body; +} + +void publish_and_basic_get_message(const char *msg_to_publish) { + amqp_connection_state_t connection_state = setup_connection_and_channel(); + + queue_declare(connection_state, test_queue_name); + basic_publish(connection_state, msg_to_publish); + + uint64_t body_size; + char *msg = basic_get(connection_state, test_queue_name, &body_size); + + assert(msg != NULL && "Test errored: memory allocation failed!"); + assert(body_size == strlen(msg_to_publish)); + assert(strncmp(msg_to_publish, msg, body_size) == 0); + free(msg); + + close_and_destroy_connection(connection_state); +} + +char *consume_message(amqp_connection_state_t connection_state_, + const char *queue_name_, uint64_t *out_body_size_) { + amqp_basic_consume_ok_t *result = + amqp_basic_consume(connection_state_, fixed_channel_id, + amqp_cstring_bytes(queue_name_), amqp_empty_bytes, + /*no_local*/ 0, + /*no_ack*/ 1, + /*exclusive*/ 0, amqp_empty_table); + assert(result != NULL); + + amqp_envelope_t envelope; + struct timeval timeout = {5, 0}; + amqp_rpc_reply_t rpc_reply = + amqp_consume_message(connection_state_, &envelope, &timeout, 0); + assert(rpc_reply.reply_type == AMQP_RESPONSE_NORMAL); + + *out_body_size_ = envelope.message.body.len; + char *body = malloc(*out_body_size_); + if (body == NULL) { + return NULL; + } + if (*out_body_size_) { + memcpy(body, envelope.message.body.bytes, *out_body_size_); + } + + amqp_destroy_envelope(&envelope); + return body; +} + +void publish_and_consume_message(const char *msg_to_publish) { + amqp_connection_state_t connection_state = setup_connection_and_channel(); + + queue_declare(connection_state, test_queue_name); + basic_publish(connection_state, msg_to_publish); + + uint64_t body_size; + char *msg = consume_message(connection_state, test_queue_name, &body_size); + + assert(msg != NULL && "Test errored: memory allocation failed!"); + assert(body_size == strlen(msg_to_publish)); + assert(strncmp(msg_to_publish, msg, body_size) == 0); + free(msg); + + close_and_destroy_connection(connection_state); +} + +int main(void) { + publish_and_basic_get_message(""); + publish_and_basic_get_message("TEST"); + + publish_and_consume_message(""); + publish_and_consume_message("TEST"); + + return 0; +} diff --git a/tests/test_merge_capabilities.c b/tests/test_merge_capabilities.c new file mode 100644 index 0000000..f82de5f --- /dev/null +++ b/tests/test_merge_capabilities.c @@ -0,0 +1,182 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_socket.h" +#include "amqp_table.h" + +#include +#include + +static int compare_bytes(amqp_bytes_t l, amqp_bytes_t r); +static int compare_amqp_table_entry(amqp_table_entry_t result, + amqp_table_entry_t expect); +static int compare_field_value(amqp_field_value_t result, + amqp_field_value_t expect); +static int compare_amqp_table(amqp_table_t* result, amqp_table_t* expect); + +static int compare_bytes(amqp_bytes_t l, amqp_bytes_t r) { + if (l.len == r.len && + (l.bytes == r.bytes || 0 == memcmp(l.bytes, r.bytes, l.len))) { + return 1; + } + return 0; +} + +static int compare_amqp_table_entry(amqp_table_entry_t result, + amqp_table_entry_t expect) { + if (!compare_bytes(result.key, expect.key)) { + return 0; + } + return compare_field_value(result.value, expect.value); +} + +static int compare_field_value(amqp_field_value_t result, + amqp_field_value_t expect) { + if (result.kind != expect.kind) { + return 0; + } + switch (result.kind) { + case AMQP_FIELD_KIND_BOOLEAN: + return result.value.boolean == expect.value.boolean; + case AMQP_FIELD_KIND_I8: + return result.value.i8 == expect.value.i8; + case AMQP_FIELD_KIND_U8: + return result.value.u8 == expect.value.u8; + case AMQP_FIELD_KIND_I16: + return result.value.i16 == expect.value.i16; + case AMQP_FIELD_KIND_U16: + return result.value.u16 == expect.value.u16; + case AMQP_FIELD_KIND_I32: + return result.value.i32 == expect.value.i32; + case AMQP_FIELD_KIND_U32: + return result.value.u32 == expect.value.u32; + case AMQP_FIELD_KIND_I64: + return result.value.i64 == expect.value.i64; + case AMQP_FIELD_KIND_U64: + case AMQP_FIELD_KIND_TIMESTAMP: + return result.value.u64 == expect.value.u64; + case AMQP_FIELD_KIND_F32: + return result.value.f32 == expect.value.f32; + case AMQP_FIELD_KIND_F64: + return result.value.f64 == expect.value.f64; + case AMQP_FIELD_KIND_DECIMAL: + return !memcmp(&result.value.decimal, &expect.value.decimal, + sizeof(expect.value.decimal)); + case AMQP_FIELD_KIND_UTF8: + case AMQP_FIELD_KIND_BYTES: + return compare_bytes(result.value.bytes, expect.value.bytes); + case AMQP_FIELD_KIND_ARRAY: { + int i; + if (result.value.array.num_entries != expect.value.array.num_entries) { + return 0; + } + for (i = 0; i < result.value.array.num_entries; ++i) { + if (!compare_field_value(result.value.array.entries[i], + expect.value.array.entries[i])) { + return 0; + } + } + return 1; + } + case AMQP_FIELD_KIND_TABLE: + return compare_amqp_table(&result.value.table, &expect.value.table); + case AMQP_FIELD_KIND_VOID: + return 1; + } + return 1; +} + +static int compare_amqp_table(amqp_table_t* result, amqp_table_t* expect) { + int i; + + if (result->num_entries != expect->num_entries) { + return 0; + } + + for (i = 0; i < expect->num_entries; ++i) { + if (!compare_amqp_table_entry(expect->entries[i], result->entries[i])) { + return 0; + } + } + return 1; +} + +static void test_merge_capabilities(amqp_table_t* base, amqp_table_t* add, + amqp_table_t* expect) { + amqp_pool_t pool; + amqp_table_t result; + int res; + init_amqp_pool(&pool, 4096); + + res = amqp_merge_capabilities(base, add, &result, &pool); + if (AMQP_STATUS_OK != res) { + fprintf(stderr, "amqp_merge_capabilities returned !ok: %d\n", res); + abort(); + } + + if (!compare_amqp_table(&result, expect)) { + fprintf(stderr, "amqp_merge_capabilities incorrect result.\n"); + abort(); + } + empty_amqp_pool(&pool); + return; +} + +int main(void) { + { + amqp_table_t sub_base; + amqp_table_t sub_add; + amqp_table_t sub_expect; + amqp_table_t base; + amqp_table_t add; + amqp_table_t expect; + + amqp_table_entry_t sub_base_entries[1]; + amqp_table_entry_t sub_add_entries[2]; + amqp_table_entry_t sub_expect_entries[2]; + + amqp_table_entry_t base_entries[3]; + amqp_table_entry_t add_entries[3]; + amqp_table_entry_t expect_entries[4]; + + sub_base_entries[0] = amqp_table_construct_utf8_entry("foo", "bar"); + sub_base.num_entries = + sizeof(sub_base_entries) / sizeof(amqp_table_entry_t); + sub_base.entries = sub_base_entries; + + sub_add_entries[0] = amqp_table_construct_utf8_entry("something", "else"); + sub_add_entries[1] = amqp_table_construct_utf8_entry("foo", "baz"); + sub_add.num_entries = sizeof(sub_add_entries) / sizeof(amqp_table_entry_t); + sub_add.entries = sub_add_entries; + + sub_expect_entries[0] = amqp_table_construct_utf8_entry("foo", "baz"); + sub_expect_entries[1] = + amqp_table_construct_utf8_entry("something", "else"); + sub_expect.num_entries = + sizeof(sub_expect_entries) / sizeof(amqp_table_entry_t); + sub_expect.entries = sub_expect_entries; + + base_entries[0] = amqp_table_construct_utf8_entry("product", "1.0"); + base_entries[1] = amqp_table_construct_utf8_entry("nooverride", "yeah"); + base_entries[2] = amqp_table_construct_table_entry("props", &sub_base); + base.num_entries = sizeof(base_entries) / sizeof(amqp_table_entry_t); + base.entries = base_entries; + + add_entries[0] = amqp_table_construct_bool_entry("bool_entry", 1); + add_entries[1] = amqp_table_construct_utf8_entry("product", "2.0"); + add_entries[2] = amqp_table_construct_table_entry("props", &sub_add); + add.num_entries = sizeof(add_entries) / sizeof(amqp_table_entry_t); + add.entries = add_entries; + + expect_entries[0] = amqp_table_construct_utf8_entry("product", "2.0"), + expect_entries[1] = amqp_table_construct_utf8_entry("nooverride", "yeah"), + expect_entries[2] = amqp_table_construct_table_entry("props", &sub_expect); + expect_entries[3] = amqp_table_construct_bool_entry("bool_entry", 1); + expect.num_entries = sizeof(expect_entries) / sizeof(amqp_table_entry_t); + expect.entries = expect_entries; + + test_merge_capabilities(&base, &add, &expect); + } + fprintf(stderr, "ok\n"); + return 0; +} diff --git a/tests/test_parse_url.c b/tests/test_parse_url.c new file mode 100644 index 0000000..420c6c9 --- /dev/null +++ b/tests/test_parse_url.c @@ -0,0 +1,188 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "config.h" + +#ifdef _MSC_VER +/* MSVC complains about strdup being deprecated in favor of _strdup */ +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#include +#include +#include + +#include + +#include + +static void match_string(const char *what, const char *expect, + const char *got) { + if (strcmp(got, expect)) { + fprintf(stderr, "Expected %s '%s', got '%s'\n", what, expect, got); + abort(); + } +} + +static void match_int(const char *what, int expect, int got) { + if (got != expect) { + fprintf(stderr, "Expected %s '%d', got '%d'\n", what, expect, got); + abort(); + } +} + +static void parse_success(const char *url, const char *user, + const char *password, const char *host, int port, + const char *vhost) { + char *s = strdup(url); + struct amqp_connection_info ci; + int res; + + res = amqp_parse_url(s, &ci); + if (res) { + fprintf(stderr, "Expected to successfully parse URL, but didn't: %s (%s)\n", + url, amqp_error_string2(res)); + abort(); + } + + match_string("user", user, ci.user); + match_string("password", password, ci.password); + match_string("host", host, ci.host); + match_int("port", port, ci.port); + match_string("vhost", vhost, ci.vhost); + + free(s); +} + +static void parse_fail(const char *url) { + char *s = strdup(url); + struct amqp_connection_info ci; + + amqp_default_connection_info(&ci); + if (amqp_parse_url(s, &ci) >= 0) { + fprintf(stderr, "Expected to fail parsing URL, but didn't: %s\n", url); + abort(); + } + + free(s); +} + +int main(void) { + /* From the spec */ + parse_success("amqp://user:pass@host:10000/vhost", "user", "pass", "host", + 10000, "vhost"); + parse_success("amqps://user:pass@host:10000/vhost", "user", "pass", "host", + 10000, "vhost"); + + parse_success("amqp://user%61:%61pass@ho%61st:10000/v%2fhost", "usera", + "apass", "hoast", 10000, "v/host"); + parse_success("amqps://user%61:%61pass@ho%61st:10000/v%2fhost", "usera", + "apass", "hoast", 10000, "v/host"); + + parse_success("amqp://", "guest", "guest", "localhost", 5672, "/"); + parse_success("amqps://", "guest", "guest", "localhost", 5671, "/"); + + parse_success("amqp://:@/", "", "", "localhost", 5672, ""); + parse_success("amqps://:@/", "", "", "localhost", 5671, ""); + + parse_success("amqp://user@", "user", "guest", "localhost", 5672, "/"); + parse_success("amqps://user@", "user", "guest", "localhost", 5671, "/"); + + parse_success("amqp://user:pass@", "user", "pass", "localhost", 5672, "/"); + parse_success("amqps://user:pass@", "user", "pass", "localhost", 5671, "/"); + + parse_success("amqp://host", "guest", "guest", "host", 5672, "/"); + parse_success("amqps://host", "guest", "guest", "host", 5671, "/"); + + parse_success("amqp://:10000", "guest", "guest", "localhost", 10000, "/"); + parse_success("amqps://:10000", "guest", "guest", "localhost", 10000, "/"); + + parse_success("amqp:///vhost", "guest", "guest", "localhost", 5672, "vhost"); + parse_success("amqps:///vhost", "guest", "guest", "localhost", 5671, "vhost"); + + parse_success("amqp://host/", "guest", "guest", "host", 5672, ""); + parse_success("amqps://host/", "guest", "guest", "host", 5671, ""); + + parse_success("amqp://host/%2f", "guest", "guest", "host", 5672, "/"); + parse_success("amqps://host/%2f", "guest", "guest", "host", 5671, "/"); + + parse_success("amqp://[::1]", "guest", "guest", "::1", 5672, "/"); + parse_success("amqps://[::1]", "guest", "guest", "::1", 5671, "/"); + + /* Various other success cases */ + parse_success("amqp://host:100", "guest", "guest", "host", 100, "/"); + parse_success("amqps://host:100", "guest", "guest", "host", 100, "/"); + + parse_success("amqp://[::1]:100", "guest", "guest", "::1", 100, "/"); + parse_success("amqps://[::1]:100", "guest", "guest", "::1", 100, "/"); + + parse_success("amqp://host/blah", "guest", "guest", "host", 5672, "blah"); + parse_success("amqps://host/blah", "guest", "guest", "host", 5671, "blah"); + + parse_success("amqp://host:100/blah", "guest", "guest", "host", 100, "blah"); + parse_success("amqps://host:100/blah", "guest", "guest", "host", 100, "blah"); + + parse_success("amqp://:100/blah", "guest", "guest", "localhost", 100, "blah"); + parse_success("amqps://:100/blah", "guest", "guest", "localhost", 100, + "blah"); + + parse_success("amqp://[::1]/blah", "guest", "guest", "::1", 5672, "blah"); + parse_success("amqps://[::1]/blah", "guest", "guest", "::1", 5671, "blah"); + + parse_success("amqp://[::1]:100/blah", "guest", "guest", "::1", 100, "blah"); + parse_success("amqps://[::1]:100/blah", "guest", "guest", "::1", 100, "blah"); + + parse_success("amqp://user:pass@host", "user", "pass", "host", 5672, "/"); + parse_success("amqps://user:pass@host", "user", "pass", "host", 5671, "/"); + + parse_success("amqp://user:pass@host:100", "user", "pass", "host", 100, "/"); + parse_success("amqps://user:pass@host:100", "user", "pass", "host", 100, "/"); + + parse_success("amqp://user:pass@:100", "user", "pass", "localhost", 100, "/"); + parse_success("amqps://user:pass@:100", "user", "pass", "localhost", 100, + "/"); + + parse_success("amqp://user:pass@[::1]", "user", "pass", "::1", 5672, "/"); + parse_success("amqps://user:pass@[::1]", "user", "pass", "::1", 5671, "/"); + + parse_success("amqp://user:pass@[::1]:100", "user", "pass", "::1", 100, "/"); + parse_success("amqps://user:pass@[::1]:100", "user", "pass", "::1", 100, "/"); + + /* Various failure cases */ + parse_fail("http://www.rabbitmq.com"); + + parse_fail("amqp://foo:bar:baz"); + parse_fail("amqps://foo:bar:baz"); + + parse_fail("amqp://foo[::1]"); + parse_fail("amqps://foo[::1]"); + + parse_fail("amqp://foo[::1]"); + parse_fail("amqps://foo[::1]"); + + parse_fail("amqp://foo:[::1]"); + parse_fail("amqps://foo:[::1]"); + + parse_fail("amqp://[::1]foo"); + parse_fail("amqps://[::1]foo"); + + parse_fail("amqp://foo:1000xyz"); + parse_fail("amqps://foo:1000xyz"); + + parse_fail("amqp://foo:1000000"); + parse_fail("amqps://foo:1000000"); + + parse_fail("amqp://foo/bar/baz"); + parse_fail("amqps://foo/bar/baz"); + + parse_fail("amqp://foo%1"); + parse_fail("amqps://foo%1"); + + parse_fail("amqp://foo%1x"); + parse_fail("amqps://foo%1x"); + + parse_fail("amqp://foo%xy"); + parse_fail("amqps://foo%xy"); + + return 0; +} diff --git a/tests/test_sasl_mechanism.c b/tests/test_sasl_mechanism.c new file mode 100644 index 0000000..3628443 --- /dev/null +++ b/tests/test_sasl_mechanism.c @@ -0,0 +1,38 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include + +#include + +static void parse_success(amqp_bytes_t mechanisms, + amqp_sasl_method_enum method) { + if (!sasl_mechanism_in_list(mechanisms, method)) { + fprintf(stderr, "Expected to find mechanism in list, but didn't: %s\n", + (char *)mechanisms.bytes); + abort(); + } +} + +static void parse_fail(amqp_bytes_t mechanisms, amqp_sasl_method_enum method) { + if (sasl_mechanism_in_list(mechanisms, method)) { + fprintf(stderr, + "Expected the mechanism not on the list, but it was present: %s\n", + (char *)mechanisms.bytes); + abort(); + } +} + +int main(void) { + parse_success(amqp_literal_bytes("DIGEST-MD5 CRAM-MD5 LOGIN PLAIN"), + AMQP_SASL_METHOD_PLAIN); + parse_fail(amqp_literal_bytes("DIGEST-MD5 CRAM-MD5 LOGIN PLAIN"), + AMQP_SASL_METHOD_EXTERNAL); + parse_success(amqp_literal_bytes("DIGEST-MD5 CRAM-MD5 EXTERNAL"), + AMQP_SASL_METHOD_EXTERNAL); + parse_fail(amqp_literal_bytes("DIGEST-MD5 CRAM-MD5 EXTERNAL"), + AMQP_SASL_METHOD_PLAIN); + return 0; +} diff --git a/tests/test_status_enum.c b/tests/test_status_enum.c new file mode 100644 index 0000000..c27a23b --- /dev/null +++ b/tests/test_status_enum.c @@ -0,0 +1,31 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +static void check_errorstrings(amqp_status_enum start, amqp_status_enum end) { + int i; + for (i = start; i > end; --i) { + const char* err = amqp_error_string2(i); + if (0 == strcmp(err, "(unknown error)")) { + printf("amqp_status_enum value %s%X", i < 0 ? "-" : "", (unsigned)i); + abort(); + } + } +} + +int main(void) { + check_errorstrings(AMQP_STATUS_OK, _AMQP_STATUS_NEXT_VALUE); + check_errorstrings(AMQP_STATUS_TCP_ERROR, _AMQP_STATUS_TCP_NEXT_VALUE); + check_errorstrings(AMQP_STATUS_SSL_ERROR, _AMQP_STATUS_SSL_NEXT_VALUE); + + return 0; +} diff --git a/tests/test_tables.c b/tests/test_tables.c new file mode 100644 index 0000000..d801a89 --- /dev/null +++ b/tests/test_tables.c @@ -0,0 +1,434 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +void die(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + abort(); +} + +static void dump_indent(int indent, FILE *out) { + int i; + + for (i = 0; i < indent; i++) { + fputc(' ', out); + } +} + +static void dump_value(int indent, amqp_field_value_t v, FILE *out) { + int i; + + dump_indent(indent, out); + fputc(v.kind, out); + + switch (v.kind) { + case AMQP_FIELD_KIND_BOOLEAN: + fputs(v.value.boolean ? " true\n" : " false\n", out); + break; + + case AMQP_FIELD_KIND_I8: + fprintf(out, " %" PRId8 "\n", v.value.i8); + break; + + case AMQP_FIELD_KIND_U8: + fprintf(out, " %" PRIu8 "\n", v.value.u8); + break; + + case AMQP_FIELD_KIND_I16: + fprintf(out, " %" PRId16 "\n", v.value.i16); + break; + + case AMQP_FIELD_KIND_U16: + fprintf(out, " %" PRIu16 "\n", v.value.u16); + break; + + case AMQP_FIELD_KIND_I32: + fprintf(out, " %" PRId32 "\n", v.value.i32); + break; + + case AMQP_FIELD_KIND_U32: + fprintf(out, " %" PRIu32 "\n", v.value.u32); + break; + + case AMQP_FIELD_KIND_I64: + fprintf(out, " %" PRId64 "\n", v.value.i64); + break; + + case AMQP_FIELD_KIND_F32: + fprintf(out, " %g\n", (double)v.value.f32); + break; + + case AMQP_FIELD_KIND_F64: + fprintf(out, " %g\n", v.value.f64); + break; + + case AMQP_FIELD_KIND_DECIMAL: + fprintf(out, " %u:::%u\n", v.value.decimal.decimals, + v.value.decimal.value); + break; + + case AMQP_FIELD_KIND_UTF8: + fprintf(out, " %.*s\n", (int)v.value.bytes.len, + (char *)v.value.bytes.bytes); + break; + + case AMQP_FIELD_KIND_BYTES: + fputc(' ', out); + for (i = 0; i < (int)v.value.bytes.len; i++) { + fprintf(out, "%02x", ((char *)v.value.bytes.bytes)[i]); + } + + fputc('\n', out); + break; + + case AMQP_FIELD_KIND_ARRAY: + fputc('\n', out); + for (i = 0; i < v.value.array.num_entries; i++) { + dump_value(indent + 2, v.value.array.entries[i], out); + } + + break; + + case AMQP_FIELD_KIND_TIMESTAMP: + fprintf(out, " %" PRIu64 "\n", v.value.u64); + break; + + case AMQP_FIELD_KIND_TABLE: + fputc('\n', out); + for (i = 0; i < v.value.table.num_entries; i++) { + dump_indent(indent + 2, out); + fprintf(out, "%.*s ->\n", (int)v.value.table.entries[i].key.len, + (char *)v.value.table.entries[i].key.bytes); + dump_value(indent + 4, v.value.table.entries[i].value, out); + } + + break; + + case AMQP_FIELD_KIND_VOID: + fputc('\n', out); + break; + + default: + fprintf(out, "???\n"); + break; + } +} + +static void test_dump_value(FILE *out) { + amqp_table_entry_t entries[8]; + amqp_table_t table; + amqp_field_value_t val; + + entries[0].key = amqp_literal_bytes("zebra"); + entries[0].value.kind = AMQP_FIELD_KIND_UTF8; + entries[0].value.value.bytes = amqp_literal_bytes("last"); + + entries[1].key = amqp_literal_bytes("aardvark"); + entries[1].value.kind = AMQP_FIELD_KIND_UTF8; + entries[1].value.value.bytes = amqp_literal_bytes("first"); + + entries[2].key = amqp_literal_bytes("middle"); + entries[2].value.kind = AMQP_FIELD_KIND_UTF8; + entries[2].value.value.bytes = amqp_literal_bytes("third"); + + entries[3].key = amqp_literal_bytes("number"); + entries[3].value.kind = AMQP_FIELD_KIND_I32; + entries[3].value.value.i32 = 1234; + + entries[4].key = amqp_literal_bytes("decimal"); + entries[4].value.kind = AMQP_FIELD_KIND_DECIMAL; + entries[4].value.value.decimal.decimals = 2; + entries[4].value.value.decimal.value = 1234; + + entries[5].key = amqp_literal_bytes("time"); + entries[5].value.kind = AMQP_FIELD_KIND_TIMESTAMP; + entries[5].value.value.u64 = 1234123412341234; + + entries[6].key = amqp_literal_bytes("beta"); + entries[6].value.kind = AMQP_FIELD_KIND_UTF8; + entries[6].value.value.bytes = amqp_literal_bytes("second"); + + entries[7].key = amqp_literal_bytes("wombat"); + entries[7].value.kind = AMQP_FIELD_KIND_UTF8; + entries[7].value.value.bytes = amqp_literal_bytes("fourth"); + + table.num_entries = 8; + table.entries = entries; + + qsort(table.entries, table.num_entries, sizeof(amqp_table_entry_t), + &amqp_table_entry_cmp); + + val.kind = AMQP_FIELD_KIND_TABLE; + val.value.table = table; + + dump_value(0, val, out); +} + +static uint8_t pre_encoded_table[] = { + 0x00, 0x00, 0x00, 0xff, 0x07, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x74, 0x72, + 0x53, 0x00, 0x00, 0x00, 0x15, 0x48, 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x61, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x69, 0x6e, 0x74, + 0x49, 0x00, 0x00, 0x30, 0x39, 0x07, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, + 0x6c, 0x44, 0x03, 0x00, 0x01, 0xe2, 0x40, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x54, 0x00, 0x00, 0x63, 0xee, 0xa0, 0x53, + 0xc1, 0x94, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x00, 0x00, 0x00, + 0x1f, 0x03, 0x6f, 0x6e, 0x65, 0x49, 0x00, 0x00, 0xd4, 0x31, 0x03, 0x74, + 0x77, 0x6f, 0x53, 0x00, 0x00, 0x00, 0x0d, 0x41, 0x20, 0x6c, 0x6f, 0x6e, + 0x67, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x04, 0x62, 0x79, 0x74, + 0x65, 0x62, 0xff, 0x04, 0x6c, 0x6f, 0x6e, 0x67, 0x6c, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x96, 0x02, 0xd2, 0x05, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x73, + 0x02, 0x8f, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x74, 0x01, 0x06, 0x62, 0x69, + 0x6e, 0x61, 0x72, 0x79, 0x78, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x20, 0x62, + 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x04, 0x76, 0x6f, 0x69, 0x64, 0x56, 0x05, 0x61, 0x72, 0x72, 0x61, 0x79, + 0x41, 0x00, 0x00, 0x00, 0x17, 0x49, 0x00, 0x00, 0xd4, 0x31, 0x53, 0x00, + 0x00, 0x00, 0x0d, 0x41, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x05, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x66, 0x40, + 0x49, 0x0f, 0xdb, 0x06, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x64, 0x40, + 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18}; + +static void test_table_codec(FILE *out) { + amqp_pool_t pool; + int result; + + amqp_table_entry_t inner_entries[2]; + amqp_table_t inner_table; + + amqp_field_value_t inner_values[2]; + amqp_array_t inner_array; + + amqp_table_entry_t entries[14]; + amqp_table_t table; + + inner_entries[0].key = amqp_literal_bytes("one"); + inner_entries[0].value.kind = AMQP_FIELD_KIND_I32; + inner_entries[0].value.value.i32 = 54321; + + inner_entries[1].key = amqp_literal_bytes("two"); + inner_entries[1].value.kind = AMQP_FIELD_KIND_UTF8; + inner_entries[1].value.value.bytes = amqp_literal_bytes("A long string"); + + inner_table.num_entries = 2; + inner_table.entries = inner_entries; + + inner_values[0].kind = AMQP_FIELD_KIND_I32; + inner_values[0].value.i32 = 54321; + + inner_values[1].kind = AMQP_FIELD_KIND_UTF8; + inner_values[1].value.bytes = amqp_literal_bytes("A long string"); + + inner_array.num_entries = 2; + inner_array.entries = inner_values; + + entries[0].key = amqp_literal_bytes("longstr"); + entries[0].value.kind = AMQP_FIELD_KIND_UTF8; + entries[0].value.value.bytes = amqp_literal_bytes("Here is a long string"); + + entries[1].key = amqp_literal_bytes("signedint"); + entries[1].value.kind = AMQP_FIELD_KIND_I32; + entries[1].value.value.i32 = 12345; + + entries[2].key = amqp_literal_bytes("decimal"); + entries[2].value.kind = AMQP_FIELD_KIND_DECIMAL; + entries[2].value.value.decimal.decimals = 3; + entries[2].value.value.decimal.value = 123456; + + entries[3].key = amqp_literal_bytes("timestamp"); + entries[3].value.kind = AMQP_FIELD_KIND_TIMESTAMP; + entries[3].value.value.u64 = 109876543209876; + + entries[4].key = amqp_literal_bytes("table"); + entries[4].value.kind = AMQP_FIELD_KIND_TABLE; + entries[4].value.value.table = inner_table; + + entries[5].key = amqp_literal_bytes("byte"); + entries[5].value.kind = AMQP_FIELD_KIND_I8; + entries[5].value.value.i8 = (int8_t)-1; + + entries[6].key = amqp_literal_bytes("long"); + entries[6].value.kind = AMQP_FIELD_KIND_I64; + entries[6].value.value.i64 = 1234567890; + + entries[7].key = amqp_literal_bytes("short"); + entries[7].value.kind = AMQP_FIELD_KIND_I16; + entries[7].value.value.i16 = 655; + + entries[8].key = amqp_literal_bytes("bool"); + entries[8].value.kind = AMQP_FIELD_KIND_BOOLEAN; + entries[8].value.value.boolean = 1; + + entries[9].key = amqp_literal_bytes("binary"); + entries[9].value.kind = AMQP_FIELD_KIND_BYTES; + entries[9].value.value.bytes = amqp_literal_bytes("a binary string"); + + entries[10].key = amqp_literal_bytes("void"); + entries[10].value.kind = AMQP_FIELD_KIND_VOID; + + entries[11].key = amqp_literal_bytes("array"); + entries[11].value.kind = AMQP_FIELD_KIND_ARRAY; + entries[11].value.value.array = inner_array; + + entries[12].key = amqp_literal_bytes("float"); + entries[12].value.kind = AMQP_FIELD_KIND_F32; + entries[12].value.value.f32 = (float)M_PI; + + entries[13].key = amqp_literal_bytes("double"); + entries[13].value.kind = AMQP_FIELD_KIND_F64; + entries[13].value.value.f64 = M_PI; + + table.num_entries = 14; + table.entries = entries; + + fprintf(out, "AAAAAAAAAA\n"); + + { + amqp_field_value_t val; + val.kind = AMQP_FIELD_KIND_TABLE; + val.value.table = table; + dump_value(0, val, out); + } + + init_amqp_pool(&pool, 4096); + + { + amqp_table_t decoded; + size_t decoding_offset = 0; + amqp_bytes_t decoding_bytes; + decoding_bytes.len = sizeof(pre_encoded_table); + decoding_bytes.bytes = pre_encoded_table; + + result = + amqp_decode_table(decoding_bytes, &pool, &decoded, &decoding_offset); + if (result < 0) { + die("Table decoding failed: %s", amqp_error_string2(result)); + } + + fprintf(out, "BBBBBBBBBB\n"); + + { + amqp_field_value_t val; + val.kind = AMQP_FIELD_KIND_TABLE; + val.value.table = decoded; + + dump_value(0, val, out); + } + } + + { + uint8_t encoding_buffer[4096]; + amqp_bytes_t encoding_result; + size_t offset = 0; + + memset(&encoding_buffer[0], 0, sizeof(encoding_buffer)); + encoding_result.len = sizeof(encoding_buffer); + encoding_result.bytes = &encoding_buffer[0]; + + result = amqp_encode_table(encoding_result, &table, &offset); + if (result < 0) { + die("Table encoding failed: %s", amqp_error_string2(result)); + } + + if (offset != sizeof(pre_encoded_table)) + die("Offset should be %ld, was %ld", (long)sizeof(pre_encoded_table), + (long)offset); + + result = memcmp(pre_encoded_table, encoding_buffer, offset); + if (result != 0) { + die("Table encoding differed", result); + } + } + + empty_amqp_pool(&pool); +} + +#define CHUNK_SIZE 4096 + +static int compare_files(FILE *f1_in, FILE *f2_in) { + char f1_buf[CHUNK_SIZE]; + char f2_buf[CHUNK_SIZE]; + int res; + + rewind(f1_in); + rewind(f2_in); + + for (;;) { + size_t f1_got = fread(f1_buf, 1, CHUNK_SIZE, f1_in); + size_t f2_got = fread(f2_buf, 1, CHUNK_SIZE, f2_in); + res = memcmp(f1_buf, f2_buf, f1_got < f2_got ? f1_got : f2_got); + + if (res) { + break; + } + + if (f1_got < CHUNK_SIZE || f2_got < CHUNK_SIZE) { + if (f1_got != f2_got) { + res = (f1_got < f2_got ? -1 : 1); + } + break; + } + } + + return res; +} + +const char *expected_file_name = "tests/test_tables.expected"; + +int main(void) { + char *srcdir = getenv("srcdir"); + FILE *out, *expected = NULL; + char *expected_path; + + out = tmpfile(); + if (out == NULL) { + die("failed to create temporary file: %s", strerror(errno)); + } + + test_table_codec(out); + fprintf(out, "----------\n"); + test_dump_value(out); + + if (srcdir == NULL) { + srcdir = "."; + } + + expected_path = malloc(strlen(srcdir) + strlen(expected_file_name) + 2); + if (!expected_path) { + die("out of memory"); + } + sprintf(expected_path, "%s/%s", srcdir, expected_file_name); + expected = fopen(expected_path, "r"); + if (!expected) { + die("failed to open %s: %s", expected_path, strerror(errno)); + } + + if (compare_files(expected, out)) { + die("output file did not have expected contents"); + } + + fclose(expected); + free(expected_path); + fclose(out); + + return 0; +} diff --git a/tests/test_tables.expected b/tests/test_tables.expected new file mode 100644 index 0000000..44d2085 --- /dev/null +++ b/tests/test_tables.expected @@ -0,0 +1,90 @@ +AAAAAAAAAA +F + longstr -> + S Here is a long string + signedint -> + I 12345 + decimal -> + D 3:::123456 + timestamp -> + T 109876543209876 + table -> + F + one -> + I 54321 + two -> + S A long string + byte -> + b -1 + long -> + l 1234567890 + short -> + s 655 + bool -> + t true + binary -> + x 612062696e61727920737472696e67 + void -> + V + array -> + A + I 54321 + S A long string + float -> + f 3.14159 + double -> + d 3.14159 +BBBBBBBBBB +F + longstr -> + S Here is a long string + signedint -> + I 12345 + decimal -> + D 3:::123456 + timestamp -> + T 109876543209876 + table -> + F + one -> + I 54321 + two -> + S A long string + byte -> + b -1 + long -> + l 1234567890 + short -> + s 655 + bool -> + t true + binary -> + x 612062696e61727920737472696e67 + void -> + V + array -> + A + I 54321 + S A long string + float -> + f 3.14159 + double -> + d 3.14159 +---------- +F + aardvark -> + S first + beta -> + S second + decimal -> + D 2:::1234 + middle -> + S third + number -> + I 1234 + time -> + T 1234123412341234 + wombat -> + S fourth + zebra -> + S last diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..9a99b1d --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,79 @@ +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +if(WIN32) + set(PLATFORM_DIR win32) +else() + set(PLATFORM_DIR unix) +endif() + +add_library(tools-common OBJECT) +target_sources(tools-common PRIVATE + common.h + common.c) +if(WIN32) + target_sources(tools-common PRIVATE + win32/compat.h + win32/compat.c) +endif() +if(ENABLE_SSL_SUPPORT) + target_compile_definitions(tools-common PRIVATE -DWITH_SSL=1) +endif() +target_include_directories(tools-common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(tools-common PRIVATE rabbitmq::rabbitmq PUBLIC popt::popt) + +add_executable(amqp-publish publish.c) +target_link_libraries(amqp-publish PRIVATE tools-common rabbitmq::rabbitmq) + +add_executable(amqp-get get.c) +target_link_libraries(amqp-get PRIVATE tools-common rabbitmq::rabbitmq) + +add_executable(amqp-consume consume.c ${PLATFORM_DIR}/process.h ${PLATFORM_DIR}/process.c) +target_include_directories(amqp-consume PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_DIR}) +target_link_libraries(amqp-consume PRIVATE tools-common rabbitmq::rabbitmq) + +add_executable(amqp-declare-queue declare_queue.c) +target_link_libraries(amqp-declare-queue PRIVATE tools-common rabbitmq::rabbitmq) + +add_executable(amqp-delete-queue delete_queue.c) +target_link_libraries(amqp-delete-queue PRIVATE tools-common rabbitmq::rabbitmq) + +install(TARGETS amqp-publish amqp-get amqp-consume amqp-declare-queue amqp-delete-queue + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT rabbitmq-c-tools) + +if (BUILD_TOOLS_DOCS) + set(DOCS_SRCS + doc/amqp-consume.xml + doc/amqp-declare-queue.xml + doc/amqp-delete-queue.xml + doc/amqp-get.xml + doc/amqp-publish.xml + doc/librabbitmq-tools.xml + ) + + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc) + set(XMLTO_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/doc/man-date.ent) + STRING(TIMESTAMP BUILD_DATE "%Y-%m-%d" UTC) + add_custom_command( + OUTPUT ${XMLTO_DEPENDS} + COMMAND echo ${BUILD_DATE} > ${XMLTO_DEPENDS} + VERBATIM + ) + + set(XMLTO_COMMAND_ARGS --skip-validation --searchpath "${CMAKE_CURRENT_BINARY_DIR}/doc") + + XMLTO(${DOCS_SRCS} + MODES man + ALL) + + foreach(file ${XMLTO_FILES_man}) + get_filename_component(fileExt ${file} EXT) + string( REGEX REPLACE "^[.]" "" fileExt ${fileExt} ) + install( + FILES ${file} + DESTINATION ${CMAKE_INSTALL_MANDIR}/man${fileExt} + COMPONENT rabbitmq-c-tool-docs + ) + endforeach() +endif() diff --git a/tools/common.c b/tools/common.c new file mode 100644 index 0000000..07ccada --- /dev/null +++ b/tools/common.c @@ -0,0 +1,487 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "common.h" +#ifdef WITH_SSL +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS +#include "compat.h" +#endif + +/* For when reading auth data from a file */ +#define MAXAUTHTOKENLEN 128 +#define USERNAMEPREFIX "username:" +#define PASSWORDPREFIX "password:" + +void die(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +void die_errno(int err, const char *fmt, ...) { + va_list ap; + + if (err == 0) { + return; + } + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(err)); + exit(1); +} + +void die_amqp_error(int err, const char *fmt, ...) { + va_list ap; + + if (err >= 0) { + return; + } + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", amqp_error_string2(err)); + exit(1); +} + +const char *amqp_server_exception_string(amqp_rpc_reply_t r) { + int res; + static char s[512]; + + switch (r.reply.id) { + case AMQP_CONNECTION_CLOSE_METHOD: { + amqp_connection_close_t *m = (amqp_connection_close_t *)r.reply.decoded; + res = snprintf(s, sizeof(s), "server connection error %d, message: %.*s", + m->reply_code, (int)m->reply_text.len, + (char *)m->reply_text.bytes); + break; + } + + case AMQP_CHANNEL_CLOSE_METHOD: { + amqp_channel_close_t *m = (amqp_channel_close_t *)r.reply.decoded; + res = snprintf(s, sizeof(s), "server channel error %d, message: %.*s", + m->reply_code, (int)m->reply_text.len, + (char *)m->reply_text.bytes); + break; + } + + default: + res = snprintf(s, sizeof(s), "unknown server error, method id 0x%08X", + r.reply.id); + break; + } + + return res >= 0 ? s : NULL; +} + +const char *amqp_rpc_reply_string(amqp_rpc_reply_t r) { + switch (r.reply_type) { + case AMQP_RESPONSE_NORMAL: + return "normal response"; + + case AMQP_RESPONSE_NONE: + return "missing RPC reply type"; + + case AMQP_RESPONSE_LIBRARY_EXCEPTION: + return amqp_error_string2(r.library_error); + + case AMQP_RESPONSE_SERVER_EXCEPTION: + return amqp_server_exception_string(r); + + default: + abort(); + } +} + +void die_rpc(amqp_rpc_reply_t r, const char *fmt, ...) { + va_list ap; + + if (r.reply_type == AMQP_RESPONSE_NORMAL) { + return; + } + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", amqp_rpc_reply_string(r)); + exit(1); +} + +static char *amqp_url; +static char *amqp_server; +static int amqp_port = -1; +static char *amqp_vhost; +static char *amqp_username; +static char *amqp_password; +static int amqp_heartbeat = 0; +static char *amqp_authfile; +#ifdef WITH_SSL +static int amqp_ssl = 0; +static char *amqp_cacert = "/etc/ssl/certs/cacert.pem"; +static char *amqp_key = NULL; +static char *amqp_cert = NULL; +#endif /* WITH_SSL */ + +const char *connect_options_title = "Connection options"; +struct poptOption connect_options[] = { + {"url", 'u', POPT_ARG_STRING, &amqp_url, 0, "the AMQP URL to connect to", + "amqp://..."}, + {"server", 's', POPT_ARG_STRING, &amqp_server, 0, + "the AMQP server to connect to", "hostname"}, + {"port", 0, POPT_ARG_INT, &amqp_port, 0, "the port to connect on", "port"}, + {"vhost", 0, POPT_ARG_STRING, &amqp_vhost, 0, + "the vhost to use when connecting", "vhost"}, + {"username", 0, POPT_ARG_STRING, &amqp_username, 0, + "the username to login with", "username"}, + {"password", 0, POPT_ARG_STRING, &amqp_password, 0, + "the password to login with", "password"}, + {"heartbeat", 0, POPT_ARG_INT, &amqp_heartbeat, 0, + "heartbeat interval, set to 0 to disable", "heartbeat"}, + {"authfile", 0, POPT_ARG_STRING, &amqp_authfile, 0, + "path to file containing username/password for authentication", "file"}, +#ifdef WITH_SSL + {"ssl", 0, POPT_ARG_NONE, &amqp_ssl, 0, "connect over SSL/TLS", NULL}, + {"cacert", 0, POPT_ARG_STRING, &amqp_cacert, 0, + "path to the CA certificate file", "cacert.pem"}, + {"key", 0, POPT_ARG_STRING, &amqp_key, 0, + "path to the client private key file", "key.pem"}, + {"cert", 0, POPT_ARG_STRING, &amqp_cert, 0, + "path to the client certificate file", "cert.pem"}, +#endif /* WITH_SSL */ + {NULL, '\0', 0, NULL, 0, NULL, NULL}}; + +void read_authfile(const char *path) { + size_t n; + FILE *fp = NULL; + char token[MAXAUTHTOKENLEN]; + + if ((amqp_username = malloc(MAXAUTHTOKENLEN)) == NULL || + (amqp_password = malloc(MAXAUTHTOKENLEN)) == NULL) { + die("Out of memory"); + } else if ((fp = fopen(path, "r")) == NULL) { + die("Could not read auth data file %s", path); + } + + if (fgets(token, MAXAUTHTOKENLEN, fp) == NULL || + strncmp(token, USERNAMEPREFIX, strlen(USERNAMEPREFIX))) { + die("Malformed auth file (missing username)"); + } + strncpy(amqp_username, &token[strlen(USERNAMEPREFIX)], MAXAUTHTOKENLEN); + /* Missing newline means token was cut off */ + n = strlen(amqp_username); + if (amqp_username[n - 1] != '\n') { + die("Username too long"); + } else { + amqp_username[n - 1] = '\0'; + } + + if (fgets(token, MAXAUTHTOKENLEN, fp) == NULL || + strncmp(token, PASSWORDPREFIX, strlen(PASSWORDPREFIX))) { + die("Malformed auth file (missing password)"); + } + strncpy(amqp_password, &token[strlen(PASSWORDPREFIX)], MAXAUTHTOKENLEN); + /* Missing newline means token was cut off */ + n = strlen(amqp_password); + if (amqp_password[n - 1] != '\n') { + die("Password too long"); + } else { + amqp_password[n - 1] = '\0'; + } + + (void)fgetc(fp); + if (!feof(fp)) { + die("Malformed auth file (trailing data)"); + } + fclose(fp); +} + +static void init_connection_info(struct amqp_connection_info *ci) { + ci->user = NULL; + ci->password = NULL; + ci->host = NULL; + ci->port = -1; + ci->vhost = NULL; + ci->user = NULL; + + amqp_default_connection_info(ci); + + if (amqp_url) + die_amqp_error(amqp_parse_url(strdup(amqp_url), ci), "Parsing URL '%s'", + amqp_url); + + if (amqp_server) { + char *colon; + if (amqp_url) { + die("--server and --url options cannot be used at the same time"); + } + + /* parse the server string into a hostname and a port */ + colon = strchr(amqp_server, ':'); + if (colon) { + char *port_end; + size_t host_len; + + /* Deprecate specifying the port number with the + --server option, because it is not ipv6 friendly. + --url now allows connection options to be + specified concisely. */ + fprintf(stderr, + "Specifying the port number with --server is deprecated\n"); + + host_len = colon - amqp_server; + ci->host = malloc(host_len + 1); + memcpy(ci->host, amqp_server, host_len); + ci->host[host_len] = 0; + + if (amqp_port >= 0) { + die("both --server and --port options specify server port"); + } + + ci->port = strtol(colon + 1, &port_end, 10); + if (ci->port < 0 || ci->port > 65535 || port_end == colon + 1 || + *port_end != 0) + die("bad server port number in '%s'", amqp_server); + } else { + ci->host = amqp_server; + ci->port = 5672; +#if WITH_SSL + if (amqp_ssl) { + ci->port = 5671; + } +#endif + } + } + +#if WITH_SSL + if (amqp_ssl && !ci->ssl) { + if (amqp_url) { + die("the --ssl option specifies an SSL connection" + " but the --url option does not"); + } else { + ci->ssl = 1; + } + } +#endif + + if (amqp_port >= 0) { + if (amqp_url) { + die("--port and --url options cannot be used at the same time"); + } + + ci->port = amqp_port; + } + + if (amqp_username) { + if (amqp_url) { + die("--username and --url options cannot be used at the same time"); + } else if (amqp_authfile) { + die("--username and --authfile options cannot be used at the same time"); + } + + ci->user = amqp_username; + } + + if (amqp_password) { + if (amqp_url) { + die("--password and --url options cannot be used at the same time"); + } else if (amqp_authfile) { + die("--password and --authfile options cannot be used at the same time"); + } + + ci->password = amqp_password; + } + + if (amqp_authfile) { + if (amqp_url) { + die("--authfile and --url options cannot be used at the same time"); + } + + read_authfile(amqp_authfile); + ci->user = amqp_username; + ci->password = amqp_password; + } + + if (amqp_vhost) { + if (amqp_url) { + die("--vhost and --url options cannot be used at the same time"); + } + + ci->vhost = amqp_vhost; + } + + if (amqp_heartbeat < 0) { + die("--heartbeat must be a positive value"); + } +} + +amqp_connection_state_t make_connection(void) { + int status; + amqp_socket_t *socket = NULL; + struct amqp_connection_info ci; + amqp_connection_state_t conn; + + init_connection_info(&ci); + conn = amqp_new_connection(); + if (ci.ssl) { +#ifdef WITH_SSL + socket = amqp_ssl_socket_new(conn); + if (!socket) { + die("creating SSL/TLS socket"); + } + if (amqp_cacert) { + amqp_ssl_socket_set_cacert(socket, amqp_cacert); + } + if (amqp_key) { + amqp_ssl_socket_set_key(socket, amqp_cert, amqp_key); + } +#else + die("librabbitmq was not built with SSL/TLS support"); +#endif + } else { + socket = amqp_tcp_socket_new(conn); + if (!socket) { + die("creating TCP socket (out of memory)"); + } + } + status = amqp_socket_open(socket, ci.host, ci.port); + if (status) { + die_amqp_error(status, "opening socket to %s:%d", ci.host, ci.port); + } + die_rpc(amqp_login(conn, ci.vhost, 0, 131072, amqp_heartbeat, + AMQP_SASL_METHOD_PLAIN, ci.user, ci.password), + "logging in to AMQP server"); + if (!amqp_channel_open(conn, 1)) { + die_rpc(amqp_get_rpc_reply(conn), "opening channel"); + } + return conn; +} + +void close_connection(amqp_connection_state_t conn) { + int res; + die_rpc(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), "closing channel"); + die_rpc(amqp_connection_close(conn, AMQP_REPLY_SUCCESS), + "closing connection"); + + res = amqp_destroy_connection(conn); + die_amqp_error(res, "closing connection"); +} + +amqp_bytes_t read_all(int fd) { + size_t space = 4096; + amqp_bytes_t bytes; + + bytes.bytes = malloc(space); + bytes.len = 0; + + for (;;) { + ssize_t res = read(fd, (char *)bytes.bytes + bytes.len, space - bytes.len); + if (res == 0) { + break; + } + + if (res < 0) { + if (errno == EINTR) { + continue; + } + + die_errno(errno, "reading"); + } + + bytes.len += res; + if (bytes.len == space) { + space *= 2; + bytes.bytes = realloc(bytes.bytes, space); + } + } + + return bytes; +} + +void write_all(int fd, amqp_bytes_t data) { + while (data.len > 0) { + ssize_t res = write(fd, data.bytes, data.len); + if (res < 0) { + die_errno(errno, "write"); + } + + data.len -= res; + data.bytes = (char *)data.bytes + res; + } +} + +void copy_body(amqp_connection_state_t conn, int fd) { + size_t body_remaining; + amqp_frame_t frame; + + int res = amqp_simple_wait_frame(conn, &frame); + die_amqp_error(res, "waiting for header frame"); + if (frame.frame_type != AMQP_FRAME_HEADER) { + die("expected header, got frame type 0x%X", frame.frame_type); + } + + body_remaining = frame.payload.properties.body_size; + while (body_remaining) { + res = amqp_simple_wait_frame(conn, &frame); + die_amqp_error(res, "waiting for body frame"); + if (frame.frame_type != AMQP_FRAME_BODY) { + die("expected body, got frame type 0x%X", frame.frame_type); + } + + write_all(fd, frame.payload.body_fragment); + body_remaining -= frame.payload.body_fragment.len; + } +} + +poptContext process_options(int argc, const char **argv, + struct poptOption *options, const char *help) { + int c; + poptContext opts = poptGetContext(NULL, argc, argv, options, 0); + poptSetOtherOptionHelp(opts, help); + + while ((c = poptGetNextOpt(opts)) >= 0) { + /* no options require explicit handling */ + } + + if (c < -1) { + fprintf(stderr, "%s: %s\n", poptBadOption(opts, POPT_BADOPTION_NOALIAS), + poptStrerror(c)); + poptPrintUsage(opts, stderr, 0); + exit(1); + } + + return opts; +} + +void process_all_options(int argc, const char **argv, + struct poptOption *options) { + poptContext opts = process_options(argc, argv, options, "[OPTIONS]..."); + const char *opt = poptPeekArg(opts); + + if (opt) { + fprintf(stderr, "unexpected operand: %s\n", opt); + poptPrintUsage(opts, stderr, 0); + exit(1); + } + + poptFreeContext(opts); +} + +amqp_bytes_t cstring_bytes(const char *str) { + return str ? amqp_cstring_bytes(str) : amqp_empty_bytes; +} diff --git a/tools/common.h b/tools/common.h new file mode 100644 index 0000000..0b8ec03 --- /dev/null +++ b/tools/common.h @@ -0,0 +1,41 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include + +#include + +#include +#include + +extern const char *amqp_server_exception_string(amqp_rpc_reply_t r); +extern const char *amqp_rpc_reply_string(amqp_rpc_reply_t r); + +extern void die(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +extern void die_errno(int err, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +extern void die_amqp_error(int err, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +extern void die_rpc(amqp_rpc_reply_t r, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +extern const char *connect_options_title; +extern struct poptOption connect_options[]; +extern amqp_connection_state_t make_connection(void); +extern void close_connection(amqp_connection_state_t conn); + +extern amqp_bytes_t read_all(int fd); +extern void write_all(int fd, amqp_bytes_t data); + +extern void copy_body(amqp_connection_state_t conn, int fd); + +#define INCLUDE_OPTIONS(options) \ + { NULL, 0, POPT_ARG_INCLUDE_TABLE, options, 0, options##_title, NULL } + +extern poptContext process_options(int argc, const char **argv, + struct poptOption *options, + const char *help); +extern void process_all_options(int argc, const char **argv, + struct poptOption *options); + +extern amqp_bytes_t cstring_bytes(const char *str); diff --git a/tools/consume.c b/tools/consume.c new file mode 100644 index 0000000..b4561b3 --- /dev/null +++ b/tools/consume.c @@ -0,0 +1,221 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include + +#include "common.h" +#include "process.h" + +#define MAX_LISTEN_KEYS 1024 +#define LISTEN_KEYS_DELIMITER "," + +/* Convert a amqp_bytes_t to an escaped string form for printing. We + use the same escaping conventions as rabbitmqctl. */ +static char *stringify_bytes(amqp_bytes_t bytes) { + /* We will need up to 4 chars per byte, plus the terminating 0 */ + char *res = malloc(bytes.len * 4 + 1); + if (res == NULL) { + return NULL; + } + uint8_t *data = bytes.bytes; + char *p = res; + size_t i; + + for (i = 0; i < bytes.len; i++) { + if (data[i] >= 32 && data[i] != 127) { + *p++ = data[i]; + } else { + *p++ = '\\'; + *p++ = '0' + (data[i] >> 6); + *p++ = '0' + (data[i] >> 3 & 0x7); + *p++ = '0' + (data[i] & 0x7); + } + } + + *p = 0; + return res; +} + +static amqp_bytes_t setup_queue(amqp_connection_state_t conn, char *queue, + char *exchange, char *routing_key, int declare, + int exclusive) { + amqp_bytes_t queue_bytes = cstring_bytes(queue); + + char *routing_key_rest; + char *routing_key_token; + char *routing_tmp; + int routing_key_count = 0; + + /* if an exchange name wasn't provided, check that we don't have options that + * require it. */ + if (!exchange && routing_key) { + fprintf(stderr, + "--routing-key option requires an exchange name to be provided " + "with --exchange\n"); + exit(1); + } + + if (!queue || exchange || declare || exclusive) { + /* Declare the queue as auto-delete. */ + amqp_queue_declare_ok_t *res = amqp_queue_declare( + conn, 1, queue_bytes, 0, 0, exclusive, 1, amqp_empty_table); + if (!res) { + die_rpc(amqp_get_rpc_reply(conn), "queue.declare"); + } + + if (!queue) { + /* the server should have provided a queue name */ + char *sq; + queue_bytes = amqp_bytes_malloc_dup(res->queue); + sq = stringify_bytes(queue_bytes); + if (sq == NULL) { + fprintf(stderr, "Memory allocation failed.\n"); + exit(1); + } + fprintf(stderr, "Server provided queue name: %s\n", sq); + free(sq); + } + + /* Bind to an exchange if requested */ + if (exchange) { + amqp_bytes_t eb = amqp_cstring_bytes(exchange); + + routing_tmp = strdup(routing_key); + if (NULL == routing_tmp) { + fprintf(stderr, "could not allocate memory to parse routing key\n"); + exit(1); + } + + for (routing_key_token = + strtok_r(routing_tmp, LISTEN_KEYS_DELIMITER, &routing_key_rest); + NULL != routing_key_token && routing_key_count < MAX_LISTEN_KEYS - 1; + routing_key_token = + strtok_r(NULL, LISTEN_KEYS_DELIMITER, &routing_key_rest)) { + + if (!amqp_queue_bind(conn, 1, queue_bytes, eb, + cstring_bytes(routing_key_token), + amqp_empty_table)) { + die_rpc(amqp_get_rpc_reply(conn), "queue.bind"); + } + } + free(routing_tmp); + } + } + + return queue_bytes; +} + +#define AMQP_CONSUME_MAX_PREFETCH_COUNT 65535 + +static void do_consume(amqp_connection_state_t conn, amqp_bytes_t queue, + int no_ack, int count, int prefetch_count, + const char *const *argv) { + int i; + + /* If there is a limit, set the qos to match */ + if (count > 0 && count <= AMQP_CONSUME_MAX_PREFETCH_COUNT && + !amqp_basic_qos(conn, 1, 0, count, 0)) { + die_rpc(amqp_get_rpc_reply(conn), "basic.qos"); + } + + /* if there is a maximum number of messages to be received at a time, set the + * qos to match */ + if (prefetch_count > 0 && prefetch_count <= AMQP_CONSUME_MAX_PREFETCH_COUNT) { + /* the maximum number of messages to be received at a time must be less + * than the global maximum number of messages. */ + if (!(count > 0 && count <= AMQP_CONSUME_MAX_PREFETCH_COUNT && + prefetch_count >= count)) { + if (!amqp_basic_qos(conn, 1, 0, prefetch_count, 0)) { + die_rpc(amqp_get_rpc_reply(conn), "basic.qos"); + } + } + } + + if (!amqp_basic_consume(conn, 1, queue, amqp_empty_bytes, 0, no_ack, 0, + amqp_empty_table)) { + die_rpc(amqp_get_rpc_reply(conn), "basic.consume"); + } + + for (i = 0; count < 0 || i < count; i++) { + amqp_frame_t frame; + struct pipeline pl; + uint64_t delivery_tag; + amqp_basic_deliver_t *deliver; + int res = amqp_simple_wait_frame(conn, &frame); + die_amqp_error(res, "waiting for header frame"); + + if (frame.frame_type != AMQP_FRAME_METHOD || + frame.payload.method.id != AMQP_BASIC_DELIVER_METHOD) { + continue; + } + + deliver = (amqp_basic_deliver_t *)frame.payload.method.decoded; + delivery_tag = deliver->delivery_tag; + + pipeline(argv, &pl); + copy_body(conn, pl.infd); + + if (finish_pipeline(&pl) && !no_ack) + die_amqp_error(amqp_basic_ack(conn, 1, delivery_tag, 0), "basic.ack"); + + amqp_maybe_release_buffers(conn); + } +} + +int main(int argc, const char **argv) { + poptContext opts; + amqp_connection_state_t conn; + const char *const *cmd_argv; + static char *queue = NULL; + static char *exchange = NULL; + static char *routing_key = NULL; + static int declare = 0; + static int exclusive = 0; + static int no_ack = 0; + static int count = -1; + static int prefetch_count = -1; + amqp_bytes_t queue_bytes; + + struct poptOption options[] = { + INCLUDE_OPTIONS(connect_options), + {"queue", 'q', POPT_ARG_STRING, &queue, 0, "the queue to consume from", + "queue"}, + {"exchange", 'e', POPT_ARG_STRING, &exchange, 0, + "bind the queue to this exchange", "exchange"}, + {"routing-key", 'r', POPT_ARG_STRING, &routing_key, 0, + "the routing key to bind with", "routing key"}, + {"declare", 'd', POPT_ARG_NONE, &declare, 0, + "declare an exclusive queue (deprecated, use --exclusive instead)", + NULL}, + {"exclusive", 'x', POPT_ARG_NONE, &exclusive, 0, + "declare the queue as exclusive", NULL}, + {"no-ack", 'A', POPT_ARG_NONE, &no_ack, 0, "consume in no-ack mode", + NULL}, + {"count", 'c', POPT_ARG_INT, &count, 0, + "stop consuming after this many messages are consumed", "limit"}, + {"prefetch-count", 'p', POPT_ARG_INT, &prefetch_count, 0, + "receive only this many message at a time from the server", "limit"}, + POPT_AUTOHELP{NULL, '\0', 0, NULL, 0, NULL, NULL}}; + + opts = process_options(argc, argv, options, "[OPTIONS]... "); + + cmd_argv = poptGetArgs(opts); + if (!cmd_argv || !cmd_argv[0]) { + fprintf(stderr, "consuming command not specified\n"); + poptPrintUsage(opts, stderr, 0); + goto error; + } + + conn = make_connection(); + queue_bytes = + setup_queue(conn, queue, exchange, routing_key, declare, exclusive); + do_consume(conn, queue_bytes, no_ack, count, prefetch_count, cmd_argv); + close_connection(conn); + return 0; + +error: + poptFreeContext(opts); + return 1; +} diff --git a/tools/declare_queue.c b/tools/declare_queue.c new file mode 100644 index 0000000..33b2e79 --- /dev/null +++ b/tools/declare_queue.c @@ -0,0 +1,43 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include "common.h" + +int main(int argc, const char **argv) { + amqp_connection_state_t conn; + static char *queue = NULL; + static int durable = 0; + + struct poptOption options[] = { + INCLUDE_OPTIONS(connect_options), + {"queue", 'q', POPT_ARG_STRING, &queue, 0, + "the queue name to declare, or the empty string", "queue"}, + {"durable", 'd', POPT_ARG_VAL, &durable, 1, "declare a durable queue", + NULL}, + POPT_AUTOHELP{NULL, '\0', 0, NULL, 0, NULL, NULL}}; + + process_all_options(argc, argv, options); + + if (queue == NULL) { + fprintf(stderr, "queue name not specified\n"); + return 1; + } + + conn = make_connection(); + { + amqp_queue_declare_ok_t *reply = amqp_queue_declare( + conn, 1, cstring_bytes(queue), 0, durable, 0, 0, amqp_empty_table); + if (reply == NULL) { + die_rpc(amqp_get_rpc_reply(conn), "queue.declare"); + } + + printf("%.*s\n", (int)reply->queue.len, (char *)reply->queue.bytes); + } + close_connection(conn); + return 0; +} diff --git a/tools/delete_queue.c b/tools/delete_queue.c new file mode 100644 index 0000000..371edef --- /dev/null +++ b/tools/delete_queue.c @@ -0,0 +1,45 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include "common.h" + +int main(int argc, const char **argv) { + amqp_connection_state_t conn; + static char *queue = NULL; + static int if_unused = 0; + static int if_empty = 0; + + struct poptOption options[] = { + INCLUDE_OPTIONS(connect_options), + {"queue", 'q', POPT_ARG_STRING, &queue, 0, "the queue name to delete", + "queue"}, + {"if-unused", 'u', POPT_ARG_VAL, &if_unused, 1, + "do not delete unless queue is unused", NULL}, + {"if-empty", 'e', POPT_ARG_VAL, &if_empty, 1, + "do not delete unless queue is empty", NULL}, + POPT_AUTOHELP{NULL, '\0', 0, NULL, 0, NULL, NULL}}; + + process_all_options(argc, argv, options); + + if (queue == NULL || *queue == '\0') { + fprintf(stderr, "queue name not specified\n"); + return 1; + } + + conn = make_connection(); + { + amqp_queue_delete_ok_t *reply = + amqp_queue_delete(conn, 1, cstring_bytes(queue), if_unused, if_empty); + if (reply == NULL) { + die_rpc(amqp_get_rpc_reply(conn), "queue.delete"); + } + printf("%u\n", reply->message_count); + } + close_connection(conn); + return 0; +} diff --git a/tools/doc/amqp-consume.xml b/tools/doc/amqp-consume.xml new file mode 100644 index 0000000..8301ded --- /dev/null +++ b/tools/doc/amqp-consume.xml @@ -0,0 +1,223 @@ + + +] +> + + + RabbitMQ C Client + + The RabbitMQ Team <info@rabbitmq.com> + + &date; + + + + amqp-consume + 1 + RabbitMQ C Client + + + + amqp-consume + Consume messages from a queue on an AMQP server + + + + + amqp-consume + + OPTION + + + command + + + args + + + + + + Description + + amqp-consume consumes messages from a + queue on an AMQP server. For each message that arrives, a + receiving command is run, with the message body supplied + to it on standard input. + + + amqp-consume can consume from an + existing queue, or it can create a new queue. It can + optionally bind the queue to an existing exchange. + + + By default, messages will be consumed with explicit + acknowledgements. A message will only be acknowledged if + the receiving command exits successfully (i.e. with an + exit code of zero). The AMQP no ack mode + (a.k.a. auto-ack mode) can be enable with the + option. + + + + + Options + + + + =queue name + + + The name of the queue to consume messages + from. + + + + If the option is + omitted, the AMQP server will assign a unique + name to the queue, and that server-assigned + name will be dixsplayed on stderr; this case + implies that an exclusive queue should be + declared. + + + + + + =exchange name + + + Specifies that an exclusive queue should + be declared, and bound to the given exchange. + The specified exchange should already exist + unless the + option is used to request the creation of an + exchange. + + + + + + =routing key + + + The routing key for binding. If omitted, an + empty routing key is assumed. + + + + + + + + + Forces an exclusive queue to be declared, + even when it otherwise would not be. That is, + when a queue name is specified with the + option, but no + binding to an exchange is requested with the + option. + Note: this option is deprecated and may be + removed in a future version, use the + option to + explicitly declare an exclusive queue. + + + + + + + + + Declared queues are non-exclusive by default, + this option forces declaration of exclusive + queues. + + + + + + =routing key + + + Enable no ack mode: The AMQP + server will unconditionally acknowledge each + message that is delivered, regardless of + whether the target command exits successfully + or not. + + + + + + =limit + + + Stop consuming after the given number of + messages have been received. + + + + + + =limit + + + Request the server to only send + limit + messages at a time. + + + If any value was passed to , + the value passed to + should be smaller than that, or otherwise it will be + ignored. + + + If / is + passed, this option has no effect. + + + + + + + + Examples + + + Consume messages from an existing queue + myqueue, and + output the message bodies on standard output via + cat: + + $ amqp-consume -q myqueue cat + + + + + Bind a new exclusive queue to an + exchange myexch, and send + each message body to the script + myscript, automatically + acknowledging them on the server: + + $ amqp-consume -A -e myexch ./myscript + + + + + + + See also + + librabbitmq-tools7 + describes connection-related options common to all the + RabbitMQ C Client tools. + + + diff --git a/tools/doc/amqp-declare-queue.xml b/tools/doc/amqp-declare-queue.xml new file mode 100644 index 0000000..0fc0440 --- /dev/null +++ b/tools/doc/amqp-declare-queue.xml @@ -0,0 +1,122 @@ + + +] +> + + + RabbitMQ C Client + + The RabbitMQ Team <info@rabbitmq.com> + + &date; + + + + amqp-declare-queue + 1 + RabbitMQ C Client + + + + amqp-declare-queue + Declare (create or assert the existence of) a queue on an AMQP server + + + + + amqp-declare-queue + + OPTION + + -d + -q queue name + + + + + Description + + amqp-declare-queue attempts to create a + queue on an AMQP server, and exits. If the empty-string is + supplied as the queue name, a fresh queue name is + generated by the server and returned. In all cases, if a + queue was successfully declared, the (raw binary) name of + the queue is printed to standard output, followed by a + newline. + + + + + Options + + + + =queue name + + + The name of the queue to declare. If the + empty string is supplied, a fresh queue name + is generated by the server. + + + + + + + + + Causes the queue to be declared with the + "durable" flag set. Durable queues survive + server restarts. By default, queues are declared + in "transient" mode. + + + + + + + + Exit Status + + If the queue was successfully declared, the exit status is + 0. If an error occurs, the exit status is 1. + + + + + Examples + + + Declare the durable queue myqueue, and + display the name of the queue on standard output: + + $ amqp-declare-queue -d -q myqueue +myqueue + + + + Declare a fresh, server-named transient queue, + and display the name of the queue on standard output + (use amqp-delete-queue + 1 to delete + it from the server once you're done): + + $ amqp-declare-queue -q "" +amq.gen-BW/wvociA8g6LFpb1PlqOA== + + + + + + + See also + + librabbitmq-tools7 + describes connection-related options common to all the + RabbitMQ C Client tools. + + + diff --git a/tools/doc/amqp-delete-queue.xml b/tools/doc/amqp-delete-queue.xml new file mode 100644 index 0000000..040a384 --- /dev/null +++ b/tools/doc/amqp-delete-queue.xml @@ -0,0 +1,94 @@ + + +] +> + + + RabbitMQ C Client + + The RabbitMQ Team <info@rabbitmq.com> + + &date; + + + + amqp-delete-queue + 1 + RabbitMQ C Client + + + + amqp-delete-queue + Delete a queue from an AMQP server + + + + + amqp-delete-queue + + OPTION + + -q queue name + + + + + Description + + amqp-delete-queue deletes a queue from + an AMQP server, and exits after printing to standard + output the number of messages that were in the queue at + the time of its deletion. + + + + + Options + + + + =queue name + + + The name of the queue to delete. + + + + + + + + Exit Status + + If the queue was successfully deleted, the exit status is + 0. If an error occurs, the exit status is 1. + + + + + Examples + + + Delete the + queue myqueue + at a moment when it has 123 messages waiting on + it: + + $ amqp-delete-queue -q myqueue +123 + + + + + + + See also + + librabbitmq-tools7 + describes connection-related options common to all the + RabbitMQ C Client tools. + + + diff --git a/tools/doc/amqp-get.xml b/tools/doc/amqp-get.xml new file mode 100644 index 0000000..08abe2b --- /dev/null +++ b/tools/doc/amqp-get.xml @@ -0,0 +1,95 @@ + + +] +> + + + RabbitMQ C Client + + The RabbitMQ Team <info@rabbitmq.com> + + &date; + + + + amqp-get + 1 + RabbitMQ C Client + + + + amqp-get + Get a message from a queue on an AMQP server + + + + + amqp-get + + OPTION + + -q queue name + + + + + Description + + amqp-get attempts to consume a single + message from a queue on an AMQP server, and exits. Unless + the queue was empty, the body of the resulting message is + sent to standard output. + + + + + Options + + + + =queue name + + + The name of the queue to consume messages + from. + + + + + + + + Exit Status + + If the queue is not empty, and a message is successfully + retrieved, the exit status is 0. If an error occurs, the + exit status is 1. If the queue is found to be empty, the + exit status is 2. + + + + + Examples + + + Get a message from the queue myqueue, and + display its body on standard output: + + $ amqp-get -q myqueue + + + + + + + See also + + librabbitmq-tools7 + describes connection-related options common to all the + RabbitMQ C Client tools. + + + diff --git a/tools/doc/amqp-publish.xml b/tools/doc/amqp-publish.xml new file mode 100644 index 0000000..54c5439 --- /dev/null +++ b/tools/doc/amqp-publish.xml @@ -0,0 +1,169 @@ + + +] +> + + + RabbitMQ C Client + + The RabbitMQ Team <info@rabbitmq.com> + + &date; + + + + amqp-publish + 1 + RabbitMQ C Client + + + + amqp-publish + Publish a message on an AMQP server + + + + + amqp-publish + + OPTION + + + + + + Description + + Publishes a message to an exchange on an AMQP server. + Options allow the various properties of the message and + parameters of the AMQP basic.publish + method to be specified. + + + By default, the message body is read from standard input. + Alternatively, the option allows the message + body to be provided as part of the command. + + + + + Options + + + + =exchange name + + + The name of the exchange to publish to. If + omitted, the default exchange (also known as + the nameless exchange) is used. + + + + + + =routing key + + + The routing key to publish with. If omitted, + an empty routing key is assumed. A routing + key must be specified when publishing to the + default exchange; in that case, according to + the AMQP specification, the routing key + corresponds to a queue name. + + + + + + + + + Use the persistent delivery mode. Without + this option, non-persistent delivery is used. + + + + + + =MIME type + + + Specifies the content-type property for the + message. If omitted, the content-type + property is not set on the message. + + + + + + =content coding + + + Specifies the content-encoding property for + the message. If omitted, the content-encoding + property is not set on the message. + + + + + + =message body + + + Specifies the message body. If omitted, the + message body is read from standard input. + + + + + + =header + + + Specifies an optional header in the form "key: value". + + + + + + + + Examples + + + Send a short message, consisting of the word + Hello to the queue + myqueue via the + default exchange: + + $ amqp-publish -r myqueue -b Hello + + + + + Send some XML data from a file to the exchange + events, with + persistent delivery mode, setting the content-type + property on the message to make the data format + explicit: + + $ amqp-publish -e events -p -C text/xml <event.xml + + + + + + + See also + + librabbitmq-tools7 + describes connection-related options common to all the + RabbitMQ C Client tools. + + + diff --git a/tools/doc/librabbitmq-tools.xml b/tools/doc/librabbitmq-tools.xml new file mode 100644 index 0000000..e7e98aa --- /dev/null +++ b/tools/doc/librabbitmq-tools.xml @@ -0,0 +1,90 @@ + + +] +> + + + RabbitMQ C Client + + The RabbitMQ Team <info@rabbitmq.com> + + &date; + + + + librabbitmq-tools + 7 + RabbitMQ C Client + + + + librabbitmq-tools + Command line AMQP tools + + + + Description + + A set of command line AMQP tools based on librabbitmq. This page + describes common options and conventions used by all of + the tools. + + + + + Common Options + + + + =hostname:port + + + The host name (or address) to connect to. + Defaults to localhost. The port number may + also be specified; if omitted, it defaults to + the standard AMQP port number (5672). + + + + + =vhost + + + The AMQP vhost to specify when connecting. + Defaults to /. + + + + + =username + + + The username to authenticate to the AMQP server with. Defaults to guest. + + + + + =password + + + The password to authenticate to the AMQP server with. Defaults to guest. + + + + + + + + See also + + + amqp-publish1 + amqp-consume1 + amqp-get1 + + + + diff --git a/tools/get.c b/tools/get.c new file mode 100644 index 0000000..88c1e10 --- /dev/null +++ b/tools/get.c @@ -0,0 +1,42 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include + +#include "common.h" + +static int do_get(amqp_connection_state_t conn, char *queue) { + amqp_rpc_reply_t r = amqp_basic_get(conn, 1, cstring_bytes(queue), 1); + die_rpc(r, "basic.get"); + + if (r.reply.id == AMQP_BASIC_GET_EMPTY_METHOD) { + return 0; + } + + copy_body(conn, 1); + return 1; +} + +int main(int argc, const char **argv) { + amqp_connection_state_t conn; + static char *queue = NULL; + int got_something; + + struct poptOption options[] = { + INCLUDE_OPTIONS(connect_options), + {"queue", 'q', POPT_ARG_STRING, &queue, 0, "the queue to consume from", + "queue"}, + POPT_AUTOHELP{NULL, '\0', 0, NULL, 0, NULL, NULL}}; + + process_all_options(argc, argv, options); + + if (!queue) { + fprintf(stderr, "queue not specified\n"); + return 1; + } + + conn = make_connection(); + got_something = do_get(conn, queue); + close_connection(conn); + return got_something ? 0 : 2; +} diff --git a/tools/publish.c b/tools/publish.c new file mode 100644 index 0000000..7243ee7 --- /dev/null +++ b/tools/publish.c @@ -0,0 +1,152 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include + +#include "common.h" + +#define MAX_LINE_LENGTH 1024 * 32 + +static void do_publish(amqp_connection_state_t conn, char *exchange, + char *routing_key, amqp_basic_properties_t *props, + amqp_bytes_t body) { + int res = amqp_basic_publish(conn, 1, cstring_bytes(exchange), + cstring_bytes(routing_key), 0, 0, props, body); + die_amqp_error(res, "basic.publish"); +} + +int main(int argc, const char **argv) { + amqp_connection_state_t conn; + static char *exchange = NULL; + static char *routing_key = NULL; + static char *content_type = NULL; + static char *content_encoding = NULL; + static char **headers = NULL; + static char *reply_to = NULL; + static char *body = NULL; + amqp_basic_properties_t props; + amqp_bytes_t body_bytes; + static int delivery = 1; /* non-persistent by default */ + static int line_buffered = 0; + static char **pos; + + struct poptOption options[] = { + INCLUDE_OPTIONS(connect_options), + {"exchange", 'e', POPT_ARG_STRING, &exchange, 0, + "the exchange to publish to", "exchange"}, + {"routing-key", 'r', POPT_ARG_STRING, &routing_key, 0, + "the routing key to publish with", "routing key"}, + {"persistent", 'p', POPT_ARG_VAL, &delivery, 2, + "use the persistent delivery mode", NULL}, + {"content-type", 'C', POPT_ARG_STRING, &content_type, 0, + "the content-type for the message", "content type"}, + {"reply-to", 't', POPT_ARG_STRING, &reply_to, 0, + "the replyTo to use for the message", "reply to"}, + {"line-buffered", 'l', POPT_ARG_VAL, &line_buffered, 2, + "treat each line from standard in as a separate message", NULL}, + {"content-encoding", 'E', POPT_ARG_STRING, &content_encoding, 0, + "the content-encoding for the message", "content encoding"}, + {"header", 'H', POPT_ARG_ARGV, &headers, 0, + "set a message header (may be specified multiple times)", + "\"key: value\""}, + {"body", 'b', POPT_ARG_STRING, &body, 0, "specify the message body", + "body"}, + POPT_AUTOHELP{NULL, '\0', 0, NULL, 0, NULL, NULL}}; + + process_all_options(argc, argv, options); + + if (!exchange && !routing_key) { + fprintf(stderr, "neither exchange nor routing key specified\n"); + return 1; + } + + memset(&props, 0, sizeof props); + props._flags = AMQP_BASIC_DELIVERY_MODE_FLAG; + props.delivery_mode = delivery; + + if (content_type) { + props._flags |= AMQP_BASIC_CONTENT_TYPE_FLAG; + props.content_type = amqp_cstring_bytes(content_type); + } + + if (content_encoding) { + props._flags |= AMQP_BASIC_CONTENT_ENCODING_FLAG; + props.content_encoding = amqp_cstring_bytes(content_encoding); + } + + if (reply_to) { + props._flags |= AMQP_BASIC_REPLY_TO_FLAG; + props.reply_to = amqp_cstring_bytes(reply_to); + } + + if (headers) { + int num = 0; + for (pos = headers; *pos; pos++) { + num++; + } + + if (num > 0) { + amqp_table_t *table = &props.headers; + table->num_entries = num; + table->entries = calloc(num, sizeof(amqp_table_entry_t)); + if (table->entries == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + return 1; + } + int i = 0; + for (pos = headers; *pos; pos++) { + char *colon = strchr(*pos, ':'); + if (colon) { + *colon++ = '\0'; + while (*colon == ' ') colon++; + table->entries[i].key = amqp_cstring_bytes(*pos); + table->entries[i].value.kind = AMQP_FIELD_KIND_UTF8; + table->entries[i].value.value.bytes = amqp_cstring_bytes(colon); + i++; + } else { + fprintf(stderr, + "Ignored header definition missing ':' delimiter in \"%s\"\n", + *pos); + } + } + props._flags |= AMQP_BASIC_HEADERS_FLAG; + } + } + + conn = make_connection(); + + if (body) { + body_bytes = amqp_cstring_bytes(body); + } else { + if (line_buffered) { + body_bytes.bytes = (char *)malloc(MAX_LINE_LENGTH); + if (body_bytes.bytes == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + return 1; + } + while (fgets(body_bytes.bytes, MAX_LINE_LENGTH, stdin)) { + body_bytes.len = strlen(body_bytes.bytes); + do_publish(conn, exchange, routing_key, &props, body_bytes); + } + } else { + body_bytes = read_all(0); + } + } + + if (!line_buffered) { + do_publish(conn, exchange, routing_key, &props, body_bytes); + } + + if (props.headers.num_entries > 0) { + free(props.headers.entries); + } + + if (!body) { + free(body_bytes.bytes); + } + + close_connection(conn); + return 0; +} diff --git a/tools/unix/process.c b/tools/unix/process.c new file mode 100644 index 0000000..6400cb9 --- /dev/null +++ b/tools/unix/process.c @@ -0,0 +1,55 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include +#include + +#include "common.h" +#include "process.h" + +extern char **environ; + +void pipeline(const char *const *argv, struct pipeline *pl) { + posix_spawn_file_actions_t file_acts; + + int pipefds[2]; + if (pipe(pipefds)) { + die_errno(errno, "pipe"); + } + + die_errno(posix_spawn_file_actions_init(&file_acts), + "posix_spawn_file_actions_init"); + die_errno(posix_spawn_file_actions_adddup2(&file_acts, pipefds[0], 0), + "posix_spawn_file_actions_adddup2"); + die_errno(posix_spawn_file_actions_addclose(&file_acts, pipefds[0]), + "posix_spawn_file_actions_addclose"); + die_errno(posix_spawn_file_actions_addclose(&file_acts, pipefds[1]), + "posix_spawn_file_actions_addclose"); + + die_errno(posix_spawnp(&pl->pid, argv[0], &file_acts, NULL, + (char *const *)argv, environ), + "posix_spawnp: %s", argv[0]); + + die_errno(posix_spawn_file_actions_destroy(&file_acts), + "posix_spawn_file_actions_destroy"); + + if (close(pipefds[0])) { + die_errno(errno, "close"); + } + + pl->infd = pipefds[1]; +} + +int finish_pipeline(struct pipeline *pl) { + int status; + + if (close(pl->infd)) { + die_errno(errno, "close"); + } + if (waitpid(pl->pid, &status, 0) < 0) { + die_errno(errno, "waitpid"); + } + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} diff --git a/tools/unix/process.h b/tools/unix/process.h new file mode 100644 index 0000000..9dbade9 --- /dev/null +++ b/tools/unix/process.h @@ -0,0 +1,10 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +struct pipeline { + int pid; + int infd; +}; + +extern void pipeline(const char *const *argv, struct pipeline *pl); +extern int finish_pipeline(struct pipeline *pl); diff --git a/tools/win32/compat.c b/tools/win32/compat.c new file mode 100644 index 0000000..b70d740 --- /dev/null +++ b/tools/win32/compat.c @@ -0,0 +1,29 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include + +#include "compat.h" + +int asprintf(char **strp, const char *fmt, ...) { + va_list ap; + int len; + + va_start(ap, fmt); + len = _vscprintf(fmt, ap); + va_end(ap); + + *strp = malloc(len + 1); + if (!*strp) { + return -1; + } + + va_start(ap, fmt); + _vsnprintf(*strp, len + 1, fmt, ap); + va_end(ap); + + (*strp)[len] = 0; + return len; +} diff --git a/tools/win32/compat.h b/tools/win32/compat.h new file mode 100644 index 0000000..83b4093 --- /dev/null +++ b/tools/win32/compat.h @@ -0,0 +1,4 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +extern int asprintf(char **strp, const char *fmt, ...); diff --git a/tools/win32/process.c b/tools/win32/process.c new file mode 100644 index 0000000..f86604f --- /dev/null +++ b/tools/win32/process.c @@ -0,0 +1,191 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include +#include +#include + +#include "common.h" +#include "process.h" + +void die_windows_error(const char *fmt, ...) { + char *msg; + + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (!FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, + GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&msg, 0, NULL)) { + msg = "(failed to retrieve Windows error message)"; + } + + fprintf(stderr, ": %s\n", msg); + exit(1); +} + +static char *make_command_line(const char *const *argv) { + int i; + size_t len = 1; /* initial quotes */ + char *buf; + char *dest; + + /* calculate the length of the required buffer, making worst + case assumptions for simplicity */ + for (i = 0;;) { + /* each character could need escaping */ + len += strlen(argv[i]) * 2; + + if (!argv[++i]) { + break; + } + + len += 3; /* quotes, space, quotes */ + } + + len += 2; /* final quotes and the terminating zero */ + + dest = buf = malloc(len); + if (!buf) { + die("allocating memory for subprocess command line"); + } + + /* Here we perform the inverse of the CommandLineToArgvW + function. Note that its rules are slightly crazy: A + sequence of backslashes only act to escape if followed by + double quotes. A sequence of backslashes not followed by + double quotes is untouched. */ + + for (i = 0;;) { + const char *src = argv[i]; + int backslashes = 0; + + *dest++ = '\"'; + + for (;;) { + switch (*src) { + case 0: + goto done; + + case '\"': + for (; backslashes; backslashes--) { + *dest++ = '\\'; + } + + *dest++ = '\\'; + *dest++ = '\"'; + break; + + case '\\': + backslashes++; + *dest++ = '\\'; + break; + + default: + backslashes = 0; + *dest++ = *src; + break; + } + + src++; + } + done: + for (; backslashes; backslashes--) { + *dest++ = '\\'; + } + + *dest++ = '\"'; + + if (!argv[++i]) { + break; + } + + *dest++ = ' '; + } + + *dest++ = 0; + return buf; +} + +void pipeline(const char *const *argv, struct pipeline *pl) { + HANDLE in_read_handle, in_write_handle; + SECURITY_ATTRIBUTES sec_attr; + PROCESS_INFORMATION proc_info; + STARTUPINFO start_info; + char *cmdline = make_command_line(argv); + + sec_attr.nLength = sizeof sec_attr; + sec_attr.bInheritHandle = TRUE; + sec_attr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&in_read_handle, &in_write_handle, &sec_attr, 0)) { + die_windows_error("CreatePipe"); + } + + if (!SetHandleInformation(in_write_handle, HANDLE_FLAG_INHERIT, 0)) { + die_windows_error("SetHandleInformation"); + } + + /* when in Rome... */ + ZeroMemory(&proc_info, sizeof proc_info); + ZeroMemory(&start_info, sizeof start_info); + + start_info.cb = sizeof start_info; + start_info.dwFlags |= STARTF_USESTDHANDLES; + + if ((start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE)) == + INVALID_HANDLE_VALUE || + (start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)) == + INVALID_HANDLE_VALUE) { + die_windows_error("GetStdHandle"); + } + + start_info.hStdInput = in_read_handle; + + if (!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, + &start_info, &proc_info)) { + die_windows_error("CreateProcess"); + } + + free(cmdline); + + if (!CloseHandle(proc_info.hThread)) { + die_windows_error("CloseHandle for thread"); + } + if (!CloseHandle(in_read_handle)) { + die_windows_error("CloseHandle"); + } + + pl->proc_handle = proc_info.hProcess; + pl->infd = _open_osfhandle((intptr_t)in_write_handle, 0); +} + +int finish_pipeline(struct pipeline *pl) { + DWORD code; + + if (close(pl->infd)) { + die_errno(errno, "close"); + } + + for (;;) { + if (!GetExitCodeProcess(pl->proc_handle, &code)) { + die_windows_error("GetExitCodeProcess"); + } + if (code != STILL_ACTIVE) { + break; + } + + if (WaitForSingleObject(pl->proc_handle, INFINITE) == WAIT_FAILED) { + die_windows_error("WaitForSingleObject"); + } + } + + if (!CloseHandle(pl->proc_handle)) { + die_windows_error("CloseHandle for process"); + } + + return code; +} diff --git a/tools/win32/process.h b/tools/win32/process.h new file mode 100644 index 0000000..3be9ac4 --- /dev/null +++ b/tools/win32/process.h @@ -0,0 +1,12 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include + +struct pipeline { + HANDLE proc_handle; + int infd; +}; + +extern void pipeline(const char *const *argv, struct pipeline *pl); +extern int finish_pipeline(struct pipeline *pl); diff --git a/travis.sh b/travis.sh new file mode 100644 index 0000000..e1182ae --- /dev/null +++ b/travis.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +# Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +# SPDX-License-Identifier: mit + +build_cmake() { + sudo apt install -y xmlto + mkdir $PWD/_build && cd $PWD/_build + cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DBUILD_TOOLS_DOCS=ON -DCMAKE_INSTALL_PREFIX=$PWD/../_install -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Wno-implicit-fallthrough -Werror" + cmake --build . --target install + ctest -V . +} + +build_framing() { + sudo apt install -y clang-format + ./regenerate_framing.sh + mkdir $PWD/_build && cd $PWD/_build + cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DCMAKE_INSTALL_PREFIX=$PWD/../_install -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Wno-implicit-fallthrough -Werror" + cmake --build . --target install + ctest -V . +} + +build_macos() { + mkdir $PWD/_build && cd $PWD/_build + cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DCMAKE_INSTALL_PREFIX=$PWD/../_install -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Werror" \ + -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl@1.1" + cmake --build . --target install + ctest -V . +} + +build_format() { + sudo apt-get install -y clang-format + ./travis/run-clang-format/run-clang-format.py \ + --clang-format-executable="${PWD}/travis/clang-format.sh" \ + --recursive examples librabbitmq tests tools +} + +build_coverage() { + mkdir $PWD/_build && cd $PWD/_build + cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DCMAKE_BUILD_TYPE=Coverage -DCMAKE_INSTALL_PREFIX=$PWD/../_install \ + -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Werror -fprofile-arcs -ftest-coverage" + cmake --build . --target install + ctest -V . + + pip install --user cpp-coveralls + coveralls --exclude tests --build-root . --root .. --gcov-options '\-lp' +} + +build_asan() { + mkdir $PWD/_build && cd $PWD/_build + cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$PWD/../_install \ + -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Werror -fsanitize=address,undefined -O1" + cmake --build . --target install + ctest -V . +} + +build_tsan() { + mkdir $PWD/_build && cd $PWD/_build + cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$PWD/../_install \ + -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Werror -fsanitize=thread,undefined -O1" + cmake --build . --target install + ctest -V . +} + +build_scan-build() { + sudo apt install -y clang-tools + mkdir $PWD/_build && cd $PWD/_build + scan-build cmake .. -GNinja -DBUILD_EXAMPLES=ON -DBUILD_TOOLS=ON -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_INSTALL_PREFIX=$PWD/../_install \ + -DCMAKE_C_FLAGS="-Wall -Wextra -Wstrict-prototypes -Wno-unused-function -Werror" + scan-build ninja install +} + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 {cmake|framing|macos|format|coverage|asan|tsan|scan-build}" + exit 1 +fi + +set -e # exit on error. +set -x # echo commands. + +eval "build_$1" diff --git a/travis/clang-format.sh b/travis/clang-format.sh new file mode 100644 index 0000000..80985e8 --- /dev/null +++ b/travis/clang-format.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -e + +exec clang-format -style=file $@