commit 2974a3dec552ed55b3c00667f3ac674f05c05f3f Author: bing Date: Fri Apr 3 11:29:43 2026 +0800 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f2dd0de --- /dev/null +++ b/.clang-format @@ -0,0 +1,168 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... + diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..989db84 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,11 @@ + +# [Choice] Debian / Ubuntu version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04 +ARG VARIANT=buster +FROM mcr.microsoft.com/vscode/devcontainers/cpp:dev-${VARIANT} + +# [Optional] Uncomment this section to install additional packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends lsb-release wget software-properties-common \ + && bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" \ + && apt-get -y install --no-install-recommends clang-format-11 clang-tidy-11 ninja-build rabbitmq-server \ + libssl-dev librabbitmq-dev libboost-dev libboost-chrono-dev libboost-system-dev diff --git a/.devcontainer/base.Dockerfile b/.devcontainer/base.Dockerfile new file mode 100644 index 0000000..1eb2f58 --- /dev/null +++ b/.devcontainer/base.Dockerfile @@ -0,0 +1,12 @@ +# [Choice] Debian / Ubuntu version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04 +ARG VARIANT=buster +FROM mcr.microsoft.com/vscode/devcontainers/base:${VARIANT} + +# Install needed packages. Use a separate RUN statement to add your own dependencies. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install build-essential cmake cppcheck valgrind clang lldb llvm gdb \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..8a88560 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +{ + "name": "C++", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick an Debian / Ubuntu OS version: debian-10, debian-9, ubuntu-20.04, ubuntu-18.04 + "args": { "VARIANT": "ubuntu-20.04" } + }, + "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], + + // Set *default* container specific settings.json values on container create. + "settings": { + "clangd.path": "clangd-11", + "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json", + "workbench.colorTheme": "Solarized Dark", + "terminal.integrated.shell.linux": "/bin/bash" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + // "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "vscodevim.vim", + "llvm-vs-code-extensions.vscode-clangd", + "twxs.cmake", + ], + + // 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", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d4e75ec --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Default for those who don't have core.autocrlf set +* text=auto + +# Things that should be treated as text +*.cpp text +*.h text +CMakeLists.txt text +*.cmake text +*.md text +*.in text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..daedd3d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: ci + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v3 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Install Deps + shell: bash + run: | + sudo apt update + sudo apt install libboost-all-dev librabbitmq-dev + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afd1bdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*~ +.*.sw? + +.cache/ +.vscode/ +build/ +CMakeCache.txt +cmake_install.cmake +CMakeFiles/ +Debug/ + +*.vcxproj* +*.sln +*.suo +*.*sdf +ipch/ +install_manifest.txt +.ycm_extra_conf.py* +compile_commands.json +*.orig diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3f127b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third-party/googletest"] + path = third-party/googletest + url = https://github.com/google/googletest.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..30529ea --- /dev/null +++ b/.travis.yml @@ -0,0 +1,55 @@ +# Travis-CI Build for SimpleAmqpClient +# see travis-ci.org for details + +language: cpp + +os: linux +dist: xenial + +jobs: + include: + - compiler: gcc + env: FLAGS="" + - compiler: clang + env: FLAGS="" + - compiler: clang + env: FLAGS="-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer" + - compiler: clang + env: FLAGS="-g -O1 -fsanitize=thread -fno-omit-frame-pointer" + +addons: + apt: + sources: + - sourceline: deb http://dl.bintray.com/rabbitmq-erlang/debian xenial erlang + key_url: https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc + - sourceline: deb https://dl.bintray.com/rabbitmq/debian xenial main + key_url: https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc + packages: + - libboost-dev + - libboost-chrono-dev + - libboost-system-dev + - rabbitmq-server + - ninja-build + +# install pre-reqs +install: + - mkdir -p _prereqs + - pushd _prereqs + - git clone https://github.com/alanxz/rabbitmq-c + - cd rabbitmq-c + - git checkout v0.10.0 + - export RABBITMQC_DIR=`pwd`/../../_install + - cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${RABBITMQC_DIR} -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=OFF -DBUILD_TOOLS=OFF . + - cmake --build . --target install + - popd + +before_script: + - mkdir _build + - cd _build + - pwd + +# Run the Build script +script: + - cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="${FLAGS}" -DCMAKE_INSTALL_PREFIX=../_install -DENABLE_TESTING=ON -DRabbitmqc_DIR=${RABBITMQ_C_DIR} .. + - cmake --build . --target install + - AMQP_BROKER=localhost ASAN_OPTIONS=detect_leaks=1 ctest -V . diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5a360da --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,316 @@ +# Most widely used distributions have cmake 3.5 or greater available as of March +# 2019. A notable exception is RHEL-7 (CentOS7). You can install a current +# version of CMake by first installing Extra Packages for Enterprise Linux +# (https://fedoraproject.org/wiki/EPEL#Extra_Packages_for_Enterprise_Linux_.28EPEL.29) +# and then issuing `yum install cmake3` on the command line. +cmake_minimum_required(VERSION 3.5) + +project(SimpleAmqpClient LANGUAGES CXX) + +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 98) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# 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(SAC_SOVERSION_CURRENT 7) +set(SAC_SOVERSION_REVISION 1) +set(SAC_SOVERSION_AGE 0) + +math(EXPR SAC_SOVERSION_MAJOR "${SAC_SOVERSION_CURRENT} - ${SAC_SOVERSION_AGE}") +math(EXPR SAC_SOVERSION_MINOR "${SAC_SOVERSION_AGE}") +math(EXPR SAC_SOVERSION_PATCH "${SAC_SOVERSION_REVISION}") + +set(SAC_VERSION ${SAC_SOVERSION_MAJOR}.${SAC_SOVERSION_MINOR}.${SAC_SOVERSION_PATCH}) +set(SAC_SOVERSION ${SAC_SOVERSION_MAJOR}) + +file(STRINGS src/SimpleAmqpClient/Version.h _API_VERSION_MAJOR REGEX "^#define SIMPLEAMQPCLIENT_VERSION_MAJOR [0-9]+$") +file(STRINGS src/SimpleAmqpClient/Version.h _API_VERSION_MINOR REGEX "^#define SIMPLEAMQPCLIENT_VERSION_MINOR [0-9]+$") +file(STRINGS src/SimpleAmqpClient/Version.h _API_VERSION_PATCH REGEX "^#define SIMPLEAMQPCLIENT_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}) + +set(SAC_APIVERSION ${_API_VERSION_MAJOR}.${_API_VERSION_MINOR}.${_API_VERSION_PATCH}) + +option(BUILD_SHARED_LIBS "Build SimpleAmqpClient as a shared library" ON) + +# Force the use of static boost library for static libraries +include(CMakeDependentOption) + +cmake_dependent_option( + Boost_Dynamic_Linking_ENABLED + "Enable boost dynamic linking" + ON + "BUILD_SHARED_LIBS" + OFF + ) + +if(Boost_Dynamic_Linking_ENABLED) + set(Boost_USE_STATIC_LIBS OFF) +else() + set(Boost_USE_STATIC_LIBS ON) +endif() +set(Boost_USE_STATIC_RUNTIME OFF) + +find_package(Boost 1.47.0 COMPONENTS chrono REQUIRED) +if(Boost_VERSION VERSION_LESS 1.89) + find_package(Boost 1.47.0 COMPONENTS chrono system REQUIRED) +endif() +include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) +link_directories(${Boost_LIBRARY_DIRS}) + +# Try using the CMake config modules first +find_package(rabbitmq-c CONFIG QUIET) +if (rabbitmq-c_FOUND) + if (BUILD_SHARED_LIBS) + set(Rabbitmqc_LIBRARY rabbitmq::rabbitmq) + else() + set(Rabbitmqc_LIBRARY rabbitmq::rabbitmq-static) + endif() + get_target_property(Rabbitmqc_INCLUDE_DIRS ${Rabbitmqc_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES) +else() + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Modules) + find_package(Rabbitmqc REQUIRED) + INCLUDE_DIRECTORIES(SYSTEM ${Rabbitmqc_INCLUDE_DIRS}) +endif() + +option(ENABLE_SSL_SUPPORT "Enable SSL support." ${Rabbitmqc_SSL_ENABLED}) + +if (ENABLE_SSL_SUPPORT) + add_definitions(-DSAC_SSL_SUPPORT_ENABLED) +endif() + +if (CMAKE_GENERATOR MATCHES ".*(Make|Ninja).*" + AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel" FORCE) + message(STATUS "CMAKE_BUILD_TYPE not specified. Using ${CMAKE_BUILD_TYPE} build") +endif () + +if (CMAKE_CXX_FLAGS STREQUAL "" + AND NOT DEFINED SAC_CXX_FLAGS_SET) + if (CMAKE_COMPILER_IS_GNUCXX + OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + SET(CMAKE_CXX_FLAGS "-Wall -Wextra" CACHE STRING "Flags used by the compiler during all build types." FORCE) + endif () + set(SAC_CXX_FLAGS_SET TRUE CACHE INTERNAL "Have the SAC default compiler flags been set?") +endif () + +include_directories(BEFORE src + ${CMAKE_CURRENT_BINARY_DIR}) + +if (WIN32) + set(SOCKET_LIBRARY ws2_32) +endif () + +set(SAC_LIB_SRCS + src/SimpleAmqpClient/SimpleAmqpClient.h + + src/SimpleAmqpClient/AmqpException.h + src/AmqpException.cpp + + src/SimpleAmqpClient/Bytes.h + + src/SimpleAmqpClient/Channel.h + src/Channel.cpp + + src/SimpleAmqpClient/ChannelImpl.h + src/ChannelImpl.cpp + + src/SimpleAmqpClient/BasicMessage.h + src/BasicMessage.cpp + + src/SimpleAmqpClient/Util.h + + src/SimpleAmqpClient/AmqpLibraryException.h + src/AmqpLibraryException.cpp + + src/SimpleAmqpClient/AmqpResponseLibraryException.h + src/AmqpResponseLibraryException.cpp + + src/SimpleAmqpClient/BadUriException.h + src/SimpleAmqpClient/ConnectionClosedException.h + src/SimpleAmqpClient/ConsumerTagNotFoundException.h + src/SimpleAmqpClient/MessageRejectedException.h + + src/SimpleAmqpClient/Envelope.h + src/Envelope.cpp + + src/SimpleAmqpClient/MessageReturnedException.h + src/MessageReturnedException.cpp + + src/SimpleAmqpClient/Table.h + src/Table.cpp + + src/SimpleAmqpClient/TableImpl.h + src/TableImpl.cpp + ) + + +add_library(SimpleAmqpClient ${SAC_LIB_SRCS}) +target_link_libraries(SimpleAmqpClient ${Rabbitmqc_LIBRARY} ${SOCKET_LIBRARY} ${Boost_LIBRARIES} $<$:Boost::dynamic_linking>) + +if (WIN32) + if (NOT BUILD_SHARED_LIBS) + target_compile_definitions(SimpleAmqpClient PUBLIC SimpleAmqpClient_STATIC) + endif () + + set_target_properties(SimpleAmqpClient PROPERTIES VERSION ${SAC_VERSION} OUTPUT_NAME SimpleAmqpClient.${SAC_SOVERSION}) +else () + set_target_properties(SimpleAmqpClient PROPERTIES VERSION ${SAC_VERSION} SOVERSION ${SAC_SOVERSION}) +endif () + +# Some smoke tests: + +option(ENABLE_TESTING "Enable smoke tests" OFF) + +if (ENABLE_TESTING) + enable_testing() + + set(BUILD_GTEST ON CACHE BOOL "" FORCE) + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + + # This only affects targets declared after this. + set(BUILD_SHARED_LIBS OFF) + + mark_as_advanced(BUILD_GMOCK) + mark_as_advanced(BUILD_GTEST) + mark_as_advanced(INSTALL_GTEST) + mark_as_advanced(gmock_build_tests) + mark_as_advanced(gtest_build_samples) + mark_as_advanced(gtest_build_tests) + mark_as_advanced(gtest_disable_pthreads) + mark_as_advanced(gtest_force_shared_crt) + mark_as_advanced(gtest_hide_internal_symbols) + + add_subdirectory(third-party/googletest) + add_subdirectory(testing) +endif (ENABLE_TESTING) + + +# Documentation generation +find_package(Doxygen COMPONENTS dot) +option(BUILD_API_DOCS "Build Doxygen API docs" ${DOXYGEN_FOUND}) + +if (BUILD_API_DOCS) + if (NOT DOXYGEN_FOUND) + message(FATAL_ERROR "Doxygen is required to build the API documentation") + endif () + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile @ONLY) + + add_custom_target(docs ALL + COMMAND ${DOXYGEN_EXECUTABLE} + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs + DEPENDS SimpleAmqpClient + COMMENT "Generating API documentation" + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in + ) +endif () + +include(GNUInstallDirs) + +install(TARGETS SimpleAmqpClient + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + +install(FILES + src/SimpleAmqpClient/AmqpException.h + src/SimpleAmqpClient/AmqpLibraryException.h + src/SimpleAmqpClient/AmqpResponseLibraryException.h + src/SimpleAmqpClient/BadUriException.h + src/SimpleAmqpClient/BasicMessage.h + src/SimpleAmqpClient/Channel.h + src/SimpleAmqpClient/ConnectionClosedException.h + src/SimpleAmqpClient/ConsumerCancelledException.h + src/SimpleAmqpClient/ConsumerTagNotFoundException.h + src/SimpleAmqpClient/Envelope.h + src/SimpleAmqpClient/MessageReturnedException.h + src/SimpleAmqpClient/MessageRejectedException.h + src/SimpleAmqpClient/SimpleAmqpClient.h + src/SimpleAmqpClient/Table.h + src/SimpleAmqpClient/Util.h + src/SimpleAmqpClient/Version.h + DESTINATION include/SimpleAmqpClient + ) + +set(prefix ${CMAKE_INSTALL_PREFIX}) +set(exec_prefix "\${prefix}") +set(libdir "\${prefix}/${CMAKE_INSTALL_LIBDIR}") +set(includedir "\${prefix}/include") +if(WIN32) + get_target_property(SIMPLEAMQPCLIENT_LIB SimpleAmqpClient OUTPUT_NAME) +else(WIN32) + set(SIMPLEAMQPCLIENT_LIB SimpleAmqpClient) +endif(WIN32) + +# Propagate package dependencies +if (BUILD_SHARED_LIBS) + set(requires_private "librabbitmq") +else (BUILD_SHARED_LIBS) + set(requires_public "librabbitmq") +endif (BUILD_SHARED_LIBS) + +# Propagate interface compile definitions +set(SIMPLEAMQPCLIENT_DEFINITIONS "") +get_target_property(propagated_definitions SimpleAmqpClient INTERFACE_COMPILE_DEFINITIONS) +if (propagated_definitions) + foreach(_def ${propagated_definitions}) + set(SIMPLEAMQPCLIENT_DEFINITIONS "${SIMPLEAMQPCLIENT_DEFINITIONS} -D${_def}") + endforeach() +endif(propagated_definitions) + +# Propagate library dependencies +set(libs_private "") +set(libs_public "") + +if (BUILD_SHARED_LIBS) + set(populate_libs "libs_private") +else (BUILD_SHARED_LIBS) + set(populate_libs "libs_public") + set(extra_win32_targets "${Rabbitmqc_LIBRARY};${SOCKET_LIBRARY}") +endif (BUILD_SHARED_LIBS) + +foreach(_lib ${Boost_LIBRARIES} ${extra_win32_targets}) + + # Check if FindBoost.cmake provided actual library paths or targets + if(TARGET ${_lib}) + get_target_property(_lib ${_lib} LOCATION) + message(WARNING "Using target ${_lib} as a library") + endif() + + get_filename_component(_LIBPATH ${_lib} PATH) + if (NOT _LIBPATH STREQUAL _LASTLIBPATH AND NOT _LIBPATH STREQUAL "") + set(${populate_libs} "${${populate_libs}} -L\"${_LIBPATH}\"") + set(_LASTLIBPATH ${_LIBPATH}) + endif() + + get_filename_component(_LIBNAME ${_lib} NAME_WLE) + if (NOT _LIBNAME STREQUAL "debug" AND NOT _LIBNAME STREQUAL "optimized") + if (NOT WIN32) + string(REGEX REPLACE "^lib" "" _LIBNAME ${_LIBNAME}) + endif() + set(_LIBNAME "-l${_LIBNAME}") + set(${populate_libs} "${${populate_libs}} ${_LIBNAME}") + endif() +endforeach() + +configure_file(libSimpleAmqpClient.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libSimpleAmqpClient.pc @ONLY) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/libSimpleAmqpClient.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..733bec2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +Contributing to SimpleAmqpClient +=============================== + +Thanks for contributing to SimpleAmqpClient. I firmly believe that +participation help make open source software great. With that in mind there +are a few things you can do to make this interaction a bit smoother. + +Please use the following guidelines when creating an issue or submitting +a pull request + +Creating an issue +----------------- + +Submitting a pull-request +------------------------ diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..bf879cc --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,80 @@ +# Changes since v2.5.0 (v2.5.1) + + - fix: restore GNUInstallDirs that was mistakenly removed. + - refactor of Channel constructors + +# Changes since v2.4 (v2.5) + +NOTE: this release requires rabbitmq-c v0.8.0 or better. + + - add: support timestamp values in `Table` (1057ed4) + - enh: improve efficiency of `BasicMessage` reducing number of copies (938b102) + - doc: many clarifications in documentation (47f4c79) + - add: support for BasicPublish message rejection due to queue full (ecfbbfc) + - add: support for additional parameters in creating SSL connection (56713c0, eaa6044) + - add: `Channel::GetSocketFD` to allow access to underlying channel socket. (7c14a2e) + - add: multiple flag on `Channel::BasicAck` (6323892) + - add: support for unsigned types in `Table` (20296d2) + - enh: support for RabbitMQ auth-failure extension (0b67021) + - fix: consumer prefetch difference on RabbitMQ v3.3+ (59a1e05) + - rabbitmq-c errors are wrapped in `AmqpLibraryException` (a3a3ef7) + + +Changes since v2.3 (v2.4): +- Add support for consumer cancellation notification (5d35698) +- Improvements in extra-frame bookkeeping reducing memory useage under certain + conditions (e9de652, f4980bc) +- API support for waiting for multiple consumers at a time (e7e701a) +- Add version header (0fc6cab) +- Add pkg-config on install (a13c99a) +- Add DeclareQueueWithCounts API (Kai Blaschke 7fbcd96) +- Support for C++11 (Alexandre Jacquin 57a8d85) +- Add BasicReject API (Luca Marturana 0c9478e) +- Upgrade gtest to v1.7.0 (8fe82fd) + +Changes since v2.2 (v2.3): +NOTE: this release uses new rabbitmq-c interfaces introduced in v0.4.0, thus +requires rabbitmq-c v0.4.0 or later. +- Add support for SSL (Ashok Anand 44b8b4e) +- Use new rabbitmq-c socket interface (Nikita Vasiliev 9f6cdac) +- Use new rabbitmq-c error-string interface (a26da26) +- Code formatting and license header updates (752ae75, 5a2f64c, c3dec10, 2b82942) +- Use new rabbitmq-c timeout interface when reading frames (d4a9f31) +- Use new rabbitmq-c interface to release memory on a per-channel basis (49b8ba8) + +Changes since v2.1 (v2.2): +NOTE: this is the last version targeting rabbitmq-c v0.3, newer versions will + target rabbitmq-c v0.4 +- Disable building test suite by default (4f6af4e) +- Default to building Release build when none is specified (c60d0e9) +- Add -Wall -Wextra to default C++ flags (bf813e5) +- Improve documentation (f967758, 23151d3) +- BUG: throw std::bad_alloc when a 0-length table is received (6d17950, d694d4b) +- Improve Channel::BasicGet documentation (ead3936) +- Disable tests that exercise the immediate flag in basic.publish (48636b1) +- Add Channel::BasicAck() overload allowing basic.ack without keeping the whole Envelope obj (0dea3b8, fcd094a) +- Add method to create Channel from an AMQP URI (c8cae56, 8dd62b5) +- Updated examples (fcc1176, a9d4eec, 03bb42d) + +Changes since v2.0-beta (v2.1) + - Add wrapping of amqp_table_t for passing table arguments to various + AMQP RPC methods (bae7b97) + - Fix for bug in BasicConsumeMessage default timeout (6412fcf3) + - Enable travis-ci continuous integration (44089d65) + - Ship google-test framework with library (8d86d2e4) + - Implement SOVERSION-ing (b44f3b7b) + - Missing include in AmqpException.cpp (20ccca9) + - Fix for memory leak in BasicPublish when exception is thrown (56e20b2) + - Fix for memory leak in BasicMessage when new body assigned (e5bf1157) + - Missing string.h include in AmqpException.h (ecee2104) + - Compile changes to compile cleanly under -Wall -Wextra (2b5a1a23) + - Fix for crash when AmqpException thrown without a class or method id (6a4fac62) + - Fix for incorrect timeout units when BasicConsumeMessage (3cdf94d9) + - Relicensed library under MIT license (a069444b) + - Fix sending unitialized data to broker (080bd9e9) + - Fix free strings returned by amqp_error_string (c7b0cfcc) + - Fix destroy amqp_connection_state object if an exception is thrown in Channel constructor (af936d0) + - Add ability to build as static library (50b6afd) + - Fix for macro redefinition (548084) + - Correct usage of stdint.h on VS2008 and earlier (795c0fea) + diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 0000000..738d225 --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,289 @@ +# Doxyfile 1.8.2 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "SimpleAmqpClient" +PROJECT_NUMBER = "@SAC_VERSION@" +PROJECT_BRIEF = "A C++ wrapper around the rabbitmq-c AMQP client library" +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 = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = YES +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 = NO +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = YES +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 = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +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 = YES +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_SOURCE_DIR@/src/SimpleAmqpClient" +INPUT += "@CMAKE_SOURCE_DIR@/README.md" +USE_MDFILE_AS_MAINPAGE = README.md +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h +RECURSIVE = YES +EXCLUDE = *Impl.h +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = *Impl.h +EXCLUDE_SYMBOLS = +#EXAMPLE_PATH = "@CMAKE_CURRENT_SOURCE_DIR@/examples" +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = NO +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# 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.SimpleAmqpClient +DOCSET_PUBLISHER_ID = com.github.alanxz +DOCSET_PUBLISHER_NAME = Alan Antonuk +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 = +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.SimpleAmqpClient +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NONE +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +SEARCHENGINE = NO +SERVER_BASED_SEARCH = NO +#--------------------------------------------------------------------------- +# 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 = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = YES +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_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# 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@/src +INCLUDE_FILE_PATTERNS = +PREDEFINED = GENERATING_DOCUMENTATION +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +DOT_NUM_THREADS = 0 +#DOT_FONTNAME = FreeSans +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 = svg +INTERACTIVE_SVG = YES +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = YES +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..c2b4051 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2010-2014 Alan Antonuk. 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. diff --git a/Modules/FindRabbitmqc.cmake b/Modules/FindRabbitmqc.cmake new file mode 100644 index 0000000..523ae93 --- /dev/null +++ b/Modules/FindRabbitmqc.cmake @@ -0,0 +1,32 @@ +#Find the Rabbitmq C library + +INCLUDE(LibFindMacros) + +# Find the include directories +FIND_PATH(Rabbitmqc_INCLUDE_DIR + NAMES amqp.h + HINTS ${Rabbitmqc_DIR}/include + ) + +FIND_LIBRARY(Rabbitmqc_LIBRARY + NAMES rabbitmq + HINTS ${Rabbitmqc_DIR}/lib + ) + +SET(Rabbitmqc_PROCESS_INCLUDES Rabbitmqc_INCLUDE_DIR) +SET(Rabbitmqc_PROCESS_LIBS Rabbitmqc_LIBRARY) + +LIBFIND_PROCESS(Rabbitmqc) + +find_file(_Rabbitmqc_SSL_HEADER + NAMES amqp_ssl_socket.h + PATHS ${Rabbitmqc_INCLUDE_DIR} + NO_DEFAULT_PATH + ) + +string(COMPARE NOTEQUAL "${_Rabbitmqc_SSL_HEADER}" + "_Rabbitmqc_SSL_HEADER-NOTFOUND" _rmqc_ssl_enabled) + +set(Rabbitmqc_SSL_ENABLED ${_rmqc_ssl_enabled} CACHE BOOL + "Rabbitmqc is SSL Enabled" FORCE) +mark_as_advanced(_Rabbitmqc_SSL_HEADER Rabbitmqc_SSL_ENABLED) diff --git a/Modules/LibFindMacros.cmake b/Modules/LibFindMacros.cmake new file mode 100644 index 0000000..69975c5 --- /dev/null +++ b/Modules/LibFindMacros.cmake @@ -0,0 +1,99 @@ +# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments +# used for the current package. For this to work, the first parameter must be the +# prefix of the current package, then the prefix of the new package etc, which are +# passed to find_package. +macro (libfind_package PREFIX) + set (LIBFIND_PACKAGE_ARGS ${ARGN}) + if (${PREFIX}_FIND_QUIETLY) + set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET) + endif (${PREFIX}_FIND_QUIETLY) + if (${PREFIX}_FIND_REQUIRED) + set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED) + endif (${PREFIX}_FIND_REQUIRED) + find_package(${LIBFIND_PACKAGE_ARGS}) +endmacro (libfind_package) + +# CMake developers made the UsePkgConfig system deprecated in the same release (2.6) +# where they added pkg_check_modules. Consequently I need to support both in my scripts +# to avoid those deprecated warnings. Here's a helper that does just that. +# Works identically to pkg_check_modules, except that no checks are needed prior to use. +macro (libfind_pkg_check_modules PREFIX PKGNAME) + if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + include(UsePkgConfig) + pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS) + else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${PREFIX} ${PKGNAME}) + endif (PKG_CONFIG_FOUND) + endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) +endmacro (libfind_pkg_check_modules) + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +macro (libfind_process PREFIX) + # Skip processing if already processed during this run + if (NOT ${PREFIX}_FOUND) + # Start with the assumption that the library was found + set (${PREFIX}_FOUND TRUE) + + # Process all includes and set _FOUND to false if any are missing + foreach (i ${${PREFIX}_PROCESS_INCLUDES}) + if (${i}) + set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}}) + mark_as_advanced(${i}) + else (${i}) + set (${PREFIX}_FOUND FALSE) + endif (${i}) + endforeach (i) + + # Process all libraries and set _FOUND to false if any are missing + foreach (i ${${PREFIX}_PROCESS_LIBS}) + if (${i}) + set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}}) + mark_as_advanced(${i}) + else (${i}) + set (${PREFIX}_FOUND FALSE) + endif (${i}) + endforeach (i) + + # Print message and/or exit on fatal error + if (${PREFIX}_FOUND) + if (NOT ${PREFIX}_FIND_QUIETLY) + message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + endif (NOT ${PREFIX}_FIND_QUIETLY) + else (${PREFIX}_FOUND) + if (${PREFIX}_FIND_REQUIRED) + foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS}) + message("${i}=${${i}}") + endforeach (i) + message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.") + endif (${PREFIX}_FIND_REQUIRED) + endif (${PREFIX}_FOUND) + endif (NOT ${PREFIX}_FOUND) +endmacro (libfind_process) + +macro(libfind_library PREFIX basename) + set(TMP "") + if(MSVC80) + set(TMP -vc80) + endif(MSVC80) + if(MSVC90) + set(TMP -vc90) + endif(MSVC90) + set(${PREFIX}_LIBNAMES ${basename}${TMP}) + if(${ARGC} GREATER 2) + set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2}) + string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES}) + set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP}) + endif(${ARGC} GREATER 2) + find_library(${PREFIX}_LIBRARY + NAMES ${${PREFIX}_LIBNAMES} + PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS} + ) +endmacro(libfind_library) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1bb413 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +SimpleAmqpClient +================== + +[SimpleAmqpClient](https://github.com/alanxz/SimpleAmqpClient) is an easy-to-use C++ +wrapper around the [rabbitmq-c](https://github.com/alanxz/rabbitmq-c) C library. +It derives inspiration from the [puka](https://github.com/majek/puka) AMQP library +in that it abstracts away the underlying AMQP wire concept of channels and uses them +as an error/consumer scope. This should make writing simple single-threaded AMQP-enabled apps easy. + +Installing +---------------- + +Known to work in the following environments: +- Windows 10 (MSVC 2019, Win64) +- Windows 7 (MSVC 10, Win64, Win32). Likely to work in others, but has not been tested +- Linux (RHEL 6.0, GCC-4.4.5, 32 and 64 bit). Likely to work on other configurations, but has not been tested +- Mac OS X (10.7, 10.6, gcc-4.2, 32 and 64-bit). Likely to work on older version, but has not been tested + +### Pre-requisites ++ [boost-1.47.0](http://www.boost.org/) or newer (uses chrono, system internally in addition to other header based libraries such as sharedptr and noncopyable) ++ [rabbitmq-c](http://github.com/alanxz/rabbitmq-c) you'll need version 0.8.0 or better. ++ [cmake 3.5+](http://www.cmake.org/) what is needed for the build system ++ [Doxygen](http://www.stack.nl/~dimitri/doxygen/) OPTIONAL only necessary to generate API documentation + +### Build procedure +This is a typical cmake project, it should work like most typical cmake projects: + +In a sibling directory to where you extracted the source code: + + mkdir simpleamqpclient-build + cd simpleamqpclient-build + cmake .. + +Then use your the appropriate build utility to build the library (make, msbuild) + +Interesting targets ++ test - will build and run the tests ++ install - will install the library and headers to whatever `CMAKE_INSTALL_PREFIX` is defined to ++ doc - will generate API documentation if you have doxygen setup + +Notes: ++ The test google-test based test suite can be enabled by passing `-DENABLE_TESTING=ON` to + cmake + +### Build procedure for Windows + +Boost libraries are needed, so you can install them using nuget: +``` +nuget install boost_chrono-vc142 -Version 1.77.0 +nuget install boost_system-vc142 -Version 1.77.0 +nuget install boost -Version 1.77.0 +``` +To build and install succesfully, [rabbitmq-c](https://github.com/alanxz/rabbitmq-c) should be built **as shared library**. + +Let *boost_chrono* and *boost_system* be in same directory ```C:\boost```, [rabbitmq-c](https://github.com/alanxz/rabbitmq-c) be on ```C:\rabbitmq-c```, +SSL be OFF, and VS2019 is used, than CMake CLI is: +``` +cd cmake -G "Visual Studio 16" -A x64 -DBoost_INCLUDE_DIR="C:/boost.XX.XX.X.X/lib/native/include" -DBOOST_ROOT="C:/boost.X.XX.X.X" -DBOOST_LIBRARYDIR="C:/boost" -DRabbitmqc_INCLUDE_DIR="C:/rabbitmq-c/include" -DRabbitmqc_LIBRARY="C:/rabbitmq-c/lib/rabbitmq.4.lib" -DBoost_USE_STATIC_LIBS=ON -DBUILD_STATIC_LIBS=ON -DENABLE_SSL_SUPPORT=OFF .. +``` + +Using the library +----------------- + + #include + +Will include all the headers necessary to use the library. +The corresponding library is SimpleAmqpClient + +The main interface to the library is the AmqpClient::Channel class. It represents +a connection to an AMQP broker, the connection is established on construction of an +instance of this class. + + AmqpClient::Channel::ptr_t connection = AmqpClient::Channel::Create("localhost"); + +All classes have a typedef ptr_t which is equivalent to boost::shared_ptr<> of the +containing class. All classes also have a Create() method does the job creating a new +ptr_t using boost::make_shared<>(). It is recommended that you use these methods +to construct objects in the library. + +Commands dealing with declaring/binding/unbinding/deleting exchanges and queues are +all done with the above AmqpClient::Channel object. If one of these commands +fails to complete a AmqpClient::ChannelException will be thrown, which can be caught +and the AmqpClient::Channel object is still useable. If a more severe error occurs +a AmqpClient::ConnectionException or AmqpClient::AmqpResponseLibraryException maybe +thrown, in which case the Channel object is no longer in a usable state and further +use will only generate more exceptions. + +Consuming messages is done by setting up a consumer using the BasicConsume method. +This method returns a consumer tag that should be used with the BasicConsumeMessage +BasicQos, BasicRecover, and BasicCancel. + + std::string consumer_tag = channel->BasicConsume("my_queue", ""); + Envelope::ptr_t envelope = channel->BasicConsumeMessage(consumer_tag); + // Alternatively: + Envelope::ptr_t envelope; + channel->BasicConsumeMessage(consumer_tag, envelope, 10); // 10 ms timeout + // To ack: + channel->BasicAck(envelope); + // To cancel: + channel->BasicCancel(consumer_tag); + diff --git a/libSimpleAmqpClient.pc.in b/libSimpleAmqpClient.pc.in new file mode 100644 index 0000000..272c2ab --- /dev/null +++ b/libSimpleAmqpClient.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: SimpleAmqpClient +Description: C++ wrapper of rabbitmq-c AMQP client library +Version: @SAC_APIVERSION@ +Requires.private: librabbitmq +Libs: -L${libdir} -l@SIMPLEAMQPCLIENT_LIB@ @libs_public@ +Libs.private: @libs_private@ +CFlags: -I${includedir} -I"@Boost_INCLUDE_DIRS@" @SIMPLEAMQPCLIENT_DEFINITIONS@ diff --git a/src/AmqpException.cpp b/src/AmqpException.cpp new file mode 100644 index 0000000..c1998cf --- /dev/null +++ b/src/AmqpException.cpp @@ -0,0 +1,201 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "SimpleAmqpClient/AmqpException.h" + +#include +#include +#include + +#include + +namespace AmqpClient { + +const boost::uint16_t ContentTooLargeException::REPLY_CODE = + AMQP_CONTENT_TOO_LARGE; +const boost::uint16_t NoRouteException::REPLY_CODE = AMQP_NO_ROUTE; +const boost::uint16_t NoConsumersException::REPLY_CODE = AMQP_NO_CONSUMERS; +const boost::uint16_t AccessRefusedException::REPLY_CODE = AMQP_ACCESS_REFUSED; +const boost::uint16_t NotFoundException::REPLY_CODE = AMQP_NOT_FOUND; +const boost::uint16_t ResourceLockedException::REPLY_CODE = + AMQP_RESOURCE_LOCKED; +const boost::uint16_t PreconditionFailedException::REPLY_CODE = + AMQP_PRECONDITION_FAILED; +const boost::uint16_t ConnectionForcedException::REPLY_CODE = + AMQP_CONNECTION_FORCED; +const boost::uint16_t InvalidPathException::REPLY_CODE = AMQP_INVALID_PATH; +const boost::uint16_t FrameErrorException::REPLY_CODE = AMQP_FRAME_ERROR; +const boost::uint16_t SyntaxErrorException::REPLY_CODE = AMQP_SYNTAX_ERROR; +const boost::uint16_t CommandInvalidException::REPLY_CODE = + AMQP_COMMAND_INVALID; +const boost::uint16_t ChannelErrorException::REPLY_CODE = AMQP_CHANNEL_ERROR; +const boost::uint16_t UnexpectedFrameException::REPLY_CODE = + AMQP_UNEXPECTED_FRAME; +const boost::uint16_t ResourceErrorException::REPLY_CODE = AMQP_RESOURCE_ERROR; +const boost::uint16_t NotAllowedException::REPLY_CODE = AMQP_NOT_ALLOWED; +const boost::uint16_t NotImplementedException::REPLY_CODE = + AMQP_NOT_IMPLEMENTED; +const boost::uint16_t InternalErrorException::REPLY_CODE = AMQP_INTERNAL_ERROR; + +void AmqpException::Throw(const amqp_rpc_reply_t &reply) { + assert(reply.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION); + + switch (reply.reply.id) { + case AMQP_CONNECTION_CLOSE_METHOD: + Throw( + *(reinterpret_cast(reply.reply.decoded))); + break; + case AMQP_CHANNEL_CLOSE_METHOD: + Throw(*(reinterpret_cast(reply.reply.decoded))); + break; + default: + throw std::logic_error( + std::string( + "Programming error: unknown server exception class/method") + .append(boost::lexical_cast(reply.reply.id))); + } +} + +void AmqpException::Throw(const amqp_channel_close_t &reply) { + std::ostringstream what; + + std::string reply_text; + if (reply.reply_text.bytes != NULL) { + reply_text = + std::string((char *)reply.reply_text.bytes, reply.reply_text.len); + } + + const char *method_name = + amqp_method_name(((reply.class_id << 16) | reply.method_id)); + if (method_name != NULL) { + what << "channel error: " << reply.reply_code << ": " << method_name + << " caused: " << reply_text; + } else { + what << "channel error: " << reply.reply_code << ": " << reply_text; + } + + switch (reply.reply_code) { + case ContentTooLargeException::REPLY_CODE: + throw ContentTooLargeException(what.str(), reply_text, reply.class_id, + reply.method_id); + case NoRouteException::REPLY_CODE: + throw NoRouteException(what.str(), reply_text, reply.class_id, + reply.method_id); + case NoConsumersException::REPLY_CODE: + throw NoConsumersException(what.str(), reply_text, reply.class_id, + reply.method_id); + case AccessRefusedException::REPLY_CODE: + throw AccessRefusedException(what.str(), reply_text, reply.class_id, + reply.method_id); + case NotFoundException::REPLY_CODE: + throw NotFoundException(what.str(), reply_text, reply.class_id, + reply.method_id); + case ResourceLockedException::REPLY_CODE: + throw ResourceLockedException(what.str(), reply_text, reply.class_id, + reply.method_id); + case PreconditionFailedException::REPLY_CODE: + throw PreconditionFailedException(what.str(), reply_text, reply.class_id, + reply.method_id); + default: + throw std::logic_error( + std::string("Programming error: unknown channel reply code: ") + .append(boost::lexical_cast(reply.reply_code))); + } +} + +void AmqpException::Throw(const amqp_connection_close_t &reply) { + std::ostringstream what; + const char *method_name = + amqp_method_name(((reply.class_id << 16) | reply.method_id)); + + std::string reply_text; + if (reply.reply_text.bytes != NULL) { + reply_text = + std::string((char *)reply.reply_text.bytes, reply.reply_text.len); + } + + if (method_name != NULL) { + what << "connection error: " << reply.reply_code << ": " << method_name + << " caused: " << reply_text; + } else { + what << "connection error: " << reply.reply_code << ": " << reply_text; + } + + switch (reply.reply_code) { + case ConnectionForcedException::REPLY_CODE: + throw ConnectionForcedException(what.str(), reply_text, reply.class_id, + reply.method_id); + case InvalidPathException::REPLY_CODE: + throw InvalidPathException(what.str(), reply_text, reply.class_id, + reply.method_id); + case FrameErrorException::REPLY_CODE: + throw FrameErrorException(what.str(), reply_text, reply.class_id, + reply.method_id); + case SyntaxErrorException::REPLY_CODE: + throw SyntaxErrorException(what.str(), reply_text, reply.class_id, + reply.method_id); + case CommandInvalidException::REPLY_CODE: + throw CommandInvalidException(what.str(), reply_text, reply.class_id, + reply.method_id); + case ChannelErrorException::REPLY_CODE: + throw ChannelErrorException(what.str(), reply_text, reply.class_id, + reply.method_id); + case UnexpectedFrameException::REPLY_CODE: + throw UnexpectedFrameException(what.str(), reply_text, reply.class_id, + reply.method_id); + case ResourceErrorException::REPLY_CODE: + throw ResourceErrorException(what.str(), reply_text, reply.class_id, + reply.method_id); + case NotAllowedException::REPLY_CODE: + throw NotAllowedException(what.str(), reply_text, reply.class_id, + reply.method_id); + case NotImplementedException::REPLY_CODE: + throw NotImplementedException(what.str(), reply_text, reply.class_id, + reply.method_id); + case InternalErrorException::REPLY_CODE: + throw InternalErrorException(what.str(), reply_text, reply.class_id, + reply.method_id); + case AccessRefusedException::REPLY_CODE: + throw AccessRefusedException(what.str(), reply_text, reply.class_id, + reply.method_id); + default: + throw std::logic_error( + std::string("Programming error: unknown connection reply code: ") + .append(boost::lexical_cast(reply.reply_code))); + } +} + +AmqpException::AmqpException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : std::runtime_error(what), + m_reply_text(reply_text), + m_class_id(class_id), + m_method_id(method_id) {} +} // namespace AmqpClient diff --git a/src/AmqpLibraryException.cpp b/src/AmqpLibraryException.cpp new file mode 100644 index 0000000..964c145 --- /dev/null +++ b/src/AmqpLibraryException.cpp @@ -0,0 +1,56 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +// Put these first to avoid warnings about INT#_C macro redefinition +#include "SimpleAmqpClient/AmqpLibraryException.h" + +#include +#include + +namespace AmqpClient { + +AmqpLibraryException AmqpLibraryException::CreateException(int error_code) { + std::string message(amqp_error_string2(error_code)); + + return AmqpLibraryException(message, error_code); +} + +AmqpLibraryException AmqpLibraryException::CreateException( + int error_code, const std::string &context) { + std::string message(context); + message.append(": "); + message.append(amqp_error_string2(error_code)); + + return AmqpLibraryException(message, error_code); +} + +AmqpLibraryException::AmqpLibraryException(const std::string &message, + int error_code) throw() + : std::runtime_error(message), m_errorCode(error_code) {} + +} // namespace AmqpClient diff --git a/src/AmqpResponseLibraryException.cpp b/src/AmqpResponseLibraryException.cpp new file mode 100644 index 0000000..2d2bbff --- /dev/null +++ b/src/AmqpResponseLibraryException.cpp @@ -0,0 +1,50 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +// Put these first to avoid warnings about INT#_C macro redefinition +#include "SimpleAmqpClient/AmqpResponseLibraryException.h" + +#include +#include + +namespace AmqpClient { + +AmqpResponseLibraryException AmqpResponseLibraryException::CreateException( + const amqp_rpc_reply_t_ &reply, const std::string &context) { + std::string message(context); + message.append(": "); + message.append(amqp_error_string2(reply.library_error)); + + return AmqpResponseLibraryException(message); +} + +AmqpResponseLibraryException::AmqpResponseLibraryException( + const std::string &message) throw() + : std::runtime_error(message) {} + +} // namespace AmqpClient diff --git a/src/BasicMessage.cpp b/src/BasicMessage.cpp new file mode 100644 index 0000000..bb64c19 --- /dev/null +++ b/src/BasicMessage.cpp @@ -0,0 +1,315 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +// Put these first to avoid warnings about INT#_C macro redefinition +#include "SimpleAmqpClient/BasicMessage.h" + +#include +#include + +#include +#include +#include + +#include "SimpleAmqpClient/TableImpl.h" + +namespace AmqpClient { + +struct BasicMessage::Impl { + std::string body; + boost::optional content_type; + boost::optional content_encoding; + boost::optional delivery_mode; + boost::optional priority; + boost::optional correlation_id; + boost::optional reply_to; + boost::optional expiration; + boost::optional message_id; + boost::optional timestamp; + boost::optional type; + boost::optional user_id; + boost::optional app_id; + boost::optional cluster_id; + boost::optional header_table; +}; + +BasicMessage::BasicMessage() : m_impl(new Impl) {} + +BasicMessage::BasicMessage(const std::string& body) : m_impl(new Impl) { + Body(body); +} + +BasicMessage::~BasicMessage() {} + +const std::string& BasicMessage::Body() const { return m_impl->body; } + +std::string& BasicMessage::Body() { return m_impl->body; } + +void BasicMessage::Body(const std::string& body) { m_impl->body = body; } + +const std::string& BasicMessage::ContentType() const { + if (ContentTypeIsSet()) { + return m_impl->content_type.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::ContentType(const std::string& content_type) { + m_impl->content_type = content_type; +} + +bool BasicMessage::ContentTypeIsSet() const { + return m_impl->content_type.is_initialized(); +} + +void BasicMessage::ContentTypeClear() { m_impl->content_type.reset(); } + +const std::string& BasicMessage::ContentEncoding() const { + if (ContentEncodingIsSet()) { + return m_impl->content_encoding.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::ContentEncoding(const std::string& content_encoding) { + m_impl->content_encoding = content_encoding; +} + +bool BasicMessage::ContentEncodingIsSet() const { + return m_impl->content_encoding.is_initialized(); +} + +void BasicMessage::ContentEncodingClear() { m_impl->content_encoding.reset(); } + +BasicMessage::delivery_mode_t BasicMessage::DeliveryMode() const { + return m_impl->delivery_mode.value_or(dm_notset); +} + +void BasicMessage::DeliveryMode(delivery_mode_t delivery_mode) { + m_impl->delivery_mode = delivery_mode; +} + +bool BasicMessage::DeliveryModeIsSet() const { + return m_impl->delivery_mode.is_initialized(); +} + +void BasicMessage::DeliveryModeClear() { m_impl->delivery_mode.reset(); } + +boost::uint8_t BasicMessage::Priority() const { + return m_impl->priority.value_or(0); +} + +void BasicMessage::Priority(boost::uint8_t priority) { + m_impl->priority = priority; +} + +bool BasicMessage::PriorityIsSet() const { + return m_impl->priority.is_initialized(); +} + +void BasicMessage::PriorityClear() { m_impl->priority.reset(); } + +const std::string& BasicMessage::CorrelationId() const { + if (CorrelationIdIsSet()) { + return m_impl->correlation_id.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::CorrelationId(const std::string& correlation_id) { + m_impl->correlation_id = correlation_id; +} + +bool BasicMessage::CorrelationIdIsSet() const { + return m_impl->correlation_id.is_initialized(); +} + +void BasicMessage::CorrelationIdClear() { m_impl->correlation_id.reset(); } + +const std::string& BasicMessage::ReplyTo() const { + if (ReplyToIsSet()) { + return m_impl->reply_to.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::ReplyTo(const std::string& reply_to) { + m_impl->reply_to = reply_to; +} + +bool BasicMessage::ReplyToIsSet() const { + return m_impl->reply_to.is_initialized(); +} + +void BasicMessage::ReplyToClear() { m_impl->reply_to.reset(); } + +const std::string& BasicMessage::Expiration() const { + if (ExpirationIsSet()) { + return m_impl->expiration.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::Expiration(const std::string& expiration) { + m_impl->expiration = expiration; +} + +bool BasicMessage::ExpirationIsSet() const { + return m_impl->expiration.is_initialized(); +} + +void BasicMessage::ExpirationClear() { m_impl->expiration.reset(); } + +const std::string& BasicMessage::MessageId() const { + if (MessageIdIsSet()) { + return m_impl->message_id.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::MessageId(const std::string& message_id) { + m_impl->message_id = message_id; +} + +bool BasicMessage::MessageIdIsSet() const { + return m_impl->message_id.is_initialized(); +} + +void BasicMessage::MessageIdClear() { m_impl->message_id.reset(); } + +boost::uint64_t BasicMessage::Timestamp() const { + return m_impl->timestamp.value_or(0); +} +void BasicMessage::Timestamp(boost::uint64_t timestamp) { + m_impl->timestamp = timestamp; +} + +bool BasicMessage::TimestampIsSet() const { + return m_impl->timestamp.is_initialized(); +} + +void BasicMessage::TimestampClear() { m_impl->timestamp.reset(); } + +const std::string& BasicMessage::Type() const { + if (TypeIsSet()) { + return m_impl->type.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::Type(const std::string& type) { m_impl->type = type; } + +bool BasicMessage::TypeIsSet() const { return m_impl->type.is_initialized(); } + +void BasicMessage::TypeClear() { m_impl->type.reset(); } + +const std::string& BasicMessage::UserId() const { + if (UserIdIsSet()) { + return m_impl->user_id.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::UserId(const std::string& user_id) { + m_impl->user_id = user_id; +} + +bool BasicMessage::UserIdIsSet() const { + return m_impl->user_id.is_initialized(); +} + +void BasicMessage::UserIdClear() { m_impl->user_id.reset(); } + +const std::string& BasicMessage::AppId() const { + if (AppIdIsSet()) { + return m_impl->app_id.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::AppId(const std::string& app_id) { m_impl->app_id = app_id; } + +bool BasicMessage::AppIdIsSet() const { + return m_impl->app_id.is_initialized(); +} + +void BasicMessage::AppIdClear() { m_impl->app_id.reset(); } + +const std::string& BasicMessage::ClusterId() const { + if (ClusterIdIsSet()) { + return m_impl->cluster_id.get(); + } + static const std::string empty; + return empty; +} + +void BasicMessage::ClusterId(const std::string& cluster_id) { + m_impl->cluster_id = cluster_id; +} + +bool BasicMessage::ClusterIdIsSet() const { + return m_impl->cluster_id.is_initialized(); +} + +void BasicMessage::ClusterIdClear() { m_impl->cluster_id.reset(); } + +Table& BasicMessage::HeaderTable() { + if (!HeaderTableIsSet()) { + m_impl->header_table = Table(); + } + return m_impl->header_table.get(); +} + +const Table& BasicMessage::HeaderTable() const { + if (HeaderTableIsSet()) { + return m_impl->header_table.get(); + } + static const Table empty; + return empty; +} + +void BasicMessage::HeaderTable(const Table& header_table) { + m_impl->header_table = header_table; +} + +bool BasicMessage::HeaderTableIsSet() const { + return m_impl->header_table.is_initialized(); +} + +void BasicMessage::HeaderTableClear() { m_impl->header_table.reset(); } + +} // namespace AmqpClient diff --git a/src/Channel.cpp b/src/Channel.cpp new file mode 100644 index 0000000..0bbcd19 --- /dev/null +++ b/src/Channel.cpp @@ -0,0 +1,1086 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +// Put these first to avoid warnings about INT#_C macro redefinition +#include +#include +#include +#ifdef SAC_SSL_SUPPORT_ENABLED +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/AmqpException.h" +#include "SimpleAmqpClient/AmqpLibraryException.h" +#include "SimpleAmqpClient/AmqpResponseLibraryException.h" +#include "SimpleAmqpClient/BadUriException.h" +#include "SimpleAmqpClient/Bytes.h" +#include "SimpleAmqpClient/Channel.h" +#include "SimpleAmqpClient/ChannelImpl.h" +#include "SimpleAmqpClient/ConsumerCancelledException.h" +#include "SimpleAmqpClient/ConsumerTagNotFoundException.h" +#include "SimpleAmqpClient/MessageRejectedException.h" +#include "SimpleAmqpClient/MessageReturnedException.h" +#include "SimpleAmqpClient/TableImpl.h" +#include "SimpleAmqpClient/Util.h" + +namespace AmqpClient { + +namespace { + +amqp_basic_properties_t CreateAmqpProperties(const BasicMessage &mes, + Detail::amqp_pool_ptr_t &pool) { + amqp_basic_properties_t ret; + ret._flags = 0; + + if (mes.ContentTypeIsSet()) { + ret.content_type = StringToBytes(mes.ContentType()); + ret._flags |= AMQP_BASIC_CONTENT_TYPE_FLAG; + } + if (mes.ContentEncodingIsSet()) { + ret.content_encoding = StringToBytes(mes.ContentEncoding()); + ret._flags |= AMQP_BASIC_CONTENT_ENCODING_FLAG; + } + if (mes.DeliveryModeIsSet()) { + // TODO: something more advanced? + ret.delivery_mode = mes.DeliveryMode(); + ret._flags |= AMQP_BASIC_DELIVERY_MODE_FLAG; + } + if (mes.PriorityIsSet()) { + ret.priority = mes.Priority(); + ret._flags |= AMQP_BASIC_PRIORITY_FLAG; + } + if (mes.CorrelationIdIsSet()) { + ret.correlation_id = StringToBytes(mes.CorrelationId()); + ret._flags |= AMQP_BASIC_CORRELATION_ID_FLAG; + } + if (mes.ReplyToIsSet()) { + ret.reply_to = StringToBytes(mes.ReplyTo()); + ret._flags |= AMQP_BASIC_REPLY_TO_FLAG; + } + if (mes.ExpirationIsSet()) { + ret.expiration = StringToBytes(mes.Expiration()); + ret._flags |= AMQP_BASIC_EXPIRATION_FLAG; + } + if (mes.MessageIdIsSet()) { + ret.message_id = StringToBytes(mes.MessageId()); + ret._flags |= AMQP_BASIC_MESSAGE_ID_FLAG; + } + if (mes.TimestampIsSet()) { + ret.timestamp = mes.Timestamp(); + ret._flags |= AMQP_BASIC_TIMESTAMP_FLAG; + } + if (mes.TypeIsSet()) { + ret.type = StringToBytes(mes.Type()); + ret._flags |= AMQP_BASIC_TYPE_FLAG; + } + if (mes.UserIdIsSet()) { + ret.user_id = StringToBytes(mes.UserId()); + ret._flags |= AMQP_BASIC_USER_ID_FLAG; + } + if (mes.AppIdIsSet()) { + ret.app_id = StringToBytes(mes.AppId()); + ret._flags |= AMQP_BASIC_APP_ID_FLAG; + } + if (mes.ClusterIdIsSet()) { + ret.cluster_id = StringToBytes(mes.ClusterId()); + ret._flags |= AMQP_BASIC_CLUSTER_ID_FLAG; + } + if (mes.HeaderTableIsSet()) { + ret.headers = + Detail::TableValueImpl::CreateAmqpTable(mes.HeaderTable(), pool); + ret._flags |= AMQP_BASIC_HEADERS_FLAG; + } + return ret; +} + +} // namespace + +const std::string Channel::EXCHANGE_TYPE_DIRECT("direct"); +const std::string Channel::EXCHANGE_TYPE_FANOUT("fanout"); +const std::string Channel::EXCHANGE_TYPE_TOPIC("topic"); + +Channel::OpenOpts Channel::OpenOpts::FromUri(const std::string &uri) { + amqp_connection_info info; + amqp_default_connection_info(&info); + + boost::shared_ptr uri_dup = + boost::shared_ptr(strdup(uri.c_str()), free); + + if (0 != amqp_parse_url(uri_dup.get(), &info)) { + throw BadUriException(); + } + + OpenOpts opts; + opts.host = info.host; + opts.vhost = info.vhost; + opts.port = info.port; + opts.auth = OpenOpts::BasicAuth(info.user, info.password); + if (info.ssl) { + opts.tls_params = OpenOpts::TLSParams(); + } + return opts; +} + +bool Channel::OpenOpts::BasicAuth::operator==(const BasicAuth &o) const { + return username == o.username && password == o.password; +} + +bool Channel::OpenOpts::ExternalSaslAuth::operator==( + const ExternalSaslAuth &o) const { + return identity == o.identity; +} + +bool Channel::OpenOpts::TLSParams::operator==(const TLSParams &o) const { + return client_key_path == o.client_key_path && + client_cert_path == o.client_cert_path && + ca_cert_path == o.ca_cert_path && + verify_hostname == o.verify_hostname && verify_peer == o.verify_peer; +} + +bool Channel::OpenOpts::operator==(const OpenOpts &o) const { + return host == o.host && vhost == o.vhost && port == o.port && + frame_max == o.frame_max && auth == o.auth && + tls_params == o.tls_params; +} + +Channel::ptr_t Channel::Open(const OpenOpts &opts) { + if (opts.host.empty()) { + throw std::runtime_error("opts.host is not specified, it is required"); + } + if (opts.vhost.empty()) { + throw std::runtime_error("opts.vhost is not specified, it is required"); + } + if (opts.port <= 0) { + throw std::runtime_error( + "opts.port is not valid, it must be a positive number"); + } + if (opts.auth.empty()) { + throw std::runtime_error("opts.auth is not specified, it is required"); + } + if (!opts.tls_params.is_initialized()) { + switch (opts.auth.which()) { + case 0: { + const OpenOpts::BasicAuth &auth = + boost::get(opts.auth); + return boost::make_shared( + OpenChannel(opts.host, opts.port, auth.username, auth.password, + opts.vhost, opts.frame_max, false)); + } + case 1: { + const OpenOpts::ExternalSaslAuth &auth = + boost::get(opts.auth); + return boost::make_shared( + OpenChannel(opts.host, opts.port, auth.identity, "", opts.vhost, + opts.frame_max, true)); + } + default: + throw std::logic_error("Unhandled auth type"); + } + } + switch (opts.auth.which()) { + case 0: { + const OpenOpts::BasicAuth &auth = + boost::get(opts.auth); + return boost::make_shared(OpenSecureChannel( + opts.host, opts.port, auth.username, auth.password, opts.vhost, + opts.frame_max, opts.tls_params.get(), false)); + } + case 1: { + const OpenOpts::ExternalSaslAuth &auth = + boost::get(opts.auth); + return boost::make_shared( + OpenSecureChannel(opts.host, opts.port, auth.identity, "", opts.vhost, + opts.frame_max, opts.tls_params.get(), true)); + } + default: + throw std::logic_error("Unhandled auth type"); + } +} + +Channel::ptr_t Channel::Create(const std::string &host, int port, + const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max) { + OpenOpts opts; + opts.host = host; + opts.vhost = vhost; + opts.port = port; + opts.frame_max = frame_max; + opts.auth = OpenOpts::BasicAuth(username, password); + return Open(opts); +} + +Channel::ptr_t Channel::CreateSaslExternal(const std::string &host, int port, + const std::string &identity, + const std::string &vhost, + int frame_max) { + OpenOpts opts; + opts.host = host; + opts.vhost = vhost; + opts.port = port; + opts.frame_max = frame_max; + opts.auth = OpenOpts::ExternalSaslAuth(identity); + return Open(opts); +} + +Channel::ptr_t Channel::CreateSecure(const std::string &path_to_ca_cert, + const std::string &host, + const std::string &path_to_client_key, + const std::string &path_to_client_cert, + int port, const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max, + bool verify_hostname_and_peer) { + OpenOpts::TLSParams params; + params.client_key_path = path_to_client_key; + params.client_cert_path = path_to_client_cert; + params.ca_cert_path = path_to_ca_cert; + params.verify_hostname = verify_hostname_and_peer; + params.verify_peer = verify_hostname_and_peer; + + OpenOpts opts; + opts.host = host; + opts.vhost = vhost; + opts.port = port; + opts.frame_max = frame_max; + opts.auth = OpenOpts::BasicAuth(username, password); + opts.tls_params = params; + + return Open(opts); +} + +Channel::ptr_t Channel::CreateSecure(const std::string &path_to_ca_cert, + const std::string &host, + const std::string &path_to_client_key, + const std::string &path_to_client_cert, + int port, const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max, + bool verify_hostname, bool verify_peer) { + OpenOpts::TLSParams params; + params.client_key_path = path_to_client_key; + params.client_cert_path = path_to_client_cert; + params.ca_cert_path = path_to_ca_cert; + params.verify_hostname = verify_hostname; + params.verify_peer = verify_peer; + + OpenOpts opts; + opts.host = host; + opts.vhost = vhost; + opts.port = port; + opts.frame_max = frame_max; + opts.auth = OpenOpts::BasicAuth(username, password); + opts.tls_params = params; + + return Open(opts); +} + +Channel::ptr_t Channel::CreateSecureSaslExternal( + const std::string &path_to_ca_cert, const std::string &host, + const std::string &path_to_client_key, + const std::string &path_to_client_cert, int port, + const std::string &identity, const std::string &vhost, int frame_max, + bool verify_hostname, bool verify_peer) { + OpenOpts::TLSParams params; + params.client_key_path = path_to_client_key; + params.client_cert_path = path_to_client_cert; + params.ca_cert_path = path_to_ca_cert; + params.verify_hostname = verify_hostname; + params.verify_peer = verify_peer; + + OpenOpts opts; + opts.host = host; + opts.vhost = vhost; + opts.port = port; + opts.frame_max = frame_max; + opts.auth = OpenOpts::ExternalSaslAuth(identity); + opts.tls_params = params; + + return Open(opts); +} + +Channel::ptr_t Channel::CreateFromUri(const std::string &uri, int frame_max) { + OpenOpts opts = OpenOpts::FromUri(uri); + if (opts.tls_params.is_initialized()) { + throw std::runtime_error( + "CreateFromUri only supports non-SSL-enabled URIs"); + } + opts.frame_max = frame_max; + return Open(opts); +} + +Channel::ptr_t Channel::CreateSecureFromUri( + const std::string &uri, const std::string &path_to_ca_cert, + const std::string &path_to_client_key, + const std::string &path_to_client_cert, bool verify_hostname_and_peer, + int frame_max) { + OpenOpts opts = OpenOpts::FromUri(uri); + if (!opts.tls_params.is_initialized()) { + throw std::runtime_error( + "CreateSecureFromUri only supports SSL-enabled URIs"); + } + + OpenOpts::TLSParams params; + opts.tls_params->client_key_path = path_to_client_key; + opts.tls_params->client_cert_path = path_to_client_cert; + opts.tls_params->ca_cert_path = path_to_ca_cert; + opts.tls_params->verify_hostname = verify_hostname_and_peer; + opts.tls_params->verify_peer = verify_hostname_and_peer; + opts.frame_max = frame_max; + + return Open(opts); +} + +Channel::ChannelImpl *Channel::OpenChannel(const std::string &host, int port, + const std::string &username, + const std::string &password, + const std::string &vhost, + int frame_max, bool sasl_external) { + ChannelImpl *impl = new ChannelImpl; + impl->m_connection = amqp_new_connection(); + + if (NULL == impl->m_connection) { + throw std::bad_alloc(); + } + + try { + amqp_socket_t *socket = amqp_tcp_socket_new(impl->m_connection); + int sock = amqp_socket_open(socket, host.c_str(), port); + impl->CheckForError(sock); + + impl->DoLogin(username, password, vhost, frame_max, sasl_external); + } catch (...) { + amqp_destroy_connection(impl->m_connection); + delete impl; + throw; + } + + impl->SetIsConnected(true); + return impl; +} + +#ifdef SAC_SSL_SUPPORT_ENABLED +Channel::ChannelImpl *Channel::OpenSecureChannel( + const std::string &host, int port, const std::string &username, + const std::string &password, const std::string &vhost, int frame_max, + const OpenOpts::TLSParams &tls_params, bool sasl_external) { + Channel::ChannelImpl *impl = new ChannelImpl; + impl->m_connection = amqp_new_connection(); + if (NULL == impl->m_connection) { + throw std::bad_alloc(); + } + + amqp_socket_t *socket = amqp_ssl_socket_new(impl->m_connection); + if (NULL == socket) { + throw std::bad_alloc(); + } +#if AMQP_VERSION >= 0x00080001 + amqp_ssl_socket_set_verify_peer(socket, tls_params.verify_peer); + amqp_ssl_socket_set_verify_hostname(socket, tls_params.verify_hostname); +#else + amqp_ssl_socket_set_verify(socket, tls_params.verify_hostname); +#endif + + try { + int status; + if (tls_params.ca_cert_path != "") { + status = + amqp_ssl_socket_set_cacert(socket, tls_params.ca_cert_path.c_str()); + if (status) { + throw AmqpLibraryException::CreateException( + status, "Error setting CA certificate for socket"); + } + } + + if (tls_params.client_key_path != "" && tls_params.client_cert_path != "") { + status = + amqp_ssl_socket_set_key(socket, tls_params.client_cert_path.c_str(), + tls_params.client_key_path.c_str()); + if (status) { + throw AmqpLibraryException::CreateException( + status, "Error setting client certificate for socket"); + } + } + + status = amqp_socket_open(socket, host.c_str(), port); + if (status) { + throw AmqpLibraryException::CreateException( + status, "Error setting client certificate for socket"); + } + + impl->DoLogin(username, password, vhost, frame_max, sasl_external); + } catch (...) { + amqp_destroy_connection(impl->m_connection); + delete impl; + throw; + } + + impl->SetIsConnected(true); + return impl; +} +#else +Channel::ChannelImpl *Channel::OpenSecureChannel( + const std::string &, int, const std::string &, const std::string &, + const std::string &, int, const OpenOpts::TLSParams &, bool) { + throw std::logic_error( + "SSL support has not been compiled into SimpleAmqpClient"); +} +#endif + +Channel::Channel(ChannelImpl *impl) : m_impl(impl) {} + +Channel::~Channel() { + amqp_connection_close(m_impl->m_connection, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(m_impl->m_connection); +} + +int Channel::GetSocketFD() const { + return amqp_get_sockfd(m_impl->m_connection); +} + +bool Channel::CheckExchangeExists(boost::string_ref exchange_name) { + const boost::array DECLARE_OK = { + {AMQP_EXCHANGE_DECLARE_OK_METHOD}}; + + amqp_exchange_declare_t declare = {}; + declare.exchange = StringRefToBytes(exchange_name); + declare.passive = true; + declare.nowait = false; + + try { + amqp_frame_t frame = + m_impl->DoRpc(AMQP_EXCHANGE_DECLARE_METHOD, &declare, DECLARE_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); + } catch (const NotFoundException& e) { + return false; + } + return true; +} + +void Channel::DeclareExchange(const std::string &exchange_name, + const std::string &exchange_type, bool passive, + bool durable, bool auto_delete) { + DeclareExchange(exchange_name, exchange_type, passive, durable, auto_delete, + Table()); +} + +void Channel::DeclareExchange(const std::string &exchange_name, + const std::string &exchange_type, bool passive, + bool durable, bool auto_delete, + const Table &arguments) { + const boost::array DECLARE_OK = { + {AMQP_EXCHANGE_DECLARE_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_exchange_declare_t declare = {}; + declare.exchange = StringToBytes(exchange_name); + declare.type = StringToBytes(exchange_type); + declare.passive = passive; + declare.durable = durable; + declare.auto_delete = auto_delete; + declare.internal = false; + declare.nowait = false; + + Detail::amqp_pool_ptr_t table_pool; + declare.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t frame = + m_impl->DoRpc(AMQP_EXCHANGE_DECLARE_METHOD, &declare, DECLARE_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::DeleteExchange(const std::string &exchange_name, bool if_unused) { + const boost::array DELETE_OK = { + {AMQP_EXCHANGE_DELETE_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_exchange_delete_t del = {}; + del.exchange = StringToBytes(exchange_name); + del.if_unused = if_unused; + del.nowait = false; + + amqp_frame_t frame = + m_impl->DoRpc(AMQP_EXCHANGE_DELETE_METHOD, &del, DELETE_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::BindExchange(const std::string &destination, + const std::string &source, + const std::string &routing_key) { + BindExchange(destination, source, routing_key, Table()); +} + +void Channel::BindExchange(const std::string &destination, + const std::string &source, + const std::string &routing_key, + const Table &arguments) { + const boost::array BIND_OK = { + {AMQP_EXCHANGE_BIND_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_exchange_bind_t bind = {}; + bind.destination = StringToBytes(destination); + bind.source = StringToBytes(source); + bind.routing_key = StringToBytes(routing_key); + bind.nowait = false; + + Detail::amqp_pool_ptr_t table_pool; + bind.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t frame = m_impl->DoRpc(AMQP_EXCHANGE_BIND_METHOD, &bind, BIND_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::UnbindExchange(const std::string &destination, + const std::string &source, + const std::string &routing_key) { + UnbindExchange(destination, source, routing_key, Table()); +} + +void Channel::UnbindExchange(const std::string &destination, + const std::string &source, + const std::string &routing_key, + const Table &arguments) { + const boost::array UNBIND_OK = { + {AMQP_EXCHANGE_UNBIND_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_exchange_unbind_t unbind = {}; + unbind.destination = StringToBytes(destination); + unbind.source = StringToBytes(source); + unbind.routing_key = StringToBytes(routing_key); + unbind.nowait = false; + + Detail::amqp_pool_ptr_t table_pool; + unbind.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t frame = + m_impl->DoRpc(AMQP_EXCHANGE_UNBIND_METHOD, &unbind, UNBIND_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +bool Channel::CheckQueueExists(boost::string_ref queue_name) { + const boost::array DECLARE_OK = { + {AMQP_QUEUE_DECLARE_OK_METHOD}}; + + amqp_queue_declare_t declare = {}; + declare.queue = StringRefToBytes(queue_name); + declare.passive = true; + declare.nowait = false; + + try { + amqp_frame_t frame = + m_impl->DoRpc(AMQP_QUEUE_DECLARE_METHOD, &declare, DECLARE_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); + } catch (const NotFoundException& e) { + return false; + } + return true; +} + +std::string Channel::DeclareQueue(const std::string &queue_name, bool passive, + bool durable, bool exclusive, + bool auto_delete) { + return DeclareQueue(queue_name, passive, durable, exclusive, auto_delete, + Table()); +} + +std::string Channel::DeclareQueue(const std::string &queue_name, bool passive, + bool durable, bool exclusive, + bool auto_delete, const Table &arguments) { + boost::uint32_t message_count; + boost::uint32_t consumer_count; + return DeclareQueueWithCounts(queue_name, message_count, consumer_count, + passive, durable, exclusive, auto_delete, + arguments); +} + +std::string Channel::DeclareQueueWithCounts(const std::string &queue_name, + boost::uint32_t &message_count, + boost::uint32_t &consumer_count, + bool passive, bool durable, + bool exclusive, bool auto_delete) { + return DeclareQueueWithCounts(queue_name, message_count, consumer_count, + passive, durable, exclusive, auto_delete, + Table()); +} + +std::string Channel::DeclareQueueWithCounts(const std::string &queue_name, + boost::uint32_t &message_count, + boost::uint32_t &consumer_count, + bool passive, bool durable, + bool exclusive, bool auto_delete, + const Table &arguments) { + const boost::array DECLARE_OK = { + {AMQP_QUEUE_DECLARE_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_queue_declare_t declare = {}; + declare.queue = StringToBytes(queue_name); + declare.passive = passive; + declare.durable = durable; + declare.exclusive = exclusive; + declare.auto_delete = auto_delete; + declare.nowait = false; + + Detail::amqp_pool_ptr_t table_pool; + declare.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t response = + m_impl->DoRpc(AMQP_QUEUE_DECLARE_METHOD, &declare, DECLARE_OK); + + amqp_queue_declare_ok_t *declare_ok = + (amqp_queue_declare_ok_t *)response.payload.method.decoded; + + std::string ret((char *)declare_ok->queue.bytes, declare_ok->queue.len); + + message_count = declare_ok->message_count; + consumer_count = declare_ok->consumer_count; + + m_impl->MaybeReleaseBuffersOnChannel(response.channel); + return ret; +} + +void Channel::DeleteQueue(const std::string &queue_name, bool if_unused, + bool if_empty) { + const boost::array DELETE_OK = { + {AMQP_QUEUE_DELETE_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_queue_delete_t del = {}; + del.queue = StringToBytes(queue_name); + del.if_unused = if_unused; + del.if_empty = if_empty; + del.nowait = false; + + amqp_frame_t frame = m_impl->DoRpc(AMQP_QUEUE_DELETE_METHOD, &del, DELETE_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::BindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key) { + BindQueue(queue_name, exchange_name, routing_key, Table()); +} + +void Channel::BindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key, + const Table &arguments) { + const boost::array BIND_OK = { + {AMQP_QUEUE_BIND_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_queue_bind_t bind = {}; + bind.queue = StringToBytes(queue_name); + bind.exchange = StringToBytes(exchange_name); + bind.routing_key = StringToBytes(routing_key); + bind.nowait = false; + + Detail::amqp_pool_ptr_t table_pool; + bind.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t frame = m_impl->DoRpc(AMQP_QUEUE_BIND_METHOD, &bind, BIND_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::UnbindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key) { + UnbindQueue(queue_name, exchange_name, routing_key, Table()); +} + +void Channel::UnbindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key, + const Table &arguments) { + const boost::array UNBIND_OK = { + {AMQP_QUEUE_UNBIND_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_queue_unbind_t unbind = {}; + unbind.queue = StringToBytes(queue_name); + unbind.exchange = StringToBytes(exchange_name); + unbind.routing_key = StringToBytes(routing_key); + + Detail::amqp_pool_ptr_t table_pool; + unbind.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t frame = + m_impl->DoRpc(AMQP_QUEUE_UNBIND_METHOD, &unbind, UNBIND_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::PurgeQueue(const std::string &queue_name) { + const boost::array PURGE_OK = { + {AMQP_QUEUE_PURGE_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_queue_purge_t purge = {}; + purge.queue = StringToBytes(queue_name); + purge.nowait = false; + + amqp_frame_t frame = m_impl->DoRpc(AMQP_QUEUE_PURGE_METHOD, &purge, PURGE_OK); + m_impl->MaybeReleaseBuffersOnChannel(frame.channel); +} + +void Channel::BasicAck(const Envelope::ptr_t &message) { + BasicAck(message->GetDeliveryInfo()); +} + +void Channel::BasicAck(const Envelope::DeliveryInfo &info) { + BasicAck(info, false); +} + +void Channel::BasicAck(const Envelope::DeliveryInfo &info, bool multiple) { + m_impl->CheckIsConnected(); + // Delivery tag is local to the channel, so its important to use + // that channel, sadly this can cause the channel to throw an exception + // which will show up as an unrelated exception in a different method + // that actually waits for a response from the broker + amqp_channel_t channel = info.delivery_channel; + if (!m_impl->IsChannelOpen(channel)) { + throw std::runtime_error( + "The channel that the message was delivered on has been closed"); + } + + m_impl->CheckForError(amqp_basic_ack(m_impl->m_connection, channel, + info.delivery_tag, multiple)); +} + +void Channel::BasicReject(const Envelope::ptr_t &message, bool requeue, + bool multiple) { + BasicReject(message->GetDeliveryInfo(), requeue, multiple); +} + +void Channel::BasicReject(const Envelope::DeliveryInfo &info, bool requeue, + bool multiple) { + m_impl->CheckIsConnected(); + // Delivery tag is local to the channel, so its important to use + // that channel, sadly this can cause the channel to throw an exception + // which will show up as an unrelated exception in a different method + // that actually waits for a response from the broker + amqp_channel_t channel = info.delivery_channel; + if (!m_impl->IsChannelOpen(channel)) { + throw std::runtime_error( + "The channel that the message was delivered on has been closed"); + } + amqp_basic_nack_t req; + req.delivery_tag = info.delivery_tag; + req.multiple = multiple; + req.requeue = requeue; + + m_impl->CheckForError(amqp_send_method(m_impl->m_connection, channel, + AMQP_BASIC_NACK_METHOD, &req)); +} + +void Channel::BasicPublish(const std::string &exchange_name, + const std::string &routing_key, + const BasicMessage::ptr_t message, bool mandatory, + bool immediate) { + m_impl->CheckIsConnected(); + amqp_channel_t channel = m_impl->GetChannel(); + + Detail::amqp_pool_ptr_t pool; + amqp_basic_properties_t properties = CreateAmqpProperties(*message, pool); + + m_impl->CheckForError(amqp_basic_publish( + m_impl->m_connection, channel, StringToBytes(exchange_name), + StringToBytes(routing_key), mandatory, immediate, &properties, + StringToBytes(message->Body()))); + + // If we've done things correctly we can get one of 4 things back from the + // broker + // - basic.ack - our channel is in confirm mode, messsage was 'dealt with' by + // the broker + // - basic.nack - our channel is in confirm mode, queue has max-length set and + // is full, queue overflow stratege is reject-publish + // - basic.return then basic.ack - the message wasn't delievered, but was + // dealt with + // - channel.close - probably tried to publish to a non-existant exchange, in + // any case error! + // - connection.clsoe - something really bad happened + const boost::array PUBLISH_ACK = { + {AMQP_BASIC_ACK_METHOD, AMQP_BASIC_RETURN_METHOD, + AMQP_BASIC_NACK_METHOD}}; + amqp_frame_t response; + boost::array channels = {{channel}}; + m_impl->GetMethodOnChannel(channels, response, PUBLISH_ACK); + + if (AMQP_BASIC_NACK_METHOD == response.payload.method.id) { + amqp_basic_nack_t *return_method = + reinterpret_cast(response.payload.method.decoded); + MessageRejectedException message_rejected(return_method->delivery_tag); + m_impl->ReturnChannel(channel); + m_impl->MaybeReleaseBuffersOnChannel(channel); + throw message_rejected; + } + + if (AMQP_BASIC_RETURN_METHOD == response.payload.method.id) { + MessageReturnedException message_returned = + m_impl->CreateMessageReturnedException( + *(reinterpret_cast( + response.payload.method.decoded)), + channel); + + const boost::array BASIC_ACK = { + {AMQP_BASIC_ACK_METHOD}}; + m_impl->GetMethodOnChannel(channels, response, BASIC_ACK); + m_impl->ReturnChannel(channel); + m_impl->MaybeReleaseBuffersOnChannel(channel); + throw message_returned; + } + + m_impl->ReturnChannel(channel); + m_impl->MaybeReleaseBuffersOnChannel(channel); +} + +bool Channel::BasicGet(Envelope::ptr_t &envelope, const std::string &queue, + bool no_ack) { + const boost::array GET_RESPONSES = { + {AMQP_BASIC_GET_OK_METHOD, AMQP_BASIC_GET_EMPTY_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_basic_get_t get = {}; + get.queue = StringToBytes(queue); + get.no_ack = no_ack; + + amqp_channel_t channel = m_impl->GetChannel(); + amqp_frame_t response = m_impl->DoRpcOnChannel(channel, AMQP_BASIC_GET_METHOD, + &get, GET_RESPONSES); + + if (AMQP_BASIC_GET_EMPTY_METHOD == response.payload.method.id) { + m_impl->ReturnChannel(channel); + m_impl->MaybeReleaseBuffersOnChannel(channel); + return false; + } + + amqp_basic_get_ok_t *get_ok = + (amqp_basic_get_ok_t *)response.payload.method.decoded; + boost::uint64_t delivery_tag = get_ok->delivery_tag; + bool redelivered = (get_ok->redelivered == 0 ? false : true); + std::string exchange((char *)get_ok->exchange.bytes, get_ok->exchange.len); + std::string routing_key((char *)get_ok->routing_key.bytes, + get_ok->routing_key.len); + + BasicMessage::ptr_t message = m_impl->ReadContent(channel); + envelope = Envelope::Create(message, "", delivery_tag, exchange, redelivered, + routing_key, channel); + + m_impl->ReturnChannel(channel); + m_impl->MaybeReleaseBuffersOnChannel(channel); + return true; +} + +void Channel::BasicRecover(const std::string &consumer) { + const boost::array RECOVER_OK = { + {AMQP_BASIC_RECOVER_OK_METHOD}}; + m_impl->CheckIsConnected(); + + amqp_basic_recover_t recover = {}; + recover.requeue = true; + + amqp_channel_t channel = m_impl->GetConsumerChannel(consumer); + + m_impl->DoRpcOnChannel(channel, AMQP_BASIC_RECOVER_METHOD, &recover, + RECOVER_OK); + m_impl->MaybeReleaseBuffersOnChannel(channel); +} + +std::string Channel::BasicConsume(const std::string &queue, + const std::string &consumer_tag, + bool no_local, bool no_ack, bool exclusive, + boost::uint16_t message_prefetch_count) { + return BasicConsume(queue, consumer_tag, no_local, no_ack, exclusive, + message_prefetch_count, Table()); +} +std::string Channel::BasicConsume(const std::string &queue, + const std::string &consumer_tag, + bool no_local, bool no_ack, bool exclusive, + boost::uint16_t message_prefetch_count, + const Table &arguments) { + m_impl->CheckIsConnected(); + amqp_channel_t channel = m_impl->GetChannel(); + + // Set this before starting the consume as it may have been set by a previous + // consumer + const boost::array QOS_OK = {{AMQP_BASIC_QOS_OK_METHOD}}; + + amqp_basic_qos_t qos = {}; + qos.prefetch_size = 0; + qos.prefetch_count = message_prefetch_count; + qos.global = m_impl->BrokerHasNewQosBehavior(); + + m_impl->DoRpcOnChannel(channel, AMQP_BASIC_QOS_METHOD, &qos, QOS_OK); + m_impl->MaybeReleaseBuffersOnChannel(channel); + + const boost::array CONSUME_OK = { + {AMQP_BASIC_CONSUME_OK_METHOD}}; + + amqp_basic_consume_t consume = {}; + consume.queue = StringToBytes(queue); + consume.consumer_tag = StringToBytes(consumer_tag); + consume.no_local = no_local; + consume.no_ack = no_ack; + consume.exclusive = exclusive; + consume.nowait = false; + + Detail::amqp_pool_ptr_t table_pool; + consume.arguments = + Detail::TableValueImpl::CreateAmqpTable(arguments, table_pool); + + amqp_frame_t response = m_impl->DoRpcOnChannel( + channel, AMQP_BASIC_CONSUME_METHOD, &consume, CONSUME_OK); + + amqp_basic_consume_ok_t *consume_ok = + (amqp_basic_consume_ok_t *)response.payload.method.decoded; + std::string tag((char *)consume_ok->consumer_tag.bytes, + consume_ok->consumer_tag.len); + m_impl->MaybeReleaseBuffersOnChannel(channel); + + m_impl->AddConsumer(tag, channel); + + return tag; +} + +void Channel::BasicQos(const std::string &consumer_tag, + boost::uint16_t message_prefetch_count) { + m_impl->CheckIsConnected(); + amqp_channel_t channel = m_impl->GetConsumerChannel(consumer_tag); + + const boost::array QOS_OK = {{AMQP_BASIC_QOS_OK_METHOD}}; + + amqp_basic_qos_t qos = {}; + qos.prefetch_size = 0; + qos.prefetch_count = message_prefetch_count; + qos.global = m_impl->BrokerHasNewQosBehavior(); + + m_impl->DoRpcOnChannel(channel, AMQP_BASIC_QOS_METHOD, &qos, QOS_OK); + m_impl->MaybeReleaseBuffersOnChannel(channel); +} + +void Channel::BasicCancel(const std::string &consumer_tag) { + m_impl->CheckIsConnected(); + amqp_channel_t channel = m_impl->GetConsumerChannel(consumer_tag); + + const boost::array CANCEL_OK = { + {AMQP_BASIC_CANCEL_OK_METHOD}}; + + amqp_basic_cancel_t cancel = {}; + cancel.consumer_tag = StringToBytes(consumer_tag); + cancel.nowait = false; + + m_impl->DoRpcOnChannel(channel, AMQP_BASIC_CANCEL_METHOD, &cancel, CANCEL_OK); + + m_impl->RemoveConsumer(consumer_tag); + + // Lets go hunting to make sure we don't have any queued frames lying around + // Otherwise these frames will potentially hang around when we don't want them + // to + // TODO: Implement queue purge + m_impl->ReturnChannel(channel); + m_impl->MaybeReleaseBuffersOnChannel(channel); +} + +Envelope::ptr_t Channel::BasicConsumeMessage(const std::string &consumer_tag) { + Envelope::ptr_t returnval; + BasicConsumeMessage(consumer_tag, returnval); + return returnval; +} + +Envelope::ptr_t Channel::BasicConsumeMessage( + const std::vector &consumer_tags) { + Envelope::ptr_t returnval; + BasicConsumeMessage(consumer_tags, returnval); + return returnval; +} + +Envelope::ptr_t Channel::BasicConsumeMessage() { + Envelope::ptr_t returnval; + BasicConsumeMessage(returnval); + return returnval; +} + +bool Channel::BasicConsumeMessage(const std::string &consumer_tag, + Envelope::ptr_t &message, int timeout) { + m_impl->CheckIsConnected(); + amqp_channel_t channel = m_impl->GetConsumerChannel(consumer_tag); + + boost::array channels = {{channel}}; + + return m_impl->ConsumeMessageOnChannel(channels, message, timeout); +} + +bool Channel::BasicConsumeMessage(const std::vector &consumer_tags, + Envelope::ptr_t &message, int timeout) { + m_impl->CheckIsConnected(); + + std::vector channels; + channels.reserve(consumer_tags.size()); + + for (std::vector::const_iterator it = consumer_tags.begin(); + it != consumer_tags.end(); ++it) { + channels.push_back(m_impl->GetConsumerChannel(*it)); + } + + return m_impl->ConsumeMessageOnChannel(channels, message, timeout); +} + +bool Channel::BasicConsumeMessage(Envelope::ptr_t &message, int timeout) { + m_impl->CheckIsConnected(); + + std::vector channels = m_impl->GetAllConsumerChannels(); + + if (0 == channels.size()) { + throw ConsumerTagNotFoundException(); + } + + return m_impl->ConsumeMessageOnChannel(channels, message, timeout); +} + +} // namespace AmqpClient diff --git a/src/ChannelImpl.cpp b/src/ChannelImpl.cpp new file mode 100644 index 0000000..eb276ca --- /dev/null +++ b/src/ChannelImpl.cpp @@ -0,0 +1,571 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#ifdef _WIN32 +#define NOMINMAX +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#else +#include +#include +#endif + +#include +#include +#include + +#include "SimpleAmqpClient/AmqpException.h" +#include "SimpleAmqpClient/AmqpLibraryException.h" +#include "SimpleAmqpClient/AmqpResponseLibraryException.h" +#include "SimpleAmqpClient/ChannelImpl.h" +#include "SimpleAmqpClient/ConnectionClosedException.h" +#include "SimpleAmqpClient/ConsumerTagNotFoundException.h" +#include "SimpleAmqpClient/TableImpl.h" +#define BOOST_BIND_GLOBAL_PLACEHOLDERS +#include + +#include +#include + +#define BROKER_HEARTBEAT 0 + +namespace AmqpClient { + +namespace { + +std::string BytesToString(amqp_bytes_t bytes) { + return std::string(reinterpret_cast(bytes.bytes), bytes.len); +} + +void SetMessageProperties(BasicMessage &mes, + const amqp_basic_properties_t &props) { + if (0 != (props._flags & AMQP_BASIC_CONTENT_TYPE_FLAG)) { + mes.ContentType(BytesToString(props.content_type)); + } + if (0 != (props._flags & AMQP_BASIC_CONTENT_ENCODING_FLAG)) { + mes.ContentEncoding(BytesToString(props.content_encoding)); + } + if (0 != (props._flags & AMQP_BASIC_DELIVERY_MODE_FLAG)) { + mes.DeliveryMode( + static_cast(props.delivery_mode)); + } + if (0 != (props._flags & AMQP_BASIC_PRIORITY_FLAG)) { + mes.Priority(props.priority); + } + if (0 != (props._flags & AMQP_BASIC_CORRELATION_ID_FLAG)) { + mes.CorrelationId(BytesToString(props.correlation_id)); + } + if (0 != (props._flags & AMQP_BASIC_REPLY_TO_FLAG)) { + mes.ReplyTo(BytesToString(props.reply_to)); + } + if (0 != (props._flags & AMQP_BASIC_EXPIRATION_FLAG)) { + mes.Expiration(BytesToString(props.expiration)); + } + if (0 != (props._flags & AMQP_BASIC_MESSAGE_ID_FLAG)) { + mes.MessageId(BytesToString(props.message_id)); + } + if (0 != (props._flags & AMQP_BASIC_TIMESTAMP_FLAG)) { + mes.Timestamp(props.timestamp); + } + if (0 != (props._flags & AMQP_BASIC_TYPE_FLAG)) { + mes.Type(BytesToString(props.type)); + } + if (0 != (props._flags & AMQP_BASIC_USER_ID_FLAG)) { + mes.UserId(BytesToString(props.user_id)); + } + if (0 != (props._flags & AMQP_BASIC_APP_ID_FLAG)) { + mes.AppId(BytesToString(props.app_id)); + } + if (0 != (props._flags & AMQP_BASIC_CLUSTER_ID_FLAG)) { + mes.ClusterId(BytesToString(props.cluster_id)); + } + if (0 != (props._flags & AMQP_BASIC_HEADERS_FLAG)) { + mes.HeaderTable(Detail::TableValueImpl::CreateTable(props.headers)); + } +} +} // namespace + +Channel::ChannelImpl::ChannelImpl() + : m_last_used_channel(0), m_is_connected(false) { + m_channels.push_back(CS_Used); +} + +Channel::ChannelImpl::~ChannelImpl() {} + +void Channel::ChannelImpl::DoLogin(const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max, + bool sasl_external) { + amqp_table_entry_t capabilties[1]; + amqp_table_entry_t capability_entry; + amqp_table_t client_properties; + + capabilties[0].key = amqp_cstring_bytes("consumer_cancel_notify"); + capabilties[0].value.kind = AMQP_FIELD_KIND_BOOLEAN; + capabilties[0].value.value.boolean = 1; + + capability_entry.key = amqp_cstring_bytes("capabilities"); + capability_entry.value.kind = AMQP_FIELD_KIND_TABLE; + capability_entry.value.value.table.num_entries = + sizeof(capabilties) / sizeof(amqp_table_entry_t); + capability_entry.value.value.table.entries = capabilties; + + client_properties.num_entries = 1; + client_properties.entries = &capability_entry; + + if (sasl_external) { + CheckRpcReply(0, amqp_login_with_properties( + m_connection, vhost.c_str(), 0, frame_max, + BROKER_HEARTBEAT, &client_properties, + AMQP_SASL_METHOD_EXTERNAL, username.c_str())); + } else { + CheckRpcReply( + 0, amqp_login_with_properties(m_connection, vhost.c_str(), 0, frame_max, + BROKER_HEARTBEAT, &client_properties, + AMQP_SASL_METHOD_PLAIN, username.c_str(), + password.c_str())); + } + + m_brokerVersion = ComputeBrokerVersion(m_connection); +} + +amqp_channel_t Channel::ChannelImpl::GetNextChannelId() { + channel_state_list_t::iterator unused_channel = + std::find(m_channels.begin(), m_channels.end(), CS_Closed); + + if (m_channels.end() == unused_channel) { + int max_channels = amqp_get_channel_max(m_connection); + if (0 == max_channels) { + max_channels = std::numeric_limits::max(); + } + if (static_cast(max_channels) < m_channels.size()) { + throw std::runtime_error("Too many channels open"); + } + + m_channels.push_back(CS_Closed); + unused_channel = m_channels.end() - 1; + } + + return unused_channel - m_channels.begin(); +} + +amqp_channel_t Channel::ChannelImpl::CreateNewChannel() { + amqp_channel_t new_channel = GetNextChannelId(); + + static const boost::array OPEN_OK = { + {AMQP_CHANNEL_OPEN_OK_METHOD}}; + amqp_channel_open_t channel_open = {}; + DoRpcOnChannel >( + new_channel, AMQP_CHANNEL_OPEN_METHOD, &channel_open, OPEN_OK); + + static const boost::array CONFIRM_OK = { + {AMQP_CONFIRM_SELECT_OK_METHOD}}; + amqp_confirm_select_t confirm_select = {}; + DoRpcOnChannel >( + new_channel, AMQP_CONFIRM_SELECT_METHOD, &confirm_select, CONFIRM_OK); + + m_channels.at(new_channel) = CS_Open; + + return new_channel; +} + +amqp_channel_t Channel::ChannelImpl::GetChannel() { + if (CS_Open == m_channels.at(m_last_used_channel)) { + m_channels[m_last_used_channel] = CS_Used; + return m_last_used_channel; + } + + channel_state_list_t::iterator it = + std::find(m_channels.begin(), m_channels.end(), CS_Open); + + if (m_channels.end() == it) { + amqp_channel_t new_channel = CreateNewChannel(); + m_channels.at(new_channel) = CS_Used; + return new_channel; + } + + *it = CS_Used; + return it - m_channels.begin(); +} + +void Channel::ChannelImpl::ReturnChannel(amqp_channel_t channel) { + m_channels.at(channel) = CS_Open; + m_last_used_channel = channel; +} + +bool Channel::ChannelImpl::IsChannelOpen(amqp_channel_t channel) { + return CS_Closed != m_channels.at(channel); +} + +void Channel::ChannelImpl::FinishCloseChannel(amqp_channel_t channel) { + m_channels.at(channel) = CS_Closed; + + amqp_channel_close_ok_t close_ok; + CheckForError(amqp_send_method(m_connection, channel, + AMQP_CHANNEL_CLOSE_OK_METHOD, &close_ok)); +} + +void Channel::ChannelImpl::FinishCloseConnection() { + SetIsConnected(false); + amqp_connection_close_ok_t close_ok; + amqp_send_method(m_connection, 0, AMQP_CONNECTION_CLOSE_OK_METHOD, &close_ok); +} + +void Channel::ChannelImpl::CheckRpcReply(amqp_channel_t channel, + const amqp_rpc_reply_t &reply) { + switch (reply.reply_type) { + case AMQP_RESPONSE_NORMAL: + return; + break; + + case AMQP_RESPONSE_LIBRARY_EXCEPTION: + // If we're getting this likely is the socket is already closed + throw AmqpResponseLibraryException::CreateException(reply, ""); + break; + + case AMQP_RESPONSE_SERVER_EXCEPTION: + if (reply.reply.id == AMQP_CHANNEL_CLOSE_METHOD) { + FinishCloseChannel(channel); + } else if (reply.reply.id == AMQP_CONNECTION_CLOSE_METHOD) { + FinishCloseConnection(); + } + AmqpException::Throw(reply); + break; + + default: + AmqpException::Throw(reply); + } +} + +void Channel::ChannelImpl::CheckForError(int ret) { + if (ret < 0) { + throw AmqpLibraryException::CreateException(ret); + } +} + +MessageReturnedException Channel::ChannelImpl::CreateMessageReturnedException( + amqp_basic_return_t &return_method, amqp_channel_t channel) { + const int reply_code = return_method.reply_code; + const std::string reply_text((char *)return_method.reply_text.bytes, + return_method.reply_text.len); + const std::string exchange((char *)return_method.exchange.bytes, + return_method.exchange.len); + const std::string routing_key((char *)return_method.routing_key.bytes, + return_method.routing_key.len); + BasicMessage::ptr_t content = ReadContent(channel); + return MessageReturnedException(content, reply_code, reply_text, exchange, + routing_key); +} + +BasicMessage::ptr_t Channel::ChannelImpl::ReadContent(amqp_channel_t channel) { + amqp_frame_t frame; + + GetNextFrameOnChannel(channel, frame); + + if (frame.frame_type != AMQP_FRAME_HEADER) { + // TODO: We should connection.close here + throw std::runtime_error( + "Channel::BasicConsumeMessage: received unexpected frame type (was " + "expected AMQP_FRAME_HEADER)"); + } + + // The memory for this is allocated in a pool associated with the connection + // The BasicMessage constructor does a deep copy of the properties structure + amqp_basic_properties_t *properties = + reinterpret_cast( + frame.payload.properties.decoded); + + // size_t could possibly be 32-bit, body_size is always 64-bit + assert(frame.payload.properties.body_size < + static_cast(std::numeric_limits::max())); + + size_t body_size = static_cast(frame.payload.properties.body_size); + size_t received_size = 0; + + BasicMessage::ptr_t message = BasicMessage::Create(); + message->Body().reserve(body_size); + + // frame #3 and up: + while (received_size < body_size) { + GetNextFrameOnChannel(channel, frame); + + if (frame.frame_type != AMQP_FRAME_BODY) + // TODO: we should connection.close here + throw std::runtime_error( + "Channel::BasicConsumeMessage: received unexpected frame type (was " + "expecting AMQP_FRAME_BODY)"); + + message->Body().append( + reinterpret_cast(frame.payload.body_fragment.bytes), + frame.payload.body_fragment.len); + received_size += frame.payload.body_fragment.len; + } + + SetMessageProperties(*message, *properties); + + return message; +} + +void Channel::ChannelImpl::CheckFrameForClose(amqp_frame_t &frame, + amqp_channel_t channel) { + if (frame.frame_type == AMQP_FRAME_METHOD) { + switch (frame.payload.method.id) { + case AMQP_CHANNEL_CLOSE_METHOD: + FinishCloseChannel(channel); + AmqpException::Throw(*reinterpret_cast( + frame.payload.method.decoded)); + break; + + case AMQP_CONNECTION_CLOSE_METHOD: + FinishCloseConnection(); + AmqpException::Throw(*reinterpret_cast( + frame.payload.method.decoded)); + break; + } + } +} + +void Channel::ChannelImpl::AddConsumer(const std::string &consumer_tag, + amqp_channel_t channel) { + m_consumer_channel_map.insert(std::make_pair(consumer_tag, channel)); +} + +amqp_channel_t Channel::ChannelImpl::RemoveConsumer( + const std::string &consumer_tag) { + std::map::iterator it = + m_consumer_channel_map.find(consumer_tag); + if (it == m_consumer_channel_map.end()) { + throw ConsumerTagNotFoundException(); + } + + amqp_channel_t result = it->second; + + m_consumer_channel_map.erase(it); + + return result; +} + +amqp_channel_t Channel::ChannelImpl::GetConsumerChannel( + const std::string &consumer_tag) { + std::map::const_iterator it = + m_consumer_channel_map.find(consumer_tag); + if (it == m_consumer_channel_map.end()) { + throw ConsumerTagNotFoundException(); + } + return it->second; +} + +std::vector Channel::ChannelImpl::GetAllConsumerChannels() + const { + std::vector ret; + for (consumer_map_t::const_iterator it = m_consumer_channel_map.begin(); + it != m_consumer_channel_map.end(); ++it) { + ret.push_back(it->second); + } + + return ret; +} + +bool Channel::ChannelImpl::CheckForQueuedMessageOnChannel( + amqp_channel_t channel) const { + frame_queue_t::const_iterator it = + std::find_if(m_frame_queue.begin(), m_frame_queue.end(), + boost::bind(&Channel::ChannelImpl::is_method_on_channel, _1, + AMQP_BASIC_DELIVER_METHOD, channel)); + + if (it == m_frame_queue.end()) { + return false; + } + + it = std::find_if( + it + 1, m_frame_queue.end(), + boost::bind(&Channel::ChannelImpl::is_on_channel, _1, channel)); + + if (it == m_frame_queue.end()) { + return false; + } + if (it->frame_type != AMQP_FRAME_HEADER) { + throw std::runtime_error("Protocol error"); + } + + uint64_t body_length = it->payload.properties.body_size; + uint64_t body_received = 0; + + while (body_received < body_length) { + it = std::find_if( + it + 1, m_frame_queue.end(), + boost::bind(&Channel::ChannelImpl::is_on_channel, _1, channel)); + + if (it == m_frame_queue.end()) { + return false; + } + if (it->frame_type != AMQP_FRAME_BODY) { + throw std::runtime_error("Protocol error"); + } + body_received += it->payload.body_fragment.len; + } + + return true; +} + +void Channel::ChannelImpl::AddToFrameQueue(const amqp_frame_t &frame) { + m_frame_queue.push_back(frame); + + if (CheckForQueuedMessageOnChannel(frame.channel)) { + boost::array channel = {{frame.channel}}; + Envelope::ptr_t envelope; + if (!ConsumeMessageOnChannelInner(channel, envelope, -1)) { + throw std::logic_error( + "ConsumeMessageOnChannelInner returned false unexpectedly"); + } + + m_delivered_messages.push_back(envelope); + } +} + +bool Channel::ChannelImpl::GetNextFrameFromBroker( + amqp_frame_t &frame, boost::chrono::microseconds timeout) { + struct timeval *tvp = NULL; + struct timeval tv_timeout; + memset(&tv_timeout, 0, sizeof(tv_timeout)); + + if (timeout != boost::chrono::microseconds::max()) { + // boost::chrono::seconds.count() returns boost::int_atleast64_t, + // long can be 32 or 64 bit depending on the platform/arch + // unless the timeout is something absurd cast to long will be ok, but + // lets guard against the case where someone does something silly + assert( + boost::chrono::duration_cast(timeout).count() < + static_cast( + std::numeric_limits::max())); + + tv_timeout.tv_sec = static_cast( + boost::chrono::duration_cast(timeout).count()); + tv_timeout.tv_usec = static_cast( + (timeout - boost::chrono::seconds(tv_timeout.tv_sec)).count()); + + tvp = &tv_timeout; + } + + int ret = amqp_simple_wait_frame_noblock(m_connection, &frame, tvp); + + if (AMQP_STATUS_TIMEOUT == ret) { + return false; + } + CheckForError(ret); + return true; +} + +bool Channel::ChannelImpl::GetNextFrameOnChannel( + amqp_channel_t channel, amqp_frame_t &frame, + boost::chrono::microseconds timeout) { + frame_queue_t::iterator it = std::find_if( + m_frame_queue.begin(), m_frame_queue.end(), + boost::bind(&Channel::ChannelImpl::is_on_channel, _1, channel)); + + if (m_frame_queue.end() != it) { + frame = *it; + m_frame_queue.erase(it); + + if (AMQP_FRAME_METHOD == frame.frame_type && + AMQP_CHANNEL_CLOSE_METHOD == frame.payload.method.id) { + FinishCloseChannel(channel); + AmqpException::Throw(*reinterpret_cast( + frame.payload.method.decoded)); + } + return true; + } + + boost::array channels = {{channel}}; + return GetNextFrameFromBrokerOnChannel(channels, frame, timeout); +} + +void Channel::ChannelImpl::MaybeReleaseBuffersOnChannel( + amqp_channel_t channel) { + if (m_frame_queue.end() == + std::find_if( + m_frame_queue.begin(), m_frame_queue.end(), + boost::bind(&Channel::ChannelImpl::is_on_channel, _1, channel))) { + amqp_maybe_release_buffers_on_channel(m_connection, channel); + } +} + +void Channel::ChannelImpl::CheckIsConnected() { + if (!m_is_connected) { + throw ConnectionClosedException(); + } +} + +namespace { +bool bytesEqual(amqp_bytes_t r, amqp_bytes_t l) { + if (r.len == l.len) { + if (0 == memcmp(r.bytes, l.bytes, r.len)) { + return true; + } + } + return false; +} +} // namespace + +boost::uint32_t Channel::ChannelImpl::ComputeBrokerVersion( + amqp_connection_state_t state) { + const amqp_table_t *properties = amqp_get_server_properties(state); + const amqp_bytes_t version = amqp_cstring_bytes("version"); + amqp_table_entry_t *version_entry = NULL; + + for (int i = 0; i < properties->num_entries; ++i) { + if (bytesEqual(properties->entries[i].key, version)) { + version_entry = &properties->entries[i]; + break; + } + } + if (NULL == version_entry) { + return 0; + } + + std::string version_string( + static_cast(version_entry->value.value.bytes.bytes), + version_entry->value.value.bytes.len); + std::vector version_components; + boost::split(version_components, version_string, boost::is_any_of(".")); + if (version_components.size() != 3) { + return 0; + } + boost::uint32_t version_major = + boost::lexical_cast(version_components[0]); + boost::uint32_t version_minor = + boost::lexical_cast(version_components[1]); + boost::uint32_t version_patch = + boost::lexical_cast(version_components[2]); + return (version_major & 0xFF) << 16 | (version_minor & 0xFF) << 8 | + (version_patch & 0xFF); +} + +} // namespace AmqpClient diff --git a/src/Envelope.cpp b/src/Envelope.cpp new file mode 100644 index 0000000..3edceb4 --- /dev/null +++ b/src/Envelope.cpp @@ -0,0 +1,48 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "SimpleAmqpClient/Envelope.h" + +namespace AmqpClient { + +Envelope::Envelope(const BasicMessage::ptr_t message, + const std::string &consumer_tag, + const boost::uint64_t delivery_tag, + const std::string &exchange, bool redelivered, + const std::string &routing_key, + const boost::uint16_t delivery_channel) + : m_message(message), + m_consumerTag(consumer_tag), + m_deliveryTag(delivery_tag), + m_exchange(exchange), + m_redelivered(redelivered), + m_routingKey(routing_key), + m_deliveryChannel(delivery_channel) {} + +Envelope::~Envelope() {} +} // namespace AmqpClient diff --git a/src/MessageReturnedException.cpp b/src/MessageReturnedException.cpp new file mode 100644 index 0000000..02719fd --- /dev/null +++ b/src/MessageReturnedException.cpp @@ -0,0 +1,49 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "SimpleAmqpClient/MessageReturnedException.h" + +#include + +namespace AmqpClient { +MessageReturnedException::MessageReturnedException( + BasicMessage::ptr_t message, boost::uint32_t reply_code, + const std::string &reply_text, const std::string &exchange, + const std::string &routing_key) throw() + : std::runtime_error( + std::string("Message returned. Reply code: ") + .append(boost::lexical_cast(reply_code)) + .append(" ") + .append(reply_text)), + m_message(message), + m_reply_code(reply_code), + m_reply_text(reply_text), + m_exchange(exchange), + m_routing_key(routing_key) {} + +} // namespace AmqpClient diff --git a/src/SimpleAmqpClient/AmqpException.h b/src/SimpleAmqpClient/AmqpException.h new file mode 100644 index 0000000..e987ae3 --- /dev/null +++ b/src/SimpleAmqpClient/AmqpException.h @@ -0,0 +1,572 @@ +#ifndef SIMPLEAMQPCLIENT_AMQPEXCEPTION_H +#define SIMPLEAMQPCLIENT_AMQPEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include + +#include "Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/AmqpException.h +/// Defines AmqpClient::AmqpException + +struct amqp_rpc_reply_t_; +struct amqp_channel_close_t_; +struct amqp_connection_close_t_; + +namespace AmqpClient { + +/** + * Base-class for exceptions from the broker + */ +class SIMPLEAMQPCLIENT_EXPORT AmqpException : public std::runtime_error { + public: + /** + * Construct an AmqpException from an amqp_rpc_reply_t and throw it + * + * @param [in] reply the reply from the RPC call + */ + static void Throw(const amqp_rpc_reply_t_ &reply); + + /** + * Construct an AmqpException from an amqp_channel_close_t and throw it + * + * @param [in] reply the channel.close AMQP method + */ + static void Throw(const amqp_channel_close_t_ &reply); + + /** + * Construct an AmqpException from an amqp_connection_close_t and throw it + * + * @param [in] reply the connection.close AMQP method + */ + static void Throw(const amqp_connection_close_t_ &reply); + + /** + * Constructor + * + * @param [in] what the error string to pass to the std::runtime_error + * base-class + * @param [in] reply_text the error message (if any) from the broker + * @param [in] class_id the class id of the method that caused the error + * @param [in] method_id the method id of the method that caused the error + */ + explicit AmqpException(const std::string &what, const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw(); + + /** + * Destructor + */ + virtual ~AmqpException() throw() {} + + /** + * Query to see if the error is soft + * + * A soft error is generally recoverable, meaning the Channel + * object that it originated from can be reused. If its a hard error + * the Channel object is closed and will throw exceptions if its used + * again. + * + * @returns `true` if its a soft error, `false` otherwise + */ + virtual bool is_soft_error() const throw() = 0; + + /** + * Get the error code returned from the broker + * + * @returns the error code + */ + virtual boost::uint16_t reply_code() const throw() = 0; + + /** + * Get the class id of the method that caused the error + * + * @returns the class id + */ + virtual boost::uint16_t class_id() const throw() { return m_class_id; } + + /** + * Get the method id of the method that caused the error + * + * @returns the method id + */ + virtual boost::uint16_t method_id() const throw() { return m_method_id; } + + /** + * Get the error string returned from the broker + * + * @returns the error string from the broker + */ + virtual std::string reply_text() const throw() { return m_reply_text; } + + protected: + /** @cond INTERNAL */ + std::string m_reply_text; + boost::uint16_t m_class_id; + boost::uint16_t m_method_id; + /** @endcond */ +}; + +/** + * Base class for exceptions that result in the connection being closed + * + * If the program receives this kind of exception, the Channel object that + * threw the exception is now disconnected from the broker and any further + * use of the channel will cause further errors + */ +class SIMPLEAMQPCLIENT_EXPORT ConnectionException : public AmqpException { + public: + /** + * Constructor + * + * @param [in] what + * @param [in] reply_text + * @param [in] class_id + * @param [in] method_id + */ + explicit ConnectionException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : AmqpException(what, reply_text, class_id, method_id) {} + + virtual bool is_soft_error() const throw() { return false; } +}; + +/** + * Base class for exceptions that are soft errors + * + * If the program receives this kind of exception, the Channel object + * that threw the exception will continue to be functional. + */ +class SIMPLEAMQPCLIENT_EXPORT ChannelException : public AmqpException { + public: + /** + * Constructor + * + * @param [in] what + * @param [in] reply_text + * @param [in] class_id + * @param [in] method_id + */ + explicit ChannelException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : AmqpException(what, reply_text, class_id, method_id) {} + + virtual bool is_soft_error() const throw() { return true; } +}; + +/** + * The connection was force closed by an operator + */ +class SIMPLEAMQPCLIENT_EXPORT ConnectionForcedException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit ConnectionForcedException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client tried to work with an invalid virtual host + */ +class SIMPLEAMQPCLIENT_EXPORT InvalidPathException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit InvalidPathException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The broker received a malformed frame. + * + * This likely indicates a bug in SimpleAmqpClient + */ +class SIMPLEAMQPCLIENT_EXPORT FrameErrorException : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit FrameErrorException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client sent a frame with bad values (out of range) + * + * This likely indicates a bug in SimpleAmqpClient + */ +class SIMPLEAMQPCLIENT_EXPORT SyntaxErrorException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** + * Constructor + */ + explicit SyntaxErrorException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client sent an invalid sequence of frames + * + * This likely indicates a bug in SimpleAmqpClient + */ +class SIMPLEAMQPCLIENT_EXPORT CommandInvalidException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit CommandInvalidException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client tried to use a Channel that isn't open + * + * This likely indicates a bug in SimpleAmqpClient + */ +class SIMPLEAMQPCLIENT_EXPORT ChannelErrorException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** + * Constructor + */ + explicit ChannelErrorException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client sent a frame the broker wasn't expecting + * + * This likely indicates a bug in SimpleAmqpClient + */ +class SIMPLEAMQPCLIENT_EXPORT UnexpectedFrameException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit UnexpectedFrameException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The broker could not complete the request because of lack of resources + * + * Likely due to clients creating too many of something + */ +class SIMPLEAMQPCLIENT_EXPORT ResourceErrorException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit ResourceErrorException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client tried to work with some entity in a manner that is prohibited by + * the server. + * + * Like due to security settings or by some other criteria. + */ +class SIMPLEAMQPCLIENT_EXPORT NotAllowedException : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit NotAllowedException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client tried to use a method that isn't implemented by the broker + */ +class SIMPLEAMQPCLIENT_EXPORT NotImplementedException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** + * Constructor + */ + explicit NotImplementedException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * An internal error occurred on the broker + */ +class SIMPLEAMQPCLIENT_EXPORT InternalErrorException + : public ConnectionException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** Constructor */ + explicit InternalErrorException(const std::string &what, + const std::string &reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ConnectionException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client tried to send content that was too large + * + * Try sending the content at a later time + */ +class SIMPLEAMQPCLIENT_EXPORT ContentTooLargeException + : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + + /** Constructor */ + explicit ContentTooLargeException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * AMQP_NO_ROUTE error, occurs when the IP stack cannot route our data to the + * broker. + */ +class SIMPLEAMQPCLIENT_EXPORT NoRouteException : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** Constructor */ + explicit NoRouteException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * AMQP_NO_CONSUMERS error. When the exchange cannot deliver to a consumer when + * the immediate flag is set. As a result of pending data on the queue or the + * absence of any consumers of the queue. + */ +class SIMPLEAMQPCLIENT_EXPORT NoConsumersException : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** Constructor */ + explicit NoConsumersException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * AMQP_ACCESS_REFUSED error. The client attempted to work with a server entity + * to which it has no access due to security settings. + */ +class SIMPLEAMQPCLIENT_EXPORT AccessRefusedException : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** Constructor */ + explicit AccessRefusedException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * AMQP_NOT_FOUND error. The client attempted to work with a server entity that + * does not exist. + */ +class SIMPLEAMQPCLIENT_EXPORT NotFoundException : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** Constructor */ + explicit NotFoundException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client attempted to work with a server entity to which it has no access + * because another client is working with it. + */ +class SIMPLEAMQPCLIENT_EXPORT ResourceLockedException + : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** Constructor */ + explicit ResourceLockedException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +/** + * The client requested a method that was not allowed because some precondition + * failed. + */ +class SIMPLEAMQPCLIENT_EXPORT PreconditionFailedException + : public ChannelException { + public: + /** reply code */ + static const boost::uint16_t REPLY_CODE; + /** Constructor */ + explicit PreconditionFailedException(const std::string &what, + const std::string reply_text, + boost::uint16_t class_id, + boost::uint16_t method_id) throw() + : ChannelException(what, reply_text, class_id, method_id) {} + + virtual boost::uint16_t reply_code() const throw() { return REPLY_CODE; } +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_AMQPEXCEPTION_H diff --git a/src/SimpleAmqpClient/AmqpLibraryException.h b/src/SimpleAmqpClient/AmqpLibraryException.h new file mode 100644 index 0000000..d8d5238 --- /dev/null +++ b/src/SimpleAmqpClient/AmqpLibraryException.h @@ -0,0 +1,84 @@ +#ifndef SIMPLEAMQPCLIENT_AMQPLIBRARYEXCEPTION_H +#define SIMPLEAMQPCLIENT_AMQPLIBRARYEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2014 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include + +#include "SimpleAmqpClient/Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/AmqpLibraryException.h +/// Defines SimpleAmqpClient::AmqpLibraryException + +struct amqp_rpc_reply_t_; + +namespace AmqpClient { + +/** + * Errors arising from incorrect usage of this library APIs + */ +class SIMPLEAMQPCLIENT_EXPORT AmqpLibraryException : public std::runtime_error { + public: + /** + * Factory-construct with an error code + */ + static AmqpLibraryException CreateException(int error_code); + /** + * Factory-construct with an error code and a string context + */ + static AmqpLibraryException CreateException(int error_code, + const std::string &context); + + /** + * Error code getter + */ + int ErrorCode() const { return m_errorCode; } + + protected: + /** @cond INTERNAL */ + explicit AmqpLibraryException(const std::string &message, + int error_code) throw(); + /** @endcond@ */ + + private: + int m_errorCode; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_AMQPLIBRARYEXCEPTION_H diff --git a/src/SimpleAmqpClient/AmqpResponseLibraryException.h b/src/SimpleAmqpClient/AmqpResponseLibraryException.h new file mode 100644 index 0000000..c76f127 --- /dev/null +++ b/src/SimpleAmqpClient/AmqpResponseLibraryException.h @@ -0,0 +1,70 @@ +#ifndef SIMPLEAMQPCLIENT_AMQPRESPONSELIBRARYEXCEPTION_H +#define SIMPLEAMQPCLIENT_AMQPRESPONSELIBRARYEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include + +#include "SimpleAmqpClient/Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/AmqpResponseLibraryException.h +/// Defines AmqpClient::AmqpResponseLibraryException + +struct amqp_rpc_reply_t_; + +namespace AmqpClient { + +/** + * `AMQP_RESPONSE_LIBRARY_EXCEPTION`: an RPC response returned a library error + */ +class SIMPLEAMQPCLIENT_EXPORT AmqpResponseLibraryException + : public std::runtime_error { + public: + /// Factory-construct from RPC reply and a string context + static AmqpResponseLibraryException CreateException( + const amqp_rpc_reply_t_ &reply, const std::string &context); + + protected: + /// Construct an instance with exact message + explicit AmqpResponseLibraryException(const std::string &message) throw(); +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_AMQPRESPONSELIBRARYEXCEPTION_H diff --git a/src/SimpleAmqpClient/BadUriException.h b/src/SimpleAmqpClient/BadUriException.h new file mode 100644 index 0000000..96d35e3 --- /dev/null +++ b/src/SimpleAmqpClient/BadUriException.h @@ -0,0 +1,46 @@ +#ifndef SIMPLEAMQPCLIENT_BADURIEXCEPTION_H +#define SIMPLEAMQPCLIENT_BADURIEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "SimpleAmqpClient/Util.h" + +/// @file SimpleAmqpClient/BadUriException.h +/// Defines AmqpClient::BadUriException + +namespace AmqpClient { + +/** "URI is malformed" exception */ +class SIMPLEAMQPCLIENT_EXPORT BadUriException : public std::runtime_error { + public: + explicit BadUriException() : std::runtime_error("URI is malformed") {} +}; +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_BADURIEXCEPTION_H diff --git a/src/SimpleAmqpClient/BasicMessage.h b/src/SimpleAmqpClient/BasicMessage.h new file mode 100644 index 0000000..ffa8789 --- /dev/null +++ b/src/SimpleAmqpClient/BasicMessage.h @@ -0,0 +1,354 @@ +#ifndef SIMPLEAMQPCLIENT_BASICMESSAGE_H +#define SIMPLEAMQPCLIENT_BASICMESSAGE_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/Table.h" +#include "SimpleAmqpClient/Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4275 4251) +#endif + +/// @file SimpleAmqpClient/BasicMessage.h +/// The AmqpClient::BasicMessage class is defined in this header file. + +namespace AmqpClient { + +/** + * An AMQP BasicMessage + */ +class SIMPLEAMQPCLIENT_EXPORT BasicMessage : boost::noncopyable { + public: + /// A shared pointer to BasicMessage + typedef boost::shared_ptr ptr_t; + + /// With durable queues, messages can be requested to persist or not + enum delivery_mode_t { + dm_notset = 0, + dm_nonpersistent = 1, + dm_persistent = 2 + }; + + /** + * Create a new empty BasicMessage object + */ + static ptr_t Create() { return boost::make_shared(); } + + /** + * Create a new BasicMessage object with given body + * + * @param body the message body. + * @returns a new BasicMessage object + */ + static ptr_t Create(const std::string& body) { + return boost::make_shared(body); + } + + /// Construct empty BasicMessage + BasicMessage(); + /// Construct BasicMessage with given body + BasicMessage(const std::string& body); + + public: + /** + * Destructor + */ + virtual ~BasicMessage(); + + /** + * Gets the message body as a std::string + */ + const std::string& Body() const; + std::string& Body(); + + /** + * Sets the message body as a std::string + */ + void Body(const std::string& body); + + /** + * Gets the content type property + */ + const std::string& ContentType() const; + /** + * Sets the content type property + */ + void ContentType(const std::string& content_type); + /** + * Determines whether the content type property is set + */ + bool ContentTypeIsSet() const; + /** + * Unsets the content type property if it is set + */ + void ContentTypeClear(); + + /** + * Gets the content encoding property + */ + const std::string& ContentEncoding() const; + /** + * Sets the content encoding property + */ + void ContentEncoding(const std::string& content_encoding); + /** + * Determines whether the content encoding property is set + */ + bool ContentEncodingIsSet() const; + /** + * Unsets the content encoding property if it is set + */ + void ContentEncodingClear(); + + /** + * Gets the delivery mode property + */ + delivery_mode_t DeliveryMode() const; + /** + * Sets the delivery mode property + */ + void DeliveryMode(delivery_mode_t delivery_mode); + /** + * Determines whether the delivery mode property is set + */ + bool DeliveryModeIsSet() const; + /** + * Unsets the delivery mode property if it is set + */ + void DeliveryModeClear(); + + /** + * Gets the priority property + */ + boost::uint8_t Priority() const; + /** + * Sets the priority property + */ + void Priority(boost::uint8_t priority); + /** + * Determines whether the priority property is set + */ + bool PriorityIsSet() const; + /** + * Unsets the priority property if it is set + */ + void PriorityClear(); + + /** + * Gets the correlation id property + */ + const std::string& CorrelationId() const; + /** + * Sets the correlation id property + */ + void CorrelationId(const std::string& correlation_id); + /** + * Determines whether the correlation id property is set + */ + bool CorrelationIdIsSet() const; + /** + * Unsets the correlation id property + */ + void CorrelationIdClear(); + + /** + * Gets the reply to property + */ + const std::string& ReplyTo() const; + /** + * Sets the reply to property + */ + void ReplyTo(const std::string& reply_to); + /** + * Determines whether the reply to property is set + */ + bool ReplyToIsSet() const; + /** + * Unsets the reply to property + */ + void ReplyToClear(); + + /** + * Gets the expiration property + */ + const std::string& Expiration() const; + /** + * Sets the expiration property + */ + void Expiration(const std::string& expiration); + /** + * Determines whether the expiration property is set + */ + bool ExpirationIsSet() const; + /** + * Unsets the expiration property + */ + void ExpirationClear(); + + /** + * Gets the message id property + */ + const std::string& MessageId() const; + /** + * Sets the message id property + */ + void MessageId(const std::string& message_id); + /** + * Determines if the message id property is set + */ + bool MessageIdIsSet() const; + /** + * Unsets the message id property + */ + void MessageIdClear(); + + /** + * Gets the timestamp property + */ + boost::uint64_t Timestamp() const; + /** + * Sets the timestamp property + */ + void Timestamp(boost::uint64_t timestamp); + /** + * Determines whether the timestamp property is set + */ + bool TimestampIsSet() const; + /** + * Unsets the timestamp property + */ + void TimestampClear(); + + /** + * Gets the type property + */ + const std::string& Type() const; + /** + * Sets the type property + */ + void Type(const std::string& type); + /** + * Determines whether the type property is set + */ + bool TypeIsSet() const; + /** + * Unsets the type property + */ + void TypeClear(); + + /** + * Gets the user id property + */ + const std::string& UserId() const; + /** + * Sets the user id property + */ + void UserId(const std::string& user_id); + /** + * Determines whether the user id property is set + */ + bool UserIdIsSet() const; + /** + * Unsets the user id property + */ + void UserIdClear(); + + /** + * Gets the app id property + */ + const std::string& AppId() const; + /** + * Sets the app id property + */ + void AppId(const std::string& app_id); + /** + * Determines whether the app id property is set + */ + bool AppIdIsSet() const; + /** + * Unsets the app id property + */ + void AppIdClear(); + + /** + * Gets the cluster id property + */ + const std::string& ClusterId() const; + /** + * Sets the custer id property + */ + void ClusterId(const std::string& cluster_id); + /** + * Determines if the cluster id property is set + */ + bool ClusterIdIsSet() const; + /** + * Unsets the cluster id property + */ + void ClusterIdClear(); + + /** + * Gets the header table property + */ + Table& HeaderTable(); + const Table& HeaderTable() const; + /** + * Sets the header table property + */ + void HeaderTable(const Table& header_table); + /** + * Is there a header table associated with the message + */ + bool HeaderTableIsSet() const; + /** + * Unsets the header table property + */ + void HeaderTableClear(); + + protected: + struct Impl; + /// PIMPL idiom + boost::scoped_ptr m_impl; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_BASICMESSAGE_H diff --git a/src/SimpleAmqpClient/Bytes.h b/src/SimpleAmqpClient/Bytes.h new file mode 100644 index 0000000..8f35177 --- /dev/null +++ b/src/SimpleAmqpClient/Bytes.h @@ -0,0 +1,26 @@ +#ifndef SIMPLEAMQPCLIENT_BYTES_H +#define SIMPLEAMQPCLIENT_BYTES_H + +#include +#include + +#include + +namespace AmqpClient { + +amqp_bytes_t StringToBytes(const std::string& str) { + amqp_bytes_t ret; + ret.bytes = reinterpret_cast(const_cast(str.data())); + ret.len = str.length(); + return ret; +} + +amqp_bytes_t StringRefToBytes(boost::string_ref str) { + amqp_bytes_t ret; + ret.bytes = reinterpret_cast(const_cast(str.data())); + ret.len = str.length(); + return ret; +} + +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_BYTES_H diff --git a/src/SimpleAmqpClient/Channel.h b/src/SimpleAmqpClient/Channel.h new file mode 100644 index 0000000..ee65bef --- /dev/null +++ b/src/SimpleAmqpClient/Channel.h @@ -0,0 +1,946 @@ +#ifndef SIMPLEAMQPCLIENT_CHANNEL_H +#define SIMPLEAMQPCLIENT_CHANNEL_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/BasicMessage.h" +#include "SimpleAmqpClient/Envelope.h" +#include "SimpleAmqpClient/Table.h" +#include "SimpleAmqpClient/Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/Channel.h +/// The AmqpClient::Channel class is defined in this header file. + +namespace AmqpClient { + +/** + * A single channel multiplexed in an AMQP connection + * + * Represents a logical AMQP channel multiplexed over a connection + */ +class SIMPLEAMQPCLIENT_EXPORT Channel : boost::noncopyable { + public: + /// a `shared_ptr` to Channel + typedef boost::shared_ptr ptr_t; + + static const std::string + EXCHANGE_TYPE_DIRECT; ///< `"direct"` string constant + static const std::string + EXCHANGE_TYPE_FANOUT; ///< `"fanout"` string constant + static const std::string EXCHANGE_TYPE_TOPIC; ///< `"topic"` string constant + + struct SIMPLEAMQPCLIENT_EXPORT OpenOpts { + /// Use username and password to authenticate with the broker. + struct SIMPLEAMQPCLIENT_EXPORT BasicAuth { + std::string username; + std::string password; + + BasicAuth() {} + BasicAuth(const std::string &username, const std::string &password) + : username(username), password(password) {} + bool operator==(const BasicAuth &) const; + }; + + /// Use External SASL method to authenticate with the broker. + struct SIMPLEAMQPCLIENT_EXPORT ExternalSaslAuth { + std::string identity; + + ExternalSaslAuth() {} + explicit ExternalSaslAuth(const std::string &identity) + : identity(identity) {} + bool operator==(const ExternalSaslAuth &) const; + }; + + /// Parameters + struct SIMPLEAMQPCLIENT_EXPORT TLSParams { + std::string client_key_path; ///< Path to client key. + std::string client_cert_path; ///< Path to client cert. + std::string ca_cert_path; ///< Path to CA cert. + bool verify_hostname; ///< Verify host matches certificate. Default: true + bool verify_peer; ///< Verify presented certificate. Default: true + + TLSParams() : verify_hostname(true), verify_peer(true) {} + bool operator==(const TLSParams &) const; + }; + + std::string host; ///< Broker hostname. Required. + std::string vhost; ///< Virtualhost on the broker. Default '/', required. + int port; ///< Port to connect to, default is 5672. + int frame_max; ///< Max frame size in bytes. Default 128KB. + /// One of BasicAuth or ExternalSaslAuth is required. + boost::variant auth; + /// Connect using TLS/SSL when set, otherwise use an unencrypted channel. + boost::optional tls_params; + + /** + * Create an OpenOpts struct from a URI. + * + * URIs look like amqp[s]://[username[:password]@]host[:port]/[vhost]. + * Unspecified parts of the URL will take default values: + * - username: guest + * - password: guest + * - host: localhost + * - port: 5672 for 'amqp', and 5671 for 'amqps' + * - vhost: '/' + * + * NOTE: for TLS/SSL connections, additional configuration is required. + */ + static OpenOpts FromUri(const std::string &uri); + + OpenOpts() : vhost("/"), port(5672), frame_max(131072) {} + bool operator==(const OpenOpts &) const; + }; + + /** + * Open a new channel to the broker. + * + * See documentation for \ref OpenOpts for details on what can be + * passed into this function. + */ + static ptr_t Open(const OpenOpts &opts); + + /** + * Creates a new channel object + * + * Creates a new connection to an AMQP broker using the supplied parameters + * and opens a single channel for use + * @param host The hostname or IP address of the AMQP broker + * @param port The port to connect to the AMQP broker on + * @param username The username used to authenticate with the AMQP broker + * @param password The password corresponding to the username used to + * authenticate with the AMQP broker + * @param vhost The virtual host on the AMQP we should connect to + * @param frame_max Request that the server limit the maximum size of any + * frame to this value + * @return a new Channel object pointer + */ + SAC_DEPRECATED("Channel::Create is deprecated. Use Channel::Open.") + static ptr_t Create(const std::string &host = "127.0.0.1", int port = 5672, + const std::string &username = "guest", + const std::string &password = "guest", + const std::string &vhost = "/", int frame_max = 131072); + + /** + * Creates a new channel object + * Creates a new connection to an AMQP broker using the supplied parameters + * and opens a single channel for use + * This channel uses the EXTERNAL SASL method for authentication to the broker + * @param host The hostname or IP address of the AMQP broker + * @param port The port to connect to the AMQP broker on + * @param identity The identity used to authenticate with the AMQP broker + * @param vhost The virtual host on the AMQP we should connect to + * @param channel_max Request that the server limit the number of channels + * for + * this connection to the specified parameter, a value of zero will use the + * broker-supplied value + * @param frame_max Request that the server limit the maximum size of any + * frame to this value + * @return a new Channel object pointer + */ + SAC_DEPRECATED( + "Channel::CreateSaslExternal is deprecated. Use Channel::Open.") + static ptr_t CreateSaslExternal(const std::string &host = "127.0.0.1", + int port = 5672, + const std::string &identity = "guest", + const std::string &vhost = "/", + int frame_max = 131072); + + /** + * Creates a new channel object, using TLS + * + * Creates a new connection to an AMQP broker using the supplied parameters + * and opens a single channel for use + * @param path_to_ca_cert Path to CA certificate file + * @param host The hostname or IP address of the AMQP broker + * @param path_to_client_key Path to client key file + * @param path_to_client_cert Path to client certificate file + * @param port The port to connect to the AMQP broker on + * @param username The username used to authenticate with the AMQP broker + * @param password The password corresponding to the username used to + * authenticate with the AMQP broker + * @param vhost The virtual host on the AMQP we should connect to + * @param frame_max Request that the server limit the maximum size of any + * frame to this value + * @param verify_hostname Verify the hostname against the certificate when + * opening the SSL connection. + * + * @return a new Channel object pointer + */ + SAC_DEPRECATED("Channel::CreateSecure is deprecated. Use Channel::Open.") + static ptr_t CreateSecure(const std::string &path_to_ca_cert = "", + const std::string &host = "127.0.0.1", + const std::string &path_to_client_key = "", + const std::string &path_to_client_cert = "", + int port = 5671, + const std::string &username = "guest", + const std::string &password = "guest", + const std::string &vhost = "/", + int frame_max = 131072, + bool verify_hostname_and_peer = true); + + /** + * Creates a new channel object + * Creates a new connection to an AMQP broker using the supplied parameters + * and opens a single channel for use + * @param path_to_ca_cert Path to ca certificate file + * @param host The hostname or IP address of the AMQP broker + * @param path_to_client_key Path to client key file + * @param path_to_client_cert Path to client certificate file + * @param port The port to connect to the AMQP broker on + * @param username The username used to authenticate with the AMQP broker + * @param password The password corresponding to the username used to + * authenticate with the AMQP broker + * @param vhost The virtual host on the AMQP we should connect to + * @param channel_max Request that the server limit the number of channels for + * this connection to the specified parameter, a value of zero will use the + * broker-supplied value + * @param frame_max Request that the server limit the maximum size of any + * frame to this value + * @param verify_host Verify the hostname against the certificate when + * opening the SSL connection. + * @param verify_peer Verify the certificate chain that is sent by the broker + * when opening the SSL connection. + * + * @return a new Channel object pointer + */ + SAC_DEPRECATED("Channel::CreateSecure is deprecated. Use Channel::Open.") + static ptr_t CreateSecure(const std::string &path_to_ca_cert, + const std::string &host, + const std::string &path_to_client_key, + const std::string &path_to_client_cert, int port, + const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max, + bool verify_hostname, bool verify_peer); + + /** + * Creates a new channel object + * Creates a new connection to an AMQP broker using the supplied parameters + * and opens a single channel for use + * This channel uses the EXTERNAL SASL method for authentication to the broker + * @param path_to_ca_cert Path to ca certificate file + * @param host The hostname or IP address of the AMQP broker + * @param path_to_client_key Path to client key file + * @param path_to_client_cert Path to client certificate file + * @param port The port to connect to the AMQP broker on + * @param identity The identity used to authenticate with the AMQP broker + * @param vhost The virtual host on the AMQP we should connect to + * @param channel_max Request that the server limit the number of channels for + * this connection to the specified parameter, a value of zero will use the + * broker-supplied value + * @param frame_max Request that the server limit the maximum size of any + * frame to this value + * @param verify_host Verify the hostname against the certificate when + * opening the SSL connection. + * @param verify_peer Verify the certificate chain that is sent by the broker + * when opening the SSL connection. + * + * @return a new Channel object pointer + */ + SAC_DEPRECATED( + "Channel::CreateSecureSaslExternal is deprecated. Use Channel::Open.") + static ptr_t CreateSecureSaslExternal(const std::string &path_to_ca_cert, + const std::string &host, + const std::string &path_to_client_key, + const std::string &path_to_client_cert, + int port, const std::string &identity, + const std::string &vhost, int frame_max, + bool verify_hostname, bool verify_peer); + + /** + * Create a new Channel object from an AMQP URI + * + * @param uri a URI of the form: + * `amqp://[username:password@]{HOSTNAME}[:PORT][/VHOST]` + * @param frame_max requests that the broker limit the maximum size of + * any frame to this value + * @returns a new Channel object + */ + SAC_DEPRECATED("Channel::CreateFromUri is deprecated. Use Channel::Open.") + static ptr_t CreateFromUri(const std::string &uri, int frame_max = 131072); + + /** + * Create a new Channel object from an AMQP URI, secured with SSL. + * If URI should start with amqps:// + * + * @param uri a URI of the form: + * `amqp[s]://[username:password@]{HOSTNAME}[:PORT][/VHOST]` + * @param path_to_ca_cert Path to ca certificate file + * @param path_to_client_key Path to client key file + * @param path_to_client_cert Path to client certificate file + * @param verify_hostname Verify the hostname against the certificate when + * opening the SSL connection and the certificate chain that is sent by the + * broker. + * @param frame_max requests that the broker limit the maximum size of + * any frame to this value + * @returns a new Channel object + */ + SAC_DEPRECATED( + "Channel::CreateSecureFromUri is deprecated. Use Channel::Open.") + static ptr_t CreateSecureFromUri(const std::string &uri, + const std::string &path_to_ca_cert, + const std::string &path_to_client_key = "", + const std::string &path_to_client_cert = "", + bool verify_hostname_and_peer = true, + int frame_max = 131072); + + private: + class ChannelImpl; + + public: + explicit Channel(ChannelImpl *impl); + virtual ~Channel(); + + /** + * Exposes the underlying socket handle + * @returns file descriptor number associated with the connection socket + * + * @warning This function exposes an internal implementation detail + * of SimpleAmqpClient. Manipulating the socket descriptor will result in + * undefined behavior of the library. Additionally SimpleAmqpClient's use of + * the socket will change depending on what version of rabbitmq-c and + * SimpleAmqpClient are used. Test carefully before depending on any specific + * behavior. + */ + int GetSocketFD() const; + + /** + * Checks to see if an exchange exists on the broker. + * + * @param exchange_name the name of the exchange to check for. + * @returns true if the exchange exists on the broker, false otherwise. + */ + bool CheckExchangeExists(boost::string_ref exchange_name); + + /** + * Declares an exchange + * + * Creates an exchange on the AMQP broker if it does not already exist + * @param exchange_name the name of the exchange + * @param exchange_type the type of exchange to be declared. Defaults to + * `direct`; other types that could be used: `fanout`, `topic` + * @param passive Indicates how the broker should react if the exchange does + * not exist. If passive is `true` and the exhange does not exist the broker + * will respond with an error and not create the exchange; exchange is created + * otherwise. Defaults to `false` (exchange is created if needed) + * @param durable Indicates whether the exchange is durable - e.g., will it + * survive a broker restart. + * @param auto_delete Indicates whether the exchange will automatically be + * removed when no queues are bound to it. + */ + void DeclareExchange( + const std::string &exchange_name, + const std::string &exchange_type = Channel::EXCHANGE_TYPE_DIRECT, + bool passive = false, bool durable = false, bool auto_delete = false); + + /** + * Declares an exchange + * + * Creates an exchange on the AMQP broker if it does not already exist + * @param exchange_name the name of the exchange + * @param exchange_type the type of exchange to be declared. Defaults to + * `direct`; other types that could be used: `fanout`, `topic` + * @param passive Indicates how the broker should react if the exchange does + * not exist. If passive is `true` and the exhange does not exist the broker + * will respond with an error and not create the exchange; exchange is created + * otherwise. Defaults to `false` (exchange is created if needed) + * @param durable Indicates whether the exchange is durable - e.g., will it + * survive a broker restart + * @param auto_delete Indicates whether the exchange will automatically be + * removed when no queues are bound to it. + * @param arguments A table of additional arguments used when creating the + * exchange + */ + void DeclareExchange(const std::string &exchange_name, + const std::string &exchange_type, bool passive, + bool durable, bool auto_delete, const Table &arguments); + + /** + * Deletes an exchange on the AMQP broker + * + * @param exchange_name the name of the exchange to be deleted + * @param if_unused when `true`, delete the exchange only if it has no queues + * bound to it, or throw `AmqpResponseServerException`. Default `false` - + * delete regardless. + */ + void DeleteExchange(const std::string &exchange_name, bool if_unused = false); + + /** + * Binds one exchange to another exchange using a given key + * @param destination the name of the exchange to route messages to + * @param source the name of the exchange to route messages from + * @param routing_key the routing key to use when binding + */ + void BindExchange(const std::string &destination, const std::string &source, + const std::string &routing_key); + + /** + * Binds one exchange to another exchange using a given key + * @param destination the name of the exchange to route messages to + * @param source the name of the exchange to route messages from + * @param routing_key the routing key to use when binding + * @param arguments A table of additional arguments used when creating the + * binding + */ + void BindExchange(const std::string &destination, const std::string &source, + const std::string &routing_key, const Table &arguments); + + /** + * Unbind an existing exchange-exchange binding + * @see BindExchange + * @param destination the name of the exchange to route messages to + * @param source the name of the exchange to route messages from + * @param routing_key the routing key to use when binding + */ + void UnbindExchange(const std::string &destination, const std::string &source, + const std::string &routing_key); + + /** + * Unbind an existing exchange-exchange binding + * @see BindExchange + * @param destination the name of the exchange to route messages to + * @param source the name of the exchange to route messages from + * @param routing_key the routing key to use when binding + * @param arguments A table of additional arguments when unbinding the + * exchange + */ + void UnbindExchange(const std::string &destination, const std::string &source, + const std::string &routing_key, const Table &arguments); + + /** + * Checks to see if a queue exists on the broker. + * + * @param queue_name the name of the exchange to check for. + * @returns true if the exchange exists on the broker, false otherwise. + */ + bool CheckQueueExists(boost::string_ref queue_name); + + /** + * Declare a queue + * + * Creates a queue on the AMQP broker if it does not already exist. + * @param queue_name The desired name of the queue. If this is an empty + * string, the broker will generate a queue name that this method will return. + * @param passive Indicated how the broker should react if the queue does not + * exist. The broker will raise an error if the queue doesn't already exist + * and passive is `true`. With passive `false` (the default), the queue gets + * created automatically, if needed. + * @param durable Indicates whether the exchange is durable - e.g., will it + * survive a broker restart. + * @param exclusive Indicates that only client can use the queue. Defaults to + * true. An exclusive queue is deleted when the connection is closed. + * @param auto_delete the queue will be deleted after at least one exchange + * has been bound to it, then has been unbound + * @returns The name of the queue created on the broker. Used mostly when the + * broker is asked to create a unique queue by not providing a queue name. + */ + std::string DeclareQueue(const std::string &queue_name, bool passive = false, + bool durable = false, bool exclusive = true, + bool auto_delete = true); + + /** + * Declares a queue + * + * Creates a queue on the AMQP broker if it does not already exist. + * @param queue_name The desired name of the queue. If this is an empty + * string, the broker will generate a queue name that this method will return. + * @param passive Indicated how the broker should react if the queue does not + * exist. The broker will raise an error if the queue doesn't already exist + * and passive is `true`. With passive `false` (the default), the queue gets + * created automatically, if needed. + * @param durable Indicates whether the exchange is durable - e.g., will it + * survive a broker restart. + * @param exclusive Indicates that only client can use the queue. Defaults to + * true. An exclusive queue is deleted when the connection is closed. + * @param auto_delete the queue will be deleted after at least one exchange + * has been bound to it, then has been unbound + * @param arguments A table of additional arguments + * @returns The name of the queue created on the broker. Used mostly when the + * broker is asked to create a unique queue by not providing a queue name. + */ + std::string DeclareQueue(const std::string &queue_name, bool passive, + bool durable, bool exclusive, bool auto_delete, + const Table &arguments); + + /** + * Declares a queue and returns current message- and consumer counts + * + * Creates a queue on the AMQP broker if it does not already exist. + * @param queue_name The desired name of the queue. If this is an empty + * string, the broker will generate a queue name that this method will return. + * @param [out] message_count The current number of messages in the declared + * queue gets passed out through this argument. + * @param [out] consumer_count The current number of consumers of the declared + * queue gets passed out through this argument. + * @param passive Indicated how the broker should react if the queue does not + * exist. The broker will raise an error if the queue doesn't already exist + * and passive is `true`. With passive `false` (the default), the queue gets + * created automatically, if needed. + * @param durable Indicates whether the exchange is durable - e.g., will it + * survive a broker restart. + * @param exclusive Indicates that only client can use the queue. Defaults to + * true. An exclusive queue is deleted when the connection is closed. + * @param auto_delete the queue will be deleted after at least one exchange + * has been bound to it, then has been unbound + * @returns The name of the queue created on the broker. Used mostly when the + * broker is asked to create a unique queue by not providing a queue name. + */ + std::string DeclareQueueWithCounts(const std::string &queue_name, + boost::uint32_t &message_count, + boost::uint32_t &consumer_count, + bool passive = false, bool durable = false, + bool exclusive = true, + bool auto_delete = true); + + /** + * Declares a queue and returns current message- and consumer counts + * + * Creates a queue on the AMQP broker if it does not already exist. + * @param queue_name The desired name of the queue. If this is an empty + * string, the broker will generate a queue name that this method will return. + * @param [out] message_count The current number of messages in the declared + * queue gets passed out through this argument. + * @param [out] consumer_count The current number of consumers of the declared + * queue gets passed out through this argument. + * @param passive Indicated how the broker should react if the queue does not + * exist. The broker will raise an error if the queue doesn't already exist + * and passive is `true`. With passive `false` (the default), the queue gets + * created automatically, if needed. + * @param durable Indicates whether the exchange is durable - e.g., will it + * survive a broker restart. + * @param exclusive Indicates that only client can use the queue. Defaults to + * true. An exclusive queue is deleted when the connection is closed. + * @param auto_delete the queue will be deleted after at least one exchange + * has been bound to it, then has been unbound + * @param arguments A table of additional arguments + * @returns The name of the queue created on the broker. Used mostly when the + * broker is asked to create a unique queue by not providing a queue name. + */ + std::string DeclareQueueWithCounts(const std::string &queue_name, + boost::uint32_t &message_count, + boost::uint32_t &consumer_count, + bool passive, bool durable, bool exclusive, + bool auto_delete, const Table &arguments); + + /** + * Deletes a queue + * + * Removes a queue from the broker. There is no indication of whether the + * queue was actually deleted on the broker. + * @param queue_name The name of the queue to remove. + * @param if_unused Only deletes the queue if the queue doesn't have any + * active consumers. + * @param if_empty Only deletes the queue if the queue is empty. + */ + void DeleteQueue(const std::string &queue_name, bool if_unused = false, + bool if_empty = false); + + /** + * Binds a queue to an exchange + * + * Connects a queue to an exchange on the broker. + * @param queue_name The name of the queue to bind. + * @param exchange_name The name of the exchange to bind to. + * @param routing_key Defines the routing key of the binding. Only messages + * with matching routing key will be delivered to the queue from the exchange. + * Defaults to `""` which means all messages will be delivered. + */ + void BindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key = ""); + + /** + * Binds a queue to an exchange + * + * Connects a queue to an exchange on the broker. + * @param queue_name The name of the queue to bind. + * @param exchange_name The name of the exchange to bind to. + * @param routing_key Defines the routing key of the binding. Only messages + * with matching routing key will be delivered to the queue from the exchange. + * Defaults to `""` which means all messages will be delivered. + * @param arguments A table of additional arguments used when binding the + * queue + */ + void BindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key, const Table &arguments); + + /** + * Unbinds a queue from an exchange + * + * Disconnects a queue from an exchange. + * @param queue_name The name of the queue to unbind. + * @param exchange_name The name of the exchange to unbind. + * @param routing_key This must match the routing_key of the binding. + * @see BindQueue + */ + void UnbindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key = ""); + + /** + * Unbinds a queue from an exchange + * + * Disconnects a queue from an exchange. + * @param queue_name The name of the queue to unbind. + * @param exchange_name The name of the exchange to unbind. + * @param routing_key This must match the routing_key of the binding. + * @param arguments A table of additional arguments. + * @see BindQueue + */ + void UnbindQueue(const std::string &queue_name, + const std::string &exchange_name, + const std::string &routing_key, const Table &arguments); + + /** + * Purges a queue + * + * Removes all messages in a queue on the broker. The queue will become empty. + * @param queue_name The name of the queue to purge. + */ + void PurgeQueue(const std::string &queue_name); + + /** + * Acknowledges a Basic message + * + * Acknowledges a message delievered using \ref BasicGet or \ref BasicConsume. + * @param message The message that is being ack'ed. + */ + void BasicAck(const Envelope::ptr_t &message); + + /** + * Acknowledges a Basic message + * + * Acknowledges a message delivered using \ref BasicGet or \ref BasicConsume. + * This overload doesn't require the \ref Envelope object to acknowledge. + * + * @param info The `delivery-tag` of the message to acknowledge. + */ + void BasicAck(const Envelope::DeliveryInfo &info); + + /** + * Acknowledges a Basic message + * + * Acknowledges a message delivered using \ref BasicGet or \ref BasicConsume. + * This overload doesn't require the \ref Envelope object to acknowledge. + * + * @note Ack'ing multiple messages (using `multiple=true`) is + * scoped to messages delivered on a given AMQP channel. Since + * SimpleAmqpClient uses one channel per consumer, this means that + * multiple-ack will acknowledge messages up to and including the current + * message id *for a given consumer*. + * + * @param info The `delivery-tag` of the message to acknowledge. + * @param multiple If `true`, ack all messages in this channel up to this + * delivery tag. If `false`, ack only this delivery tag. + */ + void BasicAck(const Envelope::DeliveryInfo &info, bool multiple); + + /** + * Reject (NAck) a Basic message + * + * Rejects a message delivered using \ref BasicGet or \ref BasicConsume + * @param message The message that is being nack'ed. + * @param requeue Tells the broker to requeue the message or not. + * @param multiple If `true`, reject all messages in this channel up to this. + */ + void BasicReject(const Envelope::ptr_t &message, bool requeue, + bool multiple = false); + + /** + * Reject (NAck) a Basic message + * + * Rejects a message delivered using \ref BasicGet or \ref BasicConsume. + * This overload doesn't require the \ref Envelope object to Reject + * @param info The `delivery-tag` of the message to Reject. + * @param requeue Tells the broker to requeue the message or not. + * @param multiple If `true`, reject all messages in this channel up to this. + */ + void BasicReject(const Envelope::DeliveryInfo &info, bool requeue, + bool multiple = false); + + /** + * Publishes a Basic message + * + * Publishes a Basic message to an exchange + * @param exchange_name The name of the exchange to publish the message to + * @param routing_key The routing key to publish with, this is used to route + * to corresponding queue(s). + * @param message The \ref BasicMessage object to publish to the queue. + * @param mandatory Requires the message to be delivered to a queue. A + * \ref MessageReturnedException is thrown if the message cannot be routed to + * a queue. + * @param immediate Requires the message to be both routed to a queue, and + * immediately delivered to a consumer. If the message is not routed, or a + * consumer cannot immediately deliver the message, + * a \ref MessageReturnedException is thrown. This has no effect when using + * RabbitMQ v3.0 and newer. + */ + void BasicPublish(const std::string &exchange_name, + const std::string &routing_key, + const BasicMessage::ptr_t message, bool mandatory = false, + bool immediate = false); + + /** + * Synchronously consume a message from a queue + * + * This function will not wait for a message to arrive in a queue, it will + * return `false` if the queue is empty. + * @note This function effectively polls the broker for a message; in general, + * better performance is realized using \ref BasicConsume / \ref + * BasicConsumeMessage. + * @param [out] message A message envelope pointer that will be populated if a + * message is delivered. + * @param queue The name of the queue to try to get the message from. + * @param no_ack Can the message be un-ack'ed. Default `true` (message does + * not need to be acked). + * @returns `true` if a message was delivered, `false` if the queue was empty. + */ + bool BasicGet(Envelope::ptr_t &message, const std::string &queue, + bool no_ack = true); + + /** + * Redeliver unacknowledged messages from the broker + * @param consumer The consumer to recover message from + */ + void BasicRecover(const std::string &consumer); + + /** + * Starts consuming Basic messages on a queue + * + * Subscribes as a consumer to a queue, so all future messages on a queue + * will be Basic.Delivered + * @note Due to a limitation to how things are done, it is only possible to + * reliably have **a single consumer per channel**; calling this + * more than once per channel may result in undefined results. + * @param queue The name of the queue to subscribe to. + * @param consumer_tag The name of the consumer. This is used to do + * operations with a consumer. + * @param no_local Defaults to true + * @param no_ack If `true`, ack'ing the message is automatically done when the + * message is delivered. Defaults to `true` (message does not have to be + * ack'ed). + * @param exclusive Means only this consumer can access the queue. + * @param message_prefetch_count Number of unacked messages the broker will + * deliver. Setting this to more than 1 will allow the broker to deliver + * messages while a current message is being processed. A value of + * 0 means no limit. This option is ignored if `no_ack = true`. + * @returns the consumer tag + */ + std::string BasicConsume(const std::string &queue, + const std::string &consumer_tag = "", + bool no_local = true, bool no_ack = true, + bool exclusive = true, + boost::uint16_t message_prefetch_count = 1); + + /** + * Starts consuming Basic messages on a queue + * + * Subscribes as a consumer to a queue, so all future messages on a queue + * will be Basic.Delivered + * @note Due to a limitation to how things are done, it is only possible to + * reliably have **a single consumer per channel**; calling this + * more than once per channel may result in undefined results. + * @param queue The name of the queue to subscribe to. + * @param consumer_tag The name of the consumer. This is used to do + * operations with a consumer. + * @param no_local Defaults to true + * @param no_ack If `true`, ack'ing the message is automatically done when the + * message is delivered. Defaults to `true` (message does not have to be + * ack'ed). + * @param exclusive Means only this consumer can access the queue. + * @param message_prefetch_count Number of unacked messages the broker will + * deliver. Setting this to more than 1 will allow the broker to deliver + * messages while a current message is being processed. A value of + * 0 means no limit. This option is ignored if `no_ack = true`. + * @param arguments A table of additional arguments when creating the consumer + * @returns the consumer tag + */ + std::string BasicConsume(const std::string &queue, + const std::string &consumer_tag, bool no_local, + bool no_ack, bool exclusive, + boost::uint16_t message_prefetch_count, + const Table &arguments); + + /** + * Modify consumer's message prefetch count + * + * Sets the number of unacknowledged messages that will be delivered + * by the broker to a consumer. + * + * Has no effect for consumer with `no_ack` set. + * + * @param consumer_tag The consumer tag to adjust the prefetch for. + * @param message_prefetch_count The number of unacknowledged message the + * broker will deliver. A value of 0 means no limit. + */ + void BasicQos(const std::string &consumer_tag, + boost::uint16_t message_prefetch_count); + + /** + * Cancels a previously created Consumer + * + * Unsubscribes a consumer from a queue. In other words undoes what + * \ref BasicConsume does. + * @param consumer_tag The same `consumer_tag` used when the consumer was + * created with \ref BasicConsume. + * @see BasicConsume + */ + void BasicCancel(const std::string &consumer_tag); + + /** + * Consumes a single message + * + * Waits for a single Basic message to be Delivered. + * + * This function only works after `BasicConsume` has successfully been called. + * + * @param consumer_tag Consumer ID (returned from \ref BasicConsume). + * @returns The next message on the queue + */ + Envelope::ptr_t BasicConsumeMessage(const std::string &consumer_tag); + + /** + * Consumes a single message from multiple consumers + * + * Waits for a single message to be delivered from a list of consumers. + * + * This function only works after \ref BasicConsume has been called. + * + * @returns The next message delivered from the broker + */ + Envelope::ptr_t BasicConsumeMessage( + const std::vector &consumer_tags); + + /** + * Consumes a single message from any open consumers + * + * Waits for a message from any consumer open on this Channel object. + * + * This function only works after \ref BasicConsume has been called. + * + * @returns The next message delivered from the broker + */ + Envelope::ptr_t BasicConsumeMessage(); + + /** + * Consumes a single message with a timeout (gets an envelope object) + * + * Waits for a single Basic message to be Delivered or the timeout to expire. + * + * This function only works after \ref BasicConsume as been successfully + * called. + * + * This function returns an envelope object which contains more information + * about the message delivered. + * + * @param consumer_tag Consumer ID (returned from \ref BasicConsume). + * @param [out] envelope The message object to save to. Empty pointer is ok. + * @param timeout Timeout, in ms, for the first part of the message to be + * delivered + * @throws MessageReturnedException If a `basic.return` is received while + * waiting for a message. + * @returns `false` on timeout, `true` on message delivery + */ + bool BasicConsumeMessage(const std::string &consumer_tag, + Envelope::ptr_t &envelope, int timeout = -1); + + /** + * Consumes a single message with a timeout from multiple consumers + * + * Waits for a single message to be delivered to one of the listed consumer + * tags, or for the timeout to expire. + * + * This function only works after `BasicConsume` has been successfully called. + * + * @param consumer_tags A list of the consumer tags to wait from. + * @param [out] envelope The message object that is delivered. + * @param timeout The timeout in milliseconds for the message to be + * delivered. 0 works like a non-blocking read, -1 is an infinite timeout. + * @returns `true` on message delivery, `false` on timeout. + */ + bool BasicConsumeMessage(const std::vector &consumer_tags, + Envelope::ptr_t &envelope, int timeout = -1); + + /** + * Consumes a single message from any consumer on a Channel + * + * Waits for a single message to be delivered to one of the consumers opened + * on this Channel, or for the timeout to occur. + * + * This function only works after \ref BasicConsume has been successfully + * called. + * + * @param [out] envelope The message object that is delivered. + * @param timeout The timeout in milliseconds for the message to be delivered. + * 0 works like a non-blocking read, -1 is an infinite timeout. + * @returns `true` on message delivery, `false` on timeout. + */ + bool BasicConsumeMessage(Envelope::ptr_t &envelope, int timeout = -1); + + private: + static ChannelImpl *OpenChannel(const std::string &host, int port, + const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max, + bool sasl_external); + + static ChannelImpl *OpenSecureChannel(const std::string &host, int port, + const std::string &username, + const std::string &password, + const std::string &vhost, int frame_max, + const OpenOpts::TLSParams &tls_params, + bool sasl_external); + + /// PIMPL idiom + boost::scoped_ptr m_impl; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_CHANNEL_H diff --git a/src/SimpleAmqpClient/ChannelImpl.h b/src/SimpleAmqpClient/ChannelImpl.h new file mode 100644 index 0000000..62af935 --- /dev/null +++ b/src/SimpleAmqpClient/ChannelImpl.h @@ -0,0 +1,369 @@ +#ifndef SIMPLEAMQPCLIENT_CHANNELIMPL_H +#define SIMPLEAMQPCLIENT_CHANNELIMPL_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +// Put these first to avoid warnings about INT#_C macro redefinition +#include +#include + +#include + +#include "SimpleAmqpClient/AmqpException.h" +#include "SimpleAmqpClient/BasicMessage.h" +#include "SimpleAmqpClient/Channel.h" +#include "SimpleAmqpClient/ConsumerCancelledException.h" +#include "SimpleAmqpClient/Envelope.h" +#include "SimpleAmqpClient/MessageReturnedException.h" +#define BOOST_BIND_GLOBAL_PLACEHOLDERS +#include +#include +#include +#include +#include + +namespace AmqpClient { + +class Channel::ChannelImpl : boost::noncopyable { + public: + ChannelImpl(); + virtual ~ChannelImpl(); + + typedef std::vector channel_list_t; + typedef std::vector frame_queue_t; + typedef std::map channel_map_t; + typedef channel_map_t::iterator channel_map_iterator_t; + + void DoLogin(const std::string &username, const std::string &password, + const std::string &vhost, int frame_max, + bool sasl_external = false); + amqp_channel_t GetChannel(); + void ReturnChannel(amqp_channel_t channel); + bool IsChannelOpen(amqp_channel_t channel); + + bool GetNextFrameFromBroker(amqp_frame_t &frame, + boost::chrono::microseconds timeout); + + bool CheckForQueuedMessageOnChannel(amqp_channel_t message_on_channel) const; + void AddToFrameQueue(const amqp_frame_t &frame); + + template + bool GetNextFrameFromBrokerOnChannel(const ChannelListType channels, + amqp_frame_t &frame_out, + boost::chrono::microseconds timeout = + boost::chrono::microseconds::max()) { + boost::chrono::steady_clock::time_point end_point; + boost::chrono::microseconds timeout_left = timeout; + if (timeout != boost::chrono::microseconds::max()) { + end_point = boost::chrono::steady_clock::now() + timeout; + } + + amqp_frame_t frame; + while (GetNextFrameFromBroker(frame, timeout_left)) { + if (channels.end() != + std::find(channels.begin(), channels.end(), frame.channel)) { + frame_out = frame; + return true; + } + + if (frame.channel == 0) { + // Only thing we care to handle on the channel0 is the connection.close + // method + if (AMQP_FRAME_METHOD == frame.frame_type && + AMQP_CONNECTION_CLOSE_METHOD == frame.payload.method.id) { + FinishCloseConnection(); + AmqpException::Throw(*reinterpret_cast( + frame.payload.method.decoded)); + } + } else { + AddToFrameQueue(frame); + } + + if (timeout != boost::chrono::microseconds::max()) { + boost::chrono::steady_clock::time_point now = + boost::chrono::steady_clock::now(); + if (now >= end_point) { + return false; + } + timeout_left = + boost::chrono::duration_cast( + end_point - now); + } + } + return false; + } + + bool GetNextFrameOnChannel( + amqp_channel_t channel, amqp_frame_t &frame, + boost::chrono::microseconds timeout = boost::chrono::microseconds::max()); + + static bool is_on_channel(const amqp_frame_t frame, amqp_channel_t channel) { + return channel == frame.channel; + } + + static bool is_frame_type_on_channel(const amqp_frame_t frame, + uint8_t frame_type, + amqp_channel_t channel) { + return frame.frame_type == frame_type && frame.channel == channel; + } + + static bool is_method_on_channel(const amqp_frame_t frame, + amqp_method_number_t method, + amqp_channel_t channel) { + return frame.channel == channel && frame.frame_type == AMQP_FRAME_METHOD && + frame.payload.method.id == method; + } + + template + static bool is_expected_method_on_channel( + const amqp_frame_t &frame, const ChannelListType channels, + const ResponseListType &expected_responses) { + return channels.end() != + std::find(channels.begin(), channels.end(), frame.channel) && + AMQP_FRAME_METHOD == frame.frame_type && + expected_responses.end() != std::find(expected_responses.begin(), + expected_responses.end(), + frame.payload.method.id); + } + + template + bool GetMethodOnChannel(const ChannelListType channels, amqp_frame_t &frame, + const ResponseListType &expected_responses, + boost::chrono::microseconds timeout = + boost::chrono::microseconds::max()) { + frame_queue_t::iterator desired_frame = std::find_if( + m_frame_queue.begin(), m_frame_queue.end(), + boost::bind( + &ChannelImpl::is_expected_method_on_channel, + _1, channels, expected_responses)); + + if (m_frame_queue.end() != desired_frame) { + frame = *desired_frame; + m_frame_queue.erase(desired_frame); + return true; + } + + boost::chrono::steady_clock::time_point end_point; + boost::chrono::microseconds timeout_left = timeout; + if (timeout != boost::chrono::microseconds::max()) { + end_point = boost::chrono::steady_clock::now() + timeout; + } + + amqp_frame_t incoming_frame; + while (GetNextFrameFromBrokerOnChannel(channels, incoming_frame, + timeout_left)) { + if (is_expected_method_on_channel(incoming_frame, channels, + expected_responses)) { + frame = incoming_frame; + return true; + } + if (AMQP_FRAME_METHOD == incoming_frame.frame_type && + AMQP_CHANNEL_CLOSE_METHOD == incoming_frame.payload.method.id) { + FinishCloseChannel(incoming_frame.channel); + try { + AmqpException::Throw(*reinterpret_cast( + incoming_frame.payload.method.decoded)); + } catch (AmqpException &) { + MaybeReleaseBuffersOnChannel(incoming_frame.channel); + throw; + } + } + m_frame_queue.push_back(incoming_frame); + + if (timeout != boost::chrono::microseconds::max()) { + boost::chrono::steady_clock::time_point now = + boost::chrono::steady_clock::now(); + if (now >= end_point) { + return false; + } + timeout_left = + boost::chrono::duration_cast( + end_point - now); + } + } + return false; + } + + template + amqp_frame_t DoRpcOnChannel(amqp_channel_t channel, boost::uint32_t method_id, + void *decoded, + const ResponseListType &expected_responses) { + CheckForError(amqp_send_method(m_connection, channel, method_id, decoded)); + + amqp_frame_t response; + boost::array channels = {{channel}}; + + GetMethodOnChannel(channels, response, expected_responses); + return response; + } + + template + amqp_frame_t DoRpc(boost::uint32_t method_id, void *decoded, + const ResponseListType &expected_responses) { + amqp_channel_t channel = GetChannel(); + amqp_frame_t ret = + DoRpcOnChannel(channel, method_id, decoded, expected_responses); + ReturnChannel(channel); + return ret; + } + + template + static bool envelope_on_channel(const Envelope::ptr_t &envelope, + const ChannelListType channels) { + return channels.end() != std::find(channels.begin(), channels.end(), + envelope->DeliveryChannel()); + } + + template + bool ConsumeMessageOnChannel(const ChannelListType channels, + Envelope::ptr_t &message, int timeout) { + envelope_list_t::iterator it = std::find_if( + m_delivered_messages.begin(), m_delivered_messages.end(), + boost::bind(ChannelImpl::envelope_on_channel, _1, + channels)); + + if (it != m_delivered_messages.end()) { + message = *it; + m_delivered_messages.erase(it); + return true; + } + + return ConsumeMessageOnChannelInner(channels, message, timeout); + } + + template + bool ConsumeMessageOnChannelInner(const ChannelListType channels, + Envelope::ptr_t &message, int timeout) { + const boost::array DELIVER_OR_CANCEL = { + {AMQP_BASIC_DELIVER_METHOD, AMQP_BASIC_CANCEL_METHOD}}; + + boost::chrono::microseconds real_timeout = + (timeout >= 0 ? boost::chrono::milliseconds(timeout) + : boost::chrono::microseconds::max()); + + amqp_frame_t deliver; + if (!GetMethodOnChannel(channels, deliver, DELIVER_OR_CANCEL, + real_timeout)) { + return false; + } + + if (AMQP_BASIC_CANCEL_METHOD == deliver.payload.method.id) { + amqp_basic_cancel_t *cancel_method = + reinterpret_cast( + deliver.payload.method.decoded); + std::string consumer_tag((char *)cancel_method->consumer_tag.bytes, + cancel_method->consumer_tag.len); + + RemoveConsumer(consumer_tag); + ReturnChannel(deliver.channel); + MaybeReleaseBuffersOnChannel(deliver.channel); + + throw ConsumerCancelledException(consumer_tag); + } + + amqp_basic_deliver_t *deliver_method = + reinterpret_cast( + deliver.payload.method.decoded); + + const std::string exchange((char *)deliver_method->exchange.bytes, + deliver_method->exchange.len); + const std::string routing_key((char *)deliver_method->routing_key.bytes, + deliver_method->routing_key.len); + const std::string in_consumer_tag( + (char *)deliver_method->consumer_tag.bytes, + deliver_method->consumer_tag.len); + const boost::uint64_t delivery_tag = deliver_method->delivery_tag; + const bool redelivered = (deliver_method->redelivered == 0 ? false : true); + MaybeReleaseBuffersOnChannel(deliver.channel); + + BasicMessage::ptr_t content = ReadContent(deliver.channel); + MaybeReleaseBuffersOnChannel(deliver.channel); + + message = Envelope::Create(content, in_consumer_tag, delivery_tag, exchange, + redelivered, routing_key, deliver.channel); + return true; + } + + amqp_channel_t CreateNewChannel(); + amqp_channel_t GetNextChannelId(); + + void CheckRpcReply(amqp_channel_t channel, const amqp_rpc_reply_t &reply); + void CheckForError(int ret); + + void CheckFrameForClose(amqp_frame_t &frame, amqp_channel_t channel); + void FinishCloseChannel(amqp_channel_t channel); + void FinishCloseConnection(); + + MessageReturnedException CreateMessageReturnedException( + amqp_basic_return_t &return_method, amqp_channel_t channel); + AmqpClient::BasicMessage::ptr_t ReadContent(amqp_channel_t channel); + + void AddConsumer(const std::string &consumer_tag, amqp_channel_t channel); + amqp_channel_t RemoveConsumer(const std::string &consumer_tag); + amqp_channel_t GetConsumerChannel(const std::string &consumer_tag); + std::vector GetAllConsumerChannels() const; + + void MaybeReleaseBuffersOnChannel(amqp_channel_t channel); + void CheckIsConnected(); + void SetIsConnected(bool state) { m_is_connected = state; } + + // The RabbitMQ broker changed the way that basic.qos worked as of v3.3.0. + // See: http://www.rabbitmq.com/consumer-prefetch.html + // Newer versions of RabbitMQ basic.qos.global set to false applies to new + // consumers made on the channel, and true applies to all consumers on the + // channel (not connection). + bool BrokerHasNewQosBehavior() const { return 0x030300 <= m_brokerVersion; } + + amqp_connection_state_t m_connection; + + private: + static boost::uint32_t ComputeBrokerVersion( + const amqp_connection_state_t state); + + frame_queue_t m_frame_queue; + + typedef std::vector envelope_list_t; + envelope_list_t m_delivered_messages; + + typedef std::map consumer_map_t; + consumer_map_t m_consumer_channel_map; + + enum channel_state_t { CS_Closed = 0, CS_Open, CS_Used }; + typedef std::vector channel_state_list_t; + + channel_state_list_t m_channels; + boost::uint32_t m_brokerVersion; + // A channel that is likely to be an CS_Open state + amqp_channel_t m_last_used_channel; + + bool m_is_connected; +}; + +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_CHANNELIMPL_H diff --git a/src/SimpleAmqpClient/ConnectionClosedException.h b/src/SimpleAmqpClient/ConnectionClosedException.h new file mode 100644 index 0000000..257d0ba --- /dev/null +++ b/src/SimpleAmqpClient/ConnectionClosedException.h @@ -0,0 +1,51 @@ +#ifndef SIMPLEAMQPCLIENT_CONNECTIONCLOSEDEXCEPTION_H +#define SIMPLEAMQPCLIENT_CONNECTIONCLOSEDEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "SimpleAmqpClient/Util.h" + +/// @file SimpleAmqpClient/ConnectionClosedException.h +/// Defines AmqpClient::ConnectionClosedException + +namespace AmqpClient { + +/** + * "Connection is closed" exception + */ +class SIMPLEAMQPCLIENT_EXPORT ConnectionClosedException + : public std::runtime_error { + public: + /// Constructor + explicit ConnectionClosedException() + : std::runtime_error("Connection is closed") {} +}; +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_CONNECTIONCLOSEDEXCEPTION_H diff --git a/src/SimpleAmqpClient/ConsumerCancelledException.h b/src/SimpleAmqpClient/ConsumerCancelledException.h new file mode 100644 index 0000000..5631c31 --- /dev/null +++ b/src/SimpleAmqpClient/ConsumerCancelledException.h @@ -0,0 +1,67 @@ +#ifndef SIMPLEAMQPCLIENT_CONSUMERCANCELLEDEXCEPTION_H +#define SIMPLEAMQPCLIENT_CONSUMERCANCELLEDEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include + +#include "SimpleAmqpClient/Util.h" + +/// @file SimpleAmqpClient/ConsumerCancelledException.h +/// Defines AmqpClient::ConsumerCancelledException + +namespace AmqpClient { + +/** + * "Consumer was cancelled" exception + * + * Happens when the server ends a consumer subscription, e.g. when the + * subscribed queue is being deleted, or when a client issues basic.cancel + * request. + */ +class SIMPLEAMQPCLIENT_EXPORT ConsumerCancelledException + : public std::runtime_error { + public: + /// Constructor + explicit ConsumerCancelledException(const std::string &consumer_tag) throw() + : std::runtime_error( + std::string("Consumer was cancelled: ").append(consumer_tag)), + m_consumer_tag(consumer_tag) {} + + /// Destructor + virtual ~ConsumerCancelledException() throw() {} + + /// Getter of the consumer tag + std::string GetConsumerTag() const { return m_consumer_tag; } + + private: + std::string m_consumer_tag; +}; +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_CONSUMERCANCELLEDEXCEPTION_H diff --git a/src/SimpleAmqpClient/ConsumerTagNotFoundException.h b/src/SimpleAmqpClient/ConsumerTagNotFoundException.h new file mode 100644 index 0000000..0c1c991 --- /dev/null +++ b/src/SimpleAmqpClient/ConsumerTagNotFoundException.h @@ -0,0 +1,61 @@ +#ifndef SIMPLEAMQPCLIENT_CONSUMERTAGNOTFOUND_H +#define SIMPLEAMQPCLIENT_CONSUMERTAGNOTFOUND_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/ConsumerTagNotFoundException.h +/// Defines AmqpClient::ConsumerTagNotFoundException + +namespace AmqpClient { + +/** + * "Consumer tag not found" exception + * + * @see BasicConsume + */ +class SIMPLEAMQPCLIENT_EXPORT ConsumerTagNotFoundException + : public std::runtime_error { + public: + /// Constructor + ConsumerTagNotFoundException() throw() + : std::runtime_error("The specified consumer tag is unknown") {} + /// Destructor + virtual ~ConsumerTagNotFoundException() throw() {} +}; + +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_CONSUMERTAGNOTFOUND_H diff --git a/src/SimpleAmqpClient/Envelope.h b/src/SimpleAmqpClient/Envelope.h new file mode 100644 index 0000000..b112e30 --- /dev/null +++ b/src/SimpleAmqpClient/Envelope.h @@ -0,0 +1,211 @@ +#ifndef SIMPLEAMQPCLIENT_ENVELOPE_H +#define SIMPLEAMQPCLIENT_ENVELOPE_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/BasicMessage.h" +#include "SimpleAmqpClient/Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4275 4251) +#endif // _MSC_VER + +/// @file SimpleAmqpClient/Envelope.h +/// The AmqpClient::Envelope class is defined in this header file. + +namespace AmqpClient { + +/** + * A "message envelope" object containing the message body and delivery metadata + */ +class SIMPLEAMQPCLIENT_EXPORT Envelope : boost::noncopyable { + public: + /// a `shared_ptr` pointer to Envelope + typedef boost::shared_ptr ptr_t; + + /** + * Creates an new envelope object + * @param message the payload + * @param consumer_tag the consumer tag the message was delivered to + * @param delivery_tag the delivery tag that the broker assigned to the + * message + * @param exchange the name of the exchange that the message was published to + * @param redelivered a flag indicating whether the message consumed as a + * result of a redelivery + * @param routing_key the routing key that the message was published with + * @param delivery_channel channel ID of the delivery (see DeliveryInfo) + * @returns a boost::shared_ptr to an envelope object + */ + static ptr_t Create(const BasicMessage::ptr_t message, + const std::string &consumer_tag, + const boost::uint64_t delivery_tag, + const std::string &exchange, bool redelivered, + const std::string &routing_key, + const boost::uint16_t delivery_channel) { + return boost::make_shared(message, consumer_tag, delivery_tag, + exchange, redelivered, routing_key, + delivery_channel); + } + + /** + * Construct a new Envelope object + * @param message the payload + * @param consumer_tag the consumer tag the message was delivered to + * @param delivery_tag the delivery tag that the broker assigned to the + * message + * @param exchange the name of the exchange that the message was published to + * @param redelivered a flag indicating whether the message consumed as a + * result of a redelivery + * @param routing_key the routing key that the message was published with + * @param delivery_channel channel ID of the delivery (see DeliveryInfo) + */ + explicit Envelope(const BasicMessage::ptr_t message, + const std::string &consumer_tag, + const boost::uint64_t delivery_tag, + const std::string &exchange, bool redelivered, + const std::string &routing_key, + const boost::uint16_t delivery_channel); + + public: + /** + * destructor + */ + virtual ~Envelope(); + + /** + * Get the payload of the envelope + * + * @returns the message + */ + inline BasicMessage::ptr_t Message() const { return m_message; } + + /** + * Get the consumer tag for the consumer that delivered the message + * + * @returns the consumer that delivered the message + */ + inline std::string ConsumerTag() const { return m_consumerTag; } + + /** + * Get the delivery tag for the message. + * + * The delivery tag is a unique tag for a given message assigned by the + * broker + * This tag is used when Ack'ing a message + * + * @returns the delivery tag for a message + */ + inline boost::uint64_t DeliveryTag() const { return m_deliveryTag; } + + /** + * Get the name of the exchange that the message was published to + * + * @returns the name of the exchange the message was published to + */ + inline std::string Exchange() const { return m_exchange; } + + /** + * Get the flag that indicates whether the message was redelivered + * + * A flag that indicates whether the message was redelievered means + * the broker tried to deliver the message and the client did not Ack + * the message, so the message was requeued, or the client asked the broker + * to Recover which forced all non-Acked messages to be redelivered + * + * @return a boolean flag indicating whether the message was redelivered + */ + inline bool Redelivered() const { return m_redelivered; } + + /** + * Get the routing key that the message was published with + * + * @returns a string containing the routing key the message was published + * with + */ + inline std::string RoutingKey() const { return m_routingKey; } + + /** + * Get the delivery channel + */ + inline boost::uint16_t DeliveryChannel() const { return m_deliveryChannel; } + + /** + * A POD carrier of delivery-tag + * + * This is server-assigned and channel-specific. + * + * The delivery tag is valid only within the channel from which the message + * was received. I.e. a client MUST NOT receive a message on one channel and + * then acknowledge it on another. + * + * The server MUST NOT use a zero value for delivery tags. Zero is reserved + * for client use, meaning "all messages so far received". + */ + struct DeliveryInfo { + /// A delivery tag, assigned by the broker to identify this delivery within + /// a channel + boost::uint64_t delivery_tag; + /// An ID of the delivery channel + boost::uint16_t delivery_channel; + }; + + /** + * Getter of the delivery-tag + */ + inline DeliveryInfo GetDeliveryInfo() const { + DeliveryInfo info; + info.delivery_tag = m_deliveryTag; + info.delivery_channel = m_deliveryChannel; + + return info; + } + + private: + const BasicMessage::ptr_t m_message; + const std::string m_consumerTag; + const boost::uint64_t m_deliveryTag; + const std::string m_exchange; + const bool m_redelivered; + const std::string m_routingKey; + const boost::uint16_t m_deliveryChannel; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +#endif // SIMPLEAMQPCLIENT_ENVELOPE_H diff --git a/src/SimpleAmqpClient/MessageRejectedException.h b/src/SimpleAmqpClient/MessageRejectedException.h new file mode 100644 index 0000000..8d4f8b4 --- /dev/null +++ b/src/SimpleAmqpClient/MessageRejectedException.h @@ -0,0 +1,70 @@ +#ifndef SIMPLEAMQPCLIENT_MESSAGEREJECTEDEXCEPTION_H +#define SIMPLEAMQPCLIENT_MESSAGEREJECTEDEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include + +#include "SimpleAmqpClient/BasicMessage.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/MessageReturnedException.h +/// Defines AmqpClient::MessageReturnedException + +namespace AmqpClient { + +/// "Message rejected" exception +class SIMPLEAMQPCLIENT_EXPORT MessageRejectedException + : public std::runtime_error { + public: + MessageRejectedException(uint64_t delivery_tag) + : std::runtime_error( + std::string("Message rejected: ") + .append(boost::lexical_cast(delivery_tag))), + m_delivery_tag(delivery_tag) {} + + /// `delivery_tag` getter + uint64_t GetDeliveryTag() { return m_delivery_tag; } + + private: + uint64_t m_delivery_tag; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_MESSAGEREJECTEDEXCEPTION_H diff --git a/src/SimpleAmqpClient/MessageReturnedException.h b/src/SimpleAmqpClient/MessageReturnedException.h new file mode 100644 index 0000000..1b67272 --- /dev/null +++ b/src/SimpleAmqpClient/MessageReturnedException.h @@ -0,0 +1,85 @@ +#ifndef SIMPLEAMQPCLIENT_MESSAGERETURNEDEXCEPTION_H +#define SIMPLEAMQPCLIENT_MESSAGERETURNEDEXCEPTION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include + +#include "SimpleAmqpClient/BasicMessage.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4275) +#endif + +/// @file SimpleAmqpClient/MessageReturnedException.h +/// Defines AmqpClient::MessageReturnedException + +namespace AmqpClient { + +/// "Message returned" exception. +class SIMPLEAMQPCLIENT_EXPORT MessageReturnedException + : public std::runtime_error { + public: + /// Constructor. + explicit MessageReturnedException(BasicMessage::ptr_t message, + boost::uint32_t reply_code, + const std::string &reply_text, + const std::string &exchange, + const std::string &routing_key) throw(); + + virtual ~MessageReturnedException() throw() {} + + /// `message` getter + BasicMessage::ptr_t message() const throw() { return m_message; } + /// `reply_code` getter + boost::uint32_t reply_code() const throw() { return m_reply_code; } + /// `reply_text` getter + std::string reply_text() const throw() { return m_reply_text; } + /// Exchange name getter + std::string exchange() const throw() { return m_exchange; } + /// Routing key getter + std::string routing_key() const throw() { return m_routing_key; } + + private: + BasicMessage::ptr_t m_message; + boost::uint32_t m_reply_code; + std::string m_reply_text; + std::string m_exchange; + std::string m_routing_key; + mutable std::string m_what; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_MESSAGERETURNEDEXCEPTION_H diff --git a/src/SimpleAmqpClient/SimpleAmqpClient.h b/src/SimpleAmqpClient/SimpleAmqpClient.h new file mode 100644 index 0000000..6185c3a --- /dev/null +++ b/src/SimpleAmqpClient/SimpleAmqpClient.h @@ -0,0 +1,49 @@ +#ifndef SIMPLEAMQPCLIENT_SIMPLEAMQPCLIENT_H +#define SIMPLEAMQPCLIENT_SIMPLEAMQPCLIENT_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +/// @file SimpleAmqpClient/SimpleAmqpClient.h +/// This "include all" header file re-exports all of SimpleAmqpClient public API + +#include "SimpleAmqpClient/AmqpException.h" +#include "SimpleAmqpClient/AmqpLibraryException.h" +#include "SimpleAmqpClient/AmqpResponseLibraryException.h" +#include "SimpleAmqpClient/BadUriException.h" +#include "SimpleAmqpClient/BasicMessage.h" +#include "SimpleAmqpClient/Channel.h" +#include "SimpleAmqpClient/ConnectionClosedException.h" +#include "SimpleAmqpClient/ConsumerCancelledException.h" +#include "SimpleAmqpClient/ConsumerTagNotFoundException.h" +#include "SimpleAmqpClient/Envelope.h" +#include "SimpleAmqpClient/MessageRejectedException.h" +#include "SimpleAmqpClient/MessageReturnedException.h" +#include "SimpleAmqpClient/Table.h" +#include "SimpleAmqpClient/Version.h" + +#endif // SIMPLEAMQPCLIENT_SIMPLEAMQPCLIENT_H diff --git a/src/SimpleAmqpClient/Table.h b/src/SimpleAmqpClient/Table.h new file mode 100644 index 0000000..ee41af8 --- /dev/null +++ b/src/SimpleAmqpClient/Table.h @@ -0,0 +1,501 @@ +#ifndef SIMPLEAMQPCLIENT_TABLE_H +#define SIMPLEAMQPCLIENT_TABLE_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/Util.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) +#endif + +/// @file SimpleAmqpClient/Table.h +/// The AmqpClient::TableValue variant is defined in this header file + +namespace AmqpClient { + +namespace Detail { +class TableValueImpl; +} // namespace Detail + +/** + * Table key + * + * Note this must be less than 128 bytes long + */ +typedef std::string TableKey; + +class TableValue; + +/** + * Array field value + */ +typedef std::vector Array; + +/** + * Field table + * + * Is just an STL map + */ +typedef std::map Table; + +typedef Table::value_type TableEntry; + +/** + * A variant type for the Table Value + */ +class SIMPLEAMQPCLIENT_EXPORT TableValue { + public: + friend class Detail::TableValueImpl; + + /** Types enumeration */ + enum ValueType { + VT_void = 0, ///< void type + VT_bool = 1, ///< boolean type + VT_int8 = 2, ///< 1-byte/char signed type + VT_int16 = 3, ///< 2-byte/short signed type + VT_int32 = 4, ///< 4-byte/int signed type + VT_int64 = 5, ///< 8-byte/long long int signed type + VT_float = 6, ///< single-precision floating point type + VT_double = 7, ///< double-precision floating point type + VT_string = 8, ///< string type + VT_array = 9, ///< array of TableValues type + VT_table = 10, ///< a table type + VT_uint8 = 11, ///< 1-byte/char unsigned type + VT_uint16 = 12, ///< 2-byte/short unsigned type + VT_uint32 = 13, ///< 4-byte/int unsigned type + VT_timestamp = 14, ///< std::time_t type + }; + + /** + * Construct void table value + * + * A table value that doesn't have any value associated with it + */ + TableValue(); + + /** + * Construct a boolean table value + * + * @param [in] value the value + */ + TableValue(bool value); + + /** + * Construct a 1-byte unsigned integer value + * + * @param [in] value the value + */ + TableValue(boost::uint8_t value); + + /** + * Construct a 1-byte signed integer value + * + * @param [in] value the value + */ + TableValue(boost::int8_t value); + + /** + * Construct a 2-byte unsigned integer value + * + * @param [in] value the value + */ + TableValue(boost::uint16_t value); + + /** + * Construct a 2-byte signed integer value + * + * @param [in] value the value + */ + TableValue(boost::int16_t value); + + /** + * Construct a 4-byte unsigned integer value + * + * @param [in] value the value + */ + TableValue(boost::uint32_t value); + + /** + * Construct a 4-byte signed integer value + * + * @param [in] value the value + */ + TableValue(boost::int32_t value); + + private: + /** + * Private + * + * RabbitMQ does not support unsigned 64-bit values in tables, + * however, timestamps are used for this. + */ + TableValue(boost::uint64_t value); + + public: + /** + * Construct an AMQP timestamp TableValue + * + * This seconds since epoch. + * + * @param [in] value the value + */ + static TableValue Timestamp(std::time_t value); + + /** + * Construct a 8-byte signed integer value + * + * @param [in] value the value + */ + TableValue(boost::int64_t value); + + /** + * Construct a single-precision floating point value + * + * @param [in] value the value + */ + TableValue(float value); + + /** + * Construct a double-precision floating point value + * + * @param [in] value the value + */ + TableValue(double value); + + /** + * Construct a character string value + * + * @param [in] value the value + */ + TableValue(const char *value); + + /** + * Construct a character string value + * + * @param [in] value the value + */ + TableValue(const std::string &value); + + /** + * Construct an array value + * + * @param [in] values the value + */ + TableValue(const std::vector &values); + + /** + * Construct a Table value + * + * @param [in] value the value + */ + TableValue(const Table &value); + + /** + * Copy-constructor + */ + TableValue(const TableValue &l); + + /** + * Assignment operator + */ + TableValue &operator=(const TableValue &l); + + /** + * Equality operator + */ + bool operator==(const TableValue &l) const; + + /** + * In-equality operator + */ + bool operator!=(const TableValue &l) const; + + /** + * Destructor + */ + virtual ~TableValue(); + + /** + * Get the type + */ + ValueType GetType() const; + + /** + * Get the boolean value + * + * @returns the value if its a VT_bool type, false otherwise + */ + bool GetBool() const; + + /** + * Get the uint8 value + * + * @returns the value if its a VT_uint8 type, 0 otherwise + */ + boost::uint8_t GetUint8() const; + + /** + * Get the int8 value + * + * @returns the value if its a VT_int8 type, 0 otherwise + */ + boost::int8_t GetInt8() const; + + /** + * Get the uint16 value + * + * @returns the value if its a VT_uint16 type, 0 otherwise + */ + boost::uint16_t GetUint16() const; + + /** + * Get the int16 value + * + * @returns the value if its a VT_int16 type, 0 otherwise + */ + boost::int16_t GetInt16() const; + + /** + * Get the uint32 value + * + * @returns the value if its a VT_uint32 type, 0 otherwise + */ + boost::uint32_t GetUint32() const; + + /** + * Get the int32 value + * + * @returns the value if its a VT_int32 type, 0 otherwise + */ + boost::int32_t GetInt32() const; + + /** + * Get the uint64 value + * + * @returns the value if its a VT_uint64 type, 0 otherwise + */ + boost::uint64_t GetUint64() const; + + /** + * Get the timestamp value + * + * @returns the value if its a VT_timestamp type, 0 otherwise + */ + std::time_t GetTimestamp() const; + + /** + * Get the int64 value + * + * @returns the value if its a VT_int64 type, 0 otherwise + */ + boost::int64_t GetInt64() const; + + /** + * Get an integral number + * + * Works for uint64 up to std::numeric_limits::max(), + * will throw a std::overflow_error otherwise. If the entire range + * of uint64_t is possible, please use GetUint64() + * + * @returns an integer number if the ValueType is VT_uint8, VT_int8, + * VT_uint16, VT_int16, VT_uint32, VT_int32,or VT_int64 type, 0 otherwise. + */ + boost::int64_t GetInteger() const; + + /** + * Get a float value + * + * @returns the value if its a VT_float type, 0. otherwise + */ + float GetFloat() const; + + /** + * Get a double value + * + * @returns the value if its a VT_double type, 0. otherwise + */ + double GetDouble() const; + + /** + * Get a floating-point value + * + * @returns the value if its a VT_float or VT_double type, 0. otherwise + */ + double GetReal() const; + + /** + * Get a string value + * + * @returns the value if its a VT_string type, an empty string otherwise + */ + std::string GetString() const; + + /** + * Gets an array + * + * @returns the value if its a VT_array type, an empty array otherwise + */ + std::vector GetArray() const; + + /** + * Gets a table + * + * @returns the value if its a VT_table type, an empty table otherwise + */ + Table GetTable() const; + + /** + * Sets the value as a void value + */ + void Set(); + + /** + * Set the value as a boolean + * + * @param [in] value the value + */ + void Set(bool value); + + /** + * Set the value as a uint8_t + * + * @param [in] value the value + */ + void Set(boost::uint8_t value); + + /** + * Set the value as a int8_t + * + * @param [in] value the value + */ + void Set(boost::int8_t value); + + /** + * Set the value as a uint16_t + * + * @param [in] value the value + */ + void Set(boost::uint16_t value); + + /** + * Set the value as a int16_t + * + * @param [in] value the value + */ + void Set(boost::int16_t value); + + /** + * Set the value as a uint32_t + * + * @param [in] value the value + */ + void Set(boost::uint32_t value); + + /** + * Set the value as a int32_t + * + * @param [in] value the value + */ + void Set(boost::int32_t value); + + /** + * Set the value as a timestamp. + * + * @param [in] value the value + */ + void SetTimestamp(std::time_t value); + + /** + * Set the value as a int64_t + * + * @param [in] value the value + */ + void Set(boost::int64_t value); + + /** + * Set the value as a float + * + * @param [in] value the value + */ + void Set(float value); + + /** + * Set the value as a double + * + * @param [in] value the value + */ + void Set(double value); + + /** + * Set the value as a string + * + * @param [in] value the value + */ + void Set(const char *value); + + /** + * Set the value as a string + * + * @param [in] value the value + */ + void Set(const std::string &value); + + /** + * Set the value as an array + * + * @param [in] value the value + */ + void Set(const std::vector &value); + + /** + * Set the value as a table + * + * @param [in] value the value + */ + void Set(const Table &value); + + private: + boost::scoped_ptr m_impl; +}; + +} // namespace AmqpClient + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMPLEAMQPCLIENT_TABLE_H diff --git a/src/SimpleAmqpClient/TableImpl.h b/src/SimpleAmqpClient/TableImpl.h new file mode 100644 index 0000000..b72bc66 --- /dev/null +++ b/src/SimpleAmqpClient/TableImpl.h @@ -0,0 +1,113 @@ +#ifndef SIMPLEAMQPCLIENT_TABLEIMPL_H +#define SIMPLEAMQPCLIENT_TABLEIMPL_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/Table.h" + +namespace AmqpClient { +namespace Detail { + +typedef boost::shared_ptr amqp_pool_ptr_t; + +struct void_t {}; + +inline bool operator==(const void_t &, const void_t &) { return true; } + +typedef std::vector array_t; + +typedef boost::variant + value_t; + +class TableValueImpl { + public: + explicit TableValueImpl(const value_t &v) : m_value(v) {} + virtual ~TableValueImpl() {} + + value_t m_value; + + static amqp_table_t CreateAmqpTable(const Table &table, + amqp_pool_ptr_t &pool); + + static Table CreateTable(const amqp_table_t &table); + + static amqp_table_t CopyTable(const amqp_table_t &table, + amqp_pool_ptr_t &pool); + + private: + static amqp_table_t CreateAmqpTableInner(const Table &table, + amqp_pool_t &pool); + static TableValue CreateTableValue(const amqp_field_value_t &entry); + static amqp_table_t CopyTableInner(const amqp_table_t &table, + amqp_pool_t &pool); + static amqp_field_value_t CopyValue(const amqp_field_value_t value, + amqp_pool_t &pool); + + public: + class generate_field_value + : public boost::static_visitor { + public: + explicit generate_field_value(amqp_pool_t &p) : pool(p) {} + virtual ~generate_field_value() {} + + amqp_field_value_t operator()(const void_t) const; + amqp_field_value_t operator()(const bool value) const; + amqp_field_value_t operator()(const boost::uint8_t value) const; + amqp_field_value_t operator()(const boost::int8_t value) const; + amqp_field_value_t operator()(const boost::uint16_t value) const; + amqp_field_value_t operator()(const boost::int16_t value) const; + amqp_field_value_t operator()(const boost::uint32_t value) const; + amqp_field_value_t operator()(const boost::int32_t value) const; + amqp_field_value_t operator()(const boost::uint64_t value) const; + amqp_field_value_t operator()(const boost::int64_t value) const; + amqp_field_value_t operator()(const float value) const; + amqp_field_value_t operator()(const double value) const; + amqp_field_value_t operator()(const std::string &value) const; + amqp_field_value_t operator()(const array_t &value) const; + amqp_field_value_t operator()(const Table &value) const; + + private: + amqp_pool_t &pool; + }; +}; + +} // namespace Detail +} // namespace AmqpClient +#endif // SIMPLEAMQPCLIENT_TABLEIMPL_H diff --git a/src/SimpleAmqpClient/Util.h b/src/SimpleAmqpClient/Util.h new file mode 100644 index 0000000..6640b53 --- /dev/null +++ b/src/SimpleAmqpClient/Util.h @@ -0,0 +1,49 @@ +#ifndef SIMPLEAMQPCLIENT_UTIL_H +#define SIMPLEAMQPCLIENT_UTIL_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#if defined(WIN32) && !defined(SimpleAmqpClient_STATIC) +# ifdef SimpleAmqpClient_EXPORTS +# define SIMPLEAMQPCLIENT_EXPORT __declspec(dllexport) +# else +# define SIMPLEAMQPCLIENT_EXPORT __declspec(dllimport) +# endif +#else +# define SIMPLEAMQPCLIENT_EXPORT +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SAC_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +#define SAC_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +#define SAC_DEPRECATED(msg) +#endif + +#endif // SIMPLEAMQPCLIENT_UTIL_H diff --git a/src/SimpleAmqpClient/Version.h b/src/SimpleAmqpClient/Version.h new file mode 100644 index 0000000..ce36e03 --- /dev/null +++ b/src/SimpleAmqpClient/Version.h @@ -0,0 +1,35 @@ +#ifndef SIMPLEAMQPCLIENT_VERSION_H +#define SIMPLEAMQPCLIENT_VERSION_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#define SIMPLEAMQPCLIENT_VERSION_MAJOR 2 +#define SIMPLEAMQPCLIENT_VERSION_MINOR 6 +#define SIMPLEAMQPCLIENT_VERSION_PATCH 0 + +#endif // SIMPLEAMQPCLIENT_VERSION_H diff --git a/src/Table.cpp b/src/Table.cpp new file mode 100644 index 0000000..c28033f --- /dev/null +++ b/src/Table.cpp @@ -0,0 +1,264 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "SimpleAmqpClient/Table.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "SimpleAmqpClient/TableImpl.h" + +namespace AmqpClient { +TableValue::TableValue() + : m_impl(new Detail::TableValueImpl(Detail::void_t())) {} + +TableValue::TableValue(bool value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::uint8_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::int8_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::uint16_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::int16_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::uint32_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::int32_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(boost::uint64_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue TableValue::Timestamp(std::time_t ts) { + return TableValue(static_cast(ts)); +} + +TableValue::TableValue(boost::int64_t value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(float value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(double value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(const char *value) + : m_impl(new Detail::TableValueImpl(std::string(value))) {} + +TableValue::TableValue(const std::string &value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(const std::vector &values) + : m_impl(new Detail::TableValueImpl(values)) {} + +TableValue::TableValue(const Table &value) + : m_impl(new Detail::TableValueImpl(value)) {} + +TableValue::TableValue(const TableValue &l) + : m_impl(new Detail::TableValueImpl(l.m_impl->m_value)) {} + +TableValue &TableValue::operator=(const TableValue &l) { + if (this != &l) { + m_impl->m_value = l.m_impl->m_value; + } + return *this; +} + +bool operator==(const Array &l, const Array &r) { + if (l.size() == r.size()) { + return std::equal(l.begin(), l.end(), r.begin()); + } + return false; +} + +bool operator==(const Table &l, const Table &r) { + if (l.size() == r.size()) { + return std::equal(l.begin(), l.end(), r.begin()); + } + return false; +} + +bool TableValue::operator==(const TableValue &l) const { + if (this == &l) { + return true; + } + + return m_impl->m_value == l.m_impl->m_value; +} + +bool TableValue::operator!=(const TableValue &l) const { + if (this == &l) { + return false; + } + + return !(m_impl->m_value == l.m_impl->m_value); +} + +TableValue::~TableValue() {} + +TableValue::ValueType TableValue::GetType() const { + return static_cast(m_impl->m_value.which()); +} + +bool TableValue::GetBool() const { return boost::get(m_impl->m_value); } + +boost::uint8_t TableValue::GetUint8() const { + return boost::get(m_impl->m_value); +} + +boost::int8_t TableValue::GetInt8() const { + return boost::get(m_impl->m_value); +} + +boost::uint16_t TableValue::GetUint16() const { + return boost::get(m_impl->m_value); +} + +boost::int16_t TableValue::GetInt16() const { + return boost::get(m_impl->m_value); +} + +boost::uint32_t TableValue::GetUint32() const { + return boost::get(m_impl->m_value); +} + +boost::int32_t TableValue::GetInt32() const { + return boost::get(m_impl->m_value); +} + +std::time_t TableValue::GetTimestamp() const { + return static_cast(boost::get(m_impl->m_value)); +} + +boost::int64_t TableValue::GetInt64() const { + return boost::get(m_impl->m_value); +} + +boost::int64_t TableValue::GetInteger() const { + switch (m_impl->m_value.which()) { + case VT_uint8: + return GetUint8(); + case VT_int8: + return GetInt8(); + case VT_uint16: + return GetUint16(); + case VT_int16: + return GetInt16(); + case VT_uint32: + return GetUint32(); + case VT_int32: + return GetInt32(); + case VT_int64: + return GetInt64(); + default: + throw boost::bad_get(); + } +} + +float TableValue::GetFloat() const { + return boost::get(m_impl->m_value); +} + +double TableValue::GetDouble() const { + return boost::get(m_impl->m_value); +} + +double TableValue::GetReal() const { + switch (m_impl->m_value.which()) { + case VT_float: + return GetFloat(); + case VT_double: + return GetDouble(); + default: + throw boost::bad_get(); + } +} + +std::string TableValue::GetString() const { + return boost::get(m_impl->m_value); +} + +std::vector TableValue::GetArray() const { + return boost::get(m_impl->m_value); +} + +Table TableValue::GetTable() const { + return boost::get
(m_impl->m_value); +} + +void TableValue::Set() { m_impl->m_value = Detail::void_t(); } + +void TableValue::Set(bool value) { m_impl->m_value = value; } + +void TableValue::Set(boost::uint8_t value) { m_impl->m_value = value; } + +void TableValue::Set(boost::int8_t value) { m_impl->m_value = value; } + +void TableValue::Set(boost::uint16_t value) { m_impl->m_value = value; } + +void TableValue::Set(boost::int16_t value) { m_impl->m_value = value; } + +void TableValue::Set(boost::uint32_t value) { m_impl->m_value = value; } + +void TableValue::Set(boost::int32_t value) { m_impl->m_value = value; } + +void TableValue::SetTimestamp(std::time_t value) { + m_impl->m_value = static_cast(value); +} + +void TableValue::Set(boost::int64_t value) { m_impl->m_value = value; } + +void TableValue::Set(float value) { m_impl->m_value = value; } + +void TableValue::Set(double value) { m_impl->m_value = value; } + +void TableValue::Set(const char *value) { + m_impl->m_value = std::string(value); +} + +void TableValue::Set(const std::string &value) { m_impl->m_value = value; } + +void TableValue::Set(const std::vector &value) { + m_impl->m_value = value; +} + +void TableValue::Set(const Table &value) { m_impl->m_value = value; } + +} // namespace AmqpClient diff --git a/src/TableImpl.cpp b/src/TableImpl.cpp new file mode 100644 index 0000000..a861060 --- /dev/null +++ b/src/TableImpl.cpp @@ -0,0 +1,361 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#ifdef _MSC_VER +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "SimpleAmqpClient/TableImpl.h" + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4800) +#endif + +namespace AmqpClient { +namespace Detail { + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const void_t) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_VOID; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const bool value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_BOOLEAN; + v.value.boolean = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::uint8_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_U8; + v.value.u8 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::int8_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_I8; + v.value.i8 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::uint16_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_U16; + v.value.u16 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::int16_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_I16; + v.value.i16 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::uint32_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_U32; + v.value.u32 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::int32_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_I32; + v.value.i32 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::uint64_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_TIMESTAMP; + v.value.u64 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const boost::int64_t value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_I64; + v.value.i64 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const float value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_F32; + v.value.f32 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const double value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_F64; + v.value.f64 = value; + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const std::string &value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_UTF8; + amqp_pool_alloc_bytes(&pool, value.size(), &v.value.bytes); + memcpy(v.value.bytes.bytes, value.data(), v.value.bytes.len); + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const array_t &value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_ARRAY; + v.value.array.num_entries = value.size(); + v.value.array.entries = (amqp_field_value_t *)amqp_pool_alloc( + &pool, sizeof(amqp_field_value_t) * value.size()); + if (NULL == v.value.array.entries) { + throw std::bad_alloc(); + } + + amqp_field_value_t *output_iterator = v.value.array.entries; + for (array_t::const_iterator it = value.begin(); it != value.end(); + ++it, ++output_iterator) { + *output_iterator = + boost::apply_visitor(generate_field_value(pool), it->m_impl->m_value); + } + return v; +} + +amqp_field_value_t TableValueImpl::generate_field_value::operator()( + const Table &value) const { + amqp_field_value_t v; + v.kind = AMQP_FIELD_KIND_TABLE; + v.value.table = CreateAmqpTableInner(value, pool); + return v; +} + +void free_pool(amqp_pool_t *pool) { + empty_amqp_pool(pool); + delete pool; +} + +amqp_table_t TableValueImpl::CreateAmqpTable(const Table &table, + amqp_pool_ptr_t &pool) { + if (0 == table.size()) { + return AMQP_EMPTY_TABLE; + } + + pool = boost::shared_ptr(new amqp_pool_t, free_pool); + init_amqp_pool(pool.get(), 1024); + + return CreateAmqpTableInner(table, *pool.get()); +} + +amqp_table_t TableValueImpl::CreateAmqpTableInner(const Table &table, + amqp_pool_t &pool) { + amqp_table_t new_table; + + new_table.num_entries = table.size(); + + new_table.entries = (amqp_table_entry_t *)amqp_pool_alloc( + &pool, sizeof(amqp_table_entry_t) * table.size()); + + if (NULL == new_table.entries) { + throw std::bad_alloc(); + } + + amqp_table_entry_t *output_it = new_table.entries; + + for (Table::const_iterator it = table.begin(); it != table.end(); + ++it, ++output_it) { + amqp_pool_alloc_bytes(&pool, it->first.size(), &output_it->key); + if (NULL == output_it->key.bytes) { + throw std::bad_alloc(); + } + + std::copy(it->first.begin(), it->first.end(), (char *)output_it->key.bytes); + + output_it->value = boost::apply_visitor( + TableValueImpl::generate_field_value(pool), it->second.m_impl->m_value); + } + + return new_table; +} + +Table TableValueImpl::CreateTable(const amqp_table_t &table) { + Table new_table; + + for (int i = 0; i < table.num_entries; ++i) { + amqp_table_entry_t *entry = &table.entries[i]; + + std::string key((char *)entry->key.bytes, entry->key.len); + + new_table.insert(TableEntry(key, CreateTableValue(entry->value))); + } + return new_table; +} + +TableValue TableValueImpl::CreateTableValue(const amqp_field_value_t &entry) { + switch (entry.kind) { + case AMQP_FIELD_KIND_VOID: + return TableValue(); + case AMQP_FIELD_KIND_BOOLEAN: + return TableValue((bool)entry.value.boolean); + case AMQP_FIELD_KIND_U8: + return TableValue(entry.value.u8); + case AMQP_FIELD_KIND_I8: + return TableValue(entry.value.i8); + case AMQP_FIELD_KIND_U16: + return TableValue(entry.value.u16); + case AMQP_FIELD_KIND_I16: + return TableValue(entry.value.i16); + case AMQP_FIELD_KIND_U32: + return TableValue(entry.value.u32); + case AMQP_FIELD_KIND_I32: + return TableValue(entry.value.i32); + case AMQP_FIELD_KIND_TIMESTAMP: + return TableValue(entry.value.u64); + case AMQP_FIELD_KIND_I64: + return TableValue(entry.value.i64); + case AMQP_FIELD_KIND_F32: + return TableValue(entry.value.f32); + case AMQP_FIELD_KIND_F64: + return TableValue(entry.value.f64); + case AMQP_FIELD_KIND_UTF8: + case AMQP_FIELD_KIND_BYTES: + return TableValue( + std::string((char *)entry.value.bytes.bytes, entry.value.bytes.len)); + case AMQP_FIELD_KIND_ARRAY: { + amqp_array_t array = entry.value.array; + Detail::array_t new_array; + + for (int i = 0; i < array.num_entries; ++i) { + new_array.push_back(CreateTableValue(array.entries[i])); + } + + return TableValue(new_array); + } + case AMQP_FIELD_KIND_TABLE: + return TableValue(CreateTable(entry.value.table)); + case AMQP_FIELD_KIND_DECIMAL: + // uint64_t is unsupported by RabbitMQ. + case AMQP_FIELD_KIND_U64: + default: + return TableValue(); + } +} + +amqp_table_t TableValueImpl::CopyTable(const amqp_table_t &table, + amqp_pool_ptr_t &pool) { + if (0 == table.num_entries) { + return AMQP_EMPTY_TABLE; + } + + pool = boost::shared_ptr(new amqp_pool_t, free_pool); + init_amqp_pool(pool.get(), 1024); + + return CopyTableInner(table, *pool.get()); +} + +amqp_table_t TableValueImpl::CopyTableInner(const amqp_table_t &table, + amqp_pool_t &pool) { + amqp_table_t new_table; + + new_table.num_entries = table.num_entries; + new_table.entries = (amqp_table_entry_t *)amqp_pool_alloc( + &pool, sizeof(amqp_table_entry_t) * table.num_entries); + if (NULL == new_table.entries) { + throw std::bad_alloc(); + } + + for (int i = 0; i < table.num_entries; ++i) { + amqp_table_entry_t *entry = &new_table.entries[i]; + amqp_pool_alloc_bytes(&pool, table.entries[i].key.len, &entry->key); + + if (NULL == entry->key.bytes) { + throw std::bad_alloc(); + } + memcpy(entry->key.bytes, table.entries[i].key.bytes, entry->key.len); + + entry->value = CopyValue(table.entries[i].value, pool); + } + + return new_table; +} + +amqp_field_value_t TableValueImpl::CopyValue(const amqp_field_value_t value, + amqp_pool_t &pool) { + amqp_field_value_t new_value = value; + + switch (value.kind) { + case AMQP_FIELD_KIND_UTF8: + case AMQP_FIELD_KIND_BYTES: + amqp_pool_alloc_bytes(&pool, value.value.bytes.len, + &new_value.value.bytes); + memcpy(new_value.value.bytes.bytes, value.value.bytes.bytes, + value.value.bytes.len); + return new_value; + case AMQP_FIELD_KIND_ARRAY: { + new_value.value.array.entries = (amqp_field_value_t *)amqp_pool_alloc( + &pool, sizeof(amqp_field_value_t) * value.value.array.num_entries); + for (int i = 0; i < value.value.array.num_entries; ++i) { + new_value.value.array.entries[i] = + CopyValue(value.value.array.entries[i], pool); + } + return new_value; + } + case AMQP_FIELD_KIND_TABLE: + new_value.value.table = CopyTableInner(value.value.table, pool); + return new_value; + default: + return new_value; + } +} +} // namespace Detail +} // namespace AmqpClient diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt new file mode 100644 index 0000000..2cd5815 --- /dev/null +++ b/testing/CMakeLists.txt @@ -0,0 +1,19 @@ +include_directories(BEFORE SYSTEM ${gtest_SOURCE_DIR}/include) +include_directories(../src) + +add_executable(test_api + connected_test.h + test_connect.cpp + test_channels.cpp + test_exchange.cpp + test_queue.cpp + test_publish.cpp + test_get.cpp + test_consume.cpp + test_message.cpp + test_table.cpp + test_ack.cpp + test_nack.cpp + ) +target_link_libraries(test_api SimpleAmqpClient gtest gtest_main) +add_test(test_api test_api) diff --git a/testing/connected_test.h b/testing/connected_test.h new file mode 100644 index 0000000..e1a0c51 --- /dev/null +++ b/testing/connected_test.h @@ -0,0 +1,58 @@ +#ifndef CONNECTED_TEST_H +#define CONNECTED_TEST_H +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include + +using namespace AmqpClient; + +class connected_test : public ::testing::Test { + public: + virtual void SetUp() { channel = Channel::Open(GetTestOpenOpts()); } + + Channel::ptr_t channel; + + static Channel::OpenOpts GetTestOpenOpts() { + Channel::OpenOpts ret; + ret.host = GetBrokerHost(); + ret.auth = Channel::OpenOpts::BasicAuth("guest", "guest"); + return ret; + } + + static std::string GetBrokerHost() { + const char *host = getenv("AMQP_BROKER"); + if (NULL != host) { + return std::string(host); + } + return std::string(""); + } +}; + +#endif // CONNECTED_TEST_H diff --git a/testing/test_ack.cpp b/testing/test_ack.cpp new file mode 100644 index 0000000..e92178b --- /dev/null +++ b/testing/test_ack.cpp @@ -0,0 +1,61 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST_F(connected_test, basic_ack_envelope) { + const BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + + Envelope::ptr_t env = channel->BasicConsumeMessage(consumer); + + channel->BasicAck(env); +} + +TEST_F(connected_test, basic_ack_deliveryinfo) { + const BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + + Envelope::DeliveryInfo info; + { + Envelope::ptr_t env = channel->BasicConsumeMessage(consumer); + info = env->GetDeliveryInfo(); + } + + channel->BasicAck(info); +} diff --git a/testing/test_channels.cpp b/testing/test_channels.cpp new file mode 100644 index 0000000..6db997e --- /dev/null +++ b/testing/test_channels.cpp @@ -0,0 +1,147 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST_F(connected_test, first_channel) { + channel->DeclareExchange("test_channel_exchange", + Channel::EXCHANGE_TYPE_FANOUT, false, false, true); + channel->DeleteExchange("test_channel_exchange"); +} + +// Check to see that channels are reused properly +TEST_F(connected_test, channel_reuse) { + channel->DeclareExchange("test_channel_exchange1", + Channel::EXCHANGE_TYPE_FANOUT, false, false, true); + channel->DeclareExchange("test_channel_exchange2", + Channel::EXCHANGE_TYPE_FANOUT, false, false, true); + channel->DeleteExchange("test_channel_exchange1"); + channel->DeleteExchange("test_channel_exchange2"); +} + +// Check to see that a new channel is created when a channel is put in an +// exception state +TEST_F(connected_test, channel_recover_from_error) { + EXPECT_THROW(channel->DeclareExchange("test_channel_exchangedoesnotexist", + Channel::EXCHANGE_TYPE_FANOUT, true, + false, true), + ChannelException); + + channel->DeclareExchange("test_channel_exchange", + Channel::EXCHANGE_TYPE_FANOUT, false, false, true); + channel->DeleteExchange("test_channel_exchange"); +} + +TEST_F(connected_test, channel_publish_success1) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + + channel->BasicPublish("", "test_channel_routingkey", message, false, false); +} + +TEST_F(connected_test, channel_publish_success2) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + + channel->BasicPublish("", "test_channel_routingkey", message, false, false); + channel->BasicPublish("", "test_channel_routingkey", message, false, false); +} + +TEST_F(connected_test, channel_publish_returned_mandatory) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + + EXPECT_THROW( + channel->BasicPublish("", "test_channel_noqueue", message, true, false), + MessageReturnedException); +} + +TEST_F(connected_test, channel_publish_full_rejected) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + Table args; + args.insert(TableEntry("x-max-length", 0)); + args.insert(TableEntry("x-overflow", "reject-publish")); + std::string queue = channel->DeclareQueue("", false, false, true, true, args); + + EXPECT_THROW(channel->BasicPublish("", queue, message), + MessageRejectedException); +} + +TEST_F(connected_test, channel_publish_bad_exchange) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + + EXPECT_THROW(channel->BasicPublish("test_channel_badexchange", + "test_channel_rk", message, false, false), + ChannelException); +} + +TEST_F(connected_test, channel_publish_bad_exchange_recover) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + + EXPECT_THROW(channel->BasicPublish("test_channel_badexchange", + "test_channel_rk", message, false, false), + ChannelException); + + channel->BasicPublish("", "test_channel_rk", message, false, false); +} + +TEST_F(connected_test, channel_consume_success) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message); + + std::string consumer = channel->BasicConsume(queue); + + Envelope::ptr_t consumed_envelope; + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, consumed_envelope)); +} + +TEST_F(connected_test, channel_consume_success_timeout) { + BasicMessage::ptr_t message = BasicMessage::Create("Test message"); + std::string queue = channel->DeclareQueue(""); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + channel->BasicPublish("", queue, message); + + Envelope::ptr_t consumed_envelope; + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, consumed_envelope, 5000)); +} + +TEST(test_channels, big_message) { + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.frame_max = 4096; + Channel::ptr_t channel = Channel::Open(opts); + BasicMessage::ptr_t message = BasicMessage::Create(std::string(4099, 'a')); + + std::string queue = channel->DeclareQueue(""); + + std::string consumer = channel->BasicConsume(queue); + channel->BasicPublish("", queue, message); + + Envelope::ptr_t consumed_envelope; + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, consumed_envelope)); +} diff --git a/testing/test_connect.cpp b/testing/test_connect.cpp new file mode 100644 index 0000000..0845558 --- /dev/null +++ b/testing/test_connect.cpp @@ -0,0 +1,131 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "SimpleAmqpClient/SimpleAmqpClient.h" +#include "connected_test.h" + +using namespace AmqpClient; + +TEST(connecting_test, connect_default) { + Channel::ptr_t channel = Channel::Create(connected_test::GetBrokerHost()); +} + +TEST(connecting_test, connect_badhost) { + EXPECT_THROW(Channel::ptr_t channel = Channel::Create("HostDoesntExist"), + std::runtime_error); +} + +TEST(connecting_test, open_badhost) { + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.host = "HostDoesNotExist"; + EXPECT_THROW(Channel::ptr_t channel = Channel::Open(opts), + std::runtime_error); +} + +TEST(connecting_test, connect_badauth) { + EXPECT_THROW(Channel::ptr_t channel = Channel::Create( + connected_test::GetBrokerHost(), 5672, "baduser", "badpass"), + AccessRefusedException); +} + +TEST(connecting_test, open_badauth) { + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.auth = Channel::OpenOpts::BasicAuth("baduser", "badpass"); + EXPECT_THROW(Channel::ptr_t channel = Channel::Open(opts), + AccessRefusedException); +} + +TEST(connecting_test, connect_badframesize) { + // AMQP Spec says we have a minimum frame size of 4096 + EXPECT_THROW( + Channel::ptr_t channel = Channel::Create( + connected_test::GetBrokerHost(), 5672, "guest", "guest", "/", 400), + AmqpResponseLibraryException); +} + +TEST(connecting_test, open_badframesize) { + // AMQP Spec says we have a minimum frame size of 4096 + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.frame_max = 400; + EXPECT_THROW(Channel::ptr_t channel = Channel::Open(opts), + AmqpResponseLibraryException); +} + +TEST(connecting_test, connect_badvhost) { + EXPECT_THROW(Channel::ptr_t channel = + Channel::Create(connected_test::GetBrokerHost(), 5672, + "guest", "guest", "nonexitant_vhost"), + NotAllowedException); +} + +TEST(connecting_test, open_badvhost) { + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.vhost = "bad_vhost"; + EXPECT_THROW(Channel::ptr_t channel = Channel::Open(opts), + NotAllowedException); +} + +TEST(connecting_test, connect_using_uri) { + std::string host_uri = "amqp://" + connected_test::GetBrokerHost(); + Channel::ptr_t channel = Channel::CreateFromUri(host_uri); +} + +TEST(connecting_test, openopts_from_uri) { + Channel::OpenOpts expected; + expected.host = "host"; + expected.vhost = "vhost"; + expected.port = 123; + expected.auth = Channel::OpenOpts::BasicAuth("user", "pass"); + + EXPECT_EQ(expected, + Channel::OpenOpts::FromUri("amqp://user:pass@host:123/vhost")); +} + +TEST(connecting_test, openopts_from_uri_defaults) { + Channel::OpenOpts expected; + expected.host = "host"; + expected.vhost = "/"; + expected.port = 5672; + expected.auth = Channel::OpenOpts::BasicAuth("guest", "guest"); + EXPECT_EQ(expected, Channel::OpenOpts::FromUri("amqp://host")); +} + +TEST(connecting_test, openopts_from_amqps_uri) { + Channel::OpenOpts expected; + expected.host = "host"; + expected.vhost = "vhost"; + expected.port = 123; + expected.auth = Channel::OpenOpts::BasicAuth("user", "pass"); + expected.tls_params = Channel::OpenOpts::TLSParams(); +} + +TEST(connecting_test, openopts_fromuri_bad) { + EXPECT_THROW(Channel::OpenOpts::FromUri("not-a-valid-uri"), BadUriException); +} diff --git a/testing/test_consume.cpp b/testing/test_consume.cpp new file mode 100644 index 0000000..507a624 --- /dev/null +++ b/testing/test_consume.cpp @@ -0,0 +1,228 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST_F(connected_test, basic_consume) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue); +} + +TEST_F(connected_test, basic_consume_badqueue) { + EXPECT_THROW(channel->BasicConsume("test_consume_noexistqueue"), + ChannelException); +} + +TEST_F(connected_test, basic_consume_duplicatetag) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue); + EXPECT_THROW(channel->BasicConsume(queue, consumer), ChannelException); +} + +TEST_F(connected_test, basic_cancel_consumer) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue); + channel->BasicCancel(consumer); +} + +TEST_F(connected_test, basic_cancel_bad_consumer) { + EXPECT_THROW(channel->BasicCancel("test_consume_noexistconsumer"), + ConsumerTagNotFoundException); +} + +TEST_F(connected_test, basic_cancel_cancelled_consumer) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue); + channel->BasicCancel(consumer); + EXPECT_THROW(channel->BasicCancel(consumer), ConsumerTagNotFoundException); +} + +TEST_F(connected_test, basic_consume_message) { + BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue); + channel->BasicPublish("", queue, message); + + Envelope::ptr_t delivered; + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, delivered, -1)); + EXPECT_EQ(consumer, delivered->ConsumerTag()); + EXPECT_EQ("", delivered->Exchange()); + EXPECT_EQ(queue, delivered->RoutingKey()); + EXPECT_EQ(message->Body(), delivered->Message()->Body()); +} + +TEST_F(connected_test, basic_consume_message_bad_consumer) { + EXPECT_THROW(channel->BasicConsumeMessage("test_consume_noexistconsumer"), + ConsumerTagNotFoundException); +} + +TEST_F(connected_test, basic_consume_inital_qos) { + BasicMessage::ptr_t message1 = BasicMessage::Create("Message1"); + BasicMessage::ptr_t message2 = BasicMessage::Create("Message2"); + BasicMessage::ptr_t message3 = BasicMessage::Create("Message3"); + + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message1, true); + channel->BasicPublish("", queue, message2, true); + channel->BasicPublish("", queue, message3, true); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + Envelope::ptr_t received1, received2; + ASSERT_TRUE(channel->BasicConsumeMessage(consumer, received1, 100)); + + EXPECT_FALSE(channel->BasicConsumeMessage(consumer, received2, 100)); + channel->BasicAck(received1); + + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, received2, 100)); +} + +TEST_F(connected_test, basic_consume_2consumers) { + BasicMessage::ptr_t message1 = BasicMessage::Create("Message1"); + BasicMessage::ptr_t message2 = BasicMessage::Create("Message2"); + BasicMessage::ptr_t message3 = BasicMessage::Create("Message3"); + + std::string queue1 = channel->DeclareQueue(""); + std::string queue2 = channel->DeclareQueue(""); + std::string queue3 = channel->DeclareQueue(""); + + channel->BasicPublish("", queue1, message1); + channel->BasicPublish("", queue2, message2); + channel->BasicPublish("", queue3, message3); + + std::string consumer1 = channel->BasicConsume(queue1, "", true, false); + std::string consumer2 = channel->BasicConsume(queue2, "", true, false); + + Envelope::ptr_t envelope1; + Envelope::ptr_t envelope2; + Envelope::ptr_t envelope3; + + channel->BasicConsumeMessage(consumer1, envelope1); + channel->BasicAck(envelope1); + channel->BasicConsumeMessage(consumer2, envelope2); + channel->BasicAck(envelope2); + channel->BasicGet(envelope3, queue3); + channel->BasicAck(envelope3); +} + +TEST_F(connected_test, basic_consume_1000messages) { + BasicMessage::ptr_t message1 = BasicMessage::Create("Message1"); + + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue, ""); + + Envelope::ptr_t msg; + for (int i = 0; i < 1000; ++i) { + message1->Timestamp(i); + channel->BasicPublish("", queue, message1, true); + channel->BasicConsumeMessage(consumer, msg); + } +} + +TEST_F(connected_test, basic_recover) { + BasicMessage::ptr_t message = BasicMessage::Create("Message1"); + + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue, "", true, false); + channel->BasicPublish("", queue, message); + + Envelope::ptr_t message1; + Envelope::ptr_t message2; + + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, message1)); + channel->BasicRecover(consumer); + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, message2)); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, basic_recover_badconsumer) { + EXPECT_THROW(channel->BasicRecover("consumer_notexist"), + ConsumerTagNotFoundException); +} + +TEST_F(connected_test, basic_qos) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue, "", true, false); + channel->BasicPublish("", queue, BasicMessage::Create("Message1")); + channel->BasicPublish("", queue, BasicMessage::Create("Message2")); + + Envelope::ptr_t incoming; + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, incoming, 100)); + EXPECT_FALSE(channel->BasicConsumeMessage(consumer, incoming, 100)); + + channel->BasicQos(consumer, 2); + EXPECT_TRUE(channel->BasicConsumeMessage(consumer, incoming, 100)); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, basic_qos_badconsumer) { + EXPECT_THROW(channel->BasicQos("consumer_notexist", 1), + ConsumerTagNotFoundException); +} + +TEST_F(connected_test, consumer_cancelled) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue, "", true, false); + channel->DeleteQueue(queue); + + EXPECT_THROW(channel->BasicConsumeMessage(consumer), + ConsumerCancelledException); +} + +TEST_F(connected_test, consumer_cancelled_one_message) { + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue, "", true, false); + + channel->BasicPublish("", queue, BasicMessage::Create("Message")); + channel->BasicConsumeMessage(consumer); + + channel->DeleteQueue(queue); + + EXPECT_THROW(channel->BasicConsumeMessage(consumer), + ConsumerCancelledException); +} + +TEST_F(connected_test, consume_multiple) { + std::string queue1 = channel->DeclareQueue(""); + std::string queue2 = channel->DeclareQueue(""); + + std::string Body = "Message 1"; + channel->BasicPublish("", queue1, BasicMessage::Create(Body)); + + channel->BasicConsume(queue1); + channel->BasicConsume(queue2); + + Envelope::ptr_t env = channel->BasicConsumeMessage(); + + EXPECT_EQ(Body, env->Message()->Body()); +} diff --git a/testing/test_exchange.cpp b/testing/test_exchange.cpp new file mode 100644 index 0000000..25264cc --- /dev/null +++ b/testing/test_exchange.cpp @@ -0,0 +1,164 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include + +#include "connected_test.h" + +TEST_F(connected_test, declare_exchange_defaults) { + channel->DeclareExchange("declare_defaults"); + channel->DeleteExchange("declare_defaults"); +} + +TEST_F(connected_test, declare_exchange_direct) { + channel->DeclareExchange("declare_direct", Channel::EXCHANGE_TYPE_DIRECT); + channel->DeleteExchange("declare_direct"); +} + +TEST_F(connected_test, declare_exchange_fanout) { + channel->DeclareExchange("declare_fanout", Channel::EXCHANGE_TYPE_FANOUT); + channel->DeleteExchange("declare_fanout"); +} + +TEST_F(connected_test, declare_exchange_topic) { + channel->DeclareExchange("declare_topic", Channel::EXCHANGE_TYPE_TOPIC); + channel->DeleteExchange("declare_topic"); +} + +TEST_F(connected_test, check_exchange_exists_succeeds) { + channel->DeclareExchange("declare_exists"); + EXPECT_TRUE(channel->CheckExchangeExists("declare_exists")); +} + +TEST_F(connected_test, declare_exchange_passive_good) { + channel->DeclareExchange("declare_passive", Channel::EXCHANGE_TYPE_DIRECT); + channel->DeclareExchange("declare_passive", Channel::EXCHANGE_TYPE_DIRECT, + true); + + channel->DeleteExchange("declare_passive"); +} + +TEST_F(connected_test, check_exchange_exists_fails) { + EXPECT_FALSE(channel->CheckExchangeExists("declare_notexist")); +} + +TEST_F(connected_test, declare_exchange_passive_notexist) { + EXPECT_THROW(channel->DeclareExchange("declare_passive_notexist", + Channel::EXCHANGE_TYPE_DIRECT, true), + ChannelException); +} + +TEST_F(connected_test, declare_exchange_typemismatch) { + channel->DeclareExchange("declare_typemismatch", + Channel::EXCHANGE_TYPE_DIRECT); + EXPECT_THROW(channel->DeclareExchange("declare_typemismatch", + Channel::EXCHANGE_TYPE_FANOUT), + ChannelException); + + channel->DeleteExchange("declare_typemismatch"); +} + +TEST_F(connected_test, declare_exchange_typemismatch2) { + channel->DeclareExchange("declare_typemismatch", + Channel::EXCHANGE_TYPE_DIRECT); + EXPECT_THROW( + channel->DeclareExchange("declare_typemismatch", + Channel::EXCHANGE_TYPE_DIRECT, false, true), + ChannelException); + + channel->DeleteExchange("declare_typemismatch"); +} + +TEST_F(connected_test, declare_exchange_durable) { + channel->DeclareExchange("declare_durable", Channel::EXCHANGE_TYPE_DIRECT, + false, true); + + channel->DeleteExchange("declare_durable"); +} + +TEST_F(connected_test, declare_exchange_autodelete) { + channel->DeclareExchange("declare_autodelete", Channel::EXCHANGE_TYPE_DIRECT, + false, false, true); + + channel->DeleteExchange("declare_autodelete"); +} + +TEST_F(connected_test, delete_exchange) { + channel->DeclareExchange("delete_exchange"); + channel->DeleteExchange("delete_exchange"); +} + +TEST_F(connected_test, delete_exhange_ifunused) { + channel->DeclareExchange("exchange_used", Channel::EXCHANGE_TYPE_DIRECT); + + channel->DeleteExchange("exchange_used", true); +} + +TEST_F(connected_test, delete_exhange_ifused) { + channel->DeclareExchange("exchange_used", Channel::EXCHANGE_TYPE_DIRECT); + std::string queue = channel->DeclareQueue(""); + channel->BindQueue(queue, "exchange_used", "whatever"); + + EXPECT_THROW(channel->DeleteExchange("exchange_used", true), + ChannelException); + + channel->DeleteQueue(queue); + channel->DeleteExchange("exchange_used"); +} + +TEST_F(connected_test, bind_exchange) { + channel->DeclareExchange("exchange_bind_dest"); + channel->DeclareExchange("exchange_bind_src"); + + channel->BindExchange("exchange_bind_dest", "exchange_bind_src", "rk"); + + channel->DeleteExchange("exchange_bind_dest"); + channel->DeleteExchange("exchange_bind_src"); +} + +TEST_F(connected_test, bind_exchange_badexchange) { + channel->DeclareExchange("exchange_bind_dest"); + + EXPECT_THROW(channel->BindExchange("exchange_bind_dest", + "exchange_bind_notexist", "rk"), + ChannelException); + + channel->DeleteExchange("exchange_bind_dest"); +} + +TEST_F(connected_test, unbind_exchange) { + channel->DeclareExchange("exchange_bind_dest"); + channel->DeclareExchange("exchange_bind_src"); + + channel->BindExchange("exchange_bind_dest", "exchange_bind_src", "rk"); + channel->UnbindExchange("exchange_bind_dest", "exchange_bind_src", "rk"); + + channel->DeleteExchange("exchange_bind_dest"); + channel->DeleteExchange("exchange_bind_src"); +} diff --git a/testing/test_get.cpp b/testing/test_get.cpp new file mode 100644 index 0000000..005781a --- /dev/null +++ b/testing/test_get.cpp @@ -0,0 +1,81 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST_F(connected_test, get_ok) { + BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message, true); + + Envelope::ptr_t new_message; + EXPECT_TRUE(channel->BasicGet(new_message, queue)); + EXPECT_EQ(message->Body(), new_message->Message()->Body()); +} + +TEST_F(connected_test, get_empty) { + BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + + Envelope::ptr_t new_message; + EXPECT_FALSE(channel->BasicGet(new_message, queue)); +} + +TEST(test_get, get_big) { + // Smallest frame size allowed by AMQP + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.frame_max = 4096; + Channel::ptr_t channel = Channel::Open(opts); + // Create a message with a body larger than a single frame + BasicMessage::ptr_t message = BasicMessage::Create(std::string(4099, 'a')); + std::string queue = channel->DeclareQueue(""); + + channel->BasicPublish("", queue, message); + Envelope::ptr_t new_message; + EXPECT_TRUE(channel->BasicGet(new_message, queue)); + EXPECT_EQ(message->Body(), new_message->Message()->Body()); +} + +TEST_F(connected_test, bad_queue) { + Envelope::ptr_t new_message; + EXPECT_THROW(channel->BasicGet(new_message, "test_get_nonexistantqueue"), + ChannelException); +} + +TEST_F(connected_test, ack_message) { + BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message, true); + + Envelope::ptr_t new_message; + EXPECT_TRUE(channel->BasicGet(new_message, queue, false)); + channel->BasicAck(new_message); + EXPECT_FALSE(channel->BasicGet(new_message, queue, false)); +} diff --git a/testing/test_message.cpp b/testing/test_message.cpp new file mode 100644 index 0000000..54e57a2 --- /dev/null +++ b/testing/test_message.cpp @@ -0,0 +1,134 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include +#include +#include + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST(basic_message, empty_message) { + BasicMessage::ptr_t empty_message = BasicMessage::Create(); + + EXPECT_EQ(std::string(), empty_message->Body()); + + // Allow the message to destruct +} + +TEST(basic_message, empty_message_add_body) { + BasicMessage::ptr_t empty_message = BasicMessage::Create(); + + EXPECT_EQ(std::string(), empty_message->Body()); + + const std::string body("Message Body"); + empty_message->Body(body); + + EXPECT_EQ(body, empty_message->Body()); + + // Allow the message to destruct +} + +TEST(basic_message, empty_message_add_body2) { + BasicMessage::ptr_t empty_message = BasicMessage::Create(); + + EXPECT_EQ(std::string(), empty_message->Body()); + + const std::string body("Message Body"); + empty_message->Body(body); + + EXPECT_EQ(body, empty_message->Body()); + + const std::string body2("Second body"); + empty_message->Body(body2); + EXPECT_EQ(body2, empty_message->Body()); + + // Allow the message to destruct +} + +TEST(basic_message, initial_message_replace) { + const std::string first_body("First message Body"); + BasicMessage::ptr_t message = BasicMessage::Create(first_body); + + EXPECT_EQ(first_body, message->Body()); + + const std::string second_body("Second message Body"); + message->Body(second_body); + + EXPECT_EQ(second_body, message->Body()); + + // Allow the message to destruct +} + +TEST(basic_message, initial_message_replace2) { + const std::string first_body("First message body"); + BasicMessage::ptr_t message = BasicMessage::Create(first_body); + EXPECT_EQ(first_body, message->Body()); + + const std::string second_body("second message body"); + message->Body(second_body); + EXPECT_EQ(second_body, message->Body()); + + const std::string third_body("3rd Body"); + message->Body(third_body); + EXPECT_EQ(third_body, message->Body()); +} + +TEST(basic_message, embedded_nulls) { + const boost::array message_data = { + {'a', 'b', 'c', 0, '1', '2', '3'}}; + const std::string body(message_data.data(), message_data.size()); + BasicMessage::ptr_t message = BasicMessage::Create(body); + EXPECT_EQ(body, message->Body()); + + const boost::array message_data2 = { + {'1', '2', '3', 0, 'a', 'b', 'c'}}; + const std::string body2(message_data2.data(), message_data2.size()); + message->Body(body2); + EXPECT_EQ(body2, message->Body()); +} + +TEST_F(connected_test, replaced_received_body) { + const std::string queue = channel->DeclareQueue(""); + const std::string consumer = channel->BasicConsume(queue); + + const std::string body("First Message Body"); + BasicMessage::ptr_t out_message = BasicMessage::Create(body); + channel->BasicPublish("", queue, out_message); + + Envelope::ptr_t envelope = channel->BasicConsumeMessage(consumer); + BasicMessage::ptr_t in_message = envelope->Message(); + EXPECT_EQ(out_message->Body(), in_message->Body()); + + const std::string body2("Second message body"); + in_message->Body(body2); + EXPECT_EQ(body2, in_message->Body()); +} diff --git a/testing/test_nack.cpp b/testing/test_nack.cpp new file mode 100644 index 0000000..597c647 --- /dev/null +++ b/testing/test_nack.cpp @@ -0,0 +1,77 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST_F(connected_test, basic_nack_envelope) { + const BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + + Envelope::ptr_t env = channel->BasicConsumeMessage(consumer); + + channel->BasicReject(env, false); +} + +TEST_F(connected_test, basic_nack_deliveryinfo) { + const BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + + Envelope::DeliveryInfo info; + { + Envelope::ptr_t env = channel->BasicConsumeMessage(consumer); + info = env->GetDeliveryInfo(); + } + + channel->BasicReject(info, false); +} + +TEST_F(connected_test, basic_nack_envelope_with_requeue) { + const BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + std::string queue = channel->DeclareQueue(""); + channel->BasicPublish("", queue, message); + + std::string consumer = channel->BasicConsume(queue, "", true, false); + + Envelope::ptr_t env = channel->BasicConsumeMessage(consumer); + + channel->BasicReject(env, true); + + Envelope::ptr_t env2 = channel->BasicConsumeMessage(consumer); + + channel->BasicReject(env2, false); +} diff --git a/testing/test_publish.cpp b/testing/test_publish.cpp new file mode 100644 index 0000000..978c650 --- /dev/null +++ b/testing/test_publish.cpp @@ -0,0 +1,88 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST_F(connected_test, publish_success) { + BasicMessage::ptr_t message = BasicMessage::Create("message body"); + + channel->BasicPublish("", "test_publish_rk", message); +} + +TEST(test_publish, publish_large_message) { + // Smallest frame size allowed by AMQP + Channel::OpenOpts opts = connected_test::GetTestOpenOpts(); + opts.frame_max = 4096; + Channel::ptr_t channel = Channel::Open(opts); + // Create a message with a body larger than a single frame + BasicMessage::ptr_t message = BasicMessage::Create(std::string(4099, 'a')); + + channel->BasicPublish("", "test_publish_rk", message); +} + +TEST_F(connected_test, publish_badexchange) { + BasicMessage::ptr_t message = BasicMessage::Create("message body"); + + EXPECT_THROW(channel->BasicPublish("test_publish_notexist", "test_publish_rk", + message), + ChannelException); +} + +TEST_F(connected_test, publish_recover_from_error) { + BasicMessage::ptr_t message = BasicMessage::Create("message body"); + + EXPECT_THROW(channel->BasicPublish("test_publish_notexist", "test_publish_rk", + message), + ChannelException); + channel->BasicPublish("", "test_publish_rk", message); +} + +TEST_F(connected_test, publish_mandatory_fail) { + BasicMessage::ptr_t message = BasicMessage::Create("message body"); + + EXPECT_THROW( + channel->BasicPublish("", "test_publish_notexist", message, true), + MessageReturnedException); +} + +TEST_F(connected_test, publish_mandatory_success) { + BasicMessage::ptr_t message = BasicMessage::Create("message body"); + std::string queue = channel->DeclareQueue(""); + + channel->BasicPublish("", queue, message, true); +} + +TEST_F(connected_test, publish_immediate_success) { + BasicMessage::ptr_t message = BasicMessage::Create("message body"); + std::string queue = channel->DeclareQueue(""); + std::string consumer = channel->BasicConsume(queue, ""); + + channel->BasicPublish("", queue, message, true); +} diff --git a/testing/test_queue.cpp b/testing/test_queue.cpp new file mode 100644 index 0000000..f157697 --- /dev/null +++ b/testing/test_queue.cpp @@ -0,0 +1,247 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include "connected_test.h" + +TEST_F(connected_test, queue_declare) { + std::string queue = channel->DeclareQueue(""); + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_declare_named) { + std::string queue = channel->DeclareQueue("declare_queue_test"); + EXPECT_EQ("declare_queue_test", queue); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, check_queue_exists_success) { + channel->DeclareQueue("declare_queue_passive"); + EXPECT_TRUE(channel->CheckQueueExists("declare_queue_passive")); +} + +TEST_F(connected_test, queue_declare_passive) { + std::string queue = channel->DeclareQueue("declare_queue_passive"); + channel->DeclareQueue("declare_queue_passive", true); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, check_queue_exists_fail) { + EXPECT_FALSE(channel->CheckQueueExists("declare_queue_notexist")); +} + +TEST_F(connected_test, queue_declare_passive_fail) { + EXPECT_THROW(channel->DeclareQueue("declare_queue_notexist", true), + ChannelException); +} + +TEST_F(connected_test, queue_declare_durable) { + std::string queue = + channel->DeclareQueue("declare_queue_durable", false, true, false); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_declare_notexclusive) { + std::string queue = + channel->DeclareQueue("declare_queue_notexclusive", false, false, false); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_declare_notautodelete) { + std::string queue = channel->DeclareQueue("declare_queue_notautodelete", + false, false, false, false); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_declare_counts) { + boost::uint32_t message_count = 123; + boost::uint32_t consumer_count = 123; + + std::string queue = channel->DeclareQueueWithCounts( + "queue_declare_counts", message_count, consumer_count); + + EXPECT_NE("", queue); + EXPECT_EQ(0, message_count); + EXPECT_EQ(0, consumer_count); + + const std::string body("Test Message"); + BasicMessage::ptr_t out_message = BasicMessage::Create(body); + channel->BasicPublish("", queue, out_message); + channel->BasicPublish("", queue, out_message); + channel->BasicPublish("", queue, out_message); + + std::string queue2 = channel->DeclareQueueWithCounts( + "queue_declare_counts", message_count, consumer_count); + + EXPECT_NE("", queue2); + EXPECT_EQ(3, message_count); + EXPECT_EQ(0, consumer_count); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_declare_counts_table) { + boost::uint32_t message_count = 123; + boost::uint32_t consumer_count = 123; + + Table qTable; + + qTable.insert(TableEntry(TableKey("IsATest"), TableValue(true))); + + std::string queue = channel->DeclareQueueWithCounts( + "queue_declare_counts_table", message_count, consumer_count, false, false, + true, true, qTable); + + EXPECT_NE("", queue); + EXPECT_EQ(0, message_count); + EXPECT_EQ(0, consumer_count); + + const std::string body("Test Message"); + BasicMessage::ptr_t out_message = BasicMessage::Create(body); + channel->BasicPublish("", queue, out_message); + channel->BasicPublish("", queue, out_message); + channel->BasicPublish("", queue, out_message); + + std::string queue2 = channel->DeclareQueueWithCounts( + "queue_declare_counts_table", message_count, consumer_count, false, false, + true, true, qTable); + + EXPECT_NE("", queue2); + EXPECT_EQ(3, message_count); + EXPECT_EQ(0, consumer_count); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_delete) { + std::string queue = channel->DeclareQueue("delete_queue"); + channel->DeleteQueue(queue); + EXPECT_THROW(channel->DeclareQueue(queue, true), ChannelException); +} + +TEST_F(connected_test, queue_delete_ifunused) { + std::string queue = channel->DeclareQueue("delete_queue_ifunused"); + channel->DeleteQueue(queue, true); + EXPECT_THROW(channel->DeclareQueue(queue, true), ChannelException); +} + +TEST_F(connected_test, queue_delete_ifused) { + std::string queue = channel->DeclareQueue("delete_queue_ifused"); + channel->BasicConsume(queue); + EXPECT_THROW(channel->DeleteQueue(queue, true), ChannelException); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_delete_ifempty) { + std::string queue = channel->DeclareQueue("delete_queue_ifempty"); + channel->DeleteQueue(queue, false, true); + + EXPECT_THROW(channel->DeclareQueue(queue, true), ChannelException); +} + +TEST_F(connected_test, queue_delete_ifnotempty) { + std::string queue = channel->DeclareQueue("delete_queue_ifnotempty"); + BasicMessage::ptr_t message = BasicMessage::Create("Message body"); + channel->BasicPublish("", queue, message, true); + + EXPECT_THROW(channel->DeleteQueue(queue, false, true), ChannelException); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_bind) { + channel->DeclareExchange("queue_bind_exchange"); + std::string queue = channel->DeclareQueue("queue_bind_queue"); + + channel->BindQueue(queue, "queue_bind_exchange", "rk"); + + channel->DeleteExchange("queue_bind_exchange"); + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_bind_badexchange) { + std::string queue = channel->DeclareQueue("queue_bind_badexchange"); + + EXPECT_THROW(channel->BindQueue(queue, "queue_bind_exchangenotexist", "rk"), + ChannelException); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_bind_badqueue) { + channel->DeclareExchange("queue_bind_badqueue"); + + EXPECT_THROW(channel->BindQueue("queue_bind_queuenotexist", + "queue_bind_badqueue", "rk"), + ChannelException); + + channel->DeleteExchange("queue_bind_badqueue"); +} + +TEST_F(connected_test, queue_bind_nokey) { + channel->DeclareExchange("queue_bind_exchange"); + std::string queue = channel->DeclareQueue("queue_bind_queue"); + + channel->BindQueue(queue, "queue_bind_exchange"); + + channel->DeleteExchange("queue_bind_exchange"); + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_unbind) { + channel->DeclareExchange("queue_unbind_exchange"); + std::string queue = channel->DeclareQueue("queue_unbind_queue"); + channel->BindQueue(queue, "queue_unbind_exchange", "rk"); + + channel->UnbindQueue(queue, "queue_unbind_exchange", "rk"); + + channel->DeleteExchange("queue_unbind_exchange"); + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_purge) { + std::string queue = channel->DeclareQueue("queue_purge"); + BasicMessage::ptr_t message = BasicMessage::Create("Message Body"); + channel->BasicPublish("", queue, message, true); + + channel->PurgeQueue(queue); + Envelope::ptr_t envelope; + EXPECT_FALSE(channel->BasicGet(envelope, queue)); + + channel->DeleteQueue(queue); +} + +TEST_F(connected_test, queue_purge_badqueue) { + EXPECT_THROW(channel->PurgeQueue("purge_queue_queuenotexist"), + ChannelException); +} diff --git a/testing/test_table.cpp b/testing/test_table.cpp new file mode 100644 index 0000000..5f02adf --- /dev/null +++ b/testing/test_table.cpp @@ -0,0 +1,896 @@ +/* + * ***** BEGIN LICENSE BLOCK ***** + * Version: MIT + * + * Copyright (c) 2010-2013 Alan Antonuk + * + * 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. + * ***** END LICENSE BLOCK ***** + */ + +#include +#include +#include +#include + +#include "connected_test.h" + +using namespace AmqpClient; + +TEST(table_value, void_value) { + TableValue value; + EXPECT_EQ(TableValue::VT_void, value.GetType()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(); + EXPECT_EQ(TableValue::VT_void, value.GetType()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, bool_value) { + bool v1 = true; + bool v2 = false; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_bool, value.GetType()); + + EXPECT_EQ(v1, value.GetBool()); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_bool, value.GetType()); + + EXPECT_EQ(v2, value.GetBool()); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, uint8_value) { + boost::uint8_t v1 = 1; + boost::uint8_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_uint8, value.GetType()); + EXPECT_EQ(v1, value.GetUint8()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_uint8, value.GetType()); + EXPECT_EQ(v2, value.GetUint8()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, int8_value) { + boost::int8_t v1 = 1; + boost::int8_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_int8, value.GetType()); + EXPECT_EQ(v1, value.GetInt8()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_int8, value.GetType()); + EXPECT_EQ(v2, value.GetInt8()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, uint16_value) { + boost::uint16_t v1 = 1; + boost::uint16_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_uint16, value.GetType()); + EXPECT_EQ(v1, value.GetUint16()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_uint16, value.GetType()); + EXPECT_EQ(v2, value.GetUint16()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, int16_value) { + boost::int16_t v1 = 1; + boost::int16_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_int16, value.GetType()); + EXPECT_EQ(v1, value.GetInt16()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_int16, value.GetType()); + EXPECT_EQ(v2, value.GetInt16()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, uint32_value) { + boost::uint32_t v1 = 1; + boost::uint32_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_uint32, value.GetType()); + EXPECT_EQ(v1, value.GetUint32()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_uint32, value.GetType()); + EXPECT_EQ(v2, value.GetUint32()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, int32_value) { + boost::int32_t v1 = 1; + boost::int32_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_int32, value.GetType()); + EXPECT_EQ(v1, value.GetInt32()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_int32, value.GetType()); + EXPECT_EQ(v2, value.GetInt32()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, timestamp_value) { + std::time_t v1 = 1; + std::time_t v2 = 2; + + TableValue value = TableValue::Timestamp(v1); + EXPECT_EQ(TableValue::VT_timestamp, value.GetType()); + EXPECT_EQ(v1, value.GetTimestamp()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.SetTimestamp(v2); + EXPECT_EQ(TableValue::VT_timestamp, value.GetType()); + EXPECT_EQ(v2, value.GetTimestamp()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, int64_value) { + boost::int64_t v1 = 1; + boost::int64_t v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_int64, value.GetType()); + EXPECT_EQ(v1, value.GetInt64()); + EXPECT_EQ(v1, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_int64, value.GetType()); + EXPECT_EQ(v2, value.GetInt64()); + EXPECT_EQ(v2, value.GetInteger()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, float_value) { + float v1 = 1.; + float v2 = 2.; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_float, value.GetType()); + EXPECT_EQ(v1, value.GetFloat()); + EXPECT_EQ(v1, value.GetReal()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_float, value.GetType()); + EXPECT_EQ(v2, value.GetFloat()); + EXPECT_EQ(v2, value.GetReal()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, double_value) { + double v1 = 1; + double v2 = 2; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_double, value.GetType()); + EXPECT_EQ(v1, value.GetDouble()); + EXPECT_EQ(v1, value.GetReal()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_double, value.GetType()); + EXPECT_EQ(v2, value.GetDouble()); + EXPECT_EQ(v2, value.GetReal()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, string_value) { + std::string v1 = "1"; + std::string v2 = "2"; + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_string, value.GetType()); + EXPECT_EQ(v1, value.GetString()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_string, value.GetType()); + EXPECT_EQ(v2, value.GetString()); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, array_value) { + Array v1; + v1.push_back(TableValue("first")); + + Array v2; + v2.push_back(TableValue((int32_t)2)); + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_array, value.GetType()); + Array v1a = value.GetArray(); + EXPECT_TRUE(v1.size() == v1a.size()); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), v1a.begin())); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_array, value.GetType()); + Array v2a = value.GetArray(); + EXPECT_TRUE(v2.size() == v2a.size()); + EXPECT_TRUE(std::equal(v2.begin(), v2.end(), v2a.begin())); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetTable(), boost::bad_get); +} + +TEST(table_value, table_value) { + Table v1; + v1.insert(TableEntry("one", 10)); + + Table v2; + v2.insert(TableEntry("two", 22.2)); + + TableValue value(v1); + EXPECT_EQ(TableValue::VT_table, value.GetType()); + Table v1a = value.GetTable(); + EXPECT_TRUE(v1.size() == v1a.size()); + EXPECT_TRUE(std::equal(v1.begin(), v1.end(), v1a.begin())); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); + + value.Set(v2); + EXPECT_EQ(TableValue::VT_table, value.GetType()); + Table v2a = value.GetTable(); + EXPECT_TRUE(v2.size() == v2a.size()); + EXPECT_TRUE(std::equal(v2.begin(), v2.end(), v2a.begin())); + + EXPECT_THROW(value.GetBool(), boost::bad_get); + EXPECT_THROW(value.GetUint8(), boost::bad_get); + EXPECT_THROW(value.GetInt8(), boost::bad_get); + EXPECT_THROW(value.GetUint16(), boost::bad_get); + EXPECT_THROW(value.GetInt16(), boost::bad_get); + EXPECT_THROW(value.GetUint32(), boost::bad_get); + EXPECT_THROW(value.GetInt32(), boost::bad_get); + EXPECT_THROW(value.GetTimestamp(), boost::bad_get); + EXPECT_THROW(value.GetInt64(), boost::bad_get); + EXPECT_THROW(value.GetInteger(), boost::bad_get); + EXPECT_THROW(value.GetFloat(), boost::bad_get); + EXPECT_THROW(value.GetDouble(), boost::bad_get); + EXPECT_THROW(value.GetReal(), boost::bad_get); + EXPECT_THROW(value.GetString(), boost::bad_get); + EXPECT_THROW(value.GetArray(), boost::bad_get); +} + +TEST(table_value, equality) { + TableValue void_val1; + TableValue void_val2; + EXPECT_EQ(void_val1, void_val1); + EXPECT_EQ(void_val1, void_val2); + + TableValue bool_val1(true); + TableValue bool_val2(bool_val1); + TableValue bool_val3(false); + TableValue bool_val4; + bool_val4.Set(bool_val3.GetBool()); + EXPECT_EQ(bool_val1, bool_val1); + EXPECT_EQ(bool_val1, bool_val2); + EXPECT_NE(bool_val1, bool_val3); + EXPECT_NE(void_val1, bool_val1); + EXPECT_EQ(bool_val3, bool_val4); + + TableValue int8_val1(int8_t(8)); + TableValue int8_val2(int8_val1); + TableValue int8_val3(int8_t(9)); + EXPECT_EQ(int8_val1, int8_val1); + EXPECT_EQ(int8_val1, int8_val2); + EXPECT_NE(int8_val1, int8_val3); + + TableValue string_val1("one"); + TableValue string_val2(std::string("one")); + std::string empty; + TableValue string_val3(empty); + EXPECT_EQ(string_val1, string_val1); + EXPECT_EQ(string_val1, string_val2); + EXPECT_NE(string_val1, string_val3); + + std::vector vec_val1; + vec_val1.push_back(void_val1); + vec_val1.push_back(int8_val1); + std::vector vec_val2; + vec_val2.push_back(bool_val1); + vec_val2.push_back(void_val1); + TableValue array_val1(vec_val1); + TableValue array_val2(array_val1); + TableValue array_val3(vec_val2); + + EXPECT_EQ(array_val1, array_val1); + EXPECT_EQ(array_val1, array_val2); + EXPECT_NE(array_val1, array_val3); + + Table tbl1; + tbl1.insert(TableEntry("key1", int8_val1)); + tbl1.insert(TableEntry("key2", "string")); + Table tbl2; + tbl2.insert(TableEntry("key1", void_val1)); + tbl2.insert(TableEntry("array", array_val1)); + + TableValue table_val1(tbl1); + TableValue table_val2(tbl1); + TableValue table_val3(tbl2); + + EXPECT_EQ(table_val1, table_val1); + EXPECT_EQ(table_val1, table_val2); + EXPECT_NE(table_val1, table_val3); +} + +TEST(table, convert_to_rabbitmq) { + Table table_in; + table_in.insert(TableEntry("void_key", TableValue())); + table_in.insert(TableEntry("bool_key", true)); + table_in.insert(TableEntry("uint8_key", uint8_t(8))); + table_in.insert(TableEntry("int8_key", int8_t(8))); + table_in.insert(TableEntry("uint16_key", uint16_t(16))); + table_in.insert(TableEntry("int16_key", int16_t(16))); + table_in.insert(TableEntry("uint32_key", uint32_t(32))); + table_in.insert(TableEntry("int32_key", int32_t(32))); + table_in.insert(TableEntry("timestamp_key", TableValue::Timestamp(64))); + table_in.insert(TableEntry("int64_key", int64_t(64))); + table_in.insert(TableEntry("float_key", float(1.5))); + table_in.insert(TableEntry("double_key", double(2.25))); + table_in.insert(TableEntry("string_key", "A string!")); + + std::vector array_in; + array_in.push_back(TableValue(false)); + array_in.push_back(TableValue(int32_t(10))); + array_in.push_back(TableValue(std::string("Another string"))); + + table_in.insert(TableEntry("array_key", array_in)); + + Table table_inner; + table_inner.insert(TableEntry("inner_string", "An inner table")); + table_inner.insert(TableEntry("inner array", array_in)); + + table_in.insert(TableEntry("table_key", table_inner)); + + BasicMessage::ptr_t message = BasicMessage::Create(); + message->HeaderTable(table_in); + + EXPECT_TRUE(message->HeaderTableIsSet()); + Table table_out = message->HeaderTable(); + EXPECT_EQ(table_in.size(), table_out.size()); + EXPECT_TRUE(std::equal(table_in.begin(), table_in.end(), table_out.begin())); +} + +TEST(table, convert_to_rabbitmq_empty) { + Table table_in; + + BasicMessage::ptr_t message = BasicMessage::Create(); + message->HeaderTable(table_in); + + Table table_out = message->HeaderTable(); + EXPECT_EQ(0, table_out.size()); +} + +TEST_F(connected_test, basic_message_header_roundtrip) { + Table table_in; + table_in.insert(TableEntry("void_key", TableValue())); + table_in.insert(TableEntry("bool_key", true)); + table_in.insert(TableEntry("uint8_key", uint8_t(8))); + table_in.insert(TableEntry("int8_key", int8_t(8))); + table_in.insert(TableEntry("uint16_key", uint16_t(16))); + table_in.insert(TableEntry("int16_key", int16_t(16))); + table_in.insert(TableEntry("uint32_key", uint32_t(32))); + table_in.insert(TableEntry("int32_key", int32_t(32))); + table_in.insert(TableEntry("timestamp_key", TableValue::Timestamp(64))); + table_in.insert(TableEntry("int64_key", int64_t(64))); + table_in.insert(TableEntry("float_key", float(1.5))); + table_in.insert(TableEntry("double_key", double(2.25))); + table_in.insert(TableEntry("string_key", "A string!")); + + std::vector array_in; + array_in.push_back(TableValue(false)); + array_in.push_back(TableValue(int32_t(10))); + array_in.push_back(TableValue(std::string("Another string"))); + + table_in.insert(TableEntry("array_key", array_in)); + + Table table_inner; + table_inner.insert(TableEntry("inner_string", "An inner table")); + table_inner.insert(TableEntry("inner array", array_in)); + + table_in.insert(TableEntry("table_key", table_inner)); + + std::string queue = channel->DeclareQueue(""); + std::string tag = channel->BasicConsume(queue, ""); + + BasicMessage::ptr_t message_in = BasicMessage::Create("Body"); + message_in->HeaderTable(table_in); + + channel->BasicPublish("", queue, message_in); + + Envelope::ptr_t envelope = channel->BasicConsumeMessage(tag); + BasicMessage::ptr_t message_out = envelope->Message(); + Table table_out = message_out->HeaderTable(); + + EXPECT_EQ(table_in.size(), table_out.size()); + EXPECT_TRUE(std::equal(table_in.begin(), table_in.end(), table_out.begin())); +} + +TEST_F(connected_test, basic_message_empty_table_roundtrip) { + std::string queue = channel->DeclareQueue(""); + std::string tag = channel->BasicConsume(queue, ""); + + Table table_in; + + BasicMessage::ptr_t message_in = BasicMessage::Create("Body"); + message_in->HeaderTable(table_in); + + channel->BasicPublish("", queue, message_in); + + Envelope::ptr_t envelope = channel->BasicConsumeMessage(tag); + BasicMessage::ptr_t message_out = envelope->Message(); + Table table_out = message_out->HeaderTable(); + + EXPECT_EQ(table_in.size(), table_out.size()); + EXPECT_TRUE(std::equal(table_in.begin(), table_in.end(), table_out.begin())); +}