Добавление Seastar, AMQP-CPP, fmt

This commit is contained in:
Sergey Maslenkov 2024-09-10 17:06:08 +03:00
parent 5d66a3292f
commit 2da60debc0
6771 changed files with 3241004 additions and 0 deletions

View File

@ -0,0 +1,2 @@
-std=c++11
-Wno-pragma-once-outside-header

20
builder/libs/AMQP-CPP/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Compiled Object files
*.slo
*.lo
*.o
# Compiled Dynamic libraries
*.so
*.so.*
*.dylib
# Compiled Static libraries
*.lai
*.la
*.a
*.a.*
/build
/.vscode
.atom-build.cson
.atom-dbg.cson
/bin

View File

@ -0,0 +1,119 @@
################
# Config
################
# C++ project
language: cpp
dist: trusty
sudo: required
group: edge
################
# Services
################
services:
- docker
################
# Build matrix
################
matrix:
include:
################
# Linux / GCC
################
- os: linux
compiler: gcc
env:
- COMPILER_PACKAGE=g++-7
- C_COMPILER=gcc-7
- CXX_COMPILER=g++-7
- os: linux
compiler: gcc
env:
- COMPILER_PACKAGE=g++-8
- C_COMPILER=gcc-8
- CXX_COMPILER=g++-8
- os: linux
compiler: gcc
env:
- COMPILER_PACKAGE=g++-9
- C_COMPILER=gcc-9
- CXX_COMPILER=g++-9
- os: linux
compiler: gcc
env:
- COMPILER_PACKAGE=g++-10
- C_COMPILER=gcc-10
- CXX_COMPILER=g++-10
################
# Linux / Clang
################
- os: linux
env:
- COMPILER_PACKAGE=clang-7
- C_COMPILER=clang-7
- CXX_COMPILER=clang++-7
- os: linux
env:
- COMPILER_PACKAGE=clang-8
- C_COMPILER=clang-8
- CXX_COMPILER=clang++-8
- os: linux
env:
- COMPILER_PACKAGE=clang-9
- C_COMPILER=clang-9
- CXX_COMPILER=clang++-9
- os: linux
env:
- COMPILER_PACKAGE=clang-10
- C_COMPILER=clang-10
- CXX_COMPILER=clang++-10
before_install:
# Show OS/compiler version (this may not be the same OS as the Docker container)
- uname -a
# Use an artful container - gives us access to latest compilers.
- docker run -e "DEBIAN_FRONTEND=noninteractive" -d --name ubuntu-test-container -v $(pwd):/travis ubuntu:focal tail -f /dev/null
- docker ps
install:
# Create our container
- docker exec -t ubuntu-test-container bash -c "apt-get update -y &&
apt-get --no-install-recommends install -y software-properties-common cmake
ninja-build libboost-all-dev libev-dev libuv1-dev ninja-build libssl-dev $COMPILER_PACKAGE &&
apt-get -y clean && rm -rf /var/lib/apt/lists/*"
################
# Build / Test
################
script:
# Run the container that we created and build the code
- docker exec -t ubuntu-test-container bash -c "cd /travis &&
export CC=/usr/bin/$C_COMPILER &&
export CXX=/usr/bin/$CXX_COMPILER &&
mkdir build.release && cd build.release &&
cmake ${CMAKE_OPTIONS} -DAMQP-CPP_BUILD_EXAMPLES=ON -DAMQP-CPP_LINUX_TCP=ON --config Release -GNinja .. &&
cmake --build . &&
cd .."

View File

@ -0,0 +1,138 @@
# Builds AMQP-CPP
#
# Options:
#
# - AMQP-CPP_BUILD_SHARED (default OFF)
# ON: Build shared lib
# OFF: Build static lib
#
# - AMQP-CPP_LINUX_TCP (default OFF)
# ON: Build posix handler implementation
# OFF: Don't build posix handler implementation
cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
# project name
project(amqpcpp)
set (VERSION_MAJOR 4)
set (VERSION_MINOR 3)
set (VERSION_PATCH 26)
set (SO_VERSION ${VERSION_MAJOR}.${VERSION_MINOR})
# build options
option(AMQP-CPP_BUILD_SHARED "Build shared library. If off, build will be static." OFF)
option(AMQP-CPP_LINUX_TCP "Build linux sockets implementation." OFF)
option(AMQP-CPP_BUILD_EXAMPLES "Build amqpcpp examples" OFF)
# pass version number to source files as macro
add_compile_definitions(VERSION=${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
# ensure c++11 on all compilers
set (CMAKE_CXX_STANDARD 17)
# add source files
# ------------------------------------------------------------------------------------------------------
# set include/ as include directory
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/include)
# macro that adds a list of provided source files to a list called SRCS.
# if variable SRCS does not yet exist, it is created.
macro (add_sources)
file (RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
foreach (_src ${ARGN})
if (_relPath)
list (APPEND SRCS "${_relPath}/${_src}")
else()
list (APPEND SRCS "${_src}")
endif()
endforeach()
if (_relPath)
# propagate SRCS to parent directory
set (SRCS ${SRCS} PARENT_SCOPE)
endif()
endmacro()
# add source files
#add_subdirectory(src)
aux_source_directory(src src_MAIN)
if(AMQP-CPP_LINUX_TCP)
#add_subdirectory(src/linux_tcp)
aux_source_directory(src/linux_tcp src_LINUX_TCP)
endif()
# potentially build the examples
if(AMQP-CPP_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
# settings for specific compilers
# ------------------------------------------------------------------------------------------------------
# we have to prevent windows from defining the max macro.
if (WIN32)
add_definitions(-DNOMINMAX)
endif()
# build targets
# ------------------------------------------------------------------------------------------------------
# set output directory
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/bin)
if(AMQP-CPP_BUILD_SHARED)
# create shared lib
#add_library(${PROJECT_NAME} SHARED ${SRCS})
add_library(${PROJECT_NAME} SHARED ${src_MAIN} ${src_LINUX_TCP})
# set shared lib version
set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${SO_VERSION})
else()
# create static lib
#add_library(${PROJECT_NAME} STATIC ${SRCS})
add_library(${PROJECT_NAME} STATIC ${src_MAIN} ${src_LINUX_TCP})
endif()
# install rules
# ------------------------------------------------------------------------------------------------------
if(AMQP-CPP_BUILD_SHARED)
# copy shared lib and its static counter part
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Config
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION lib
)
else()
# copy static lib
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Config
ARCHIVE DESTINATION lib
)
endif()
# copy header files
install(DIRECTORY include/amqpcpp/ DESTINATION include/amqpcpp
FILES_MATCHING PATTERN "*.h")
install(FILES include/amqpcpp.h DESTINATION include)
install(EXPORT ${PROJECT_NAME}Config DESTINATION cmake)
export(TARGETS ${PROJECT_NAME} FILE ${PROJECT_NAME}Config.cmake)
set(DEST_DIR "${CMAKE_INSTALL_PREFIX}")
set(PRIVATE_LIBS "-llibamqpcc")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/amqpcpp.pc.in"
"${CMAKE_CURRENT_BINARY_DIR}/amqpcpp.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/amqpcpp.pc" DESTINATION lib/pkgconfig)
# submodule support
# ------------------------------------------------------------------------------------------------------
target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:include/>
)
if(AMQP-CPP_LINUX_TCP)
target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS})
# Find OpenSSL and provide include dirs
find_package(OpenSSL REQUIRED)
target_include_directories(${PROJECT_NAME} PRIVATE ${OPENSSL_INCLUDE_DIR})
endif()

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,37 @@
PREFIX ?= /usr
INCLUDE_DIR = ${PREFIX}/include
LIBRARY_DIR = ${PREFIX}/lib
export LIBRARY_NAME = amqpcpp
export SONAME = 4.3
export VERSION = 4.3.26
all:
$(MAKE) VERSION=${VERSION} -C src all
pure:
$(MAKE) VERSION=${VERSION} -C src pure
release:
$(MAKE) VERSION=${VERSION} -C src release
static:
$(MAKE) VERSION=${VERSION} -C src static
shared:
$(MAKE) VERSION=${VERSION} -C src shared
clean:
$(MAKE) -C src clean
install:
mkdir -p ${INCLUDE_DIR}/$(LIBRARY_NAME)
mkdir -p ${INCLUDE_DIR}/$(LIBRARY_NAME)/linux_tcp
mkdir -p ${LIBRARY_DIR}
cp -f include/$(LIBRARY_NAME).h ${INCLUDE_DIR}
cp -f include/amqpcpp/*.h ${INCLUDE_DIR}/$(LIBRARY_NAME)
cp -f include/amqpcpp/linux_tcp/*.h ${INCLUDE_DIR}/$(LIBRARY_NAME)/linux_tcp
-cp -f src/lib$(LIBRARY_NAME).so.$(VERSION) ${LIBRARY_DIR}
-cp -f src/lib$(LIBRARY_NAME).a.$(VERSION) ${LIBRARY_DIR}
ln -s -f lib$(LIBRARY_NAME).so.$(VERSION) $(LIBRARY_DIR)/lib$(LIBRARY_NAME).so.$(SONAME)
ln -s -f lib$(LIBRARY_NAME).so.$(VERSION) $(LIBRARY_DIR)/lib$(LIBRARY_NAME).so
ln -s -f lib$(LIBRARY_NAME).a.$(VERSION) $(LIBRARY_DIR)/lib$(LIBRARY_NAME).a

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
prefix=@DEST_DIR@
libdir=${prefix}/lib
includedir=${prefix}/include
Name: amqpcpp
Description: AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker
Version: @VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@
Libs: -L${libdir} -lamqpcpp
Cflags: -I${includedir}

View File

@ -0,0 +1,19 @@
version: '1.0.{build}'
image: Visual Studio 2017
platform:
- x64
configuration:
- Release
- Debug
install:
- git submodule update --init --recursive
before_build:
- cmake -G "Visual Studio 15 2017 Win64" .
build:
project: $(APPVEYOR_BUILD_FOLDER)\amqpcpp.sln

View File

@ -0,0 +1 @@
a.out

View File

@ -0,0 +1,32 @@
###################################
# Boost
###################################
add_executable(amqpcpp_boost_example libboostasio.cpp)
add_dependencies(amqpcpp_boost_example amqpcpp)
target_link_libraries(amqpcpp_boost_example amqpcpp boost_system pthread dl ssl)
###################################
# Libev
###################################
add_executable(amqpcpp_libev_example libev.cpp)
add_dependencies(amqpcpp_libev_example amqpcpp)
target_link_libraries(amqpcpp_libev_example amqpcpp ev pthread dl ssl)
###################################
# Libuv
###################################
add_executable(amqpcpp_libuv_example libuv.cpp)
add_dependencies(amqpcpp_libuv_example amqpcpp)
target_link_libraries(amqpcpp_libuv_example amqpcpp uv pthread dl ssl)

View File

@ -0,0 +1,56 @@
/**
* LibBoostAsio.cpp
*
* Test program to check AMQP functionality based on Boost's asio io_service.
*
* @author Gavin Smith <gavin.smith@coralbay.tv>
*
* Compile with g++ -std=c++14 libboostasio.cpp -o boost_test -lpthread -lboost_system -lamqpcpp
*/
/**
* Dependencies
*/
#include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <amqpcpp.h>
#include <amqpcpp/libboostasio.h>
/**
* Main program
* @return int
*/
int main()
{
// access to the boost asio handler
// note: we suggest use of 2 threads - normally one is fin (we are simply demonstrating thread safety).
boost::asio::io_service service(4);
// handler for libev
AMQP::LibBoostAsioHandler handler(service);
// make a connection
AMQP::TcpConnection connection(&handler, AMQP::Address("amqp://guest:guest@localhost/"));
// we need a channel too
AMQP::TcpChannel channel(&connection);
// create a temporary queue
channel.declareQueue(AMQP::exclusive).onSuccess([&connection](const std::string &name, uint32_t messagecount, uint32_t consumercount) {
// report the name of the temporary queue
std::cout << "declared queue " << name << std::endl;
// now we can close the connection
connection.close();
});
// run the handler
// a t the moment, one will need SIGINT to stop. In time, should add signal handling through boost API.
return service.run();
}

View File

@ -0,0 +1,235 @@
/**
* LibEV.cpp
*
* Test program to check AMQP functionality based on LibEV
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2022 Copernica BV
*/
/**
* Dependencies
*/
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>
/**
* Custom handler
*/
class MyHandler : public AMQP::LibEvHandler
{
private:
/**
* Method that is called when a connection error occurs
* @param connection
* @param message
*/
virtual void onError(AMQP::TcpConnection *connection, const char *message) override
{
std::cout << "error: " << message << std::endl;
}
/**
* Method that is called when the TCP connection ends up in a connected state
* @param connection The TCP connection
*/
virtual void onConnected(AMQP::TcpConnection *connection) override
{
std::cout << "connected" << std::endl;
}
/**
* Method that is called when the TCP connection ends up in a ready
* @param connection The TCP connection
*/
virtual void onReady(AMQP::TcpConnection *connection) override
{
std::cout << "ready" << std::endl;
}
/**
* Method that is called when the TCP connection is closed
* @param connection The TCP connection
*/
virtual void onClosed(AMQP::TcpConnection *connection) override
{
std::cout << "closed" << std::endl;
}
/**
* Method that is called when the TCP connection is detached
* @param connection The TCP connection
*/
virtual void onDetached(AMQP::TcpConnection *connection) override
{
std::cout << "detached" << std::endl;
}
public:
/**
* Constructor
* @param ev_loop
*/
MyHandler(struct ev_loop *loop) : AMQP::LibEvHandler(loop) {}
/**
* Destructor
*/
virtual ~MyHandler() = default;
};
/**
* Class that runs a timer
*/
class MyTimer
{
private:
/**
* The actual watcher structure
* @var struct ev_io
*/
struct ev_timer _timer;
/**
* Pointer towards the AMQP channel
* @var AMQP::TcpChannel
*/
AMQP::TcpChannel *_channel;
/**
* Name of the queue
* @var std::string
*/
std::string _queue;
/**
* Callback method that is called by libev when the timer expires
* @param loop The loop in which the event was triggered
* @param timer Internal timer object
* @param revents The events that triggered this call
*/
static void callback(struct ev_loop *loop, struct ev_timer *timer, int revents)
{
// retrieve the this pointer
MyTimer *self = static_cast<MyTimer*>(timer->data);
// publish a message
self->_channel->publish("", self->_queue, "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
public:
/**
* Constructor
* @param loop
* @param channel
* @param queue
*/
MyTimer(struct ev_loop *loop, AMQP::TcpChannel *channel, std::string queue) :
_channel(channel), _queue(std::move(queue))
{
// initialize the libev structure
ev_timer_init(&_timer, callback, 0.005, 1.005);
// this object is the data
_timer.data = this;
// and start it
ev_timer_start(loop, &_timer);
}
/**
* Destructor
*/
virtual ~MyTimer()
{
// @todo to be implemented
}
};
/**
* Main program
* @return int
*/
int main()
{
// access to the event loop
auto *loop = EV_DEFAULT;
// handler for libev
MyHandler handler(loop);
// init the SSL library
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSL_library_init();
#else
OPENSSL_init_ssl(0, NULL);
#endif
// make a connection
AMQP::Address address("amqp://guest:guest@localhost/");
// AMQP::Address address("amqps://guest:guest@localhost/");
AMQP::TcpConnection connection(&handler, address);
// we need a channel too
AMQP::TcpChannel channel(&connection);
// create a temporary queue
channel.declareQueue(AMQP::exclusive).onSuccess([&connection, &channel, loop](const std::string &queuename, uint32_t messagecount, uint32_t consumercount) {
// report the name of the temporary queue
std::cout << "declared queue " << queuename << std::endl;
// close the channel
//channel.close().onSuccess([&connection, &channel]() {
//
// // report that channel was closed
// std::cout << "channel closed" << std::endl;
//
// // close the connection
// connection.close();
//});
// construct a timer that is going to publish stuff
auto *timer = new MyTimer(loop, &channel, queuename);
// start a consumer
channel.consume(queuename).onSuccess([](const std::string &tag) {
// the consumer is ready
std::cout << "started consuming with tag " << tag << std::endl;
}).onCancelled([](const std::string &tag) {
// the consumer was cancelled by the server
std::cout << "consumer " << tag << " was cancelled" << std::endl;
}).onReceived([&channel, queuename](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) {
std::cout << "received " << deliveryTag << std::endl;
// we remove the queue -- to see if this indeed causes the onCancelled method to be called
if (deliveryTag > 3) channel.removeQueue(queuename);
// ack the message
channel.ack(deliveryTag);
});
//connection.close();
});
// run the loop
ev_run(loop, 0);
// done
return 0;
}

View File

@ -0,0 +1,53 @@
/**
* Libevent.cpp
*
* Test program to check AMQP functionality based on Libevent
*
* @author Brent Dimmig <brentdimmig@gmail.com>
*/
/**
* Dependencies
*/
#include <event2/event.h>
#include <amqpcpp.h>
#include <amqpcpp/libevent.h>
/**
* Main program
* @return int
*/
int main()
{
// access to the event loop
auto evbase = event_base_new();
// handler for libevent
AMQP::LibEventHandler handler(evbase);
// make a connection
AMQP::TcpConnection connection(&handler, AMQP::Address("amqp://localhost/"));
// we need a channel too
AMQP::TcpChannel channel(&connection);
// create a temporary queue
channel.declareQueue(AMQP::exclusive).onSuccess([&connection](const std::string &name, uint32_t messagecount, uint32_t consumercount) {
// report the name of the temporary queue
std::cout << "declared queue " << name << std::endl;
// now we can close the connection
connection.close();
});
// run the loop
event_base_dispatch(evbase);
event_base_free(evbase);
// done
return 0;
}

View File

@ -0,0 +1,86 @@
/**
* LibUV.cpp
*
* Test program to check AMQP functionality based on LibUV
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2017 Copernica BV
*/
/**
* Dependencies
*/
#include <uv.h>
#include <amqpcpp.h>
#include <amqpcpp/libuv.h>
/**
* Custom handler
*/
class MyHandler : public AMQP::LibUvHandler
{
private:
/**
* Method that is called when a connection error occurs
* @param connection
* @param message
*/
virtual void onError(AMQP::TcpConnection *connection, const char *message) override
{
std::cout << "error: " << message << std::endl;
}
/**
* Method that is called when the TCP connection ends up in a connected state
* @param connection The TCP connection
*/
virtual void onConnected(AMQP::TcpConnection *connection) override
{
std::cout << "connected" << std::endl;
}
public:
/**
* Constructor
* @param uv_loop
*/
MyHandler(uv_loop_t *loop) : AMQP::LibUvHandler(loop) {}
/**
* Destructor
*/
virtual ~MyHandler() = default;
};
/**
* Main program
* @return int
*/
int main()
{
// access to the event loop
auto *loop = uv_default_loop();
// handler for libev
MyHandler handler(loop);
// make a connection
AMQP::TcpConnection connection(&handler, AMQP::Address("amqp://guest:guest@localhost/"));
// we need a channel too
AMQP::TcpChannel channel(&connection);
// create a temporary queue
channel.declareQueue(AMQP::exclusive).onSuccess([&connection](const std::string &name, uint32_t messagecount, uint32_t consumercount) {
// report the name of the temporary queue
std::cout << "declared queue " << name << std::endl;
});
// run the loop
uv_run(loop, UV_RUN_DEFAULT);
// done
return 0;
}

View File

@ -0,0 +1,87 @@
/**
* AMQP.h
*
* Starting point for all includes of the Copernica AMQP library
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2018 Copernica BV
*/
#pragma once
// base C++ include files
#include <vector>
#include <string>
#include <memory>
#include <map>
#include <unordered_map>
#include <queue>
#include <limits>
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
// base C include files
#include <stdint.h>
#include <math.h>
// fix strcasecmp on non linux platforms
#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)) && !defined(__CYGWIN__)
#define strcasecmp _stricmp
#endif
// forward declarations
#include "amqpcpp/classes.h"
// utility classes
#include "amqpcpp/endian.h"
#include "amqpcpp/buffer.h"
#include "amqpcpp/bytebuffer.h"
#include "amqpcpp/inbuffer.h"
#include "amqpcpp/outbuffer.h"
#include "amqpcpp/watchable.h"
#include "amqpcpp/monitor.h"
// amqp types
#include "amqpcpp/field.h"
#include "amqpcpp/numericfield.h"
#include "amqpcpp/decimalfield.h"
#include "amqpcpp/stringfield.h"
#include "amqpcpp/booleanset.h"
#include "amqpcpp/fieldproxy.h"
#include "amqpcpp/table.h"
#include "amqpcpp/array.h"
#include "amqpcpp/voidfield.h"
// envelope for publishing and consuming
#include "amqpcpp/metadata.h"
#include "amqpcpp/envelope.h"
#include "amqpcpp/message.h"
// mid level includes
#include "amqpcpp/exchangetype.h"
#include "amqpcpp/flags.h"
#include "amqpcpp/callbacks.h"
#include "amqpcpp/deferred.h"
#include "amqpcpp/deferredconsumer.h"
#include "amqpcpp/deferredqueue.h"
#include "amqpcpp/deferreddelete.h"
#include "amqpcpp/deferredcancel.h"
#include "amqpcpp/deferredconfirm.h"
#include "amqpcpp/deferredget.h"
#include "amqpcpp/deferredrecall.h"
#include "amqpcpp/channelimpl.h"
#include "amqpcpp/channel.h"
#include "amqpcpp/tagger.h"
#include "amqpcpp/throttle.h"
#include "amqpcpp/reliable.h"
#include "amqpcpp/login.h"
#include "amqpcpp/address.h"
#include "amqpcpp/connectionhandler.h"
#include "amqpcpp/connectionimpl.h"
#include "amqpcpp/connection.h"
#include "amqpcpp/openssl.h"

View File

@ -0,0 +1,470 @@
/**
* Address.h
*
* An AMQP address in the "amqp://user:password@hostname:port/vhost" notation
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Includes
*/
#include <type_traits>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Address
{
private:
/**
* Helper class to do case insensitive comparison
*/
struct icasecmp
{
/**
* Comparison operator <. Should exhibit SWO.
* @param lhs
* @param rhs
* @return bool lhs < rhs
*/
bool operator() (const std::string& lhs, const std::string& rhs) const { return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; }
};
private:
/**
* The auth method
* @var bool
*/
bool _secure = false;
/**
* Login data (username + password)
* @var Login
*/
Login _login;
/**
* The hostname
* @var std::string
*/
std::string _hostname;
/**
* Port number
* @var uint16_t
*/
uint16_t _port = 5672;
/**
* The vhost
* @var std::string
*/
std::string _vhost;
/**
* Extra provided options after the question mark /vhost?option=value
* @var std::map<std::string,std::string>
*/
std::map<std::string, std::string, icasecmp> _options;
/**
* The default port
* @return uint16_t
*/
uint16_t defaultport() const
{
return _secure ? 5671 : 5672;
}
public:
/**
* Constructor to parse an address string
* The address should start with "amqp://
* @param data
* @param size
* @throws std::runtime_error
*/
Address(const char *data, size_t size) : _vhost("/")
{
// position of the last byte
const char *last = data + size;
// must start with ampqs:// to have a secure connection (and we also assign a different default port)
if (strncmp(data, "amqps://", 8) == 0) _secure = true;
// otherwise protocol must be amqp://
else if (strncmp(data, "amqp://", 7) != 0) throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\"");
// assign default port (we may overwrite it later)
_port = defaultport();
// begin of the string was parsed
data += _secure ? 8 : 7;
// do we have a '@' to split user-data and hostname?
const char *at = (const char *)memchr(data, '@', last - data);
// do we have one?
if (at != nullptr)
{
// size of the user:password
size_t loginsize = at - data;
// colon could split username and password
const char *colon = (const char *)memchr(data, ':', loginsize);
// assign the login
_login = Login(
std::string(data, colon ? colon - data : loginsize),
std::string(colon ? colon + 1 : "", colon ? at - colon - 1 : 0)
);
// set data to the start of the hostname
data = at + 1;
}
// find out where the vhost is set (starts with a slash)
const char *slash = (const char *)memchr(data, '/', last - data);
// where to start looking for the question mark, we also want to support urls where the
// hostname does not have a slash.
const char *start = slash ? slash : data;
// we search for the ? for extra options
const char *qm = static_cast<const char *>(memchr(start, '?', last - start));
// if there is a questionmark, we need to parse all options
if (qm != nullptr && last - qm > 1)
{
// we start at question mark now
start = qm;
do {
// find the next equals sign and start of the next parameter
const char *equals = (const char *)memchr(start + 1, '=', last - start - 1);
const char *next = (const char *)memchr(start + 1, '&', last - start - 1);
// assign it to the options if we found an equals sign
if (equals) _options[std::string(start + 1, equals - start - 1)] = std::string(equals + 1, (next ? next - equals : last - equals) - 1);
// we now have a new start, the next '&...'
start = next;
// keep iterating as long as there are more vars
} while (start);
}
// was a vhost set?
if (slash != nullptr && last - slash > 1) _vhost.assign(slash + 1, (qm ? qm - slash : last - slash) - 1);
// the hostname is everything until the slash, check is portnumber was set
const char *colon = (const char *)memchr(data, ':', last - data);
// was a portnumber specified (colon must appear before the slash of the vhost)
if (colon && (!slash || colon < slash))
{
// a portnumber was set to
_hostname.assign(data, colon - data);
// calculate the port
_port = atoi(std::string(colon + 1, slash ? slash - colon - 1 : last - colon - 1).data());
}
else
{
// no portnumber was set
_hostname.assign(data, slash ? slash - data : last - data);
}
}
/**
* Constructor to parse an address string
* The address should start with amqp:// or amqps://
* @param data
* @throws std::runtime_error
*/
Address(const char *data) : Address(data, strlen(data)) {}
/**
* Constructor based on std::string
* @param address
*/
Address(const std::string &address) : Address(address.data(), address.size()) {}
/**
* Constructor based on already known properties
* @param host
* @param port
* @param login
* @param vhost
* @param secure
*/
Address(std::string host, uint16_t port, Login login, std::string vhost, bool secure = false) :
_secure(secure),
_login(std::move(login)),
_hostname(std::move(host)),
_port(port),
_vhost(std::move(vhost)) {}
/**
* Destructor
*/
virtual ~Address() = default;
/**
* Should we open a secure connection?
* @return bool
*/
bool secure() const
{
return _secure;
}
/**
* Expose the login data
* @return Login
*/
const Login &login() const
{
return _login;
}
/**
* Host name
* @return std::string
*/
const std::string &hostname() const
{
return _hostname;
}
/**
* Port number
* @return uint16_t
*/
uint16_t port() const
{
return _port;
}
/**
* The vhost to connect to
* @return std::string
*/
const std::string &vhost() const
{
return _vhost;
}
/**
* Get access to the options
* @return std::map<std::string,std::string>
*/
const decltype(_options) &options() const
{
return _options;
}
/**
* Cast to a string
* @return std::string
*/
operator std::string () const
{
// result object
std::string str(_secure ? "amqps://" : "amqp://");
// append login
str.append(_login.user()).append(":").append(_login.password()).append("@").append(_hostname);
// do we need a special portnumber?
if (_port != 5672) str.append(":").append(std::to_string(_port));
// append default vhost
str.append("/");
// do we have a special vhost?
if (_vhost != "/") str.append(_vhost);
// iterate over all options, appending them
if (!_options.empty())
{
// first append a question mark
str.push_back('?');
// iterate over all the options
for (const auto &kv : _options) str.append(kv.first).append("=").append(kv.second).append("&");
// remove the extra &
str.erase(str.size() - 1);
}
// done
return str;
}
/**
* Comparison operator
* @param that
* @return bool
*/
bool operator==(const Address &that) const
{
// security setting should match
if (_secure != that._secure) return false;
// logins must match
if (_login != that._login) return false;
// hostname must match, but are not case sensitive
if (strcasecmp(_hostname.data(), that._hostname.data()) != 0) return false;
// portnumber must match
if (_port != that._port) return false;
// and the vhosts, they must match too
if (_vhost != that._vhost) return false;
// and the options as well
return _options == that._options;
}
/**
* Comparison operator
* @param that
* @return bool
*/
bool operator!=(const Address &that) const
{
// the opposite of operator==
return !operator==(that);
}
/**
* Comparison operator that is useful if addresses have to be ordered
* @param that
* @return bool
*/
bool operator<(const Address &that) const
{
// compare auth methods (amqp comes before amqps)
if (_secure != that._secure) return !_secure;
// compare logins
if (_login != that._login) return _login < that._login;
// hostname must match, but are not case sensitive
int result = strcasecmp(_hostname.data(), that._hostname.data());
// if hostnames are not equal, we know the result
if (result != 0) return result < 0;
// portnumber must match
if (_port != that._port) return _port < that._port;
// and finally compare the vhosts
if (_vhost < that._vhost) return _vhost < that._vhost;
// and finally lexicographically compare the options
return _options < that._options;
}
/**
* Friend function to allow writing the address to a stream
* @param stream
* @param address
* @return std::ostream
*/
friend std::ostream &operator<<(std::ostream &stream, const Address &address)
{
// start with the protocol and login
stream << (address._secure ? "amqps://" : "amqp://");
// do we have a login?
if (address._login) stream << address._login << "@";
// write hostname
stream << address._hostname;
// do we need a special portnumber?
if (address._port != address.defaultport()) stream << ":" << address._port;
// append default vhost
stream << "/";
// do we have a special vhost or options?
if (address._vhost != "/") stream << address._vhost;
// iterate over all options, appending them
if (!address._options.empty())
{
// first append a question mark
stream << '?';
// is this the first option?
bool first = true;
// iterate over all the options
for (const auto &kv : address._options)
{
// write the pair to the stream
stream << (first ? "" : "&") << kv.first << "=" << kv.second;
// no longer on first option
first = false;
}
}
// done
return stream;
}
/**
* Get an integer option
* @param name
* @param fallback
* @return T
*/
template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
T option(const char *name, T fallback) const
{
// find the const char* version of the option
const char *value = option(name);
// if there is a value, convert it to integral, otherwise return the fallback
return value ? static_cast<T>(atoll(value)) : fallback;
}
/**
* Get a const char * option, returns nullptr if it does not exist.
* @return const char *
*/
const char *option(const char *name) const
{
// find the option
auto iter = _options.find(name);
// if not found, we return the default
if (iter == _options.end()) return nullptr;
// return the value in the map
return iter->second.c_str();
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,119 @@
/**
* Addresses.h
*
* Class that contains multiple addresses
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2017 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <cstring>
/**
* Begin of namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Addresses
{
private:
/**
* The actual addresses
* @var std::vector<Address>
*/
std::vector<Address> _addresses;
public:
/**
* Constructor for a comma separated list
* @param buffer
* @param size
* @throws std::runtime_error
*/
Addresses(const char *buffer, size_t size)
{
// keep looping
while (true)
{
// look for the comma
const char *comma = memchr(buffer, ',', size);
// stop if there is no comma
if (comma == nullptr) break;
// size of the address
size_t addresssize = comma - buffer - 1;
// add address
_addresses.emplace_back(buffer, addresssize);
// update for next iteration
buffer += addresssize + 1;
size -= addresssize + 1;
}
// do we have more?
if (size > 0) _addresses.emplace_back(buffer, size);
// was anything found?
if (_addresses.size() > 0) return;
// no addresses found
throw std::runtime_error("no addresses");
}
/**
* Constructor for a comma separated list
* @param buffer
* @throws std::runtime_error
*/
Addresses(const char *buffer) : Addresses(buffer, strlen(buffer)) {}
/**
* Constructor for a comma separated list
* @param buffer
* @throws std::runtime_error
*/
Addresses(const std::string &buffer) : Addresses(buffer.data(), buffer.size()) {}
/**
* Destructed
*/
virtual ~Addresses() = default;
/**
* Number of addresses
* @return size_t
*/
size_t size() const
{
return _addresses.size();
}
/**
* Expose an address by index
* @param index
* @return Address
*/
const Address &operator[](size_t index) const
{
return _addresses.at(index);
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,258 @@
/**
* AMQP field array
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "field.h"
#include "fieldproxy.h"
#include <vector>
#include <ostream>
/**
* Set up namespace
*/
namespace AMQP {
/**
* AMQP field array
*/
class Array : public Field
{
private:
/**
* Definition of an array as a vector
* @typedef
*/
typedef std::vector<std::unique_ptr<Field>> FieldArray;
/**
* The actual fields
* @var FieldArray
*/
FieldArray _fields;
public:
/**
* Constructor to construct an array from a received frame
*
* @param frame received frame
*/
Array(InBuffer &frame);
/**
* Copy constructor
* @param array
*/
Array(const Array &array);
/**
* Move constructor
* @param array
*/
Array(Array &&array) : _fields(std::move(array._fields)) {}
/**
* Constructor for an empty Array
*/
Array() {}
/**
* Destructor
*/
virtual ~Array() {}
/**
* Create a new instance of this object
* @return Field*
*/
virtual std::unique_ptr<Field> clone() const override
{
return std::unique_ptr<Array>(new Array(*this));
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
virtual size_t size() const override;
/**
* Set a field
*
* @param index field index
* @param value field value
* @return Array
*/
Array set(uint8_t index, const Field &value)
{
// make a copy
auto ptr = value.clone();
// should we overwrite an existing record?
if (index >= _fields.size())
{
// append index
_fields.push_back(std::move(ptr));
}
else
{
// overwrite pointer
_fields[index] = std::move(ptr);
}
// allow chaining
return *this;
}
/**
* Get a field
*
* If the field does not exist, an empty string is returned
*
* @param index field index
* @return Field
*/
const Field &get(uint8_t index) const;
/**
* Get number of elements on this array
*
* @return array size
*/
uint32_t count() const;
/**
* Remove last element from array
*/
void pop_back();
/**
* Add field to end of array
*
* @param value
*/
void push_back(const Field &value);
/**
* Get a field
*
* @param index field index
* @return ArrayFieldProxy
*/
ArrayFieldProxy operator[](uint8_t index)
{
return ArrayFieldProxy(this, index);
}
/**
* Get a const field
* @param index field index
* @return Field
*/
const Field &operator[](uint8_t index) const
{
return get(index);
}
/**
* Write encoded payload to the given buffer.
* @param buffer
*/
virtual void fill(OutBuffer& buffer) const override;
/**
* Get the type ID that is used to identify this type of
* field in a field table
* @return char
*/
virtual char typeID() const override
{
return 'A';
}
/**
* We are an array field
*
* @return true, because we are an array
*/
bool isArray() const override
{
return true;
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// prefix
stream << "array(";
// is this the first iteration
bool first = true;
// loop through all members
for (auto &iter : _fields)
{
// split with comma
if (!first) stream << ",";
// show output
stream << *iter;
// no longer first iter
first = false;
}
// postfix
stream << ")";
}
/**
* Cast to array.
*
* @note: This function may look silly and unnecessary. We are, after all, already
* an array. The whole reason we still have this function is that it is virtual
* and if we do not declare a cast to array on a pointer to base (i.e. Field)
* will return an empty field instead of the expected array.
*
* Yes, clang gets this wrong and gives incorrect warnings here. See
* https://llvm.org/bugs/show_bug.cgi?id=28263 for more information
*
* @return Ourselves
*/
virtual operator const Array& () const override
{
// this already is an array, so no cast is necessary
return *this;
}
};
/**
* Custom output stream operator
* @param stream
* @param field
* @return ostream
*/
inline std::ostream &operator<<(std::ostream &stream, const ArrayFieldProxy &field)
{
// get underlying field, and output that
return stream << field.get();
}
/**
* end namespace
*/
}

View File

@ -0,0 +1,202 @@
/**
* BooleanSet.h
*
* AMQP can store eight booleans in a single byte. This class
* is a utility class for setting and getting the eight
* booleans
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <ostream>
#include "field.h"
#include "outbuffer.h"
#include "inbuffer.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class BooleanSet : public Field
{
private:
/**
* The actual byte
* @var uint8_t
*/
uint8_t _byte;
public:
/**
* Constructor
* @param first the first bool to set
* @param second the second bool to set
* @param third the third bool to set
* @param fourth the fourth bool to set
* @param fifth the fifth bool to set
* @param sixth the sixth bool to set
* @param seventh the seventh bool to set
* @param eigth the eigth bool to set
*/
BooleanSet(bool first = false, bool second = false, bool third = false, bool fourth = false, bool fifth = false, bool sixth = false, bool seventh = false, bool eigth = false)
{
_byte = 0;
set(0, first);
set(1, second);
set(2, third);
set(3, fourth);
set(4, fifth);
set(5, sixth);
set(6, seventh);
set(7, eigth);
}
/**
* Constructor based on incoming data
* @param frame
*/
BooleanSet(InBuffer &frame)
{
_byte = frame.nextUint8();
}
/**
* Copy constructor
* @param that
*/
BooleanSet(const BooleanSet &that)
{
_byte = that._byte;
}
/**
* Destructor
*/
virtual ~BooleanSet() {}
/**
* Extending from field forces us to implement a clone function.
* @return unique_ptr
*/
virtual std::unique_ptr<Field> clone() const override
{
return std::unique_ptr<Field>(new BooleanSet(*this));
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// prefix
stream << "booleanset(";
// the members
for (int i=0; i<8; i++) stream << (i == 0 ? "" : ",") << (get(i) ? 1 : 0);
// postfix
stream << ")";
}
/**
* Get one of the booleans
* @param index from 0 to 7, where 0 is rightmost bit
* @return bool
*/
bool get(uint32_t index) const
{
// bigger than seven is not an option
if (index > 7) return false;
// magic bit manipulation...
return 0 != ((1 << index) & _byte);
}
/**
* Set a boolean in the set
* @param index
* @param value
*/
void set(uint32_t index, bool value)
{
// index must be valid
if (index > 7) return;
// are we setting or unsetting
if (value)
{
// magic bit manipulation...
_byte |= (1 << index);
}
else
{
// magic bit manipulation...
_byte &= ~(1 << index);
}
}
/**
* Fill the buffer
* @param buffer
*/
virtual void fill(OutBuffer& buffer) const override
{
buffer.add(_byte);
}
/**
* Get the byte value
* @return value
*/
uint8_t value() const
{
return _byte;
}
/**
* Type ID
* @return char
*/
virtual char typeID() const override
{
return 't';
}
/**
* We are a boolean field
*
* @return true, because we are a boolean
*/
bool isBoolean() const override
{
return true;
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
*/
virtual size_t size() const override
{
// booleanset takes up a single byte.
return 1;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,84 @@
/**
* Buffer.h
*
* Interface that can be implemented by client applications and that
* is passed to the Connection::parse() method.
*
* Normally, the Connection::parse() method is fed with a byte
* array. However, if you're receiving big frames, it may be inconvenient
* to copy these big frames into continguous byte arrays, and you
* prefer using objects that internally use linked lists or other
* ways to store the bytes. In such sitations, you can implement this
* interface and pass that to the connection.
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Buffer
{
public:
/**
* Destructor
*/
virtual ~Buffer() {}
/**
* Total size of the buffer
* @return size_t
*/
virtual size_t size() const = 0;
/**
* Get access to a single byte
*
* No safety checks are necessary: this method will only be called
* for bytes that actually exist
*
* @param pos position in the buffer
* @return char value of the byte in the buffer
*/
virtual char byte(size_t pos) const = 0;
/**
* Get access to the raw data
* @param pos position in the buffer
* @param size number of continuous bytes
* @return char*
*/
virtual const char *data(size_t pos, size_t size) const = 0;
/**
* Copy bytes to a buffer
*
* No safety checks are necessary: this method will only be called
* for bytes that actually exist
*
* @param pos position in the buffer
* @param size number of bytes to copy
* @param buffer buffer to copy into
* @return void* pointer to buffer
*/
virtual void *copy(size_t pos, size_t size, void *buffer) const = 0;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,140 @@
/**
* ByteByffer.h
*
* Very simple implementation of the buffer class that simply wraps
* around a buffer of bytes
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Open namespace
*/
namespace AMQP {
/**
* Class definition
*/
class ByteBuffer : public Buffer
{
protected:
/**
* The actual byte buffer
* @var const char *
*/
const char *_data;
/**
* Size of the buffer
* @var size_t
*/
size_t _size;
public:
/**
* Constructor
* @param data
* @param size
*/
ByteBuffer(const char *data, size_t size) : _data(data), _size(size) {}
/**
* No copy'ing
* @param that
*/
ByteBuffer(const ByteBuffer &that) = delete;
/**
* Move constructor
* @param that
*/
ByteBuffer(ByteBuffer &&that) : _data(that._data), _size(that._size)
{
// reset other object
that._data = nullptr;
that._size = 0;
}
/**
* Destructor
*/
virtual ~ByteBuffer() {}
/**
* Move assignment operator
* @param that
*/
ByteBuffer &operator=(ByteBuffer &&that)
{
// skip self-assignment
if (this == &that) return *this;
// copy members
_data = that._data;
_size = that._size;
// reset other object
that._data = nullptr;
that._size = 0;
// done
return *this;
}
/**
* Total size of the buffer
* @return size_t
*/
virtual size_t size() const override
{
return _size;
}
/**
* Get access to a single byte
* @param pos position in the buffer
* @return char value of the byte in the buffer
*/
virtual char byte(size_t pos) const override
{
return _data[pos];
}
/**
* Get access to the raw data
* @param pos position in the buffer
* @param size number of continuous bytes
* @return char*
*/
virtual const char *data(size_t pos, size_t size) const override
{
// make sure compilers dont complain about unused parameters
(void) size;
// expose the data
return _data + pos;
}
/**
* Copy bytes to a buffer
* @param pos position in the buffer
* @param size number of bytes to copy
* @param buffer buffer to copy into
* @return size_t pointer to buffer
*/
virtual void *copy(size_t pos, size_t size, void *buffer) const override
{
return memcpy(buffer, _data + pos, size);
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,98 @@
/**
* Callbacks.h
*
* Class storing deferred callbacks of different type.
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <string>
#include <functional>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class Message;
class MetaData;
/**
* Generic callbacks that are used by many deferred objects
*/
using SuccessCallback = std::function<void()>;
using ErrorCallback = std::function<void(const char *message)>;
using FinalizeCallback = std::function<void()>;
/**
* Declaring and deleting a queue
*/
using QueueCallback = std::function<void(const std::string &name, uint32_t messagecount, uint32_t consumercount)>;
using DeleteCallback = std::function<void(uint32_t deletedmessages)>;
/**
* When retrieving the size of a queue in some way
*/
using EmptyCallback = std::function<void()>;
using CountCallback = std::function<void(uint32_t messagecount)>;
using SizeCallback = std::function<void(uint64_t messagesize)>;
/**
* Starting and stopping a consumer
*/
using ConsumeCallback = std::function<void(const std::string &consumer)>;
using CancelCallback = std::function<void(const std::string &consumer)>;
/**
* Receiving messages, either via consume(), get() or as returned messages
* The following methods receive the returned message in multiple parts
*/
using StartCallback = std::function<void(const std::string &exchange, const std::string &routingkey)>;
using HeaderCallback = std::function<void(const MetaData &metaData)>;
using DataCallback = std::function<void(const char *data, size_t size)>;
using DeliveredCallback = std::function<void(uint64_t deliveryTag, bool redelivered)>;
/**
* For returned messages amqp-cpp first calls a return-callback before the start,
* header and data callbacks are called. Instead of the deliver-callback, a
* returned-callback is called.
*/
using ReturnCallback = std::function<void(int16_t code, const std::string &message)>;
using ReturnedCallback = std::function<void()>;
/**
* If you do not want to merge all data into a single string, you can als
* implement callbacks that return the collected message.
*/
using MessageCallback = std::function<void(const Message &message, uint64_t deliveryTag, bool redelivered)>;
using BounceCallback = std::function<void(const Message &message, int16_t code, const std::string &description)>;
/**
* When using publisher confirms, AckCallback is called when server confirms that message is received
* and processed. NackCallback is called otherwise.
*/
using AckCallback = std::function<void(uint64_t deliveryTag, bool multiple)>;
using NackCallback = std::function<void(uint64_t deliveryTag, bool multiple, bool requeue)>;
/**
* When using a confirm wrapped channel, these callbacks are called when a message is acknowledged/nacked.
*/
using PublishAckCallback = std::function<void()>;
using PublishNackCallback = std::function<void()>;
using PublishLostCallback = std::function<void()>;
/**
* End namespace
*/
}

View File

@ -0,0 +1,616 @@
/**
* Class describing a (mid-level) AMQP channel implementation
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Channel
{
private:
/**
* The implementation for the channel
* @var std::shared_ptr<ChannelImpl>
*/
std::shared_ptr<ChannelImpl> _implementation;
public:
/**
* Construct a channel object
*
* The passed in connection pointer must remain valid for the
* lifetime of the channel. Watch out: this method throws an error
* if the channel could not be constructed (for example because the
* max number of AMQP channels has been reached)
*
* @param connection
* @throws std::runtime_error
*/
Channel(Connection *connection);
/**
* Copy'ing of channel objects is not supported
* @param channel
*/
Channel(const Channel &channel) = delete;
/**
* But movement _is_ allowed
* @param channel
*/
Channel(Channel &&channel) : _implementation(std::move(channel._implementation)) {}
/**
* Destructor
*/
virtual ~Channel()
{
// close the channel (this will eventually destruct the channel)
// note that the channel may be in an invalid state in case it was moved, hence the "if"
if (_implementation) _implementation->close();
}
/**
* No assignments of other channels
* @param channel
* @return Channel
*/
Channel &operator=(const Channel &channel) = delete;
/**
* Callback that is called when the channel was succesfully created.
*
* Only one callback can be registered. Calling this function multiple
* times will remove the old callback.
*
* @param callback the callback to execute
*/
inline void onReady(const SuccessCallback& callback) { return onReady(SuccessCallback(callback)); }
void onReady(SuccessCallback&& callback)
{
_implementation->onReady(std::move(callback));
}
/**
* Callback that is called when an error occurs.
*
* Only one error callback can be registered. Calling this function
* multiple times will remove the old callback.
*
* @param callback the callback to execute
*/
inline void onError(const ErrorCallback& callback) { return onError(ErrorCallback(callback)); }
void onError(ErrorCallback&& callback)
{
_implementation->onError(std::move(callback));
}
/**
* Pause deliveries on a channel
*
* This will stop all incoming messages
*
* Note that this function does *not* work using RabbitMQ. For more info
* @see https://www.rabbitmq.com/specification.html#method-status-channel.flow
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &pause()
{
return _implementation->pause();
}
/**
* Resume a paused channel
*
* This will resume incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &resume()
{
return _implementation->resume();
}
/**
* Is the channel ready / has it passed the initial handshake?
* @return bool
*/
bool ready() const
{
return _implementation->ready();
}
/**
* Is the channel usable / not yet closed?
* @return bool
*/
bool usable() const
{
return _implementation->usable();
}
/**
* Is the channel connected?
* This method is deprecated: use Channel::usable()
* @return bool
* @deprecated
*/
bool connected() const
{
return usable();
}
/**
* Put channel in a confirm mode (RabbitMQ specific)
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
DeferredConfirm &confirmSelect()
{
return _implementation->confirmSelect();
}
/**
* Start a transaction
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &startTransaction()
{
return _implementation->startTransaction();
}
/**
* Commit the current transaction
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &commitTransaction()
{
return _implementation->commitTransaction();
}
/**
* Rollback the current transaction
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &rollbackTransaction()
{
return _implementation->rollbackTransaction();
}
/**
* Declare an exchange
*
* If an empty name is supplied, a name will be assigned by the server.
*
* The following flags can be used for the exchange:
*
* - durable exchange survives a broker restart
* - autodelete exchange is automatically removed when all connected queues are removed
* - passive only check if the exchange exist
* - internal create an internal exchange
*
* @param name name of the exchange
* @param type exchange type
* @param flags exchange flags
* @param arguments additional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &declareExchange(const std::string_view &name, ExchangeType type, int flags, const Table &arguments) { return _implementation->declareExchange(name, type, flags, arguments); }
Deferred &declareExchange(const std::string_view &name, ExchangeType type, const Table &arguments) { return _implementation->declareExchange(name, type, 0, arguments); }
Deferred &declareExchange(const std::string_view &name, ExchangeType type = fanout, int flags = 0) { return _implementation->declareExchange(name, type, flags, Table()); }
Deferred &declareExchange(ExchangeType type, int flags, const Table &arguments) { return _implementation->declareExchange(std::string_view(), type, flags, arguments); }
Deferred &declareExchange(ExchangeType type, const Table &arguments) { return _implementation->declareExchange(std::string_view(), type, 0, arguments); }
Deferred &declareExchange(ExchangeType type = fanout, int flags = 0) { return _implementation->declareExchange(std::string_view(), type, flags, Table()); }
/**
* Remove an exchange
*
* The following flags can be used for the exchange:
*
* - ifunused only delete if no queues are connected
* @param name name of the exchange to remove
* @param flags optional flags
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &removeExchange(const std::string_view &name, int flags = 0) { return _implementation->removeExchange(name, flags); }
/**
* Bind two exchanges to each other
*
* @param source the source exchange
* @param target the target exchange
* @param routingkey the routing key
* @param arguments additional bind arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &bindExchange(const std::string_view &source, const std::string_view &target, const std::string_view &routingkey, const Table &arguments) { return _implementation->bindExchange(source, target, routingkey, arguments); }
Deferred &bindExchange(const std::string_view &source, const std::string_view &target, const std::string_view &routingkey) { return _implementation->bindExchange(source, target, routingkey, Table()); }
/**
* Unbind two exchanges from one another
*
* @param target the target exchange
* @param source the source exchange
* @param routingkey the routing key
* @param arguments additional unbind arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &unbindExchange(const std::string_view &target, const std::string_view &source, const std::string_view &routingkey, const Table &arguments) { return _implementation->unbindExchange(target, source, routingkey, arguments); }
Deferred &unbindExchange(const std::string_view &target, const std::string_view &source, const std::string_view &routingkey) { return _implementation->unbindExchange(target, source, routingkey, Table()); }
/**
* Declare a queue
*
* If you do not supply a name, a name will be assigned by the server.
*
* The flags can be a combination of the following values:
*
* - durable queue survives a broker restart
* - autodelete queue is automatically removed when all connected consumers are gone
* - passive only check if the queue exist
* - exclusive the queue only exists for this connection, and is automatically removed when connection is gone
*
* @param name name of the queue
* @param flags combination of flags
* @param arguments optional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(const std::string &name, uint32_t messageCount, uint32_t consumerCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](const std::string &name, uint32_t messageCount, uint32_t consumerCount) {
*
* std::cout << "Queue '" << name << "' has been declared with " << messageCount << " messages and " << consumerCount << " consumers" << std::endl;
*
* });
*/
DeferredQueue &declareQueue(const std::string_view &name, int flags, const Table &arguments) { return _implementation->declareQueue(name, flags, arguments); }
DeferredQueue &declareQueue(const std::string_view &name, const Table &arguments) { return _implementation->declareQueue(name, 0, arguments); }
DeferredQueue &declareQueue(const std::string_view &name, int flags = 0) { return _implementation->declareQueue(name, flags, Table()); }
DeferredQueue &declareQueue(int flags, const Table &arguments) { return _implementation->declareQueue(std::string_view(), flags, arguments); }
DeferredQueue &declareQueue(const Table &arguments) { return _implementation->declareQueue(std::string_view(), 0, arguments); }
DeferredQueue &declareQueue(int flags = 0) { return _implementation->declareQueue(std::string_view(), flags, Table()); }
/**
* Bind a queue to an exchange
*
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param arguments additional bind arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &bindQueue(const std::string_view &exchange, const std::string_view &queue, const std::string_view &routingkey, const Table &arguments) { return _implementation->bindQueue(exchange, queue, routingkey, arguments); }
Deferred &bindQueue(const std::string_view &exchange, const std::string_view &queue, const std::string_view &routingkey) { return _implementation->bindQueue(exchange, queue, routingkey, Table()); }
/**
* Unbind a queue from an exchange
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param arguments additional bind arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &unbindQueue(const std::string_view &exchange, const std::string_view &queue, const std::string_view &routingkey, const Table &arguments) { return _implementation->unbindQueue(exchange, queue, routingkey, arguments); }
Deferred &unbindQueue(const std::string_view &exchange, const std::string_view &queue, const std::string_view &routingkey) { return _implementation->unbindQueue(exchange, queue, routingkey, Table()); }
/**
* Purge a queue
*
* @param name name of the queue
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(uint32_t messageCount);
*
* For example: channel.purgeQueue("myqueue").onSuccess([](uint32_t messageCount) {
*
* std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl;
*
* });
*/
DeferredDelete &purgeQueue(const std::string_view &name){ return _implementation->purgeQueue(name); }
/**
* Remove a queue
*
* The following flags can be used for the exchange:
*
* - ifunused only delete if no consumers are connected
* - ifempty only delete if the queue is empty
*
* @param name name of the queue to remove
* @param flags optional flags
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(uint32_t messageCount);
*
* For example: channel.removeQueue("myqueue").onSuccess([](uint32_t messageCount) {
*
* std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl;
*
* });
*/
DeferredDelete &removeQueue(const std::string_view &name, int flags = 0) { return _implementation->removeQueue(name, flags); }
/**
* Publish a message to an exchange
*
* You have to supply the name of an exchange and a routing key. RabbitMQ will then try
* to send the message to one or more queues. With the optional flags parameter you can
* specify what should happen if the message could not be routed to a queue. By default,
* unroutable message are silently discarded.
*
* If you set the 'mandatory' and/or 'immediate' flag, messages that could not be handled
* are returned to the application. Make sure that you have called the recall()-method and
* have set up all appropriate handlers to process these returned messages before you start
* publishing.
*
* The following flags can be supplied:
*
* - mandatory If set, server returns messages that are not sent to a queue
* - immediate If set, server returns messages that can not immediately be forwarded to a consumer.
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
* @param flags optional flags
*/
bool publish(const std::string_view &exchange, const std::string_view &routingKey, const Envelope &envelope, int flags = 0) { return _implementation->publish(exchange, routingKey, envelope, flags); }
bool publish(const std::string_view &exchange, const std::string_view &routingKey, const std::string &message, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message.data(), message.size()), flags); }
bool publish(const std::string_view &exchange, const std::string_view &routingKey, const char *message, size_t size, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message, size), flags); }
bool publish(const std::string_view &exchange, const std::string_view &routingKey, const char *message, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message, strlen(message)), flags); }
/**
* Set the Quality of Service (QOS) for this channel
*
* When you consume messages, every single message needs to be ack'ed to inform
* the RabbitMQ server that is has been received. The Qos setting specifies the
* number of unacked messages that may exist in the client application. The server
* stops delivering more messages if the number of unack'ed messages has reached
* the prefetchCount
*
* @param prefetchCount maximum number of messages to prefetch
* @param global share counter between all consumers on the same channel
* @return bool whether the Qos frame is sent.
*/
Deferred &setQos(uint16_t prefetchCount, bool global = false)
{
return _implementation->setQos(prefetchCount, global);
}
/**
* Tell the RabbitMQ server that we're ready to consume messages
*
* After this method is called, RabbitMQ starts delivering messages to the client
* application. The consume tag is a string identifier that you can use to identify
* the consumer if you later want to stop it with with a channel::cancel() call.
* If you do not specify a consumer tag, the server will assign one for you.
*
* The following flags are supported:
*
* - nolocal if set, messages published on this channel are not also consumed
* - noack if set, consumed messages do not have to be acked, this happens automatically
* - exclusive request exclusive access, only this consumer can access the queue
*
* @param queue the queue from which you want to consume
* @param tag a consumer tag that will be associated with this consume operation
* @param flags additional flags
* @param arguments additional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(const std::string_view& tag);
*
* For example: channel.consume("myqueue").onSuccess([](const std::string_view& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
DeferredConsumer &consume(const std::string_view &queue, const std::string_view &tag, int flags, const Table &arguments) { return _implementation->consume(queue, tag, flags, arguments); }
DeferredConsumer &consume(const std::string_view &queue, const std::string_view &tag, int flags = 0) { return _implementation->consume(queue, tag, flags, Table()); }
DeferredConsumer &consume(const std::string_view &queue, const std::string_view &tag, const Table &arguments) { return _implementation->consume(queue, tag, 0, arguments); }
DeferredConsumer &consume(const std::string_view &queue, int flags, const Table &arguments) { return _implementation->consume(queue, std::string_view(), flags, arguments); }
DeferredConsumer &consume(const std::string_view &queue, int flags = 0) { return _implementation->consume(queue, std::string_view(), flags, Table()); }
DeferredConsumer &consume(const std::string_view &queue, const Table &arguments) { return _implementation->consume(queue, std::string_view(), 0, arguments); }
/**
* Tell the messages that you are ready to recall/take back messages that messages thar are unroutable.
*
* When you use the publish() method in combination with the 'immediate' or 'mandatory' flag, rabbitmq
* sends back unroutable messages. With this recall() method you can install a sort of pseudo-consumer
* that defines how such returned-messages are processed.
*
* Watch out: when you call this method more than once, you always get access to the same object. You
* can thus not install multiple callbacks for the same event.
*/
DeferredRecall &recall() { return _implementation->recall(); }
/**
* Cancel a running consume call
*
* If you want to stop a running consumer, you can use this method with the consumer tag
*
* @param tag the consumer tag
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(const std::string& tag);
*
* For example: channel.cancel("myqueue").onSuccess([](const std::string& tag) {
*
* std::cout << "Stopped consuming under tag " << tag << std::endl;
*
* });
*/
DeferredCancel &cancel(const std::string_view &tag) { return _implementation->cancel(tag); }
/**
* Retrieve a single message from RabbitMQ
*
* When you call this method, you can get one single message from the queue (or none
* at all if the queue is empty). The deferred object that is returned, should be used
* to install a onEmpty() and onSuccess() callback function that will be called
* when the message is consumed and/or when the message could not be consumed.
*
* The following flags are supported:
*
* - noack if set, consumed messages do not have to be acked, this happens automatically
*
* @param queue name of the queue to consume from
* @param flags optional flags
*
* The object returns a deferred handler. Callbacks can be installed
* using onSuccess(), onEmpty(), onError() and onFinalize() methods.
*
* The onSuccess() callback has the following signature:
*
* void myCallback(const Message &message, uint64_t deliveryTag, bool redelivered);
*
* For example: channel.get("myqueue").onSuccess([](const Message &message, uint64_t deliveryTag, bool redelivered) {
*
* std::cout << "Message fetched" << std::endl;
*
* }).onEmpty([]() {
*
* std::cout << "Queue is empty" << std::endl;
*
* });
*/
DeferredGet &get(const std::string_view &queue, int flags = 0) { return _implementation->get(queue, flags); }
/**
* Acknoldge a received message
*
* When a message is received in the DeferredConsumer::onReceived() method,
* you must acknowledge it so that RabbitMQ removes it from the queue (unless
* you are consuming with the noack option). This method can be used for
* this acknowledging.
*
* The following flags are supported:
*
* - multiple acknowledge multiple messages: all un-acked messages that were earlier delivered are acknowledged too
*
* @param deliveryTag the unique delivery tag of the message
* @param flags optional flags
* @return bool
*/
bool ack(uint64_t deliveryTag, int flags=0) { return _implementation->ack(deliveryTag, flags); }
/**
* Reject or nack a message
*
* When a message was received in the DeferredConsumer::onReceived() method,
* and you don't want to acknowledge it, you can also choose to reject it by
* calling this reject method.
*
* The following flags are supported:
*
* - multiple reject multiple messages: all un-acked messages that were earlier delivered are unacked too
* - requeue if set, the message is put back in the queue, otherwise it is dead-lettered/removed
*
* @param deliveryTag the unique delivery tag of the message
* @param flags optional flags
* @return bool
*/
bool reject(uint64_t deliveryTag, int flags=0) { return _implementation->reject(deliveryTag, flags); }
/**
* Recover all messages that were not yet acked
*
* This method asks the server to redeliver all unacknowledged messages on a specified
* channel. Zero or more messages may be redelivered.
*
* The following flags are supported:
*
* - requeue if set, the server will requeue the messages, so the could also end up with at different consumer
*
* @param flags
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &recover(int flags = 0) { return _implementation->recover(flags); }
/**
* Close the current channel
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &close() { return _implementation->close(); }
/**
* Get the channel we're working on
* @return uint16_t
*/
uint16_t id() const
{
return _implementation->id();
}
/**
* Some internal classes may touch our implementation
*/
friend class Tagger;
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,803 @@
/**
* ChannelImpl.h
*
* Extended channel object that is used internally by the library, but
* that has a private constructor so that it can not be used from outside
* the AMQP library
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "exchangetype.h"
#include "watchable.h"
#include "callbacks.h"
#include "copiedbuffer.h"
#include "deferred.h"
#include "monitor.h"
#include <memory>
#include <queue>
#include <map>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class DeferredReceiver;
class BasicDeliverFrame;
class DeferredConsumer;
class BasicGetOKFrame;
class ConsumedMessage;
class ConnectionImpl;
class DeferredDelete;
class DeferredCancel;
class DeferredConfirm;
class DeferredQueue;
class DeferredGet;
class DeferredRecall;
class Connection;
class Envelope;
class Table;
class Frame;
/**
* Class definition
*/
class ChannelImpl : public Watchable, public std::enable_shared_from_this<ChannelImpl>
{
private:
/**
* Pointer to the connection
* @var ConnectionImpl
*/
ConnectionImpl *_connection = nullptr;
/**
* Callback when the channel is ready
* @var SuccessCallback
*/
SuccessCallback _readyCallback;
/**
* Callback when the channel errors out
* @var ErrorCallback
*/
ErrorCallback _errorCallback;
/**
* Handler that deals with incoming messages as a result of publish operations
* @var DeferredRecall
*/
std::shared_ptr<DeferredRecall> _recall;
/**
* Handler that deals with publisher confirms frames
* @var std::shared_ptr<DeferredConfirm>
*/
std::shared_ptr<DeferredConfirm> _confirm;
/**
* Handlers for all consumers that are active
* @var std::map<std::string,std::shared_ptr<DeferredConsumer>
*/
std::map<std::string,std::shared_ptr<DeferredConsumer>> _consumers;
/**
* Pointer to the oldest deferred result (the first one that is going
* to be executed)
*
* @var Deferred
*/
std::shared_ptr<Deferred> _oldestCallback;
/**
* Pointer to the newest deferred result (the last one to be added).
*
* @var Deferred
*/
std::shared_ptr<Deferred> _newestCallback;
/**
* The channel number
* @var uint16_t
*/
uint16_t _id = 0;
/**
* State of the channel object
* @var enum
*/
enum {
state_connected,
state_ready,
state_closing,
state_closed
} _state = state_closed;
/**
* The frames that still need to be send out
*
* We store the data as well as whether they
* should be handled synchronously.
*
* @var std::queue
*/
std::queue<CopiedBuffer> _queue;
/**
* Are we currently operating in synchronous mode? Meaning: do we first have
* to wait for the answer to previous instructions before we send a new instruction?
* @var bool
*/
bool _synchronous = false;
/**
* The current object that is busy receiving a message
* @var std::shared_ptr<DeferredReceiver>
*/
std::shared_ptr<DeferredReceiver> _receiver;
/**
* Attach the connection
* @param connection
* @return bool
*/
bool attach(Connection *connection);
/**
* Push a deferred result
* @param result The deferred result
* @return Deferred The object just pushed
*/
Deferred &push(const std::shared_ptr<Deferred> &deferred);
/**
* Send a framen and push a deferred result
* @param frame The frame to send
* @return Deferred The object just pushed
*/
Deferred &push(const Frame &frame);
protected:
/**
* Construct a channel object
*
* Note that the constructor is private, and that the Channel class is
* a friend. By doing this we ensure that nobody can instantiate this
* object, and that it can thus only be used inside the library.
*/
ChannelImpl();
public:
/**
* Copy'ing of channel objects is not supported
* @param channel
*/
ChannelImpl(const ChannelImpl &channel) = delete;
/**
* Destructor
*/
virtual ~ChannelImpl();
/**
* No assignments of other channels
* @param channel
* @return Channel
*/
ChannelImpl &operator=(const ChannelImpl &channel) = delete;
/**
* Invalidate the channel
* This method is called when the connection is destructed
*/
void detach()
{
// connection is gone
_connection = nullptr;
}
/**
* Expose the currently installed callbacks
* @return ErrorCallback
*/
const ErrorCallback &onError() const { return _errorCallback; }
const SuccessCallback &onReady() const { return _readyCallback; }
/**
* Callback that is called when the channel was succesfully created.
* @param callback the callback to execute
*/
inline void onReady(const SuccessCallback& callback) { return onReady(SuccessCallback(callback)); }
void onReady(SuccessCallback&& callback)
{
// store callback
_readyCallback = std::move(callback);
// direct call if channel is already ready
if (_state == state_ready && _readyCallback) _readyCallback();
}
/**
* Callback that is called when an error occurs.
*
* Only one error callback can be registered. Calling this function
* multiple times will remove the old callback.
*
* @param callback the callback to execute
*/
inline void onError(const ErrorCallback& callback) { return onError(ErrorCallback(callback)); }
void onError(ErrorCallback&& callback);
/**
* Pause deliveries on a channel
*
* This will stop all incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &pause();
/**
* Resume a paused channel
*
* This will resume incoming messages
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &resume();
/**
* Is the channel usable / not yet closed?
* @return bool
*/
bool usable() const
{
return _state == state_connected || _state == state_ready;
}
/**
* Is the channel ready / has it passed the initial handshake?
* @return bool
*/
bool ready() const
{
return _state == state_ready;
}
/**
* Put channel in a confirm mode (RabbitMQ specific)
*/
DeferredConfirm &confirmSelect();
/**
* Start a transaction
*/
Deferred &startTransaction();
/**
* Commit the current transaction
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &commitTransaction();
/**
* Rollback the current transaction
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &rollbackTransaction();
/**
* declare an exchange
*
* @param name name of the exchange to declare
* @param type type of exchange
* @param flags additional settings for the exchange
* @param arguments additional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &declareExchange(const std::string_view &name, ExchangeType type, int flags, const Table &arguments);
/**
* bind two exchanges
* @param source exchange which binds to target
* @param target exchange to bind to
* @param routingKey routing key
* @param arguments additional arguments for binding
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &bindExchange(const std::string_view &source, const std::string_view &target, const std::string_view &routingkey, const Table &arguments);
/**
* unbind two exchanges
* @param source the source exchange
* @param target the target exchange
* @param routingkey the routing key
* @param arguments additional unbind arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &unbindExchange(const std::string_view &source, const std::string_view &target, const std::string_view &routingkey, const Table &arguments);
/**
* remove an exchange
*
* @param name name of the exchange to remove
* @param flags additional settings for deleting the exchange
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &removeExchange(const std::string_view &name, int flags);
/**
* declare a queue
* @param name queue name
* @param flags additional settings for the queue
* @param arguments additional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
DeferredQueue &declareQueue(const std::string_view &name, int flags, const Table &arguments);
/**
* Bind a queue to an exchange
*
* @param exchangeName name of the exchange to bind to
* @param queueName name of the queue
* @param routingkey routingkey
* @param arguments additional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &bindQueue(const std::string_view &exchangeName, const std::string_view &queueName, const std::string_view &routingkey, const Table &arguments);
/**
* Unbind a queue from an exchange
*
* @param exchange the source exchange
* @param queue the target queue
* @param routingkey the routing key
* @param arguments additional bind arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &unbindQueue(const std::string_view &exchangeName, const std::string_view &queueName, const std::string_view &routingkey, const Table &arguments);
/**
* Purge a queue
* @param queue queue to purge
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.purgeQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue purged, all " << messageCount << " messages removed" << std::endl;
*
* });
*/
DeferredDelete &purgeQueue(const std::string_view &name);
/**
* Remove a queue
* @param queue queue to remove
* @param flags additional flags
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, uint32_t messageCount);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, uint32_t messageCount) {
*
* std::cout << "Queue deleted, along with " << messageCount << " messages" << std::endl;
*
* });
*/
DeferredDelete &removeQueue(const std::string_view &name, int flags);
/**
* Publish a message to an exchange
*
* If the mandatory or immediate flag is set, and the message could not immediately
* be published, the message will be returned to the client.
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
* @param flags optional flags
* @return bool
*/
bool publish(const std::string_view &exchange, const std::string_view &routingKey, const Envelope &envelope, int flags);
/**
* Set the Quality of Service (QOS) of the entire connection
* @param prefetchCount maximum number of messages to prefetch
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* @param count number of messages to pre-fetch
* @param global share count between all consumers on the same channel
*/
Deferred &setQos(uint16_t prefetchCount, bool global = false);
/**
* Tell the RabbitMQ server that we're ready to consume messages
* @param queue the queue from which you want to consume
* @param tag a consumer tag that will be associated with this consume operation
* @param flags additional flags
* @param arguments additional arguments
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(AMQP::Channel *channel, const std::string& tag);
*
* For example: channel.declareQueue("myqueue").onSuccess([](AMQP::Channel *channel, const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
DeferredConsumer& consume(const std::string_view &queue, const std::string_view &tag, int flags, const Table &arguments);
/**
* Tell that you are prepared to recall/take back messages that could not be
* published. This is only meaningful if you pass the 'immediate' or 'mandatory'
* flag to publish() operations.
*
* THis function returns a deferred handler more or less similar to the object
* return by the consume() method and that can be used to install callbacks that
* handle the recalled messages.
*/
DeferredRecall &recall();
/**
* Cancel a running consumer
* @param tag the consumer tag
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*
* The onSuccess() callback that you can install should have the following signature:
*
* void myCallback(const std::string& tag);
*
* For example: channel.declareQueue("myqueue").onSuccess([](const std::string& tag) {
*
* std::cout << "Started consuming under tag " << tag << std::endl;
*
* });
*/
DeferredCancel &cancel(const std::string_view &tag);
/**
* Retrieve a single message from RabbitMQ
*
* When you call this method, you can get one single message from the queue (or none
* at all if the queue is empty). The deferred object that is returned, should be used
* to install a onEmpty() and onSuccess() callback function that will be called
* when the message is consumed and/or when the message could not be consumed.
*
* The following flags are supported:
*
* - noack if set, consumed messages do not have to be acked, this happens automatically
*
* @param queue name of the queue to consume from
* @param flags optional flags
*
* The object returns a deferred handler. Callbacks can be installed
* using onSuccess(), onEmpty(), onError() and onFinalize() methods.
*
* The onSuccess() callback has the following signature:
*
* void myCallback(const Message &message, uint64_t deliveryTag, bool redelivered);
*
* For example: channel.get("myqueue").onSuccess([](const Message &message, uint64_t deliveryTag, bool redelivered) {
*
* std::cout << "Message fetched" << std::endl;
*
* }).onEmpty([]() {
*
* std::cout << "Queue is empty" << std::endl;
*
* });
*/
DeferredGet &get(const std::string_view &queue, int flags = 0);
/**
* Acknowledge a message
* @param deliveryTag the delivery tag
* @param flags optional flags
* @return bool
*/
bool ack(uint64_t deliveryTag, int flags);
/**
* Reject a message
* @param deliveryTag the delivery tag
* @param flags optional flags
* @return bool
*/
bool reject(uint64_t deliveryTag, int flags);
/**
* Recover messages that were not yet ack'ed
* @param flags optional flags
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &recover(int flags);
/**
* Close the current channel
*
* This function returns a deferred handler. Callbacks can be installed
* using onSuccess(), onError() and onFinalize() methods.
*/
Deferred &close();
/**
* Get the channel we're working on
* @return uint16_t
*/
uint16_t id() const
{
return _id;
}
/**
* Send a frame over the channel
* @param frame frame to send
* @return bool was frame succesfully sent?
*/
bool send(CopiedBuffer &&frame);
/**
* Send a frame over the channel
* @param frame frame to send
* @return bool was frame succesfully sent?
*/
bool send(const Frame &frame);
/**
* Is this channel waiting for an answer before it can send furher instructions
* @return bool
*/
bool waiting() const
{
return _synchronous || !_queue.empty();
}
/**
* The max payload size for frames
* @return uint32_t
*/
uint32_t maxPayload() const;
/**
* Signal the channel that a synchronous operation was completed, and that any
* queued frames can be sent out.
* @return false if an error on the connection level occurred, true if not
*/
bool flush();
/**
* Report to the handler that the channel is opened
*/
void reportReady()
{
// if we are still in connected state we are now ready
if (_state == state_connected) _state = state_ready;
// send out more instructions if there is a queue
flush();
// inform handler
if (_readyCallback) _readyCallback();
}
/**
* Report to the handler that the channel is closed
*
* Returns whether the channel object is still valid
*
* @return bool
*/
bool reportClosed()
{
// change state
_state = state_closed;
// create a monitor, because the callbacks could destruct the current object
Monitor monitor(this);
// and pass on to the reportSuccess() method which will call the
// appropriate deferred object to report the successful operation
bool result = reportSuccess();
// leap out if object no longer exists
if (!monitor.valid()) return result;
// all later deferred objects should report an error, because it
// was not possible to complete the instruction as the channel is
// now closed (but the channel onError does not have to run)
reportError("Channel has been closed", false);
// done
return result;
}
/**
* Report success
*
* Returns whether the channel object is still valid
*
* @param mixed
* @return bool
*/
template <typename... Arguments>
bool reportSuccess(Arguments ...parameters)
{
// skip if there is no oldest callback
if (!_oldestCallback) return true;
// we are going to call callbacks that could destruct the channel
Monitor monitor(this);
// flush the queue, which will send the next operation if the current operation was synchronous
flush();
// the call to flush may have resulted in a call to reportError
if (!monitor.valid()) return false;
// copy the callback (so that it will not be destructed during
// the "reportSuccess" call, if the channel is destructed during the call)
auto cb = _oldestCallback;
// the call to flush might have caused the callback to have been invoked; check once more
if (!cb) return true;
// call the callback
auto next = cb->reportSuccess(std::forward<Arguments>(parameters)...);
// leap out if channel no longer exist
if (!monitor.valid()) return false;
// in case the callback-shared-pointer is still kept in scope (for example because it
// is stored in the list of consumers), we do want to ensure that it no longer maintains
// a chain of queued deferred objects
cb->unchain();
// set the oldest callback
_oldestCallback = next;
// if there was no next callback, the newest callback was just used
if (!next) _newestCallback = nullptr;
// we are still valid
return true;
}
/**
* Report that a consumer was cancelled by the server (for example because the
* queue was removed or the node on which the queue was stored was terminated)
* @param tag the consumer tag
*/
void reportCancelled(const std::string &tag);
/**
* Report an error message on a channel
* @param message the error message
* @param notifyhandler should the channel-wide handler also be called?
*/
void reportError(const char *message, bool notifyhandler = true);
/**
* Install a consumer
* @param consumertag The consumer tag
* @param consumer The consumer object
*/
void install(const std::string &consumertag, const std::shared_ptr<DeferredConsumer> &consumer)
{
// install the consumer handler
_consumers[consumertag] = consumer;
}
/**
* Install the current consumer
* @param receiver The receiver object
*/
void install(const std::shared_ptr<DeferredReceiver> &receiver)
{
// store object as current receiver
_receiver = receiver;
}
/**
* Uninstall a consumer callback
* @param consumertag The consumer tag
*/
void uninstall(const std::string &consumertag)
{
// erase the callback
_consumers.erase(consumertag);
}
/**
* Fetch the receiver for a specific consumer tag
* @param consumertag the consumer tag
* @return the receiver object
*/
DeferredConsumer *consumer(const std::string &consumertag) const;
/**
* Retrieve the current object that is receiving a message
* @return The handler responsible for the current message
*/
DeferredReceiver *receiver() const { return _receiver.get(); }
/**
* Retrieve the recalls-object that handles bounces
* @return The deferred recall object
*/
DeferredRecall *recalls() const { return _recall.get(); }
/**
* Retrieve the deferred confirm that handles publisher confirms
* @return The deferred confirm object
*/
DeferredConfirm *confirm() const { return _confirm.get(); }
/**
* The channel class is its friend, thus can it instantiate this object
*/
friend class Channel;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,47 @@
/**
* Classes.h
*
* List of all declared classes
*
* @copyright 2014 - 2017 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* All classes defined by this library
*/
class Array;
class BasicDeliverFrame;
class BasicGetOKFrame;
class BasicHeaderFrame;
class BasicReturnFrame;
class BasicAckFrame;
class BasicNackFrame;
class BodyFrame;
class Channel;
class Connection;
class ConnectionHandler;
class ConnectionImpl;
class CopiedBuffer;
class Exchange;
class Frame;
class Login;
class Monitor;
class OutBuffer;
class InBuffer;
class Table;
/**
* End of namespace
*/
}

View File

@ -0,0 +1,266 @@
/**
* Class describing a mid-level Amqp connection
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Connection
{
private:
/**
* The actual implementation
* @var ConnectionImpl
*/
ConnectionImpl _implementation;
public:
/**
* Construct an AMQP object based on full login data
*
* The first parameter is a handler object. This handler class is
* an interface that should be implemented by the caller.
*
* @param handler Connection handler
* @param login Login data
* @param vhost Vhost to use
*/
Connection(ConnectionHandler *handler, const Login &login, const std::string &vhost) : _implementation(this, handler, login, vhost) {}
/**
* Construct with default vhost
* @param handler Connection handler
* @param login Login data
*/
Connection(ConnectionHandler *handler, const Login &login) : _implementation(this, handler, login, "/") {}
/**
* Construct an AMQP object with default login data and default vhost
* @param handler Connection handler
*/
Connection(ConnectionHandler *handler, const std::string &vhost) : _implementation(this, handler, Login(), vhost) {}
/**
* Construct an AMQP object with default login data and default vhost
* @param handler Connection handler
*/
Connection(ConnectionHandler *handler) : _implementation(this, handler, Login(), "/") {}
/**
* No copy'ing, we do not support having two identical connection objects
* @param connection
*/
Connection(const Connection &connection) = delete;
/**
* Destructor
*/
virtual ~Connection() {}
/**
* No assignments of other connections
* @param connection
* @return Connection
*/
Connection &operator=(const Connection &connection) = delete;
/**
* Retrieve the login data
* @return Login
*/
const Login &login() const
{
return _implementation.login();
}
/**
* Retrieve the vhost
* @return string
*/
const std::string &vhost() const
{
return _implementation.vhost();
}
/**
* Send a ping/heartbeat to the channel to keep it alive
* @return bool
*/
bool heartbeat()
{
return _implementation.heartbeat();
}
/**
* Parse data that was recevied from RabbitMQ
*
* Every time that data comes in from RabbitMQ, you should call this method to parse
* the incoming data, and let it handle by the AMQP library. This method returns the number
* of bytes that were processed.
*
* If not all bytes could be processed because it only contained a partial frame, you should
* call this same method later on when more data is available. The AMQP library does not do
* any buffering, so it is up to the caller to ensure that the old data is also passed in that
* later call.
*
* @param buffer buffer to decode
* @param size size of the buffer to decode
* @return number of bytes that were processed
*/
uint64_t parse(const char *buffer, size_t size)
{
return _implementation.parse(ByteBuffer(buffer, size));
}
/**
* Parse data that was recevied from RabbitMQ
*
* Every time that data comes in from RabbitMQ, you should call this method to parse
* the incoming data, and let it handle by the AMQP library. This method returns the number
* of bytes that were processed.
*
* If not all bytes could be processed because it only contained a partial frame, you should
* call this same method later on when more data is available. The AMQP library does not do
* any buffering, so it is up to the caller to ensure that the old data is also passed in that
* later call.
*
* This method accepts a buffer object. This is an interface that is defined by the AMQP
* library, that can be implemented by you to allow faster access to a buffer.
*
* @param buffer buffer to decode
* @return number of bytes that were processed
*/
uint64_t parse(const Buffer &buffer)
{
return _implementation.parse(buffer);
}
/**
* Report that the connection was lost in the middle of an operation
*
* The AMQP protocol normally has a nice closing handshake, and a connection
* is elegantly closed via calls to the close() and parse() methods. The parse()
* methods recognizes the close-confirmation and will report this to the handler.
* However, if you notice yourself that the connection is lost in the middle of
* an operation (for example due to a crashing RabbitMQ server), you should
* explicitly tell the connection object about it, so that it can cancel all
* pending operations. For all pending operations the error and finalize callbacks
* will be called. The ConnectionHandler::onError() method will however _not_ be
* called.
*
* @param message the message that has to be passed to all error handlers
* @return bool false if the connection already was failed
*/
bool fail(const char *message)
{
return _implementation.fail(message);
}
/**
* Max frame size
*
* If you allocate memory to receive data that you are going to pass to the parse() method,
* it might be useful to have an insight in the max frame size. The parse() method process
* one frame at a time, so you must at least be able to read in buffers of this specific
* frame size.
*
* @return size_t
*/
uint32_t maxFrame() const
{
return _implementation.maxFrame();
}
/**
* Expected number of bytes for the next parse() call.
*
* This method returns the number of bytes that the next call to parse() at least expects to
* do something meaningful with it.
*
* @return size_t
*/
uint32_t expected() const
{
return _implementation.expected();
}
/**
* Is the connection ready to accept instructions / has passed the login handshake and not closed?
* @return bool
*/
bool ready() const
{
return _implementation.ready();
}
/**
* Is (or was) the connection initialized
* @return bool
*/
bool initialized() const
{
return _implementation.initialized();
}
/**
* Is the connection in a usable state, or is it already closed or
* in the process of being closed?
* @return bool
*/
bool usable() const
{
return _implementation.usable();
}
/**
* Close the connection
* This will close all channels
* @return bool
*/
bool close()
{
return _implementation.close();
}
/**
* Retrieve the number of channels that are active for this connection
* @return std::size_t
*/
std::size_t channels() const
{
return _implementation.channels();
}
/**
* Is the connection busy waiting for an answer from the server? (in the
* meantime you can already send more instructions over it)
* @return bool
*/
bool waiting() const
{
return _implementation.waiting();
}
/**
* Some classes have access to private properties
*/
friend class ChannelImpl;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,234 @@
/**
* ConnectionHandler.h
*
* Interface that should be implemented by the caller of the library and
* that is passed to the AMQP connection. This interface contains all sorts
* of methods that are called when data needs to be sent, or when the
* AMQP connection ends up in a broken state.
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <cstdint>
#include <stddef.h>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class Connection;
/**
* Class definition
*/
class ConnectionHandler
{
public:
/**
* Destructor
*/
virtual ~ConnectionHandler() = default;
/**
* When the connection is being set up, the client and server exchange
* some information. This includes for example their name and version,
* copyright statement and the operating system name. Nothing in this
* exchange of information is very relevant for the actual AMQP protocol,
* but by overriding this method you can read out the information that
* was sent by the server, and you can decide which information you
* want to send back that describe the client. In RabbitMQ's management
* console the client-properties are visible on the "connections" tab,
* which could be helpful in certain scenarios, like debugging.
*
* The read-only "server" parameter contains the information sent by
* the server, while the "client" table may be filled with information
* about your application. The AMQP protocol says that this table should
* at least be filled with data for the "product", "version", "platform",
* "copyright" and "information" keys. However, you do not have to
* override this method, and even when you do, you do not have to ensure
* that these properties are indeed set, because the AMQP-CPP library
* takes care of filling in properties that were not explicitly set.
*
* @param connection The connection about which information is exchanged
* @param server Properties sent by the server
* @param client Properties that are to be sent back
*/
virtual void onProperties(Connection *connection, const Table &server, Table &client)
{
// make sure compilers dont complaint about unused parameters
(void) connection;
(void) server;
(void) client;
}
/**
* Method that is called when the heartbeat frequency is negotiated
* between the server and the client durion connection setup. You
* normally do not have to override this method, because in the default
* implementation the suggested heartbeat is simply rejected by the client.
*
* However, if you want to enable heartbeats you can override this
* method. You should "return interval" if you want to accept the
* heartbeat interval that was suggested by the server, or you can
* return an alternative value if you want a shorter or longer interval.
* Return 0 if you want to disable heartbeats.
*
* If heartbeats are enabled, you yourself are responsible to send
* out a heartbeat every *interval / 2* number of seconds by calling
* the Connection::heartbeat() method.
*
* @param connection The connection that suggested a heartbeat interval
* @param interval The suggested interval from the server
* @return uint16_t The interval to use
*/
virtual uint16_t onNegotiate(Connection *connection, uint16_t interval)
{
// make sure compilers dont complain about unused parameters
(void) connection;
(void) interval;
// default implementation, disable heartbeats
return 0;
}
/**
* Method that is called by AMQP-CPP when data has to be sent over the
* network. You must implement this method and send the data over a
* socket that is connected with RabbitMQ.
*
* Note that the AMQP library does no buffering by itself. This means
* that this method should always send out all data or do the buffering
* itself.
*
* @param connection The connection that created this output
* @param buffer Data to send
* @param size Size of the buffer
*/
virtual void onData(Connection *connection, const char *buffer, size_t size) = 0;
/**
* Method that is called when the AMQP-CPP library received a heartbeat
* frame that was sent by the server to the client.
*
* You do not have to do anything here, the client sends back a heartbeat
* frame automatically, but if you like, you can implement/override this
* method if you want to be notified of such heartbeats
*
* @param connection The connection over which the heartbeat was received
*/
virtual void onHeartbeat(Connection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* When the connection ends up in an error state this method is called.
* This happens when data comes in that does not match the AMQP protocol,
* or when an error message was sent by the server to the client.
*
* After this method is called, the connection no longer is in a valid
* state and can no longer be used.
*
* This method has an empty default implementation, although you are very
* much advised to implement it. When an error occurs, the connection
* is no longer usable, so you probably want to know.
*
* @param connection The connection that entered the error state
* @param message Error message
*/
virtual void onError(Connection *connection, const char *message)
{
// make sure compilers dont complain about unused parameters
(void) connection;
(void) message;
}
/**
* Method that is called when the login attempt succeeded. After this method
* is called, the connection is ready to use, and the RabbitMQ server is
* ready to receive instructions.
*
* According to the AMQP protocol, you must wait for the connection to become
* ready (and this onConnected method to be called) before you can start
* sending instructions to RabbitMQ. However, if you prematurely do send
* instructions, this AMQP-CPP library caches all methods that you call
* before the connection is ready and flushes them the moment the connection
* has been set up, so technically there is no real reason to wait for this
* method to be called before you send the first instructions.
*
* @param connection The connection that can now be used
*/
virtual void onReady(Connection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the AMQP connection was closed.
*
* This is the counter part of a call to Connection::close() and it confirms
* that the connection was _correctly_ closed. Note that this only applies
* to the AMQP connection, the underlying TCP connection is not managed by
* AMQP-CPP and is still active.
*
* @param connection The connection that was closed and that is now unusable
*/
virtual void onClosed(Connection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the AMQP connection was blocked.
*
* This method is called, when the server connection gets blocked for the first
* time due to the broker running low on a resource (memory or disk). For
* example, when a RabbitMQ node detects that it is low on RAM, it sends a
* notification to all connected publishing clients supporting this feature.
* If before the connections are unblocked the node also starts running low on
* disk space, another notification will not be sent.
*
* @param connection The connection that was blocked
* @param reason Why was the connection blocked
*/
virtual void onBlocked(Connection *connection, const char *reason)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the AMQP connection is no longer blocked.
*
* This method is called when all resource alarms have cleared and the
* connection is fully unblocked.
*
* @param connection The connection that is no longer blocked
*/
virtual void onUnblocked(Connection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,496 @@
/**
* Connection implementation
*
* This is the implementation of the connection - a class that can only be
* constructed by the connection class itselves and that has all sorts of
* methods that are only useful inside the library
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "watchable.h"
#include "connectionhandler.h"
#include "channelimpl.h"
#include "copiedbuffer.h"
#include "monitor.h"
#include "login.h"
#include <unordered_map>
#include <memory>
#include <queue>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class Connection;
class Buffer;
class Frame;
/**
* Class definition
*/
class ConnectionImpl : public Watchable
{
protected:
/**
* The parent connection object
* @var Connection
*/
Connection *_parent;
/**
* The connection handler
* @var ConnectionHandler
*/
ConnectionHandler *_handler;
/**
* State of the connection
* The current state is the last frame sent to the server
* @var enum
*/
enum {
state_protocol, // protocol headers are being passed
state_handshake, // busy with the handshake to open the connection
state_connected, // connection is set up and ready for communication
state_closing, // connection is busy closing (we have sent the close frame)
state_closed, // connection is closed
} _state = state_protocol;
/**
* Has the close() method been called? If this is true, we automatically
* send a close-frame after all pending operations are finsihed.
* @var bool
*/
bool _closed = false;
/**
* All channels that are active
* @var std::unordered_map<uint16_t, std::shared_ptr<ChannelImpl>>
*/
std::unordered_map<uint16_t, std::shared_ptr<ChannelImpl>> _channels;
/**
* The last unused channel ID
* @var uint16_t
*/
uint16_t _nextFreeChannel = 1;
/**
* Max number of channels (0 for unlimited)
* @var uint16_t
*/
uint16_t _maxChannels = 0;
/**
* Max frame size
* @var uint32_t
*/
uint32_t _maxFrame = 4096;
/**
* Number of expected bytes that will hold the next incoming frame
* We start with seven because that is the header of a frame
* @var uint32_t
*/
uint32_t _expected = 7;
/**
* The login for the server (login, password)
* @var Login
*/
Login _login;
/**
* Vhost to connect to
* @var string
*/
std::string _vhost;
/**
* Queued messages that should be sent after the connection has been established
* @var queue
*/
std::queue<CopiedBuffer> _queue;
/**
* Helper method to send the close frame
* Return value tells if the connection is still valid
* @return bool
*/
bool sendClose();
/**
* Is any channel waiting for an answer on a synchronous call?
* @return bool
*/
bool waitingChannels() const;
/**
* Is the channel waiting for a response from the peer (server)
* @return bool
*/
bool waiting() const;
/**
* Helper method for the fail() method
* @param monitor
* @param message
* @return bool
*/
bool fail(const Monitor &monitor, const char *message);
private:
/**
* Construct an AMQP object based on full login data
*
* The first parameter is a handler object. This handler class is
* an interface that should be implemented by the caller.
*
* Note that the constructor is private to ensure that nobody can construct
* this class, only the real Connection class via a friend construct
*
* @param parent Parent connection object
* @param handler Connection handler
* @param login Login data
*/
ConnectionImpl(Connection *parent, ConnectionHandler *handler, const Login &login, const std::string &vhost);
public:
/**
* Copy'ing connections is impossible
* @param connection
*/
ConnectionImpl(const ConnectionImpl &connection) = delete;
/**
* Destructor
*/
virtual ~ConnectionImpl();
/**
* No assignments of other connections
* @param connection
* @return ConnectionImpl
*/
ConnectionImpl &operator=(const ConnectionImpl &connection) = delete;
/**
* What is the state of the connection - is the protocol handshake completed?
* @return bool
*/
bool protocolOk() const
{
// must be busy doing the connection handshake, or already connected
return _state == state_handshake || _state == state_connected;
}
/**
* Mark the protocol as being ok
* @param server properties sent by the server
* @param client properties to be send back
*/
void setProtocolOk(const Table &server, Table &client)
{
// if object is destructed
Monitor monitor(this);
// check if user-space wants to set these properties
_handler->onProperties(_parent, server, client);
// leap out if userspace destructed the object
if (!monitor.valid()) return;
// move on to handshake state
if (_state == state_protocol) _state = state_handshake;
}
/**
* Are we fully connected and ready for instructions? This is true after the initial
* protocol and login handshake were completed and the connection is not closed.
* @return bool
*/
bool ready() const
{
// state must be connected
return _state == state_connected;
}
/**
* Is (or was) the connection initialized
* @return bool
*/
bool initialized() const
{
// We are initalized if we have passed the initialized state. So we are in
// a connected, closing, or closed state.
return _state == state_connected || _state == state_closing || _state == state_closed;
}
/**
* Are we closing down?
* @return bool
*/
bool closing() const
{
// state must be connected
return _state == state_closing;
}
/**
* Are we closed?
* @return bool
*/
bool closed() const
{
// state must be connected
return _state == state_closed;
}
/**
* Is the connection in a usable state / not yet closed?
* @return bool
*/
bool usable() const
{
return (_state == state_protocol || _state == state_handshake || _state == state_connected) && !_closed;
}
/**
* Mark the connection as ready
*/
void setReady();
/**
* Retrieve the login data
* @return Login
*/
const Login &login() const
{
return _login;
}
/**
* Retrieve the vhost
* @return string
*/
const std::string &vhost() const
{
return _vhost;
}
/**
* Store the max number of channels and max number of frames
* @param channels max number of channels
* @param size max frame size
*/
void setCapacity(uint16_t channels, uint32_t size)
{
_maxChannels = channels;
_maxFrame = size;
}
/**
* The max frame size
* @return uint32_t
*/
uint32_t maxFrame() const
{
return _maxFrame;
}
/**
* The max payload size for body frames
* @return uint32_t
*/
uint32_t maxPayload() const
{
// 8 bytes for header and end-of-frame byte
return _maxFrame - 8;
}
/**
* The number of bytes that can best be passed to the next call to the parse() method
* @return uint32_t
*/
uint32_t expected() const
{
return _expected;
}
/**
* Add a channel to the connection, and return the channel ID that it
* is allowed to use, or 0 when no more ID's are available
* @param channel
* @return uint16_t
*/
uint16_t add(const std::shared_ptr<ChannelImpl> &channel);
/**
* Remove a channel
* @param channel
*/
void remove(const ChannelImpl *channel);
/**
* Parse the buffer into a recognized frame
*
* Every time that data comes in on the connection, you should call this method to parse
* the incoming data, and let it handle by the AMQP library. This method returns the number
* of bytes that were processed.
*
* If not all bytes could be processed because it only contained a partial frame, you should
* call this same method later on when more data is available. The AMQP library does not do
* any buffering, so it is up to the caller to ensure that the old data is also passed in that
* later call.
*
* @param buffer buffer to decode
* @return number of bytes that were processed
*/
uint64_t parse(const Buffer &buffer);
/**
* Fail all pending - this can be called by user-space when it is recognized that the
* underlying connection is lost. All error-handlers for all operations and open
* channels will be called. This will _not_ call ConnectionHandler::onError() method.
*
* @return bool
*/
bool fail(const char *message);
/**
* Close the connection
* This will also close all channels
* @return bool
*/
bool close();
/**
* Send a frame over the connection
*
* This is an internal method that you normally do not have to call yourself
*
* @param frame the frame to send
* @return bool
*/
bool send(const Frame &frame);
/**
* Send buffered data over the connection
*
* @param buffer the buffer with data to send
*/
bool send(CopiedBuffer &&buffer);
/**
* Get a channel by its identifier
*
* This method only works if you had already created the channel before.
* This is an internal method that you will not need if you cache the channel
* object.
*
* @param number channel identifier
* @return channel the channel object, or nullptr if not yet created
*/
std::shared_ptr<ChannelImpl> channel(int number)
{
auto iter = _channels.find(number);
return iter == _channels.end() ? nullptr : iter->second;
}
/**
* Report an error message
* @param message
*/
void reportError(const char *message);
/**
* Report that the connection is closed
*/
void reportClosed()
{
// change state
_state = state_closed;
// inform the handler
_handler->onClosed(_parent);
}
/**
* Report that the connection is blocked
* @param reason
*/
void reportBlocked(const char *reason)
{
// inform the handler
_handler->onBlocked(_parent, reason);
}
/**
* Report that the connection is unblocked
*/
void reportUnblocked()
{
// inform the handler
_handler->onUnblocked(_parent);
}
/**
* Retrieve the amount of channels this connection has
* @return std::size_t
*/
std::size_t channels() const
{
return _channels.size();
}
/**
* Set the heartbeat timeout
* @param heartbeat suggested heartbeat timeout by server
* @return uint16_t accepted heartbeat timeout from client
*/
uint16_t setHeartbeat(uint16_t heartbeat)
{
// pass to the handler
return _handler->onNegotiate(_parent, heartbeat);
}
/**
* Report a heartbeat to the connection handler
*/
void reportHeartbeat()
{
// pass to handler
_handler->onHeartbeat(_parent);
}
/**
* Send a heartbeat to keep the connection alive
* @return bool
*/
bool heartbeat();
/**
* The actual connection is a friend and can construct this class
*/
friend class Connection;
friend class ChannelImpl;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,156 @@
/**
* CopiedBuffer.h
*
* If an output buffer (frame) cannot immediately be sent, we copy it to
* memory using this CopiedBuffer class
*
* @copyright 2017 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
#include <cstring>
#include "endian.h"
#include "frame.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class CopiedBuffer : public OutBuffer
{
private:
/**
* The total capacity of the out buffer
* @var size_t
*/
size_t _capacity;
/**
* Pointer to the beginning of the buffer
* @var const char *
*/
char *_buffer;
/**
* Current size of the buffer
* @var size_t
*/
size_t _size = 0;
/**
* Whether the frame is synchronous
* @var bool
*/
bool _synchronous = false;
protected:
/**
* The method that adds the actual data
* @param data
* @param size
*/
virtual void append(const void *data, size_t size) override
{
// copy into the buffer
memcpy(_buffer + _size, data, size);
// update the size
_size += size;
}
public:
/**
* Constructor
* @param frame
*/
CopiedBuffer(const Frame &frame) :
_capacity(frame.totalSize()),
_buffer((char *)malloc(_capacity)),
_synchronous(frame.synchronous())
{
// tell the frame to fill this buffer
frame.fill(*this);
// append an end of frame byte (but not when still negotiating the protocol)
if (frame.needsSeparator()) add((uint8_t)206);
}
/**
* Disabled copy constructor to prevent expensive copy operations
* @param that
*/
CopiedBuffer(const CopiedBuffer &that) = delete;
/**
* Move constructor
* @param that
*/
CopiedBuffer(CopiedBuffer &&that) :
_capacity(that._capacity),
_buffer(that._buffer),
_size(that._size),
_synchronous(that._synchronous)
{
// reset the other object
that._buffer = nullptr;
that._size = 0;
that._capacity = 0;
}
/**
* Destructor
*/
virtual ~CopiedBuffer()
{
// deallocate the buffer
free(_buffer);
}
/**
* Get access to the internal buffer
* @return const char*
*/
const char *data() const
{
// expose member
return _buffer;
}
/**
* Current size of the output buffer
* @return size_t
*/
size_t size() const
{
// expose member
return _size;
}
/**
* Whether the frame is to be sent synchronously
* @return bool
*/
bool synchronous() const noexcept
{
// expose member
return _synchronous;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,242 @@
/**
* Decimal field type for AMQP
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <cmath>
#include <ostream>
#include "field.h"
#include "outbuffer.h"
#include "inbuffer.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class implementation
*/
class DecimalField : public Field
{
/**
* To preserve precision the decision is made to work with the places and number.
* These values are sent in the framedata, so no precision will be lost.
* Other options, such as floats, doubles, Decimal32 etc result in loss of precision
* and this is something which is not acceptable.
*
* Only (in)equality and assignment operators are implemented since the decimalfield
* is not supposed to be altered.
* e.q. ==, != and =
*
* When requesting the value of this object there are 3 choices;
* float, double or DecimalField
* e.g. valueFloat(), valueDouble() and value()
*/
private:
/**
* The number of places, which means the number of decimals
* e.g. number = 1234, places = 2, true value is 12.34
*/
uint8_t _places;
/**
* The number without the decimals
*/
uint32_t _number;
protected:
/**
* Write encoded payload to the given buffer.
*/
virtual void fill(OutBuffer& buffer) const override
{
// encode fields
buffer.add(_places);
buffer.add(_number);
}
public:
/**
* Construct decimal field
*
* @param places the number of places
* @param number the integer number
*/
DecimalField(uint8_t places = 0, uint32_t number = 0) :
_places(places),
_number(number)
{}
/**
* Construct based on incoming data
* @param frame
*/
DecimalField(InBuffer &frame)
{
_places = frame.nextUint8();
_number = frame.nextUint32();
}
/**
* Destructor
*/
virtual ~DecimalField() {}
/**
* Create a new identical instance of this object
* @return unique_ptr
*/
virtual std::unique_ptr<Field> clone() const override
{
return std::unique_ptr<Field>(new DecimalField(_places, _number));
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// output floating point value
stream << "decimal(" << _number / pow(10.0f, _places) << ")";
}
/**
* Assign a new value
*
* @param value new value for field
* @return DecimalField
*/
DecimalField& operator=(const DecimalField& value)
{
// if it's the same object, skip assignment and just return *this
if (this == &value) return *this;
// not the same object, copy values to this object.
_places = value._places;
_number = value._number;
// allow chaining
return *this;
}
/**
* Casts decimalfield to double
* e.g. "double x = decimalfield" will work
*
* @return double value of decimalfield in double format
*/
virtual operator double() const override
{
return _number / pow(10.0f, _places);
}
/**
* Casts decimalfield to float
* e.g. "float x = decimalfield" will work
*
* @return float value of decimalfield in float format
*/
virtual operator float() const override
{
return static_cast<float>(_number / pow(10.0f, _places));
}
/**
* Check for equality between this and another DecimalField
*
* @param value value to be checked for equality
* @return boolean whether values are equal
*/
bool operator==(const DecimalField& value) const
{
// check if everything is the same
// precision is taken into account, e.q. 1.0 != 1.00
// meaning number:10, places:1 is not equal to number:100, places:2
return _number == value.number() && _places == value.places();
}
/**
* Check for inequality between this and another DecimalField
*
* @param value value to be checked for inequality
* @return boolean whether values are inequal
*/
bool operator!=(const DecimalField& value) const
{
return !(*this == value);
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
*/
virtual size_t size() const override
{
// the sum of all fields
return 5;
}
/**
* Get the number of places
* @return uint8_t
*/
uint8_t places() const
{
return _places;
}
/**
* Get the number without decimals
* @return uint32_t
*/
uint32_t number() const
{
return _number;
}
/**
* Return the DecimalField
* To preserve precision DecimalField is returned, containing the number and places.
* @return return DecimalField
*/
DecimalField value() const
{
return *this;
}
/**
* We are a decimal field
*
* @return true, because we are a decimal field
*/
bool isDecimal() const override
{
return true;
}
/**
* Get the type ID that is used to identify this type of
* field in a field table
*/
virtual char typeID() const override
{
return 'D';
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,311 @@
/**
* Deferred.h
*
* Class describing a set of actions that could
* possibly happen in the future that can be
* caught.
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
#include <stdint.h>
#include "callbacks.h"
/**
* Set up namespace
*/
namespace AMQP {
// forward declaration
class ChannelImpl;
/**
* Class definition
*/
class Deferred
{
private:
/**
* Callback to execute either way
* @var FinalizeCallback
*/
FinalizeCallback _finalizeCallback;
protected:
/**
* Callback to execute on success
* @var SuccessCallback
*/
SuccessCallback _successCallback;
/**
* Callback to execute on failure
* @var ErrorCallback
*/
ErrorCallback _errorCallback;
/**
* Pointer to the next deferred object
* @var Deferred
*/
std::shared_ptr<Deferred> _next;
/**
* Do we already know we failed?
* @var bool
*/
bool _failed;
/**
* The next deferred object
* @return Deferred
*/
const std::shared_ptr<Deferred> &next() const
{
return _next;
}
/**
* Indicate success
* @return Deferred Next deferred result
*/
virtual const std::shared_ptr<Deferred> &reportSuccess() const
{
// execute callbacks if registered
if (_successCallback) _successCallback();
// return the next deferred result
return _next;
}
/**
* Report success for queue declared messages
* @param name Name of the new queue
* @param messagecount Number of messages in the queue
* @param consumercount Number of consumers linked to the queue
* @return Deferred Next deferred result
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(const std::string &name, uint32_t messagecount, uint32_t consumercount) const
{
// make sure compilers dont complain about unused parameters
(void) name;
(void) messagecount;
(void) consumercount;
// this is the same as a regular success message
return reportSuccess();
}
/**
* Report success for frames that report delete operations
* @param messagecount Number of messages that were deleted
* @return Deferred
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(uint32_t messagecount) const
{
// make sure compilers dont complain about unused parameters
(void) messagecount;
// this is the same as a regular success message
return reportSuccess();
}
/**
* Report success for a get operation
*
* @param messagecount Number of messages left in the queue
* @param deliveryTag Delivery tag of the message coming in
* @param redelivered Was the message redelivered?
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(uint32_t messagecount, uint64_t deliveryTag, bool redelivered)
{
// make sure compilers dont complain about unused parameters
(void) messagecount;
(void) deliveryTag;
(void) redelivered;
// this is the same as a regular success message
return reportSuccess();
}
/**
* Report success for frames that report cancel operations
* @param name Consumer tag that is cancelled
* @return Deferred
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(const std::string &name)
{
// make sure compilers dont complain about unused parameters
(void) name;
// this is the same as a regular success message
return reportSuccess();
}
/**
* Indicate failure
* @param error Description of the error that occured
* @return Deferred Next deferred result
*/
const std::shared_ptr<Deferred> &reportError(const char *error)
{
// from this moment on the object should be listed as failed
_failed = true;
// execute callbacks if registered
if (_errorCallback) _errorCallback(error);
// return the next deferred result
return _next;
}
/**
* Add a pointer to the next deferred result
* @param deferred
*/
void add(const std::shared_ptr<Deferred> &deferred)
{
// store pointer
_next = deferred;
}
/**
* Remove this object from the chain of deferreds
*/
void unchain()
{
// we no longer need the next pointer
_next = nullptr;
}
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class Tagger;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param failed are we already failed?
*/
Deferred(bool failed = false) : _failed(failed) {}
public:
/**
* Deleted copy and move constructors
*/
Deferred(const Deferred &that) = delete;
Deferred(Deferred &&that) = delete;
/**
* Destructor
*/
virtual ~Deferred()
{
// report to the finalize callback
if (_finalizeCallback) _finalizeCallback();
}
/**
* Cast to a boolean
*/
operator bool () const
{
return !_failed;
}
/**
* Register a function to be called
* if and when the operation succesfully
* completes.
*
* Only one callback can be registered at a time.
* Successive calls to this function will clear
* callbacks registered before.
*
* @param callback the callback to execute
*/
inline Deferred &onSuccess(const SuccessCallback& callback) { return onSuccess(SuccessCallback(callback)); }
Deferred &onSuccess(SuccessCallback&& callback)
{
// store callback
_successCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called
* if and when the operation fails.
*
* Only one callback can be registered at a time.
* Successive calls to this function will clear
* callbacks registered before.
*
* @param callback the callback to execute
*/
inline Deferred &onError(const ErrorCallback& callback) { return onError(ErrorCallback(callback)); }
Deferred &onError(ErrorCallback&& callback)
{
// store callback
_errorCallback = std::move(callback);
// if the object is already in a failed state, we call the callback right away
if (_failed) _errorCallback("Frame could not be sent");
// allow chaining
return *this;
}
/**
* Register a function to be called
* if and when the operation completes
* or fails. This function will be called
* either way.
*
* In the case of success, the provided
* error parameter will be an empty string.
*
* Only one callback can be registered at at time.
* Successive calls to this function will clear
* callbacks registered before.
*
* @param callback the callback to execute
*/
inline Deferred &onFinalize(const FinalizeCallback& callback) { return onFinalize(FinalizeCallback(callback)); }
Deferred &onFinalize(FinalizeCallback&& callback)
{
// if the object is already in a failed state, we call the callback right away
if (_failed) callback();
// otherwise we store callback until it's time for the call
else _finalizeCallback = std::move(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,105 @@
/**
* DeferredCancel.h
*
* Deferred callback for instructions that cancel a running consumer. This
* deferred object allows one to register a callback that also gets the
* consumer tag as one of its parameters.
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredCancel : public Deferred
{
private:
/**
* Pointer to the channel
* @var ChannelImpl
*/
ChannelImpl *_channel;
/**
* Callback to execute when the instruction is completed
* @var CancelCallback
*/
CancelCallback _cancelCallback;
/**
* Report success for frames that report cancel operations
* @param name Consumer tag that is cancelled
* @return Deferred
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(const std::string &name) override;
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param channel Pointer to the channel
* @param failed Are we already failed?
*/
DeferredCancel(ChannelImpl *channel, bool failed = false) :
Deferred(failed), _channel(channel) {}
public:
/**
* Register a function to be called when the cancel operation succeeded
*
* Only one callback can be registered. Successive calls
* to this function will clear callbacks registered before.
*
* @param callback the callback to execute
*/
inline DeferredCancel &onSuccess(const CancelCallback& callback) { return onSuccess(CancelCallback(callback)); }
DeferredCancel &onSuccess(CancelCallback&& callback)
{
// store callback
_cancelCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when the cancel operation succeeded
* @param callback
*/
inline DeferredCancel &onSuccess(const SuccessCallback& callback) { return onSuccess(SuccessCallback(callback)); }
DeferredCancel &onSuccess(SuccessCallback&& callback)
{
// call base
Deferred::onSuccess(std::move(callback));
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,121 @@
/**
* DeferredConfirm.h
*
* Deferred callback for RabbitMQ-specific publisher confirms mechanism.
*
* @author Marcin Gibula <m.gibula@gmail.com>
* @copyright 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredConfirm : public Deferred
{
private:
/**
* Callback to execute when server confirms that message is processed
* @var AckCallback
*/
AckCallback _ackCallback;
/**
* Callback to execute when server sends negative acknowledgement
* @var NackCallback
*/
NackCallback _nackCallback;
/**
* Process an ACK frame
*
* @param frame The frame to process
*/
void process(BasicAckFrame &frame);
/**
* Process an ACK frame
*
* @param frame The frame to process
*/
void process(BasicNackFrame &frame);
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class BasicAckFrame;
friend class BasicNackFrame;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param boolean are we already failed?
*/
DeferredConfirm(bool failed = false) : Deferred(failed) {}
public:
/**
* Register the function that is called when channel is put in publisher
* confirmed mode
* @param callback
*/
inline DeferredConfirm &onSuccess(const SuccessCallback& callback) { return onSuccess(SuccessCallback(callback)); }
DeferredConfirm &onSuccess(SuccessCallback&& callback)
{
// call base
Deferred::onSuccess(std::move(callback));
// allow chaining
return *this;
}
/**
* Callback that is called when the broker confirmed message publication
* @param callback the callback to execute
*/
inline DeferredConfirm &onAck(const AckCallback& callback) { return onAck(AckCallback(callback)); }
DeferredConfirm &onAck(AckCallback&& callback)
{
// store callback
_ackCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Callback that is called when the broker denied message publication
* @param callback the callback to execute
*/
inline DeferredConfirm &onNack(const NackCallback& callback) { return onNack(NackCallback(callback)); }
DeferredConfirm &onNack(NackCallback&& callback)
{
// store callback
_nackCallback = std::move(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,323 @@
/**
* DeferredConsumer.h
*
* Deferred callback for consumers
*
* @copyright 2014 - 2022 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "deferredextreceiver.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declararions
*/
class BasicDeliverFrame;
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredConsumer : public DeferredExtReceiver, public std::enable_shared_from_this<DeferredConsumer>
{
private:
/**
* Callback to execute when consumption has started
* @var ConsumeCallback
*/
ConsumeCallback _consumeCallback;
/**
* Callback to excute when the server has cancelled the consumer
* @var CancelCallback
*/
CancelCallback _cancelCallback;
/**
* Process a delivery frame
*
* @param frame The frame to process
*/
void process(BasicDeliverFrame &frame);
/**
* Report success for frames that report start consumer operations
* @param name Consumer tag that is started
* @return Deferred
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(const std::string &name) override;
/**
* Report that the server has cancelled this consumer
* @param namae The consumer tag
*/
void reportCancelled(const std::string &name)
{
// report
if (_cancelCallback) _cancelCallback(name);
}
/**
* Get reference to self to prevent that object falls out of scope
* @return std::shared_ptr
*/
virtual std::shared_ptr<DeferredReceiver> lock() override { return shared_from_this(); }
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
friend class BasicDeliverFrame;
public:
/**
* Constructor that should only be called from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param channel the channel implementation
* @param failed are we already failed?
*/
DeferredConsumer(ChannelImpl *channel, bool failed = false) :
DeferredExtReceiver(failed, channel) {}
public:
/**
* Register a callback function that gets called when the consumer is
* started. In the callback you will for receive the consumer-tag
* that you need to later stop the consumer
* @param callback
*/
inline DeferredConsumer &onSuccess(const ConsumeCallback& callback) { return onSuccess(ConsumeCallback(callback)); }
DeferredConsumer &onSuccess(ConsumeCallback&& callback)
{
// store the callback
_consumeCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when the consumer starts.
* It is recommended to use the onSuccess() method mentioned above
* since that will also pass the consumer-tag as parameter.
* @param callback
*/
inline DeferredConsumer &onSuccess(const SuccessCallback& callback) { return onSuccess(SuccessCallback(callback)); }
DeferredConsumer &onSuccess(SuccessCallback&& callback)
{
// call base
Deferred::onSuccess(std::move(std::move(callback)));
// allow chaining
return *this;
}
/**
* Register a function to be called when a full message is received
* @param callback the callback to execute
*/
inline DeferredConsumer &onReceived(const MessageCallback& callback) { return onReceived(MessageCallback(callback)); }
DeferredConsumer &onReceived(MessageCallback&& callback)
{
// store callback
_messageCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Alias for onReceived() (see above)
* @param callback the callback to execute
*/
inline DeferredConsumer &onMessage(const MessageCallback& callback) { return onMessage(MessageCallback(callback)); }
DeferredConsumer &onMessage(MessageCallback&& callback)
{
// store callback
_messageCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* RabbitMQ sends a message in multiple frames to its consumers.
* The AMQP-CPP library collects these frames and merges them into a
* single AMQP::Message object that is passed to the callback that
* you can set with the onReceived() or onMessage() methods (see above).
*
* However, you can also write your own algorithm to merge the frames.
* In that case you can install callbacks to handle the frames. Every
* message is sent in a number of frames:
*
* - a begin frame that marks the start of the message
* - an optional header if the message was sent with an envelope
* - zero or more data frames (usually 1, but more for large messages)
* - an end frame to mark the end of the message.
*
* To install handlers for these frames, you can use the onBegin(),
* onHeaders(), onData() and onComplete() methods.
*
* If you just rely on the onReceived() or onMessage() callbacks, you
* do not need any of the methods below this line.
*/
/**
* Register the function that is called when the start frame of a new
* consumed message is received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredConsumer &onBegin(const StartCallback& callback) { return onBegin(StartCallback(callback)); }
DeferredConsumer &onBegin(StartCallback&& callback)
{
// store callback
_startCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when the start frame of a new
* consumed message is received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredConsumer &onStart(const StartCallback& callback) { return onStart(StartCallback(callback)); }
DeferredConsumer &onStart(StartCallback&& callback)
{
// store callback
_startCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function that is called when the message size is known
*
* @param callback The callback to invoke for message headers
* @return Same object for chaining
*/
inline DeferredConsumer &onSize(const SizeCallback& callback) { return onSize(SizeCallback(callback)); }
DeferredConsumer &onSize(SizeCallback&& callback)
{
// store callback
_sizeCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when message headers come in
*
* @param callback The callback to invoke for message headers
* @return Same object for chaining
*/
inline DeferredConsumer &onHeaders(const HeaderCallback& callback) { return onHeaders(HeaderCallback(callback)); }
DeferredConsumer &onHeaders(HeaderCallback&& callback)
{
// store callback
_headerCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function to be called when a chunk of data comes in
*
* Note that this function may be called zero, one or multiple times
* for each incoming message depending on the size of the message data.
*
* If you install this callback you very likely also want to install
* the onComplete callback so you know when the last data part was
* received.
*
* @param callback The callback to invoke for chunks of message data
* @return Same object for chaining
*/
inline DeferredConsumer &onData(const DataCallback& callback) { return onData(DataCallback(callback)); }
DeferredConsumer &onData(DataCallback&& callback)
{
// store callback
_dataCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a funtion to be called when a message was completely received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredConsumer &onComplete(const DeliveredCallback& callback) { return onComplete(DeliveredCallback(callback)); }
DeferredConsumer &onComplete(DeliveredCallback&& callback)
{
// store callback
_deliveredCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a funtion to be called when a message was completely received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredConsumer &onDelivered(const DeliveredCallback& callback) { return onDelivered(DeliveredCallback(callback)); }
DeferredConsumer &onDelivered(DeliveredCallback&& callback)
{
// store callback
_deliveredCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a funtion to be called when the server cancelled the consumer
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredConsumer &onCancelled(const CancelCallback& callback) { return onCancelled(CancelCallback(callback)); }
DeferredConsumer &onCancelled(CancelCallback&& callback)
{
// store callback
_cancelCallback = std::move(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,107 @@
/**
* DeferredDelete.h
*
* Deferred callback for instructions that delete or purge queues, and that
* want to report the number of deleted messages.
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredDelete : public Deferred
{
private:
/**
* Callback to execute when the instruction is completed
* @var DeleteCallback
*/
DeleteCallback _deleteCallback;
/**
* Report success for queue delete and queue purge messages
* @param messagecount Number of messages that were deleted
* @return Deferred Next deferred result
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(uint32_t messagecount) const override
{
// skip if no special callback was installed
if (!_deleteCallback) return Deferred::reportSuccess();
// call the callback
_deleteCallback(messagecount);
// return next object
return _next;
}
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param boolean are we already failed?
*/
DeferredDelete(bool failed = false) : Deferred(failed) {}
public:
/**
* Register a function to be called when the queue is deleted or purged
*
* Only one callback can be registered. Successive calls
* to this function will clear callbacks registered before.
*
* @param callback the callback to execute
*/
inline DeferredDelete &onSuccess(const DeleteCallback& callback) { return onSuccess(DeleteCallback(callback)); }
DeferredDelete &onSuccess(DeleteCallback&& callback)
{
// store callback
_deleteCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when the queue is deleted or purged
* @param callback
*/
inline DeferredDelete &onSuccess(const SuccessCallback& callback) { return onSuccess(SuccessCallback(callback)); }
DeferredDelete &onSuccess(SuccessCallback&& callback)
{
// call base
Deferred::onSuccess(std::move(callback));
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,91 @@
/**
* DeferredExtReceiver.h
*
* Extended receiver that _wants_ to receive message (because it is
* consuming or get'ting messages. This is the base class for both
* the DeferredConsumer as well as the DeferredGet classes, but not
* the base of the DeferredRecall (which can also receive returned
* messages, but not as a result of an explicit request)
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2018 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "deferredreceiver.h"
/**
* Begin of namespace
*/
namespace AMQP {
/**
* Class definition
*/
class DeferredExtReceiver : public DeferredReceiver
{
protected:
/**
* The delivery tag for the current message
* @var uint64_t
*/
uint64_t _deliveryTag = 0;
/**
* Is this a redelivered message
* @var bool
*/
bool _redelivered = false;
/**
* Callback for incoming messages
* @var MessageCallback
*/
MessageCallback _messageCallback;
/**
* Callback for when a message was complete finished
* @var DeliveredCallback
*/
DeliveredCallback _deliveredCallback;
/**
* Initialize the object to send out a message
* @param exchange the exchange to which the message was published
* @param routingkey the routing key that was used to publish the message
*/
virtual void initialize(const std::string &exchange, const std::string &routingkey) override;
/**
* Indicate that a message was done
*/
virtual void complete() override;
/**
* Constructor
* @param failed Have we already failed?
* @param channel The channel we are consuming on
*/
DeferredExtReceiver(bool failed, ChannelImpl *channel) :
DeferredReceiver(failed, channel) {}
public:
/**
* Destructor
*/
virtual ~DeferredExtReceiver() = default;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,304 @@
/**
* DeferredGet.h
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "deferredextreceiver.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*
* This class implements the 'shared_from_this' functionality, because
* it grabs a self-pointer when the callback is running, otherwise the onFinalize()
* is called before the actual message is consumed.
*/
class DeferredGet : public DeferredExtReceiver, public std::enable_shared_from_this<DeferredGet>
{
private:
/**
* Callback in case the queue is empty
* @var EmptyCallback
*/
EmptyCallback _emptyCallback;
/**
* Callback with the number of messages still in the queue
* @var CountCallback
*/
CountCallback _countCallback;
/**
* Report success for a get operation
* @param messagecount Number of messages left in the queue
* @param deliveryTag Delivery tag of the message coming in
* @param redelivered Was the message redelivered?
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(uint32_t messagecount, uint64_t deliveryTag, bool redelivered) override;
/**
* Report success when queue was empty
* @return Deferred
*/
virtual const std::shared_ptr<Deferred> &reportSuccess() const override;
/**
* Get reference to self to prevent that object falls out of scope
* @return std::shared_ptr
*/
virtual std::shared_ptr<DeferredReceiver> lock() override { return shared_from_this(); }
/**
* Extended implementation of the complete method that is called when a message was fully received
*/
virtual void complete() override;
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param channel the channel implementation
* @param failed are we already failed?
*/
DeferredGet(ChannelImpl *channel, bool failed = false) :
DeferredExtReceiver(failed, channel) {}
public:
/**
* Register a function to be called when a message arrives
* This fuction is also available as onReceived() and onMessage() because I always forget which name I gave to it
* @param callback
*/
inline DeferredGet &onSuccess(const MessageCallback& callback) { return onSuccess(MessageCallback(callback)); }
DeferredGet &onSuccess(MessageCallback&& callback)
{
// store the callback
_messageCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called when an error occurs. This should be defined, otherwise the base methods are used.
* @param callback
*/
inline DeferredGet &onError(const ErrorCallback& callback) { return onError(ErrorCallback(callback)); }
DeferredGet &onError(ErrorCallback&& callback)
{
// store the callback
_errorCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called when a message arrives
* This fuction is also available as onSuccess() and onMessage() because I always forget which name I gave to it
* @param callback the callback to execute
*/
inline DeferredGet &onReceived(const MessageCallback& callback) { return onReceived(MessageCallback(callback)); }
DeferredGet &onReceived(MessageCallback&& callback)
{
// store callback
_messageCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called when a message arrives
* This fuction is also available as onSuccess() and onReceived() because I always forget which name I gave to it
* @param callback the callback to execute
*/
inline DeferredGet &onMessage(const MessageCallback& callback) { return onMessage(MessageCallback(callback)); }
DeferredGet &onMessage(MessageCallback&& callback)
{
// store callback
_messageCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called if no message could be fetched
* @param callback the callback to execute
*/
inline DeferredGet &onEmpty(const EmptyCallback& callback) { return onEmpty(EmptyCallback(callback)); }
DeferredGet &onEmpty(EmptyCallback&& callback)
{
// store callback
_emptyCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function to be called when queue size information is known
* @param callback the callback to execute
*/
inline DeferredGet &onCount(const CountCallback& callback) { return onCount(CountCallback(callback)); }
DeferredGet &onCount(CountCallback&& callback)
{
// store callback
_countCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function to be called when a new message is expected
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredGet &onBegin(const StartCallback& callback) { return onBegin(StartCallback(callback)); }
DeferredGet &onBegin(StartCallback&& callback)
{
// store callback
_startCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function to be called when a new message is expected
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredGet &onStart(const StartCallback& callback) { return onStart(StartCallback(callback)); }
DeferredGet &onStart(StartCallback&& callback)
{
// store callback
_startCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function that is called when the message size is known
*
* @param callback The callback to invoke for message headers
* @return Same object for chaining
*/
inline DeferredGet &onSize(const SizeCallback& callback) { return onSize(SizeCallback(callback)); }
DeferredGet &onSize(SizeCallback&& callback)
{
// store callback
_sizeCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function to be called when message headers come in
*
* @param callback The callback to invoke for message headers
* @return Same object for chaining
*/
inline DeferredGet &onHeaders(const HeaderCallback& callback) { return onHeaders(HeaderCallback(callback)); }
DeferredGet &onHeaders(HeaderCallback&& callback)
{
// store callback
_headerCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function to be called when a chunk of data comes in
*
* Note that this function may be called zero, one or multiple times
* for each incoming message depending on the size of the message data.
*
* If you install this callback you very likely also want to install
* the onComplete callback so you know when the last data part was
* received.
*
* @param callback The callback to invoke for chunks of message data
* @return Same object for chaining
*/
inline DeferredGet &onData(const DataCallback& callback) { return onData(DataCallback(callback)); }
DeferredGet &onData(DataCallback&& callback)
{
// store callback
_dataCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a funtion to be called when a message was completely received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredGet &onComplete(const DeliveredCallback& callback) { return onComplete(DeliveredCallback(callback)); }
DeferredGet &onComplete(DeliveredCallback&& callback)
{
// store callback
_deliveredCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a funtion to be called when a message was completely received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredGet &onDelivered(const DeliveredCallback& callback) { return onDelivered(DeliveredCallback(callback)); }
DeferredGet &onDelivered(DeliveredCallback&& callback)
{
// store callback
_deliveredCallback = std::move(callback);
// allow chaining
return *this;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,150 @@
/**
* DeferredPublish.h
*
* Deferred callback for RabbitMQ-specific publisher confirms mechanism per-message.
*
* @author Michael van der Werve <michael.vanderwerve@mailerq.com>
* @copyright 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredPublish : public Deferred
{
private:
/**
* Callback to execute when server confirms that message is processed
* @var AckCallback
*/
PublishAckCallback _ackCallback;
/**
* Callback to execute when server sends negative acknowledgement
* @var NackCallback
*/
PublishNackCallback _nackCallback;
/**
* Callback to execute when message is lost (nack / error)
* @var LostCallback
*/
PublishLostCallback _lostCallback;
/**
* Report an ack, calls the callback.
*/
void reportAck()
{
// check if the callback is set
if (_ackCallback) _ackCallback();
}
/**
* Report an nack, calls the callback if set.
*/
void reportNack()
{
// check if the callback is set
if (_nackCallback) _nackCallback();
// message is 'lost'
if (_lostCallback) _lostCallback();
}
/**
* Indicate failure
* @param error Description of the error that occured
*/
void reportError(const char *error)
{
// from this moment on the object should be listed as failed
_failed = true;
// message is lost
if (_lostCallback) _lostCallback();
// execute callbacks if registered
if (_errorCallback) _errorCallback(error);
}
/**
* The wrapped confirmed channel implementation may call our
* private members and construct us
*/
template <class T>
friend class Reliable;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param boolean are we already failed?
*/
DeferredPublish(bool failed = false) : Deferred(failed) {}
public:
/**
* Callback that is called when the broker confirmed message publication
* @param callback the callback to execute
*/
inline DeferredPublish &onAck(const PublishAckCallback& callback) { return onAck(PublishAckCallback(callback)); }
DeferredPublish &onAck(PublishAckCallback&& callback)
{
// store callback
_ackCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Callback that is called when the broker denied message publication
* @param callback the callback to execute
*/
inline DeferredPublish &onNack(const PublishNackCallback& callback) { return onNack(PublishNackCallback(callback)); }
DeferredPublish &onNack(PublishNackCallback&& callback)
{
// store callback
_nackCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Callback that is called when a message is lost, either through RabbitMQ
* rejecting it or because of a channel error
* @param callback the callback to execute
*/
inline DeferredPublish &onLost(const PublishLostCallback& callback) { return onLost(PublishLostCallback(callback)); }
DeferredPublish &onLost(PublishLostCallback&& callback)
{
// store callback
_lostCallback = std::move(callback);
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,107 @@
/**
* DeferredQueue.h
*
* Deferred callback for "declare-queue" instructions.
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* We extend from the default deferred and add extra functionality
*/
class DeferredQueue : public Deferred
{
private:
/**
* Callback to execute when the queue is declared
* @var QueueCallback
*/
QueueCallback _queueCallback;
/**
* Report success for queue declared messages
* @param name Name of the new queue
* @param messagecount Number of messages in the queue
* @param consumercount Number of consumers linked to the queue
* @return Deferred Next deferred result
*/
virtual const std::shared_ptr<Deferred> &reportSuccess(const std::string &name, uint32_t messagecount, uint32_t consumercount) const override
{
// skip if no special callback was installed
if (!_queueCallback) return Deferred::reportSuccess();
// call the queue callback
_queueCallback(name, messagecount, consumercount);
// return next object
return _next;
}
/**
* The channel implementation may call our
* private members and construct us
*/
friend class ChannelImpl;
friend class ConsumedMessage;
public:
/**
* Protected constructor that can only be called
* from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param bool are we already failed?
*/
DeferredQueue(bool failed = false) : Deferred(failed) {}
public:
/**
* Register a function to be called when the queue is declared
*
* Only one callback can be registered. Successive calls
* to this function will clear callbacks registered before.
*
* @param callback the callback to execute
*/
inline DeferredQueue &onSuccess(const QueueCallback& callback) { return onSuccess(QueueCallback(callback)); }
DeferredQueue &onSuccess(QueueCallback&& callback)
{
// store callback
_queueCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when the queue is declared
* @param callback
*/
inline DeferredQueue &onSuccess(const SuccessCallback& callback) { return onSuccess(SuccessCallback(callback)); }
DeferredQueue &onSuccess(SuccessCallback&& callback)
{
// call base
Deferred::onSuccess(std::move(callback));
// allow chaining
return *this;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,249 @@
/**
* DeferredRecall.h
*
* Class that an be used to install callback methods that define how
* returned messages should be handled.
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2018 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Begin of namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class ChannelImpl;
/**
* Class definition
*/
class DeferredRecall : public DeferredReceiver, public std::enable_shared_from_this<DeferredRecall>
{
private:
/**
* The error code
* @var int16_t
*/
int16_t _code = 0;
/**
* The error message
* @var std::string
*/
std::string _description;
/**
* Callback that is called when a message is returned
* @var BounceCallback
*/
BounceCallback _bounceCallback;
/**
* Begin of a bounced message
* @var ReturnCallback
*/
ReturnCallback _beginCallback;
/**
* End of a bounced message
* @var ReturnedCallback
*/
ReturnedCallback _completeCallback;
/**
* Process a return frame
*
* @param frame The frame to process
*/
void process(BasicReturnFrame &frame);
/**
* Get reference to self to prevent that object falls out of scope
* @return std::shared_ptr
*/
virtual std::shared_ptr<DeferredReceiver> lock() override { return shared_from_this(); }
/**
* Extended implementation of the complete method that is called when a message was fully received
*/
virtual void complete() override;
/**
* Classes that can access private members
*/
friend class BasicReturnFrame;
public:
/**
* Constructor that should only be called from within the channel implementation
*
* Note: this constructor _should_ be protected, but because make_shared
* will then not work, we have decided to make it public after all,
* because the work-around would result in not-so-easy-to-read code.
*
* @param channel the channel implementation
* @param failed are we already failed?
*/
DeferredRecall(ChannelImpl *channel, bool failed = false) :
DeferredReceiver(failed, channel) {}
public:
/**
* Register a function to be called when a full message is returned
* @param callback the callback to execute
*/
inline DeferredRecall &onReceived(const BounceCallback& callback) { return onReceived(BounceCallback(callback)); }
DeferredRecall &onReceived(BounceCallback&& callback)
{
// store callback
_bounceCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Alias for onReceived() (see above)
* @param callback the callback to execute
*/
inline DeferredRecall &onMessage(const BounceCallback& callback) { return onMessage(BounceCallback(callback)); }
DeferredRecall &onMessage(BounceCallback&& callback)
{
// store callback
_bounceCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Alias for onReceived() (see above)
* @param callback the callback to execute
*/
inline DeferredRecall &onReturned(const BounceCallback& callback) { return onReturned(BounceCallback(callback)); }
DeferredRecall &onReturned(BounceCallback&& callback)
{
// store callback
_bounceCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Alias for onReceived() (see above)
* @param callback the callback to execute
*/
inline DeferredRecall &onBounced(const BounceCallback& callback) { return onBounced(BounceCallback(callback)); }
DeferredRecall &onBounced(BounceCallback&& callback)
{
// store callback
_bounceCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when the start frame of a new
* consumed message is received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredRecall &onBegin(const ReturnCallback& callback) { return onBegin(ReturnCallback(callback)); }
DeferredRecall &onBegin(ReturnCallback&& callback)
{
// store callback
_beginCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a function that is called when the message size is known
*
* @param callback The callback to invoke for message headers
* @return Same object for chaining
*/
inline DeferredRecall &onSize(const SizeCallback& callback) { return onSize(SizeCallback(callback)); }
DeferredRecall &onSize(SizeCallback&& callback)
{
// store callback
_sizeCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function that is called when message headers come in
*
* @param callback The callback to invoke for message headers
* @return Same object for chaining
*/
inline DeferredRecall &onHeaders(const HeaderCallback& callback) { return onHeaders(HeaderCallback(callback)); }
DeferredRecall &onHeaders(HeaderCallback&& callback)
{
// store callback
_headerCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register the function to be called when a chunk of data comes in
*
* Note that this function may be called zero, one or multiple times
* for each incoming message depending on the size of the message data.
*
* If you install this callback you very likely also want to install
* the onComplete callback so you know when the last data part was
* received.
*
* @param callback The callback to invoke for chunks of message data
* @return Same object for chaining
*/
inline DeferredRecall &onData(const DataCallback& callback) { return onData(DataCallback(callback)); }
DeferredRecall &onData(DataCallback&& callback)
{
// store callback
_dataCallback = std::move(callback);
// allow chaining
return *this;
}
/**
* Register a funtion to be called when a message was completely received
*
* @param callback The callback to invoke
* @return Same object for chaining
*/
inline DeferredRecall &onComplete(const ReturnedCallback& callback) { return onComplete(ReturnedCallback(callback)); }
DeferredRecall &onComplete(ReturnedCallback&& callback)
{
// store callback
_completeCallback = std::move(callback);
// allow chaining
return *this;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,145 @@
/**
* DeferredReceiver.h
*
* Base class for the deferred consumer, the deferred get and the
* deferred publisher (that may receive returned messages)
*
* @copyright 2016 - 2018 Copernica B.V.
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "deferred.h"
#include "stack_ptr.h"
#include "message.h"
/**
* Start namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class BasicDeliverFrame;
class BasicGetOKFrame;
class BasicHeaderFrame;
class BodyFrame;
/**
* Base class for deferred consumers
*/
class DeferredReceiver : public Deferred
{
private:
/**
* Size of the body of the current message
* @var uint64_t
*/
uint64_t _bodySize = 0;
protected:
/**
* Initialize the object to send out a message
* @param exchange the exchange to which the message was published
* @param routingkey the routing key that was used to publish the message
*/
virtual void initialize(const std::string &exchange, const std::string &routingkey);
/**
* Get reference to self to prevent that object falls out of scope
* @return std::shared_ptr
*/
virtual std::shared_ptr<DeferredReceiver> lock() = 0;
/**
* Indicate that a message was done
*/
virtual void complete() = 0;
private:
/**
* Process the message headers
*
* @param frame The frame to process
*/
void process(BasicHeaderFrame &frame);
/**
* Process the message data
*
* @param frame The frame to process
*/
void process(BodyFrame &frame);
/**
* Frames may be processed
*/
friend class ChannelImpl;
friend class BasicGetOKFrame;
friend class BasicHeaderFrame;
friend class BodyFrame;
protected:
/**
* The channel to which the consumer is linked
* @var ChannelImpl
*/
ChannelImpl *_channel;
/**
* Callback for new message
* @var StartCallback
*/
StartCallback _startCallback;
/**
* Callback that is called when size of the message is known
* @var SizeCallback
*/
SizeCallback _sizeCallback;
/**
* Callback for incoming headers
* @var HeaderCallback
*/
HeaderCallback _headerCallback;
/**
* Callback for when a chunk of data comes in
* @var DataCallback
*/
DataCallback _dataCallback;
/**
* The message that we are currently receiving
* @var stack_ptr<Message>
*/
stack_ptr<Message> _message;
/**
* Constructor
* @param failed Have we already failed?
* @param channel The channel we are consuming on
*/
DeferredReceiver(bool failed, ChannelImpl *channel) :
Deferred(failed), _channel(channel) {}
public:
/**
* Destructor
*/
virtual ~DeferredReceiver() = default;
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,153 @@
/**
* Endian.h
*
* On Apple systems, there are no macro's to convert between little
* and big endian byte orders. This header file adds the missing macros
*
* @author madmongo1 <https://github.com/madmongo1>
*
* And we have also copied code from the "portable_endian.h" file by
* Mathias Panzenböck. His license:
*
* "License": Public Domain
* I, Mathias Panzenböck, place this file hereby into the public
* domain. Use it at your own risk for whatever you like. In case
* there are jurisdictions that don't support putting things in the
* public domain you can also consider it to be "dual licensed"
* under the BSD, MIT and Apache licenses, if you want to. This
* code is trivial anyway. Consider it an example on how to get the
* endian conversion functions on different platforms.
*/
/**
* Include guard
*/
#pragma once
/**
* The contents of the file are only relevant for Apple
*/
#if defined(__APPLE__)
// dependencies
#include <machine/endian.h>
#include <libkern/OSByteOrder.h>
// define 16 bit macros
#define htobe16(x) OSSwapHostToBigInt16(x)
#define htole16(x) OSSwapHostToLittleInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define le16toh(x) OSSwapLittleToHostInt16(x)
// define 32 bit macros
#define htobe32(x) OSSwapHostToBigInt32(x)
#define htole32(x) OSSwapHostToLittleInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#define le32toh(x) OSSwapLittleToHostInt32(x)
// define 64 but macros
#define htobe64(x) OSSwapHostToBigInt64(x)
#define htole64(x) OSSwapHostToLittleInt64(x)
#define be64toh(x) OSSwapBigToHostInt64(x)
#define le64toh(x) OSSwapLittleToHostInt64(x)
/**
* And on Windows systems weird things are going on as well
*/
#elif (defined(_WIN16) || defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)) && !defined(__CYGWIN__)
#include <winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
//# include <sys/param.h>
#if BYTE_ORDER == LITTLE_ENDIAN
#define htobe16(x) htons(x)
#define htole16(x) (x)
#define be16toh(x) ntohs(x)
#define le16toh(x) (x)
#define htobe32(x) htonl(x)
#define htole32(x) (x)
#define be32toh(x) ntohl(x)
#define le32toh(x) (x)
#define htobe64(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
#define htole64(x) (x)
#define be64toh(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
#define le64toh(x) (x)
#elif BYTE_ORDER == BIG_ENDIAN
/* that would be xbox 360 */
#define htobe16(x) (x)
#define htole16(x) __builtin_bswap16(x)
#define be16toh(x) (x)
#define le16toh(x) __builtin_bswap16(x)
#define htobe32(x) (x)
#define htole32(x) __builtin_bswap32(x)
#define be32toh(x) (x)
#define le32toh(x) __builtin_bswap32(x)
#define htobe64(x) (x)
#define htole64(x) __builtin_bswap64(x)
#define be64toh(x) (x)
#define le64toh(x) __builtin_bswap64(x)
#else
#error byte order not supported
#endif
#define __BYTE_ORDER BYTE_ORDER
#define __BIG_ENDIAN BIG_ENDIAN
#define __LITTLE_ENDIAN LITTLE_ENDIAN
#define __PDP_ENDIAN PDP_ENDIAN
/**
* OpenBSD handling
*/
#elif defined(__OpenBSD__)
#include <sys/endian.h>
/**
* NetBSD handling
*/
#elif defined(__NetBSD__) || defined(__DragonFly__)
#include <sys/endian.h>
#define be16toh(x) betoh16(x)
#define le16toh(x) letoh16(x)
#define be32toh(x) betoh32(x)
#define le32toh(x) letoh32(x)
#define be64toh(x) betoh64(x)
#define le64toh(x) letoh64(x)
/**
* FreeBSD handling
*/
#elif defined(__FreeBSD__)
#include <sys/endian.h>
/**
* QNX Neutrino handling
*/
#elif defined(__QNXNTO__)
#include <net/netbyte.h>
/**
* Not on apple, and not on windows
*/
#else
// this is the normal linux way of doing things
#include <endian.h>
// end of "#if defined(__APPLE__)"
#endif

View File

@ -0,0 +1,208 @@
/**
* EntityImpl.h
*
* Common base class for exchanges and queues.
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Namespace
*/
namespace AMQP {
/**
* Class definition
*/
class EntityImpl
{
protected:
/**
* The channel on which we communicate
* @var Channel
*/
Channel *_channel;
/**
* Name of the queue/exchange
* @var string
*/
std::string _name;
/**
* Is this a durable queue/exchange?
* A durable queue/exchange survives a broker restart
* @var bool
*/
bool _durable = false;
/**
* Is this a passive queue/exchange?
* If set, only check if the queue/exchange exists without actually creating it
* @var bool
*/
bool _passive = false;
/**
* Is this an auto-delete queue/exchange?
* If set, the entity is removed when it is no longer used (for queues
* when all consumers are gone, for exchanges when all queues are gone)
* @var bool
*/
bool _autoDelete = false;
/**
* Additional arguments
* @var Table
*/
Table _arguments;
/**
* Constructor is protected and can only be accessed by derived classes
* @param channel
*/
EntityImpl(Channel *channel) : _channel(channel) {}
public:
/**
* Destructor
*/
virtual ~EntityImpl();
/**
* Retrieve the name
* @return string
*/
std::string &name()
{
return _name;
}
/**
* Change the name
* You must declare the entity before this has effect
* @param string
*/
void setName(const std::string &name)
{
_name = name;
}
/**
* Is the queue or exchange durable
* The entity survives a broker restart if it is durable
* @return bool
*/
bool durable()
{
return _durable;
}
/**
* Mark the object as durable
* @param bool
*/
void setDurable(bool durable)
{
_durable = durable;
}
/**
* Is the passive bit set
* If set, the declare method only checks if the queue/exchange exists without actually creating it
* @return bool
*/
bool passive()
{
return _passive;
}
/**
* Change the passive bit
* @param bool
*/
void setPassive(bool passive)
{
_passive = passive;
}
/**
* Is the auto-delete property set?
* The entity is removed when the consumers and/or queues are unlinked from it
* @return bool
*/
bool autoDelete()
{
return _autoDelete;
}
/**
* Update the auto-delete bit
* @param bool
*/
void setAutoDelete(bool autoDelete)
{
_autoDelete = autoDelete;
}
/**
* Set a custom argument
* @param name Name of the argument
* @param value Value of the argument
*/
void setArgument(const std::string &name, const std::string &value)
{
_arguments[name] = value;
}
/**
* Retrieve a argument
* @param name Name of the argument
* @return The value of the argument
*/
std::string &argument(const std::string &name)
{
return _arguments[name];
}
/**
* Bind to a different exchange
* @param exchange The exchange to bind to
* @param key The routing key
* @return bool
*/
virtual bool bind(const std::string &exchange, const std::string &key) = 0;
/**
* Unbind from an exchange
* @param exchange Exchange to unbind from
* @param key The routing key
* @return bool
*/
virtual bool unbind(const std::string &exchange, const std::string &key) = 0;
/**
* Declare the queue/exchange
* @return bool
*/
virtual bool declare() = 0;
/**
* Remove the queue/exchange
* @return bool
*/
virtual bool remove() = 0;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,145 @@
/**
* Envelope.h
*
* When you send or receive a message to the rabbitMQ server, it is encapsulated
* in an envelope that contains additional meta information as well.
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "metadata.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
* The envelope extends from MetaData, although this is conceptually not entirely
* correct: and envelope _holds_ meta data and a body, so it would have been more
* correct to make the MetaData instance a member. But by extending we automatically
* make all meta-data properties accesible.
*/
class Envelope : public MetaData
{
protected:
/**
* Pointer to the body data (the memory is not managed by the AMQP library!)
* @var const char *
*/
const char *_body;
/**
* Size of the data
* @var uint64_t
*/
uint64_t _bodySize;
public:
/**
* Constructor
* The data buffer that you pass to this constructor must be valid during
* the lifetime of the Envelope object.
* @param body
* @param size
*/
Envelope(const char *body, uint64_t size) : MetaData(), _body(body), _bodySize(size) {}
Envelope(const std::string_view &body) : Envelope(body.data(), body.size()) {}
Envelope(const char *body) : Envelope(body, strlen(body)) {}
/**
* Constructor that preserves meta-data, but installs different body
* @param metadata
* @param body
* @param size
*/
Envelope(const MetaData &metadata, const char *body, uint64_t size) : MetaData(metadata), _body(body), _bodySize(size) {}
Envelope(const MetaData &metadata, const std::string_view &body) : Envelope(metadata, body.data(), body.size()) {}
Envelope(const MetaData &metadata, const char *body) : Envelope(metadata, body, strlen(body)) {}
/**
* Read envelope frmo an input-buffer
* This method is the counterpart of the Envelope::fill() method, and is not used
* by the library itself, but might be useful for applications that want to store
* envelopes.
* @param frame
*/
Envelope(InBuffer &buffer) : MetaData(buffer)
{
// extract the properties
_bodySize = buffer.nextUint64();
_body = buffer.nextData(_bodySize);
}
/**
* Disabled copy constructor
* @param envelope the envelope to copy
*/
Envelope(const Envelope &envelope) = delete;
/**
* Destructor
*/
virtual ~Envelope() {}
/**
* Access to the full message data
* @return buffer
*/
const char *body() const
{
return _body;
}
/**
* Size of the body
* @return uint64_t
*/
uint64_t bodySize() const
{
return _bodySize;
}
/**
* Size of the envelope, this is the size of the meta+data plus the number of bytes
* required to store the size of the body + the actual body. This method is not used
* by the AMQP-CPP library, but could be useful if you feel the need to store
* @return size_t
*/
size_t size() const
{
// this is the size of the meta-data + the size of the body
return MetaData::size() + _bodySize + sizeof(uint64_t);
}
/**
* Fill an output buffer
* This method is not used by this library, but could be useful if you want to store
* the meta-data + message contents (
* @param buffer
*/
void fill(OutBuffer &buffer) const
{
// first we store the meta-data
MetaData::fill(buffer);
// now the size of the message body + the actual body
buffer.add(_bodySize);
buffer.add(_body, _bodySize);
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,40 @@
/**
* Exception.h
*
* Base class for all AMQP exceptions
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <stdexcept>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Base exception class
*/
class Exception : public std::runtime_error
{
protected:
/**
* Constructor
* @param what
*/
explicit Exception(const std::string &what) : runtime_error(what) {}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,36 @@
/**
* ExchangeType.h
*
* The various exchange types that are supported
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* The class
*/
enum ExchangeType
{
fanout,
direct,
topic,
headers,
consistent_hash,
message_deduplication
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,134 @@
/**
* Available field types for AMQP
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class InBuffer;
class OutBuffer;
class Array;
class Table;
/**
* Base field class
*
* This class cannot be constructed, but serves
* as the base class for all AMQP field types
*/
class Field
{
protected:
/**
* Decode a field by fetching a type and full field from a frame
* The returned field is allocated on the heap!
* @param frame
* @return std::unique_ptr<Field>
*/
static std::unique_ptr<Field> decode(InBuffer &frame);
public:
/**
* Destructor
*/
virtual ~Field() {}
/**
* Create a new instance on the heap of this object, identical to the object passed
* @return Field*
*/
virtual std::unique_ptr<Field> clone() const = 0;
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
virtual size_t size() const = 0;
/**
* Write encoded payload to the given buffer.
* @param buffer
*/
virtual void fill(OutBuffer& buffer) const = 0;
/**
* Get the type ID that is used to identify this type of
* field in a field table
* @return char
*/
virtual char typeID() const = 0;
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const = 0;
/**
* Casting operators
* @return mixed
*/
virtual operator const std::string& () const;
virtual operator const char * () const { return nullptr; }
virtual operator uint8_t () const { return 0; }
virtual operator uint16_t () const { return 0; }
virtual operator uint32_t () const { return 0; }
virtual operator uint64_t () const { return 0; }
virtual operator int8_t () const { return 0; }
virtual operator int16_t () const { return 0; }
virtual operator int32_t () const { return 0; }
virtual operator int64_t () const { return 0; }
virtual operator float () const { return 0; }
virtual operator double () const { return 0; }
virtual operator const Array& () const;
virtual operator const Table& () const;
/**
* Check the field type
*
* @return Is the field a specific type?
*/
virtual bool isInteger() const { return false; }
virtual bool isDecimal() const { return false; }
virtual bool isArray() const { return false; }
virtual bool isTable() const { return false; }
virtual bool isBoolean() const { return false; }
virtual bool isString() const { return false; }
virtual bool isVoid() const { return false; }
};
/**
* Custom output stream operator
* @param stream
* @param field
* @return ostream
*/
inline std::ostream &operator<<(std::ostream &stream, const Field &field)
{
field.output(stream);
return stream;
}
/**
* end namespace
*/
}

View File

@ -0,0 +1,275 @@
/**
* Field proxy. Returned by the table. Can be casted to the
* relevant native type (std::string or numeric)
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <cstdint>
#include <string>
#include "stringfield.h"
#include "booleanset.h"
#include "decimalfield.h"
#include "numericfield.h"
#include "voidfield.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class Table;
class Array;
class Field;
/**
* Class implementation
*/
template <typename T, typename I>
class FieldProxy
{
private:
/**
* The table or array possibly holding the requested field
*/
T *_source;
/**
* The key in the table
*/
I _index;
public:
/**
* Construct the field proxy
*
* @param table the table possibly holding the field
* @oaram index key in table map
*/
FieldProxy(T *source, I index) :
_source(source),
_index(index)
{}
/**
* Assign a boolean value
*
* @param value
*/
FieldProxy& operator=(bool value)
{
// assign value and allow chaining
_source->set(_index, BooleanSet(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(uint8_t value)
{
// assign value and allow chaining
_source->set(_index, UOctet(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(int8_t value)
{
// assign value and allow chaining
_source->set(_index, Octet(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(uint16_t value)
{
// assign value and allow chaining
_source->set(_index, UShort(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(int16_t value)
{
// assign value and allow chaining
_source->set(_index, Short(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(uint32_t value)
{
// assign value and allow chaining
_source->set(_index, ULong(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(int32_t value)
{
// assign value and allow chaining
_source->set(_index, Long(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(uint64_t value)
{
// assign value and allow chaining
_source->set(_index, ULongLong(value));
return *this;
}
/**
* Assign a numeric value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(int64_t value)
{
// assign value and allow chaining
_source->set(_index, LongLong(value));
return *this;
}
/**
* Assign a decimal value
*
* @param value
* @return FieldProxy
*/
FieldProxy& operator=(const DecimalField &value)
{
// assign value and allow chaining
_source->set(_index, DecimalField(value));
return *this;
}
/**
* Assign a string value
*
* @param value
* @return FieldProxy
*/
FieldProxy &operator=(const std::string &value)
{
// in theory we should make a distinction between short and long string,
// but in practive only long strings are accepted
_source->set(_index, LongString(value));
// allow chaining
return *this;
}
/**
* Assign a string value
*
* @param value
* @return FieldProxy
*/
FieldProxy &operator=(const char *value)
{
// cast to a string
return operator=(std::string(value));
}
/**
* Assign an array value
* @param value
* @return FieldProxy
*/
FieldProxy &operator=(const Array &value)
{
// assign value and allow chaining
_source->set(_index, value);
return *this;
}
/**
* Assign a table value
* @param value
* @return FieldProxy
*/
FieldProxy &operator=(const Table &value)
{
// assign value and allow chaining
_source->set(_index, value);
return *this;
}
/**
* Get the underlying field
* @return Field
*/
const Field &get() const
{
return _source->get(_index);
}
/**
* Get a boolean
* @return bool
*/
template <typename TARGET>
operator TARGET () const
{
// retrieve the value
return _source->get(_index);
}
};
// define types for array- and table-based field proxy
using AssociativeFieldProxy = FieldProxy<Table, std::string>;
using ArrayFieldProxy = FieldProxy<Array, uint8_t>;
/**
* end namespace
*/
}

View File

@ -0,0 +1,47 @@
/**
* AmqpFlags.h
*
* The various flags that are supported
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* All bit flags
* @var int
*/
extern const int durable;
extern const int autodelete;
extern const int active;
extern const int passive;
extern const int ifunused;
extern const int ifempty;
extern const int global;
extern const int nolocal;
extern const int noack;
extern const int exclusive;
extern const int nowait;
extern const int mandatory;
extern const int immediate;
extern const int redelivered;
extern const int multiple;
extern const int requeue;
extern const int readable;
extern const int writable;
extern const int internal;
/**
* End of namespace
*/
}

View File

@ -0,0 +1,108 @@
/**
* Frame.h
*
* Base class for frames. This base class can not be constructed from outside
* the library, and is only used internally.
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "protocolexception.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class ConnectionImpl;
/**
* Class definition
*/
class Frame
{
protected:
/**
* Protected constructor to ensure that no objects are created from
* outside the library
*/
Frame() {}
public:
/**
* Destructor
*/
virtual ~Frame() {}
/**
* return the total size of the frame
* @return uint32_t
*/
virtual uint32_t totalSize() const = 0;
/**
* Fill an output buffer
* @param buffer
*/
virtual void fill(OutBuffer &buffer) const = 0;
/**
* Is this a frame that is part of the connection setup?
* @return bool
*/
virtual bool partOfHandshake() const { return false; }
/**
* Is this a frame that is part of the connection close operation?
* @return bool
*/
virtual bool partOfShutdown() const { return false; }
/**
* Does this frame need an end-of-frame seperator?
* @return bool
*/
virtual bool needsSeparator() const { return true; }
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const { return false; }
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
// this is an exception
throw ProtocolException("unimplemented frame");
// unreachable
return false;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,143 @@
/**
* InBuffer.h
*
* The InBuffer class is a wrapper around a data buffer and that adds
* some safety checks so that the rest of the library can safely read
* from it.
*
* This is a class that is used internally by the AMQP library. As a user
* of this library, you normally do not have to instantiate it. However,
* if you do want to store or safe messages yourself, it sometimes can
* be useful to implement it.
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <cstdint>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class Buffer;
/**
* Class definition
*/
class InBuffer
{
protected:
/**
* The buffer we are reading from
* @var Buffer
*/
const Buffer &_buffer;
/**
* Number of bytes already processed
* @var size_t
*/
size_t _skip = 0;
public:
/**
* Constructor
* @param buffer Binary buffer
*/
InBuffer(const Buffer &buffer) : _buffer(buffer) {}
/**
* Destructor
*/
virtual ~InBuffer() {}
/**
* Read the next uint8_t from the buffer
* @return uint8_t value read
*/
uint8_t nextUint8();
/**
* Read the next int8_t from the buffer
* @return int8_t value read
*/
int8_t nextInt8();
/**
* Read the next uint16_t from the buffer
* @return uint16_t value read
*/
uint16_t nextUint16();
/**
* Read the next int16_t from the buffer
* @return int16_t value read
*/
int16_t nextInt16();
/**
* Read the next uint32_t from the buffer
* @return uint32_t value read
*/
uint32_t nextUint32();
/**
* Read the next int32_t from the buffer
* @return int32_t value read
*/
int32_t nextInt32();
/**
* Read the next uint64_t from the buffer
* @return uint64_t value read
*/
uint64_t nextUint64();
/**
* Read the next int64_t from the buffer
* @return int64_t value read
*/
int64_t nextInt64();
/**
* Read a float from the buffer
* @return float float read from buffer.
*/
float nextFloat();
/**
* Read a double from the buffer
* @return double double read from buffer
*/
double nextDouble();
/**
* Get a pointer to the next binary buffer of a certain size
* @param size
* @return char*
*/
const char *nextData(size_t size);
/**
* The checker may access private data
*/
friend class BufferCheck;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,539 @@
/**
* LibBoostAsio.h
*
* Implementation for the AMQP::TcpHandler for boost::asio. You can use this class
* instead of a AMQP::TcpHandler class, just pass the boost asio service to the
* constructor and you're all set. See tests/libboostasio.cpp for example.
*
* Watch out: this class was not implemented or reviewed by the original author of
* AMQP-CPP. However, we do get a lot of questions and issues from users of this class,
* so we cannot guarantee its quality. If you run into such issues too, it might be
* better to implement your own handler that interact with boost.
*
*
* @author Gavin Smith <gavin.smith@coralbay.tv>
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/bind/bind.hpp>
#include <boost/function.hpp>
#include "amqpcpp/linux_tcp.h"
// C++17 has 'weak_from_this()' support.
#if __cplusplus >= 201701L
#define PTR_FROM_THIS(T) weak_from_this()
#else
#define PTR_FROM_THIS(T) std::weak_ptr<T>(shared_from_this())
#endif
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
* @note Because of a limitation on Windows, this will only work on POSIX based systems - see https://github.com/chriskohlhoff/asio/issues/70
*/
class LibBoostAsioHandler : public virtual TcpHandler
{
protected:
/**
* Helper class that wraps a boost io_context socket monitor.
*/
class Watcher : public virtual std::enable_shared_from_this<Watcher>
{
private:
/**
* The boost asio io_context which is responsible for detecting events.
* @var class boost::asio::io_context&
*/
boost::asio::io_context & _iocontext;
using strand_weak_ptr = std::weak_ptr<boost::asio::io_context::strand>;
/**
* The boost asio io_context::strand managed pointer.
* @var class std::shared_ptr<boost::asio::io_context>
*/
strand_weak_ptr _wpstrand;
/**
* The boost tcp socket.
* @var class boost::asio::ip::tcp::socket
* @note https://stackoverflow.com/questions/38906711/destroying-boost-asio-socket-without-closing-native-handler
*/
boost::asio::posix::stream_descriptor _socket;
/**
* The boost asynchronous deadline timer.
* @var class boost::asio::deadline_timer
*/
boost::asio::deadline_timer _timer;
/**
* A boolean that indicates if the watcher is monitoring for read events.
* @var _read True if reads are being monitored else false.
*/
bool _read{false};
/**
* A boolean that indicates if the watcher has a pending read event.
* @var _read True if read is pending else false.
*/
bool _read_pending{false};
/**
* A boolean that indicates if the watcher is monitoring for write events.
* @var _read True if writes are being monitored else false.
*/
bool _write{false};
/**
* A boolean that indicates if the watcher has a pending write event.
* @var _read True if read is pending else false.
*/
bool _write_pending{false};
using handler_cb = boost::function<void(boost::system::error_code,std::size_t)>;
using io_handler = boost::function<void(const boost::system::error_code&, const std::size_t)>;
using timer_handler = boost::function<void(boost::system::error_code)>;
/**
* Builds a io handler callback that executes the io callback in a strand.
* @param io_handler The handler callback to dispatch
* @return handler_cb A function wrapping the execution of the handler function in a io_context::strand.
*/
handler_cb get_dispatch_wrapper(io_handler fn)
{
const strand_weak_ptr wpstrand = _wpstrand;
return [fn, wpstrand](const boost::system::error_code &ec, const std::size_t bytes_transferred)
{
const strand_shared_ptr strand = wpstrand.lock();
if (!strand)
{
fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled), std::size_t{0});
return;
}
boost::asio::dispatch(strand->context().get_executor(), boost::bind(fn, ec, bytes_transferred));
};
}
/**
* Binds and returns a read handler for the io operation.
* @param connection The connection being watched.
* @param fd The file descripter being watched.
* @return handler callback
*/
handler_cb get_read_handler(TcpConnection *const connection, const int fd)
{
auto fn = boost::bind(&Watcher::read_handler,
this,
boost::placeholders::_1,
boost::placeholders::_2,
PTR_FROM_THIS(Watcher),
connection,
fd);
return get_dispatch_wrapper(fn);
}
/**
* Binds and returns a read handler for the io operation.
* @param connection The connection being watched.
* @param fd The file descripter being watched.
* @return handler callback
*/
handler_cb get_write_handler(TcpConnection *const connection, const int fd)
{
auto fn = boost::bind(&Watcher::write_handler,
this,
boost::placeholders::_1,
boost::placeholders::_2,
PTR_FROM_THIS(Watcher),
connection,
fd);
return get_dispatch_wrapper(fn);
}
/**
* Binds and returns a lamba function handler for the io operation.
* @param connection The connection being watched.
* @param timeout The file descripter being watched.
* @return handler callback
*/
timer_handler get_timer_handler(TcpConnection *const connection, const uint16_t timeout)
{
const auto fn = boost::bind(&Watcher::timeout_handler,
this,
boost::placeholders::_1,
PTR_FROM_THIS(Watcher),
connection,
timeout);
const strand_weak_ptr wpstrand = _wpstrand;
return [fn, wpstrand](const boost::system::error_code &ec)
{
const strand_shared_ptr strand = wpstrand.lock();
if (!strand)
{
fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled));
return;
}
boost::asio::dispatch(strand->context().get_executor(), boost::bind(fn, ec));
};
}
/**
* Handler method that is called by boost's io_context when the socket pumps a read event.
* @param ec The status of the callback.
* @param bytes_transferred The number of bytes transferred.
* @param awpWatcher A weak pointer to this object.
* @param connection The connection being watched.
* @param fd The file descriptor being watched.
* @note The handler will get called if a read is cancelled.
*/
void read_handler(const boost::system::error_code &ec,
const std::size_t bytes_transferred,
const std::weak_ptr<Watcher> awpWatcher,
TcpConnection *const connection,
const int fd)
{
// Resolve any potential problems with dangling pointers
// (remember we are using async).
const std::shared_ptr<Watcher> apWatcher = awpWatcher.lock();
if (!apWatcher) { return; }
_read_pending = false;
if ((!ec || ec == boost::asio::error::would_block) && _read)
{
connection->process(fd, AMQP::readable);
_read_pending = true;
_socket.async_read_some(
boost::asio::null_buffers(),
get_read_handler(connection, fd));
}
}
/**
* Handler method that is called by boost's io_context when the socket pumps a write event.
* @param ec The status of the callback.
* @param bytes_transferred The number of bytes transferred.
* @param awpWatcher A weak pointer to this object.
* @param connection The connection being watched.
* @param fd The file descriptor being watched.
* @note The handler will get called if a write is cancelled.
*/
void write_handler(const boost::system::error_code ec,
const std::size_t bytes_transferred,
const std::weak_ptr<Watcher> awpWatcher,
TcpConnection *const connection,
const int fd)
{
// Resolve any potential problems with dangling pointers
// (remember we are using async).
const std::shared_ptr<Watcher> apWatcher = awpWatcher.lock();
if (!apWatcher) { return; }
_write_pending = false;
if ((!ec || ec == boost::asio::error::would_block) && _write)
{
connection->process(fd, AMQP::writable);
_write_pending = true;
_socket.async_write_some(
boost::asio::null_buffers(),
get_write_handler(connection, fd));
}
}
/**
* Callback method that is called by libev when the timer expires
* @param ec error code returned from loop
* @param loop The loop in which the event was triggered
* @param connection
* @param timeout
*/
void timeout_handler(const boost::system::error_code &ec,
std::weak_ptr<Watcher> awpThis,
TcpConnection *const connection,
const uint16_t timeout)
{
// Resolve any potential problems with dangling pointers
// (remember we are using async).
const std::shared_ptr<Watcher> apTimer = awpThis.lock();
if (!apTimer) { return; }
if (!ec)
{
if (connection)
{
// send the heartbeat
connection->heartbeat();
}
// Reschedule the timer for the future:
_timer.expires_at(_timer.expires_at() + boost::posix_time::seconds(timeout));
// Posts the timer event
_timer.async_wait(get_timer_handler(connection, timeout));
}
}
public:
/**
* Constructor- initialises the watcher and assigns the filedescriptor to
* a boost socket for monitoring.
* @param io_context The boost io_context
* @param wpstrand A weak pointer to a io_context::strand instance.
* @param fd The filedescriptor being watched
*/
Watcher(boost::asio::io_context &io_context,
const strand_weak_ptr wpstrand,
const int fd) :
_iocontext(io_context),
_wpstrand(wpstrand),
_socket(io_context),
_timer(io_context)
{
_socket.assign(fd);
_socket.non_blocking(true);
}
/**
* Watchers cannot be copied or moved
*
* @param that The object to not move or copy
*/
Watcher(Watcher &&that) = delete;
Watcher(const Watcher &that) = delete;
/**
* Destructor
*/
~Watcher()
{
_read = false;
_write = false;
_socket.release();
stop_timer();
}
/**
* Change the events for which the filedescriptor is monitored
* @param events
*/
void events(TcpConnection *connection, int fd, int events)
{
// 1. Handle reads?
_read = ((events & AMQP::readable) != 0);
// Read requsted but no read pending?
if (_read && !_read_pending)
{
_read_pending = true;
_socket.async_read_some(
boost::asio::null_buffers(),
get_read_handler(connection, fd));
}
// 2. Handle writes?
_write = ((events & AMQP::writable) != 0);
// Write requested but no write pending?
if (_write && !_write_pending)
{
_write_pending = true;
_socket.async_write_some(
boost::asio::null_buffers(),
get_write_handler(connection, fd));
}
}
/**
* Change the expire time
* @param connection
* @param timeout
*/
void set_timer(TcpConnection *connection, uint16_t timeout)
{
// stop timer in case it was already set
stop_timer();
// Reschedule the timer for the future:
_timer.expires_from_now(boost::posix_time::seconds(timeout));
// Posts the timer event
_timer.async_wait(get_timer_handler(connection, timeout));
}
/**
* Stop the timer
*/
void stop_timer()
{
// do nothing if it was never set
_timer.cancel();
}
};
/**
* The boost asio io_context.
* @var class boost::asio::io_context&
*/
boost::asio::io_context & _iocontext;
using strand_shared_ptr = std::shared_ptr<boost::asio::io_context::strand>;
/**
* The boost asio io_context::strand managed pointer.
* @var class std::shared_ptr<boost::asio::io_context>
*/
strand_shared_ptr _strand;
/**
* All I/O watchers that are active, indexed by their filedescriptor
* @var std::map<int,Watcher>
*/
std::map<int, std::shared_ptr<Watcher> > _watchers;
/**
* Method that is called by AMQP-CPP to register a filedescriptor for readability or writability
* @param connection The TCP connection object that is reporting
* @param fd The filedescriptor to be monitored
* @param flags Should the object be monitored for readability or writability?
*/
void monitor(TcpConnection *const connection,
const int fd,
const int flags) override
{
// do we already have this filedescriptor
auto iter = _watchers.find(fd);
// was it found?
if (iter == _watchers.end())
{
// we did not yet have this watcher - but that is ok if no filedescriptor was registered
if (flags == 0){ return; }
// construct a new pair (watcher/timer), and put it in the map
const std::shared_ptr<Watcher> apWatcher =
std::make_shared<Watcher>(_iocontext, _strand, fd);
_watchers[fd] = apWatcher;
// explicitly set the events to monitor
apWatcher->events(connection, fd, flags);
}
else if (flags == 0)
{
// the watcher does already exist, but we no longer have to watch this watcher
_watchers.erase(iter);
}
else
{
// Change the events on which to act.
iter->second->events(connection,fd,flags);
}
}
protected:
/**
* Method that is called when the heartbeat frequency is negotiated between the server and the client.
* @param connection The connection that suggested a heartbeat interval
* @param interval The suggested interval from the server
* @return uint16_t The interval to use
*/
virtual uint16_t onNegotiate(TcpConnection *connection, uint16_t interval) override
{
// skip if no heartbeats are needed
if (interval == 0) return 0;
const auto fd = connection->fileno();
auto iter = _watchers.find(fd);
if (iter == _watchers.end()) return 0;
// set the timer
iter->second->set_timer(connection, interval);
// we agree with the interval
return interval;
}
public:
/**
* Handler cannot be default constructed.
*
* @param that The object to not move or copy
*/
LibBoostAsioHandler() = delete;
/**
* Constructor
* @param io_context The boost io_context to wrap
*/
explicit LibBoostAsioHandler(boost::asio::io_context &io_context) :
_iocontext(io_context),
_strand(std::make_shared<boost::asio::io_context::strand>(_iocontext))
//_timer(std::make_shared<Timer>(_iocontext,_strand))
{
}
/**
* Handler cannot be copied or moved
*
* @param that The object to not move or copy
*/
LibBoostAsioHandler(LibBoostAsioHandler &&that) = delete;
LibBoostAsioHandler(const LibBoostAsioHandler &that) = delete;
/**
* Returns a reference to the boost io_context object that is being used.
* @return The boost io_context object.
*/
boost::asio::io_context &service()
{
return _iocontext;
}
/**
* Destructor
*/
~LibBoostAsioHandler() override = default;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,531 @@
/**
* LibEV.h
*
* Implementation for the AMQP::TcpHandler that is optimized for libev. You can
* use this class instead of a AMQP::TcpHandler class, just pass the event loop
* to the constructor and you're all set
*
* Compile with: "g++ -std=c++11 libev.cpp -lamqpcpp -lev -lpthread"
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <ev.h>
#include <list>
#include "amqpcpp/linux_tcp.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class LibEvHandler : public TcpHandler
{
private:
/**
* Internal interface for the object that is being watched
*/
class Watchable
{
public:
/**
* The method that is called when a filedescriptor becomes active
* @param fd
* @param events
*/
virtual void onActive(int fd, int events) = 0;
/**
* Method that is called when the timer expires
*/
virtual void onExpired() = 0;
};
/**
* Helper class that wraps a libev I/O watcher
*/
class Watcher
{
private:
/**
* The event loop to which it is attached
* @var struct ev_loop
*/
struct ev_loop *_loop;
/**
* The actual watcher structure
* @var struct ev_io
*/
struct ev_io _io;
/**
* Callback method that is called by libev when a filedescriptor becomes active
* @param loop The loop in which the event was triggered
* @param w Internal watcher object
* @param revents Events triggered
*/
static void callback(struct ev_loop *loop, struct ev_io *watcher, int revents)
{
// retrieve the watched object
Watchable *object = static_cast<Watchable*>(watcher->data);
// tell the object that its filedescriptor is active
object->onActive(watcher->fd, revents);
}
public:
/**
* Constructor
* @param loop The current event loop
* @param object The object being watched
* @param fd The filedescriptor being watched
* @param events The events that should be monitored
* @param priority The priority for the watcher
*/
Watcher(struct ev_loop *loop, Watchable *object, int fd, int events, int priority) : _loop(loop)
{
// initialize the libev structure
ev_io_init(&_io, callback, fd, events);
// install a priority
ev_set_priority(&_io, priority);
// store the object in the data "void*"
_io.data = object;
// start the watcher
ev_io_start(_loop, &_io);
}
/**
* Watchers cannot be copied or moved
*
* @param that The object to not move or copy
*/
Watcher(Watcher &&that) = delete;
Watcher(const Watcher &that) = delete;
/**
* Destructor
*/
virtual ~Watcher()
{
// stop the watcher
ev_io_stop(_loop, &_io);
}
/**
* Check if a filedescriptor is covered by the watcher
* @param fd
* @return bool
*/
bool contains(int fd) const { return _io.fd == fd; }
/**
* Change the events for which the filedescriptor is monitored
* @param events
*/
void events(int events)
{
// stop the watcher if it was active
ev_io_stop(_loop, &_io);
// set the events
ev_io_set(&_io, _io.fd, events);
// and restart it
ev_io_start(_loop, &_io);
}
};
/**
* Wrapper around a connection, this will monitor the filedescriptors
* and run a timer to send out heartbeats
*/
class Wrapper : private Watchable
{
private:
/**
* The connection that is monitored
* @var TcpConnection
*/
TcpConnection *_connection;
/**
* The event loop to which it is attached
* @var struct ev_loop
*/
struct ev_loop *_loop;
/**
* The watcher for the timer
* @var struct ev_io
*/
struct ev_timer _timer;
/**
* IO-watchers to monitor filedescriptors
* @var std::list
*/
std::list<Watcher> _watchers;
/**
* When should we send the next heartbeat?
* @var ev_tstamp
*/
ev_tstamp _next = 0.0;
/**
* When does the connection expire / was the server for a too longer period of time idle?
* During connection setup, this member is used as the connect-timeout.
* @var ev_tstamp
*/
ev_tstamp _expire;
/**
* Timeout after which the connection is no longer considered alive.
* A heartbeat must be sent every _timeout / 2 seconds.
* Value zero means heartbeats are disabled, or not yet negotiated.
* @var uint16_t
*/
uint16_t _timeout = 0;
/**
* Callback method that is called by libev when the timer expires
* @param loop The loop in which the event was triggered
* @param timer Internal timer object
* @param revents The events that triggered this call
*/
static void callback(struct ev_loop *loop, struct ev_timer *timer, int revents)
{
// retrieve the object
Watchable *object = static_cast<Watchable*>(timer->data);
// tell the object that it expired
object->onExpired();
}
/**
* Do we need timers / is this a timed monitor?
* @return bool
*/
bool timed() const
{
// if neither timers are set
return _expire > 0.0 || _next > 0.0;
}
/**
* Method that is called when the timer expired
*/
virtual void onExpired() override
{
// get the current time
ev_tstamp now = ev_now(_loop);
// timer is no longer active, so the refcounter in the loop is restored
ev_ref(_loop);
// if the onNegotiate method was not yet called, and no heartbeat timeout was negotiated
if (_timeout == 0)
{
// this can happen in three situations: 1. a connect-timeout, 2. user space has
// told us that we're not interested in heartbeats, 3. rabbitmq does not want heartbeats,
// in either case we're no longer going to run further timers.
_next = _expire = 0.0;
// if we have an initialized connection, user-space must have overridden the onNegotiate
// method, so we keep using the connection
if (_connection->initialized()) return;
// this is a connection timeout, close the connection from our side too
return (void)_connection->close(true);
}
else if (now >= _expire)
{
// the server was inactive for a too long period of time, reset state
_next = _expire = 0.0; _timeout = 0;
// close the connection because server was inactive (we close it with immediate effect,
// because it was inactive so we cannot trust it to respect the AMQP close handshake)
return (void)_connection->close(true);
}
else if (now >= _next)
{
// it's time for the next heartbeat
_connection->heartbeat();
// remember when we should send out the next one, so the next one should be
// sent only after _timout/2 seconds again _from now_ (no catching up)
_next = now + std::max(_timeout / 2, 1);
}
// reset the timer to trigger again later
ev_timer_set(&_timer, std::min(_next, _expire) - now, 0.0);
// and start it again
ev_timer_start(_loop, &_timer);
// and because the timer is running again, we restore the refcounter
ev_unref(_loop);
}
/**
* Method that is called when a filedescriptor becomes active
* @param fd the filedescriptor that is active
* @param events the events that are active (readable/writable)
*/
virtual void onActive(int fd, int events) override
{
// if the server is readable, we have some extra time before it expires, the expire time
// is set to 1.5 * _timeout to close the connection when the third heartbeat is about to be sent
if (_timeout != 0 && (events & EV_READ)) _expire = ev_now(_loop) + _timeout * 1.5;
// pass on to the connection
_connection->process(fd, events);
}
public:
/**
* Constructor
* @param loop The current event loop
* @param connection The TCP connection
* @param timeout Connect timeout
* @param priority The priority (high priorities are invoked earlier
*/
Wrapper(struct ev_loop *loop, AMQP::TcpConnection *connection, uint16_t timeout, int priority) :
_connection(connection),
_loop(loop),
_next(0.0),
_expire(ev_now(loop) + timeout),
_timeout(0)
{
// store the object in the data "void*"
_timer.data = this;
// initialize the libev structure, it should expire after the connection timeout
ev_timer_init(&_timer, callback, timeout, 0.0);
// set a priority
ev_set_priority(&_timer, priority);
// start the timer (this is the time that we reserve for setting up the connection)
ev_timer_start(_loop, &_timer);
// the timer should not keep the event loop active
ev_unref(_loop);
}
/**
* Watchers cannot be copied or moved
*
* @param that The object to not move or copy
*/
Wrapper(Wrapper &&that) = delete;
Wrapper(const Wrapper &that) = delete;
/**
* Destructor
*/
virtual ~Wrapper()
{
// the timer was already stopped
if (!timed()) return;
// stop the timer
ev_timer_stop(_loop, &_timer);
// restore loop refcount
ev_ref(_loop);
}
/**
* Start the timer (and expose the interval)
* @param interval the heartbeat interval proposed by the server
* @return uint16_t the heartbeat interval that we accepted
*/
uint16_t start(uint16_t timeout)
{
// we now know for sure that the connection was set up
_timeout = timeout;
// if heartbeats are disabled we do not have to set it
if (_timeout == 0) return 0;
// calculate current time
auto now = ev_now(_loop);
// we also know when the next heartbeat should be sent
_next = now + std::max(_timeout / 2, 1);
// because the server has just sent us some data, we will update the expire time too
_expire = now + _timeout * 1.5;
// stop the existing timer (we have to stop it and restart it, because ev_timer_set()
// on its own does not change the running timer) (note that we assume that the timer
// is already running and keeps on running, so no calls to ev_ref()/en_unref() here)
ev_timer_stop(_loop, &_timer);
// find the earliest thing that expires
ev_timer_set(&_timer, std::min(_next, _expire) - now, 0.0);
// and start it again
ev_timer_start(_loop, &_timer);
// expose the accepted interval
return _timeout;
}
/**
* Check if the timer is associated with a certain connection
* @param connection
* @return bool
*/
bool contains(const AMQP::TcpConnection *connection) const
{
// compare the connections
return _connection == connection;
}
/**
* Monitor a filedescriptor
* @param fd
* @param events
*/
void monitor(int fd, int events)
{
// should we remove?
if (events == 0)
{
// remove the io-watcher
_watchers.remove_if([fd](const Watcher &watcher) -> bool {
// compare filedescriptors
return watcher.contains(fd);
});
}
else
{
// look in the array for this filedescriptor
for (auto &watcher : _watchers)
{
// do we have a match?
if (watcher.contains(fd)) return watcher.events(events);
}
// we need a watcher
Watchable *watchable = this;
// we should monitor a new filedescriptor
_watchers.emplace_back(_loop, watchable, fd, events, ev_priority(&_timer));
}
}
};
/**
* The event loop
* @var struct ev_loop*
*/
struct ev_loop *_loop;
/**
* Each connection is wrapped
* @var std::list
*/
std::list<Wrapper> _wrappers;
/**
* The priority that watchers should have (higher prio means libev gives more prio to this eveht)
* @var int
*/
int _priority;
/**
* Lookup a connection-wrapper, when the wrapper is not found, we construct one
* @param connection
* @return Wrapper
*/
Wrapper &lookup(TcpConnection *connection)
{
// look for the appropriate connection
for (auto &wrapper : _wrappers)
{
// do we have a match?
if (wrapper.contains(connection)) return wrapper;
}
// add to the wrappers
_wrappers.emplace_back(_loop, connection, 60, _priority);
// done
return _wrappers.back();
}
protected:
/**
* Method that is called by AMQP-CPP to register a filedescriptor for readability or writability
* @param connection The TCP connection object that is reporting
* @param fd The filedescriptor to be monitored
* @param flags Should the object be monitored for readability or writability?
*/
virtual void monitor(TcpConnection *connection, int fd, int flags) override
{
// lookup the appropriate wrapper and start monitoring
lookup(connection).monitor(fd, flags);
}
/**
* Method that is called when the heartbeat timeout is negotiated between the server and the client.
* @param connection The connection that suggested a heartbeat timeout
* @param timeout The suggested timeout from the server
* @return uint16_t The timeout to use
*/
virtual uint16_t onNegotiate(TcpConnection *connection, uint16_t timeout) override
{
// lookup the wrapper, and start the timer to check for activity and send heartbeats
return lookup(connection).start(timeout);
}
/**
* Method that is called when the TCP connection is destructed
* @param connection The TCP connection
*/
virtual void onDetached(TcpConnection *connection) override
{
// remove from the array
_wrappers.remove_if([connection](const Wrapper &wrapper) -> bool {
return wrapper.contains(connection);
});
}
public:
/**
* Constructor
* @param loop The event loop to wrap
* @param priority The libev priority (higher priorities are invoked earlier)
*/
LibEvHandler(struct ev_loop *loop, int priority = 0) : _loop(loop), _priority(priority) {}
/**
* Destructor
*/
virtual ~LibEvHandler() = default;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,191 @@
/**
* LibEvent.h
*
* Implementation for the AMQP::TcpHandler that is optimized for libevent. You can
* use this class instead of a AMQP::TcpHandler class, just pass the event loop
* to the constructor and you're all set
*
* Compile with: "g++ -std=c++11 libevent.cpp -lamqpcpp -levent -lpthread"
*
* @author Brent Dimmig <brentdimmig@gmail.com>
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <event2/event.h>
#include <amqpcpp/flags.h>
#include <amqpcpp/linux_tcp.h>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class LibEventHandler : public TcpHandler
{
private:
/**
* Helper class that wraps a libev I/O watcher
*/
class Watcher
{
private:
/**
* The actual event structure
* @var struct event
*/
struct event * _event;
/**
* Callback method that is called by libevent when a filedescriptor becomes active
* @param fd The filedescriptor with an event
* @param what Events triggered
* @param connection_arg void * to the connection
*/
static void callback(evutil_socket_t fd, short what, void *connection_arg)
{
// retrieve the connection
TcpConnection *connection = static_cast<TcpConnection*>(connection_arg);
// setup amqp flags
int amqp_flags = 0;
if (what & EV_READ)
amqp_flags |= AMQP::readable;
if (what & EV_WRITE)
amqp_flags |= AMQP::writable;
// tell the connection that its filedescriptor is active
connection->process(fd, amqp_flags);
}
public:
/**
* Constructor
* @param evbase The current event loop
* @param connection The connection being watched
* @param fd The filedescriptor being watched
* @param events The events that should be monitored
*/
Watcher(struct event_base *evbase, TcpConnection *connection, int fd, int events)
{
// setup libevent flags
short event_flags = EV_PERSIST;
if (events & AMQP::readable)
event_flags |= EV_READ;
if (events & AMQP::writable)
event_flags |= EV_WRITE;
// initialize the event
_event = event_new(evbase, fd, event_flags, callback, connection);
event_add(_event, nullptr);
}
/**
* Destructor
*/
virtual ~Watcher()
{
// stop the event
event_del(_event);
// free the event
event_free(_event);
}
/**
* Change the events for which the filedescriptor is monitored
* @param events
*/
void events(int events)
{
// stop the event if it was active
event_del(_event);
// setup libevent flags
short event_flags = EV_PERSIST;
if (events & AMQP::readable)
event_flags |= EV_READ;
if (events & AMQP::writable)
event_flags |= EV_WRITE;
// set the events
event_assign(_event, event_get_base(_event), event_get_fd(_event), event_flags,
event_get_callback(_event), event_get_callback_arg(_event));
// and restart it
event_add(_event, nullptr);
}
};
/**
* The event loop
* @var struct event_base*
*/
struct event_base *_evbase;
/**
* All I/O watchers that are active, indexed by their filedescriptor
* @var std::map<int,Watcher>
*/
std::map<int,std::unique_ptr<Watcher>> _watchers;
/**
* Method that is called by AMQP-CPP to register a filedescriptor for readability or writability
* @param connection The TCP connection object that is reporting
* @param fd The filedescriptor to be monitored
* @param flags Should the object be monitored for readability or writability?
*/
virtual void monitor(TcpConnection *connection, int fd, int flags) override
{
// do we already have this filedescriptor
auto iter = _watchers.find(fd);
// was it found?
if (iter == _watchers.end())
{
// we did not yet have this watcher - but that is ok if no filedescriptor was registered
if (flags == 0) return;
// construct a new watcher, and put it in the map
_watchers[fd] = std::unique_ptr<Watcher>(new Watcher(_evbase, connection, fd, flags));
}
else if (flags == 0)
{
// the watcher does already exist, but we no longer have to watch this watcher
_watchers.erase(iter);
}
else
{
// change the events
iter->second->events(flags);
}
}
public:
/**
* Constructor
* @param evbase The event loop to wrap
*/
LibEventHandler(struct event_base *evbase) : _evbase(evbase) {}
/**
* Destructor
*/
virtual ~LibEventHandler() = default;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,224 @@
/**
* LibUV.h
*
* Implementation for the AMQP::TcpHandler that is optimized for libuv. You can
* use this class instead of a AMQP::TcpHandler class, just pass the event loop
* to the constructor and you're all set.
*
* Based heavily on the libev.h implementation by Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
*
* @author David Nikdel <david@nikdel.com>
* @copyright 2015 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <uv.h>
#include "amqpcpp/linux_tcp.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class LibUvHandler : public TcpHandler
{
private:
/**
* Helper class that wraps a libev I/O watcher
*/
class Watcher
{
private:
/**
* The event loop to which it is attached
* @var uv_loop_t
*/
uv_loop_t *_loop;
/**
* The actual watcher structure
* @var uv_poll_t
*/
uv_poll_t *_poll;
/**
* Callback method that is called by libuv when a filedescriptor becomes active
* @param handle Internal poll handle
* @param status LibUV error code UV_*
* @param events Events triggered
*/
static void callback(uv_poll_t *handle, int status, int events)
{
// retrieve the connection
TcpConnection *connection = static_cast<TcpConnection*>(handle->data);
// tell the connection that its filedescriptor is active
int fd = -1;
uv_fileno(reinterpret_cast<uv_handle_t*>(handle), &fd);
connection->process(fd, uv_to_amqp_events(status, events));
}
public:
/**
* Constructor
* @param loop The current event loop
* @param connection The connection being watched
* @param fd The filedescriptor being watched
* @param events The events that should be monitored
*/
Watcher(uv_loop_t *loop, TcpConnection *connection, int fd, int events) : _loop(loop)
{
// create a new poll
_poll = new uv_poll_t();
// initialize the libev structure
uv_poll_init(_loop, _poll, fd);
// store the connection in the data "void*"
_poll->data = connection;
// start the watcher
uv_poll_start(_poll, amqp_to_uv_events(events), callback);
}
/**
* Watchers cannot be copied or moved
*
* @param that The object to not move or copy
*/
Watcher(Watcher &&that) = delete;
Watcher(const Watcher &that) = delete;
/**
* Destructor
*/
virtual ~Watcher()
{
// stop the watcher
uv_poll_stop(_poll);
// close the handle
uv_close(reinterpret_cast<uv_handle_t*>(_poll), [](uv_handle_t* handle) {
// delete memory once closed
delete reinterpret_cast<uv_poll_t*>(handle);
});
}
/**
* Change the events for which the filedescriptor is monitored
* @param events
*/
void events(int events)
{
// update the events being watched for
uv_poll_start(_poll, amqp_to_uv_events(events), callback);
}
private:
/**
* Convert event flags from UV format to AMQP format
*/
static int uv_to_amqp_events(int status, int events)
{
// if the socket is closed report both so we pick up the error
if (status != 0)
return AMQP::readable | AMQP::writable;
// map read or write
int amqp_events = 0;
if (events & UV_READABLE)
amqp_events |= AMQP::readable;
if (events & UV_WRITABLE)
amqp_events |= AMQP::writable;
return amqp_events;
}
/**
* Convert event flags from AMQP format to UV format
*/
static int amqp_to_uv_events(int events)
{
int uv_events = 0;
if (events & AMQP::readable)
uv_events |= UV_READABLE;
if (events & AMQP::writable)
uv_events |= UV_WRITABLE;
return uv_events;
}
};
/**
* The event loop
* @var uv_loop_t*
*/
uv_loop_t *_loop;
/**
* All I/O watchers that are active, indexed by their filedescriptor
* @var std::map<int,Watcher>
*/
std::map<int,std::unique_ptr<Watcher>> _watchers;
/**
* Method that is called by AMQP-CPP to register a filedescriptor for readability or writability
* @param connection The TCP connection object that is reporting
* @param fd The filedescriptor to be monitored
* @param flags Should the object be monitored for readability or writability?
*/
virtual void monitor(TcpConnection *connection, int fd, int flags) override
{
// do we already have this filedescriptor
auto iter = _watchers.find(fd);
// was it found?
if (iter == _watchers.end())
{
// we did not yet have this watcher - but that is ok if no filedescriptor was registered
if (flags == 0) return;
// construct a new watcher, and put it in the map
_watchers[fd] = std::unique_ptr<Watcher>(new Watcher(_loop, connection, fd, flags));
}
else if (flags == 0)
{
// the watcher does already exist, but we no longer have to watch this watcher
_watchers.erase(iter);
}
else
{
// change the events
iter->second->events(flags);
}
}
public:
/**
* Constructor
* @param loop The event loop to wrap
*/
LibUvHandler(uv_loop_t *loop) : _loop(loop) {}
/**
* Destructor
*/
virtual ~LibUvHandler() = default;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,4 @@
#include "linux_tcp/tcpparent.h"
#include "linux_tcp/tcphandler.h"
#include "linux_tcp/tcpconnection.h"
#include "linux_tcp/tcpchannel.h"

View File

@ -0,0 +1,62 @@
/**
* TcpChannel.h
*
* Extended channel that can be constructed on top of a TCP connection
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2017 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class TcpChannel : public Channel
{
public:
/**
* Constructor
*
* The passed in connection pointer must remain valid for the
* lifetime of the channel. A constructor is thrown if the channel
* cannot be connected (because the connection is already closed or
* because max number of channels has been reached)
*
* @param connection
* @throws std::runtime_error
*/
TcpChannel(TcpConnection *connection) :
Channel(&connection->_connection) {}
/**
* Destructor
*/
virtual ~TcpChannel() {}
/**
* Copying is not allowed.
* @param other
*/
TcpChannel(const TcpChannel &other) = delete;
/**
* But movement is allowed
* @param other
*/
TcpChannel(TcpChannel &&other) = default;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,357 @@
/**
* TcpConnection.h
*
* Extended Connection object that creates a TCP connection for the
* IO between the client application and the RabbitMQ server.
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2021 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class TcpState;
class TcpChannel;
/**
* Class definition
*/
class TcpConnection :
private ConnectionHandler,
private Watchable,
private TcpParent
{
private:
/**
* User-space handler object
* @var TcpHandler
*/
TcpHandler *_handler;
/**
* The state of the TCP connection - this state objecs changes based on
* the state of the connection (resolving, connected or closed)
* @var std::unique_ptr<TcpState>
*/
std::unique_ptr<TcpState> _state;
/**
* The underlying AMQP connection
* @var Connection
*/
Connection _connection;
/**
* The channel may access out _connection
* @friend
*/
friend TcpChannel;
/**
* Method that is called when the RabbitMQ server and your client application
* exchange some properties that describe their identity.
* @param connection The connection about which information is exchanged
* @param server Properties sent by the server
* @param client Properties that are to be sent back
*/
virtual void onProperties(Connection *connection, const Table &server, Table &client) override;
/**
* Method that is called when the heartbeat frequency is negotiated.
* @param connection The connection that suggested a heartbeat interval
* @param interval The suggested interval from the server
* @return uint16_t The interval to use
*/
virtual uint16_t onNegotiate(Connection *connection, uint16_t interval) override;
/**
* Method that is called by the connection when data needs to be sent over the network
* @param connection The connection that created this output
* @param buffer Data to send
* @param size Size of the buffer
*/
virtual void onData(Connection *connection, const char *buffer, size_t size) override;
/**
* Method that is called when the server sends a heartbeat to the client
* @param connection The connection over which the heartbeat was received
*/
virtual void onHeartbeat(Connection *connection) override
{
// pass on to tcp handler
if (_handler) _handler->onHeartbeat(this);
}
/**
* Method called when the connection ends up in an error state
* @param connection The connection that entered the error state
* @param message Error message
*/
virtual void onError(Connection *connection, const char *message) override;
/**
* Method that is called when the AMQP connection is established
* @param connection The connection that can now be used
*/
virtual void onReady(Connection *connection) override
{
// pass on to the handler
if (_handler) _handler->onReady(this);
}
/**
* Method that is called when the connection was closed.
* @param connection The connection that was closed and that is now unusable
*/
virtual void onClosed(Connection *connection) override;
/**
* Method that is called when the AMQP connection was blocked.
* @param connection The connection that was blocked
* @param reason Why was the connection blocked
*/
virtual void onBlocked(Connection *connection, const char *reason) override
{
// pass to user space
if (_handler) _handler->onBlocked(this, reason);
}
/**
* Method that is called when the AMQP connection is no longer blocked.
* @param connection The connection that is no longer blocked
*/
virtual void onUnblocked(Connection *connection)
{
// pass to user space
if (_handler) _handler->onUnblocked(this);
}
/**
* Method that is called when the tcp connection has been established
* @param state
*/
virtual void onConnected(TcpState *state) override
{
// pass on to the handler
if (_handler) _handler->onConnected(this);
}
/**
* Method that is called when right before connection is being secured
* @param state
* @param ssl
* @return bool
*/
virtual bool onSecuring(TcpState *state, SSL *ssl) override
{
// pass on to user-space
return _handler && _handler->onSecuring(this, ssl);
}
/**
* Method that is called when the connection is secured
* @param state
* @param ssl
* @return bool
*/
virtual bool onSecured(TcpState *state, const SSL *ssl) override
{
// pass on to user-space
return _handler && _handler->onSecured(this, ssl);
}
/**
* Method to be called when data was received
* @param state
* @param buffer
* @return size_t
*/
virtual size_t onReceived(TcpState *state, const Buffer &buffer) override
{
// pass on to the connection
return _connection.parse(buffer);
}
/**
* Method to be called when we need to monitor a different filedescriptor
* @param state
* @param fd
* @param events
*/
virtual void onIdle(TcpState *state, int socket, int events) override
{
// pass on to user-space
if (_handler) _handler->monitor(this, socket, events);
}
/**
* Method that is called when an error occurs (the connection is lost)
* @param state
* @param error
* @param connected
*/
virtual void onError(TcpState *state, const char *message, bool connected) override;
/**
* Method to be called when it is detected that the connection was lost
* @param state
*/
virtual void onLost(TcpState *state) override;
/**
* The expected number of bytes
* @return size_t
*/
virtual size_t expected() override
{
// pass on to the connection
return _connection.expected();
}
public:
/**
* Constructor
* @param handler User implemented handler object
* @param hostname The address to connect to
*/
TcpConnection(TcpHandler *handler, const Address &address);
/**
* No copying
* @param that
*/
TcpConnection(const TcpConnection &that) = delete;
/**
* Destructor
*/
virtual ~TcpConnection() noexcept;
/**
* The filedescriptor that is used for this connection
* @return int
*/
int fileno() const;
/**
* Process the TCP connection
*
* This method should be called when the filedescriptor that is registered
* in the event loop becomes active. You should pass in a flag holding the
* flags AMQP::readable or AMQP::writable to indicate whether the descriptor
* was readable or writable, or bitwise-or if it was both
*
* @param fd The filedescriptor that became readable or writable
* @param events What sort of events occured?
*/
void process(int fd, int flags);
/**
* Close the connection in an elegant fashion. This closes all channels and the
* TCP connection. Note that the connection is not immediately closed: first all
* pending operations are completed, and then an AMQP closing-handshake is
* performed. If you pass a parameter "immediate=true" the connection is
* immediately closed, without waiting for earlier commands (and your handler's
* onError() method is called about the premature close, including the onLost() and
* onDetached()).
* @return bool
*/
bool close(bool immediate = false);
/**
* Is the connection connected, meaning: it has passed the login handshake
* and isn't closed yet?
* @return bool
*/
bool ready() const
{
return _connection.ready();
}
/**
* Is the connection initialized, meaning: it has passed the login handshake?
* It may be closing or closed
* @return bool
*/
bool initialized() const
{
return _connection.initialized();
}
/**
* Is the connection in a usable state / not yet closed or being closed
* When a connection is usable, you can send further commands over it. When it is
* unusable, it may still be connected and finished queued commands.
* @return bool
*/
bool usable() const
{
return _connection.usable();
}
/**
* Is the connection closed and full dead? The entire TCP connection has been discarded.
* @return bool
*/
bool closed() const;
/**
* The max frame size. Useful if you set up a buffer to parse incoming data: it does not have to exceed this size.
* @return uint32_t
*/
uint32_t maxFrame() const
{
return _connection.maxFrame();
}
/**
* The number of bytes that can best be passed to the next call to the parse() method.
* @return uint32_t
*/
uint32_t expected() const
{
return _connection.expected();
}
/**
* Return the number of channels this connection has.
* @return std::size_t
*/
std::size_t channels() const
{
// return the number of channels this connection has
return _connection.channels();
}
/**
* The number of outgoing bytes queued on this connection.
* @return std::size_t
*/
std::size_t queued() const;
/**
* Send a heartbeat
* @return bool
*/
bool heartbeat()
{
return _connection.heartbeat();
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,27 @@
#pragma once
/**
* No MSG_NOSIGNAL on OS X.
* Avoid SIGPIPE by using sockopt call.
*/
#ifdef MSG_NOSIGNAL
# define AMQP_CPP_MSG_NOSIGNAL MSG_NOSIGNAL
#else
# define AMQP_CPP_MSG_NOSIGNAL 0
# ifdef SO_NOSIGPIPE
# define AMQP_CPP_USE_SO_NOSIGPIPE
# else
# error "Cannot block SIGPIPE!"
# endif
#endif
#ifdef AMQP_CPP_USE_SO_NOSIGPIPE
/**
* Ignore SIGPIPE when there is no MSG_NOSIGNAL.
*/
inline void set_sockopt_nosigpipe(int socket)
{
int optval = 1;
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
}
#endif

View File

@ -0,0 +1,275 @@
/**
* TcpHandler.h
*
* Interface to be implemented by the caller of the AMQP library in case
* the "Tcp" class is being used as alternative for the ConnectionHandler
* class.
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2015 - 2021 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class TcpConnection;
/**
* Class definition
*/
class TcpHandler
{
public:
/**
* Destructor
*/
virtual ~TcpHandler() = default;
/**
* Method that is called immediately after a connection has been constructed.
* @param connection The connection object that was just constructed
*/
virtual void onAttached(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the TCP connection ends up in a connected state
* This method is called after the TCP connection has been set up, but before
* the (optional) secure TLS connection is ready, and before the AMQP login
* handshake has been completed. If this step has been set, the onLost()
* method will also always be called when the connection is closed.
* @param connection The TCP connection
*/
virtual void onConnected(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called after a TCP connection has been set up, and right before
* the SSL handshake is going to be performed to secure the connection (only for
* amqps:// connections). This method can be overridden in user space to load
* client side certificates.
* @param connection The connection for which TLS was just started
* @param ssl Pointer to the SSL structure that can be modified
* @return bool True to proceed / accept the connection, false to break up
*/
virtual bool onSecuring(TcpConnection *connection, SSL *ssl)
{
// make sure compilers dont complain about unused parameters
(void) connection;
(void) ssl;
// default implementation: do not do anything, just allow the connection
return true;
}
/**
* Method that is called after a TCP connection has been set up and the initial
* TLS handshake is finished too, but right before the AMQP login handshake is
* going to take place and the first data is going to be sent over the connection.
* This method allows you to inspect the TLS certificate and other connection
* properties, and to break up the connection if you find it not secure enough.
* The default implementation considers all connections to be secure, even if the
* connection has a self-signed or even invalid certificate. To be more strict,
* override this method, inspect the certificate and return false if you do not
* want to use the connection. The passed in SSL pointer is a pointer to a SSL
* structure from the openssl library. This method is only called for secure
* connections (connection with an amqps:// address).
* @param connection The connection for which TLS was just started
* @param ssl Pointer to the SSL structure that can be inspected
* @return bool True to proceed / accept the connection, false to break up
*/
virtual bool onSecured(TcpConnection *connection, const SSL *ssl)
{
// make sure compilers dont complain about unused parameters
(void) connection;
(void) ssl;
// default implementation: do not inspect anything, just allow the connection
return true;
}
/**
* Method that is called when the RabbitMQ server and your client application
* exchange some properties that describe their identity.
* @param connection The connection about which information is exchanged
* @param server Properties sent by the server
* @param client Properties that are to be sent back
*/
virtual void onProperties(TcpConnection *connection, const Table &server, Table &client)
{
// make sure compilers dont complaint about unused parameters
(void) connection;
(void) server;
(void) client;
}
/**
* Method that is called when the heartbeat frequency is negotiated
* between the server and the client. Applications can override this method
* if they want to use a different heartbeat interval (for example: return 0
* to disable heartbeats)
* @param connection The connection that suggested a heartbeat interval
* @param interval The suggested interval from the server
* @return uint16_t The interval to use
*
* @see ConnectionHandler::onNegotiate
*/
virtual uint16_t onNegotiate(TcpConnection *connection, uint16_t interval)
{
// make sure compilers dont complain about unused parameters
(void) connection;
(void) interval;
// default implementation, suggested heartbeat is ok
return interval;
}
/**
* Method that is called after the AMQP login handshake has been completed
* and the connection object is ready for sending out actual AMQP instructions
* @param connection The TCP connection
*/
virtual void onReady(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the server sends a heartbeat to the client
* @param connection The connection over which the heartbeat was received
* @see ConnectionHandler::onHeartbeat
*/
virtual void onHeartbeat(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the connection ends up in an error state
* This could either be an error at the AMQP level, but could also
* be an error at the TCP of SSL level (like a broken connection).
* If the connection is connected (the onConnected() method was called
* before), the onLost() method is going to be called too.
* @param connection The TCP connection
* @param message Error message
*/
virtual void onError(TcpConnection *connection, const char *message)
{
// make sure compilers dont complain about unused parameters
(void) connection;
(void) message;
}
/**
* Method that is called when the AMQP protocol was gracefully ended.
* This is the counter-part of a call to connection.close(). Note that
* the underlying TCP connection is still alive, and onLost() and
* onDetached() (see below) are going to be called too.
* @param connection The TCP connection
*/
virtual void onClosed(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the AMQP connection was blocked.
*
* This method is called, when the server connection gets blocked for the first
* time due to the broker running low on a resource (memory or disk). For
* example, when a RabbitMQ node detects that it is low on RAM, it sends a
* notification to all connected publishing clients supporting this feature.
* If before the connections are unblocked the node also starts running low on
* disk space, another notification will not be sent.
*
* @param connection The connection that was blocked
* @param reason Why was the connection blocked
*/
virtual void onBlocked(TcpConnection *connection, const char *reason)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the AMQP connection is no longer blocked.
*
* This method is called when all resource alarms have cleared and the
* connection is fully unblocked.
*
* @param connection The connection that is no longer blocked
*/
virtual void onUnblocked(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the TCP connection is lost or closed. This
* is always called if you have also received a call to onConnected().
* @param connection The TCP connection
*/
virtual void onLost(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Method that is called when the handler will no longer be notified.
* This is the last call to your handler, and it is typically used
* to clean up stuff.
* @param connection The connection that is being destructed
*/
virtual void onDetached(TcpConnection *connection)
{
// make sure compilers dont complain about unused parameters
(void) connection;
}
/**
* Monitor a filedescriptor for readability or writability
*
* When a TCP connection is opened, it creates a non-blocking socket
* connection. This method is called to inform you about this socket,
* so that you can include it in the event loop. When the socket becomes
* active, you should call the "process()" method in the Tcp class.
*
* The flags is AMQP::readable if the filedescriptor should be monitored
* for readability, AMQP::writable if it is to be monitored for writability,
* or AMQP::readable | AMQP::writable if it has to be checked for both.
* If flags has value 0, the filedescriptor should be removed from the
* event loop.
*
* @param connection The TCP connection object that is reporting
* @param fd The filedescriptor to be monitored
* @param flags Should the object be monitored for readability or writability?
*/
virtual void monitor(TcpConnection *connection, int fd, int flags) = 0;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,106 @@
/**
* TcpParent.h
*
* Interface to be implemented by the parent of a tcp-state. This is
* an _internal_ interface that is not relevant for user-space applications.
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2018 - 2021 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <openssl/ssl.h>
/**
* Begin of namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class TcpState;
class Buffer;
/**
* Class definition
*/
class TcpParent
{
public:
/**
* Destructor
*/
virtual ~TcpParent() = default;
/**
* Method that is called when the TCP connection has been established
* @param state
*/
virtual void onConnected(TcpState *state) = 0;
/**
* Method that is called right before a connection is secured and that allows userspac to change SSL
* @param state
* @param ssl
* @return bool
*/
virtual bool onSecuring(TcpState *state, SSL *ssl) = 0;
/**
* Method that is called when the connection is secured
* @param state
* @param ssl
* @return bool
*/
virtual bool onSecured(TcpState *state, const SSL *ssl) = 0;
/**
* Method to be called when data was received
* @param state
* @param buffer
* @return size_t
*/
virtual size_t onReceived(TcpState *state, const Buffer &buffer) = 0;
/**
* Method to be called when we need to monitor a different filedescriptor
* @param state
* @param fd
* @param events
*/
virtual void onIdle(TcpState *state, int socket, int events) = 0;
/**
* Method that is called when an error occurs (the connection is lost)
* @param state
* @param error
* @param connected
*/
virtual void onError(TcpState *state, const char *message, bool connected = true) = 0;
/**
* Method to be called when it is detected that the connection was lost
* @param state
*/
virtual void onLost(TcpState *state) = 0;
/**
* The expected number of bytes
* @return size_t
*/
virtual size_t expected() = 0;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,173 @@
/**
* The login information to access a server
*
* This class combines login, password and vhost
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <string>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Login
{
private:
/**
* The username
* @var string
*/
std::string _user;
/**
* The password
* @var string
*/
std::string _password;
public:
/**
* Default constructor
*/
Login() : _user("guest"), _password("guest") {}
/**
* Constructor
* @param user
* @param password
*/
Login(std::string user, std::string password) :
_user(std::move(user)), _password(std::move(password)) {}
/**
* Constructor
* @param user
* @param password
*/
Login(const char *user, const char *password) :
_user(user), _password(password) {}
/**
* Destructor
*/
virtual ~Login() = default;
/**
* Cast to boolean: is the login set?
* @return bool
*/
operator bool () const
{
return !_user.empty() || !_password.empty();
}
/**
* Negate operator: is it not set
* @return bool
*/
bool operator! () const
{
return _user.empty() && _password.empty();
}
/**
* Retrieve the user name
* @return string
*/
const std::string &user() const
{
return _user;
}
/**
* Retrieve the password
* @return string
*/
const std::string &password() const
{
return _password;
}
/**
* String representation in SASL PLAIN mode
* @return string
*/
std::string saslPlain() const
{
// we need an initial string
std::string result("\0", 1);
// append other elements
return result.append(_user).append("\0",1).append(_password);
}
/**
* Comparison operator
* @param that
* @return bool
*/
bool operator==(const Login &that) const
{
// username and password must match
return _user == that._user && _password == that._password;
}
/**
* Comparison operator
* @param that
* @return bool
*/
bool operator!=(const Login &that) const
{
// the opposite of operator==
return !operator==(that);
}
/**
* Comparison operator
* @param that
* @return bool
*/
bool operator<(const Login &that) const
{
// compare users
if (_user != that._user) return _user < that._user;
// compare passwords
return _password < that._password;
}
/**
* Friend function to allow writing the login to a stream
* @param stream
* @param login
* @return std::ostream
*/
friend std::ostream &operator<<(std::ostream &stream, const Login &login)
{
// write username and password
return stream << login._user << ":" << login._password;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,182 @@
/**
* Message.h
*
* An incoming message has the same sort of information as an outgoing
* message, plus some additional information.
*
* Message objects can not be constructed by end users, they are only constructed
* by the AMQP library, and passed to user callbacks.
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "envelope.h"
#include <limits>
#include <stdexcept>
#include <algorithm>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class DeferredReceiver;
/**
* Class definition
*/
class Message : public Envelope
{
private:
/**
* An allocated and mutable block of memory underlying _body
* @var char *
*/
char *_mutableBody = nullptr;
protected:
/**
* The exchange to which it was originally published
* @var string
*/
std::string _exchange;
/**
* The routing key that was originally used
* @var string
*/
std::string _routingkey;
/**
* Number of bytes already filled
* @var size_t
*/
size_t _filled = 0;
/**
* We are an open book to the consumer handler
*/
friend class DeferredReceiver;
/**
* Set the body size
* This field is set when the header is received
* @param uint64_t
*/
void setBodySize(uint64_t size)
{
// safety-check: on 32-bit platforms size_t is obviously also a 32-bit dword
// in which case casting the uint64_t to a size_t could result in truncation
// here we check whether the given size fits inside a size_t
if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error("message is too big for this system");
// store the new size
_bodySize = size;
}
/**
* Append data
* @param buffer incoming data
* @param size size of the data
* @return bool true if the message is now complete
*/
bool append(const char *buffer, uint64_t size)
{
// is the body already allocated?
if (_mutableBody)
{
// prevent overflow
size = std::min(size, _bodySize - _filled);
// append more data
memcpy(_mutableBody + _filled, buffer, (size_t)size);
// update filled data
_filled += (size_t)size;
}
else if (size >= _bodySize)
{
// we do not have to combine multiple frames, so we can store
// the buffer pointer in the message
_body = buffer;
}
else
{
// allocate the buffer
_mutableBody = (char *)malloc((size_t)_bodySize);
// expose the body in its immutable form
_body = _mutableBody;
// store the initial data
_filled = std::min((size_t)size, (size_t)_bodySize);
memcpy(_mutableBody, buffer, _filled);
}
// check if we're done
return _filled >= _bodySize;
}
public:
/**
* Constructor
*
* @param exchange
* @param routingKey
*/
Message(std::string exchange, std::string routingkey) :
Envelope(nullptr, 0), _exchange(std::move(exchange)), _routingkey(std::move(routingkey))
{}
/**
* Disabled copy constructor
* @param message the message to copy
*/
Message(const Message &message) = delete;
/**
* Destructor
*/
virtual ~Message()
{
if (_mutableBody) free(_mutableBody);
}
/**
* The exchange to which it was originally published
* @var string
*/
const std::string &exchange() const
{
// expose member
return _exchange;
}
/**
* The routing key that was originally used
* @var string
*/
const std::string &routingkey() const
{
// expose member
return _routingkey;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,402 @@
/**
* MetaData.h
*
* With every published message a set of meta data is passed to. This class
* holds all that meta data.
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "booleanset.h"
#include "stringfield.h"
#include "table.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class MetaData
{
protected:
/**
* First set of booleans
* @var BooleanSet
*/
BooleanSet _bools1;
/**
* Second set of booleans
* @var BooleanSet
*/
BooleanSet _bools2;
/**
* MIME content type
* @var ShortString
*/
ShortString _contentType;
/**
* MIME content encoding
* @var ShortString
*/
ShortString _contentEncoding;
/**
* message header field table
* @var Table
*/
Table _headers;
/**
* Delivery mode (non-persistent (1) or persistent (2))
* @var UOctet
*/
UOctet _deliveryMode = 0;
/**
* boolean whether field was sent to us
* @var UOctet
*/
UOctet _priority = 0;
/**
* application correlation identifier
* @var ShortString
*/
ShortString _correlationID;
/**
* address to reply to
* @var ShortString
*/
ShortString _replyTo;
/**
* message expiration identifier
* @var ShortString
*/
ShortString _expiration;
/**
* application message identifier
* @var ShortString
*/
ShortString _messageID;
/**
* message timestamp
* @var Timestamp
*/
Timestamp _timestamp;
/**
* message type name
* @var ShortString
*/
ShortString _typeName;
/**
* creating user id
* @var ShortString
*/
ShortString _userID;
/**
* creating application id
* @var ShortString
*/
ShortString _appID;
/**
* Deprecated cluster ID
* @var ShortString
*/
ShortString _clusterID;
public:
/**
* Constructor for empty meta data. Can be useful when user-space wants to preserve all meta-data
*/
MetaData() {}
/**
* Read incoming frame
* @param frame
*/
MetaData(InBuffer &frame) :
_bools1(frame),
_bools2(frame)
{
// only copy the properties that were sent
if (hasContentType()) _contentType = ShortString(frame);
if (hasContentEncoding()) _contentEncoding = ShortString(frame);
if (hasHeaders()) _headers = Table(frame);
if (hasDeliveryMode()) _deliveryMode = UOctet(frame);
if (hasPriority()) _priority = UOctet(frame);
if (hasCorrelationID()) _correlationID = ShortString(frame);
if (hasReplyTo()) _replyTo = ShortString(frame);
if (hasExpiration()) _expiration = ShortString(frame);
if (hasMessageID()) _messageID = ShortString(frame);
if (hasTimestamp()) _timestamp = Timestamp(frame);
if (hasTypeName()) _typeName = ShortString(frame);
if (hasUserID()) _userID = ShortString(frame);
if (hasAppID()) _appID = ShortString(frame);
if (hasClusterID()) _clusterID = ShortString(frame);
}
/**
* Destructor
*/
virtual ~MetaData() {}
/**
* Set all meta data
* @param data
*/
void set(const MetaData &data)
{
// simply copy all fields
_bools1 = data._bools1;
_bools2 = data._bools2;
_contentType = data._contentType;
_contentEncoding = data._contentEncoding;
_headers = data._headers;
_deliveryMode = data._deliveryMode;
_priority = data._priority;
_correlationID = data._correlationID;
_replyTo = data._replyTo;
_expiration = data._expiration;
_messageID = data._messageID;
_timestamp = data._timestamp;
_typeName = data._typeName;
_userID = data._userID;
_appID = data._appID;
_clusterID = data._clusterID;
}
/**
* Check if a certain field is set
* @return bool
*/
bool hasExpiration () const { return _bools1.get(0); }
bool hasReplyTo () const { return _bools1.get(1); }
bool hasCorrelationID () const { return _bools1.get(2); }
bool hasPriority () const { return _bools1.get(3); }
bool hasDeliveryMode () const { return _bools1.get(4); }
bool hasHeaders () const { return _bools1.get(5); }
bool hasContentEncoding () const { return _bools1.get(6); }
bool hasContentType () const { return _bools1.get(7); }
bool hasClusterID () const { return _bools2.get(2); }
bool hasAppID () const { return _bools2.get(3); }
bool hasUserID () const { return _bools2.get(4); }
bool hasTypeName () const { return _bools2.get(5); }
bool hasTimestamp () const { return _bools2.get(6); }
bool hasMessageID () const { return _bools2.get(7); }
/**
* Set the various supported fields
* @param value
*/
void setExpiration (const std::string &value) { _expiration = value; _bools1.set(0,true); }
void setReplyTo (const std::string &value) { _replyTo = value; _bools1.set(1,true); }
void setCorrelationID (const std::string &value) { _correlationID = value; _bools1.set(2,true); }
void setPriority (uint8_t value) { _priority = value; _bools1.set(3,true); }
void setDeliveryMode (uint8_t value) { _deliveryMode = value; _bools1.set(4,true); }
void setHeaders (const Table &value) { _headers = value; _bools1.set(5,true); }
void setContentEncoding (const std::string &value) { _contentEncoding = value; _bools1.set(6,true); }
void setContentType (const std::string &value) { _contentType = value; _bools1.set(7,true); }
void setClusterID (const std::string &value) { _clusterID = value; _bools2.set(2,true); }
void setAppID (const std::string &value) { _appID = value; _bools2.set(3,true); }
void setUserID (const std::string &value) { _userID = value; _bools2.set(4,true); }
void setTypeName (const std::string &value) { _typeName = value; _bools2.set(5,true); }
void setTimestamp (uint64_t value) { _timestamp = value; _bools2.set(6,true); }
void setMessageID (const std::string &value) { _messageID = value; _bools2.set(7,true); }
/**
* Set the various supported fields using r-value references
* @param value moveable value
*/
void setExpiration (std::string &&value) { _expiration = std::move(value); _bools1.set(0,true); }
void setReplyTo (std::string &&value) { _replyTo = std::move(value); _bools1.set(1,true); }
void setCorrelationID (std::string &&value) { _correlationID = std::move(value); _bools1.set(2,true); }
void setHeaders (Table &&value) { _headers = std::move(value); _bools1.set(5,true); }
void setContentEncoding (std::string &&value) { _contentEncoding = std::move(value); _bools1.set(6,true); }
void setContentType (std::string &&value) { _contentType = std::move(value); _bools1.set(7,true); }
void setClusterID (std::string &&value) { _clusterID = std::move(value); _bools2.set(2,true); }
void setAppID (std::string &&value) { _appID = std::move(value); _bools2.set(3,true); }
void setUserID (std::string &&value) { _userID = std::move(value); _bools2.set(4,true); }
void setTypeName (std::string &&value) { _typeName = std::move(value); _bools2.set(5,true); }
void setMessageID (std::string &&value) { _messageID = std::move(value); _bools2.set(7,true); }
/**
* Set the various supported fields using data buffers
* @param value data buffer
* @param size size of the buffer
*/
void setExpiration (const char *value, size_t size) { _expiration.assign(value, size); _bools1.set(0,true); }
void setReplyTo (const char *value, size_t size) { _replyTo.assign(value, size); _bools1.set(1,true); }
void setCorrelationID (const char *value, size_t size) { _correlationID.assign(value, size); _bools1.set(2,true); }
void setContentEncoding (const char *value, size_t size) { _contentEncoding.assign(value, size); _bools1.set(6,true); }
void setContentType (const char *value, size_t size) { _contentType.assign(value, size); _bools1.set(7,true); }
void setClusterID (const char *value, size_t size) { _clusterID.assign(value, size); _bools2.set(2,true); }
void setAppID (const char *value, size_t size) { _appID.assign(value, size); _bools2.set(3,true); }
void setUserID (const char *value, size_t size) { _userID.assign(value, size); _bools2.set(4,true); }
void setTypeName (const char *value, size_t size) { _typeName.assign(value, size); _bools2.set(5,true); }
void setMessageID (const char *value, size_t size) { _messageID.assign(value, size); _bools2.set(7,true); }
/**
* Set the various supported fields using c strings
* @param value data buffer
*/
void setExpiration (const char *value) { _expiration.assign(value); _bools1.set(0,true); }
void setReplyTo (const char *value) { _replyTo.assign(value); _bools1.set(1,true); }
void setCorrelationID (const char *value) { _correlationID.assign(value); _bools1.set(2,true); }
void setContentEncoding (const char *value) { _contentEncoding.assign(value); _bools1.set(6,true); }
void setContentType (const char *value) { _contentType.assign(value); _bools1.set(7,true); }
void setClusterID (const char *value) { _clusterID.assign(value); _bools2.set(2,true); }
void setAppID (const char *value) { _appID.assign(value); _bools2.set(3,true); }
void setUserID (const char *value) { _userID.assign(value); _bools2.set(4,true); }
void setTypeName (const char *value) { _typeName.assign(value); _bools2.set(5,true); }
void setMessageID (const char *value) { _messageID.assign(value); _bools2.set(7,true); }
/**
* Methods to remove properties from the header
*/
void removeExpiration () { _expiration .clear(); _bools1.set(0,false); }
void removeReplyTo () { _replyTo .clear(); _bools1.set(1,false); }
void removeCorrelationID () { _correlationID .clear(); _bools1.set(2,false); }
void removePriority () { _priority .clear(); _bools1.set(3,false); }
void removeDeliveryMode () { _deliveryMode .clear(); _bools1.set(4,false); }
void removeHeaders () { _headers .clear(); _bools1.set(5,false); }
void removeContentEncoding () { _contentEncoding.clear(); _bools1.set(6,false); }
void removeContentType () { _contentType .clear(); _bools1.set(7,false); }
void removeClusterID () { _clusterID .clear(); _bools2.set(2,false); }
void removeAppID () { _appID .clear(); _bools2.set(3,false); }
void removeUserID () { _userID .clear(); _bools2.set(4,false); }
void removeTypeName () { _typeName .clear(); _bools2.set(5,false); }
void removeTimestamp () { _timestamp .clear(); _bools2.set(6,false); }
void removeMessageID () { _messageID .clear(); _bools2.set(7,false); }
/**
* Retrieve the fields
* @return string
*/
const std::string &expiration () const { return _expiration; }
const std::string &replyTo () const { return _replyTo; }
const std::string &correlationID () const { return _correlationID; }
uint8_t priority () const { return _priority; }
uint8_t deliveryMode () const { return _deliveryMode; }
const Table &headers () const { return _headers; }
const std::string &contentEncoding() const { return _contentEncoding; }
const std::string &contentType () const { return _contentType; }
const std::string &clusterID () const { return _clusterID; }
const std::string &appID () const { return _appID; }
const std::string &userID () const { return _userID; }
const std::string &typeName () const { return _typeName; }
uint64_t timestamp () const { return _timestamp; }
const std::string &messageID () const { return _messageID; }
/**
* Is this a message with persistent storage
* This is an alias for retrieving the delivery mode and checking if it is set to 2
* @return bool
*/
bool persistent() const
{
return hasDeliveryMode() && deliveryMode() == 2;
}
/**
* Set whether storage should be persistent or not
* @param bool
*/
void setPersistent(bool value = true)
{
if (value)
{
// simply set the delivery mode
setDeliveryMode(2);
}
else
{
// we remove the field from the header
_deliveryMode = 0;
_bools1.set(4,false);
}
}
/**
* Total size
* @return uint32_t
*/
uint32_t size() const
{
// the result (2 for the two boolean sets)
uint32_t result = 2;
if (hasExpiration()) result += (uint32_t)_expiration.size();
if (hasReplyTo()) result += (uint32_t)_replyTo.size();
if (hasCorrelationID()) result += (uint32_t)_correlationID.size();
if (hasPriority()) result += (uint32_t)_priority.size();
if (hasDeliveryMode()) result += (uint32_t)_deliveryMode.size();
if (hasHeaders()) result += (uint32_t)_headers.size();
if (hasContentEncoding()) result += (uint32_t)_contentEncoding.size();
if (hasContentType()) result += (uint32_t)_contentType.size();
if (hasClusterID()) result += (uint32_t)_clusterID.size();
if (hasAppID()) result += (uint32_t)_appID.size();
if (hasUserID()) result += (uint32_t)_userID.size();
if (hasTypeName()) result += (uint32_t)_typeName.size();
if (hasTimestamp()) result += (uint32_t)_timestamp.size();
if (hasMessageID()) result += (uint32_t)_messageID.size();
// done
return result;
}
/**
* Fill an output buffer
* @param buffer
*/
void fill(OutBuffer &buffer) const
{
// the two boolean sets are always present
_bools1.fill(buffer);
_bools2.fill(buffer);
// only copy the properties that were sent
if (hasContentType()) _contentType.fill(buffer);
if (hasContentEncoding()) _contentEncoding.fill(buffer);
if (hasHeaders()) _headers.fill(buffer);
if (hasDeliveryMode()) _deliveryMode.fill(buffer);
if (hasPriority()) _priority.fill(buffer);
if (hasCorrelationID()) _correlationID.fill(buffer);
if (hasReplyTo()) _replyTo.fill(buffer);
if (hasExpiration()) _expiration.fill(buffer);
if (hasMessageID()) _messageID.fill(buffer);
if (hasTimestamp()) _timestamp.fill(buffer);
if (hasTypeName()) _typeName.fill(buffer);
if (hasUserID()) _userID.fill(buffer);
if (hasAppID()) _appID.fill(buffer);
if (hasClusterID()) _clusterID.fill(buffer);
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,138 @@
/**
* Monitor.h
*
* A monitor object monitors if the connection is still valid. When the
* connection is parsing incoming data, it calls the user handler for each
* incoming frame. However, it is unknown what this handler is going to do,
* it could for example decide to destruct the connection object. In that
* case the connection object should stop further handling the data. This
* monitor class is used to check if the connection has been destructed.
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "watchable.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class Monitor
{
private:
/**
* The object being watched
* @var Watchable
*/
Watchable *_watchable;
/**
* Invalidate the object
*/
void invalidate()
{
_watchable = nullptr;
}
public:
/**
* Constructor
* @param watchable
*/
Monitor(Watchable *watchable) : _watchable(watchable)
{
// register with the watchable
_watchable->add(this);
}
/**
* Copy constructor
* @param monitor
*/
Monitor(const Monitor &monitor) : _watchable(monitor._watchable)
{
// register with the watchable
if (_watchable) _watchable->add(this);
}
/**
* Assignment operator
* @param monitor
*/
Monitor& operator= (const Monitor &monitor)
{
// remove from watchable
if (_watchable) _watchable->remove(this);
// replace watchable
_watchable = monitor._watchable;
// register with the watchable
if (_watchable) _watchable->add(this);
return *this;
}
/**
* Destructor
*/
virtual ~Monitor()
{
// remove from watchable
if (_watchable) _watchable->remove(this);
}
/**
* Cast to boolean: is object in valid state?
* @return bool
*/
operator bool () const
{
return _watchable != nullptr;
}
/**
* Negate operator: is the object in an invalid state?
* @return bool
*/
bool operator! () const
{
return _watchable == nullptr;
}
/**
* Check if the object is valid
* @return bool
*/
bool valid() const
{
return _watchable != nullptr;
}
/**
* The watchable can access private data
*/
friend class Watchable;
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,228 @@
/**
* Numeric field types for AMQP
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
#include <type_traits>
#include "inbuffer.h"
#include "outbuffer.h"
#include "field.h"
#include <ostream>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Template for numeric field types
*/
template<
typename T,
char F,
typename = typename std::enable_if<std::is_arithmetic<T>::value, T>
>
class NumericField : public Field
{
private:
/**
* Field value
*/
T _value;
public:
using Type = T;
/**
* Default constructor, assign 0
*/
NumericField() : _value(0) {}
/**
* Construct numeric field from
* one of numeric types
*
* @param value field value
*/
NumericField(T value) : _value(value) {}
/**
* Parse based on incoming buffer
* @param frame
*/
NumericField(InBuffer &frame)
{
// The Microsoft Visual Studio compiler thinks that there is an issue
// with the following code, so we temporarily disable a specific warning
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
if (std::is_same<int8_t, typename std::remove_cv<T>::type>::value) _value = frame.nextInt8();
else if (std::is_same<int16_t, typename std::remove_cv<T>::type>::value) _value = frame.nextInt16();
else if (std::is_same<int32_t, typename std::remove_cv<T>::type>::value) _value = frame.nextInt32();
else if (std::is_same<int64_t, typename std::remove_cv<T>::type>::value) _value = frame.nextInt64();
else if (std::is_same<uint8_t, typename std::remove_cv<T>::type>::value) _value = frame.nextUint8();
else if (std::is_same<uint16_t, typename std::remove_cv<T>::type>::value) _value = frame.nextUint16();
else if (std::is_same<uint32_t, typename std::remove_cv<T>::type>::value) _value = frame.nextUint32();
else if (std::is_same<uint64_t, typename std::remove_cv<T>::type>::value) _value = frame.nextUint64();
else if (std::is_same<float, typename std::remove_cv<T>::type>::value) _value = frame.nextFloat();
else if (std::is_same<double, typename std::remove_cv<T>::type>::value) _value = frame.nextDouble();
// re-enable the warning
#if defined(_MSC_VER)
#pragma warning( pop )
#endif
}
/**
* Destructor
*/
virtual ~NumericField() {}
/**
* Create a new instance of this object
* @return unique_ptr
*/
virtual std::unique_ptr<Field> clone() const override
{
// create a new copy of ourselves and return it
return std::unique_ptr<Field>(new NumericField(_value));
}
/**
* Assign a new value
*
* @param value new value for field
* @return NumericField
*/
NumericField& operator=(T value)
{
_value = value;
return *this;
};
/**
* Clear the field
* @return NumericField
*/
NumericField& clear()
{
_value = 0;
return *this;
}
/**
* Get the value
* @return mixed
*/
operator uint8_t () const override { return (uint8_t)_value; }
operator uint16_t() const override { return (uint16_t)_value; }
operator uint32_t() const override { return (uint32_t)_value; }
operator uint64_t() const override { return (uint64_t)_value; }
operator int8_t () const override { return (int8_t)_value; }
operator int16_t () const override { return (int16_t)_value; }
operator int32_t () const override { return (int32_t)_value; }
operator int64_t () const override { return (int64_t)_value; }
operator float () const override { return (float)_value; }
operator double () const override { return (double)_value; }
/**
* Get the value
* @return mixed
*/
T value() const
{
// return internal value
return _value;
}
/**
* We are an integer field
*
* @return true, because we are an integer
*/
bool isInteger() const override
{
return std::is_integral<T>::value;
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
virtual size_t size() const override
{
// numeric types have no extra storage requirements
return sizeof(_value);
}
/**
* Write encoded payload to the given buffer.
* @param buffer OutBuffer to write to
*/
virtual void fill(OutBuffer& buffer) const override
{
// store converted value
T value = _value;
// write to buffer
// adding a value takes care of host to network byte order
buffer.add(value);
}
/**
* Get the type ID that is used to identify this type of
* field in a field table
*/
virtual char typeID() const override
{
return F;
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// show
stream << "numeric(" << value() << ")";
}
};
/**
* Concrete numeric types for AMQP
*/
typedef NumericField<int8_t, 'b'> Octet;
typedef NumericField<uint8_t, 'B'> UOctet;
typedef NumericField<int16_t, 'U'> Short;
typedef NumericField<uint16_t, 'u'> UShort;
typedef NumericField<int32_t, 'I'> Long;
typedef NumericField<uint32_t, 'i'> ULong;
typedef NumericField<int64_t, 'L'> LongLong;
typedef NumericField<uint64_t, 'l'> ULongLong;
typedef NumericField<uint64_t, 'T'> Timestamp;
/**
* Concrete floating-point types for AMQP
*/
typedef NumericField<float, 'f'> Float;
typedef NumericField<double, 'd'> Double;
/**
* end namespace
*/
}

View File

@ -0,0 +1,37 @@
/**
* OpenSSL.h
*
* Function to set openssl features
*
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
* @copyright 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Begin of namespace
*/
namespace AMQP {
/**
* To make secure "amqps://" connections, AMQP-CPP relies on functions from the
* openssl library. It your application is dynamically linked to openssl (because
* it was compiled with the "-lssl" flag), this works flawlessly because AMQPCPP
* can then locate the openssl symbols in its own project space. However, if the
* openssl library was not linked, you either cannot use amqps:// connections,
* or you have to supply a handle to the openssl library yourself, using the
* following method.
*
* @param handle Handle returned by dlopen() that has access to openssl
*/
void openssl(void *handle);
/**
* End of namespace
*/
}

View File

@ -0,0 +1,192 @@
/**
* OutBuffer.h
*
* This is a utility class for writing various data types to a binary
* string, and converting the values to network byte order
*
* @copyright 2014 - 2017 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
#include <cstring>
#include "endian.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class OutBuffer
{
protected:
/**
* The method that adds the actual data
* @param data
* @param size
*/
virtual void append(const void *data, size_t size) = 0;
public:
/**
* Destructor
*/
virtual ~OutBuffer()
{
}
/**
* Add a binary buffer to the buffer
* @param string char* to the string
* @param size size of string
*/
void add(const char *string, size_t size)
{
// append data
append(string, size);
}
/**
* Add a binary buffer to the buffer
* @param string char* to the string
* @param size size of string
*/
void add(const std::string &string)
{
// add data
append(string.c_str(), string.size());
}
/**
* add a uint8_t to the buffer
* @param value value to add
*/
void add(uint8_t value)
{
// append one byte
append(&value, sizeof(value));
}
/**
* add a uint16_t to the buffer
* @param value value to add
*/
void add(uint16_t value)
{
// convert to network byte order
uint16_t v = htobe16(value);
// append the data
append(&v, sizeof(v));
}
/**
* add a uint32_t to the buffer
* @param value value to add
*/
void add(uint32_t value)
{
// convert to network byte order
uint32_t v = htobe32(value);
// append the data
append(&v, sizeof(v));
}
/**
* add a uint64_t to the buffer
* @param value value to add
*/
void add(uint64_t value)
{
// convert to network byte order
uint64_t v = htobe64(value);
// append the data
append(&v, sizeof(v));
}
/**
* add a int8_t to the buffer
* @param value value to add
*/
void add(int8_t value)
{
// append the data
append(&value, sizeof(value));
}
/**
* add a int16_t to the buffer
* @param value value to add
*/
void add(int16_t value)
{
// convert to network byte order
int16_t v = htobe16(value);
// append the data
append(&v, sizeof(v));
}
/**
* add a int32_t to the buffer
* @param value value to add
*/
void add(int32_t value)
{
// convert into network byte order
int32_t v = htobe32(value);
// append the data
append(&v, sizeof(v));
}
/**
* add a int64_t to the buffer
* @param value value to add
*/
void add(int64_t value)
{
// copy into the buffer
int64_t v = htobe64(value);
// append the data
append(&v, sizeof(v));
}
/**
* add a float to the buffer
* @param value value to add
*/
void add(float value)
{
// append the data
append(&value, sizeof(value));
}
/**
* add a double to the buffer
* @param value value to add
*/
void add(double value)
{
// append the data
append(&value, sizeof(value));
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,42 @@
/**
* ProtocolException.h
*
* This exception is thrown internally in the library when invalid data is
* received from the server. The best remedy is to close the connection
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "exception.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class ProtocolException : public Exception
{
public:
/**
* Constructor
* @param what
*/
explicit ProtocolException(const std::string &what) : Exception(what) {}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,287 @@
/**
* Reliable.h
*
* A channel wrapper based on AMQP::Tagger that allows message callbacks to be installed
* on the publish-confirms, to be called when they a confirmation is received from RabbitMQ.
*
* You can also change the base class and use Reliable<Throttle> if you not only
* want to be notified about the publish-confirms, but want to use it for automatic
* throttling at the same time.
*
* @author Michael van der Werve <michael.vanderwerve@mailerq.com>
* @copyright 2020 - 2023 Copernica BV
*/
/**
* Header guard
*/
#pragma once
/**
* Includes
*/
#include "deferredpublish.h"
#include "tagger.h"
#include <memory>
/**
* Begin of namespaces
*/
namespace AMQP {
/**
* Class definition
*/
template <typename BASE=Tagger>
class Reliable : public BASE
{
private:
// make sure it is a proper channel
static_assert(std::is_base_of<Tagger, BASE>::value, "base should be derived from a confirmed channel.");
/**
* Set of open deliverytags. We want a normal set (not unordered_set) because
* removal will be cheaper for whole ranges.
* @var size_t
*/
std::map<size_t, std::shared_ptr<DeferredPublish>> _handlers;
/**
* Called when the deliverytag(s) are acked
* @param deliveryTag
* @param multiple
*/
void onAck(uint64_t deliveryTag, bool multiple) override
{
// monitor the object, watching for destruction since these ack/nack handlers
// could destruct the object
Monitor monitor(this);
// single element is simple
if (!multiple)
{
// find the element
auto iter = _handlers.find(deliveryTag);
// we did not find it (this should not be possible, unless somebody explicitly called)
// the base-class publish methods for some reason.
if (iter == _handlers.end()) return BASE::onAck(deliveryTag, multiple);
// get the handler (we store it first so that we can remove it)
auto handler = iter->second;
// erase it from the map (we remove it before the call, because the callback might update
// the _handlers and invalidate the iterator)
_handlers.erase(iter);
// call the ack handler
handler->reportAck();
}
// do multiple at once
else
{
// keep looping for as long as the object is in a valid state
while (monitor && !_handlers.empty())
{
// get the first handler
auto iter = _handlers.begin();
// make sure this is the right deliverytag, if we've passed it we leap out
if (iter->first > deliveryTag) break;
// get the handler
auto handler = iter->second;
// remove it from the map (before we make a call to userspace, so that user space
// can add even more handlers, without invalidating iterators)
_handlers.erase(iter);
// call the ack handler
handler->reportAck();
}
}
// make sure the object is still valid
if (!monitor) return;
// call base handler as well
BASE::onAck(deliveryTag, multiple);
}
/**
* Called when the deliverytag(s) are nacked
* @param deliveryTag
* @param multiple
*/
void onNack(uint64_t deliveryTag, bool multiple) override
{
// monitor the object, watching for destruction since these ack/nack handlers
// could destruct the object
Monitor monitor(this);
// single element is simple
if (!multiple)
{
// find the element
auto iter = _handlers.find(deliveryTag);
// we did not find it (this should not be possible, unless somebody explicitly called)
// the base-class publish methods for some reason.
if (iter == _handlers.end()) return BASE::onNack(deliveryTag, multiple);
// get the handler (we store it first so that we can remove it)
auto handler = iter->second;
// erase it from the map (we remove it before the call, because the callback might update
// the _handlers and invalidate the iterator)
_handlers.erase(iter);
// call the ack handler
handler->reportNack();
}
// nack multiple elements
else
{
// keep looping for as long as the object is in a valid state
while (monitor && !_handlers.empty())
{
// get the first handler
auto iter = _handlers.begin();
// make sure this is the right deliverytag, if we've passed it we leap out
if (iter->first > deliveryTag) break;
// get the handler
auto handler = iter->second;
// remove it from the map (before we make a call to userspace, so that user space
// can add even more handlers, without invalidating iterators)
_handlers.erase(iter);
// call the ack handler
handler->reportNack();
}
}
// if the object is no longer valid, return
if (!monitor) return;
// call the base handler
BASE::onNack(deliveryTag, multiple);
}
/**
* Method that is called to report an error
* @param message
*/
void reportError(const char *message) override
{
// monitor the object, watching for destruction since these ack/nack handlers
// could destruct the object
Monitor monitor(this);
// move the handlers out
auto handlers = std::move(_handlers);
// iterate over all the messages
// call the handlers
for (const auto &iter : handlers)
{
// call the handler
iter.second->reportError(message);
// if we were destructed in the meantime, we leap out
if (!monitor) return;
}
// if the monitor is no longer valid, leap out
if (!monitor) return;
// call the base handler
BASE::reportError(message);
}
public:
/**
* Constructor
* @param channel
* @param throttle
*/
template <typename ...Args>
Reliable(Args &&...args) : BASE(std::forward<Args>(args)...) {}
/**
* Deleted copy constructor, deleted move constructor
* @param other
*/
Reliable(const Reliable &other) = delete;
Reliable(Reliable &&other) = delete;
/**
* Deleted copy assignment, deleted move assignment
* @param other
*/
Reliable &operator=(const Reliable &other) = delete;
Reliable &operator=(Reliable &&other) = delete;
/**
* Virtual destructor
*/
virtual ~Reliable() = default;
/**
* Method to check how many messages are still unacked.
* @return size_t
*/
virtual size_t unacknowledged() const override { return _handlers.size(); }
/**
* Publish a message to an exchange. See amqpcpp/channel.h for more details on the flags.
* Delays actual publishing depending on the publisher confirms sent by RabbitMQ.
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
* @param flags optional flags
* @return bool
*/
DeferredPublish &publish(const std::string_view &exchange, const std::string_view &routingKey, const std::string_view &message, int flags = 0) { return publish(exchange, routingKey, Envelope(message.data(), message.size()), flags); }
DeferredPublish &publish(const std::string_view &exchange, const std::string_view &routingKey, const char *message, size_t size, int flags = 0) { return publish(exchange, routingKey, Envelope(message, size), flags); }
DeferredPublish &publish(const std::string_view &exchange, const std::string_view &routingKey, const char *message, int flags = 0) { return publish(exchange, routingKey, Envelope(message, strlen(message)), flags); }
/**
* Publish a message to an exchange. See amqpcpp/channel.h for more details on the flags.
* Delays actual publishing depending on the publisher confirms sent by RabbitMQ.
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
* @param flags optional flags
*/
DeferredPublish &publish(const std::string_view &exchange, const std::string_view &routingKey, const Envelope &envelope, int flags = 0)
{
// publish the entire thing, and remember if it failed at any point
uint64_t tag = BASE::publish(exchange, routingKey, envelope, flags);
// create the publish deferred object, if we got no tag we failed
auto handler = std::make_shared<DeferredPublish>(tag == 0);
// add it to the open handlers
_handlers[tag] = handler;
// return the dereferenced handler
return *handler;
}
};
/**
* End of namespaces
*/
}

View File

@ -0,0 +1,146 @@
/**
* stack_ptr.h
*
* Implementation of an object that behaves like a
* smart pointer but is actually managed on the stack
*
* @copyright 2016 Copernica B.V.
*/
/**
* Dependencies
*/
#include <type_traits>
#include <utility>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Stack-based smart pointer
*/
template <typename T>
class stack_ptr
{
private:
/**
* Storage for the object
* @var typename std::aligned_storage<sizeof(T), alignof(T)>::type
*/
typename std::aligned_storage<sizeof(T), alignof(T)>::type _data;
/**
* Is the pointer initialized?
* @var boolean
*/
bool _initialized = false;
public:
/**
* Constructor
*/
stack_ptr() = default;
/**
* Copy and moving is disabled
*
* @param that The stack_ptr we refuse to copy/move
*/
stack_ptr(const stack_ptr &that) = delete;
stack_ptr(stack_ptr &&that) = delete;
/**
* Destructor
*/
~stack_ptr()
{
// reset the pointer
reset();
}
/**
* Reset the pointer
*/
void reset()
{
// are we initialized?
if (!_initialized) return;
// destroy the object
reinterpret_cast<T*>(&_data)->~T();
// the object is not currently initialized
_initialized = false;
}
/**
* Construct the object
*
* @param ... Zero or more constructor arguments for T
*/
template <typename... Arguments>
void construct(Arguments&&... parameters)
{
// first reset the current object
reset();
// initialize new object
new (&_data) T(std::forward<Arguments>(parameters)...);
// we are now initialized
_initialized = true;
}
/**
* Is the object initialized?
*
* @return Are we currently managing an object?
*/
operator bool() const
{
// are we initialized with an object?
return _initialized;
}
/**
* Retrieve a pointer to the object
*
* @return Pointer to the object or nullptr if no object is managed
*/
T *get() const
{
// do we have a managed object
if (!_initialized) return nullptr;
// return pointer to the managed object
return const_cast<T*>(reinterpret_cast<const T*>(&_data));
}
/**
* Retrieve a reference to the object
*
* @return Reference to the object, undefined if no object is managed
*/
T &operator*() const
{
// dereference the pointer
return *operator->();
}
/**
* Retrieve a pointer to the object
*
* @return Pointer to the object, undefined if no object is managed
*/
T *operator->() const
{
// return pointer to the managed object
return const_cast<T*>(reinterpret_cast<const T*>(&_data));
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,309 @@
/**
* String field types for amqp
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "field.h"
#include "outbuffer.h"
#include "numericfield.h"
#include "inbuffer.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Base class for string types
*/
template <typename T, char F>
class StringField : public Field
{
private:
/**
* Pointer to string data
* @var string
*/
std::string _data;
public:
/**
* Initialize empty string
*/
StringField() {}
/**
* Construct based on a std::string
* @param value string value
*/
StringField(const std::string &value) : _data(value) {}
/**
* Construct based on a std::string
* @param value string value
*/
StringField(const std::string_view &value) : _data(value) {}
/**
* Construct based on a std::string
* @param value string value
*/
StringField(std::string &&value) : _data(std::move(value)) {}
/**
* Construct based on a buffer
* @param buffer buffer value
* @param size size of the buffer
*/
StringField(const char *buffer, size_t size) : _data(buffer, size) {}
/**
* Construct based on a c-string
* @param buffer buffer value
*/
StringField(const char *buffer) : _data(buffer) {}
/**
* Construct based on received data
* @param frame
*/
StringField(InBuffer &frame)
{
// get the size
T size(frame);
// allocate string
_data = std::string(frame.nextData(size.value()), (size_t) size.value());
}
/**
* Clean up memory used
*/
virtual ~StringField() = default;
/**
* Create a new instance of this object
* @return std::unique_ptr<Field>
*/
virtual std::unique_ptr<Field> clone() const override
{
// create a new copy of ourselves and return it
return std::unique_ptr<Field>(new StringField(_data));
}
/**
* Assign a new value
* @param value new value
*/
StringField& operator=(const std::string &value)
{
// overwrite data
_data = value;
// allow chaining
return *this;
}
/**
* Assign a new value
* @param value new value
*/
StringField& operator=(std::string &&value)
{
// overwrite data
_data = std::move(value);
// allow chaining
return *this;
}
/**
* Assign a new value
* @param value new value
*/
StringField& operator=(const char *value)
{
// overwrite data
_data.assign(value);
// allow chaining
return *this;
}
/**
* Assign a new value
* @param value
* @return StringField
*/
StringField& assign(const std::string &value)
{
// overwrite data
_data = value;
// allow chaining
return *this;
}
/**
* Assign a new value
* @param value new value
* @return StringField
*/
StringField& assign(std::string &&value)
{
// overwrite data
_data = std::move(value);
// allow chaining
return *this;
}
/**
* Assign a new value
* @param value new value
* @return StringField
*/
StringField& assign(const char *value)
{
// overwrite data
_data.assign(value);
// allow chaining
return *this;
}
/**
* Assign a new value
* @param value new value
* @return StringField
*/
StringField& assign(const char *value, size_t size)
{
// overwrite data
_data.assign(value, size);
// allow chaining
return *this;
}
/**
* Make the field empty
* @return StringField
*/
StringField &clear()
{
// clear internal dta
_data.clear();
// allow chaining
return *this;
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
virtual size_t size() const override
{
// find out size of the size parameter
T size((typename T::Type)_data.size());
// size of the uint8 or uint32 + the actual string size
return size.size() + _data.size();
}
/**
* Get the value
* @return string
*/
virtual operator const std::string& () const override
{
return _data;
}
/**
* Get the value
* @return string
*/
const std::string& value() const
{
// get data
return _data;
}
/**
* Get the maximum allowed string length for this field
* @return size_t
*/
constexpr static size_t maxLength()
{
return T::max();
}
/**
* Write encoded payload to the given buffer.
* @param buffer
*/
virtual void fill(OutBuffer& buffer) const override
{
// create size
T size((typename T::Type)_data.size());
// first, write down the size of the string
size.fill(buffer);
// write down the string content
buffer.add(_data);
}
/**
* Get the type ID that is used to identify this type of
* field in a field table
* @return char
*/
virtual char typeID() const override
{
return F;
}
/**
* We are a string
*
* @return true, because we are a string
*/
bool isString() const override
{
return true;
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// show
stream << "string(" << value() << ")";
}
};
/**
* Concrete string types for AMQP
*/
typedef StringField<UOctet, 's'> ShortString;
typedef StringField<ULong, 'S'> LongString;
/**
* end namespace
*/
}

View File

@ -0,0 +1,304 @@
/**
* AMQP field table
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "field.h"
#include "fieldproxy.h"
#include <cstddef>
#include <vector>
#include <map>
/**
* Set up namespace
*/
namespace AMQP {
/**
* AMQP field table
*/
class Table : public Field
{
private:
/**
* We define a custom type for storing fields
* @typedef FieldMap
*/
typedef std::map<std::string, std::unique_ptr<Field> > FieldMap;
/**
* Store the fields
* @var FieldMap
*/
FieldMap _fields;
public:
/**
* Constructor that creates an empty table
*/
Table() {}
/**
* Decode the data from a received frame into a table
*
* @param frame received frame to decode
*/
Table(InBuffer &frame);
/**
* Copy constructor
* @param table
*/
Table(const Table &table);
/**
* Move constructor
* @param table
*/
Table(Table &&table) : _fields(std::move(table._fields)) {}
/**
* Destructor
*/
virtual ~Table() {}
/**
* Assignment operator
* @param table
* @return Table
*/
Table &operator=(const Table &table);
/**
* Move assignment operator
* @param table
* @return Table
*/
Table &operator=(Table &&table);
/**
* Retrieve all keys in the table
*
* @return Vector with all keys in the table
*/
std::vector<std::string> keys() const;
/**
* Create a new instance on the heap of this object, identical to the object passed
* @return Field*
*/
virtual std::unique_ptr<Field> clone() const override
{
return std::unique_ptr<Table>(new Table(*this));
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
*/
virtual size_t size() const override;
/**
* Set a field
* @param name field name
* @param value field value
* @return Table
*/
Table &set(const std::string& name, const Field &value)
{
// copy to a new pointer and store it
_fields[name] = value.clone();
// allow chaining
return *this;
}
/**
* Aliases for setting values
* @param name
* @param value
* @return Table&
*/
Table &set(const std::string &name, bool value) { return set(name, BooleanSet(value)); }
Table &set(const std::string &name, uint8_t value) { return set(name, UOctet(value)); }
Table &set(const std::string &name, int8_t value) { return set(name, Octet(value)); }
Table &set(const std::string &name, uint16_t value) { return set(name, UShort(value)); }
Table &set(const std::string &name, int16_t value) { return set(name, Short(value)); }
Table &set(const std::string &name, uint32_t value) { return set(name, ULong(value)); }
Table &set(const std::string &name, int32_t value) { return set(name, Long(value)); }
Table &set(const std::string &name, uint64_t value) { return set(name, ULongLong(value)); }
Table &set(const std::string &name, int64_t value) { return set(name, LongLong(value)); }
Table &set(const std::string &name, const std::string &value) { return set(name, LongString(value)); }
Table &set(const std::string &name, const char *value) { return set(name, LongString(std::string(value))); }
Table &set(const std::string &name, std::nullptr_t) { return set(name, VoidField()); }
/**
* Clear the entire table
* @return Table
*/
Table &clear()
{
_fields.clear();
return *this;
}
/**
* Is a certain field set in the table
* @param name
* @return bool
*/
bool contains(const std::string &name) const
{
return _fields.find(name) != _fields.end();
}
/**
* Get a field
*
* If the field does not exist, an empty string field is returned
*
* @param name field name
* @return the field value
*/
const Field &get(const std::string &name) const;
/**
* Get a field
*
* @param name field name
*/
AssociativeFieldProxy operator[](const std::string& name)
{
return AssociativeFieldProxy(this, name);
}
/**
* Get a field
*
* @param name field name
*/
AssociativeFieldProxy operator[](const char *name)
{
return AssociativeFieldProxy(this, name);
}
/**
* Get a const field
*
* @param name field name
*/
const Field &operator[](const std::string& name) const
{
return get(name);
}
/**
* Get a const field
*
* @param name field name
*/
const Field &operator[](const char *name) const
{
return get(name);
}
/**
* Write encoded payload to the given buffer.
* @param buffer
*/
virtual void fill(OutBuffer& buffer) const override;
/**
* Get the type ID that is used to identify this type of
* field in a field table
*/
virtual char typeID() const override
{
return 'F';
}
/**
* We are a table
*
* @return true, because we are a table
*/
bool isTable() const override
{
return true;
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// prefix
stream << "table(";
// is this the first iteration
bool first = true;
// loop through all members
for (auto &iter : _fields)
{
// split with comma
if (!first) stream << ",";
// show output
stream << iter.first << ":" << *iter.second;
// no longer first iter
first = false;
}
// postfix
stream << ")";
}
/**
* Cast to table.
*
* @note: This function may look silly and unnecessary. We are, after all, already
* a table. The whole reason we still have this function is that it is virtual
* and if we do not declare a cast to table on a pointer to base (i.e. Field)
* will return an empty field instead of the expected table.
*
* Yes, clang gets this wrong and gives incorrect warnings here. See
* https://llvm.org/bugs/show_bug.cgi?id=28263 for more information
*
* @return Ourselves
*/
virtual operator const Table& () const override
{
// this already is a table, so no cast is necessary
return *this;
}
};
/**
* Custom output stream operator
* @param stream
* @param field
* @return ostream
*/
inline std::ostream &operator<<(std::ostream &stream, const AssociativeFieldProxy &field)
{
// get underlying field, and output that
return stream << field.get();
}
/**
* end namespace
*/
}

View File

@ -0,0 +1,153 @@
/**
* Tagger.h
*
* Base class that enables publisher confirms and keeps track of the sent
* messages. You can wrap this class around a AMQP::Channel object and use
* this object for publishing instead. This is a base class that you cannot
* use directly. You should instead use:
*
* - Throttle: to throttle traffic to prevent flooding RabbitMQ
* - Reliable<Tagger>: to be notified about publish-confirms via callbacks
* - Reliable<Throttle>: to have throttle + notifications via callbacks
*
* @author Michael van der Werve <michael.vanderwerve@mailerq.com>
* @copyright 2020 - 2023 Copernica BV
*/
/**
* Header guard
*/
#pragma once
/**
* Includes
*/
#include "deferredpublish.h"
#include <memory>
/**
* Begin of namespaces
*/
namespace AMQP {
/**
* Class definition
*/
class Tagger : public Watchable
{
protected:
/**
* The implementation for the channel
* @var std::shared_ptr<ChannelImpl>
*/
std::shared_ptr<ChannelImpl> _implementation;
/**
* Current id, always starts at 1.
* @var uint64_t
*/
uint64_t _current = 1;
/**
* Deferred to set up on the close
* @var std::shared_ptr<Deferred>
*/
std::shared_ptr<Deferred> _close;
/**
* Callback to call when an error occurred
* @var ErrorCallback
*/
ErrorCallback _errorCallback;
protected:
/**
* Send method for a frame
* @param id
* @param frame
*/
virtual bool send(uint64_t id, const Frame &frame);
/**
* Method that is called to report an error.
* @param message
*/
virtual void reportError(const char *message);
/**
* Method that gets called on ack/nack. If these methods are overridden, make sure
* to also call the base class methods.
* @param deliveryTag
* @param multiple
*/
virtual void onAck(uint64_t deliveryTag, bool multiple);
virtual void onNack(uint64_t deliveryTag, bool multiple);
public:
/**
* Constructor
* @param channel
*/
Tagger(AMQP::Channel &channel);
/**
* Deleted copy constructor, deleted move constructor
* @param other
*/
Tagger(const Tagger &other) = delete;
Tagger(Tagger &&other) = delete;
/**
* Deleted copy assignment, deleted move assignment
* @param other
*/
Tagger &operator=(const Tagger &other) = delete;
Tagger &operator=(Tagger &&other) = delete;
/**
* Virtual destructor
*/
virtual ~Tagger();
/**
* Method to check how many messages are still unacked.
* @return size_t
*/
virtual size_t unacknowledged() const { return 0; }
/**
* Publish a message to an exchange. See amqpcpp/channel.h for more details on the flags.
* Delays actual publishing depending on the publisher confirms sent by RabbitMQ.
*
* @param exchange the exchange to publish to
* @param routingkey the routing key
* @param envelope the full envelope to send
* @param message the message to send
* @param size size of the message
* @param flags optional flags
* @return uint64_t
*/
uint64_t publish(const std::string_view &exchange, const std::string_view &routingKey, const Envelope &envelope, int flags = 0);
uint64_t publish(const std::string_view &exchange, const std::string_view &routingKey, const std::string_view &message, int flags = 0) { return publish(exchange, routingKey, Envelope(message.data(), message.size()), flags); }
uint64_t publish(const std::string_view &exchange, const std::string_view &routingKey, const char *message, size_t size, int flags = 0) { return publish(exchange, routingKey, Envelope(message, size), flags); }
uint64_t publish(const std::string_view &exchange, const std::string_view &routingKey, const char *message, int flags = 0) { return publish(exchange, routingKey, Envelope(message, strlen(message)), flags); }
/**
* Close underlying channel
* @return Deferred&
*/
Deferred &close();
/**
* Install an error callback
* @param callback
*/
inline void onError(const ErrorCallback& callback) { return onError(ErrorCallback(callback)); }
void onError(ErrorCallback&& callback);
};
/**
* End of namespaces
*/
}

View File

@ -0,0 +1,149 @@
/**
* Throttle.h
*
* A channel wrapper that publishes more messages as soon as there is more capacity.
*
* @author Michael van der Werve <michael.vanderwerve@mailerq.com>
* @copyright 2020 Copernica BV
*/
/**
* Header guard
*/
#pragma once
/**
* Includes
*/
#include <cstdint>
#include <set>
#include <queue>
#include "copiedbuffer.h"
#include "channelimpl.h"
#include "tagger.h"
/**
* Begin of namespaces
*/
namespace AMQP {
/**
* Forward declarations
*/
class Channel;
/**
* Class definition
*/
class Throttle : public Tagger
{
protected:
/**
* Last sent ID
* @var uint64_t
*/
uint64_t _last = 0;
/**
* Throttle
* @var size_t
*/
size_t _throttle;
/**
* Messages that should still be sent out.
* @var queue
*/
std::queue<std::pair<uint64_t, CopiedBuffer>> _queue;
/**
* Set of open deliverytags. We want a normal set (not unordered_set) because
* removal will be cheaper for whole ranges.
* @var size_t
*/
std::set<size_t> _open;
protected:
/**
* Send method for a frame
* @param id
* @param frame
*/
virtual bool send(uint64_t id, const Frame &frame) override;
/**
* Method that is called to report an error
* @param message
*/
virtual void reportError(const char *message) override;
/**
* Method that is called to report an ack/nack
* @param deliveryTag
* @param multiple
*/
virtual void onAck(uint64_t deliveryTag, bool multiple) override;
virtual void onNack(uint64_t deliveryTag, bool multiple) override;
public:
/**
* Constructor. Warning: this takes control of the channel, there should be no extra
* handlers set on the channel (onError) and no further publishes should be done on the
* raw channel either. Doing this will cause the throttle to work incorrectly, as the
* counters are not properly updated.
* @param channel
* @param throttle
*/
Throttle(Channel &channel, size_t throttle);
/**
* Deleted copy constructor, deleted move constructor
* @param other
*/
Throttle(const Throttle &other) = delete;
Throttle(Throttle &&other) = delete;
/**
* Deleted copy assignment, deleted move assignment
* @param other
*/
Throttle &operator=(const Throttle &other) = delete;
Throttle &operator=(Throttle &&other) = delete;
/**
* Virtual destructor
*/
virtual ~Throttle() = default;
/**
* Method to check how many messages are still unacked.
* @return size_t
*/
virtual size_t unacknowledged() const override { return _open.size() + (_current - _last - 1); }
/**
* Get the throttle
* @return size_t
*/
size_t throttle() const { return _throttle; }
/**
* Set a new throttle. Note that this will only gradually take effect when set down, and
* the update is picked up on the next acknowledgement.
* @param size_t
*/
void throttle(size_t throttle) { _throttle = throttle; }
/**
* Flush the throttle. This flushes it _without_ taking the throttle into account, e.g. the messages
* are sent in a burst over the channel.
* @param max optional maximum, 0 is flush all
*/
size_t flush(size_t max = 0);
};
/**
* End of namespaces
*/
}

View File

@ -0,0 +1,103 @@
/**
* Void field type for AMQP
*
* @copyright
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <memory>
#include "outbuffer.h"
#include "field.h"
/**
* Set up namespace
*/
namespace AMQP
{
class VoidField : public Field
{
public:
/**
* Default constructor
*/
VoidField() = default;
/**
* Construct based on incoming data
* @param frame
*/
VoidField(InBuffer &frame) { (void)frame; }
/**
* Destructor
*/
virtual ~VoidField() = default;
/**
* Create a new instance of this object
* @return unique_ptr
*/
virtual std::unique_ptr<Field> clone() const override
{
// create a new copy of ourselves and return it
return std::unique_ptr<Field>(new VoidField);
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
virtual size_t size() const override
{
// numeric types have no extra storage requirements
return 0;
}
/**
* Write encoded payload to the given buffer.
* @param buffer OutBuffer to write to
*/
virtual void fill(OutBuffer &buffer) const override { (void)buffer; }
/**
* Get the type ID that is used to identify this type of
* field in a field table
*/
virtual char typeID() const override
{
return 'V';
}
/**
* Output the object to a stream
* @param std::ostream
*/
virtual void output(std::ostream &stream) const override
{
// show
stream << "void()";
}
/**
* We are an void field
*
* @return true, because we are an void
*/
bool isVoid() const override
{
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,81 @@
/**
* Watchable.h
*
* Every class that overrides from the Watchable class can be monitored for
* destruction by a Monitor object
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include <vector>
#include <algorithm>
/**
* Set up namespace
*/
namespace AMQP {
/**
* Forward declarations
*/
class Monitor;
/**
* Class definition
*/
class Watchable
{
private:
/**
* The monitors
* @var std::vector
*/
std::vector<Monitor*> _monitors;
/**
* Add a monitor
* @param monitor
*/
void add(Monitor *monitor)
{
// add to the vector
_monitors.push_back(monitor);
}
/**
* Remove a monitor
* @param monitor
*/
void remove(Monitor *monitor)
{
// put the monitor at the end of the vector
auto iter = std::remove(_monitors.begin(), _monitors.end(), monitor);
// make the vector smaller
_monitors.erase(iter, _monitors.end());
}
public:
/**
* Destructor
*/
virtual ~Watchable();
/**
* Only a monitor has full access
*/
friend class Monitor;
};
/**
* End of namespace
*/
}

1
builder/libs/AMQP-CPP/src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.d

View File

@ -0,0 +1,96 @@
add_sources(
array.cpp
basicackframe.h
basiccancelframe.h
basiccancelokframe.h
basicconsumeframe.h
basicconsumeokframe.h
basicdeliverframe.h
basicframe.h
basicgetemptyframe.h
basicgetframe.h
basicgetokframe.h
basicheaderframe.h
basicnackframe.h
basicpublishframe.h
basicqosframe.h
basicqosokframe.h
basicrecoverasyncframe.h
basicrecoverframe.h
basicrecoverokframe.h
basicrejectframe.h
basicreturnframe.h
bodyframe.h
channelcloseframe.h
channelcloseokframe.h
channelflowframe.h
channelflowokframe.h
channelframe.h
channelimpl.cpp
channelopenframe.h
channelopenokframe.h
confirmselectframe.h
confirmselectokframe.h
connectioncloseframe.h
connectioncloseokframe.h
connectionframe.h
connectionimpl.cpp
connectionopenframe.h
connectionopenokframe.h
connectionsecureframe.h
connectionsecureokframe.h
connectionstartframe.h
connectionstartokframe.h
connectiontuneframe.h
connectiontuneokframe.h
consumedmessage.h
deferredcancel.cpp
deferredconfirm.cpp
deferredconsumer.cpp
deferredreceiver.cpp
deferredextreceiver.cpp
deferredrecall.cpp
deferredget.cpp
exchangebindframe.h
exchangebindokframe.h
exchangedeclareframe.h
exchangedeclareokframe.h
exchangedeleteframe.h
exchangedeleteokframe.h
exchangeframe.h
exchangeunbindframe.h
exchangeunbindokframe.h
extframe.h
field.cpp
flags.cpp
framecheck.h
headerframe.h
heartbeatframe.h
includes.h
methodframe.h
passthroughbuffer.h
protocolheaderframe.h
queuebindframe.h
queuebindokframe.h
queuedeclareframe.h
queuedeclareokframe.h
queuedeleteframe.h
queuedeleteokframe.h
queueframe.h
queuepurgeframe.h
queuepurgeokframe.h
queueunbindframe.h
queueunbindokframe.h
receivedframe.cpp
reducedbuffer.h
returnedmessage.h
table.cpp
transactioncommitframe.h
transactioncommitokframe.h
transactionframe.h
transactionrollbackframe.h
transactionrollbackokframe.h
transactionselectframe.h
transactionselectokframe.h
watchable.cpp
)

View File

@ -0,0 +1,55 @@
CPP = g++
RM = rm -f
CPPFLAGS = -Wall -c -I../include -std=c++17 -MD -Wno-class-conversion -DVERSION=${VERSION}
LD = g++
LD_FLAGS = -Wall -shared
SHARED_LIB = lib$(LIBRARY_NAME).so.$(VERSION)
STATIC_LIB = lib$(LIBRARY_NAME).a.$(VERSION)
SOURCES = $(wildcard *.cpp) $(wildcard linux_tcp/*.cpp)
SHARED_OBJECTS = $(SOURCES:%.cpp=%.o)
STATIC_OBJECTS = $(SOURCES:%.cpp=%.s.o)
DEPENDENCIES = $(SOURCES:%.cpp=%.d)
PURE_SHARED_OBJECTS = $(filter-out tcpconnection.o, $(SOURCES:%.cpp=%.o))
PURE_STATIC_OBJECTS = $(filter-out tcpconnection.s.o, $(SOURCES:%.cpp=%.s.o))
ifeq ($(shell uname -s),Darwin)
SONAMEPARAMETER = -install_name
else
SONAMEPARAMETER = -soname
endif
-include ${DEPENDENCIES}
all: CPPFLAGS += -g
all: LD_FLAGS += -g
all: shared static
pure: CPPFLAGS += -g
pure: LD_FLAGS += -g
pure: shared_pure static_pure
release: CPPFLAGS += -O2
release: LD_FLAGS += -O2
release: shared static
shared: ${SHARED_OBJECTS} ${SHARED_LIB}
shared_pure: ${PURE_SHARED_OBJECTS} ${SHARED_LIB}
static: ${STATIC_OBJECTS} ${STATIC_LIB}
static_pure: ${PURE_STATIC_OBJECTS} ${STATIC_LIB}
${SHARED_LIB}: ${SHARED_OBJECTS}
${LD} ${LD_FLAGS} -Wl,${SONAMEPARAMETER},lib$(LIBRARY_NAME).so.$(SONAME) -o $@ ${SHARED_OBJECTS}
${STATIC_LIB}: ${STATIC_OBJECTS}
ar rcs ${STATIC_LIB} ${STATIC_OBJECTS}
clean:
${RM} *.obj *~* ${SHARED_OBJECTS} ${STATIC_OBJECTS} ${SHARED_LIB} ${STATIC_LIB} ${DEPENDENCIES}
${SHARED_OBJECTS}:
${CPP} ${CPPFLAGS} -fpic -o $@ ${@:%.o=%.cpp}
${STATIC_OBJECTS}:
${CPP} ${CPPFLAGS} -o $@ ${@:%.s.o=%.cpp}

View File

@ -0,0 +1,140 @@
/**
* Array.cpp
*
* Implementation of an array
*
*/
#include "includes.h"
// we live in the copernica namespace
namespace AMQP {
/**
* Constructor based on incoming frame
* @param frame
*/
Array::Array(InBuffer &frame)
{
// use this to see if we've read too many bytes.
uint32_t charsToRead = frame.nextUint32();
// keep going until all data is read
while (charsToRead > 0)
{
// one byte less for the field type
charsToRead -= 1;
// read the field type and construct the field
auto field = Field::decode(frame);
if (!field) continue;
// less bytes to read
charsToRead -= (uint32_t)field->size();
// add the additional field
_fields.push_back(std::move(field));
}
}
/**
* Copy constructor
* @param array
*/
Array::Array(const Array &array)
{
// loop through the other array
for (auto iter = array._fields.begin(); iter != array._fields.end(); iter++)
{
// add to this vector
_fields.push_back((*iter)->clone());
}
}
/**
* Get a field
*
* If the field does not exist, an empty string is returned
*
* @param index field index
* @return Field
*/
const Field &Array::get(uint8_t index) const
{
// used if index does not exist
static ShortString empty;
// check whether we have that many elements
if (index >= _fields.size()) return empty;
// get value
return *_fields[index];
}
/**
* Number of entries in the array
* @return uint32_t
*/
uint32_t Array::count() const
{
return (uint32_t)_fields.size();
}
/**
* Remove a field from the array
*/
void Array::pop_back()
{
_fields.pop_back();
}
/**
* Add a field to the array
* @param value
*/
void Array::push_back(const Field& value)
{
_fields.push_back(value.clone());
}
/**
* Get the size this field will take when
* encoded in the AMQP wire-frame format
* @return size_t
*/
size_t Array::size() const
{
// store the size (four bytes for the initial size)
size_t size = 4;
// iterate over all elements
for (const auto &item : _fields)
{
// add the size of the field type and size of element
size += sizeof(item->typeID());
size += item->size();
}
// return the result
return size;
}
/**
* Write encoded payload to the given buffer.
* @param buffer
*/
void Array::fill(OutBuffer& buffer) const
{
// store total size for all elements
buffer.add(static_cast<uint32_t>(size()-4));
// iterate over all elements
for (const auto &item : _fields)
{
// encode the element type and element
buffer.add((uint8_t)item->typeID());
item->fill(buffer);
}
}
// end namespace
}

View File

@ -0,0 +1,145 @@
/**
* Class describing a basic acknowledgement frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class defintion
*/
class BasicAckFrame : public BasicFrame {
private:
/**
* server-assigned and channel specific delivery tag
* @var uint64_t
*/
uint64_t _deliveryTag;
/**
* if set, tag is treated as "up to and including", so client can acknowledge multiple messages with a single method
* if not set, refers to single message
* @var BooleanSet
*/
BooleanSet _multiple;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
* @return pointer to object to allow for chaining
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// add the delivery tag
buffer.add(_deliveryTag);
// add the booleans
_multiple.fill(buffer);
}
public:
/**
* Construct a basic acknowledgement frame
*
* @param channel Channel identifier
* @param deliveryTag server-assigned and channel specific delivery tag
* @param multiple acknowledge mutiple messages
*/
BasicAckFrame(uint16_t channel, uint64_t deliveryTag, bool multiple = false) :
BasicFrame(channel, 9),
_deliveryTag(deliveryTag),
_multiple(multiple) {}
/**
* Construct based on received frame
* @param frame
*/
BasicAckFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deliveryTag(frame.nextUint64()),
_multiple(frame) {}
/**
* Destructor
*/
virtual ~BasicAckFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 80;
}
/**
* Return the server-assigned and channel specific delivery tag
* @return uint64_t
*/
uint64_t deliveryTag() const
{
return _deliveryTag;
}
/**
* Return whether to acknowledge multiple messages
* @return bool
*/
bool multiple() const
{
return _multiple.get(0);
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// get the current confirm
auto confirm = channel->confirm();
// if there is no deferred confirm, we can just as well stop
if (confirm == nullptr) return false;
// process the frame
confirm->process(*this);
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,135 @@
/**
* Class describing a basic cancel frame
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicCancelFrame : public BasicFrame
{
private:
/**
* Holds the consumer tag specified by the client or provided by the server.
* @var ShortString
*/
ShortString _consumerTag;
/**
* whether to wait for a response
* @var BooleanSet
*/
BooleanSet _noWait;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// add consumer tag + booleans
_consumerTag.fill(buffer);
_noWait.fill(buffer);
}
public:
/**
* Construct a basic cancel frame from a received frame
*
* @param frame received frame to parse
*/
BasicCancelFrame(ReceivedFrame &frame) : BasicFrame(frame), _consumerTag(frame), _noWait(frame) {}
/**
* Construct a basic cancel frame
*
* @param channel Channel identifier
* @param consumerTag consumertag specified by client of provided by server
* @param noWait whether to wait for a response.
*/
BasicCancelFrame(uint16_t channel, const std::string_view &consumerTag, bool noWait = false) :
BasicFrame(channel, (uint32_t)(consumerTag.size() + 2)), // 1 for extra string size, 1 for bool
_consumerTag(consumerTag),
_noWait(noWait) {}
/**
* Destructor
*/
virtual ~BasicCancelFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous when the nowait option is not used
return !noWait();
}
/**
* Return the consumertag, which is specified by the client or provided by the server
* @return string
*/
const std::string& consumerTag() const
{
return _consumerTag;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 30;
}
/**
* Return whether to wait for a response
* @return boolean
*/
bool noWait() const
{
return _noWait.get(0);
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report
channel->reportCancelled(consumerTag());
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,108 @@
/**
* Class describing a basic cancel ok frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicCancelOKFrame : public BasicFrame
{
private:
/**
* Holds the consumer tag specified by the client or provided by the server.
* @var ShortString
*/
ShortString _consumerTag;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// add own information
_consumerTag.fill(buffer);
}
public:
/**
* Construct a basic cancel ok frame
*
* @param frame received frame
*/
BasicCancelOKFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_consumerTag(frame)
{}
/**
* Construct a basic cancel ok frame (client-side)
* @param channel channel identifier
* @param consumerTag holds the consumertag specified by client or server
*/
BasicCancelOKFrame(uint16_t channel, std::string& consumerTag) :
BasicFrame(channel, (uint32_t)(consumerTag.length() + 1)), // add 1 byte for encoding the size of consumer tag
_consumerTag(consumerTag)
{}
/**
* Destructor
*/
virtual ~BasicCancelOKFrame() {}
/**
* Return the consumertag, which is specified by the client or provided by the server
* @return string
*/
const std::string& consumerTag() const
{
return _consumerTag;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 31;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report
channel->reportSuccess<const std::string&>(consumerTag());
// done
return true;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,203 @@
/**
* Class describing a basic consume frame
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicConsumeFrame : public BasicFrame
{
private:
/**
* Field that is no longer used
* @var uint16_t
*/
uint16_t _deprecated = 0;
/**
* specifies the name of the queue to consume from
* @var Table
*/
ShortString _queueName;
/**
* specifies the identifier for the consumer tag.
* This tag is local to a channel so two clients can use the same consumer tags.
* If empty, the server generates a tag.
* @var ShortString
*/
ShortString _consumerTag;
/**
* Booleans sent in frame
* 0: noLocal
* 1: noAck
* 2: exclusive
* 3: noWait
* @var BooleanSet
*/
BooleanSet _bools;
/**
* additional arguments, implementation dependent
* @var Table
*/
Table _filter;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// fill the buffer
buffer.add((uint16_t) _deprecated);
_queueName.fill(buffer);
_consumerTag.fill(buffer);
_bools.fill(buffer);
_filter.fill(buffer);
}
public:
/**
* Construct a basic consume frame
*
* @param channel channel we're working on
* @param queueName name of the queue to consume from
* @param consumerTag identifier for the consumer tag.
* @param noLocal no-local
* @param noAck no acknowledgements
* @param exclusive exclusive channel
* @param noWait don't wait for a response
* @param filter additional arguments
*/
BasicConsumeFrame(uint16_t channel, const std::string_view &queueName, const std::string_view &consumerTag, bool noLocal = false, bool noAck = false, bool exclusive = false, bool noWait = false, const Table& filter = {}) :
BasicFrame(channel, (uint32_t)(queueName.length() + consumerTag.length() + 5 + filter.size())), // size of vars, +1 for each shortstring size, +1 for bools, +2 for deprecated value
_queueName(queueName),
_consumerTag(consumerTag),
_bools(noLocal, noAck, exclusive, noWait),
_filter(filter)
{}
/**
* Constructor based on incoming data
* @param frame
*/
BasicConsumeFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deprecated(frame.nextUint16()), // read deprecated info
_queueName(frame),
_consumerTag(frame),
_bools(frame),
_filter(frame)
{}
/**
* Destructor
*/
virtual ~BasicConsumeFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
// we are synchronous when the nowait option is not set
return !noWait();
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 20;
}
/**
* Return the name of the queue to consume from
* @return string
*/
const std::string& queueName() const
{
return _queueName;
}
/**
* return the identifier for the consumertag
* @return string
*/
const std::string& consumerTag() const
{
return _consumerTag;
}
/**
* return the value of the noLocal bool
* @return bool
*/
bool noLocal() const
{
return _bools.get(0);
}
/**
* return the value of the noAck bool
* @return bool
*/
bool noAck() const
{
return _bools.get(1);
}
/**
* return whether the queue is exclusive
* @return bool
*/
bool exclusive() const
{
return _bools.get(2);
}
/**
* return whether to wait for a response
* @return bool
*/
bool noWait() const
{
return _bools.get(3);
}
/**
* return the additional filter arguments
* @return Table
*/
const Table& filter() const
{
return _filter;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,108 @@
/**
* Class describing a basic consume ok frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicConsumeOKFrame : public BasicFrame
{
private:
/**
* Holds the consumer tag specified by the client or provided by the server.
* @var ShortString
*/
ShortString _consumerTag;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// add payload
_consumerTag.fill(buffer);
}
public:
/**
* Construct a basic consume frame
*
* @param consumerTag consumertag specified by client of provided by server
*/
BasicConsumeOKFrame(uint16_t channel, const std::string& consumerTag) :
BasicFrame(channel, (uint32_t)(consumerTag.length() + 1)), // length of string + 1 for encoding of stringsize
_consumerTag(consumerTag)
{}
/**
* Construct a basic consume ok frame from a received frame
*
* @param frame received frame
*/
BasicConsumeOKFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_consumerTag(frame)
{}
/**
* Destructor
*/
virtual ~BasicConsumeOKFrame() {}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 21;
}
/**
* Return the consumertag, which is specified by the client or provided by the server
* @return std::string
*/
const std::string& consumerTag() const
{
return _consumerTag;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report
channel->reportSuccess(consumerTag());
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,215 @@
/**
* Class describing a basic deliver frame
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "basicframe.h"
#include "amqpcpp/stringfield.h"
#include "amqpcpp/booleanset.h"
#include "amqpcpp/connectionimpl.h"
#include "amqpcpp/deferredconsumer.h"
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicDeliverFrame : public BasicFrame
{
private:
/**
* identifier for the consumer, valid within current channel
* @var ShortString
*/
ShortString _consumerTag;
/**
* server-assigned and channel specific delivery tag
* @var uint64_t
*/
uint64_t _deliveryTag;
/**
* indicates whether the message has been previously delivered to this (or another) client
* @var BooleanSet
*/
BooleanSet _redelivered;
/**
* the name of the exchange to publish to. An empty exchange name means the default exchange.
* @var ShortString
*/
ShortString _exchange;
/**
* Message routing key
* @var ShortString
*/
ShortString _routingKey;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
BasicFrame::fill(buffer);
_consumerTag.fill(buffer);
buffer.add(_deliveryTag);
_redelivered.fill(buffer);
_exchange.fill(buffer);
_routingKey.fill(buffer);
}
public:
/**
* Construct a basic deliver frame (client side)
*
* @param channel channel we're working on
* @param consumerTag identifier for the consumer, valid within current channel
* @param deliveryTag server-assigned and channel specific delivery tag
* @param redelivered indicates whether the message has been previously delivered to this (or another) client
* @param exchange name of exchange to publish to
* @param routingKey message routing key
*/
BasicDeliverFrame(uint16_t channel, const std::string& consumerTag, uint64_t deliveryTag, bool redelivered = false, const std::string& exchange = "", const std::string& routingKey = "") :
BasicFrame(channel, (uint32_t)(consumerTag.length() + exchange.length() + routingKey.length() + 12)),
// length of strings + 1 byte per string for stringsize, 8 bytes for uint64_t and 1 for bools
_consumerTag(consumerTag),
_deliveryTag(deliveryTag),
_redelivered(redelivered),
_exchange(exchange),
_routingKey(routingKey)
{}
/**
* Construct a basic deliver frame from a received frame
*
* @param frame received frame
*/
BasicDeliverFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_consumerTag(frame),
_deliveryTag(frame.nextUint64()),
_redelivered(frame),
_exchange(frame),
_routingKey(frame)
{}
/**
* Destructor
*/
virtual ~BasicDeliverFrame() {}
/**
* Return the name of the exchange to publish to
* @return string
*/
const std::string& exchange() const
{
return _exchange;
}
/**
* Return the routing key
* @return string
*/
const std::string& routingKey() const
{
return _routingKey;
}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 60;
}
/**
* Return the server-assigned and channel specific delivery tag
* @return uint64_t
*/
uint64_t deliveryTag() const
{
return _deliveryTag;
}
/**
* Return the identifier for the consumer (channel specific)
* @return string
*/
const std::string& consumerTag() const
{
return _consumerTag;
}
/**
* Return whether the message has been previously delivered to (another) client
* @return bool
*/
bool redelivered() const
{
return _redelivered.get(0);
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// get the appropriate consumer object
auto consumer = channel->consumer(_consumerTag);
// skip if there was no consumer for this tag
if (consumer == nullptr) return false;
// initialize the object, because we're about to receive a message
consumer->process(*this);
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,62 @@
/**
* Class describing an AMQP basic frame
*
* @copyright 2014 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "methodframe.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class implementation
*/
class BasicFrame : public MethodFrame
{
protected:
/**
* Constructor
* @param channel The channel ID
* @param size Payload size
*/
BasicFrame(uint16_t channel, uint32_t size) : MethodFrame(channel, size) {}
/**
* Constructor based on a received frame
* @param frame
*/
BasicFrame(ReceivedFrame &frame) : MethodFrame(frame) {}
public:
/**
* Destructor
*/
virtual ~BasicFrame() {}
/**
* Class id
* @return uint16_t
*/
virtual uint16_t classID() const override
{
return 60;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,99 @@
/**
* Class describing a basic get empty frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class implementation
*/
class BasicGetEmptyFrame : public BasicFrame
{
private:
/**
* Field that is no longer used
* @var ShortString
*/
ShortString _deprecated;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// recreate deprecated field and encode
_deprecated.fill(buffer);
}
public:
/**
* Construct a basic get empty frame
*
* @param channel channel we're working on
*/
BasicGetEmptyFrame(uint16_t channel) :
BasicFrame(channel, 1) // 1 for encoding the deprecated cluster id (shortstring)
{}
/**
* Constructor for incoming data
* @param frame received frame
*/
BasicGetEmptyFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deprecated(frame)
{}
/**
* Destructor
*/
virtual ~BasicGetEmptyFrame() {}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 72;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report
channel->reportSuccess();
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,116 @@
/**
* Class describing a basic get frame
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicGetFrame : public BasicFrame
{
private:
/**
* Deprecated field
* @var uint16_t
*/
uint16_t _deprecated = 0;
/**
* name of the queue to get a message from
* @var ShortString
*/
ShortString _queue;
/**
* if set, server does not expect acknowledgement for messages. Server dequeues message after sending
* @var BooleanSet
*/
BooleanSet _noAck;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// encode other values
buffer.add(_deprecated);
_queue.fill(buffer);
_noAck.fill(buffer);
}
public:
/**
* Construct a basic get frame
*
* @param channel channel we're working on
* @param queue name of the queue
* @param noAck whether server expects acknowledgements for messages
*/
BasicGetFrame(uint16_t channel, const std::string_view &queue, bool noAck = false) :
BasicFrame(channel, (uint32_t)(queue.length() + 4)), // 1 for bool, 1 for string size, 2 for deprecated field
_queue(queue),
_noAck(noAck)
{}
/**
* Constructor based on incoming frame
* @param frame
*/
BasicGetFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deprecated(frame.nextUint16()),
_queue(frame),
_noAck(frame)
{}
/**
* Destructor
*/
virtual ~BasicGetFrame() {}
/**
* Return the name of the queue
* @return string
*/
const std::string& queue() const
{
return _queue;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 70;
}
/**
* Return whether the server expects acknowledgements for messages
* @return boolean
*/
bool noAck() const
{
return _noAck.get(0);
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,194 @@
/**
* Class describing a basic get ok frame
*
* @copyright 2014 - 2018 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicGetOKFrame : public BasicFrame
{
private:
/**
* server-assigned and channel specific delivery tag
* @var uint64_t
*/
uint64_t _deliveryTag;
/**
* indicates whether the message has been previously delivered to this (or another) client
* @var BooleanSet
*/
BooleanSet _redelivered;
/**
* the name of the exchange to publish to. An empty exchange name means the default exchange.
* @var ShortString
*/
ShortString _exchange;
/**
* Message routing key
* @var ShortString
*/
ShortString _routingKey;
/**
* number of messages in the queue
* @var uint32_t
*/
uint32_t _messageCount;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// encode rest of the fields
buffer.add(_deliveryTag);
_redelivered.fill(buffer);
_exchange.fill(buffer);
_routingKey.fill(buffer);
buffer.add(_messageCount);
}
public:
/**
* Construct a basic get ok frame
*
* @param channel channel we're working on
* @param deliveryTag server-assigned and channel specific delivery tag
* @param redelivered indicates whether the message has been previously delivered to this (or another) client
* @param exchange name of exchange to publish to
* @param routingKey message routing key
* @param messageCount number of messages in the queue
*/
BasicGetOKFrame(uint16_t channel, uint64_t deliveryTag, bool redelivered, const std::string& exchange, const std::string& routingKey, uint32_t messageCount) :
BasicFrame(channel, (uint32_t)(exchange.length() + routingKey.length() + 15)), // string length, +1 for each shortsrting length + 8 (uint64_t) + 4 (uint32_t) + 1 (bool)
_deliveryTag(deliveryTag),
_redelivered(redelivered),
_exchange(exchange),
_routingKey(routingKey),
_messageCount(messageCount)
{}
/**
* Construct a basic get ok frame from a received frame
*
* @param frame received frame
*/
BasicGetOKFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deliveryTag(frame.nextUint64()),
_redelivered(frame),
_exchange(frame),
_routingKey(frame),
_messageCount(frame.nextUint32())
{}
/**
* Destructor
*/
virtual ~BasicGetOKFrame() {}
/**
* Return the name of the exchange to publish to
* @return string
*/
const std::string& exchange() const
{
return _exchange;
}
/**
* Return the routing key
* @return string
*/
const std::string& routingKey() const
{
return _routingKey;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 71;
}
/**
* Return the server-assigned and channel specific delivery tag
* @return uint64_t
*/
uint64_t deliveryTag() const
{
return _deliveryTag;
}
/**
* Return the number of messages in the queue
* @return uint32_t
*/
uint32_t messageCount() const
{
return _messageCount;
}
/**
* Return whether the message has been previously delivered to (another) client
* @return bool
*/
bool redelivered() const
{
return _redelivered.get(0);
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report success for the get operation (this will also update the current receiver!)
channel->reportSuccess(messageCount(), _deliveryTag, redelivered());
// get the current receiver object
auto *receiver = channel->receiver();
// check if we have a valid receiver
if (receiver == nullptr) return false;
// initialize the receiver for the upcoming message
receiver->initialize(_exchange, _routingKey);
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,168 @@
/**
* Class describing an AMQP basic header frame
*
* @copyright 2014 - 2020 Copernica BV
*/
/**
* Include guard
*/
#pragma once
/**
* Dependencies
*/
#include "headerframe.h"
#include "amqpcpp/metadata.h"
#include "amqpcpp/envelope.h"
#include "amqpcpp/connectionimpl.h"
#include "amqpcpp/deferredreceiver.h"
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class implementation
*/
class BasicHeaderFrame : public HeaderFrame
{
private:
/**
* Weight field, unused but must be sent, always value 0;
* @var uint16_t
*/
uint16_t _weight = 0;
/**
* Body size, sum of the sizes of all body frames following the content header
* @var uint64_t
*/
uint64_t _bodySize;
/**
* The meta data
* @var MetaData
*/
MetaData _metadata;
protected:
/**
* Encode a header frame to a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer &buffer) const override
{
// call base
HeaderFrame::fill(buffer);
// fill own fields.
buffer.add(_weight);
buffer.add(_bodySize);
// the meta data
_metadata.fill(buffer);
}
/**
* Construct an empty basic header frame
* @param channel channel we're working on
* @param metadata the meta-data
* @param bodysize size of the body
*/
BasicHeaderFrame(uint16_t channel, const MetaData &metadata, size_t bodysize) :
HeaderFrame(channel, 10 + metadata.size()), // there are at least 10 bytes sent, weight (2), bodySize (8), plus the size of the meta data
_bodySize(bodysize),
_metadata(metadata)
{}
public:
/**
* Construct an empty basic header frame
*
* All options are set using setter functions.
*
* @param channel channel we're working on
* @param envelope the envelope
*/
BasicHeaderFrame(uint16_t channel, const Envelope &envelope) :
BasicHeaderFrame(channel, envelope, envelope.bodySize()) {}
/**
* Constructor to parse incoming frame
* @param frame
*/
BasicHeaderFrame(ReceivedFrame &frame) :
HeaderFrame(frame),
_weight(frame.nextUint16()),
_bodySize(frame.nextUint64()),
_metadata(frame)
{}
/**
* Destructor
*/
virtual ~BasicHeaderFrame() = default;
/**
* Size of the body
* @return uint64_t
*/
uint64_t bodySize() const
{
return _bodySize;
}
/**
* The metadata sent in this frame
*
* @return All the metadata for this message
*/
const MetaData &metaData() const
{
return _metadata;
}
/**
* The class ID
* @return uint16_t
*/
virtual uint16_t classID() const override
{
return 60;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// we need a channel
if (channel == nullptr) return false;
// do we have an object that is receiving this data?
auto *receiver = channel->receiver();
// check if we have a valid channel and consumer
if (receiver == nullptr) return false;
// the channel can process the frame
receiver->process(*this);
// done
return true;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,143 @@
/**
* Class describing a basic negative-acknowledgement frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class defintion
*/
class BasicNackFrame : public BasicFrame {
private:
/**
* server-assigned and channel specific delivery tag
* @var uint64_t
*/
uint64_t _deliveryTag;
/**
* The additional bits
* @var BooleanSet
*/
BooleanSet _bits;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
* @return pointer to object to allow for chaining
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// add the delivery tag
buffer.add(_deliveryTag);
// add the booleans
_bits.fill(buffer);
}
public:
/**
* Construct a basic negative-acknowledgement frame
*
* @param channel Channel identifier
* @param deliveryTag server-assigned and channel specific delivery tag
* @param multiple nack mutiple messages
* @param requeue requeue the message
*/
BasicNackFrame(uint16_t channel, uint64_t deliveryTag, bool multiple = false, bool requeue = false) :
BasicFrame(channel, 9),
_deliveryTag(deliveryTag),
_bits(multiple, requeue) {}
/**
* Construct based on received frame
* @param frame
*/
BasicNackFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deliveryTag(frame.nextUint64()),
_bits(frame) {}
/**
* Destructor
*/
virtual ~BasicNackFrame() {}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 120;
}
/**
* Return the server-assigned and channel specific delivery tag
* @return uint64_t
*/
uint64_t deliveryTag() const
{
return _deliveryTag;
}
/**
* Return whether to acknowledgement multiple messages
* @return bool
*/
bool multiple() const
{
return _bits.get(0);
}
/**
* Should the message be put back in the queue?
* @return bool
*/
bool requeue() const
{
return _bits.get(1);
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if(!channel) return false;
// get the current confirm
auto confirm = channel->confirm();
// if there is no deferred confirm, we can just as well stop
if (confirm == nullptr) return false;
// process the frame
confirm->process(*this);
// done
return true;
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,158 @@
/**
* Class describing a basic publish frame
*
* @copyright 2014 - 2023 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP{
/**
* Class implementation
*/
class BasicPublishFrame : public BasicFrame
{
private:
/**
* Variable that no longer is in use
* @var int16_t
*/
int16_t _deprecated = 0;
/**
* the name of the exchange to publish to. An empty exchange name means the default exchange.
* @var ShortString
*/
ShortString _exchange;
/**
* Message routing key
* @var ShortString
*/
ShortString _routingKey;
/**
* BooleanSet, contains:
* 0: mandatory, indicate mandatory routing. If set, server will return unroutable message, otherwise server silently drops message
* 1: immediate, Request immediate delivery, if set and cannot be routed, return unroutable message.
* @var BooleanSet
*/
BooleanSet _bools;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// encode fields
buffer.add(_deprecated);
_exchange.fill(buffer);
_routingKey.fill(buffer);
_bools.fill(buffer);
}
public:
/**
* Construct a basic publish frame
*
* @param channel channel we're working on
* @param exchange name of exchange to publish to @default = ""
* @param routingKey message routing key @default = ""
* @param mandatory indicate mandatory routing @default = false
* @param immediate request immediate delivery @default = false
*/
BasicPublishFrame(uint16_t channel, const std::string_view &exchange = "", const std::string_view &routingKey = "", bool mandatory = false, bool immediate = false) :
BasicFrame(channel, (uint32_t)(exchange.length() + routingKey.length() + 5)), // 1 extra per string (for the size), 1 for bools, 2 for deprecated field
_exchange(exchange),
_routingKey(routingKey),
_bools(mandatory, immediate)
{}
/**
* Construct a basic publish frame from a received frame
*
* @param frame received frame to parse
*/
BasicPublishFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_deprecated(frame.nextInt16()),
_exchange(frame),
_routingKey(frame),
_bools(frame)
{}
/**
* Destructor
*/
virtual ~BasicPublishFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
return false;
}
/**
* Return the name of the exchange to publish to
* @return string
*/
const std::string& exchange() const
{
return _exchange;
}
/**
* Return the routing key
* @return string
*/
const std::string& routingKey() const
{
return _routingKey;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 40;
}
/**
* Return the mandatory routing indication
* @return boolean
*/
bool mandatory() const
{
return _bools.get(0);
}
/**
* Return the request immediate delivery indication
* @return boolean
*/
bool immediate() const
{
return _bools.get(1);
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,125 @@
/**
* Class describing a basic QOS frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class implementation
*/
class BasicQosFrame : public BasicFrame
{
private:
/**
* specifies the size of the prefetch window in octets
* @var int32_t
*/
int32_t _prefetchSize;
/**
* specifies a prefetch window in terms of whole messages
* @var int16_t
*/
int16_t _prefetchCount;
/**
* apply QoS settings to entire connection
* @var BooleanSet
*/
BooleanSet _global;
protected:
/**
* Encode a frame on a string buffer
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// add fields
buffer.add(_prefetchSize);
buffer.add(_prefetchCount);
_global.fill(buffer);
}
public:
/**
* Construct a basic qos frame
*
* @param channel channel we're working on
* @param prefetchCount specifies a prefetch window in terms of whole messages
* @param global share prefetch count with all consumers on the same channel
* @default false
*/
BasicQosFrame(uint16_t channel, int16_t prefetchCount = 0, bool global = false) :
BasicFrame(channel, 7), // 4 (int32) + 2 (int16) + 1 (bool)
_prefetchSize(0),
_prefetchCount(prefetchCount),
_global(global)
{}
/**
* Constructor based on incoming frame
* @param frame
*/
BasicQosFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_prefetchSize(frame.nextInt32()),
_prefetchCount(frame.nextInt16()),
_global(frame)
{}
/**
* Destructor
*/
virtual ~BasicQosFrame() {}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 10;
}
/**
* Return the prefetch count
* @return int16_t
*/
int16_t prefetchCount() const
{
return _prefetchCount;
}
/**
* returns the value of global
* @return boolean
*/
bool global() const
{
return _global.get(0);
}
/**
* returns the prefetch size
* @return int32_t
*/
int32_t prefetchSize() const
{
return _prefetchSize;
}
};
/**
* End namespace
*/
}

View File

@ -0,0 +1,81 @@
/**
* Class describing a basic QOS frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class implementation
*/
class BasicQosOKFrame : public BasicFrame
{
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base, then done (no other params)
BasicFrame::fill(buffer);
}
public:
/**
* Construct a basic qos ok frame
* @param channel channel we're working on
*/
BasicQosOKFrame(uint16_t channel) : BasicFrame(channel, 0) {}
/**
* Constructor based on incoming data
* @param frame
*/
BasicQosOKFrame(ReceivedFrame &frame) : BasicFrame(frame) {}
/**
* Destructor
*/
virtual ~BasicQosOKFrame() {}
/**
* Return the method id
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 11;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report
channel->reportSuccess();
// done
return true;
}
};
/**
* End of namespace
*/
}

View File

@ -0,0 +1,99 @@
/**
* Class describing a basic recover-async frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class BasicRecoverAsyncFrame : public BasicFrame {
private:
/**
* Server will try to requeue messages. If requeue is false or requeue attempt fails, messages are discarded or dead-lettered
* @var BooleanSet
*/
BooleanSet _requeue;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// encode fields
_requeue.fill(buffer);
}
public:
/**
* Construct a basic recover async frame from a received frame
*
* @param frame received frame
*/
BasicRecoverAsyncFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_requeue(frame)
{}
/**
* Construct a basic recover-async frame
*
* @param channel channel we're working on
* @param requeue whether to attempt to requeue messages
*/
BasicRecoverAsyncFrame(uint16_t channel, bool requeue = false) :
BasicFrame(channel, 1), //sizeof bool
_requeue(requeue)
{}
/**
* Destructor
*/
virtual ~BasicRecoverAsyncFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
virtual bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 100;
}
/**
* Return whether the server will try to requeue
* @return bool
*/
bool requeue() const
{
return _requeue.get(0);
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,100 @@
/**
* Class describing a basic recover frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class BasicRecoverFrame : public BasicFrame {
private:
/**
* Server will try to requeue messages. If requeue is false or requeue attempt fails, messages are discarded or dead-lettered
* @var BooleanSet
*/
BooleanSet _requeue;
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base
BasicFrame::fill(buffer);
// encode fields
_requeue.fill(buffer);
}
public:
/**
* Construct a basic recover frame from a received frame
*
* @param frame received frame
*/
BasicRecoverFrame(ReceivedFrame &frame) :
BasicFrame(frame),
_requeue(frame)
{}
/**
* Construct a basic recover frame
*
* @param channel channel ID
* @param requeue whether to attempt to requeue messages
*/
BasicRecoverFrame(uint16_t channel, bool requeue = false) :
BasicFrame(channel, 1), //sizeof bool
_requeue(requeue)
{}
/**
* Destructor
*/
virtual ~BasicRecoverFrame() {}
/**
* Is this a synchronous frame?
*
* After a synchronous frame no more frames may be
* sent until the accompanying -ok frame arrives
*/
bool synchronous() const override
{
return false;
}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 110;
}
/**
* Return whether the server will try to requeue
* @return bool
*/
bool requeue() const
{
return _requeue.get(0);
}
};
/**
* end namespace
*/
}

View File

@ -0,0 +1,87 @@
/**
* Class describing a basic recover-async frame
*
* @copyright 2014 Copernica BV
*/
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
*/
class BasicRecoverOKFrame : public BasicFrame {
protected:
/**
* Encode a frame on a string buffer
*
* @param buffer buffer to write frame to
*/
virtual void fill(OutBuffer& buffer) const override
{
// call base then done, no other fields to encode
BasicFrame::fill(buffer);
}
public:
/**
* Construct a basic recover ok frame from a received frame
*
* @param frame received frame
*/
BasicRecoverOKFrame(ReceivedFrame &frame) :
BasicFrame(frame)
{}
/**
* Construct a basic recover ok frame
*
* @param channel channel id
*/
BasicRecoverOKFrame(uint16_t channel) :
BasicFrame(channel, 0)
{}
/**
* Destructor
*/
virtual ~BasicRecoverOKFrame() {}
/**
* Return the method ID
* @return uint16_t
*/
virtual uint16_t methodID() const override
{
return 111;
}
/**
* Process the frame
* @param connection The connection over which it was received
* @return bool Was it succesfully processed?
*/
virtual bool process(ConnectionImpl *connection) override
{
// we need the appropriate channel
auto channel = connection->channel(this->channel());
// channel does not exist
if (!channel) return false;
// report
channel->reportSuccess();
// done
return true;
}
};
/**
* End of namespace
*/
}

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