From a091921e88034e4a762b231c95666e4be614f075 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 16 Jun 2017 10:14:42 +0100 Subject: [PATCH 001/168] added connection::heartbeat() method, userspace programs are responsible for calling this method once every 60 seconds --- include/channelimpl.h | 2 +- include/connection.h | 18 +++++++++--------- include/connectionhandler.h | 4 ++++ include/connectionimpl.h | 23 +++++++---------------- include/tcphandler.h | 4 +--- src/channelimpl.cpp | 1 - src/connectionimpl.cpp | 13 ++++++++++++- src/heartbeatframe.h | 3 --- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/include/channelimpl.h b/include/channelimpl.h index 7ddf1e3..1ba6130 100644 --- a/include/channelimpl.h +++ b/include/channelimpl.h @@ -508,7 +508,7 @@ public: * @return bool */ bool reject(uint64_t deliveryTag, int flags); - + /** * Recover messages that were not yet ack'ed * @param flags optional flags diff --git a/include/connection.h b/include/connection.h index 18215f4..e7e1021 100644 --- a/include/connection.h +++ b/include/connection.h @@ -94,6 +94,15 @@ public: return _implementation.vhost(); } + /** + * Send a ping/heartbeat to the channel to keep it alive + * @return bool + */ + bool ping() + { + return _implementation.ping(); + } + /** * Parse data that was recevied from RabbitMQ * @@ -195,15 +204,6 @@ public: return _implementation.waiting(); } - /** - * Retrieve the heartbeat delay used by this connection - * @return uint16_t - */ - uint16_t heartbeat() const - { - return _implementation.heartbeat(); - } - /** * Some classes have access to private properties */ diff --git a/include/connectionhandler.h b/include/connectionhandler.h index e46fa8d..d2c4786 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -51,6 +51,10 @@ public: * alternative heartbeat interval, you can override this method * to use an other interval. You should return 0 if you want to * disable heartbeats. + * + * If heartbeats are enabled, you yourself are responsible to send + * out a heartbeat every *interval* 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 diff --git a/include/connectionimpl.h b/include/connectionimpl.h index aaa8efa..b01486f 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -124,12 +124,6 @@ protected: */ std::queue _queue; - /** - * Heartbeat delay - * @var uint16_t - */ - uint16_t _heartbeat = 0; - /** * Helper method to send the close frame * Return value tells if the connection is still valid @@ -413,15 +407,6 @@ public: return _channels.size(); } - /** - * Heartbeat delay - * @return uint16_t - */ - uint16_t heartbeat() const - { - return _heartbeat; - } - /** * Set the heartbeat delay * @param heartbeat suggested heartbeat by server @@ -430,7 +415,7 @@ public: uint16_t setHeartbeat(uint16_t heartbeat) { // pass to the handler - return _heartbeat = _handler->onNegotiate(_parent, heartbeat); + return _handler->onNegotiate(_parent, heartbeat); } /** @@ -441,6 +426,12 @@ public: // 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 diff --git a/include/tcphandler.h b/include/tcphandler.h index 7c86a4a..5549425 100644 --- a/include/tcphandler.h +++ b/include/tcphandler.h @@ -33,7 +33,7 @@ public: /** * Destructor */ - virtual ~TcpHandler() {} + virtual ~TcpHandler() = default; /** * Method that is called when the heartbeat frequency is negotiated @@ -96,8 +96,6 @@ public: * @param flags Should the object be monitored for readability or writability? */ virtual void monitor(TcpConnection *connection, int fd, int flags) = 0; - - }; /** diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 1ddf54f..73471ab 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -39,7 +39,6 @@ #include "basicrejectframe.h" #include "basicgetframe.h" - /** * Set up namespace */ diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index ca81a1f..4be9464 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -3,7 +3,7 @@ * * Implementation of an AMQP connection * - * @copyright 2014 - 2016 Copernica BV + * @copyright 2014 - 2017 Copernica BV */ #include "includes.h" #include "protocolheaderframe.h" @@ -11,6 +11,7 @@ #include "connectioncloseframe.h" #include "reducedbuffer.h" #include "passthroughbuffer.h" +#include "heartbeatframe.h" /** * set namespace @@ -374,6 +375,16 @@ bool ConnectionImpl::send(CopiedBuffer &&buffer) return true; } +/** + * Send a ping / heartbeat frame to keep the connection alive + * @return bool + */ +bool ConnectionImpl::heartbeat() +{ + // send a frame + return send(HeartbeatFrame()); +} + /** * End of namspace */ diff --git a/src/heartbeatframe.h b/src/heartbeatframe.h index 955c57d..cd4320b 100644 --- a/src/heartbeatframe.h +++ b/src/heartbeatframe.h @@ -59,9 +59,6 @@ public: { // notify the connection-handler connection->reportHeartbeat(); - - // send back the same frame - connection->send(*this); // done return true; From 90d99464744766bd3e58f676e89ce9ad646f6025 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 16 Jun 2017 10:36:34 +0100 Subject: [PATCH 002/168] implemented heartbeats for libev --- include/connection.h | 4 +- include/libev.h | 108 +++++++++++++++++++++++++++++++++++++++- include/tcpconnection.h | 9 ++++ 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/include/connection.h b/include/connection.h index e7e1021..5fc1389 100644 --- a/include/connection.h +++ b/include/connection.h @@ -98,9 +98,9 @@ public: * Send a ping/heartbeat to the channel to keep it alive * @return bool */ - bool ping() + bool heartbeat() { - return _implementation.ping(); + return _implementation.heartbeat(); } /** diff --git a/include/libev.h b/include/libev.h index d489657..21577a4 100644 --- a/include/libev.h +++ b/include/libev.h @@ -119,6 +119,84 @@ private: } }; + /** + * Timer class to periodically fire a heartbeat + */ + class Timer + { + 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_timer _timer; + + /** + * 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 connection + TcpConnection *connection = static_cast(timer->data); + + // send the heartbeat + connection->heartbeat(); + } + + public: + /** + * Constructor + * @param loop The current event loop + */ + Timer(struct ev_loop *loop) : _loop(loop) + { + // initialize the libev structure + ev_timer_init(&_timer, callback, 60.0, 60.0); + } + + /** + * Watchers cannot be copied or moved + * + * @param that The object to not move or copy + */ + Timer(Watcher &&that) = delete; + Timer(const Watcher &that) = delete; + + /** + * Destructor + */ + virtual ~Timer() + { + // stop the timer + ev_timer_stop(_loop, &_timer); + } + + /** + * Change the expire time + * @param connection + * @param timeout + */ + void set(TcpConnection *connection, uint16_t timeout) + { + // store the connection in the data "void*" + _timer.data = connection; + + // set the timer + ev_timer_set(&_timer, timeout, timeout); + + // and start it + ev_timer_start(_loop, &_timer); + } + }; /** * The event loop @@ -131,6 +209,12 @@ private: * @var std::map */ std::map> _watchers; + + /** + * A timer to periodically send out heartbeats + * @var Timer + */ + Timer _timer; /** @@ -165,12 +249,34 @@ private: } } +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 + { + // call base to find out the timeout that the client wants + interval = TcpHandler::onNegotiate(connection, interval); + + // skip if base does not want a timeout + if (interval == 0) return 0; + + // set the timer + _timer.set(connection, interval); + + // done + return interval; + } + public: /** * Constructor * @param loop The event loop to wrap */ - LibEvHandler(struct ev_loop *loop) : _loop(loop) {} + LibEvHandler(struct ev_loop *loop) : _loop(loop), _timer(loop) {} /** * Destructor diff --git a/include/tcpconnection.h b/include/tcpconnection.h index 632063d..5441eb5 100644 --- a/include/tcpconnection.h +++ b/include/tcpconnection.h @@ -176,6 +176,15 @@ public: // return the amount of channels this connection has return _connection.channels(); } + + /** + * Send a heartbeat + * @return bool + */ + bool heartbeat() + { + return _connection.heartbeat(); + } }; /** From fda26f20f4c701a36553792f4fcbb176a222e5fc Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 16 Jun 2017 11:17:13 +0100 Subject: [PATCH 003/168] update the readme, heartbeats are now disabled by default, only the libev implementation has enabled them, resolves #137 --- README.md | 60 +++++++++++++++++++++++++++++++++++++ include/connectionhandler.h | 4 +-- include/libev.h | 7 ++--- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b324354..28b1564 100644 --- a/README.md +++ b/README.md @@ -434,6 +434,66 @@ libevent. For other event loops (like libuv and boost asio) we do not yet have such examples. +HEARTBEATS +========== + +The AMQP protocol supports *heartbeats*. If this heartbeat feature is enabled, the +client and the server negotiate a heartbeat interval during connection setup, and +they agree to send at least *some kind of data* over the connection during every +iteration of that interval. The normal data that is sent over the connection (like +publishing or consuming messages) is normally sufficient to keep the connection alive, +but if the client or server was idle during the negotiated interval time, a dummy +heartbeat message must be sent instead. + +The default behavior of the AMQP-CPP library is to disable heartbeats. The +proposed heartbeat interval of the server during connection setup (the server +normally suggests an interval of 60 seconds) is vetoed by the AMQP-CPP library so +no heartbeats are ever needed to be sent over the connection. This means that you +can safely keep your AMQP connection idle for as long as you like, and/or run long +lasting algorithms after you've consumed a message from RabbitMQ, without having +to worry about the connection being idle for too long. + +You can however choose to enable these heartbeats. If you want to enable heartbeats, +simple implement the onNegotiate() method inside your ConnectionHandler or +TcpHandler class and have it return the interval that you find appropriate. + +````c++ +#include + +class MyTcpHandler : public AMQP::TcpHandler +{ + /** + * Method that is called when the server tries to negotiate a heartbeat + * interval, and that is overridden to get rid of the default implementation + * (which vetoes the suggested heartbeat interval), and accept the interval + * instead. + * @param connection The connection on which the error occured + * @param interval The suggested interval in seconds + */ + virtual void onNegotiate(AMQP::TcpConnection *connection, uint16_t interval) + { + // we accept the suggestion from the server, but if the interval is smaller + // that one minute, we will use a one minute interval instead + if (interval < 60) interval = 60; + + // @todo + // set a timer in your event loop, and make sure that you call + // connection->heartbeat() every _interval_ seconds. + + // return the interval that we want to use + return interval; + } +}; +```` + +If you have enabled heartbeats, it is your own responsibility to ensure that the +```connection->heartbeat()``` method is called at least once during this period, +or that you call one of the other channel or connection methods to send data +over the connection. + +In the libev event loop implementation the heartbeats are enabled by default. + + CHANNELS ======== diff --git a/include/connectionhandler.h b/include/connectionhandler.h index d2c4786..c68af2a 100644 --- a/include/connectionhandler.h +++ b/include/connectionhandler.h @@ -62,8 +62,8 @@ public: */ virtual uint16_t onNegotiate(Connection *connection, uint16_t interval) { - // default implementation, suggested heartbeat is ok - return interval; + // default implementation, disable heartbeats + return 0; } /** diff --git a/include/libev.h b/include/libev.h index 21577a4..886f41d 100644 --- a/include/libev.h +++ b/include/libev.h @@ -258,16 +258,13 @@ protected: */ virtual uint16_t onNegotiate(TcpConnection *connection, uint16_t interval) override { - // call base to find out the timeout that the client wants - interval = TcpHandler::onNegotiate(connection, interval); - - // skip if base does not want a timeout + // skip if no heartbeats are needed if (interval == 0) return 0; // set the timer _timer.set(connection, interval); - // done + // we agree with the interval return interval; } From 49337d078d2064550321343734245b82d1914ca0 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 19 Jun 2017 10:24:03 +0200 Subject: [PATCH 004/168] heartbeats will now only be sent if the connection is idle --- include/connectionimpl.h | 10 +++++++++- src/connectionimpl.cpp | 21 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/include/connectionimpl.h b/include/connectionimpl.h index b01486f..3c805a9 100644 --- a/include/connectionimpl.h +++ b/include/connectionimpl.h @@ -123,6 +123,12 @@ protected: * @var queue */ std::queue _queue; + + /** + * Is the connection idle (meaning: a heartbeat is necessary) + * @var bool + */ + bool _idle = true; /** * Helper method to send the close frame @@ -429,9 +435,11 @@ public: /** * Send a heartbeat to keep the connection alive + * By default, this function does nothing if the connection is not in an idle state + * @param force always send the heartbeat, even if the connection is not idle * @return bool */ - bool heartbeat(); + bool heartbeat(bool force=false); /** * The actual connection is a friend and can construct this class diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 4be9464..f2bd13c 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -268,6 +268,9 @@ void ConnectionImpl::setConnected() { // get the next message const auto &buffer = _queue.front(); + + // data is going to be sent, thus connection is not idle + _idle = false; // send it _handler->onData(_parent, buffer.data(), buffer.size()); @@ -336,6 +339,9 @@ bool ConnectionImpl::send(const Frame &frame) // are we still setting up the connection? if ((_state == state_connected && _queue.empty()) || frame.partOfHandshake()) { + // the data will be sent, so connection is not idle + _idle = false; + // we need an output buffer (this will immediately send the data) PassthroughBuffer buffer(_parent, _handler, frame); } @@ -362,6 +368,9 @@ bool ConnectionImpl::send(CopiedBuffer &&buffer) // are we waiting for other frames to be sent before us? if (_queue.empty()) { + // data will be sent, thus connection is not empty + _idle = false; + // send it directly _handler->onData(_parent, buffer.data(), buffer.size()); } @@ -377,12 +386,20 @@ bool ConnectionImpl::send(CopiedBuffer &&buffer) /** * Send a ping / heartbeat frame to keep the connection alive + * @param force also send heartbeat if connection is not idle * @return bool */ -bool ConnectionImpl::heartbeat() +bool ConnectionImpl::heartbeat(bool force) { + // do nothing if the connection is not idle (but we do reset the idle state to ensure + // that the next heartbeat will be sent if nothing is going to change from now on) + if (!force && !_idle) return _idle = true; + // send a frame - return send(HeartbeatFrame()); + if (!send(HeartbeatFrame())) return false; + + // frame has been sent, we treat the connection as idle now until some other data is sent over it + return _idle = true; } /** From acd6698db3211440b763651bcdacf14241fd865b Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 21 Jun 2017 09:35:48 +0200 Subject: [PATCH 005/168] the libev timer no longer takes ownership over the event loop --- include/libev.h | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/libev.h b/include/libev.h index 886f41d..9e6ea1a 100644 --- a/include/libev.h +++ b/include/libev.h @@ -151,6 +151,24 @@ private: // send the heartbeat connection->heartbeat(); } + + /** + * Stop the timer + */ + void stop() + { + // do nothing if it was never set + if (_timer.data == nullptr) return; + + // restore loop refcount + ev_ref(_loop); + + // stop the timer + ev_timer_stop(_loop, &_timer); + + // restore data nullptr to indicate that timer is not set + _timer.data = nullptr; + } public: /** @@ -159,6 +177,9 @@ private: */ Timer(struct ev_loop *loop) : _loop(loop) { + // there is no data yet + _timer.data = nullptr; + // initialize the libev structure ev_timer_init(&_timer, callback, 60.0, 60.0); } @@ -177,7 +198,7 @@ private: virtual ~Timer() { // stop the timer - ev_timer_stop(_loop, &_timer); + stop(); } /** @@ -187,6 +208,9 @@ private: */ void set(TcpConnection *connection, uint16_t timeout) { + // stop timer in case it was already set + stop(); + // store the connection in the data "void*" _timer.data = connection; @@ -195,6 +219,9 @@ private: // and start it ev_timer_start(_loop, &_timer); + + // the timer should not keep the event loop active + ev_unref(_loop); } }; From 27ac6aeea5f9e730aec83ce3d61d8a4f485b02e5 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 21 Jun 2017 09:44:52 +0200 Subject: [PATCH 006/168] added operator<< to write a amqp address to a stream --- include/address.h | 24 ++++++++++++++++++++++++ include/login.h | 12 ++++++++++++ 2 files changed, 36 insertions(+) diff --git a/include/address.h b/include/address.h index 2117995..c2e9f21 100644 --- a/include/address.h +++ b/include/address.h @@ -236,6 +236,30 @@ public: // the opposite of operator== return !operator==(that); } + + /** + * 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 << "amqp://" << address._login; + + // do we need a special portnumber? + if (address._port != 5672) stream << ":" << address._port; + + // append default vhost + stream << "/"; + + // do we have a special vhost? + if (address._vhost != "/") stream << address._vhost; + + // done + return stream; + } }; /** diff --git a/include/login.h b/include/login.h index f68f1aa..d4599d9 100644 --- a/include/login.h +++ b/include/login.h @@ -120,6 +120,18 @@ public: return !operator==(that); } + /** + * 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; + } + }; /** From e1f92ec2cccb18ae859204e219da14c0e56f98ff Mon Sep 17 00:00:00 2001 From: Rafal Goslawski Date: Wed, 21 Jun 2017 11:13:01 +0200 Subject: [PATCH 007/168] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d3575b0..8ec3e81 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ INCLUDE_DIR = ${PREFIX}/include LIBRARY_DIR = ${PREFIX}/lib export LIBRARY_NAME = amqpcpp export SONAME = 2.7 -export VERSION = 2.7.1 +export VERSION = 2.7.4 all: $(MAKE) -C src all From 1e03b64cb495f3e95fcb791d7bf93dbde9e83122 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 13 Jul 2017 14:48:37 +0200 Subject: [PATCH 008/168] renamed project to amqpcpp for cmake (fixes #140) --- CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a902dde..3c73267 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 2.8) -project(amqp-cpp) +project(amqpcpp) # ensure c++11 on all compilers include(set_cxx_norm.cmake) @@ -27,14 +27,14 @@ add_subdirectory(include) option(BUILD_SHARED "build shared library" OFF) if(BUILD_SHARED) - add_library(amqp-cpp SHARED ${SRCS}) - set_target_properties(amqp-cpp PROPERTIES SOVERSION 2.7) - install(TARGETS amqp-cpp + add_library(amqpcpp SHARED ${SRCS}) + set_target_properties(amqpcpp PROPERTIES SOVERSION 2.7) + install(TARGETS amqpcpp LIBRARY DESTINATION lib ) else() - add_library(amqp-cpp STATIC ${SRCS}) - install(TARGETS amqp-cpp + add_library(amqpcpp STATIC ${SRCS}) + install(TARGETS amqpcpp ARCHIVE DESTINATION lib ) endif() From 204b68401ed56779c9d84202bdd85a21b2cf3035 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 12 Oct 2017 11:22:47 +0100 Subject: [PATCH 009/168] Added boost asio TCP handler for use on POSIX-based systems --- include/libboostasio.hpp | 261 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 include/libboostasio.hpp diff --git a/include/libboostasio.hpp b/include/libboostasio.hpp new file mode 100644 index 0000000..dc815ec --- /dev/null +++ b/include/libboostasio.hpp @@ -0,0 +1,261 @@ +/** + * LibBoostAsio.h + * + * Implementation for the AMQP::TcpHandler that is optimized 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 + * + * @author Gavin Smith + */ + +/** + * Include guard + */ +#pragma once + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * Class definition + * @note Because of a limitation with boost::asio on Windows, this will only work on POSIX based systems - see https://github.com/chriskohlhoff/asio/issues/70 + */ +class LibBoostAsioHandler : public virtual TcpHandler +{ +private: + + /** + * Helper class that wraps a boost io_service socket monitor. + */ + class Watcher + { + private: + + /** + * The boost asio io_service which is responsible for detecting events. + * @var class boost::asio::io_service& + */ + boost::asio::io_service & _ioservice; + + /** + * 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; + + + /** + * 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 is monitoring for write events. + * @var _read True if writes are being monitored else false. + */ + bool _write{false}; + + /** + * Handler method that is called by boost's io_service when the socket pumps a read event. + * @param ec The status of the callback. + * @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(boost::system::error_code ec, TcpConnection *connection, int fd) + { + if (!ec && _read) + { + connection->process(fd, AMQP::readable); + + _socket.async_read_some(boost::asio::null_buffers(), + boost::bind(&Watcher::read_handler, + this, + boost::placeholders::_1, + connection, + fd)); + } + } + + /** + * Handler method that is called by boost's io_service when the socket pumps a write event. + * @param ec The status of the callback. + * @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(boost::system::error_code ec, TcpConnection *connection, int fd) + { + if (!ec && _write) + { + connection->process(fd, AMQP::writable); + + _socket.async_write_some(boost::asio::null_buffers(), + boost::bind(&Watcher::write_handler, + this, + boost::placeholders::_1, + connection, + fd)); + } + } + + public: + /** + * Constructor + * @param io_service The boost io_service + * @param connection The connection being watched + * @param fd The filedescriptor being watched + * @param events The events that should be monitored + */ + Watcher(boost::asio::io_service &io_service, TcpConnection *connection, int fd, int events) : + _ioservice(io_service), + _socket(_ioservice) + { + _socket.assign(fd); + + _socket.non_blocking(true); + + // configure monitoring + this->events(connection,fd,events); + } + + /** + * 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() + { + _socket.release(); + } + + /** + * Change the events for which the filedescriptor is monitored + * @param events + */ + void events(TcpConnection *connection, int fd, int events) + { + bool bRead(_read); + bool bWrite(_write); + + // 1. Handle reads? + _read = ((events & AMQP::readable) != 0); + + if (!bRead && _read) + { + _socket.async_read_some(boost::asio::null_buffers(), + boost::bind(&Watcher::read_handler, + this, + boost::placeholders::_1, + connection, + fd)); + } + + // 2. Handle writes? + _write = ((events & AMQP::writable) != 0); + + if (!bWrite && _write) + { + _socket.async_write_some(boost::asio::null_buffers(), + boost::bind(&Watcher::write_handler, + this, + boost::placeholders::_1, + connection, + fd)); + } + } + }; + + /** + * The boost asio io_service. + * @var class boost::asio::io_service& + */ + boost::asio::io_service & _ioservice; + + + /** + * All I/O watchers that are active, indexed by their filedescriptor + * @var std::map + */ + std::map > _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 *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 pair (watcher/timer), and put it in the map + _watchers[fd] = std::make_unique(_ioservice, 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); + } + } + + +public: + + /** + * Handler cannot be default constructed. + * + * @param that The object to not move or copy + */ + LibBoostAsioHandler() = delete; + + /** + * Constructor + * @param loop The boost io_service to wrap + */ + explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service) {} + + /** + * Handler cannot be copied or moved + * + * @param that The object to not move or copy + */ + LibBoostAsioHandler(LibBoostAsioHandler &&that) = delete; + LibBoostAsioHandler(const LibBoostAsioHandler &that) = delete; + + + /** + * Destructor + */ + ~LibBoostAsioHandler() override = default; +}; + + +/** + * End of namespace + */ +} + From e9cb9fbe92b2b37a99b305d960c87e687cbad8cd Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 12 Oct 2017 11:41:41 +0100 Subject: [PATCH 010/168] Renamed libboostasio.hpp to libboostasio.h. Added header to copied files as part of install. --- include/CMakeLists.txt | 1 + include/{libboostasio.hpp => libboostasio.h} | 0 2 files changed, 1 insertion(+) rename include/{libboostasio.hpp => libboostasio.h} (100%) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 455d1e6..140f8f2 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -30,6 +30,7 @@ field.h fieldproxy.h flags.h frame.h +libboostasio.h libev.h libevent.h libuv.h diff --git a/include/libboostasio.hpp b/include/libboostasio.h similarity index 100% rename from include/libboostasio.hpp rename to include/libboostasio.h From d1b2139af05b215550386dc9792b51130f384190 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 12 Oct 2017 17:11:38 +0100 Subject: [PATCH 011/168] Cleaned up the handler upon conducting some additional testing. --- include/libboostasio.h | 121 ++++++++++++++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 21 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index dc815ec..897beb3 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -13,6 +13,14 @@ */ #pragma once +/** + * Dependencies + */ +#include +#include +#include + + /** * Set up namespace */ @@ -20,7 +28,7 @@ namespace AMQP { /** * Class definition - * @note Because of a limitation with boost::asio on Windows, this will only work on POSIX based systems - see https://github.com/chriskohlhoff/asio/issues/70 + * @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 { @@ -29,7 +37,7 @@ private: /** * Helper class that wraps a boost io_service socket monitor. */ - class Watcher + class Watcher : public virtual std::enable_shared_from_this { private: @@ -51,31 +59,62 @@ private: * 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}; + 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}; + 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}; /** * Handler method that is called by boost's io_service when the socket pumps a read event. * @param ec The status of the callback. + * @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(boost::system::error_code ec, TcpConnection *connection, int fd) + void read_handler(boost::system::error_code ec, + std::weak_ptr awpWatcher, + TcpConnection *connection, + int fd) { - if (!ec && _read) + // Resolve any potential problems with dangling pointers + // (remember we are using async). + const std::shared_ptr 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(), boost::bind(&Watcher::read_handler, this, boost::placeholders::_1, +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L + weak_from_this() +#else + shared_from_this(), +#endif connection, fd)); } @@ -84,20 +123,39 @@ private: /** * Handler method that is called by boost's io_service when the socket pumps a write event. * @param ec The status of the callback. + * @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(boost::system::error_code ec, TcpConnection *connection, int fd) + void write_handler(boost::system::error_code ec, + std::weak_ptr awpWatcher, + TcpConnection *connection, + int fd) { - if (!ec && _write) + // Resolve any potential problems with dangling pointers + // (remember we are using async). + const std::shared_ptr 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(), boost::bind(&Watcher::write_handler, this, boost::placeholders::_1, +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L + weak_from_this() +#else + shared_from_this(), +#endif connection, fd)); } @@ -105,13 +163,12 @@ private: public: /** - * Constructor + * Constructor- initialises the watcher and assigns the filedescriptor to + * a boost socket for monitoring. * @param io_service The boost io_service - * @param connection The connection being watched * @param fd The filedescriptor being watched - * @param events The events that should be monitored */ - Watcher(boost::asio::io_service &io_service, TcpConnection *connection, int fd, int events) : + Watcher(boost::asio::io_service &io_service, int fd) : _ioservice(io_service), _socket(_ioservice) { @@ -119,8 +176,6 @@ private: _socket.non_blocking(true); - // configure monitoring - this->events(connection,fd,events); } /** @@ -136,6 +191,8 @@ private: */ ~Watcher() { + _read = false; + _write = false; _socket.release(); } @@ -145,18 +202,23 @@ private: */ void events(TcpConnection *connection, int fd, int events) { - bool bRead(_read); - bool bWrite(_write); - // 1. Handle reads? _read = ((events & AMQP::readable) != 0); - if (!bRead && _read) + if (_read && !_read_pending) { + _read_pending = true; + _socket.async_read_some(boost::asio::null_buffers(), boost::bind(&Watcher::read_handler, this, boost::placeholders::_1, +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L + weak_from_this() +#else + shared_from_this(), +#endif connection, fd)); } @@ -164,12 +226,20 @@ private: // 2. Handle writes? _write = ((events & AMQP::writable) != 0); - if (!bWrite && _write) + if (_write && !_write_pending) { + _write_pending = true; + _socket.async_write_some(boost::asio::null_buffers(), boost::bind(&Watcher::write_handler, this, boost::placeholders::_1, +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L + weak_from_this() +#else + shared_from_this(), +#endif connection, fd)); } @@ -187,7 +257,7 @@ private: * All I/O watchers that are active, indexed by their filedescriptor * @var std::map */ - std::map > _watchers; + std::map > _watchers; /** @@ -208,7 +278,8 @@ private: if (flags == 0){ return; } // construct a new pair (watcher/timer), and put it in the map - _watchers[fd] = std::make_unique(_ioservice, connection, fd, flags); + _watchers[fd] = std::make_shared(_ioservice, fd); + _watchers[fd]->events(connection, fd, flags); } else if (flags == 0) { @@ -246,6 +317,14 @@ public: LibBoostAsioHandler(LibBoostAsioHandler &&that) = delete; LibBoostAsioHandler(const LibBoostAsioHandler &that) = delete; + /** + * Returns a reference to the boost io_service object that is being used. + * @return The boost io_service object. + */ + boost::asio::io_service &service() + { + return _ioservice; + } /** * Destructor From e3e0c6da198235ab7cac2276a997b7738aee81e2 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Fri, 13 Oct 2017 09:26:32 +0100 Subject: [PATCH 012/168] Added timer boost asio event handler to perform health checks. --- include/libboostasio.h | 137 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 3 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 897beb3..b8e0758 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -92,7 +92,7 @@ private: TcpConnection *connection, int fd) { - // Resolve any potential problems with dangling pointers + // Resolve any potential problems with dangling pointers // (remember we are using async). const std::shared_ptr apWatcher = awpWatcher.lock(); if (!apWatcher) { return; } @@ -133,7 +133,7 @@ private: TcpConnection *connection, int fd) { - // Resolve any potential problems with dangling pointers + // Resolve any potential problems with dangling pointers // (remember we are using async). const std::shared_ptr apWatcher = awpWatcher.lock(); if (!apWatcher) { return; } @@ -246,6 +246,111 @@ private: } }; + /** + * Timer class to periodically fire a heartbeat + */ + class Timer : public std::enable_shared_from_this + { + private: + + /** + * The boost asio io_service which is responsible for detecting events. + * @var class boost::asio::io_service& + */ + boost::asio::io_service & _ioservice; + + /** + * The boost asynchronous deadline timer. + * @var class boost::asio::deadline_timer + */ + boost::asio::deadline_timer _timer; + + /** + * 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 + */ + void timeout(const boost::system::error_code &ec, + std::weak_ptr awpThis, + TcpConnection *connection, + uint16_t timeout) + { + // Resolve any potential problems with dangling pointers + // (remember we are using async). + const std::shared_ptr apTimer = awpThis.lock(); + if (!apTimer) { return; } + + if (!ec) + { + if (connection) + { + // send the heartbeat + connection->heartbeat(); + } + + // Reschedule the timer for 1 second in the future: + _timer.expires_at(_timer.expires_at() + boost::posix_time::seconds(timeout)); + + // Posts the timer event + _timer.async_wait(boost::bind(&Timer::timeout,this,boost::placeholders::_1, shared_from_this(), connection, timeout)); + } + } + + /** + * Stop the timer + */ + void stop() + { + // do nothing if it was never set + _timer.cancel(); + } + + public: + /** + * Constructor + * @param loop The current event loop + */ + Timer(boost::asio::io_service &io_service) : + _ioservice(io_service), + _timer(_ioservice) + { + + } + + /** + * Timers cannot be copied or moved + * + * @param that The object to not move or copy + */ + Timer(Timer &&that) = delete; + Timer(const Timer &that) = delete; + + /** + * Destructor + */ + ~Timer() + { + // stop the timer + stop(); + } + + /** + * Change the expire time + * @param connection + * @param timeout + */ + void set(TcpConnection *connection, uint16_t timeout) + { + // stop timer in case it was already set + stop(); + + + _timer.expires_from_now(boost::posix_time::seconds(timeout)); + _timer.async_wait(boost::bind(&Timer::timeout,this,boost::placeholders::_1, shared_from_this(),connection, timeout)); + } + }; + /** * The boost asio io_service. * @var class boost::asio::io_service& @@ -260,6 +365,9 @@ private: std::map > _watchers; + std::shared_ptr _timer; + + /** * Method that is called by AMQP-CPP to register a filedescriptor for readability or writability * @param connection The TCP connection object that is reporting @@ -293,6 +401,24 @@ private: } } +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; + + // set the timer + _timer->set(connection, interval); + + // we agree with the interval + return interval; + } public: @@ -307,7 +433,12 @@ public: * Constructor * @param loop The boost io_service to wrap */ - explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service) {} + explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : + _ioservice(io_service), + _timer(std::make_shared(_ioservice)) + { + + } /** * Handler cannot be copied or moved From 12ffe885c1216b0cba04a3a32c055bc3ce16dfd1 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 19 Oct 2017 14:44:29 +0100 Subject: [PATCH 013/168] Cleaned up existing boost handler. There were a few bits that needed tidying up. --- include/libboostasio.h | 84 ++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index b8e0758..9e90215 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -19,8 +19,19 @@ #include #include #include +#include +// boost::function< void(boost::system::error_code ec) > f = +#define STRAND_HANDLER(_fn) \ +[this, fn = _fn, strand = _strand](const boost::system::error_code ec) \ +{ \ + const std::shared_ptr apStrand = strand.lock(); \ + if (!apStrand) { return; } \ + \ + apStrand->dispatch(boost::bind(fn,ec)); \ +} + /** * Set up namespace */ @@ -87,10 +98,10 @@ private: * @param fd The file descriptor being watched. * @note The handler will get called if a read is cancelled. */ - void read_handler(boost::system::error_code ec, - std::weak_ptr awpWatcher, - TcpConnection *connection, - int fd) + void read_handler(const boost::system::error_code ec, + const std::weak_ptr awpWatcher, + TcpConnection *const connection, + const int fd) { // Resolve any potential problems with dangling pointers // (remember we are using async). @@ -128,10 +139,10 @@ private: * @param fd The file descriptor being watched. * @note The handler will get called if a write is cancelled. */ - void write_handler(boost::system::error_code ec, - std::weak_ptr awpWatcher, - TcpConnection *connection, - int fd) + void write_handler(const boost::system::error_code ec, + const std::weak_ptr awpWatcher, + TcpConnection *const connection, + const int fd) { // Resolve any potential problems with dangling pointers // (remember we are using async). @@ -168,14 +179,13 @@ private: * @param io_service The boost io_service * @param fd The filedescriptor being watched */ - Watcher(boost::asio::io_service &io_service, int fd) : + Watcher(boost::asio::io_service &io_service, const int fd) : _ioservice(io_service), _socket(_ioservice) { _socket.assign(fd); _socket.non_blocking(true); - } /** @@ -205,6 +215,7 @@ private: // 1. Handle reads? _read = ((events & AMQP::readable) != 0); + // Read requsted but no read pending? if (_read && !_read_pending) { _read_pending = true; @@ -226,6 +237,7 @@ private: // 2. Handle writes? _write = ((events & AMQP::writable) != 0); + // Write requested but no write pending? if (_write && !_write_pending) { _write_pending = true; @@ -247,8 +259,8 @@ private: }; /** - * Timer class to periodically fire a heartbeat - */ + * Timer class to periodically fire a heartbeat + */ class Timer : public std::enable_shared_from_this { private: @@ -258,7 +270,7 @@ private: * @var class boost::asio::io_service& */ boost::asio::io_service & _ioservice; - + /** * The boost asynchronous deadline timer. * @var class boost::asio::deadline_timer @@ -273,8 +285,8 @@ private: */ void timeout(const boost::system::error_code &ec, std::weak_ptr awpThis, - TcpConnection *connection, - uint16_t timeout) + TcpConnection *const connection, + const uint16_t timeout) { // Resolve any potential problems with dangling pointers // (remember we are using async). @@ -289,11 +301,21 @@ private: connection->heartbeat(); } - // Reschedule the timer for 1 second in the future: + // Reschedule the timer for the future: _timer.expires_at(_timer.expires_at() + boost::posix_time::seconds(timeout)); // Posts the timer event - _timer.async_wait(boost::bind(&Timer::timeout,this,boost::placeholders::_1, shared_from_this(), connection, timeout)); + _timer.async_wait(boost::bind(&Timer::timeout, + this, + boost::placeholders::_1, +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L + weak_from_this() +#else + shared_from_this(), +#endif + connection, + timeout)); } } @@ -345,9 +367,18 @@ private: // stop timer in case it was already set stop(); - _timer.expires_from_now(boost::posix_time::seconds(timeout)); - _timer.async_wait(boost::bind(&Timer::timeout,this,boost::placeholders::_1, shared_from_this(),connection, timeout)); + _timer.async_wait(boost::bind(&Timer::timeout, + this, + boost::placeholders::_1, +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L + weak_from_this() +#else + shared_from_this(), +#endif + connection, + timeout)); } }; @@ -374,7 +405,9 @@ private: * @param fd The filedescriptor to be monitored * @param flags Should the object be monitored for readability or writability? */ - void monitor(TcpConnection *connection, int fd, int flags) override + void monitor(TcpConnection *const connection, + const int fd, + const int flags) override { // do we already have this filedescriptor auto iter = _watchers.find(fd); @@ -386,8 +419,13 @@ private: if (flags == 0){ return; } // construct a new pair (watcher/timer), and put it in the map - _watchers[fd] = std::make_shared(_ioservice, fd); - _watchers[fd]->events(connection, fd, flags); + const std::shared_ptr apWatcher = + std::make_shared(_ioservice, fd); + + _watchers[fd] = apWatcher; + + // explicitly set the events to monitor + apWatcher->events(connection, fd, flags); } else if (flags == 0) { @@ -431,7 +469,7 @@ public: /** * Constructor - * @param loop The boost io_service to wrap + * @param io_service The boost io_service to wrap */ explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service), From a9daf7e7699b598a61d89c15cb8d90cf31792e85 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 19 Oct 2017 15:59:40 +0100 Subject: [PATCH 014/168] Added strand to LibBoostAsioHandler.h to ensure that 1 thread goes through the 'gate' (from the io_service set of threads) at any one time. --- include/libboostasio.h | 163 +++++++++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 53 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 9e90215..5ed6489 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -16,18 +16,39 @@ /** * Dependencies */ +#include + #include #include #include #include -// boost::function< void(boost::system::error_code ec) > f = -#define STRAND_HANDLER(_fn) \ -[this, fn = _fn, strand = _strand](const boost::system::error_code ec) \ +/////////////////////////////////////////////////////////////////// +#define STRAND_SOCKET_HANDLER(_fn) \ +[fn = _fn, strand = _strand](const boost::system::error_code &ec, \ + const std::size_t bytes_transferred) \ { \ const std::shared_ptr apStrand = strand.lock(); \ - if (!apStrand) { return; } \ + if (!apStrand) \ + { \ + fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled),std::size_t{0}); \ + return; \ + } \ + \ + apStrand->dispatch(boost::bind(fn,ec,bytes_transferred)); \ +} + +/////////////////////////////////////////////////////////////////// +#define STRAND_TIMER_HANDLER(_fn) \ +[fn = _fn, strand = _strand](const boost::system::error_code &ec) \ +{ \ + const std::shared_ptr apStrand = strand.lock(); \ + if (!apStrand) \ + { \ + fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); \ + return; \ + } \ \ apStrand->dispatch(boost::bind(fn,ec)); \ } @@ -57,6 +78,12 @@ private: * @var class boost::asio::io_service& */ boost::asio::io_service & _ioservice; + + /** + * The boost asio io_service::strand managed pointer. + * @var class std::shared_ptr + */ + std::weak_ptr _strand; /** * The boost tcp socket. @@ -98,7 +125,8 @@ private: * @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, + void read_handler(const boost::system::error_code &ec, + const std::size_t bytes_transferred, const std::weak_ptr awpWatcher, TcpConnection *const connection, const int fd) @@ -115,19 +143,21 @@ private: connection->process(fd, AMQP::readable); _read_pending = true; - - _socket.async_read_some(boost::asio::null_buffers(), - boost::bind(&Watcher::read_handler, - this, - boost::placeholders::_1, + + _socket.async_read_some(boost::asio::null_buffers(), + STRAND_SOCKET_HANDLER( + boost::bind(&Watcher::read_handler, + this, + boost::placeholders::_1, + boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this() #else - shared_from_this(), + shared_from_this(), #endif - connection, - fd)); + connection, + fd))); } } @@ -140,6 +170,7 @@ private: * @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 awpWatcher, TcpConnection *const connection, const int fd) @@ -158,17 +189,19 @@ private: _write_pending = true; _socket.async_write_some(boost::asio::null_buffers(), - boost::bind(&Watcher::write_handler, - this, - boost::placeholders::_1, + STRAND_SOCKET_HANDLER( + boost::bind(&Watcher::write_handler, + this, + boost::placeholders::_1, + boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this() #else - shared_from_this(), + shared_from_this(), #endif - connection, - fd)); + connection, + fd))); } } @@ -179,8 +212,11 @@ private: * @param io_service The boost io_service * @param fd The filedescriptor being watched */ - Watcher(boost::asio::io_service &io_service, const int fd) : + Watcher(boost::asio::io_service &io_service, + const std::weak_ptr strand, + const int fd) : _ioservice(io_service), + _strand(strand), _socket(_ioservice) { _socket.assign(fd); @@ -221,17 +257,19 @@ private: _read_pending = true; _socket.async_read_some(boost::asio::null_buffers(), - boost::bind(&Watcher::read_handler, - this, - boost::placeholders::_1, + STRAND_SOCKET_HANDLER( + boost::bind(&Watcher::read_handler, + this, + boost::placeholders::_1, + boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this() #else - shared_from_this(), + shared_from_this(), #endif - connection, - fd)); + connection, + fd))); } // 2. Handle writes? @@ -243,17 +281,19 @@ private: _write_pending = true; _socket.async_write_some(boost::asio::null_buffers(), - boost::bind(&Watcher::write_handler, - this, - boost::placeholders::_1, + STRAND_SOCKET_HANDLER( + boost::bind(&Watcher::write_handler, + this, + boost::placeholders::_1, + boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this() #else - shared_from_this(), + shared_from_this(), #endif - connection, - fd)); + connection, + fd))); } } }; @@ -271,6 +311,12 @@ private: */ boost::asio::io_service & _ioservice; + /** + * The boost asio io_service::strand managed pointer. + * @var class std::shared_ptr + */ + std::weak_ptr _strand; + /** * The boost asynchronous deadline timer. * @var class boost::asio::deadline_timer @@ -305,17 +351,18 @@ private: _timer.expires_at(_timer.expires_at() + boost::posix_time::seconds(timeout)); // Posts the timer event - _timer.async_wait(boost::bind(&Timer::timeout, - this, - boost::placeholders::_1, + _timer.async_wait(STRAND_TIMER_HANDLER( + boost::bind(&Timer::timeout, + this, + boost::placeholders::_1, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this() #else - shared_from_this(), + shared_from_this(), #endif - connection, - timeout)); + connection, + timeout))); } } @@ -333,8 +380,10 @@ private: * Constructor * @param loop The current event loop */ - Timer(boost::asio::io_service &io_service) : + Timer(boost::asio::io_service &io_service, + const std::weak_ptr strand) : _ioservice(io_service), + _strand(strand), _timer(_ioservice) { @@ -368,17 +417,18 @@ private: stop(); _timer.expires_from_now(boost::posix_time::seconds(timeout)); - _timer.async_wait(boost::bind(&Timer::timeout, - this, - boost::placeholders::_1, + _timer.async_wait(STRAND_TIMER_HANDLER( + boost::bind(&Timer::timeout, + this, + boost::placeholders::_1, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this() #else - shared_from_this(), + shared_from_this(), #endif - connection, - timeout)); + connection, + timeout))); } }; @@ -388,6 +438,12 @@ private: */ boost::asio::io_service & _ioservice; + /** + * The boost asio io_service::strand managed pointer. + * @var class std::shared_ptr + */ + std::shared_ptr _strand; + /** * All I/O watchers that are active, indexed by their filedescriptor @@ -420,7 +476,7 @@ private: // construct a new pair (watcher/timer), and put it in the map const std::shared_ptr apWatcher = - std::make_shared(_ioservice, fd); + std::make_shared(_ioservice, _strand, fd); _watchers[fd] = apWatcher; @@ -473,7 +529,8 @@ public: */ explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service), - _timer(std::make_shared(_ioservice)) + _strand(std::make_shared(_ioservice)), + _timer(std::make_shared(_ioservice,_strand)) { } From cdd9918e6a79ac6297988cd3d796d1898cb74035 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 19 Oct 2017 16:06:41 +0100 Subject: [PATCH 015/168] Cleaned up docs/comments in LibBoostAsioHandler --- include/libboostasio.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 5ed6489..c1c92cf 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -120,6 +120,7 @@ private: /** * Handler method that is called by boost's io_service 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. @@ -164,6 +165,7 @@ private: /** * Handler method that is called by boost's io_service 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. @@ -210,6 +212,7 @@ private: * Constructor- initialises the watcher and assigns the filedescriptor to * a boost socket for monitoring. * @param io_service The boost io_service + * @param strand A weak pointer to a io_service::strand instance. * @param fd The filedescriptor being watched */ Watcher(boost::asio::io_service &io_service, @@ -378,7 +381,8 @@ private: public: /** * Constructor - * @param loop The current event loop + * @param io_service The boost asio io_service. + * @param strand A weak pointer to a io_service::strand instance. */ Timer(boost::asio::io_service &io_service, const std::weak_ptr strand) : From 5410f1abdd8a155f6a072c24a47ec49c5079d881 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Thu, 19 Oct 2017 16:14:21 +0100 Subject: [PATCH 016/168] Replaced tabs with spaces in LibBoostAsioHandler --- include/libboostasio.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index c1c92cf..0a76c76 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -33,8 +33,8 @@ if (!apStrand) \ { \ fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled),std::size_t{0}); \ - return; \ - } \ + return; \ + } \ \ apStrand->dispatch(boost::bind(fn,ec,bytes_transferred)); \ } @@ -47,8 +47,8 @@ if (!apStrand) \ { \ fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); \ - return; \ - } \ + return; \ + } \ \ apStrand->dispatch(boost::bind(fn,ec)); \ } From 034e72bbc2aa3924bb5aac3a0d1238e3781b25b9 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Mon, 23 Oct 2017 11:40:49 +0100 Subject: [PATCH 017/168] Added example/test application to demonstrate boost asio io_service handler. --- include/libboostasio.h | 21 ++++++++------- tests/libboostasio.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 tests/libboostasio.cpp diff --git a/include/libboostasio.h b/include/libboostasio.h index 0a76c76..2df986c 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -3,9 +3,11 @@ * * Implementation for the AMQP::TcpHandler that is optimized 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 + * to the constructor and you're all set. See tests/libboostasio.cpp for example. * * @author Gavin Smith + * + * */ /** @@ -19,9 +21,10 @@ #include #include +#include +#include #include #include -#include /////////////////////////////////////////////////////////////////// @@ -78,7 +81,7 @@ private: * @var class boost::asio::io_service& */ boost::asio::io_service & _ioservice; - + /** * The boost asio io_service::strand managed pointer. * @var class std::shared_ptr @@ -127,7 +130,7 @@ private: * @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::size_t bytes_transferred, const std::weak_ptr awpWatcher, TcpConnection *const connection, const int fd) @@ -144,8 +147,8 @@ private: connection->process(fd, AMQP::readable); _read_pending = true; - - _socket.async_read_some(boost::asio::null_buffers(), + + _socket.async_read_some(boost::asio::null_buffers(), STRAND_SOCKET_HANDLER( boost::bind(&Watcher::read_handler, this, @@ -313,13 +316,13 @@ private: * @var class boost::asio::io_service& */ boost::asio::io_service & _ioservice; - + /** * The boost asio io_service::strand managed pointer. * @var class std::shared_ptr */ std::weak_ptr _strand; - + /** * The boost asynchronous deadline timer. * @var class boost::asio::deadline_timer @@ -483,7 +486,7 @@ private: std::make_shared(_ioservice, _strand, fd); _watchers[fd] = apWatcher; - + // explicitly set the events to monitor apWatcher->events(connection, fd, flags); } diff --git a/tests/libboostasio.cpp b/tests/libboostasio.cpp new file mode 100644 index 0000000..ee9555c --- /dev/null +++ b/tests/libboostasio.cpp @@ -0,0 +1,59 @@ +/** + * LibBoostAsio.cpp + * + * Test program to check AMQP functionality based on Boost's asio io_service. + * + * @author Gavin Smith + * + * Compile with g++ libboostasio.cpp -o boost_test -lpthread -lboost_system -lamqpcpp + */ + +/** + * Dependencies + */ +#include +#include +#include + + +#include +#include + +/** + * 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); + + // create a work object to process our events. + boost::asio::io_service::work work(service); + + // 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(); +} + From d75ae2ebe14f44841478f4203025354390403079 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 24 Oct 2017 12:54:29 +0200 Subject: [PATCH 018/168] fix compile issue because comma was missing --- include/libboostasio.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 2df986c..3a49f03 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -156,7 +156,7 @@ private: boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this(), #else shared_from_this(), #endif @@ -201,7 +201,7 @@ private: boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this(), #else shared_from_this(), #endif @@ -270,7 +270,7 @@ private: boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this(), #else shared_from_this(), #endif @@ -294,7 +294,7 @@ private: boost::placeholders::_2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this(), #else shared_from_this(), #endif @@ -363,7 +363,7 @@ private: boost::placeholders::_1, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this(), #else shared_from_this(), #endif @@ -430,7 +430,7 @@ private: boost::placeholders::_1, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L - weak_from_this() + weak_from_this(), #else shared_from_this(), #endif From 562703b1cfe0d7fadc027c3593c56b483a34ded0 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 1 Nov 2017 08:20:52 +0100 Subject: [PATCH 019/168] renamed major() and minor() methods of the ConnectionStartFrame class to fix warning from new gcc version that these function names are more or less reserved --- src/connectionstartframe.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/connectionstartframe.h b/src/connectionstartframe.h index a8a0868..139d2b9 100644 --- a/src/connectionstartframe.h +++ b/src/connectionstartframe.h @@ -121,7 +121,7 @@ public: * Major AMQP version number * @return uint8_t */ - uint8_t major() const + uint8_t majorVersion() const { return _major; } @@ -130,7 +130,7 @@ public: * Minor AMQP version number * @return uint8_t */ - uint8_t minor() const + uint8_t minorVersion() const { return _minor; } From ea538715a72f1fd469d2906ccc10e0368202952b Mon Sep 17 00:00:00 2001 From: zerodefect Date: Wed, 1 Nov 2017 12:18:47 +0000 Subject: [PATCH 020/168] Updated readme.md to reflect inclusion of boost asio implementation. --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28b1564..632e9c0 100644 --- a/README.md +++ b/README.md @@ -429,10 +429,16 @@ instantiate it directly (like we did in the example), but to create your own "MyHandler" class that extends from it, and in which you also implement the onError() method to report possible connection errors to your end users. -Currently, we have only added such an example TcpHandler implementation for libev and -libevent. For other event loops (like libuv and boost asio) we do not yet have +Currently, we have example TcpHandler implementations for libev, +libevent, and Boost's asio. For other event loops (like libuv) we do not yet have such examples. +| TCP Handler Impl | Header File Location | Sample File Location | +| ----------------------- | ---------------------- | --------------------- | +| Boost asio (io_service) | include/libboostasio.h | tests/libboostasio.cpp | +| libev | include/libev.h | tests/libev.cpp | +| libevent | include/libevent.h | tests/libevent.cpp | +| libuv | include/libuv.h | (Not available) | HEARTBEATS ========== From 60466cc6bf2ac57704415d63db61bf0d21f41a2b Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 17 Nov 2017 11:38:54 +0100 Subject: [PATCH 021/168] Updated comment for Channel and TcpChannel constructors (to prevent issues like #154) --- include/channel.h | 4 ++++ include/tcpchannel.h | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/channel.h b/include/channel.h index af01f02..bff4085 100644 --- a/include/channel.h +++ b/include/channel.h @@ -29,6 +29,10 @@ private: public: /** * Construct a channel object + * + * The passed in connection pointer must remain valid for the + * lifetime of the channel. + * * @param connection */ Channel(Connection *connection) : _implementation(new ChannelImpl()) diff --git a/include/tcpchannel.h b/include/tcpchannel.h index 3d19053..e8b5db1 100644 --- a/include/tcpchannel.h +++ b/include/tcpchannel.h @@ -4,7 +4,7 @@ * Extended channel that can be constructed on top of a TCP connection * * @author Emiel Bruijntjes - * @copyright 2015 Copernica BV + * @copyright 2015 - 2017 Copernica BV */ /** @@ -25,6 +25,10 @@ class TcpChannel : public Channel public: /** * Constructor + * + * The passed in connection pointer must remain valid for the + * lifetime of the channel. + * * @param connection */ TcpChannel(TcpConnection *connection) : From 276abe4b06e170ff546407fb8196672f3ff02dbf Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 17 Nov 2017 12:18:04 +0100 Subject: [PATCH 022/168] added libuv example program to check issue #155 --- tests/libev.cpp | 45 +++++++++++++++++++++++--- tests/libuv.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 tests/libuv.cpp diff --git a/tests/libev.cpp b/tests/libev.cpp index 1bb610b..e4c428d 100644 --- a/tests/libev.cpp +++ b/tests/libev.cpp @@ -4,7 +4,7 @@ * Test program to check AMQP functionality based on LibEV * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2017 Copernica BV */ /** @@ -14,6 +14,44 @@ #include #include +/** + * 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; + } + +public: + /** + * Constructor + * @param ev_loop + */ + MyHandler(struct ev_loop *loop) : AMQP::LibEvHandler(loop) {} + + /** + * Destructor + */ + virtual ~MyHandler() = default; +}; + /** * Main program * @return int @@ -24,7 +62,7 @@ int main() auto *loop = EV_DEFAULT; // handler for libev - AMQP::LibEvHandler handler(loop); + MyHandler handler(loop); // make a connection AMQP::TcpConnection connection(&handler, AMQP::Address("amqp://guest:guest@localhost/")); @@ -37,9 +75,6 @@ int main() // 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 diff --git a/tests/libuv.cpp b/tests/libuv.cpp new file mode 100644 index 0000000..8963eae --- /dev/null +++ b/tests/libuv.cpp @@ -0,0 +1,86 @@ +/** + * LibUV.cpp + * + * Test program to check AMQP functionality based on LibUV + * + * @author Emiel Bruijntjes + * @copyright 2015 - 2017 Copernica BV + */ + +/** + * Dependencies + */ +#include +#include +#include + +/** + * 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; +} + From c63f62189e2fa4a21a227119dfee2de82af766f0 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 21 Nov 2017 16:45:05 +0100 Subject: [PATCH 023/168] better docblocks so that future users will understand the difference between the callbacks for a consumer (mentioned in issue #156) --- include/deferredconsumer.h | 137 ++++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 56 deletions(-) diff --git a/include/deferredconsumer.h b/include/deferredconsumer.h index d5dd161..d7d7bf8 100644 --- a/include/deferredconsumer.h +++ b/include/deferredconsumer.h @@ -72,7 +72,86 @@ public: public: /** - * Register the function to be called when a new message is expected + * 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 + */ + DeferredConsumer &onSuccess(const ConsumeCallback &callback) + { + // store the callback + _consumeCallback = 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 + */ + DeferredConsumer &onSuccess(const SuccessCallback &callback) + { + // call base + Deferred::onSuccess(callback); + + // allow chaining + return *this; + } + + /** + * Register a function to be called when a full message is received + * @param callback the callback to execute + */ + DeferredConsumer &onReceived(const MessageCallback &callback) + { + // store callback + _messageCallback = callback; + + // allow chaining + return *this; + } + + /** + * Alias for onReceived() (see above) + * @param callback the callback to execute + */ + DeferredConsumer &onMessage(const MessageCallback &callback) + { + // store callback + _messageCallback = 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 @@ -87,7 +166,7 @@ public: } /** - * Register the function to be called when message headers come in + * 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 @@ -123,60 +202,6 @@ public: return *this; } - /** - * Register the function that is called when the consumer starts - * @param callback - */ - DeferredConsumer &onSuccess(const ConsumeCallback &callback) - { - // store the callback - _consumeCallback = callback; - - // allow chaining - return *this; - } - - /** - * Register the function that is called when the consumer starts - * @param callback - */ - DeferredConsumer &onSuccess(const SuccessCallback &callback) - { - // call base - Deferred::onSuccess(callback); - - // allow chaining - return *this; - } - - /** - * Register a function to be called when a message arrives - * This fuction is also available as onMessage() because I always forget which name I gave to it - * @param callback the callback to execute - */ - DeferredConsumer &onReceived(const MessageCallback &callback) - { - // store callback - _messageCallback = callback; - - // allow chaining - return *this; - } - - /** - * Register a function to be called when a message arrives - * This fuction is also available as onReceived() because I always forget which name I gave to it - * @param callback the callback to execute - */ - DeferredConsumer &onMessage(const MessageCallback &callback) - { - // store callback - _messageCallback = callback; - - // allow chaining - return *this; - } - /** * Register a funtion to be called when a message was completely received * From f05aba0782d0a3dca6b7315e73928eff64267245 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 12 Dec 2017 17:10:51 +0100 Subject: [PATCH 024/168] added TcpConnection::fileno() to expose the internal filedescriptor / socket --- include/tcpconnection.h | 6 ++++++ src/tcpconnected.h | 6 ++++++ src/tcpconnection.cpp | 10 ++++++++++ src/tcpstate.h | 6 ++++++ 4 files changed, 28 insertions(+) diff --git a/include/tcpconnection.h b/include/tcpconnection.h index 5441eb5..681ecd5 100644 --- a/include/tcpconnection.h +++ b/include/tcpconnection.h @@ -117,6 +117,12 @@ public: */ virtual ~TcpConnection() noexcept {} + /** + * The filedescriptor that is used for this connection + * @return int + */ + int fileno() const; + /** * Process the TCP connection * diff --git a/src/tcpconnected.h b/src/tcpconnected.h index 85bc377..dd9a71e 100644 --- a/src/tcpconnected.h +++ b/src/tcpconnected.h @@ -142,6 +142,12 @@ public: // close the socket close(_socket); } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } /** * Process the filedescriptor in the object diff --git a/src/tcpconnection.cpp b/src/tcpconnection.cpp index cf31fe7..6a1d8b0 100644 --- a/src/tcpconnection.cpp +++ b/src/tcpconnection.cpp @@ -27,6 +27,16 @@ TcpConnection::TcpConnection(TcpHandler *handler, const Address &address) : _state(new TcpResolver(this, address.hostname(), address.port(), handler)), _connection(this, address.login(), address.vhost()) {} +/** + * The filedescriptor that is used for this connection + * @return int + */ +int TcpConnection::fileno() const +{ + // pass on to the state object + return _state->fileno(); +} + /** * Process the TCP connection * This method should be called when the filedescriptor that is registered diff --git a/src/tcpstate.h b/src/tcpstate.h index bdedcf2..93ddc21 100644 --- a/src/tcpstate.h +++ b/src/tcpstate.h @@ -57,6 +57,12 @@ public: */ virtual ~TcpState() = default; + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const { return -1; } + /** * Process the filedescriptor in the object * @param fd The filedescriptor that is active From 5c002774de87bab1ed1c122e6b2d41f8ce1b9e05 Mon Sep 17 00:00:00 2001 From: Rafal Goslawski Date: Fri, 15 Dec 2017 15:41:47 +0100 Subject: [PATCH 025/168] Update Makefile Bump version to 2.8 --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8ec3e81..9f1fe75 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ PREFIX ?= /usr INCLUDE_DIR = ${PREFIX}/include LIBRARY_DIR = ${PREFIX}/lib export LIBRARY_NAME = amqpcpp -export SONAME = 2.7 -export VERSION = 2.7.4 +export SONAME = 2.8 +export VERSION = 2.8.0 all: $(MAKE) -C src all From 0a360ad09c58c51fe0a32803e3cfcd185ff08985 Mon Sep 17 00:00:00 2001 From: Taivo Pungas Date: Thu, 21 Dec 2017 15:01:35 +0200 Subject: [PATCH 026/168] README: quick link from top to TcpConnection explanation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 632e9c0..8fc0303 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ sure that your compiler is up-to-date and supports C++11. **Note for the reader:** This readme file has a peculiar structure. We start explaining the pure and hard core low level interface in which you have to take care of opening socket connections yourself. In reality, you probably want -to use the simpler TCP interface that is being described later on. +to use the simpler TCP interface that is being described [later on](#tcp-connections). ABOUT From b25bd583a36e775b701ad07ba0076e4ec459b881 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 29 Dec 2017 19:27:40 +0100 Subject: [PATCH 027/168] added operator< to AMQP::Address --- include/address.h | 25 ++++++++++++++++++++++++- include/login.h | 14 ++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/include/address.h b/include/address.h index c2e9f21..9e54779 100644 --- a/include/address.h +++ b/include/address.h @@ -216,7 +216,7 @@ public: // logins must match if (_login != that._login) return false; - // hostname must match, but are nt case sensitive + // hostname must match, but are not case sensitive if (strcasecmp(_hostname.data(), that._hostname.data()) != 0) return false; // portnumber must match @@ -237,6 +237,29 @@ public: 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 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 + return _vhost < that._vhost; + } + /** * Friend function to allow writing the address to a stream * @param stream diff --git a/include/login.h b/include/login.h index d4599d9..b9276da 100644 --- a/include/login.h +++ b/include/login.h @@ -119,6 +119,20 @@ public: // 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 From fff71d21a86a2d79f917d2dffdc95d3162672fce Mon Sep 17 00:00:00 2001 From: Alessandro Pischedda Date: Wed, 17 Jan 2018 15:15:48 +0100 Subject: [PATCH 028/168] Update Makefile Use c++14 in order to use 'initialization lambda' --- tests/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Makefile b/tests/Makefile index b5bf2f3..e27d884 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,6 +1,6 @@ -CPP = g++ -CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g -LD = g++ +CPP = g++ +CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++14 -g +LD = g++ LDFLAGS = -lamqpcpp -lcopernica_event -lcopernica_network -lev RESULT = a.out SOURCES = $(wildcard *.cpp) From 11b2c7914fe357b972d7fa2770e7e669dc591254 Mon Sep 17 00:00:00 2001 From: Alessandro Pischedda Date: Wed, 17 Jan 2018 15:16:57 +0100 Subject: [PATCH 029/168] Fix g++ command Needs std=c++14 --- tests/libboostasio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/libboostasio.cpp b/tests/libboostasio.cpp index ee9555c..c71c9e3 100644 --- a/tests/libboostasio.cpp +++ b/tests/libboostasio.cpp @@ -5,7 +5,7 @@ * * @author Gavin Smith * - * Compile with g++ libboostasio.cpp -o boost_test -lpthread -lboost_system -lamqpcpp + * Compile with g++ -std=c++14 libboostasio.cpp -o boost_test -lpthread -lboost_system -lamqpcpp */ /** From 97124dc6041b047eff333c9d2df84e42530269fe Mon Sep 17 00:00:00 2001 From: Alessandro Pischedda Date: Wed, 17 Jan 2018 15:20:04 +0100 Subject: [PATCH 030/168] Fix compile remove ?boost::placeholders::' . --- include/libboostasio.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 3a49f03..2ac1930 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -152,8 +152,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::read_handler, this, - boost::placeholders::_1, - boost::placeholders::_2, + _1, + _2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -197,8 +197,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::write_handler, this, - boost::placeholders::_1, - boost::placeholders::_2, + _1, + _2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -266,8 +266,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::read_handler, this, - boost::placeholders::_1, - boost::placeholders::_2, + _1, + _2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -290,8 +290,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::write_handler, this, - boost::placeholders::_1, - boost::placeholders::_2, + _1, + _2, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -360,7 +360,7 @@ private: _timer.async_wait(STRAND_TIMER_HANDLER( boost::bind(&Timer::timeout, this, - boost::placeholders::_1, + _1, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -427,7 +427,7 @@ private: _timer.async_wait(STRAND_TIMER_HANDLER( boost::bind(&Timer::timeout, this, - boost::placeholders::_1, + _1, // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), From 4c2ef4adb49ed111ed28b57e668c3947a1a83570 Mon Sep 17 00:00:00 2001 From: Alessandro Pischedda Date: Thu, 18 Jan 2018 09:13:19 +0100 Subject: [PATCH 031/168] Revert changes Revert to std=c++11. --- tests/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Makefile b/tests/Makefile index e27d884..ee7f59c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,5 @@ CPP = g++ -CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++14 -g +CPPFLAGS = -Wall -c -I. -O2 -flto -std=c++11 -g LD = g++ LDFLAGS = -lamqpcpp -lcopernica_event -lcopernica_network -lev RESULT = a.out From fd14790166c03596c464024ffe27d69cd79e0bff Mon Sep 17 00:00:00 2001 From: Alessandro Pischedda Date: Thu, 18 Jan 2018 10:08:56 +0100 Subject: [PATCH 032/168] fix boost::placeholders undefined error and remain well explicited than use just '_1' or '_2' --- include/libboostasio.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 2ac1930..f0d4ed4 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -152,8 +152,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::read_handler, this, - _1, - _2, + boost::arg<1>(), + boost::arg<2>(), // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -197,8 +197,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::write_handler, this, - _1, - _2, + boost::arg<1>(), + boost::arg<2>(), // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -266,8 +266,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::read_handler, this, - _1, - _2, + boost::arg<1>(), + boost::arg<2>(), // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -290,8 +290,8 @@ private: STRAND_SOCKET_HANDLER( boost::bind(&Watcher::write_handler, this, - _1, - _2, + boost::arg<1>(), + boost::arg<2>(), // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -360,7 +360,7 @@ private: _timer.async_wait(STRAND_TIMER_HANDLER( boost::bind(&Timer::timeout, this, - _1, + boost::arg<1>(), // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), @@ -427,7 +427,7 @@ private: _timer.async_wait(STRAND_TIMER_HANDLER( boost::bind(&Timer::timeout, this, - _1, + boost::arg<1>(), // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L weak_from_this(), From 001dfaa7e00b1df1874e241d10c9351c7dc654ba Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 16:47:53 +0100 Subject: [PATCH 033/168] - Moved linux specific TCP implementation to a seperate folder and removed it from default build. Does not yet build correctly if you want that. - Fixed conversion warnings from type to uint32_t, which happens a lot around here. This is no functional change, just making it explicit so the compiler doesn't warn. --- .gitignore | 2 + CMakeLists.txt | 9 +- amqpcpp.h | 3 - include/CMakeLists.txt | 54 ---------- include/message.h | 1 + include/metadata.h | 28 ++--- include/numericfield.h | 18 ++-- include/stringfield.h | 4 +- include/{ => tcpconnection}/tcpchannel.h | 0 include/{ => tcpconnection}/tcpconnection.h | 0 include/{ => tcpconnection}/tcpdefines.h | 0 include/{ => tcpconnection}/tcphandler.h | 0 src/.gitignore | 1 - src/CMakeLists.txt | 110 +++----------------- src/array.cpp | 4 +- src/basiccancelframe.h | 2 +- src/basiccancelokframe.h | 2 +- src/basicconsumeframe.h | 2 +- src/basicconsumeokframe.h | 2 +- src/basicdeliverframe.h | 2 +- src/basicgetframe.h | 2 +- src/basicgetokframe.h | 2 +- src/basicpublishframe.h | 2 +- src/basicreturnframe.h | 2 +- src/channelcloseframe.h | 2 +- src/channelimpl.cpp | 2 +- src/connectioncloseframe.h | 2 +- src/connectionimpl.cpp | 2 +- src/connectionopenframe.h | 2 +- src/connectionsecureframe.h | 2 +- src/connectionsecureokframe.h | 2 +- src/connectionstartframe.h | 2 +- src/connectionstartokframe.h | 2 +- src/exchangebindframe.h | 2 +- src/exchangedeclareframe.h | 2 +- src/exchangedeleteframe.h | 2 +- src/exchangeunbindframe.h | 2 +- src/framecheck.h | 2 +- src/includes.h | 21 ++-- src/queuebindframe.h | 2 +- src/queuedeclareframe.h | 2 +- src/queuedeclareokframe.h | 2 +- src/queuedeleteframe.h | 2 +- src/queuepurgeframe.h | 2 +- src/queueunbindframe.h | 2 +- src/table.cpp | 4 +- src/tcpconnection/CMakeLists.txt | 3 + src/tcpconnection/includes.h | 96 +++++++++++++++++ src/{ => tcpconnection}/tcpclosed.h | 0 src/{ => tcpconnection}/tcpconnected.h | 0 src/{ => tcpconnection}/tcpconnection.cpp | 0 src/{ => tcpconnection}/tcpinbuffer.h | 0 src/{ => tcpconnection}/tcpoutbuffer.h | 0 src/{ => tcpconnection}/tcpresolver.h | 0 src/{ => tcpconnection}/tcpstate.h | 0 55 files changed, 189 insertions(+), 227 deletions(-) delete mode 100644 include/CMakeLists.txt rename include/{ => tcpconnection}/tcpchannel.h (100%) rename include/{ => tcpconnection}/tcpconnection.h (100%) rename include/{ => tcpconnection}/tcpdefines.h (100%) rename include/{ => tcpconnection}/tcphandler.h (100%) delete mode 100644 src/.gitignore create mode 100644 src/tcpconnection/CMakeLists.txt create mode 100644 src/tcpconnection/includes.h rename src/{ => tcpconnection}/tcpclosed.h (100%) rename src/{ => tcpconnection}/tcpconnected.h (100%) rename src/{ => tcpconnection}/tcpconnection.cpp (100%) rename src/{ => tcpconnection}/tcpinbuffer.h (100%) rename src/{ => tcpconnection}/tcpoutbuffer.h (100%) rename src/{ => tcpconnection}/tcpresolver.h (100%) rename src/{ => tcpconnection}/tcpstate.h (100%) diff --git a/.gitignore b/.gitignore index 4a62ba7..3235c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ *.la *.a *.a.* +/build +/.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c73267..34f19e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.1) project(amqpcpp) # ensure c++11 on all compilers -include(set_cxx_norm.cmake) -set_cxx_norm (${CXX_NORM_CXX11}) +set (CMAKE_CXX_STANDARD 11) macro (add_sources) file (RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") @@ -22,7 +21,9 @@ macro (add_sources) endmacro() add_subdirectory(src) -add_subdirectory(include) +if(AMQPCPP_BUILD_TCP) + add_subdirectory(src/tcpconnection) +endif() option(BUILD_SHARED "build shared library" OFF) diff --git a/amqpcpp.h b/amqpcpp.h index a1ac8d5..960f5f2 100644 --- a/amqpcpp.h +++ b/amqpcpp.h @@ -72,6 +72,3 @@ #include #include #include -#include -#include -#include diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt deleted file mode 100644 index 140f8f2..0000000 --- a/include/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -add_sources( -address.h -addresses.h -array.h -booleanset.h -buffer.h -bytebuffer.h -callbacks.h -channel.h -channelimpl.h -classes.h -connection.h -connectionhandler.h -connectionimpl.h -copiedbuffer.h -decimalfield.h -deferred.h -deferredcancel.h -deferredconsumer.h -deferredconsumerbase.h -deferreddelete.h -deferredget.h -deferredqueue.h -endian.h -entityimpl.h -envelope.h -exception.h -exchangetype.h -field.h -fieldproxy.h -flags.h -frame.h -libboostasio.h -libev.h -libevent.h -libuv.h -login.h -message.h -metadata.h -monitor.h -numericfield.h -outbuffer.h -protocolexception.h -receivedframe.h -stack_ptr.h -stringfield.h -table.h -tcpchannel.h -tcpconnection.h -tcpdefines.h -tcphandler.h -watchable.h - -) diff --git a/include/message.h b/include/message.h index 5ce4e68..21a5767 100644 --- a/include/message.h +++ b/include/message.h @@ -21,6 +21,7 @@ #include "envelope.h" #include #include +#include /** * Set up namespace diff --git a/include/metadata.h b/include/metadata.h index 4b1a04e..8b18d99 100644 --- a/include/metadata.h +++ b/include/metadata.h @@ -302,20 +302,20 @@ public: // the result (2 for the two boolean sets) uint32_t result = 2; - if (hasExpiration()) result += _expiration.size(); - if (hasReplyTo()) result += _replyTo.size(); - if (hasCorrelationID()) result += _correlationID.size(); - if (hasPriority()) result += _priority.size(); - if (hasDeliveryMode()) result += _deliveryMode.size(); - if (hasHeaders()) result += _headers.size(); - if (hasContentEncoding()) result += _contentEncoding.size(); - if (hasContentType()) result += _contentType.size(); - if (hasClusterID()) result += _clusterID.size(); - if (hasAppID()) result += _appID.size(); - if (hasUserID()) result += _userID.size(); - if (hasTypeName()) result += _typeName.size(); - if (hasTimestamp()) result += _timestamp.size(); - if (hasMessageID()) result += _messageID.size(); + 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; diff --git a/include/numericfield.h b/include/numericfield.h index 29f2f42..5c1e8a1 100644 --- a/include/numericfield.h +++ b/include/numericfield.h @@ -42,6 +42,8 @@ private: T _value; public: + using Type = T; + /** * Default constructor, assign 0 */ @@ -116,14 +118,14 @@ public: * Get the value * @return mixed */ - operator uint8_t () const override { return _value; } - operator uint16_t() const override { return _value; } - operator uint32_t() const override { return _value; } - operator uint64_t() const override { return _value; } - operator int8_t () const override { return _value; } - operator int16_t () const override { return _value; } - operator int32_t () const override { return _value; } - operator int64_t () const override { return _value; } + 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; } /** * Get the value diff --git a/include/stringfield.h b/include/stringfield.h index bab6310..b4836f4 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -119,7 +119,7 @@ public: virtual size_t size() const override { // find out size of the size parameter - T size(_data.size()); + T size((T::Type)_data.size()); // size of the uint8 or uint32 + the actual string size return size.size() + _data.size(); @@ -160,7 +160,7 @@ public: virtual void fill(OutBuffer& buffer) const override { // create size - T size(_data.size()); + T size((T::Type)_data.size()); // first, write down the size of the string size.fill(buffer); diff --git a/include/tcpchannel.h b/include/tcpconnection/tcpchannel.h similarity index 100% rename from include/tcpchannel.h rename to include/tcpconnection/tcpchannel.h diff --git a/include/tcpconnection.h b/include/tcpconnection/tcpconnection.h similarity index 100% rename from include/tcpconnection.h rename to include/tcpconnection/tcpconnection.h diff --git a/include/tcpdefines.h b/include/tcpconnection/tcpdefines.h similarity index 100% rename from include/tcpdefines.h rename to include/tcpconnection/tcpdefines.h diff --git a/include/tcphandler.h b/include/tcpconnection/tcphandler.h similarity index 100% rename from include/tcphandler.h rename to include/tcpconnection/tcphandler.h diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index a438335..0000000 --- a/src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.d diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eae0c22..d61b3e0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,100 +1,14 @@ add_sources( -addressinfo.h -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 -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 -deferredconsumer.cpp -deferredconsumerbase.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 -pipe.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 -tcpclosed.h -tcpconnected.h -tcpconnection.cpp -tcpinbuffer.h -tcpoutbuffer.h -tcpresolver.h -tcpstate.h -transactioncommitframe.h -transactioncommitokframe.h -transactionframe.h -transactionrollbackframe.h -transactionrollbackokframe.h -transactionselectframe.h -transactionselectokframe.h -watchable.cpp + array.cpp + channelimpl.cpp + connectionimpl.cpp + deferredcancel.cpp + deferredconsumer.cpp + deferredconsumerbase.cpp + deferredget.cpp + field.cpp + flags.cpp + receivedframe.cpp + table.cpp + watchable.cpp ) diff --git a/src/array.cpp b/src/array.cpp index d04c865..9017faf 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -29,7 +29,7 @@ Array::Array(ReceivedFrame &frame) if (!field) continue; // less bytes to read - charsToRead -= field->size(); + charsToRead -= (uint32_t)field->size(); // add the additional field _fields.push_back(std::shared_ptr(field)); @@ -76,7 +76,7 @@ const Field &Array::get(uint8_t index) const */ uint32_t Array::count() const { - return _fields.size(); + return (uint32_t)_fields.size(); } /** diff --git a/src/basiccancelframe.h b/src/basiccancelframe.h index 6dd26dd..cdcf916 100644 --- a/src/basiccancelframe.h +++ b/src/basiccancelframe.h @@ -59,7 +59,7 @@ public: * @param noWait whether to wait for a response. */ BasicCancelFrame(uint16_t channel, const std::string& consumerTag, bool noWait = false) : - BasicFrame(channel, consumerTag.size() + 2), // 1 for extra string size, 1 for bool + BasicFrame(channel, (uint32_t)(consumerTag.size() + 2)), // 1 for extra string size, 1 for bool _consumerTag(consumerTag), _noWait(noWait) {} diff --git a/src/basiccancelokframe.h b/src/basiccancelokframe.h index 192417f..a894c5c 100644 --- a/src/basiccancelokframe.h +++ b/src/basiccancelokframe.h @@ -53,7 +53,7 @@ public: * @param consumerTag holds the consumertag specified by client or server */ BasicCancelOKFrame(uint16_t channel, std::string& consumerTag) : - BasicFrame(channel, consumerTag.length() + 1), // add 1 byte for encoding the size of consumer tag + BasicFrame(channel, (uint32_t)(consumerTag.length() + 1)), // add 1 byte for encoding the size of consumer tag _consumerTag(consumerTag) {} diff --git a/src/basicconsumeframe.h b/src/basicconsumeframe.h index 7e57eee..39a8930 100644 --- a/src/basicconsumeframe.h +++ b/src/basicconsumeframe.h @@ -86,7 +86,7 @@ public: * @param filter additional arguments */ BasicConsumeFrame(uint16_t channel, const std::string& queueName, const std::string& consumerTag, bool noLocal = false, bool noAck = false, bool exclusive = false, bool noWait = false, const Table& filter = {}) : - BasicFrame(channel, (queueName.length() + consumerTag.length() + 5 + filter.size())), // size of vars, +1 for each shortstring size, +1 for bools, +2 for deprecated value + 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), diff --git a/src/basicconsumeokframe.h b/src/basicconsumeokframe.h index 927fa38..9ec6397 100644 --- a/src/basicconsumeokframe.h +++ b/src/basicconsumeokframe.h @@ -43,7 +43,7 @@ public: * @param consumerTag consumertag specified by client of provided by server */ BasicConsumeOKFrame(uint16_t channel, const std::string& consumerTag) : - BasicFrame(channel, consumerTag.length() + 1), // length of string + 1 for encoding of stringsize + BasicFrame(channel, (uint32_t)(consumerTag.length() + 1)), // length of string + 1 for encoding of stringsize _consumerTag(consumerTag) {} diff --git a/src/basicdeliverframe.h b/src/basicdeliverframe.h index 1252969..7986355 100644 --- a/src/basicdeliverframe.h +++ b/src/basicdeliverframe.h @@ -87,7 +87,7 @@ public: * @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, (consumerTag.length() + exchange.length() + routingKey.length() + 12)), + 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), diff --git a/src/basicgetframe.h b/src/basicgetframe.h index f7244c8..4d112ff 100644 --- a/src/basicgetframe.h +++ b/src/basicgetframe.h @@ -59,7 +59,7 @@ public: * @param noAck whether server expects acknowledgements for messages */ BasicGetFrame(uint16_t channel, const std::string& queue, bool noAck = false) : - BasicFrame(channel, queue.length() + 4), // 1 for bool, 1 for string size, 2 for deprecated field + BasicFrame(channel, (uint32_t)(queue.length() + 4)), // 1 for bool, 1 for string size, 2 for deprecated field _queue(queue), _noAck(noAck) {} diff --git a/src/basicgetokframe.h b/src/basicgetokframe.h index 5cc3a12..0de5ea9 100644 --- a/src/basicgetokframe.h +++ b/src/basicgetokframe.h @@ -76,7 +76,7 @@ public: * @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, (exchange.length() + routingKey.length() + 15)), // string length, +1 for each shortsrting length + 8 (uint64_t) + 4 (uint32_t) + 1 (bool) + 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), diff --git a/src/basicpublishframe.h b/src/basicpublishframe.h index 975c2ab..bbaed57 100644 --- a/src/basicpublishframe.h +++ b/src/basicpublishframe.h @@ -70,7 +70,7 @@ public: * @param immediate request immediate delivery @default = false */ BasicPublishFrame(uint16_t channel, const std::string& exchange = "", const std::string& routingKey = "", bool mandatory = false, bool immediate = false) : - BasicFrame(channel, exchange.length() + routingKey.length() + 5), // 1 extra per string (for the size), 1 for bools, 2 for deprecated field + 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) diff --git a/src/basicreturnframe.h b/src/basicreturnframe.h index 2fedce6..e140e9c 100644 --- a/src/basicreturnframe.h +++ b/src/basicreturnframe.h @@ -67,7 +67,7 @@ public: * @param routingKey message routing key */ BasicReturnFrame(uint16_t channel, int16_t replyCode, const std::string& replyText = "", const std::string& exchange = "", const std::string& routingKey = "") : - BasicFrame(channel, replyText.length() + exchange.length() + routingKey.length() + 5), // 3 for each string (extra size byte), 2 for uint16_t + BasicFrame(channel, (uint32_t)(replyText.length() + exchange.length() + routingKey.length() + 5)), // 3 for each string (extra size byte), 2 for uint16_t _replyCode(replyCode), _replyText(replyText), _exchange(exchange), diff --git a/src/channelcloseframe.h b/src/channelcloseframe.h index d47d88d..48476af 100644 --- a/src/channelcloseframe.h +++ b/src/channelcloseframe.h @@ -82,7 +82,7 @@ public: * @param failingMethod failing method id if applicable */ ChannelCloseFrame(uint16_t channel, uint16_t code = 0, const std::string& text = "", uint16_t failingClass = 0, uint16_t failingMethod = 0) : - ChannelFrame(channel, (text.length() + 7)), // sizeof code, failingclass, failingmethod (2byte + 2byte + 2byte) + text length + text length byte + ChannelFrame(channel, (uint32_t)(text.length() + 7)), // sizeof code, failingclass, failingmethod (2byte + 2byte + 2byte) + text length + text length byte _code(code), _text(text), _failingClass(failingClass), diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 73471ab..9d32874 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -481,7 +481,7 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin uint64_t chunksize = std::min(static_cast(maxpayload), bytesleft); // send out a body frame - if (!send(BodyFrame(_id, data + bytessent, chunksize))) return false; + if (!send(BodyFrame(_id, data + bytessent, (uint32_t)chunksize))) return false; // channel still valid? if (!monitor.valid()) return false; diff --git a/src/connectioncloseframe.h b/src/connectioncloseframe.h index 17378c0..9828bb5 100644 --- a/src/connectioncloseframe.h +++ b/src/connectioncloseframe.h @@ -82,7 +82,7 @@ public: * @param failingMethod id of the failing method if applicable */ ConnectionCloseFrame(uint16_t code, const std::string &text, uint16_t failingClass = 0, uint16_t failingMethod = 0) : - ConnectionFrame(text.length() + 7), // 1 for extra string byte, 2 for each uint16 + ConnectionFrame((uint32_t)(text.length() + 7)), // 1 for extra string byte, 2 for each uint16 _code(code), _text(text), _failingClass(failingClass), diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index f2bd13c..756e084 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -146,7 +146,7 @@ uint64_t ConnectionImpl::parse(const Buffer &buffer) // have the initial bytes of the header, we already know how much // data we need for the next frame, otherwise we need at least 7 // bytes for processing the header of the next frame - _expected = receivedFrame.header() ? receivedFrame.totalSize() : 7; + _expected = receivedFrame.header() ? (uint32_t)receivedFrame.totalSize() : 7; // we're ready for now return processed; diff --git a/src/connectionopenframe.h b/src/connectionopenframe.h index ca78dd6..c063357 100644 --- a/src/connectionopenframe.h +++ b/src/connectionopenframe.h @@ -60,7 +60,7 @@ public: * @param vhost name of virtual host to open */ ConnectionOpenFrame(const std::string &vhost) : - ConnectionFrame(vhost.length() + 3), // length of vhost + byte to encode this length + deprecated shortstring size + deprecated bool + ConnectionFrame((uint32_t)(vhost.length() + 3)), // length of vhost + byte to encode this length + deprecated shortstring size + deprecated bool _vhost(vhost), _deprecatedCapabilities(""), _deprecatedInsist() diff --git a/src/connectionsecureframe.h b/src/connectionsecureframe.h index f8564ec..8c128d1 100644 --- a/src/connectionsecureframe.h +++ b/src/connectionsecureframe.h @@ -43,7 +43,7 @@ public: * @param challenge the challenge */ ConnectionSecureFrame(const std::string& challenge) : - ConnectionFrame(challenge.length() + 4), // 4 for the length of the challenge (uint32_t) + ConnectionFrame((uint32_t)(challenge.length() + 4)), // 4 for the length of the challenge (uint32_t) _challenge(challenge) {} diff --git a/src/connectionsecureokframe.h b/src/connectionsecureokframe.h index d5c907b..7867804 100644 --- a/src/connectionsecureokframe.h +++ b/src/connectionsecureokframe.h @@ -43,7 +43,7 @@ public: * @param response the challenge response */ ConnectionSecureOKFrame(const std::string& response) : - ConnectionFrame(response.length() + 4), //response length + uint32_t for encoding the length + ConnectionFrame((uint32_t)(response.length() + 4)), //response length + uint32_t for encoding the length _response(response) {} diff --git a/src/connectionstartframe.h b/src/connectionstartframe.h index 139d2b9..f11c69b 100644 --- a/src/connectionstartframe.h +++ b/src/connectionstartframe.h @@ -81,7 +81,7 @@ public: * @param locales available locales */ ConnectionStartFrame(uint8_t major, uint8_t minor, const Table& properties, const std::string& mechanisms, const std::string& locales) : - ConnectionFrame((properties.size() + mechanisms.length() + locales.length() + 10)), // 4 for each longstring (size-uint32), 2 major/minor + ConnectionFrame((uint32_t)(properties.size() + mechanisms.length() + locales.length() + 10)), // 4 for each longstring (size-uint32), 2 major/minor _major(major), _minor(minor), _properties(properties), diff --git a/src/connectionstartokframe.h b/src/connectionstartokframe.h index 5ca994e..bc46d5e 100644 --- a/src/connectionstartokframe.h +++ b/src/connectionstartokframe.h @@ -82,7 +82,7 @@ public: * @param locale selected locale. */ ConnectionStartOKFrame(const Table& properties, const std::string& mechanism, const std::string& response, const std::string& locale) : - ConnectionFrame((properties.size() + mechanism.length() + response.length() + locale.length() + 6)), // 1 byte extra per shortstring, 4 per longstring + ConnectionFrame((uint32_t)(properties.size() + mechanism.length() + response.length() + locale.length() + 6)), // 1 byte extra per shortstring, 4 per longstring _properties(properties), _mechanism(mechanism), _response(response), diff --git a/src/exchangebindframe.h b/src/exchangebindframe.h index 1d01161..0b6a092 100644 --- a/src/exchangebindframe.h +++ b/src/exchangebindframe.h @@ -95,7 +95,7 @@ public: * @param arguments */ ExchangeBindFrame(uint16_t channel, const std::string &destination, const std::string &source, const std::string &routingKey, bool noWait, const Table &arguments) : - ExchangeFrame(channel, (destination.length() + source.length() + routingKey.length() + arguments.size() + 6)), // 1 for each string, 1 for booleanset, 2 for deprecated field + ExchangeFrame(channel, (uint32_t)(destination.length() + source.length() + routingKey.length() + arguments.size() + 6)), // 1 for each string, 1 for booleanset, 2 for deprecated field _destination(destination), _source(source), _routingKey(routingKey), diff --git a/src/exchangedeclareframe.h b/src/exchangedeclareframe.h index aa940dd..cb45f01 100644 --- a/src/exchangedeclareframe.h +++ b/src/exchangedeclareframe.h @@ -81,7 +81,7 @@ public: * @param arguments additional arguments */ ExchangeDeclareFrame(uint16_t channel, const std::string& name, const std::string& type, bool passive, bool durable, bool noWait, const Table& arguments) : - ExchangeFrame(channel, (name.length() + type.length() + arguments.size() + 5)), // size of name, type and arguments + 1 (all booleans are stored in 1 byte) + 2 (deprecated short) + 2 (string sizes) + ExchangeFrame(channel, (uint32_t)(name.length() + type.length() + arguments.size() + 5)), // size of name, type and arguments + 1 (all booleans are stored in 1 byte) + 2 (deprecated short) + 2 (string sizes) _name(name), _type(type), _bools(passive, durable, false, false, noWait), diff --git a/src/exchangedeleteframe.h b/src/exchangedeleteframe.h index cdd906f..2fc9e23 100644 --- a/src/exchangedeleteframe.h +++ b/src/exchangedeleteframe.h @@ -73,7 +73,7 @@ public: * @param bool noWait Do not wait for a response */ ExchangeDeleteFrame(uint16_t channel, const std::string& name, bool ifUnused = false, bool noWait = false) : - ExchangeFrame(channel, name.length() + 4), // length of the name, 1 byte for encoding this length, 1 for bools, 2 for deprecated short + ExchangeFrame(channel, (uint32_t)(name.length() + 4)), // length of the name, 1 byte for encoding this length, 1 for bools, 2 for deprecated short _name(name), _bools(ifUnused, noWait) {} diff --git a/src/exchangeunbindframe.h b/src/exchangeunbindframe.h index d92d941..bfd3469 100644 --- a/src/exchangeunbindframe.h +++ b/src/exchangeunbindframe.h @@ -95,7 +95,7 @@ public: * @param arguments */ ExchangeUnbindFrame(uint16_t channel, const std::string &destination, const std::string &source, const std::string &routingKey, bool noWait, const Table &arguments) : - ExchangeFrame(channel, (destination.length() + source.length() + routingKey.length() + arguments.size() + 6)), // 1 for each string, 1 for booleanset, 2 for deprecated field + ExchangeFrame(channel, (uint32_t)(destination.length() + source.length() + routingKey.length() + arguments.size() + 6)), // 1 for each string, 1 for booleanset, 2 for deprecated field _destination(destination), _source(source), _routingKey(routingKey), diff --git a/src/framecheck.h b/src/framecheck.h index ab8c959..675cebe 100644 --- a/src/framecheck.h +++ b/src/framecheck.h @@ -50,7 +50,7 @@ public: virtual ~FrameCheck() { // update the number of bytes to skip - _frame->_skip += _size; + _frame->_skip += (uint32_t)_size; } }; diff --git a/src/includes.h b/src/includes.h index 2a10061..ddabc00 100644 --- a/src/includes.h +++ b/src/includes.h @@ -9,7 +9,7 @@ // c and c++ dependencies #include -#include +#include // TODO cstring #include #include #include @@ -21,14 +21,19 @@ #include #include #include -#include -#include -#include -#include -#include + +#include // TODO is this needed + #include #include +// TODO make this nice +#ifdef _MSC_VER +//not #if defined(_WIN32) || defined(_WIN64) because we have strncasecmp in mingw +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + // forward declarations #include "../include/classes.h" @@ -41,7 +46,6 @@ #include "../include/copiedbuffer.h" #include "../include/watchable.h" #include "../include/monitor.h" -#include "../include/tcpdefines.h" // amqp types #include "../include/field.h" @@ -75,8 +79,6 @@ #include "../include/connectionhandler.h" #include "../include/connectionimpl.h" #include "../include/connection.h" -#include "../include/tcphandler.h" -#include "../include/tcpconnection.h" // classes that are very commonly used #include "../include/exception.h" @@ -91,6 +93,5 @@ #include "queueframe.h" #include "basicframe.h" #include "transactionframe.h" -#include "addressinfo.h" diff --git a/src/queuebindframe.h b/src/queuebindframe.h index bc4d2e9..8418fe1 100644 --- a/src/queuebindframe.h +++ b/src/queuebindframe.h @@ -87,7 +87,7 @@ public: * @param Table arguments additional arguments */ QueueBindFrame(uint16_t channel, const std::string& name, const std::string& exchange, const std::string& routingKey = "", bool noWait = false, const Table& arguments = {}) : - QueueFrame(channel, (name.length() + exchange.length() + routingKey.length() + arguments.size() + 6) ), // 3 extra per string, 1 for bools, 2 for deprecated field + QueueFrame(channel, (uint32_t)(name.length() + exchange.length() + routingKey.length() + arguments.size() + 6) ), // 3 extra per string, 1 for bools, 2 for deprecated field _name(name), _exchange(exchange), _routingKey(routingKey), diff --git a/src/queuedeclareframe.h b/src/queuedeclareframe.h index 475c8b6..627b17c 100644 --- a/src/queuedeclareframe.h +++ b/src/queuedeclareframe.h @@ -80,7 +80,7 @@ public: * @param Table arguments additional arguments, implementation dependent */ QueueDeclareFrame(uint16_t channel, const std::string& name = "", bool passive = false, bool durable = false, bool exclusive = false, bool autoDelete = false, bool noWait = false, const Table& arguments = {}) : - QueueFrame(channel, (name.length() + arguments.size() + 4 ) ), // 1 extra for string size, 1 for bools, 2 for deprecated value + QueueFrame(channel, (uint32_t)(name.length() + arguments.size() + 4 ) ), // 1 extra for string size, 1 for bools, 2 for deprecated value _name(name), _bools(passive, durable, exclusive, autoDelete, noWait), _arguments(arguments) diff --git a/src/queuedeclareokframe.h b/src/queuedeclareokframe.h index 44821a5..99ad1e8 100644 --- a/src/queuedeclareokframe.h +++ b/src/queuedeclareokframe.h @@ -62,7 +62,7 @@ public: * @param failingMethod failing method id if applicable */ QueueDeclareOKFrame(uint16_t channel, const std::string& name, int32_t messageCount, int32_t consumerCount) : - QueueFrame(channel, name.length() + 9), // 4 per int, 1 for string size + QueueFrame(channel, (uint32_t)(name.length() + 9)), // 4 per int, 1 for string size _name(name), _messageCount(messageCount), _consumerCount(consumerCount) diff --git a/src/queuedeleteframe.h b/src/queuedeleteframe.h index dc9a00a..f76e736 100644 --- a/src/queuedeleteframe.h +++ b/src/queuedeleteframe.h @@ -69,7 +69,7 @@ public: * @param noWait do not wait on response */ QueueDeleteFrame(uint16_t channel, const std::string& name, bool ifUnused = false, bool ifEmpty = false, bool noWait = false) : - QueueFrame(channel, name.length() + 4), // 1 for string length, 1 for bools, 2 for deprecated field + QueueFrame(channel, (uint32_t)(name.length() + 4)), // 1 for string length, 1 for bools, 2 for deprecated field _name(name), _bools(ifUnused, ifEmpty, noWait) {} diff --git a/src/queuepurgeframe.h b/src/queuepurgeframe.h index 08cfad5..016cfd0 100644 --- a/src/queuepurgeframe.h +++ b/src/queuepurgeframe.h @@ -65,7 +65,7 @@ public: * @return newly created Queuepurgeframe */ QueuePurgeFrame(uint16_t channel, const std::string& name, bool noWait = false) : - QueueFrame(channel, name.length() + 4), // 1 extra for string length, 1 for bool, 2 for deprecated field + QueueFrame(channel, (uint32_t)(name.length() + 4)), // 1 extra for string length, 1 for bool, 2 for deprecated field _name(name), _noWait(noWait) {} diff --git a/src/queueunbindframe.h b/src/queueunbindframe.h index 8460663..e40b3b9 100644 --- a/src/queueunbindframe.h +++ b/src/queueunbindframe.h @@ -80,7 +80,7 @@ public: * @param arguments additional arguments, implementation dependant. */ QueueUnbindFrame(uint16_t channel, const std::string& name, const std::string& exchange, const std::string& routingKey = "", const Table& arguments = {} ) : - QueueFrame(channel, (name.length() + exchange.length() + routingKey.length() + arguments.size() + 5) ), // 1 per string, 2 for deprecated field + QueueFrame(channel, (uint32_t)(name.length() + exchange.length() + routingKey.length() + arguments.size() + 5) ), // 1 per string, 2 for deprecated field _name(name), _exchange(exchange), _routingKey(routingKey), diff --git a/src/table.cpp b/src/table.cpp index ac2fd45..d4177f4 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -21,7 +21,7 @@ Table::Table(ReceivedFrame &frame) ShortString name(frame); // subtract number of bytes to read, plus one byte for the decoded type - bytesToRead -= (name.size() + 1); + bytesToRead -= (uint32_t)(name.size() + 1); // get the field Field *field = Field::decode(frame); @@ -31,7 +31,7 @@ Table::Table(ReceivedFrame &frame) _fields[name] = std::shared_ptr(field); // subtract size - bytesToRead -= field->size(); + bytesToRead -= (uint32_t)field->size(); } } diff --git a/src/tcpconnection/CMakeLists.txt b/src/tcpconnection/CMakeLists.txt new file mode 100644 index 0000000..d5cb67f --- /dev/null +++ b/src/tcpconnection/CMakeLists.txt @@ -0,0 +1,3 @@ +add_sources( + tcpconnection.cpp +) diff --git a/src/tcpconnection/includes.h b/src/tcpconnection/includes.h new file mode 100644 index 0000000..2a10061 --- /dev/null +++ b/src/tcpconnection/includes.h @@ -0,0 +1,96 @@ +/** + * Includes.h + * + * The includes that are necessary to compile the AMQP library + * This file also holds includes that may not be necessary for including the library + * + * @documentation private + */ + +// c and c++ dependencies +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// forward declarations +#include "../include/classes.h" + +// utility classes +#include "../include/endian.h" +#include "../include/buffer.h" +#include "../include/bytebuffer.h" +#include "../include/receivedframe.h" +#include "../include/outbuffer.h" +#include "../include/copiedbuffer.h" +#include "../include/watchable.h" +#include "../include/monitor.h" +#include "../include/tcpdefines.h" + +// amqp types +#include "../include/field.h" +#include "../include/numericfield.h" +#include "../include/decimalfield.h" +#include "../include/stringfield.h" +#include "../include/booleanset.h" +#include "../include/fieldproxy.h" +#include "../include/table.h" +#include "../include/array.h" + +// envelope for publishing and consuming +#include "../include/metadata.h" +#include "../include/envelope.h" +#include "../include/message.h" + +// mid level includes +#include "../include/exchangetype.h" +#include "../include/flags.h" +#include "../include/callbacks.h" +#include "../include/deferred.h" +#include "../include/deferredconsumer.h" +#include "../include/deferredqueue.h" +#include "../include/deferreddelete.h" +#include "../include/deferredcancel.h" +#include "../include/deferredget.h" +#include "../include/channelimpl.h" +#include "../include/channel.h" +#include "../include/login.h" +#include "../include/address.h" +#include "../include/connectionhandler.h" +#include "../include/connectionimpl.h" +#include "../include/connection.h" +#include "../include/tcphandler.h" +#include "../include/tcpconnection.h" + +// classes that are very commonly used +#include "../include/exception.h" +#include "../include/protocolexception.h" +#include "../include/frame.h" +#include "extframe.h" +#include "methodframe.h" +#include "headerframe.h" +#include "connectionframe.h" +#include "channelframe.h" +#include "exchangeframe.h" +#include "queueframe.h" +#include "basicframe.h" +#include "transactionframe.h" +#include "addressinfo.h" + + diff --git a/src/tcpclosed.h b/src/tcpconnection/tcpclosed.h similarity index 100% rename from src/tcpclosed.h rename to src/tcpconnection/tcpclosed.h diff --git a/src/tcpconnected.h b/src/tcpconnection/tcpconnected.h similarity index 100% rename from src/tcpconnected.h rename to src/tcpconnection/tcpconnected.h diff --git a/src/tcpconnection.cpp b/src/tcpconnection/tcpconnection.cpp similarity index 100% rename from src/tcpconnection.cpp rename to src/tcpconnection/tcpconnection.cpp diff --git a/src/tcpinbuffer.h b/src/tcpconnection/tcpinbuffer.h similarity index 100% rename from src/tcpinbuffer.h rename to src/tcpconnection/tcpinbuffer.h diff --git a/src/tcpoutbuffer.h b/src/tcpconnection/tcpoutbuffer.h similarity index 100% rename from src/tcpoutbuffer.h rename to src/tcpconnection/tcpoutbuffer.h diff --git a/src/tcpresolver.h b/src/tcpconnection/tcpresolver.h similarity index 100% rename from src/tcpresolver.h rename to src/tcpconnection/tcpresolver.h diff --git a/src/tcpstate.h b/src/tcpconnection/tcpstate.h similarity index 100% rename from src/tcpstate.h rename to src/tcpconnection/tcpstate.h From 50761bd40d1a7c8106166b6279f30319ef408cca Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 17:12:38 +0100 Subject: [PATCH 034/168] CMakefile cleanup. No functional changes. --- CMakeLists.txt | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 34f19e4..5d242a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,34 +21,29 @@ macro (add_sources) endmacro() add_subdirectory(src) -if(AMQPCPP_BUILD_TCP) +if(BUILD_TCP) add_subdirectory(src/tcpconnection) endif() option(BUILD_SHARED "build shared library" OFF) if(BUILD_SHARED) - add_library(amqpcpp SHARED ${SRCS}) - set_target_properties(amqpcpp PROPERTIES SOVERSION 2.7) - install(TARGETS amqpcpp - LIBRARY DESTINATION lib - ) + add_library(${PROJECT_NAME} SHARED ${SRCS}) + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.7) # TODO version incorrect + install(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION lib + ) else() - add_library(amqpcpp STATIC ${SRCS}) - install(TARGETS amqpcpp - ARCHIVE DESTINATION lib - ) + add_library(${PROJECT_NAME} STATIC ${SRCS}) + install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION lib + ) endif() Include_directories(${PROJECT_SOURCE_DIR}) install(DIRECTORY include/ DESTINATION include/amqpcpp FILES_MATCHING PATTERN "*.h") install(FILES amqpcpp.h DESTINATION include) -option(BUILD_TUTORIALS "build rabbitmq tutorials" OFF) -if(BUILD_TUTORIALS) -# add_subdirectory(examples/rabbitmq_tutorials) -endif() - set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) From 9935f8414bdda7854bb43c18723fd56b1529a36e Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 23 Jan 2018 17:17:21 +0100 Subject: [PATCH 035/168] update copyright year when connection is established to rabbitmq --- src/connectionstartframe.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/connectionstartframe.h b/src/connectionstartframe.h index 139d2b9..6bea81c 100644 --- a/src/connectionstartframe.h +++ b/src/connectionstartframe.h @@ -196,8 +196,8 @@ public: properties["product"] = "Copernica AMQP library"; properties["version"] = "Unknown"; properties["platform"] = "Unknown"; - properties["copyright"] = "Copyright 2015 Copernica BV"; - properties["information"] = "http://www.copernica.com"; + properties["copyright"] = "Copyright 2015 - 2018 Copernica BV"; + properties["information"] = "https://www.copernica.com"; properties["capabilities"] = capabilities; // move connection to handshake mode From 5701d28b2b5fa20f6836b08689f8d4c4de070965 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 18:49:59 +0100 Subject: [PATCH 036/168] added BUILD_TCP option to build the linux sockets implementation. --- .gitignore | 2 ++ include/stringfield.h | 5 ++--- src/{ => tcpconnection}/addressinfo.h | 0 src/tcpconnection/includes.h | 24 +++++++++++++++++++++--- src/{ => tcpconnection}/pipe.h | 0 5 files changed, 25 insertions(+), 6 deletions(-) rename src/{ => tcpconnection}/addressinfo.h (100%) rename src/{ => tcpconnection}/pipe.h (100%) diff --git a/.gitignore b/.gitignore index 3235c3a..7d920ab 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ *.a.* /build /.vscode +.atom-build.cson +.atom-dbg.cson diff --git a/include/stringfield.h b/include/stringfield.h index b4836f4..f9dd7da 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -119,7 +119,7 @@ public: virtual size_t size() const override { // find out size of the size parameter - T size((T::Type)_data.size()); + T size(_data.size()); // size of the uint8 or uint32 + the actual string size return size.size() + _data.size(); @@ -160,7 +160,7 @@ public: virtual void fill(OutBuffer& buffer) const override { // create size - T size((T::Type)_data.size()); + T size(_data.size()); // first, write down the size of the string size.fill(buffer); @@ -210,4 +210,3 @@ typedef StringField LongString; * end namespace */ } - diff --git a/src/addressinfo.h b/src/tcpconnection/addressinfo.h similarity index 100% rename from src/addressinfo.h rename to src/tcpconnection/addressinfo.h diff --git a/src/tcpconnection/includes.h b/src/tcpconnection/includes.h index 2a10061..1b24322 100644 --- a/src/tcpconnection/includes.h +++ b/src/tcpconnection/includes.h @@ -1,12 +1,31 @@ /** * Includes.h * - * The includes that are necessary to compile the AMQP library + * The includes that are necessary to compile the optional TCP part of the AMQP library * This file also holds includes that may not be necessary for including the library * * @documentation private */ +#include "../includes.h" +// c and c++ dependencies +#include +#include +#include +#include +#include + +// utility classes +#include "../../include/tcpconnection/tcpdefines.h" + +// mid level includes +#include "../../include/tcpconnection/tcphandler.h" +#include "../../include/tcpconnection/tcpconnection.h" + +// classes that are very commonly used +#include "addressinfo.h" + +/* // c and c++ dependencies #include #include @@ -92,5 +111,4 @@ #include "basicframe.h" #include "transactionframe.h" #include "addressinfo.h" - - +*/ diff --git a/src/pipe.h b/src/tcpconnection/pipe.h similarity index 100% rename from src/pipe.h rename to src/tcpconnection/pipe.h From 4652a9cbdfee3b49c7cc64e8efdb93dfc9c4a386 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Tue, 23 Jan 2018 18:55:46 +0100 Subject: [PATCH 037/168] libboostasio.h does not compile with clang-3.9 due to following error when compile if cpp17 preprocessor check: embedding a directive within macro arguments has undefined behavior [-Werror,-Wembedded-directive]. Fix this by moving the bind away from preprocessor macro. Replace the STRAND_SOCKET_HANDLER, STRAND_TIMER_HANDLER macros with template functions. --- include/libboostasio.h | 134 ++++++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index f0d4ed4..96058c1 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -1,5 +1,4 @@ -/** - * LibBoostAsio.h +bBoostAsio.h * * Implementation for the AMQP::TcpHandler that is optimized for boost::asio. You can * use this class instead of a AMQP::TcpHandler class, just pass the boost asio service @@ -27,33 +26,35 @@ #include -/////////////////////////////////////////////////////////////////// -#define STRAND_SOCKET_HANDLER(_fn) \ -[fn = _fn, strand = _strand](const boost::system::error_code &ec, \ - const std::size_t bytes_transferred) \ -{ \ - const std::shared_ptr apStrand = strand.lock(); \ - if (!apStrand) \ - { \ - fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled),std::size_t{0}); \ - return; \ - } \ - \ - apStrand->dispatch(boost::bind(fn,ec,bytes_transferred)); \ +template +void strand_sock_dispatch_func( + Func fn, + std::weak_ptr strand, + const boost::system::error_code &ec, + const std::size_t bytes_transferred +) { + const std::shared_ptr apStrand = strand.lock(); + if (!apStrand) + { + fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled), std::size_t{0}); + return; + } + apStrand->dispatch(boost::bind(fn, ec, bytes_transferred)); } -/////////////////////////////////////////////////////////////////// -#define STRAND_TIMER_HANDLER(_fn) \ -[fn = _fn, strand = _strand](const boost::system::error_code &ec) \ -{ \ - const std::shared_ptr apStrand = strand.lock(); \ - if (!apStrand) \ - { \ - fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); \ - return; \ - } \ - \ - apStrand->dispatch(boost::bind(fn,ec)); \ +template +void strand_timer_dispatch_func( + Func fn, + std::weak_ptr strand, + const boost::system::error_code &ec +) { + const std::shared_ptr apStrand = strand.lock(); + if (!apStrand) + { + fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); + return; + } + apStrand->dispatch(boost::bind(fn,ec)); } /** @@ -148,9 +149,7 @@ private: _read_pending = true; - _socket.async_read_some(boost::asio::null_buffers(), - STRAND_SOCKET_HANDLER( - boost::bind(&Watcher::read_handler, + auto func = boost::bind(&Watcher::read_handler, this, boost::arg<1>(), boost::arg<2>(), @@ -161,7 +160,13 @@ private: shared_from_this(), #endif connection, - fd))); + fd); + _socket.async_read_some( + boost::asio::null_buffers(), + [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { + strand_sock_dispatch_func(f, strand, ec, bytes_transferred); + } + ); } } @@ -193,9 +198,7 @@ private: _write_pending = true; - _socket.async_write_some(boost::asio::null_buffers(), - STRAND_SOCKET_HANDLER( - boost::bind(&Watcher::write_handler, + auto func = boost::bind(&Watcher::write_handler, this, boost::arg<1>(), boost::arg<2>(), @@ -206,7 +209,13 @@ private: shared_from_this(), #endif connection, - fd))); + fd); + _socket.async_write_some( + boost::asio::null_buffers(), + [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { + strand_sock_dispatch_func(f, strand, ec, bytes_transferred); + } + ); } } @@ -262,9 +271,7 @@ private: { _read_pending = true; - _socket.async_read_some(boost::asio::null_buffers(), - STRAND_SOCKET_HANDLER( - boost::bind(&Watcher::read_handler, + auto func = boost::bind(&Watcher::read_handler, this, boost::arg<1>(), boost::arg<2>(), @@ -275,7 +282,13 @@ private: shared_from_this(), #endif connection, - fd))); + fd); + _socket.async_read_some( + boost::asio::null_buffers(), + [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { + strand_sock_dispatch_func(f, strand, ec, bytes_transferred); + } + ); } // 2. Handle writes? @@ -286,9 +299,7 @@ private: { _write_pending = true; - _socket.async_write_some(boost::asio::null_buffers(), - STRAND_SOCKET_HANDLER( - boost::bind(&Watcher::write_handler, + auto func = boost::bind(&Watcher::write_handler, this, boost::arg<1>(), boost::arg<2>(), @@ -299,7 +310,13 @@ private: shared_from_this(), #endif connection, - fd))); + fd); + _socket.async_write_some( + boost::asio::null_buffers(), + [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { + strand_sock_dispatch_func(f, strand, ec, bytes_transferred); + } + ); } } }; @@ -353,12 +370,7 @@ private: 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(STRAND_TIMER_HANDLER( - boost::bind(&Timer::timeout, + auto func = boost::bind(&Timer::timeout, this, boost::arg<1>(), // C++17 has 'weak_from_this()' support. @@ -368,7 +380,17 @@ private: shared_from_this(), #endif connection, - timeout))); + timeout); + + // Reschedule the timer for the future: + _timer.expires_at(_timer.expires_at() + boost::posix_time::seconds(timeout)); + + // Posts the timer event + _timer.async_wait( + [f = func, strand = _strand](const boost::system::error_code &ec) { + strand_timer_dispatch_func(f, strand, ec); + } + ); } } @@ -423,9 +445,7 @@ private: // stop timer in case it was already set stop(); - _timer.expires_from_now(boost::posix_time::seconds(timeout)); - _timer.async_wait(STRAND_TIMER_HANDLER( - boost::bind(&Timer::timeout, + auto func = boost::bind(&Timer::timeout, this, boost::arg<1>(), // C++17 has 'weak_from_this()' support. @@ -435,7 +455,13 @@ private: shared_from_this(), #endif connection, - timeout))); + timeout); + _timer.expires_from_now(boost::posix_time::seconds(timeout)); + _timer.async_wait( + [f = func, strand = _strand](const boost::system::error_code &ec) { + strand_timer_dispatch_func(f, strand, ec); + } + ); } }; From 001bf3871548cb56ceef3b4926bb3240c107f50f Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 18:58:51 +0100 Subject: [PATCH 038/168] Set output directory to bin/ --- .gitignore | 1 + CMakeLists.txt | 4 ++++ set_cxx_norm.cmake | 55 ---------------------------------------------- 3 files changed, 5 insertions(+), 55 deletions(-) delete mode 100644 set_cxx_norm.cmake diff --git a/.gitignore b/.gitignore index 7d920ab..3e127e5 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ /.vscode .atom-build.cson .atom-dbg.cson +/bin \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d242a9..1fc29d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,11 @@ cmake_minimum_required(VERSION 3.1) +# project name project(amqpcpp) +# set output directory +set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) + # ensure c++11 on all compilers set (CMAKE_CXX_STANDARD 11) diff --git a/set_cxx_norm.cmake b/set_cxx_norm.cmake deleted file mode 100644 index da2af82..0000000 --- a/set_cxx_norm.cmake +++ /dev/null @@ -1,55 +0,0 @@ -# Version -cmake_minimum_required(VERSION 2.6.3) - -set(CXX_NORM_CXX98 1) # C++98 -set(CXX_NORM_CXX03 2) # C++03 -set(CXX_NORM_CXX11 3) # C++11 - -# - Set the wanted C++ norm -# Adds the good argument to the command line in function of the compiler -# Currently only works with g++ and clang++ -macro(set_cxx_norm NORM) - - # Extract c++ compiler --version output - exec_program( - ${CMAKE_CXX_COMPILER} - ARGS --version - OUTPUT_VARIABLE _compiler_output - ) - # Keep only the first line - string(REGEX REPLACE - "(\n.*$)" - "" - cxx_compiler_version "${_compiler_output}" - ) - # Extract the version number - string(REGEX REPLACE - "([^0-9.])|([0-9.][^0-9.])" - "" - cxx_compiler_version "${cxx_compiler_version}" - ) - - if(CMAKE_COMPILER_IS_GNUCXX) - - if(${NORM} EQUAL ${CXX_NORM_CXX98}) - add_definitions("-std=c++98") - elseif(${NORM} EQUAL ${CXX_NORM_CXX03}) - add_definitions("-std=c++03") - elseif(${NORM} EQUAL ${CXX_NORM_CXX11}) - if(${cxx_compiler_version} VERSION_LESS "4.7.0") - add_definitions("-std=c++0x") - else() - add_definitions("-std=c++11") - endif() - endif() - - elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") - - if(${NORM} EQUAL ${CXX_NORM_CXX11}) - add_definitions("-std=c++11") - endif() - - endif() - -endmacro() - From b1be2267f6885a909ca28c1c5509e90fdbfc8847 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 19:02:46 +0100 Subject: [PATCH 039/168] comments --- src/tcpconnection/includes.h | 91 +----------------------------------- 1 file changed, 2 insertions(+), 89 deletions(-) diff --git a/src/tcpconnection/includes.h b/src/tcpconnection/includes.h index 1b24322..6623eba 100644 --- a/src/tcpconnection/includes.h +++ b/src/tcpconnection/includes.h @@ -6,6 +6,7 @@ * * @documentation private */ +// include files from main library #include "../includes.h" // c and c++ dependencies @@ -23,92 +24,4 @@ #include "../../include/tcpconnection/tcpconnection.h" // classes that are very commonly used -#include "addressinfo.h" - -/* -// c and c++ dependencies -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forward declarations -#include "../include/classes.h" - -// utility classes -#include "../include/endian.h" -#include "../include/buffer.h" -#include "../include/bytebuffer.h" -#include "../include/receivedframe.h" -#include "../include/outbuffer.h" -#include "../include/copiedbuffer.h" -#include "../include/watchable.h" -#include "../include/monitor.h" -#include "../include/tcpdefines.h" - -// amqp types -#include "../include/field.h" -#include "../include/numericfield.h" -#include "../include/decimalfield.h" -#include "../include/stringfield.h" -#include "../include/booleanset.h" -#include "../include/fieldproxy.h" -#include "../include/table.h" -#include "../include/array.h" - -// envelope for publishing and consuming -#include "../include/metadata.h" -#include "../include/envelope.h" -#include "../include/message.h" - -// mid level includes -#include "../include/exchangetype.h" -#include "../include/flags.h" -#include "../include/callbacks.h" -#include "../include/deferred.h" -#include "../include/deferredconsumer.h" -#include "../include/deferredqueue.h" -#include "../include/deferreddelete.h" -#include "../include/deferredcancel.h" -#include "../include/deferredget.h" -#include "../include/channelimpl.h" -#include "../include/channel.h" -#include "../include/login.h" -#include "../include/address.h" -#include "../include/connectionhandler.h" -#include "../include/connectionimpl.h" -#include "../include/connection.h" -#include "../include/tcphandler.h" -#include "../include/tcpconnection.h" - -// classes that are very commonly used -#include "../include/exception.h" -#include "../include/protocolexception.h" -#include "../include/frame.h" -#include "extframe.h" -#include "methodframe.h" -#include "headerframe.h" -#include "connectionframe.h" -#include "channelframe.h" -#include "exchangeframe.h" -#include "queueframe.h" -#include "basicframe.h" -#include "transactionframe.h" -#include "addressinfo.h" -*/ +#include "addressinfo.h" \ No newline at end of file From 27f765b5f7339d65ae8409976a8147e378560d73 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 19:15:10 +0100 Subject: [PATCH 040/168] linux tcp is now opt-out instead of opt-in(to keep compatibility with the original lib). --- CMakeLists.txt | 22 ++++++++++++------- .../{tcpconnection => linux_tcp}/tcpchannel.h | 0 .../tcpconnection.h | 0 .../{tcpconnection => linux_tcp}/tcpdefines.h | 0 .../{tcpconnection => linux_tcp}/tcphandler.h | 0 .../CMakeLists.txt | 0 .../addressinfo.h | 0 src/{tcpconnection => linux_tcp}/includes.h | 0 src/{tcpconnection => linux_tcp}/pipe.h | 0 src/{tcpconnection => linux_tcp}/tcpclosed.h | 0 .../tcpconnected.h | 0 .../tcpconnection.cpp | 0 .../tcpinbuffer.h | 0 .../tcpoutbuffer.h | 0 .../tcpresolver.h | 0 src/{tcpconnection => linux_tcp}/tcpstate.h | 0 16 files changed, 14 insertions(+), 8 deletions(-) rename include/{tcpconnection => linux_tcp}/tcpchannel.h (100%) rename include/{tcpconnection => linux_tcp}/tcpconnection.h (100%) rename include/{tcpconnection => linux_tcp}/tcpdefines.h (100%) rename include/{tcpconnection => linux_tcp}/tcphandler.h (100%) rename src/{tcpconnection => linux_tcp}/CMakeLists.txt (100%) rename src/{tcpconnection => linux_tcp}/addressinfo.h (100%) rename src/{tcpconnection => linux_tcp}/includes.h (100%) rename src/{tcpconnection => linux_tcp}/pipe.h (100%) rename src/{tcpconnection => linux_tcp}/tcpclosed.h (100%) rename src/{tcpconnection => linux_tcp}/tcpconnected.h (100%) rename src/{tcpconnection => linux_tcp}/tcpconnection.cpp (100%) rename src/{tcpconnection => linux_tcp}/tcpinbuffer.h (100%) rename src/{tcpconnection => linux_tcp}/tcpoutbuffer.h (100%) rename src/{tcpconnection => linux_tcp}/tcpresolver.h (100%) rename src/{tcpconnection => linux_tcp}/tcpstate.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fc29d9..d99d9e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.1) # project name project(amqpcpp) +# build options +option(BUILD_PURE "build in pure mode" OFF) + # set output directory set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) @@ -24,13 +27,20 @@ macro (add_sources) endif() endmacro() +# add source files add_subdirectory(src) -if(BUILD_TCP) - add_subdirectory(src/tcpconnection) +if(NOT NO_LINUX_TCP) + add_subdirectory(src/linux_tcp) endif() -option(BUILD_SHARED "build shared library" OFF) +# we have to prevent windows from defining the max macro. TODO more +if (WIN32) + add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) +endif() + +# TODO Cleanup into this part of the cmakefile +option(BUILD_SHARED "build shared library" OFF) if(BUILD_SHARED) add_library(${PROJECT_NAME} SHARED ${SRCS}) set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.7) # TODO version incorrect @@ -49,8 +59,4 @@ install(DIRECTORY include/ DESTINATION include/amqpcpp install(FILES amqpcpp.h DESTINATION include) set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) -set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) - -if (WIN32) - add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) -endif() +set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) \ No newline at end of file diff --git a/include/tcpconnection/tcpchannel.h b/include/linux_tcp/tcpchannel.h similarity index 100% rename from include/tcpconnection/tcpchannel.h rename to include/linux_tcp/tcpchannel.h diff --git a/include/tcpconnection/tcpconnection.h b/include/linux_tcp/tcpconnection.h similarity index 100% rename from include/tcpconnection/tcpconnection.h rename to include/linux_tcp/tcpconnection.h diff --git a/include/tcpconnection/tcpdefines.h b/include/linux_tcp/tcpdefines.h similarity index 100% rename from include/tcpconnection/tcpdefines.h rename to include/linux_tcp/tcpdefines.h diff --git a/include/tcpconnection/tcphandler.h b/include/linux_tcp/tcphandler.h similarity index 100% rename from include/tcpconnection/tcphandler.h rename to include/linux_tcp/tcphandler.h diff --git a/src/tcpconnection/CMakeLists.txt b/src/linux_tcp/CMakeLists.txt similarity index 100% rename from src/tcpconnection/CMakeLists.txt rename to src/linux_tcp/CMakeLists.txt diff --git a/src/tcpconnection/addressinfo.h b/src/linux_tcp/addressinfo.h similarity index 100% rename from src/tcpconnection/addressinfo.h rename to src/linux_tcp/addressinfo.h diff --git a/src/tcpconnection/includes.h b/src/linux_tcp/includes.h similarity index 100% rename from src/tcpconnection/includes.h rename to src/linux_tcp/includes.h diff --git a/src/tcpconnection/pipe.h b/src/linux_tcp/pipe.h similarity index 100% rename from src/tcpconnection/pipe.h rename to src/linux_tcp/pipe.h diff --git a/src/tcpconnection/tcpclosed.h b/src/linux_tcp/tcpclosed.h similarity index 100% rename from src/tcpconnection/tcpclosed.h rename to src/linux_tcp/tcpclosed.h diff --git a/src/tcpconnection/tcpconnected.h b/src/linux_tcp/tcpconnected.h similarity index 100% rename from src/tcpconnection/tcpconnected.h rename to src/linux_tcp/tcpconnected.h diff --git a/src/tcpconnection/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp similarity index 100% rename from src/tcpconnection/tcpconnection.cpp rename to src/linux_tcp/tcpconnection.cpp diff --git a/src/tcpconnection/tcpinbuffer.h b/src/linux_tcp/tcpinbuffer.h similarity index 100% rename from src/tcpconnection/tcpinbuffer.h rename to src/linux_tcp/tcpinbuffer.h diff --git a/src/tcpconnection/tcpoutbuffer.h b/src/linux_tcp/tcpoutbuffer.h similarity index 100% rename from src/tcpconnection/tcpoutbuffer.h rename to src/linux_tcp/tcpoutbuffer.h diff --git a/src/tcpconnection/tcpresolver.h b/src/linux_tcp/tcpresolver.h similarity index 100% rename from src/tcpconnection/tcpresolver.h rename to src/linux_tcp/tcpresolver.h diff --git a/src/tcpconnection/tcpstate.h b/src/linux_tcp/tcpstate.h similarity index 100% rename from src/tcpconnection/tcpstate.h rename to src/linux_tcp/tcpstate.h From d71040b2e564a9f0c23cca2bffc441abd513f918 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Tue, 23 Jan 2018 19:16:44 +0100 Subject: [PATCH 041/168] fix bad opening comment block --- include/libboostasio.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 96058c1..dd229f9 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -1,4 +1,5 @@ -bBoostAsio.h +/** + * LibBoostAsio.h * * Implementation for the AMQP::TcpHandler that is optimized for boost::asio. You can * use this class instead of a AMQP::TcpHandler class, just pass the boost asio service From 80850810d307cae76be162e128109d57de10d4b3 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 19:17:49 +0100 Subject: [PATCH 042/168] fixed compile error at linux_tcp --- src/linux_tcp/includes.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/linux_tcp/includes.h b/src/linux_tcp/includes.h index 6623eba..0acb43e 100644 --- a/src/linux_tcp/includes.h +++ b/src/linux_tcp/includes.h @@ -17,11 +17,11 @@ #include // utility classes -#include "../../include/tcpconnection/tcpdefines.h" +#include "../../include/linux_tcp/tcpdefines.h" // mid level includes -#include "../../include/tcpconnection/tcphandler.h" -#include "../../include/tcpconnection/tcpconnection.h" +#include "../../include/linux_tcp/tcphandler.h" +#include "../../include/linux_tcp/tcpconnection.h" // classes that are very commonly used -#include "addressinfo.h" \ No newline at end of file +#include "addressinfo.h" From 715b683867fb15c9d283cb0233a68007f01899da Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 19:22:33 +0100 Subject: [PATCH 043/168] restore ignore file for make. --- src/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/.gitignore diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.d From 271cb5b60d6dbc91360d4af3bb14a6836d5cb102 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 23 Jan 2018 19:27:11 +0100 Subject: [PATCH 044/168] linux sockets opt-in again. we're breaking some other stuff anyway, so better do it well. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d99d9e6..8f9170f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.1) project(amqpcpp) # build options -option(BUILD_PURE "build in pure mode" OFF) +option(LINUX_TCP "Build linux sockets implementation" OFF) # set output directory set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) @@ -29,7 +29,7 @@ endmacro() # add source files add_subdirectory(src) -if(NOT NO_LINUX_TCP) +if(LINUX_TCP) add_subdirectory(src/linux_tcp) endif() From 9863fd87ee767adc14c0c69fb6e993ecacd0b05c Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 23 Jan 2018 20:23:11 +0000 Subject: [PATCH 045/168] Added travis.yml file. --- .travis.yml | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4adc133 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,112 @@ +################ +# project config +################ + +# C++ project +language: cpp + +dist: trusty +sudo: required +group: edge + + +################ +# build matrix +################ + +matrix: + include: + + ################ + # Linux / GCC + ################ + + - os: linux + compiler: gcc + env: + - COMPILER=g++-4.9 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-4.9', 'ninja-build'] + + - os: linux + compiler: gcc + env: + - COMPILER=g++-5 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-5', 'ninja-build'] + + - os: linux + compiler: gcc + env: + - COMPILER=g++-6 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-6', 'ninja-build'] + + - os: linux + compiler: gcc + env: + - COMPILER=g++-7 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-7', 'ninja-build'] + + ################ + # Linux / Clang + ################ + + - os: linux + compiler: clang + env: + - COMPILER=clang++-3.9 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-6', 'clang-3.9', 'ninja-build'] + + - os: linux + compiler: clang + env: + - COMPILER=clang++-4.0 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0'] + packages: ['clang-4.0', 'ninja-build'] + + - os: linux + compiler: clang + env: + - COMPILER=clang++-5.0 + - CXXFLAGS=-std=c++11 + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] + packages: ['clang-5.0', 'ninja-build'] + +################ +# build / test +################ + +script: + + # show OS/compiler version + - uname -a + - $CXX --version + + # compile and execute unit tests + - mkdir -p build.release && cd build.release + - cmake .. ${CMAKE_OPTIONS} -GNinja && cmake --build . --config Release + - cd .. + From 1fb885d334a88c3176da234c38c937b220c449aa Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 23 Jan 2018 20:46:23 +0000 Subject: [PATCH 046/168] Fixed failed builds. Removed some os/compiler combinations that I couldn't get working. --- .travis.yml | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4adc133..434fb28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,31 +21,19 @@ matrix: # Linux / GCC ################ - - os: linux - compiler: gcc - env: - - COMPILER=g++-4.9 - - CXXFLAGS=-std=c++11 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-4.9', 'ninja-build'] - - os: linux compiler: gcc env: - COMPILER=g++-5 - - CXXFLAGS=-std=c++11 addons: apt: - sources: ['ubuntu-toolchain-r-test'] + #sources: ['ubuntu-toolchain-r-test'] packages: ['g++-5', 'ninja-build'] - os: linux compiler: gcc env: - COMPILER=g++-6 - - CXXFLAGS=-std=c++11 addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -55,7 +43,6 @@ matrix: compiler: gcc env: - COMPILER=g++-7 - - CXXFLAGS=-std=c++11 addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -65,21 +52,10 @@ matrix: # Linux / Clang ################ - - os: linux - compiler: clang - env: - - COMPILER=clang++-3.9 - - CXXFLAGS=-std=c++11 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'clang-3.9', 'ninja-build'] - - os: linux compiler: clang env: - COMPILER=clang++-4.0 - - CXXFLAGS=-std=c++11 addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0'] @@ -89,7 +65,6 @@ matrix: compiler: clang env: - COMPILER=clang++-5.0 - - CXXFLAGS=-std=c++11 addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] From e9307533fadd01605307a3edeb7976773f8fb27e Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 23 Jan 2018 21:12:31 +0000 Subject: [PATCH 047/168] Fixed failed builds. --- .travis.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 434fb28..06f69b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,17 +23,15 @@ matrix: - os: linux compiler: gcc - env: - - COMPILER=g++-5 + env: COMPILER=g++-5 addons: apt: - #sources: ['ubuntu-toolchain-r-test'] + sources: ['ubuntu-toolchain-r-test'] packages: ['g++-5', 'ninja-build'] - os: linux compiler: gcc - env: - - COMPILER=g++-6 + env: COMPILER=g++-6 addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -41,8 +39,7 @@ matrix: - os: linux compiler: gcc - env: - - COMPILER=g++-7 + env: COMPILER=g++-7 addons: apt: sources: ['ubuntu-toolchain-r-test'] @@ -54,8 +51,7 @@ matrix: - os: linux compiler: clang - env: - - COMPILER=clang++-4.0 + env: COMPILER=clang++-4.0 addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0'] @@ -63,8 +59,7 @@ matrix: - os: linux compiler: clang - env: - - COMPILER=clang++-5.0 + env: COMPILER=clang++-5.0 addons: apt: sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] From 4072430fb317af91820dd67b2738e8fa967b7032 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 00:19:55 +0100 Subject: [PATCH 048/168] Attempt to fix warning C4267 --- include/stringfield.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/stringfield.h b/include/stringfield.h index f9dd7da..85ddef9 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -119,7 +119,7 @@ public: virtual size_t size() const override { // find out size of the size parameter - T size(_data.size()); + T size((T::Type)_data.size()); // size of the uint8 or uint32 + the actual string size return size.size() + _data.size(); @@ -160,7 +160,7 @@ public: virtual void fill(OutBuffer& buffer) const override { // create size - T size(_data.size()); + T size((T::Type)_data.size()); // first, write down the size of the string size.fill(buffer); From b211fc7779687ad5e9e8bed5a5ac69329dfb9c65 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 00:38:07 +0100 Subject: [PATCH 049/168] fixed 32 bit warnings. --- include/message.h | 10 +++++----- src/connectionimpl.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/message.h b/include/message.h index 21a5767..5b1bb02 100644 --- a/include/message.h +++ b/include/message.h @@ -94,10 +94,10 @@ protected: size = std::min(size, _bodySize - _filled); // append more data - memcpy(_body + _filled, buffer, size); + memcpy(_body + _filled, buffer, (size_t)size); // update filled data - _filled += size; + _filled += (size_t)size; } else if (size >= _bodySize) { @@ -108,16 +108,16 @@ protected: else { // allocate the buffer - _body = (char *)malloc(_bodySize); + _body = (char *)malloc((size_t)_bodySize); // remember that the buffer was allocated, so that the destructor can get rid of it _allocated = true; // append more data - memcpy(_body, buffer, std::min(size, _bodySize)); + memcpy(_body, buffer, std::min((size_t)size, (size_t)_bodySize)); // update filled data - _filled = std::min(size, _bodySize); + _filled = std::min((size_t)size, (size_t)_bodySize); } // check if we're done diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 756e084..332e106 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -125,7 +125,7 @@ uint64_t ConnectionImpl::parse(const Buffer &buffer) try { // try to recognize the frame - ReducedBuffer reduced_buf(buffer, processed); + ReducedBuffer reduced_buf(buffer, (size_t)processed); ReceivedFrame receivedFrame(reduced_buf, _maxFrame); // do we have the full frame? From 4c7a71e3a6de98f1125058a5f8b47cce47660820 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 00:59:46 +0100 Subject: [PATCH 050/168] Fixed compile error on g++ caused by fixing warning. --- include/numericfield.h | 3 +-- include/stringfield.h | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/include/numericfield.h b/include/numericfield.h index 5c1e8a1..a54c3f8 100644 --- a/include/numericfield.h +++ b/include/numericfield.h @@ -1,6 +1,6 @@ /** * Numeric field types for AMQP - * + * * @copyright 2014 Copernica BV */ @@ -215,4 +215,3 @@ typedef NumericField Double; * end namespace */ } - diff --git a/include/stringfield.h b/include/stringfield.h index 85ddef9..edcee29 100644 --- a/include/stringfield.h +++ b/include/stringfield.h @@ -119,7 +119,7 @@ public: virtual size_t size() const override { // find out size of the size parameter - T size((T::Type)_data.size()); + T size((typename T::Type)_data.size()); // size of the uint8 or uint32 + the actual string size return size.size() + _data.size(); @@ -160,7 +160,7 @@ public: virtual void fill(OutBuffer& buffer) const override { // create size - T size((T::Type)_data.size()); + T size((typename T::Type)_data.size()); // first, write down the size of the string size.fill(buffer); From cf7261add52a43391028288f2770dc2a307d63cf Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 01:58:18 +0100 Subject: [PATCH 051/168] Reordered public include files so they are now in include/amqpcpp/. --- CMakeLists.txt | 3 + amqpcpp.h => include/amqpcpp.h | 0 include/{ => amqpcpp}/address.h | 0 include/{ => amqpcpp}/addresses.h | 0 include/{ => amqpcpp}/array.h | 0 include/{ => amqpcpp}/booleanset.h | 0 include/{ => amqpcpp}/buffer.h | 0 include/{ => amqpcpp}/bytebuffer.h | 0 include/{ => amqpcpp}/callbacks.h | 0 include/{ => amqpcpp}/channel.h | 0 include/{ => amqpcpp}/channelimpl.h | 0 include/{ => amqpcpp}/classes.h | 0 include/{ => amqpcpp}/connection.h | 0 include/{ => amqpcpp}/connectionhandler.h | 0 include/{ => amqpcpp}/connectionimpl.h | 0 include/{ => amqpcpp}/copiedbuffer.h | 0 include/{ => amqpcpp}/decimalfield.h | 0 include/{ => amqpcpp}/deferred.h | 0 include/{ => amqpcpp}/deferredcancel.h | 0 include/{ => amqpcpp}/deferredconsumer.h | 0 include/{ => amqpcpp}/deferredconsumerbase.h | 0 include/{ => amqpcpp}/deferreddelete.h | 0 include/{ => amqpcpp}/deferredget.h | 0 include/{ => amqpcpp}/deferredqueue.h | 0 include/{ => amqpcpp}/endian.h | 0 include/{ => amqpcpp}/entityimpl.h | 0 include/{ => amqpcpp}/envelope.h | 0 include/{ => amqpcpp}/exception.h | 0 include/{ => amqpcpp}/exchangetype.h | 0 include/{ => amqpcpp}/field.h | 0 include/{ => amqpcpp}/fieldproxy.h | 0 include/{ => amqpcpp}/flags.h | 0 include/{ => amqpcpp}/frame.h | 0 include/{ => amqpcpp}/libboostasio.h | 0 include/{ => amqpcpp}/libev.h | 0 include/{ => amqpcpp}/libevent.h | 0 include/{ => amqpcpp}/libuv.h | 0 include/{ => amqpcpp}/linux_tcp/tcpchannel.h | 0 .../{ => amqpcpp}/linux_tcp/tcpconnection.h | 0 include/{ => amqpcpp}/linux_tcp/tcpdefines.h | 0 include/{ => amqpcpp}/linux_tcp/tcphandler.h | 0 include/{ => amqpcpp}/login.h | 0 include/{ => amqpcpp}/message.h | 0 include/{ => amqpcpp}/metadata.h | 0 include/{ => amqpcpp}/monitor.h | 0 include/{ => amqpcpp}/numericfield.h | 0 include/{ => amqpcpp}/outbuffer.h | 0 include/{ => amqpcpp}/protocolexception.h | 0 include/{ => amqpcpp}/receivedframe.h | 0 include/{ => amqpcpp}/stack_ptr.h | 0 include/{ => amqpcpp}/stringfield.h | 0 include/{ => amqpcpp}/table.h | 0 include/{ => amqpcpp}/watchable.h | 0 src/basicdeliverframe.h | 6 +- src/basicheaderframe.h | 8 +- src/bodyframe.h | 4 +- src/deferredconsumerbase.cpp | 2 +- src/extframe.h | 4 +- src/includes.h | 78 +++++++++---------- src/passthroughbuffer.h | 2 +- 60 files changed, 55 insertions(+), 52 deletions(-) rename amqpcpp.h => include/amqpcpp.h (100%) rename include/{ => amqpcpp}/address.h (100%) rename include/{ => amqpcpp}/addresses.h (100%) rename include/{ => amqpcpp}/array.h (100%) rename include/{ => amqpcpp}/booleanset.h (100%) rename include/{ => amqpcpp}/buffer.h (100%) rename include/{ => amqpcpp}/bytebuffer.h (100%) rename include/{ => amqpcpp}/callbacks.h (100%) rename include/{ => amqpcpp}/channel.h (100%) rename include/{ => amqpcpp}/channelimpl.h (100%) rename include/{ => amqpcpp}/classes.h (100%) rename include/{ => amqpcpp}/connection.h (100%) rename include/{ => amqpcpp}/connectionhandler.h (100%) rename include/{ => amqpcpp}/connectionimpl.h (100%) rename include/{ => amqpcpp}/copiedbuffer.h (100%) rename include/{ => amqpcpp}/decimalfield.h (100%) rename include/{ => amqpcpp}/deferred.h (100%) rename include/{ => amqpcpp}/deferredcancel.h (100%) rename include/{ => amqpcpp}/deferredconsumer.h (100%) rename include/{ => amqpcpp}/deferredconsumerbase.h (100%) rename include/{ => amqpcpp}/deferreddelete.h (100%) rename include/{ => amqpcpp}/deferredget.h (100%) rename include/{ => amqpcpp}/deferredqueue.h (100%) rename include/{ => amqpcpp}/endian.h (100%) rename include/{ => amqpcpp}/entityimpl.h (100%) rename include/{ => amqpcpp}/envelope.h (100%) rename include/{ => amqpcpp}/exception.h (100%) rename include/{ => amqpcpp}/exchangetype.h (100%) rename include/{ => amqpcpp}/field.h (100%) rename include/{ => amqpcpp}/fieldproxy.h (100%) rename include/{ => amqpcpp}/flags.h (100%) rename include/{ => amqpcpp}/frame.h (100%) rename include/{ => amqpcpp}/libboostasio.h (100%) rename include/{ => amqpcpp}/libev.h (100%) rename include/{ => amqpcpp}/libevent.h (100%) rename include/{ => amqpcpp}/libuv.h (100%) rename include/{ => amqpcpp}/linux_tcp/tcpchannel.h (100%) rename include/{ => amqpcpp}/linux_tcp/tcpconnection.h (100%) rename include/{ => amqpcpp}/linux_tcp/tcpdefines.h (100%) rename include/{ => amqpcpp}/linux_tcp/tcphandler.h (100%) rename include/{ => amqpcpp}/login.h (100%) rename include/{ => amqpcpp}/message.h (100%) rename include/{ => amqpcpp}/metadata.h (100%) rename include/{ => amqpcpp}/monitor.h (100%) rename include/{ => amqpcpp}/numericfield.h (100%) rename include/{ => amqpcpp}/outbuffer.h (100%) rename include/{ => amqpcpp}/protocolexception.h (100%) rename include/{ => amqpcpp}/receivedframe.h (100%) rename include/{ => amqpcpp}/stack_ptr.h (100%) rename include/{ => amqpcpp}/stringfield.h (100%) rename include/{ => amqpcpp}/table.h (100%) rename include/{ => amqpcpp}/watchable.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f9170f..a2c5057 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) # ensure c++11 on all compilers set (CMAKE_CXX_STANDARD 11) +# set include/ as include directory +include_directories(${CMAKE_SOURCE_DIR}/include) + macro (add_sources) file (RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") foreach (_src ${ARGN}) diff --git a/amqpcpp.h b/include/amqpcpp.h similarity index 100% rename from amqpcpp.h rename to include/amqpcpp.h diff --git a/include/address.h b/include/amqpcpp/address.h similarity index 100% rename from include/address.h rename to include/amqpcpp/address.h diff --git a/include/addresses.h b/include/amqpcpp/addresses.h similarity index 100% rename from include/addresses.h rename to include/amqpcpp/addresses.h diff --git a/include/array.h b/include/amqpcpp/array.h similarity index 100% rename from include/array.h rename to include/amqpcpp/array.h diff --git a/include/booleanset.h b/include/amqpcpp/booleanset.h similarity index 100% rename from include/booleanset.h rename to include/amqpcpp/booleanset.h diff --git a/include/buffer.h b/include/amqpcpp/buffer.h similarity index 100% rename from include/buffer.h rename to include/amqpcpp/buffer.h diff --git a/include/bytebuffer.h b/include/amqpcpp/bytebuffer.h similarity index 100% rename from include/bytebuffer.h rename to include/amqpcpp/bytebuffer.h diff --git a/include/callbacks.h b/include/amqpcpp/callbacks.h similarity index 100% rename from include/callbacks.h rename to include/amqpcpp/callbacks.h diff --git a/include/channel.h b/include/amqpcpp/channel.h similarity index 100% rename from include/channel.h rename to include/amqpcpp/channel.h diff --git a/include/channelimpl.h b/include/amqpcpp/channelimpl.h similarity index 100% rename from include/channelimpl.h rename to include/amqpcpp/channelimpl.h diff --git a/include/classes.h b/include/amqpcpp/classes.h similarity index 100% rename from include/classes.h rename to include/amqpcpp/classes.h diff --git a/include/connection.h b/include/amqpcpp/connection.h similarity index 100% rename from include/connection.h rename to include/amqpcpp/connection.h diff --git a/include/connectionhandler.h b/include/amqpcpp/connectionhandler.h similarity index 100% rename from include/connectionhandler.h rename to include/amqpcpp/connectionhandler.h diff --git a/include/connectionimpl.h b/include/amqpcpp/connectionimpl.h similarity index 100% rename from include/connectionimpl.h rename to include/amqpcpp/connectionimpl.h diff --git a/include/copiedbuffer.h b/include/amqpcpp/copiedbuffer.h similarity index 100% rename from include/copiedbuffer.h rename to include/amqpcpp/copiedbuffer.h diff --git a/include/decimalfield.h b/include/amqpcpp/decimalfield.h similarity index 100% rename from include/decimalfield.h rename to include/amqpcpp/decimalfield.h diff --git a/include/deferred.h b/include/amqpcpp/deferred.h similarity index 100% rename from include/deferred.h rename to include/amqpcpp/deferred.h diff --git a/include/deferredcancel.h b/include/amqpcpp/deferredcancel.h similarity index 100% rename from include/deferredcancel.h rename to include/amqpcpp/deferredcancel.h diff --git a/include/deferredconsumer.h b/include/amqpcpp/deferredconsumer.h similarity index 100% rename from include/deferredconsumer.h rename to include/amqpcpp/deferredconsumer.h diff --git a/include/deferredconsumerbase.h b/include/amqpcpp/deferredconsumerbase.h similarity index 100% rename from include/deferredconsumerbase.h rename to include/amqpcpp/deferredconsumerbase.h diff --git a/include/deferreddelete.h b/include/amqpcpp/deferreddelete.h similarity index 100% rename from include/deferreddelete.h rename to include/amqpcpp/deferreddelete.h diff --git a/include/deferredget.h b/include/amqpcpp/deferredget.h similarity index 100% rename from include/deferredget.h rename to include/amqpcpp/deferredget.h diff --git a/include/deferredqueue.h b/include/amqpcpp/deferredqueue.h similarity index 100% rename from include/deferredqueue.h rename to include/amqpcpp/deferredqueue.h diff --git a/include/endian.h b/include/amqpcpp/endian.h similarity index 100% rename from include/endian.h rename to include/amqpcpp/endian.h diff --git a/include/entityimpl.h b/include/amqpcpp/entityimpl.h similarity index 100% rename from include/entityimpl.h rename to include/amqpcpp/entityimpl.h diff --git a/include/envelope.h b/include/amqpcpp/envelope.h similarity index 100% rename from include/envelope.h rename to include/amqpcpp/envelope.h diff --git a/include/exception.h b/include/amqpcpp/exception.h similarity index 100% rename from include/exception.h rename to include/amqpcpp/exception.h diff --git a/include/exchangetype.h b/include/amqpcpp/exchangetype.h similarity index 100% rename from include/exchangetype.h rename to include/amqpcpp/exchangetype.h diff --git a/include/field.h b/include/amqpcpp/field.h similarity index 100% rename from include/field.h rename to include/amqpcpp/field.h diff --git a/include/fieldproxy.h b/include/amqpcpp/fieldproxy.h similarity index 100% rename from include/fieldproxy.h rename to include/amqpcpp/fieldproxy.h diff --git a/include/flags.h b/include/amqpcpp/flags.h similarity index 100% rename from include/flags.h rename to include/amqpcpp/flags.h diff --git a/include/frame.h b/include/amqpcpp/frame.h similarity index 100% rename from include/frame.h rename to include/amqpcpp/frame.h diff --git a/include/libboostasio.h b/include/amqpcpp/libboostasio.h similarity index 100% rename from include/libboostasio.h rename to include/amqpcpp/libboostasio.h diff --git a/include/libev.h b/include/amqpcpp/libev.h similarity index 100% rename from include/libev.h rename to include/amqpcpp/libev.h diff --git a/include/libevent.h b/include/amqpcpp/libevent.h similarity index 100% rename from include/libevent.h rename to include/amqpcpp/libevent.h diff --git a/include/libuv.h b/include/amqpcpp/libuv.h similarity index 100% rename from include/libuv.h rename to include/amqpcpp/libuv.h diff --git a/include/linux_tcp/tcpchannel.h b/include/amqpcpp/linux_tcp/tcpchannel.h similarity index 100% rename from include/linux_tcp/tcpchannel.h rename to include/amqpcpp/linux_tcp/tcpchannel.h diff --git a/include/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h similarity index 100% rename from include/linux_tcp/tcpconnection.h rename to include/amqpcpp/linux_tcp/tcpconnection.h diff --git a/include/linux_tcp/tcpdefines.h b/include/amqpcpp/linux_tcp/tcpdefines.h similarity index 100% rename from include/linux_tcp/tcpdefines.h rename to include/amqpcpp/linux_tcp/tcpdefines.h diff --git a/include/linux_tcp/tcphandler.h b/include/amqpcpp/linux_tcp/tcphandler.h similarity index 100% rename from include/linux_tcp/tcphandler.h rename to include/amqpcpp/linux_tcp/tcphandler.h diff --git a/include/login.h b/include/amqpcpp/login.h similarity index 100% rename from include/login.h rename to include/amqpcpp/login.h diff --git a/include/message.h b/include/amqpcpp/message.h similarity index 100% rename from include/message.h rename to include/amqpcpp/message.h diff --git a/include/metadata.h b/include/amqpcpp/metadata.h similarity index 100% rename from include/metadata.h rename to include/amqpcpp/metadata.h diff --git a/include/monitor.h b/include/amqpcpp/monitor.h similarity index 100% rename from include/monitor.h rename to include/amqpcpp/monitor.h diff --git a/include/numericfield.h b/include/amqpcpp/numericfield.h similarity index 100% rename from include/numericfield.h rename to include/amqpcpp/numericfield.h diff --git a/include/outbuffer.h b/include/amqpcpp/outbuffer.h similarity index 100% rename from include/outbuffer.h rename to include/amqpcpp/outbuffer.h diff --git a/include/protocolexception.h b/include/amqpcpp/protocolexception.h similarity index 100% rename from include/protocolexception.h rename to include/amqpcpp/protocolexception.h diff --git a/include/receivedframe.h b/include/amqpcpp/receivedframe.h similarity index 100% rename from include/receivedframe.h rename to include/amqpcpp/receivedframe.h diff --git a/include/stack_ptr.h b/include/amqpcpp/stack_ptr.h similarity index 100% rename from include/stack_ptr.h rename to include/amqpcpp/stack_ptr.h diff --git a/include/stringfield.h b/include/amqpcpp/stringfield.h similarity index 100% rename from include/stringfield.h rename to include/amqpcpp/stringfield.h diff --git a/include/table.h b/include/amqpcpp/table.h similarity index 100% rename from include/table.h rename to include/amqpcpp/table.h diff --git a/include/watchable.h b/include/amqpcpp/watchable.h similarity index 100% rename from include/watchable.h rename to include/amqpcpp/watchable.h diff --git a/src/basicdeliverframe.h b/src/basicdeliverframe.h index 7986355..4d4c408 100644 --- a/src/basicdeliverframe.h +++ b/src/basicdeliverframe.h @@ -13,9 +13,9 @@ * Dependencies */ #include "basicframe.h" -#include "../include/stringfield.h" -#include "../include/booleanset.h" -#include "../include/connectionimpl.h" +#include "amqpcpp/stringfield.h" +#include "amqpcpp/booleanset.h" +#include "amqpcpp/connectionimpl.h" /** * Set up namespace diff --git a/src/basicheaderframe.h b/src/basicheaderframe.h index 5260621..c6ec1c4 100644 --- a/src/basicheaderframe.h +++ b/src/basicheaderframe.h @@ -13,10 +13,10 @@ * Dependencies */ #include "headerframe.h" -#include "../include/metadata.h" -#include "../include/envelope.h" -#include "../include/connectionimpl.h" -#include "../include/deferredconsumerbase.h" +#include "amqpcpp/metadata.h" +#include "amqpcpp/envelope.h" +#include "amqpcpp/connectionimpl.h" +#include "amqpcpp/deferredconsumerbase.h" /** * Set up namespace diff --git a/src/bodyframe.h b/src/bodyframe.h index ba34cd2..ae1621d 100644 --- a/src/bodyframe.h +++ b/src/bodyframe.h @@ -13,8 +13,8 @@ * Dependencies */ #include "extframe.h" -#include "../include/connectionimpl.h" -#include "../include/deferredconsumerbase.h" +#include "amqpcpp/connectionimpl.h" +#include "amqpcpp/deferredconsumerbase.h" /** * Set up namespace diff --git a/src/deferredconsumerbase.cpp b/src/deferredconsumerbase.cpp index 240842b..09ed0e4 100644 --- a/src/deferredconsumerbase.cpp +++ b/src/deferredconsumerbase.cpp @@ -10,7 +10,7 @@ /** * Dependencies */ -#include "../include/deferredconsumerbase.h" +#include "amqpcpp/deferredconsumerbase.h" #include "basicdeliverframe.h" #include "basicgetokframe.h" #include "basicheaderframe.h" diff --git a/src/extframe.h b/src/extframe.h index d6de772..513c674 100644 --- a/src/extframe.h +++ b/src/extframe.h @@ -19,8 +19,8 @@ /** * Dependencies */ -#include "../include/frame.h" -#include "../include/receivedframe.h" +#include "amqpcpp/frame.h" +#include "amqpcpp/receivedframe.h" /** * Set up namespace diff --git a/src/includes.h b/src/includes.h index ddabc00..122f2d0 100644 --- a/src/includes.h +++ b/src/includes.h @@ -35,55 +35,55 @@ #endif // forward declarations -#include "../include/classes.h" +#include "amqpcpp/classes.h" // utility classes -#include "../include/endian.h" -#include "../include/buffer.h" -#include "../include/bytebuffer.h" -#include "../include/receivedframe.h" -#include "../include/outbuffer.h" -#include "../include/copiedbuffer.h" -#include "../include/watchable.h" -#include "../include/monitor.h" +#include "amqpcpp/endian.h" +#include "amqpcpp/buffer.h" +#include "amqpcpp/bytebuffer.h" +#include "amqpcpp/receivedframe.h" +#include "amqpcpp/outbuffer.h" +#include "amqpcpp/copiedbuffer.h" +#include "amqpcpp/watchable.h" +#include "amqpcpp/monitor.h" // amqp types -#include "../include/field.h" -#include "../include/numericfield.h" -#include "../include/decimalfield.h" -#include "../include/stringfield.h" -#include "../include/booleanset.h" -#include "../include/fieldproxy.h" -#include "../include/table.h" -#include "../include/array.h" +#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" // envelope for publishing and consuming -#include "../include/metadata.h" -#include "../include/envelope.h" -#include "../include/message.h" +#include "amqpcpp/metadata.h" +#include "amqpcpp/envelope.h" +#include "amqpcpp/message.h" // mid level includes -#include "../include/exchangetype.h" -#include "../include/flags.h" -#include "../include/callbacks.h" -#include "../include/deferred.h" -#include "../include/deferredconsumer.h" -#include "../include/deferredqueue.h" -#include "../include/deferreddelete.h" -#include "../include/deferredcancel.h" -#include "../include/deferredget.h" -#include "../include/channelimpl.h" -#include "../include/channel.h" -#include "../include/login.h" -#include "../include/address.h" -#include "../include/connectionhandler.h" -#include "../include/connectionimpl.h" -#include "../include/connection.h" +#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/deferredget.h" +#include "amqpcpp/channelimpl.h" +#include "amqpcpp/channel.h" +#include "amqpcpp/login.h" +#include "amqpcpp/address.h" +#include "amqpcpp/connectionhandler.h" +#include "amqpcpp/connectionimpl.h" +#include "amqpcpp/connection.h" // classes that are very commonly used -#include "../include/exception.h" -#include "../include/protocolexception.h" -#include "../include/frame.h" +#include "amqpcpp/exception.h" +#include "amqpcpp/protocolexception.h" +#include "amqpcpp/frame.h" #include "extframe.h" #include "methodframe.h" #include "headerframe.h" diff --git a/src/passthroughbuffer.h b/src/passthroughbuffer.h index 87509f3..4126eca 100644 --- a/src/passthroughbuffer.h +++ b/src/passthroughbuffer.h @@ -17,7 +17,7 @@ */ #include #include -#include "../include/frame.h" +#include "amqpcpp/frame.h" /** * Set up namespace From 413af81635cff1c25264bd53645d24809fa13e97 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 02:03:16 +0100 Subject: [PATCH 052/168] Fixed unix compile error after previous commit. --- src/linux_tcp/includes.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/linux_tcp/includes.h b/src/linux_tcp/includes.h index 0acb43e..a83e9bd 100644 --- a/src/linux_tcp/includes.h +++ b/src/linux_tcp/includes.h @@ -17,11 +17,11 @@ #include // utility classes -#include "../../include/linux_tcp/tcpdefines.h" +#include "amqpcpp/linux_tcp/tcpdefines.h" // mid level includes -#include "../../include/linux_tcp/tcphandler.h" -#include "../../include/linux_tcp/tcpconnection.h" +#include "amqpcpp/linux_tcp/tcphandler.h" +#include "amqpcpp/linux_tcp/tcpconnection.h" // classes that are very commonly used #include "addressinfo.h" From b558b8cac63910ef85d03d7a9607b42e831d395e Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 02:09:44 +0100 Subject: [PATCH 053/168] comments --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a2c5057..04cbc91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ if(LINUX_TCP) add_subdirectory(src/linux_tcp) endif() -# we have to prevent windows from defining the max macro. TODO more +# we have to prevent windows from defining the max macro. TODO why WIN32_LEAN_AND_MEAN? if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() From 8477fbb2727db8447397e72529322c54f481df55 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Wed, 24 Jan 2018 13:24:43 +0100 Subject: [PATCH 054/168] add PTR_FROM_THIS define; remove io_handler code duplication via read/write/dispatch wrapper functions. --- include/libboostasio.h | 231 ++++++++++++++++------------------------- 1 file changed, 92 insertions(+), 139 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index dd229f9..10bac80 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -26,37 +26,12 @@ #include #include - -template -void strand_sock_dispatch_func( - Func fn, - std::weak_ptr strand, - const boost::system::error_code &ec, - const std::size_t bytes_transferred -) { - const std::shared_ptr apStrand = strand.lock(); - if (!apStrand) - { - fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled), std::size_t{0}); - return; - } - apStrand->dispatch(boost::bind(fn, ec, bytes_transferred)); -} - -template -void strand_timer_dispatch_func( - Func fn, - std::weak_ptr strand, - const boost::system::error_code &ec -) { - const std::shared_ptr apStrand = strand.lock(); - if (!apStrand) - { - fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); - return; - } - apStrand->dispatch(boost::bind(fn,ec)); -} +// C++17 has 'weak_from_this()' support. +#if __cplusplus >= 201701L +#define PTR_FROM_THIS weak_from_this +#else +#define PTR_FROM_THIS shared_from_this +#endif /** * Set up namespace @@ -122,6 +97,52 @@ private: */ bool _write_pending{false}; + using handler_cb = boost::function; + using io_handler = boost::function; + + /** + * 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_service::strand. + */ + handler_cb get_dispatch_wrapper(io_handler fn) + { + return [fn, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) + { + const std::shared_ptr apStrand = strand.lock(); + if (!apStrand) + { + fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled), std::size_t{0}); + return; + } + apStrand->dispatch(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, _1, _2, PTR_FROM_THIS(), 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, _1, _2, PTR_FROM_THIS(), connection, fd); + return get_dispatch_wrapper(fn); + } + /** * Handler method that is called by boost's io_service when the socket pumps a read event. * @param ec The status of the callback. @@ -149,25 +170,8 @@ private: connection->process(fd, AMQP::readable); _read_pending = true; - - auto func = boost::bind(&Watcher::read_handler, - this, - boost::arg<1>(), - boost::arg<2>(), -// C++17 has 'weak_from_this()' support. -#if __cplusplus >= 201701L - weak_from_this(), -#else - shared_from_this(), -#endif - connection, - fd); - _socket.async_read_some( - boost::asio::null_buffers(), - [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { - strand_sock_dispatch_func(f, strand, ec, bytes_transferred); - } - ); + + _socket.async_read_some(boost::asio::null_buffers(), get_read_handler(connection, fd)); } } @@ -199,24 +203,7 @@ private: _write_pending = true; - auto func = boost::bind(&Watcher::write_handler, - this, - boost::arg<1>(), - boost::arg<2>(), -// C++17 has 'weak_from_this()' support. -#if __cplusplus >= 201701L - weak_from_this(), -#else - shared_from_this(), -#endif - connection, - fd); - _socket.async_write_some( - boost::asio::null_buffers(), - [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { - strand_sock_dispatch_func(f, strand, ec, bytes_transferred); - } - ); + _socket.async_write_some(boost::asio::null_buffers(), get_write_handler(connection, fd)); } } @@ -272,24 +259,7 @@ private: { _read_pending = true; - auto func = boost::bind(&Watcher::read_handler, - this, - boost::arg<1>(), - boost::arg<2>(), -// C++17 has 'weak_from_this()' support. -#if __cplusplus >= 201701L - weak_from_this(), -#else - shared_from_this(), -#endif - connection, - fd); - _socket.async_read_some( - boost::asio::null_buffers(), - [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { - strand_sock_dispatch_func(f, strand, ec, bytes_transferred); - } - ); + _socket.async_read_some(boost::asio::null_buffers(), get_read_handler(connection, fd)); } // 2. Handle writes? @@ -300,24 +270,7 @@ private: { _write_pending = true; - auto func = boost::bind(&Watcher::write_handler, - this, - boost::arg<1>(), - boost::arg<2>(), -// C++17 has 'weak_from_this()' support. -#if __cplusplus >= 201701L - weak_from_this(), -#else - shared_from_this(), -#endif - connection, - fd); - _socket.async_write_some( - boost::asio::null_buffers(), - [f = func, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { - strand_sock_dispatch_func(f, strand, ec, bytes_transferred); - } - ); + _socket.async_write_some(boost::asio::null_buffers(), get_write_handler(connection, fd)); } } }; @@ -347,11 +300,39 @@ private: */ boost::asio::deadline_timer _timer; + using handler_fn = boost::function; + /** + * 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 + */ + handler_fn get_handler(TcpConnection *const connection, const uint16_t timeout) + { + auto fn = boost::bind(&Timer::timeout, + this, + _1, + PTR_FROM_THIS(), + connection, + timeout); + return [fn, this](const boost::system::error_code &ec) + { + const std::shared_ptr apStrand = _strand.lock(); + if (!apStrand) + { + fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); + return; + } + apStrand->dispatch(boost::bind(fn, ec)); + }; + } + /** * 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 timer Internal timer object - * @param revents The events that triggered this call + * @param connection + * @param timeout */ void timeout(const boost::system::error_code &ec, std::weak_ptr awpThis, @@ -371,27 +352,11 @@ private: connection->heartbeat(); } - auto func = boost::bind(&Timer::timeout, - this, - boost::arg<1>(), -// C++17 has 'weak_from_this()' support. -#if __cplusplus >= 201701L - weak_from_this(), -#else - shared_from_this(), -#endif - connection, - timeout); - // Reschedule the timer for the future: _timer.expires_at(_timer.expires_at() + boost::posix_time::seconds(timeout)); // Posts the timer event - _timer.async_wait( - [f = func, strand = _strand](const boost::system::error_code &ec) { - strand_timer_dispatch_func(f, strand, ec); - } - ); + _timer.async_wait(get_handler(connection, timeout)); } } @@ -446,23 +411,11 @@ private: // stop timer in case it was already set stop(); - auto func = boost::bind(&Timer::timeout, - this, - boost::arg<1>(), -// C++17 has 'weak_from_this()' support. -#if __cplusplus >= 201701L - weak_from_this(), -#else - shared_from_this(), -#endif - connection, - timeout); + // Reschedule the timer for the future: _timer.expires_from_now(boost::posix_time::seconds(timeout)); - _timer.async_wait( - [f = func, strand = _strand](const boost::system::error_code &ec) { - strand_timer_dispatch_func(f, strand, ec); - } - ); + + // Posts the timer event + _timer.async_wait(get_handler(connection, timeout)); } }; From 959e6238ae24820e0e5aca49f62bd5882616604b Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Wed, 24 Jan 2018 15:06:35 +0100 Subject: [PATCH 055/168] code formatting; capture strand by copy in timer handler --- include/libboostasio.h | 46 ++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/include/libboostasio.h b/include/libboostasio.h index 10bac80..3f80b89 100644 --- a/include/libboostasio.h +++ b/include/libboostasio.h @@ -127,7 +127,13 @@ private: */ handler_cb get_read_handler(TcpConnection *const connection, const int fd) { - auto fn = boost::bind(&Watcher::read_handler, this, _1, _2, PTR_FROM_THIS(), connection, fd); + auto fn = boost::bind(&Watcher::read_handler, + this, + _1, + _2, + PTR_FROM_THIS(), + connection, + fd); return get_dispatch_wrapper(fn); } @@ -139,7 +145,13 @@ private: */ handler_cb get_write_handler(TcpConnection *const connection, const int fd) { - auto fn = boost::bind(&Watcher::write_handler, this, _1, _2, PTR_FROM_THIS(), connection, fd); + auto fn = boost::bind(&Watcher::write_handler, + this, + _1, + _2, + PTR_FROM_THIS(), + connection, + fd); return get_dispatch_wrapper(fn); } @@ -171,7 +183,9 @@ private: _read_pending = true; - _socket.async_read_some(boost::asio::null_buffers(), get_read_handler(connection, fd)); + _socket.async_read_some( + boost::asio::null_buffers(), + get_read_handler(connection, fd)); } } @@ -203,7 +217,9 @@ private: _write_pending = true; - _socket.async_write_some(boost::asio::null_buffers(), get_write_handler(connection, fd)); + _socket.async_write_some( + boost::asio::null_buffers(), + get_write_handler(connection, fd)); } } @@ -259,7 +275,9 @@ private: { _read_pending = true; - _socket.async_read_some(boost::asio::null_buffers(), get_read_handler(connection, fd)); + _socket.async_read_some( + boost::asio::null_buffers(), + get_read_handler(connection, fd)); } // 2. Handle writes? @@ -270,7 +288,9 @@ private: { _write_pending = true; - _socket.async_write_some(boost::asio::null_buffers(), get_write_handler(connection, fd)); + _socket.async_write_some( + boost::asio::null_buffers(), + get_write_handler(connection, fd)); } } }; @@ -310,14 +330,14 @@ private: handler_fn get_handler(TcpConnection *const connection, const uint16_t timeout) { auto fn = boost::bind(&Timer::timeout, - this, - _1, - PTR_FROM_THIS(), - connection, - timeout); - return [fn, this](const boost::system::error_code &ec) + this, + _1, + PTR_FROM_THIS(), + connection, + timeout); + return [fn, strand = _strand](const boost::system::error_code &ec) { - const std::shared_ptr apStrand = _strand.lock(); + const std::shared_ptr apStrand = strand.lock(); if (!apStrand) { fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); From 4da26b02a418e75b2dad9baca576e99aeffe9ec7 Mon Sep 17 00:00:00 2001 From: "DAQ-AART\\AartStuurman" Date: Wed, 24 Jan 2018 17:07:23 +0100 Subject: [PATCH 056/168] changed version nr. moved BUILD_SHARED option to top with other build option. --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04cbc91..d6aa4f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,8 @@ cmake_minimum_required(VERSION 3.1) project(amqpcpp) # build options -option(LINUX_TCP "Build linux sockets implementation" OFF) +option(BUILD_SHARED "Build shared library. If off, build will be static." OFF) +option(LINUX_TCP "Build linux sockets implementation." OFF) # set output directory set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) @@ -43,10 +44,9 @@ endif() # TODO Cleanup into this part of the cmakefile -option(BUILD_SHARED "build shared library" OFF) if(BUILD_SHARED) add_library(${PROJECT_NAME} SHARED ${SRCS}) - set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.7) # TODO version incorrect + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.8) # TODO version incorrect install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION lib ) From da3eee946017b21020c5c4c6a57186f5d2760da4 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 18:22:11 +0100 Subject: [PATCH 057/168] Cleanup CMake file. --- CMakeLists.txt | 54 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6aa4f6..6f74335 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,15 @@ +# Builds AMQP-CPP +# +# Options: +# +# - BUILD_SHARED (default OFF) +# ON: Build shared lib +# OFF: Build static lib +# +# - LINUX_TCP (default OFF) +# ON: Build posix handler implementation +# OFF: Don't build posix handler implementation + cmake_minimum_required(VERSION 3.1) # project name @@ -7,15 +19,17 @@ project(amqpcpp) option(BUILD_SHARED "Build shared library. If off, build will be static." OFF) option(LINUX_TCP "Build linux sockets implementation." OFF) -# set output directory -set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) - # ensure c++11 on all compilers set (CMAKE_CXX_STANDARD 11) +# add source files +# ------------------------------------------------------------------------------------------------------ + # set include/ as include directory include_directories(${CMAKE_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}) @@ -37,29 +51,45 @@ if(LINUX_TCP) add_subdirectory(src/linux_tcp) endif() +# settings for specific compilers +# ------------------------------------------------------------------------------------------------------ + # we have to prevent windows from defining the max macro. TODO why WIN32_LEAN_AND_MEAN? if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() +# build targets +# ------------------------------------------------------------------------------------------------------ + +# set output directory +set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) -# TODO Cleanup into this part of the cmakefile if(BUILD_SHARED) + # create shared lib add_library(${PROJECT_NAME} SHARED ${SRCS}) - set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.8) # TODO version incorrect + # set shared lib version + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.8) +else() + # create static lib + add_library(${PROJECT_NAME} STATIC ${SRCS}) +endif() + +# install rules +# ------------------------------------------------------------------------------------------------------ + +if(BUILD_SHARED) + # copy static lib install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION lib ) else() - add_library(${PROJECT_NAME} STATIC ${SRCS}) + # copy shared lib and its static counter part install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION lib ) endif() -Include_directories(${PROJECT_SOURCE_DIR}) -install(DIRECTORY include/ DESTINATION include/amqpcpp +# copy header files +install(DIRECTORY include/amqpcpp DESTINATION include/amqpcpp FILES_MATCHING PATTERN "*.h") -install(FILES amqpcpp.h DESTINATION include) - -set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) -set(AMQP-CPP_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) \ No newline at end of file +install(FILES amqpcpp.h DESTINATION include) \ No newline at end of file From 33a2f96fb126d3a2a50dbb648f9caaa56d9b33e1 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 24 Jan 2018 18:56:11 +0100 Subject: [PATCH 058/168] fixed installation command --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f74335..f94ca7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,6 @@ else() ) endif() # copy header files -install(DIRECTORY include/amqpcpp DESTINATION include/amqpcpp +install(DIRECTORY include/amqpcpp/ DESTINATION include/amqpcpp FILES_MATCHING PATTERN "*.h") -install(FILES amqpcpp.h DESTINATION include) \ No newline at end of file +install(FILES include/amqpcpp.h DESTINATION include) \ No newline at end of file From b13adcebd1309bd723d6c549c3fb1097915f7a16 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Thu, 25 Jan 2018 22:47:55 +0100 Subject: [PATCH 059/168] appveyor config --- appveyor.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..299b257 --- /dev/null +++ b/appveyor.yml @@ -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)\$(APPVEYOR_PROJECT_NAME).sln \ No newline at end of file From a0933563a6addb0b27e6c4dbaf16b7440c7c6b11 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Thu, 25 Jan 2018 22:49:56 +0100 Subject: [PATCH 060/168] fix sln name --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 299b257..5fa95c7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -16,4 +16,4 @@ before_build: - cmake -G "Visual Studio 15 2017 Win64" . build: - project: $(APPVEYOR_BUILD_FOLDER)\$(APPVEYOR_PROJECT_NAME).sln \ No newline at end of file + project: $(APPVEYOR_BUILD_FOLDER)\amqpcpp.sln \ No newline at end of file From 70bbc720459d2c167a40b410cf82bf0a2bc8d77d Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Thu, 25 Jan 2018 23:14:47 +0100 Subject: [PATCH 061/168] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8fc0303..eec547a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +[![Build status](https://ci.appveyor.com/api/projects/status/4fnbmx32chf3h3f7?svg=true)](https://ci.appveyor.com/project/surgura/amqp-cpp) VC++ + +[![Build Status](https://travis-ci.org/surgura/AMQP-CPP.svg?branch=master)](https://travis-ci.org/surgura/AMQP-CPP) GCC & Clang + AMQP-CPP ======== From d0bfa0bfc753da90cbad3f53e08e2e8e5d384caa Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Fri, 26 Jan 2018 22:14:43 +0100 Subject: [PATCH 062/168] Updated Makefile to have same results as before the update. --- src/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 7448733..a05ea7a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,7 +5,7 @@ LD = g++ LD_FLAGS = -Wall -shared SHARED_LIB = lib$(LIBRARY_NAME).so.$(VERSION) STATIC_LIB = lib$(LIBRARY_NAME).a.$(VERSION) -SOURCES = $(wildcard *.cpp) +SOURCES = $(wildcard *.cpp) $(wildcard linux_tcp/*.cpp) SHARED_OBJECTS = $(SOURCES:%.cpp=%.o) STATIC_OBJECTS = $(SOURCES:%.cpp=%.s.o) DEPENDENCIES = $(SOURCES:%.cpp=%.d) @@ -53,4 +53,3 @@ ${SHARED_OBJECTS}: ${STATIC_OBJECTS}: ${CPP} ${CPPFLAGS} -o $@ ${@:%.s.o=%.cpp} - From 99cc34d922b6a4959ba879602c378ce27a900a88 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Fri, 26 Jan 2018 22:42:28 +0100 Subject: [PATCH 063/168] Added linux_tcp.h so we can now once again use the tcp implementation of the handler. --- include/amqpcpp.h | 70 +++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/include/amqpcpp.h b/include/amqpcpp.h index 960f5f2..9b2b097 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -29,46 +29,46 @@ #include // forward declarations -#include +#include "amqpcpp/classes.h" // utility classes -#include -#include -#include -#include -#include -#include -#include +#include "amqpcpp/endian.h" +#include "amqpcpp/buffer.h" +#include "amqpcpp/bytebuffer.h" +#include "amqpcpp/receivedframe.h" +#include "amqpcpp/outbuffer.h" +#include "amqpcpp/watchable.h" +#include "amqpcpp/monitor.h" // amqp types -#include -#include -#include -#include -#include -#include -#include -#include +#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" // envelope for publishing and consuming -#include -#include -#include +#include "amqpcpp/metadata.h" +#include "amqpcpp/envelope.h" +#include "amqpcpp/message.h" // mid level includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#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/deferredget.h" +#include "amqpcpp/channelimpl.h" +#include "amqpcpp/channel.h" +#include "amqpcpp/login.h" +#include "amqpcpp/address.h" +#include "amqpcpp/connectionhandler.h" +#include "amqpcpp/connectionimpl.h" +#include "amqpcpp/connection.h" From 62065431b344512a080ab6e7b06847e66af0d345 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Fri, 26 Jan 2018 22:42:56 +0100 Subject: [PATCH 064/168] forgot file --- include/amqpcpp/linux_tcp.h | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 include/amqpcpp/linux_tcp.h diff --git a/include/amqpcpp/linux_tcp.h b/include/amqpcpp/linux_tcp.h new file mode 100644 index 0000000..c8b0d70 --- /dev/null +++ b/include/amqpcpp/linux_tcp.h @@ -0,0 +1,3 @@ +#include "linux_tcp/tcphandler.h" +#include "linux_tcp/tcpconnection.h" +#include "linux_tcp/tcpchannel.h" From dd6bbada96a3e481660eb4ae25a5146eab6292ac Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Fri, 26 Jan 2018 23:04:01 +0100 Subject: [PATCH 065/168] Fix build error for Make where include files could not be found. Make now installs headers correctly. --- Makefile | 6 ++++-- src/Makefile | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9f1fe75..9af8712 100644 --- a/Makefile +++ b/Makefile @@ -25,9 +25,11 @@ clean: install: mkdir -p ${INCLUDE_DIR}/$(LIBRARY_NAME) + mkdir -p ${INCLUDE_DIR}/$(LIBRARY_NAME)/linux_tcp mkdir -p ${LIBRARY_DIR} - cp -f $(LIBRARY_NAME).h ${INCLUDE_DIR} - cp -f include/*.h ${INCLUDE_DIR}/$(LIBRARY_NAME) + 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 -r -s -f $(LIBRARY_DIR)/lib$(LIBRARY_NAME).so.$(VERSION) $(LIBRARY_DIR)/lib$(LIBRARY_NAME).so.$(SONAME) diff --git a/src/Makefile b/src/Makefile index a05ea7a..3471fd5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@ CPP = g++ RM = rm -f -CPPFLAGS = -Wall -c -I. -std=c++11 -MD +CPPFLAGS = -Wall -c -I../include -std=c++11 -MD LD = g++ LD_FLAGS = -Wall -shared SHARED_LIB = lib$(LIBRARY_NAME).so.$(VERSION) From f7e8cf541453507c7f8e0b2158bb8e46b95e9351 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Fri, 26 Jan 2018 23:50:41 +0100 Subject: [PATCH 066/168] Updated README.md to reflect changes. --- README.md | 177 +++++++++++++++++++++++++++++------------------------- 1 file changed, 95 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 632e9c0..a37af09 100644 --- a/README.md +++ b/README.md @@ -5,33 +5,33 @@ AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker. The library can be used to parse incoming data from a RabbitMQ server, and to generate frames that can be sent to a RabbitMQ server. -This library has a layered architecture, and allows you - if you like - to -completely take care of the network layer. If you want to set up and manage the +This library has a layered architecture, and allows you - if you like - to +completely take care of the network layer. If you want to set up and manage the network connections yourself, the AMQP-CPP library will not make a connection to -RabbitMQ by itself, nor will it create sockets and/or perform IO operations. As -a user of this library, you create the socket connection and implement a certain -interface that you pass to the AMQP-CPP library and that the library will use +RabbitMQ by itself, nor will it create sockets and/or perform IO operations. As +a user of this library, you create the socket connection and implement a certain +interface that you pass to the AMQP-CPP library and that the library will use for IO operations. -Intercepting this network layer is however optional, the AMQP-CPP library also -comes with a predefined Tcp module that can be used if you trust the AMQP library +Intercepting this network layer is however optional, the AMQP-CPP library also +comes with a predefined Tcp module that can be used if you trust the AMQP library to take care of the network handling. In that case, the AMQP-CPP library does all the system calls to set up network connections and send and receive the data. -This layered architecture makes the library extremely flexible and portable: it -does not necessarily rely on operating system specific IO calls, and can be +This layered architecture makes the library extremely flexible and portable: it +does not necessarily rely on operating system specific IO calls, and can be easily integrated into any kind of event loop. If you want to implement the AMQP -protocol on top of some [unusual other communication layer](https://tools.ietf.org/html/rfc1149), -this library can be used for that - but if you want to use it with regular TCP -connections, setting it up is just as easy. +protocol on top of some [unusual other communication layer](https://tools.ietf.org/html/rfc1149), +this library can be used for that - but if you want to use it with regular TCP +connections, setting it up is just as easy. -AMQP-CPP is fully asynchronous and does not do any blocking (system) calls, so +AMQP-CPP is fully asynchronous and does not do any blocking (system) calls, so it can be used in high performance applications without the need for threads. -The AMQP-CPP library uses C++11 features, so if you intend use it, please make +The AMQP-CPP library uses C++11 features, so if you intend use it, please make sure that your compiler is up-to-date and supports C++11. -**Note for the reader:** This readme file has a peculiar structure. We start +**Note for the reader:** This readme file has a peculiar structure. We start explaining the pure and hard core low level interface in which you have to take care of opening socket connections yourself. In reality, you probably want to use the simpler TCP interface that is being described later on. @@ -41,13 +41,13 @@ ABOUT ===== This library is created and maintained by Copernica (www.copernica.com), and is -used inside the MailerQ (www.mailerq.com), Yothalot (www.yothalot.com) and -AMQPipe (www.amqpipe.com) applications. MailerQ is a tool for sending large +used inside the MailerQ (www.mailerq.com), Yothalot (www.yothalot.com) and +AMQPipe (www.amqpipe.com) applications. MailerQ is a tool for sending large volumes of email, using AMQP message queues, Yothalot is a big data processing -map/reduce framework and AMQPipe is a tool for high-speed processing messages +map/reduce framework and AMQPipe is a tool for high-speed processing messages between AMQP pipes -Do you appreciate our work and are you looking for high quality email solutions? +Do you appreciate our work and are you looking for high quality email solutions? Then check out our other commercial and open source solutions: * Copernica Marketing Suite (www.copernica.com) @@ -62,24 +62,38 @@ Then check out our other commercial and open source solutions: INSTALLING ========== +There are two methods to compile AMQP-CPP, CMake and Make. CMake is platform portable, but the Makefile only works on Linux. +## CMake +The CMake file supports both building and installing. You can choose not to use the install functionality, and instead to manually use the build output at bin/. Keep in mind that the TCP module is only supported for Linux. An example install method would be: +``` bash +mkdir build +cd build +cmake .. [-DBUILD_SHARED] [-DLINUX_TCP] +cmake --build .. --target install +``` -If you are on some kind of Linux environment, installing the library is as easy +### BUILD_SHARED +Default OFF +ON: Build shared library +OFF: Build static library + +### LINUX_TCP +Default OFF +ON: Build linux-only TCP module +OFF: Do not build the module. + +## Make +Installing the library is as easy as running `make` and `make install`. This will install the full version of the AMQP-CPP, including the system specific TCP module. If you do not need the -additional TCP module (because you take care of handling the network stuff -yourself), you can also compile a pure form of the library. Use `make pure` +additional TCP module (because you take care of handling the network stuff +yourself), you can also compile a pure form of the library. Use `make pure` and `make install` for that. -For users on a non-Linux environment: this library is known to work on your -environment too (after all, it does not do any operating specific system calls), -but it might take some extra effort to get your compiler to compile it. Please -send in your pull requests once you have it running, so that others can benefit -from your experiences. - When you compile an application that uses the AMQP-CPP library, do not forget to link with the library. For gcc and clang the linker flag is -lamqpcpp. -If you use the fullblown version of AMQP-CPP (with the TCP module), you also -need to pass a -lpthread linker flag, because the TCP module uses a thread +If you use the fullblown version of AMQP-CPP (with the TCP module), you also +need to pass a -lpthread linker flag, because the TCP module uses a thread for running an asynchronous and non-blocking DNS hostname lookup. @@ -87,11 +101,11 @@ HOW TO USE AMQP-CPP =================== As we mentioned above, the library can be used in a network-agnostic fashion. -It then does not do any IO by itself, and you need to pass an object to the +It then does not do any IO by itself, and you need to pass an object to the library that the library can use for IO. So, before you start using the -library, you first you need to create a class that extends from the -ConnectionHandler base class. This is a class with a number of methods that are -called by the library every time it wants to send out data, or when it needs to +library, you first you need to create a class that extends from the +ConnectionHandler base class. This is a class with a number of methods that are +called by the library every time it wants to send out data, or when it needs to inform you that an error occured. ````c++ @@ -204,16 +218,16 @@ example call the "send()" or "write()" system call to send out the data to the RabbitMQ server. But what about data in the other direction? How does the library receive data back from RabbitMQ? -In this raw setup, the AMQP-CPP library does not do any IO by itself and it is -therefore also not possible for the library to receive data from a -socket. It is again up to you to do this. If, for example, you notice in your -event loop that the socket that is connected with the RabbitMQ server becomes -readable, you should read out that socket (for example by using the recv() system -call), and pass the received bytes to the AMQP-CPP library. This is done by +In this raw setup, the AMQP-CPP library does not do any IO by itself and it is +therefore also not possible for the library to receive data from a +socket. It is again up to you to do this. If, for example, you notice in your +event loop that the socket that is connected with the RabbitMQ server becomes +readable, you should read out that socket (for example by using the recv() system +call), and pass the received bytes to the AMQP-CPP library. This is done by calling the parse() method in the Connection object. The Connection::parse() method gets two parameters, a pointer to a buffer of -data that you just read from the socket, and a parameter that holds the size of +data that you just read from the socket, and a parameter that holds the size of this buffer. The code snippet below comes from the Connection.h C++ header file. ````c++ @@ -249,9 +263,9 @@ both the old data, and the new data. To optimize your calls to the parse() method, you _could_ use the Connection::expected() and Connection::maxFrame() methods. The expected() method returns the number of bytes that the library prefers to receive next. It is pointless to call the parse() method -with a smaller buffer, and it is best to call the method with a buffer of exactly this -size. The maxFrame() returns the max frame size for AMQP messages. If you read your -messages into a reusable buffer, you could allocate this buffer up to this size, so that +with a smaller buffer, and it is best to call the method with a buffer of exactly this +size. The maxFrame() returns the max frame size for AMQP messages. If you read your +messages into a reusable buffer, you could allocate this buffer up to this size, so that you never will have to reallocate. @@ -259,17 +273,17 @@ TCP CONNECTIONS =============== Although the AMQP-CPP library gives you extreme flexibility by letting you setup -your own network connections, the reality is that most if not all AMQP connections -use the TCP protocol. To help you out, the library therefore also comes with a +your own network connections, the reality is that most if not all AMQP connections +use the TCP protocol. To help you out, the library therefore also comes with a TCP module that takes care of setting up the network connections, and sending -and receiving the data. +and receiving the data. If you want to use this TCP module, you should not use the AMQP::Connection and AMQP::Channel classes that you saw above, but the alternative AMQP::TcpConnection and AMQP::TcpChannel classes instead. You also do not have to create your own class -that implements the "AMQP::ConnectionHandler" interface - but a class that inherits -from "AMQP::TcpHandler" instead. You especially need to implement the "monitor()" -method in that class, as that is needed by the AMQP-CPP library to interact with +that implements the "AMQP::ConnectionHandler" interface - but a class that inherits +from "AMQP::TcpHandler" instead. You especially need to implement the "monitor()" +method in that class, as that is needed by the AMQP-CPP library to interact with the main event loop: ````c++ @@ -334,7 +348,7 @@ class MyTcpHandler : public AMQP::TcpHandler // descriptor to the main application event loop (like the select() or // poll() loop). When the event loop reports that the descriptor becomes // readable and/or writable, it is up to you to inform the AMQP-CPP - // library that the filedescriptor is active by calling the + // library that the filedescriptor is active by calling the // connection->process(fd, flags) method. } }; @@ -344,8 +358,8 @@ The "monitor()" method can be used to integrate the AMQP filedescriptors in your application's event loop. For some popular event loops (libev, libevent), we have already added example handler objects (see the next section for that). -Using the TCP module of the AMQP-CPP library is easier than using the -raw AMQP::Connection and AMQP::Channel objects, because you do not have to +Using the TCP module of the AMQP-CPP library is easier than using the +raw AMQP::Connection and AMQP::Channel objects, because you do not have to create the sockets and connections yourself, and you also do not have to take care of buffering network data. @@ -376,9 +390,9 @@ EXISTING EVENT LOOPS Both the pure AMQP::Connection as well as the easier AMQP::TcpConnection class allow you to integrate AMQP-CPP in your own event loop. Whether you take care -of running the event loop yourself (for example by using the select() system -call), or if you use an existing library for it (like libevent, libev or libuv), -you can implement the "monitor()" method to watch the file descriptors and +of running the event loop yourself (for example by using the select() system +call), or if you use an existing library for it (like libevent, libev or libuv), +you can implement the "monitor()" method to watch the file descriptors and hand over control back to AMQP-CPP when one of the sockets become active. For libev and libevent users, we have even implemented an example implementation, @@ -394,26 +408,26 @@ int main() { // access to the event loop auto *loop = EV_DEFAULT; - + // handler for libev (so we don't have to implement AMQP::TcpHandler!) AMQP::LibEvHandler handler(loop); - + // 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 ev_run(loop, 0); @@ -426,7 +440,7 @@ The AMQP::LibEvHandler and AMQP::LibEventHandler classes are extended AMQP::TcpH classes, with an implementation of the monitor() method that simply adds the filedescriptor to the event loop. If you use this class however, it is recommended not to instantiate it directly (like we did in the example), but to create your own -"MyHandler" class that extends from it, and in which you also implement the +"MyHandler" class that extends from it, and in which you also implement the onError() method to report possible connection errors to your end users. Currently, we have example TcpHandler implementations for libev, @@ -443,15 +457,15 @@ such examples. HEARTBEATS ========== -The AMQP protocol supports *heartbeats*. If this heartbeat feature is enabled, the -client and the server negotiate a heartbeat interval during connection setup, and -they agree to send at least *some kind of data* over the connection during every -iteration of that interval. The normal data that is sent over the connection (like -publishing or consuming messages) is normally sufficient to keep the connection alive, +The AMQP protocol supports *heartbeats*. If this heartbeat feature is enabled, the +client and the server negotiate a heartbeat interval during connection setup, and +they agree to send at least *some kind of data* over the connection during every +iteration of that interval. The normal data that is sent over the connection (like +publishing or consuming messages) is normally sufficient to keep the connection alive, but if the client or server was idle during the negotiated interval time, a dummy heartbeat message must be sent instead. -The default behavior of the AMQP-CPP library is to disable heartbeats. The +The default behavior of the AMQP-CPP library is to disable heartbeats. The proposed heartbeat interval of the server during connection setup (the server normally suggests an interval of 60 seconds) is vetoed by the AMQP-CPP library so no heartbeats are ever needed to be sent over the connection. This means that you @@ -460,7 +474,7 @@ lasting algorithms after you've consumed a message from RabbitMQ, without having to worry about the connection being idle for too long. You can however choose to enable these heartbeats. If you want to enable heartbeats, -simple implement the onNegotiate() method inside your ConnectionHandler or +simple implement the onNegotiate() method inside your ConnectionHandler or TcpHandler class and have it return the interval that you find appropriate. ````c++ @@ -478,14 +492,14 @@ class MyTcpHandler : public AMQP::TcpHandler */ virtual void onNegotiate(AMQP::TcpConnection *connection, uint16_t interval) { - // we accept the suggestion from the server, but if the interval is smaller + // we accept the suggestion from the server, but if the interval is smaller // that one minute, we will use a one minute interval instead if (interval < 60) interval = 60; // @todo // set a timer in your event loop, and make sure that you call // connection->heartbeat() every _interval_ seconds. - + // return the interval that we want to use return interval; } @@ -503,7 +517,7 @@ In the libev event loop implementation the heartbeats are enabled by default. CHANNELS ======== -In the above example we created a channel object. A channel is a sort of virtual +In the above example we created a channel object. A channel is a sort of virtual connection, and it is possible to create many channels that all use the same connection. @@ -517,8 +531,8 @@ documented. All operations that you can perform on a channel are non-blocking. This means that it is not possible for a method (like Channel::declareExchange()) to -immediately return 'true' or 'false'. Instead, almost every method of the Channel -class returns an instance of the 'Deferred' class. This 'Deferred' object can be +immediately return 'true' or 'false'. Instead, almost every method of the Channel +class returns an instance of the 'Deferred' class. This 'Deferred' object can be used to install handlers that will be called in case of success or failure. For example, if you call the channel.declareExchange() method, the AMQP-CPP library @@ -610,7 +624,7 @@ channel object right after it was constructed. CHANNEL ERRORS ============== -It is important to realize that any error that occurs on a channel, +It is important to realize that any error that occurs on a channel, invalidates the entire channel, including all subsequent instructions that were already sent over it. This means that if you call multiple methods in a row, and the first method fails, all subsequent methods will not be executed either: @@ -642,7 +656,7 @@ requires and extra instruction to be sent to the RabbitMQ server, so some extra bytes are sent over the network, and some additional resources in both the client application and the RabbitMQ server are used (although this is all very limited). -If possible, it is best to make use of this feature. For example, if you have an important AMQP +If possible, it is best to make use of this feature. For example, if you have an important AMQP connection that you use for consuming messages, and at the same time you want to send another instruction to RabbitMQ (like declaring a temporary queue), it is best to set up a new channel for this 'declare' instruction. If the declare fails, @@ -657,7 +671,7 @@ void myDeclareMethod(AMQP::Connection *connection) { // create temporary channel to declare a queue AMQP::Channel channel(connection); - + // declare the queue (the channel object is destructed before the // instruction reaches the server, but the AMQP-CPP library can deal // with this) @@ -840,8 +854,8 @@ channel.commitTransaction() }); ```` -Note that AMQP transactions are not as powerful as transactions that are -knows in the database world. It is not possible to wrap all sort of +Note that AMQP transactions are not as powerful as transactions that are +knows in the database world. It is not possible to wrap all sort of operations in a transaction, they are only meaningful for publishing and consuming. @@ -992,4 +1006,3 @@ class that can be directly plugged into libev, libevent and libuv event loops. For performance reasons, we need to investigate if we can limit the number of times an incoming or outgoing messages is copied. - From 2f7c061cf1b0d8c04dcd0d660f8684a9c2f6b53d Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Fri, 26 Jan 2018 23:51:06 +0100 Subject: [PATCH 067/168] Format fix --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a37af09..ce6d5a4 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Then check out our other commercial and open source solutions: INSTALLING ========== + There are two methods to compile AMQP-CPP, CMake and Make. CMake is platform portable, but the Makefile only works on Linux. ## CMake The CMake file supports both building and installing. You can choose not to use the install functionality, and instead to manually use the build output at bin/. Keep in mind that the TCP module is only supported for Linux. An example install method would be: From c8380d4af06bb3c2aba32421a16fa9b983b8c866 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:24:42 +0100 Subject: [PATCH 068/168] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ce6d5a4..39894e2 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,16 @@ cmake --build .. --target install ### BUILD_SHARED Default OFF + ON: Build shared library + OFF: Build static library ### LINUX_TCP Default OFF + ON: Build linux-only TCP module + OFF: Do not build the module. ## Make From 017dc11dced5abb012e0797b8e236e33c017b598 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:30:54 +0100 Subject: [PATCH 069/168] Update README.md --- README.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 39894e2..671f536 100644 --- a/README.md +++ b/README.md @@ -73,19 +73,10 @@ cmake .. [-DBUILD_SHARED] [-DLINUX_TCP] cmake --build .. --target install ``` -### BUILD_SHARED -Default OFF - -ON: Build shared library - -OFF: Build static library - -### LINUX_TCP -Default OFF - -ON: Build linux-only TCP module - -OFF: Do not build the module. +Option|Default|ON|OFF +------|-------|--|--- +BUILD_SHARED|OFF|Build shared library|Build static library +LINUX_TCP|OFF|Build linux-only TCP module|Don't build linux-only TCP module ## Make Installing the library is as easy From 8f7b3f6fd427dc54fe1bb50b9f88e02f58fa008a Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:34:09 +0100 Subject: [PATCH 070/168] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 671f536..ba747fe 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,10 @@ cmake .. [-DBUILD_SHARED] [-DLINUX_TCP] cmake --build .. --target install ``` -Option|Default|ON|OFF -------|-------|--|--- -BUILD_SHARED|OFF|Build shared library|Build static library -LINUX_TCP|OFF|Build linux-only TCP module|Don't build linux-only TCP module +Option|Default|Meaning +------|-------|------- +BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? +LINUX_TCP|OFF|Is the linux-only TCP module built? ## Make Installing the library is as easy From f90f0ed3213f958082c8222dbd19be238a4203fa Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:35:22 +0100 Subject: [PATCH 071/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba747fe..33725af 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ INSTALLING There are two methods to compile AMQP-CPP, CMake and Make. CMake is platform portable, but the Makefile only works on Linux. ## CMake -The CMake file supports both building and installing. You can choose not to use the install functionality, and instead to manually use the build output at bin/. Keep in mind that the TCP module is only supported for Linux. An example install method would be: +The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at bin/. Keep in mind that the TCP module is only supported for Linux. An example install method would be: ``` bash mkdir build cd build From f21425431963699c1fecebd0bc6a1cae2cd9384b Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:35:55 +0100 Subject: [PATCH 072/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33725af..95f53fa 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ INSTALLING There are two methods to compile AMQP-CPP, CMake and Make. CMake is platform portable, but the Makefile only works on Linux. ## CMake -The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at bin/. Keep in mind that the TCP module is only supported for Linux. An example install method would be: +The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: ``` bash mkdir build cd build From f4ab1137d3c22e3f4661f77098456a35881fb5bf Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:40:51 +0100 Subject: [PATCH 073/168] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 95f53fa..64b8a19 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,12 @@ Then check out our other commercial and open source solutions: INSTALLING ========== -There are two methods to compile AMQP-CPP, CMake and Make. CMake is platform portable, but the Makefile only works on Linux. +There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. After building there are two relevant files to include when using the library. +File|Include when? +----|------------ +amqpcpp.h|Always +amqpcpp/linux_tcp.h|If using the Linux-only TCP module. + ## CMake The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: ``` bash From 6065fbfe90d7f7bd6721a1915c993a61bb4a320f Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:41:30 +0100 Subject: [PATCH 074/168] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 64b8a19..f745698 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ INSTALLING ========== There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. After building there are two relevant files to include when using the library. + File|Include when? ----|------------ amqpcpp.h|Always From 3909cb3ea26f66d8d05c0e3665dae5b7f64f824d Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:42:00 +0100 Subject: [PATCH 075/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f745698..5d08c23 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform por File|Include when? ----|------------ amqpcpp.h|Always -amqpcpp/linux_tcp.h|If using the Linux-only TCP module. +amqpcpp/linux_tcp.h|If using the Linux-only TCP module ## CMake The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: From a783afc1cd017d2004e3332efc4afd894233cd07 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:42:37 +0100 Subject: [PATCH 076/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d08c23..37f415e 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ cmake --build .. --target install Option|Default|Meaning ------|-------|------- BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? -LINUX_TCP|OFF|Is the linux-only TCP module built? +LINUX_TCP|OFF|Should the linux-only TCP module be built? ## Make Installing the library is as easy From 89d96072308ef66e87d0353e89c4e74e9fd74b43 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:42:55 +0100 Subject: [PATCH 077/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37f415e..c0dec81 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ cmake --build .. --target install Option|Default|Meaning ------|-------|------- BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? -LINUX_TCP|OFF|Should the linux-only TCP module be built? +LINUX_TCP|OFF|Should the Linux-only TCP module be built? ## Make Installing the library is as easy From 0b8e77fdd24c607547ccf26a7449914e8c291d7f Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:46:47 +0100 Subject: [PATCH 078/168] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c0dec81..8fc1349 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Then check out our other commercial and open source solutions: INSTALLING ========== +AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. After building there are two relevant files to include when using the library. From 09bf7e6fa548d0511ed7f3261e845254ee7fa835 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Sun, 28 Jan 2018 22:59:21 +0100 Subject: [PATCH 079/168] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 07bf07c..22026a9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -[![Build status](https://ci.appveyor.com/api/projects/status/4fnbmx32chf3h3f7?svg=true)](https://ci.appveyor.com/project/surgura/amqp-cpp) VC++ - -[![Build Status](https://travis-ci.org/surgura/AMQP-CPP.svg?branch=master)](https://travis-ci.org/surgura/AMQP-CPP) GCC & Clang - AMQP-CPP ======== From 221ed561d9d2ae66168b02c1385d1d14ed12ad0b Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 00:28:50 +0100 Subject: [PATCH 080/168] Fixed shared build on windows. Prefixed compile options. --- CMakeLists.txt | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f94ca7a..911a70d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,8 @@ cmake_minimum_required(VERSION 3.1) project(amqpcpp) # build options -option(BUILD_SHARED "Build shared library. If off, build will be static." OFF) -option(LINUX_TCP "Build linux sockets implementation." OFF) +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) # ensure c++11 on all compilers set (CMAKE_CXX_STANDARD 11) @@ -47,16 +47,16 @@ endmacro() # add source files add_subdirectory(src) -if(LINUX_TCP) +if(AMQP-CPP_LINUX_TCP) add_subdirectory(src/linux_tcp) endif() # settings for specific compilers # ------------------------------------------------------------------------------------------------------ -# we have to prevent windows from defining the max macro. TODO why WIN32_LEAN_AND_MEAN? +# we have to prevent windows from defining the max macro. if (WIN32) - add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) + add_definitions(-DNOMINMAX) endif() # build targets @@ -65,7 +65,7 @@ endif() # set output directory set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) -if(BUILD_SHARED) +if(AMQP-CPP_BUILD_SHARED) # create shared lib add_library(${PROJECT_NAME} SHARED ${SRCS}) # set shared lib version @@ -78,13 +78,15 @@ endif() # install rules # ------------------------------------------------------------------------------------------------------ -if(BUILD_SHARED) - # copy static lib +if(AMQP-CPP_BUILD_SHARED) + # copy shared lib and its static counter part install(TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION lib LIBRARY DESTINATION lib + RUNTIME DESTINATION lib ) else() - # copy shared lib and its static counter part + # copy static lib install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION lib ) From 5cbc09c6806b822b53934725db2c2ed9c016a258 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 10:45:30 +0100 Subject: [PATCH 081/168] Update CMakeLists.txt --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 911a70d..3c8fddb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ # # Options: # -# - BUILD_SHARED (default OFF) +# - AMQP-CPP_BUILD_SHARED (default OFF) # ON: Build shared lib # OFF: Build static lib # -# - LINUX_TCP (default OFF) +# - AMQP-CPP_LINUX_TCP (default OFF) # ON: Build posix handler implementation # OFF: Don't build posix handler implementation @@ -94,4 +94,4 @@ endif() # copy header files install(DIRECTORY include/amqpcpp/ DESTINATION include/amqpcpp FILES_MATCHING PATTERN "*.h") -install(FILES include/amqpcpp.h DESTINATION include) \ No newline at end of file +install(FILES include/amqpcpp.h DESTINATION include) From ea70d43034f3a7126c78065a951a447e37409ef2 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 16:34:22 +0100 Subject: [PATCH 082/168] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 22026a9..88aed6c 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ File|Include when? amqpcpp.h|Always amqpcpp/linux_tcp.h|If using the Linux-only TCP module +On Windows you are required to define NOMINMAX when compiling code that includes public AMQP-CPP header files. + ## CMake The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: ``` bash From 3ea9972cf6e5134fbf6e7696418494a9ccb17db2 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 16:34:50 +0100 Subject: [PATCH 083/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88aed6c..6ff9d91 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ File|Include when? amqpcpp.h|Always amqpcpp/linux_tcp.h|If using the Linux-only TCP module -On Windows you are required to define NOMINMAX when compiling code that includes public AMQP-CPP header files. +On Windows you are required to define `NOMINMAX` when compiling code that includes public AMQP-CPP header files. ## CMake The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: From f76f617c5a275a73cf7f0dda0c279f679b5bf188 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 16:35:41 +0100 Subject: [PATCH 084/168] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ff9d91..333ec76 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,9 @@ INSTALLING ========== AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. -There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. After building there are two relevant files to include when using the library. +There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. + +After building there are two relevant files to include when using the library. File|Include when? ----|------------ From 51ee13311775aa95c031a5e20a47c1ff92790e5d Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 16:37:57 +0100 Subject: [PATCH 085/168] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 333ec76..bb9bd5f 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Then check out our other commercial and open source solutions: INSTALLING ========== -AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. +AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. If you use this module, you are required to link with `pthread`. There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. From 0995b3e0ec3e30b6f2a65a817b9ebc9c74b2cce0 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 17:12:06 +0100 Subject: [PATCH 086/168] fixed htonll compile error on windows. --- include/amqpcpp/endian.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/amqpcpp/endian.h b/include/amqpcpp/endian.h index 8662464..f1adab9 100644 --- a/include/amqpcpp/endian.h +++ b/include/amqpcpp/endian.h @@ -72,7 +72,7 @@ #define be32toh(x) ntohl(x) #define le32toh(x) (x) -#define htobe64(x) htonll(x) +#define htobe64(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) #define htole64(x) (x) #define be64toh(x) ntohll(x) #define le64toh(x) (x) From 99974d36d3f723fd56d68c2c07a1d2f1db929d80 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 17:29:27 +0100 Subject: [PATCH 087/168] fixed strcasecmp error on windows platforms. --- include/amqpcpp.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/amqpcpp.h b/include/amqpcpp.h index 9b2b097..85b7867 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -28,6 +28,11 @@ #include #include +// 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" From 6243786b0ee30f483aee1b29c35f729774d8cb30 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 29 Jan 2018 18:01:25 +0100 Subject: [PATCH 088/168] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb9bd5f..f38e941 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ INSTALLING ========== AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. If you use this module, you are required to link with `pthread`. -There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. +There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. Building of a shared library is currently not supported on Windows. After building there are two relevant files to include when using the library. @@ -86,7 +86,7 @@ cmake --build .. --target install Option|Default|Meaning ------|-------|------- -BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? +BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. LINUX_TCP|OFF|Should the Linux-only TCP module be built? ## Make From 3d823adb5119d4a0cea24d0d48c416147318e81c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 30 Jan 2018 09:27:43 +0100 Subject: [PATCH 089/168] add include for boost/function.hpp --- include/amqpcpp/libboostasio.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 3f80b89..5937286 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -25,6 +25,7 @@ #include #include #include +#include // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L From c2873827707146a9e5df1517378be43e5d22a16a Mon Sep 17 00:00:00 2001 From: Aljar Meesters Date: Tue, 30 Jan 2018 10:45:16 +0100 Subject: [PATCH 090/168] fix includes because existing code was no longer compiling after the latest changes --- include/amqpcpp.h | 9 ++++++++- include/amqpcpp/libev.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/amqpcpp.h b/include/amqpcpp.h index 85b7867..f002eae 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -3,7 +3,8 @@ * * Starting point for all includes of the Copernica AMQP library * - * @documentation public + * @author Emiel Bruijntjes + * @copyright 2015 - 2018 Copernica BV */ #pragma once @@ -77,3 +78,9 @@ #include "amqpcpp/connectionhandler.h" #include "amqpcpp/connectionimpl.h" #include "amqpcpp/connection.h" + +// tcp level includes +#include "amqpcpp/tcphandler.h" +#include "amqpcpp/tcpconnection.h" +#include "amqpcpp/tcpchannel.h" + diff --git a/include/amqpcpp/libev.h b/include/amqpcpp/libev.h index 9e6ea1a..d14a3b6 100644 --- a/include/amqpcpp/libev.h +++ b/include/amqpcpp/libev.h @@ -8,7 +8,7 @@ * Compile with: "g++ -std=c++11 libev.cpp -lamqpcpp -lev -lpthread" * * @author Emiel Bruijntjes - * @copyright 2015 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** From 4b2c423896238c0b5eda5a574d919da32140d083 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 30 Jan 2018 10:07:27 +0000 Subject: [PATCH 091/168] Build examples now with travis-ci to ensure new changes don't break existing code. Fixed warnings as a result of lambda captures. --- .travis.yml | 95 +++++++++++++++------------- CMakeLists.txt | 6 ++ README.md | 2 + examples/CMakeLists.txt | 32 ++++++++++ {tests => examples}/libboostasio.cpp | 0 {tests => examples}/libev.cpp | 0 {tests => examples}/libevent.cpp | 0 {tests => examples}/libuv.cpp | 0 include/amqpcpp/libboostasio.h | 54 ++++++++++------ include/amqpcpp/libev.h | 2 + include/amqpcpp/libuv.h | 2 + src/CMakeLists.txt | 77 ++++++++++++++++++++++ src/linux_tcp/CMakeLists.txt | 9 +++ 13 files changed, 216 insertions(+), 63 deletions(-) create mode 100644 examples/CMakeLists.txt rename {tests => examples}/libboostasio.cpp (100%) rename {tests => examples}/libev.cpp (100%) rename {tests => examples}/libevent.cpp (100%) rename {tests => examples}/libuv.cpp (100%) diff --git a/.travis.yml b/.travis.yml index 06f69b0..d3be12c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ ################ -# project config +# Config ################ # C++ project @@ -11,7 +11,14 @@ group: edge ################ -# build matrix +# Services +################ + +services: + - docker + +################ +# Build matrix ################ matrix: @@ -22,61 +29,63 @@ matrix: ################ - os: linux - compiler: gcc - env: COMPILER=g++-5 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-5', 'ninja-build'] + env: + - COMPILER_PACKAGE=g++-6 + - C_COMPILER=gcc-6 + - CXX_COMPILER=g++-6 - os: linux compiler: gcc - env: COMPILER=g++-6 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'ninja-build'] - - - os: linux - compiler: gcc - env: COMPILER=g++-7 - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-7', 'ninja-build'] + env: + - COMPILER_PACKAGE=g++-7 + - C_COMPILER=gcc-7 + - CXX_COMPILER=g++-7 ################ # Linux / Clang ################ - os: linux - compiler: clang - env: COMPILER=clang++-4.0 - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0'] - packages: ['clang-4.0', 'ninja-build'] + env: + - COMPILER_PACKAGE=clang-4.0 + - C_COMPILER=clang-4.0 + - CXX_COMPILER=clang++-4.0 - os: linux - compiler: clang - env: COMPILER=clang++-5.0 - addons: - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] - packages: ['clang-5.0', 'ninja-build'] + env: + - COMPILER_PACKAGE=clang-5.0 + - C_COMPILER=clang-5.0 + - CXX_COMPILER=clang++-5.0 + +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 -d --name ubuntu-test-container -v $(pwd):/travis ubuntu:artful 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 $COMPILER_PACKAGE && + apt-get -y clean && rm -rf /var/lib/apt/lists/*" ################ -# build / test +# Build / Test ################ script: - # show OS/compiler version - - uname -a - - $CXX --version - - # compile and execute unit tests - - mkdir -p build.release && cd build.release - - cmake .. ${CMAKE_OPTIONS} -GNinja && cmake --build . --config Release - - cd .. - + # 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 -GNinja && + cmake --build . --config Release && + cd .." diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c8fddb..b9875bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ project(amqpcpp) # 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) # ensure c++11 on all compilers set (CMAKE_CXX_STANDARD 11) @@ -91,6 +92,11 @@ else() ARCHIVE DESTINATION lib ) endif() + +if(AMQP-CPP_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + # copy header files install(DIRECTORY include/amqpcpp/ DESTINATION include/amqpcpp FILES_MATCHING PATTERN "*.h") diff --git a/README.md b/README.md index f38e941..ef0178e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ AMQP-CPP ======== +[![Build Status](https://travis-ci.org/CopernicaMarketingSoftware/AMQP-CPP.svg?branch=master)](https://travis-ci.org/CopernicaMarketingSoftware/AMQP-CPP) + AMQP-CPP is a C++ library for communicating with a RabbitMQ message broker. The library can be used to parse incoming data from a RabbitMQ server, and to generate frames that can be sent to a RabbitMQ server. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..beee70e --- /dev/null +++ b/examples/CMakeLists.txt @@ -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) + +################################### +# Libev +################################### + +add_executable(amqpcpp_libev_example libev.cpp) + +add_dependencies(amqpcpp_libev_example amqpcpp) + +target_link_libraries(amqpcpp_libev_example amqpcpp ev pthread) + + +################################### +# Libuv +################################### + +add_executable(amqpcpp_libuv_example libuv.cpp) + +add_dependencies(amqpcpp_libuv_example amqpcpp) + +target_link_libraries(amqpcpp_libuv_example amqpcpp uv pthread) \ No newline at end of file diff --git a/tests/libboostasio.cpp b/examples/libboostasio.cpp similarity index 100% rename from tests/libboostasio.cpp rename to examples/libboostasio.cpp diff --git a/tests/libev.cpp b/examples/libev.cpp similarity index 100% rename from tests/libev.cpp rename to examples/libev.cpp diff --git a/tests/libevent.cpp b/examples/libevent.cpp similarity index 100% rename from tests/libevent.cpp rename to examples/libevent.cpp diff --git a/tests/libuv.cpp b/examples/libuv.cpp similarity index 100% rename from tests/libuv.cpp rename to examples/libuv.cpp diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 5937286..25b4575 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -24,9 +24,12 @@ #include #include #include +#include #include #include +#include "amqpcpp/linux_tcp.h" + // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L #define PTR_FROM_THIS weak_from_this @@ -60,11 +63,13 @@ private: */ boost::asio::io_service & _ioservice; + typedef std::weak_ptr strand_weak_ptr; + /** * The boost asio io_service::strand managed pointer. * @var class std::shared_ptr */ - std::weak_ptr _strand; + strand_weak_ptr _wpstrand; /** * The boost tcp socket. @@ -108,15 +113,17 @@ private: */ handler_cb get_dispatch_wrapper(io_handler fn) { - return [fn, strand = _strand](const boost::system::error_code &ec, const std::size_t bytes_transferred) + const strand_weak_ptr wpstrand = _wpstrand; + + return [fn, wpstrand](const boost::system::error_code &ec, const std::size_t bytes_transferred) { - const std::shared_ptr apStrand = strand.lock(); - if (!apStrand) + 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; } - apStrand->dispatch(boost::bind(fn, ec, bytes_transferred)); + strand->dispatch(boost::bind(fn, ec, bytes_transferred)); }; } @@ -229,14 +236,14 @@ private: * Constructor- initialises the watcher and assigns the filedescriptor to * a boost socket for monitoring. * @param io_service The boost io_service - * @param strand A weak pointer to a io_service::strand instance. + * @param wpstrand A weak pointer to a io_service::strand instance. * @param fd The filedescriptor being watched */ Watcher(boost::asio::io_service &io_service, - const std::weak_ptr strand, + const strand_weak_ptr wpstrand, const int fd) : _ioservice(io_service), - _strand(strand), + _wpstrand(wpstrand), _socket(_ioservice) { _socket.assign(fd); @@ -309,11 +316,13 @@ private: */ boost::asio::io_service & _ioservice; + typedef std::weak_ptr strand_weak_ptr; + /** * The boost asio io_service::strand managed pointer. * @var class std::shared_ptr */ - std::weak_ptr _strand; + strand_weak_ptr _wpstrand; /** * The boost asynchronous deadline timer. @@ -330,21 +339,24 @@ private: */ handler_fn get_handler(TcpConnection *const connection, const uint16_t timeout) { - auto fn = boost::bind(&Timer::timeout, + const auto fn = boost::bind(&Timer::timeout, this, _1, PTR_FROM_THIS(), connection, timeout); - return [fn, strand = _strand](const boost::system::error_code &ec) + + const strand_weak_ptr wpstrand = _wpstrand; + + return [fn, wpstrand](const boost::system::error_code &ec) { - const std::shared_ptr apStrand = strand.lock(); - if (!apStrand) + const strand_shared_ptr strand = wpstrand.lock(); + if (!strand) { fn(boost::system::errc::make_error_code(boost::system::errc::operation_canceled)); return; } - apStrand->dispatch(boost::bind(fn, ec)); + strand->dispatch(boost::bind(fn, ec)); }; } @@ -394,13 +406,13 @@ private: /** * Constructor * @param io_service The boost asio io_service. - * @param strand A weak pointer to a io_service::strand instance. + * @param wpstrand A weak pointer to a io_service::strand instance. */ Timer(boost::asio::io_service &io_service, - const std::weak_ptr strand) : - _ioservice(io_service), - _strand(strand), - _timer(_ioservice) + const strand_weak_ptr wpstrand) : + _ioservice(io_service), + _wpstrand(wpstrand), + _timer(_ioservice) { } @@ -446,11 +458,13 @@ private: */ boost::asio::io_service & _ioservice; + typedef std::shared_ptr strand_shared_ptr; + /** * The boost asio io_service::strand managed pointer. * @var class std::shared_ptr */ - std::shared_ptr _strand; + strand_shared_ptr _strand; /** diff --git a/include/amqpcpp/libev.h b/include/amqpcpp/libev.h index d14a3b6..1fc1f32 100644 --- a/include/amqpcpp/libev.h +++ b/include/amqpcpp/libev.h @@ -21,6 +21,8 @@ */ #include +#include "amqpcpp/linux_tcp.h" + /** * Set up namespace */ diff --git a/include/amqpcpp/libuv.h b/include/amqpcpp/libuv.h index d48303f..eebf204 100644 --- a/include/amqpcpp/libuv.h +++ b/include/amqpcpp/libuv.h @@ -21,6 +21,8 @@ */ #include +#include "amqpcpp/linux_tcp.h" + /** * Set up namespace */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d61b3e0..6cc8e1a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,91 @@ 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 + 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 deferredconsumer.cpp deferredconsumerbase.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 ) diff --git a/src/linux_tcp/CMakeLists.txt b/src/linux_tcp/CMakeLists.txt index d5cb67f..55d0e16 100644 --- a/src/linux_tcp/CMakeLists.txt +++ b/src/linux_tcp/CMakeLists.txt @@ -1,3 +1,12 @@ add_sources( + addressinfo.h + includes.h + pipe.h + tcpclosed.h + tcpconnected.h tcpconnection.cpp + tcpinbuffer.h + tcpoutbuffer.h + tcpresolver.h + tcpstate.h ) From 1e9d1360f73a2802002d2bfe749805ec40da6944 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 30 Jan 2018 10:43:08 +0000 Subject: [PATCH 092/168] Fixed build error. --- include/amqpcpp.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/amqpcpp.h b/include/amqpcpp.h index f002eae..4d7735a 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -80,7 +80,5 @@ #include "amqpcpp/connection.h" // tcp level includes -#include "amqpcpp/tcphandler.h" -#include "amqpcpp/tcpconnection.h" -#include "amqpcpp/tcpchannel.h" +#include "amqpcpp/linux_tcp.h" From af1b54e26822dfcd61de6d5b6554ec306d03ee9b Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 30 Jan 2018 11:45:40 +0000 Subject: [PATCH 093/168] Removed double inclusion of boost/function.hpp --- include/amqpcpp/libboostasio.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 25b4575..18b2a10 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include From da8aef4810c94560f5711799de4070337c1e5890 Mon Sep 17 00:00:00 2001 From: zerodefect Date: Tue, 30 Jan 2018 12:11:32 +0000 Subject: [PATCH 094/168] Update CMakeLists.txt --- CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9875bc..9bcb914 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,11 @@ if(AMQP-CPP_LINUX_TCP) add_subdirectory(src/linux_tcp) endif() +# potentially build the examples +if(AMQP-CPP_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + # settings for specific compilers # ------------------------------------------------------------------------------------------------------ @@ -93,10 +98,6 @@ else() ) endif() -if(AMQP-CPP_BUILD_EXAMPLES) - add_subdirectory(examples) -endif() - # copy header files install(DIRECTORY include/amqpcpp/ DESTINATION include/amqpcpp FILES_MATCHING PATTERN "*.h") From c1ea6a47a57c2d9142706e7fca8e745412b12750 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Wed, 31 Jan 2018 16:42:19 +0100 Subject: [PATCH 095/168] add gcc5 support --- .travis.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d3be12c..49caa01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,14 @@ matrix: # Linux / GCC ################ + - os: linux + compiler: gcc + env: + - COMPILER_PACKAGE=g++-5 + - C_COMPILER=gcc-5 + - CXX_COMPILER=g++-5 + - CXXFLAGS=-std=c++11 + - os: linux env: - COMPILER_PACKAGE=g++-6 @@ -86,6 +94,6 @@ script: 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 -GNinja && - cmake --build . --config Release && + cmake ${CMAKE_OPTIONS} -DAMQP-CPP_BUILD_EXAMPLES=ON -DAMQP-CPP_LINUX_TCP=ON -GNinja .. && + cmake --config Release --build . && cd .." From b713d48bb97cd674ed145414733ee8cead54f90c Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Thu, 1 Feb 2018 12:44:48 +0100 Subject: [PATCH 096/168] add second constructor with heartbeat interval; apply modern cpp typedefs; Add comments; --- include/amqpcpp/libboostasio.h | 40 +++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 18b2a10..2ba7918 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -62,7 +62,7 @@ private: */ boost::asio::io_service & _ioservice; - typedef std::weak_ptr strand_weak_ptr; + using strand_weak_ptr = std::weak_ptr; /** * The boost asio io_service::strand managed pointer. @@ -77,7 +77,6 @@ private: */ boost::asio::posix::stream_descriptor _socket; - /** * A boolean that indicates if the watcher is monitoring for read events. * @var _read True if reads are being monitored else false. @@ -315,7 +314,7 @@ private: */ boost::asio::io_service & _ioservice; - typedef std::weak_ptr strand_weak_ptr; + using strand_weak_ptr = std::weak_ptr; /** * The boost asio io_service::strand managed pointer. @@ -330,6 +329,7 @@ private: boost::asio::deadline_timer _timer; using handler_fn = boost::function; + /** * Binds and returns a lamba function handler for the io operation. * @param connection The connection being watched. @@ -457,7 +457,7 @@ private: */ boost::asio::io_service & _ioservice; - typedef std::shared_ptr strand_shared_ptr; + using strand_shared_ptr = std::shared_ptr; /** * The boost asio io_service::strand managed pointer. @@ -465,16 +465,23 @@ private: */ strand_shared_ptr _strand; - /** * All I/O watchers that are active, indexed by their filedescriptor * @var std::map */ std::map > _watchers; - + /** + * The boost asio io_service::deadline_timer managed pointer. + * @var class std::shared_ptr + */ std::shared_ptr _timer; + /** + * The heartbeat timer interval (in seconds). + * @var uint16_t + */ + uint16_t _timer_interval; /** * Method that is called by AMQP-CPP to register a filedescriptor for readability or writability @@ -528,6 +535,9 @@ protected: // skip if no heartbeats are needed if (interval == 0) return 0; + // use the most frequent heartbeat interval (user-specified or rabbit server default). + interval = (_timer_interval > 0 && _timer_interval < interval) ? _timer_interval : interval; + // set the timer _timer->set(connection, interval); @@ -551,7 +561,22 @@ public: explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service), _strand(std::make_shared(_ioservice)), - _timer(std::make_shared(_ioservice,_strand)) + _timer(std::make_shared(_ioservice,_strand)), + _timer_interval(0) + { + + } + + /** + * Constructor + * @param io_service The boost io_service to wrap + * @param interval The interval to use when negotiating heartbeat with rabbit server. + */ + explicit LibBoostAsioHandler(boost::asio::io_service &io_service, uint16_t interval) : + _ioservice(io_service), + _strand(std::make_shared(_ioservice)), + _timer(std::make_shared(_ioservice,_strand)), + _timer_interval(interval) { } @@ -584,4 +609,3 @@ public: * End of namespace */ } - From ae3b94fe92b3e5b39b29ff57b33cc9ee7d3b5803 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Thu, 1 Feb 2018 13:05:41 +0100 Subject: [PATCH 097/168] use custom heartbeat interval outright, if set. --- include/amqpcpp/libboostasio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 2ba7918..fcc0705 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -536,7 +536,7 @@ protected: if (interval == 0) return 0; // use the most frequent heartbeat interval (user-specified or rabbit server default). - interval = (_timer_interval > 0 && _timer_interval < interval) ? _timer_interval : interval; + interval = (_timer_interval > 0) ? _timer_interval : interval; // set the timer _timer->set(connection, interval); From 33c8e76a7ea73a5595002e74ad4be80ccd3f5ba8 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Thu, 1 Feb 2018 13:11:23 +0100 Subject: [PATCH 098/168] correct comment --- include/amqpcpp/libboostasio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index fcc0705..56f73d4 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -535,7 +535,7 @@ protected: // skip if no heartbeats are needed if (interval == 0) return 0; - // use the most frequent heartbeat interval (user-specified or rabbit server default). + // choose heartbeat interval to use (user-specified or rabbit server default). interval = (_timer_interval > 0) ? _timer_interval : interval; // set the timer From 5a853134aae2b26cc36618c641bcf655e1cb3448 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Thu, 1 Feb 2018 15:27:59 +0100 Subject: [PATCH 099/168] remove second ctor with heartbeat interval (extend class instead). Change private to protected to allow LibBoostAsioHandler to be extended. --- include/amqpcpp/libboostasio.h | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 56f73d4..9c09645 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -47,7 +47,7 @@ namespace AMQP { */ class LibBoostAsioHandler : public virtual TcpHandler { -private: +protected: /** * Helper class that wraps a boost io_service socket monitor. @@ -477,12 +477,6 @@ private: */ std::shared_ptr _timer; - /** - * The heartbeat timer interval (in seconds). - * @var uint16_t - */ - uint16_t _timer_interval; - /** * Method that is called by AMQP-CPP to register a filedescriptor for readability or writability * @param connection The TCP connection object that is reporting @@ -535,9 +529,6 @@ protected: // skip if no heartbeats are needed if (interval == 0) return 0; - // choose heartbeat interval to use (user-specified or rabbit server default). - interval = (_timer_interval > 0) ? _timer_interval : interval; - // set the timer _timer->set(connection, interval); @@ -567,20 +558,6 @@ public: } - /** - * Constructor - * @param io_service The boost io_service to wrap - * @param interval The interval to use when negotiating heartbeat with rabbit server. - */ - explicit LibBoostAsioHandler(boost::asio::io_service &io_service, uint16_t interval) : - _ioservice(io_service), - _strand(std::make_shared(_ioservice)), - _timer(std::make_shared(_ioservice,_strand)), - _timer_interval(interval) - { - - } - /** * Handler cannot be copied or moved * From accc1810a07e9871cc1c3b299ab5e14b9e40a216 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Thu, 1 Feb 2018 15:31:19 +0100 Subject: [PATCH 100/168] remove timer interval from ctor init list --- include/amqpcpp/libboostasio.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 9c09645..c6fb532 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -552,8 +552,7 @@ public: explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service), _strand(std::make_shared(_ioservice)), - _timer(std::make_shared(_ioservice,_strand)), - _timer_interval(0) + _timer(std::make_shared(_ioservice,_strand)) { } From f5540e9af2d44211159cf5067a5cc0f199064a12 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Feb 2018 21:54:56 +0100 Subject: [PATCH 101/168] fixed autodelete flag for declaring an exchange and added support for internal exchange. this fixes #183 --- include/amqpcpp/channel.h | 3 ++- include/amqpcpp/flags.h | 3 ++- src/channelimpl.cpp | 11 +++++++++-- src/exchangedeclareframe.h | 31 +++++++++++++++++++++++++------ src/flags.cpp | 3 ++- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/include/amqpcpp/channel.h b/include/amqpcpp/channel.h index bff4085..c41e0aa 100644 --- a/include/amqpcpp/channel.h +++ b/include/amqpcpp/channel.h @@ -1,7 +1,7 @@ /** * Class describing a (mid-level) AMQP channel implementation * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -170,6 +170,7 @@ public: * - 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 diff --git a/include/amqpcpp/flags.h b/include/amqpcpp/flags.h index 3a4a2c3..37779af 100644 --- a/include/amqpcpp/flags.h +++ b/include/amqpcpp/flags.h @@ -3,7 +3,7 @@ * * The various flags that are supported * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -38,6 +38,7 @@ extern const int multiple; extern const int requeue; extern const int readable; extern const int writable; +extern const int internal; /** * End of namespace diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 9d32874..e655657 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -252,7 +252,7 @@ Deferred &ChannelImpl::close() Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType type, int flags, const Table &arguments) { // convert exchange type - std::string exchangeType; + const char *exchangeType = ""; // convert the exchange type into a string if (type == ExchangeType::fanout) exchangeType = "fanout"; @@ -261,8 +261,15 @@ Deferred &ChannelImpl::declareExchange(const std::string &name, ExchangeType typ else if (type == ExchangeType::headers) exchangeType = "headers"; else if (type == ExchangeType::consistent_hash) exchangeType = "x-consistent-hash"; + // the boolean options + bool passive = flags & AMQP::passive; + bool durable = flags & AMQP::durable; + bool autodelete = flags & AMQP::autodelete; + bool internal = flags & AMQP::internal; + bool nowait = flags & AMQP::nowait; + // send declare exchange frame - return push(ExchangeDeclareFrame(_id, name, exchangeType, (flags & passive) != 0, (flags & durable) != 0, false, arguments)); + return push(ExchangeDeclareFrame(_id, name, exchangeType, passive, durable, autodelete, internal, nowait, arguments)); } /** diff --git a/src/exchangedeclareframe.h b/src/exchangedeclareframe.h index cb45f01..5e02b2c 100644 --- a/src/exchangedeclareframe.h +++ b/src/exchangedeclareframe.h @@ -1,7 +1,7 @@ /** * Class describing an AMQP exchange declare frame * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -77,16 +77,17 @@ public: * @param type exchange type * @param passive do not create exchange if it does not exist * @param durable durable exchange + * @param autodelete is this an auto-delete exchange? + * @param internal is this an internal exchange * @param noWait do not wait on response * @param arguments additional arguments */ - ExchangeDeclareFrame(uint16_t channel, const std::string& name, const std::string& type, bool passive, bool durable, bool noWait, const Table& arguments) : - ExchangeFrame(channel, (uint32_t)(name.length() + type.length() + arguments.size() + 5)), // size of name, type and arguments + 1 (all booleans are stored in 1 byte) + 2 (deprecated short) + 2 (string sizes) + ExchangeDeclareFrame(uint16_t channel, const std::string& name, const char *type, bool passive, bool durable, bool autodelete, bool internal, bool nowait, const Table& arguments) : + ExchangeFrame(channel, (uint32_t)(name.length() + strlen(type) + arguments.size() + 5)), // size of name, type and arguments + 1 (all booleans are stored in 1 byte) + 2 (deprecated short) + 2 (string sizes) _name(name), _type(type), - _bools(passive, durable, false, false, noWait), - _arguments(arguments) - {} + _bools(passive, durable, autodelete, internal, nowait), + _arguments(arguments) {} /** * Construct parsing a declare frame from a received frame @@ -162,6 +163,24 @@ public: { return _bools.get(1); } + + /** + * Is this an autodelete exchange? + * @return bool + */ + bool autoDelete() const + { + return _bools.get(2); + } + + /** + * Is this an internal exchange? + * @return bool + */ + bool internal() const + { + return _bools.get(3); + } /** * Do not wait for a response diff --git a/src/flags.cpp b/src/flags.cpp index 946b51c..24351cb 100644 --- a/src/flags.cpp +++ b/src/flags.cpp @@ -3,7 +3,7 @@ * * The various flags that are supported * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ #include "includes.h" @@ -32,6 +32,7 @@ const int immediate = 0x1000; const int redelivered = 0x2000; const int multiple = 0x4000; const int requeue = 0x8000; +const int internal = 0x10000; /** * Flags for event loops From ec327de3961547b752d0dda3cbc421568ef86212 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 7 Feb 2018 10:08:32 +0100 Subject: [PATCH 102/168] less conservative caching of outgoing data. This fixes #184 --- include/amqpcpp/channelimpl.h | 12 +++++++++--- src/channelimpl.cpp | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/amqpcpp/channelimpl.h b/include/amqpcpp/channelimpl.h index 1ba6130..3d90266 100644 --- a/include/amqpcpp/channelimpl.h +++ b/include/amqpcpp/channelimpl.h @@ -5,7 +5,7 @@ * that has a private constructor so that it can not be used from outside * the AMQP library * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -122,7 +122,8 @@ private: std::queue> _queue; /** - * Are we currently operating in synchronous mode? + * 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; @@ -567,6 +568,9 @@ public: // if we are still in connected state we are now ready if (_state == state_connected) _state = state_ready; + + // the last (possibly synchronous) operation was received, so we're no longer in synchronous mode + if (_synchronous && _queue.empty()) _synchronous = false; // inform handler if (_readyCallback) _readyCallback(); @@ -586,7 +590,6 @@ public: { // change state _state = state_closed; - _synchronous = false; // create a monitor, because the callbacks could destruct the current object Monitor monitor(this); @@ -620,6 +623,9 @@ public: { // skip if there is no oldest callback if (!_oldestCallback) return true; + + // the last (possibly synchronous) operation was received, so we're no longer in synchronous mode + if (_synchronous && _queue.empty()) _synchronous = false; // we are going to call callbacks that could destruct the channel Monitor monitor(this); diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index e655657..a498864 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -3,7 +3,7 @@ * * Implementation for a channel * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ #include "includes.h" #include "basicdeliverframe.h" @@ -693,7 +693,7 @@ bool ChannelImpl::send(const Frame &frame) // added to the list of deferred objects. it will be notified about // the error when the close operation succeeds if (_state == state_closing) return true; - + // are we currently in synchronous mode or are there // other frames waiting for their turn to be sent? if (_synchronous || !_queue.empty()) From 65da35d4643c58f023ee3ab0bfbf4f7845f4fea8 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 19 Feb 2018 12:07:07 +0100 Subject: [PATCH 103/168] Fix documentation to contain correct build options. README.md contained incorrectly named build options. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ef0178e..715e6a5 100644 --- a/README.md +++ b/README.md @@ -82,14 +82,14 @@ The CMake file supports both building and installing. You can choose not to use ``` bash mkdir build cd build -cmake .. [-DBUILD_SHARED] [-DLINUX_TCP] +cmake .. [-DAMQP-CPP_AMQBUILD_SHARED] [-DAMQP-CPP_LINUX_TCP] cmake --build .. --target install ``` Option|Default|Meaning ------|-------|------- -BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. -LINUX_TCP|OFF|Should the Linux-only TCP module be built? +AMQP-CPP_BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. +AMQP-CPP_LINUX_TCP|OFF|Should the Linux-only TCP module be built? ## Make Installing the library is as easy From 4a02f24a24394c2c576736015eb664d3eddf2bf8 Mon Sep 17 00:00:00 2001 From: Steven Geddis Date: Thu, 22 Feb 2018 09:33:01 +0100 Subject: [PATCH 104/168] hunterize --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bcb914..b31f800 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ # ON: Build posix handler implementation # OFF: Don't build posix handler implementation -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.2 FATAL_ERROR) # project name project(amqpcpp) @@ -87,13 +87,13 @@ endif() if(AMQP-CPP_BUILD_SHARED) # copy shared lib and its static counter part install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION lib + ARCHIVE DESTINATION lib EXPORT ${PROJECT_NAME}Config LIBRARY DESTINATION lib RUNTIME DESTINATION lib ) else() # copy static lib - install(TARGETS ${PROJECT_NAME} + install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Config ARCHIVE DESTINATION lib ) endif() @@ -102,3 +102,6 @@ endif() 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) From e0feb17ecc3de878e2e69fefa909cc436155936a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 27 Feb 2018 05:08:21 +0100 Subject: [PATCH 105/168] renamed deferredconsumerbase into deferredreceiver, because it is not only a base class for the consumer, but also for other receiving operations: get requests and in the future also for returned messages --- include/amqpcpp/channelimpl.h | 20 +-- include/amqpcpp/deferredconsumer.h | 8 +- include/amqpcpp/deferredconsumerbase.h | 166 ------------------------- include/amqpcpp/deferredget.h | 8 +- include/amqpcpp/message.h | 6 +- src/basicgetokframe.h | 11 +- src/basicheaderframe.h | 14 ++- src/bodyframe.h | 16 ++- src/channelimpl.cpp | 14 +-- src/deferredconsumerbase.cpp | 142 --------------------- 10 files changed, 56 insertions(+), 349 deletions(-) delete mode 100644 include/amqpcpp/deferredconsumerbase.h delete mode 100644 src/deferredconsumerbase.cpp diff --git a/include/amqpcpp/channelimpl.h b/include/amqpcpp/channelimpl.h index 3d90266..c5b788b 100644 --- a/include/amqpcpp/channelimpl.h +++ b/include/amqpcpp/channelimpl.h @@ -34,7 +34,7 @@ namespace AMQP { /** * Forward declarations */ -class DeferredConsumerBase; +class DeferredReceiver; class BasicDeliverFrame; class DeferredConsumer; class BasicGetOKFrame; @@ -75,9 +75,9 @@ private: /** * Handlers for all consumers that are active - * @var std::map + * @var std::map */ - std::map> _consumers; + std::map> _consumers; /** * Pointer to the oldest deferred result (the first one that is going @@ -129,10 +129,10 @@ private: bool _synchronous = false; /** - * The current consumer receiving a message - * @var std::shared_ptr + * The current object that is busy receiving a message + * @var std::shared_ptr */ - std::shared_ptr _consumer; + std::shared_ptr _receiver; /** * Attach the connection @@ -664,13 +664,13 @@ public: * @param consumer The consumer handler * @param active Is this the new active consumer */ - void install(std::string consumertag, const std::shared_ptr &consumer, bool active = false) + void install(std::string consumertag, const std::shared_ptr &consumer, bool active = false) { // install the consumer handler _consumers[consumertag] = consumer; // should we become the current consumer? - if (active) _consumer = consumer; + if (active) _receiver = consumer; } /** @@ -691,11 +691,11 @@ public: void process(BasicDeliverFrame &frame); /** - * Retrieve the current consumer handler + * Retrieve the current object that is receiving a message * * @return The handler responsible for the current message */ - DeferredConsumerBase *consumer(); + DeferredReceiver *receiver(); /** * Mark the current consumer as done diff --git a/include/amqpcpp/deferredconsumer.h b/include/amqpcpp/deferredconsumer.h index d7d7bf8..bc260d5 100644 --- a/include/amqpcpp/deferredconsumer.h +++ b/include/amqpcpp/deferredconsumer.h @@ -3,7 +3,7 @@ * * Deferred callback for consumers * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -14,7 +14,7 @@ /** * Dependencies */ -#include "deferredconsumerbase.h" +#include "deferredreceiver.h" /** * Set up namespace @@ -24,7 +24,7 @@ namespace AMQP { /** * We extend from the default deferred and add extra functionality */ -class DeferredConsumer : public DeferredConsumerBase +class DeferredConsumer : public DeferredReceiver { private: /** @@ -68,7 +68,7 @@ public: * @param failed are we already failed? */ DeferredConsumer(ChannelImpl *channel, bool failed = false) : - DeferredConsumerBase(failed, channel) {} + DeferredReceiver(failed, channel) {} public: /** diff --git a/include/amqpcpp/deferredconsumerbase.h b/include/amqpcpp/deferredconsumerbase.h deleted file mode 100644 index 5b3bbdb..0000000 --- a/include/amqpcpp/deferredconsumerbase.h +++ /dev/null @@ -1,166 +0,0 @@ -/** - * deferredconsumerbase.h - * - * Base class for the deferred consumer and the - * deferred get. - * - * @copyright 2016 - 2017 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 DeferredConsumerBase : - public Deferred, - public std::enable_shared_from_this -{ -private: - /** - * Size of the body of the current message - * @var uint64_t - */ - uint64_t _bodySize = 0; - - /** - * Process a delivery frame - * - * @param frame The frame to process - */ - void process(BasicDeliverFrame &frame); - - /** - * Process a delivery frame from a get request - * - * @param frame The frame to process - */ - void process(BasicGetOKFrame &frame); - - /** - * 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); - - /** - * Indicate that a message was done - */ - void complete(); - - /** - * Announce that a message has been received - * @param message The message to announce - * @param deliveryTag The delivery tag (for ack()ing) - * @param redelivered Is this a redelivered message - */ - virtual void announce(const Message &message, uint64_t deliveryTag, bool redelivered) const = 0; - - /** - * Frames may be processed - */ - friend class ChannelImpl; - friend class BasicDeliverFrame; - friend class BasicGetOKFrame; - friend class BasicHeaderFrame; - friend class BodyFrame; -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; - - /** - * The channel to which the consumer is linked - * @var ChannelImpl - */ - ChannelImpl *_channel; - - /** - * Callback for new message - * @var BeginCallback - */ - BeginCallback _beginCallback; - - /** - * Callback for incoming headers - * @var HeaderCallback - */ - HeaderCallback _headerCallback; - - /** - * Callback for when a chunk of data comes in - * @var DataCallback - */ - DataCallback _dataCallback; - - /** - * Callback for incoming messages - * @var MessageCallback - */ - MessageCallback _messageCallback; - - /** - * Callback for when a message was complete finished - * @var CompleteCallback - */ - CompleteCallback _completeCallback; - - /** - * The message that we are currently receiving - * @var stack_ptr - */ - stack_ptr _message; - - /** - * Constructor - * - * @param failed Have we already failed? - * @param channel The channel we are consuming on - */ - DeferredConsumerBase(bool failed, ChannelImpl *channel) : Deferred(failed), _channel(channel) {} -public: -}; - -/** - * End namespace - */ -} diff --git a/include/amqpcpp/deferredget.h b/include/amqpcpp/deferredget.h index 4270d34..71fadbc 100644 --- a/include/amqpcpp/deferredget.h +++ b/include/amqpcpp/deferredget.h @@ -2,7 +2,7 @@ * DeferredGet.h * * @author Emiel Bruijntjes - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -13,7 +13,7 @@ /** * Dependencies */ -#include "deferredconsumerbase.h" +#include "deferredreceiver.h" /** * Set up namespace @@ -27,7 +27,7 @@ namespace AMQP { * it grabs a self-pointer when the callback is running, otherwise the onFinalize() * is called before the actual message is consumed. */ -class DeferredGet : public DeferredConsumerBase +class DeferredGet : public DeferredReceiver { private: /** @@ -84,7 +84,7 @@ public: * @param failed are we already failed? */ DeferredGet(ChannelImpl *channel, bool failed = false) : - DeferredConsumerBase(failed, channel) {} + DeferredReceiver(failed, channel) {} public: /** diff --git a/include/amqpcpp/message.h b/include/amqpcpp/message.h index 5b1bb02..7c8f59e 100644 --- a/include/amqpcpp/message.h +++ b/include/amqpcpp/message.h @@ -7,7 +7,7 @@ * Message objects can not be constructed by end users, they are only constructed * by the AMQP library, and passed to user callbacks. * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -31,7 +31,7 @@ namespace AMQP { /** * Forward declarations */ -class DeferredConsumerBase; +class DeferredReceiver; /** * Class definition @@ -61,7 +61,7 @@ protected: /** * We are an open book to the consumer handler */ - friend class DeferredConsumerBase; + friend class DeferredReceiver; /** * Set the body size diff --git a/src/basicgetokframe.h b/src/basicgetokframe.h index 0de5ea9..e90ae5e 100644 --- a/src/basicgetokframe.h +++ b/src/basicgetokframe.h @@ -1,7 +1,7 @@ /** * Class describing a basic get ok frame * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -173,11 +173,14 @@ public: // report success for the get operation channel->reportSuccess(messageCount(), deliveryTag(), redelivered()); - // check if we have a valid consumer - if (!channel->consumer()) return false; + // get the current receiver object + auto *receiver = channel->receiver(); + + // check if we have a valid receiver + if (receiver == nullptr) return false; // pass on to consumer - channel->consumer()->process(*this); + receiver->process(*this); // done return true; diff --git a/src/basicheaderframe.h b/src/basicheaderframe.h index c6ec1c4..bc60a17 100644 --- a/src/basicheaderframe.h +++ b/src/basicheaderframe.h @@ -1,7 +1,7 @@ /** * Class describing an AMQP basic header frame * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -16,7 +16,7 @@ #include "amqpcpp/metadata.h" #include "amqpcpp/envelope.h" #include "amqpcpp/connectionimpl.h" -#include "amqpcpp/deferredconsumerbase.h" +#include "amqpcpp/deferredreceiver.h" /** * Set up namespace @@ -134,12 +134,18 @@ public: { // 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 (!channel || !channel->consumer()) return false; + if (receiver == nullptr) return false; // the channel can process the frame - channel->consumer()->process(*this); + receiver->process(*this); // done return true; diff --git a/src/bodyframe.h b/src/bodyframe.h index ae1621d..5dc4920 100644 --- a/src/bodyframe.h +++ b/src/bodyframe.h @@ -1,7 +1,7 @@ /** * Class describing an AMQP Body Frame * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -14,7 +14,7 @@ */ #include "extframe.h" #include "amqpcpp/connectionimpl.h" -#include "amqpcpp/deferredconsumerbase.h" +#include "amqpcpp/deferredreceiver.h" /** * Set up namespace @@ -105,12 +105,18 @@ public: { // we need the appropriate channel auto channel = connection->channel(this->channel()); + + // we must have a channel object + if (channel == nullptr) return false; + + // get the object that is receiving the messages + auto *receiver = channel->receiver(); - // check if we have a valid channel and consumer - if (!channel || !channel->consumer()) return false; + // check if we have a valid receiver + if (receiver == nullptr) return false; // the consumer may process the frame - channel->consumer()->process(*this); + receiver->process(*this); // done return true; diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index a498864..a5c17c6 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -828,20 +828,20 @@ void ChannelImpl::process(BasicDeliverFrame &frame) // we are going to be receiving a message, store // the handler for the incoming message - _consumer = iter->second; + _receiver = iter->second; // let the consumer process the frame - _consumer->process(frame); + _receiver->process(frame); } /** - * Retrieve the current consumer handler + * Retrieve the current receiver handler * * @return The handler responsible for the current message */ -DeferredConsumerBase *ChannelImpl::consumer() +DeferredReceiver *ChannelImpl::receiver() { - return _consumer.get(); + return _receiver.get(); } /** @@ -849,8 +849,8 @@ DeferredConsumerBase *ChannelImpl::consumer() */ void ChannelImpl::complete() { - // no more consumer - _consumer.reset(); + // no more receiver + _receiver.reset(); } /** diff --git a/src/deferredconsumerbase.cpp b/src/deferredconsumerbase.cpp deleted file mode 100644 index 09ed0e4..0000000 --- a/src/deferredconsumerbase.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/** - * deferredconsumerbase.cpp - * - * Base class for the deferred consumer and the - * deferred get. - * - * @copyright 2016 - 2017 Copernica B.V. - */ - -/** - * Dependencies - */ -#include "amqpcpp/deferredconsumerbase.h" -#include "basicdeliverframe.h" -#include "basicgetokframe.h" -#include "basicheaderframe.h" -#include "bodyframe.h" - -/** - * Start namespace - */ -namespace AMQP { - -/** - * Process a delivery frame - * - * @param frame The frame to process - */ -void DeferredConsumerBase::process(BasicDeliverFrame &frame) -{ - // retrieve the delivery tag and whether we were redelivered - _deliveryTag = frame.deliveryTag(); - _redelivered = frame.redelivered(); - - // anybody interested in the new message? - if (_beginCallback) _beginCallback(); - - // do we have anybody interested in messages? - if (_messageCallback) _message.construct(frame.exchange(), frame.routingKey()); -} - -/** - * Process a delivery frame from a get request - * - * @param frame The frame to process - */ -void DeferredConsumerBase::process(BasicGetOKFrame &frame) -{ - // retrieve the delivery tag and whether we were redelivered - _deliveryTag = frame.deliveryTag(); - _redelivered = frame.redelivered(); - - // anybody interested in the new message? - if (_beginCallback) _beginCallback(); - - // do we have anybody interested in messages? - if (_messageCallback) _message.construct(frame.exchange(), frame.routingKey()); -} - -/** - * Process the message headers - * - * @param frame The frame to process - */ -void DeferredConsumerBase::process(BasicHeaderFrame &frame) -{ - // store the body size - _bodySize = frame.bodySize(); - - // do we have a message? - if (_message) - { - // store the body size and metadata - _message->setBodySize(_bodySize); - _message->set(frame.metaData()); - } - - // anybody interested in the headers? - if (_headerCallback) _headerCallback(frame.metaData()); - - // no body data expected? then we are now complete - if (!_bodySize) complete(); -} - -/** - * Process the message data - * - * @param frame The frame to process - */ -void DeferredConsumerBase::process(BodyFrame &frame) -{ - // make sure we stay in scope - auto self = shared_from_this(); - - // update the bytes still to receive - _bodySize -= frame.payloadSize(); - - // anybody interested in the data? - if (_dataCallback) _dataCallback(frame.payload(), frame.payloadSize()); - - // do we have a message? then append the data - if (_message) _message->append(frame.payload(), frame.payloadSize()); - - // if all bytes were received we are now complete - if (!_bodySize) complete(); -} - -/** - * Indicate that a message was done - */ -void DeferredConsumerBase::complete() -{ - // make sure we stay in scope - auto self = shared_from_this(); - - // also monitor the channel - Monitor monitor{ _channel }; - - // do we have a message? - if (_message) - { - // announce the message - announce(*_message, _deliveryTag, _redelivered); - - // and destroy it - _message.reset(); - } - - // do we have to inform anyone about completion? - if (_completeCallback) _completeCallback(_deliveryTag, _redelivered); - - // do we still have a valid channel - if (!monitor.valid()) return; - - // we are now done executing - _channel->complete(); -} - -/** - * End namespace - */ -} From 3ccc6af475fc1188ad3610b5df20afcf55491350 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 27 Feb 2018 05:09:03 +0100 Subject: [PATCH 106/168] added deferredreceiver files (forgotter in prev commit) --- include/amqpcpp/deferredreceiver.h | 166 +++++++++++++++++++++++++++++ src/deferredreceiver.cpp | 141 ++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 include/amqpcpp/deferredreceiver.h create mode 100644 src/deferredreceiver.cpp diff --git a/include/amqpcpp/deferredreceiver.h b/include/amqpcpp/deferredreceiver.h new file mode 100644 index 0000000..a315247 --- /dev/null +++ b/include/amqpcpp/deferredreceiver.h @@ -0,0 +1,166 @@ +/** + * 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, + public std::enable_shared_from_this +{ +private: + /** + * Size of the body of the current message + * @var uint64_t + */ + uint64_t _bodySize = 0; + + /** + * Process a delivery frame + * + * @param frame The frame to process + */ + void process(BasicDeliverFrame &frame); + + /** + * Process a delivery frame from a get request + * + * @param frame The frame to process + */ + void process(BasicGetOKFrame &frame); + + /** + * 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); + + /** + * Indicate that a message was done + */ + void complete(); + + /** + * Announce that a message has been received + * @param message The message to announce + * @param deliveryTag The delivery tag (for ack()ing) + * @param redelivered Is this a redelivered message + */ + virtual void announce(const Message &message, uint64_t deliveryTag, bool redelivered) const = 0; + + /** + * Frames may be processed + */ + friend class ChannelImpl; + friend class BasicDeliverFrame; + friend class BasicGetOKFrame; + friend class BasicHeaderFrame; + friend class BodyFrame; +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; + + /** + * The channel to which the consumer is linked + * @var ChannelImpl + */ + ChannelImpl *_channel; + + /** + * Callback for new message + * @var BeginCallback + */ + BeginCallback _beginCallback; + + /** + * Callback for incoming headers + * @var HeaderCallback + */ + HeaderCallback _headerCallback; + + /** + * Callback for when a chunk of data comes in + * @var DataCallback + */ + DataCallback _dataCallback; + + /** + * Callback for incoming messages + * @var MessageCallback + */ + MessageCallback _messageCallback; + + /** + * Callback for when a message was complete finished + * @var CompleteCallback + */ + CompleteCallback _completeCallback; + + /** + * The message that we are currently receiving + * @var stack_ptr + */ + stack_ptr _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: +}; + +/** + * End namespace + */ +} diff --git a/src/deferredreceiver.cpp b/src/deferredreceiver.cpp new file mode 100644 index 0000000..22cedc5 --- /dev/null +++ b/src/deferredreceiver.cpp @@ -0,0 +1,141 @@ +/** + * DeferredReceiver.cpp + * + * Implementation file for the DeferredReceiver class + * + * @copyright 2016 - 2018 Copernica B.V. + */ + +/** + * Dependencies + */ +#include "amqpcpp/deferredreceiver.h" +#include "basicdeliverframe.h" +#include "basicgetokframe.h" +#include "basicheaderframe.h" +#include "bodyframe.h" + +/** + * Start namespace + */ +namespace AMQP { + +/** + * Process a delivery frame + * + * @param frame The frame to process + */ +void DeferredReceiver::process(BasicDeliverFrame &frame) +{ + // retrieve the delivery tag and whether we were redelivered + _deliveryTag = frame.deliveryTag(); + _redelivered = frame.redelivered(); + + // anybody interested in the new message? + if (_beginCallback) _beginCallback(); + + // do we have anybody interested in messages? + if (_messageCallback) _message.construct(frame.exchange(), frame.routingKey()); +} + +/** + * Process a delivery frame from a get request + * + * @param frame The frame to process + */ +void DeferredReceiver::process(BasicGetOKFrame &frame) +{ + // retrieve the delivery tag and whether we were redelivered + _deliveryTag = frame.deliveryTag(); + _redelivered = frame.redelivered(); + + // anybody interested in the new message? + if (_beginCallback) _beginCallback(); + + // do we have anybody interested in messages? + if (_messageCallback) _message.construct(frame.exchange(), frame.routingKey()); +} + +/** + * Process the message headers + * + * @param frame The frame to process + */ +void DeferredReceiver::process(BasicHeaderFrame &frame) +{ + // store the body size + _bodySize = frame.bodySize(); + + // do we have a message? + if (_message) + { + // store the body size and metadata + _message->setBodySize(_bodySize); + _message->set(frame.metaData()); + } + + // anybody interested in the headers? + if (_headerCallback) _headerCallback(frame.metaData()); + + // no body data expected? then we are now complete + if (!_bodySize) complete(); +} + +/** + * Process the message data + * + * @param frame The frame to process + */ +void DeferredReceiver::process(BodyFrame &frame) +{ + // make sure we stay in scope + auto self = shared_from_this(); + + // update the bytes still to receive + _bodySize -= frame.payloadSize(); + + // anybody interested in the data? + if (_dataCallback) _dataCallback(frame.payload(), frame.payloadSize()); + + // do we have a message? then append the data + if (_message) _message->append(frame.payload(), frame.payloadSize()); + + // if all bytes were received we are now complete + if (!_bodySize) complete(); +} + +/** + * Indicate that a message was done + */ +void DeferredReceiver::complete() +{ + // make sure we stay in scope + auto self = shared_from_this(); + + // also monitor the channel + Monitor monitor{ _channel }; + + // do we have a message? + if (_message) + { + // announce the message + announce(*_message, _deliveryTag, _redelivered); + + // and destroy it + _message.reset(); + } + + // do we have to inform anyone about completion? + if (_completeCallback) _completeCallback(_deliveryTag, _redelivered); + + // do we still have a valid channel + if (!monitor.valid()) return; + + // we are now done executing + _channel->complete(); +} + +/** + * End namespace + */ +} From 520fe40201d73aac8f3dded0550bd03a76c7ead4 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 17:34:27 +0100 Subject: [PATCH 107/168] refactored handling of incoming messages from consume and get operations, to prepare for future handling of returned messages and publisher confirms. this also implies a small change to the api: the begin-callback when a message is received now also gets the original exchange and routing key (which could be useful) --- include/amqpcpp/callbacks.h | 2 +- include/amqpcpp/channelimpl.h | 39 +++++++++--------- include/amqpcpp/deferredconsumer.h | 23 ++++++++--- include/amqpcpp/deferredget.h | 15 ++++--- include/amqpcpp/deferredreceiver.h | 42 ++++++++----------- src/basicdeliverframe.h | 13 ++++-- src/basicgetokframe.h | 8 ++-- src/channelimpl.cpp | 43 ++++--------------- src/consumedmessage.h | 2 +- src/deferredconsumer.cpp | 33 +++++++++------ src/deferredget.cpp | 33 +++++---------- src/deferredreceiver.cpp | 66 +++++++++--------------------- 12 files changed, 137 insertions(+), 182 deletions(-) diff --git a/include/amqpcpp/callbacks.h b/include/amqpcpp/callbacks.h index a7c85b9..67e3757 100644 --- a/include/amqpcpp/callbacks.h +++ b/include/amqpcpp/callbacks.h @@ -38,7 +38,7 @@ using SuccessCallback = std::function; using ErrorCallback = std::function; using FinalizeCallback = std::function; using EmptyCallback = std::function; -using BeginCallback = std::function; +using BeginCallback = std::function; using HeaderCallback = std::function; using DataCallback = std::function; using MessageCallback = std::function; diff --git a/include/amqpcpp/channelimpl.h b/include/amqpcpp/channelimpl.h index c5b788b..3f1577e 100644 --- a/include/amqpcpp/channelimpl.h +++ b/include/amqpcpp/channelimpl.h @@ -75,9 +75,9 @@ private: /** * Handlers for all consumers that are active - * @var std::map + * @var std::map */ - std::map> _consumers; + std::map> _consumers; /** * Pointer to the oldest deferred result (the first one that is going @@ -130,7 +130,7 @@ private: /** * The current object that is busy receiving a message - * @var std::shared_ptr + * @var std::shared_ptr */ std::shared_ptr _receiver; @@ -659,18 +659,23 @@ public: /** * Install a consumer - * * @param consumertag The consumer tag - * @param consumer The consumer handler - * @param active Is this the new active consumer + * @param consumer The consumer object */ - void install(std::string consumertag, const std::shared_ptr &consumer, bool active = false) + void install(const std::string &consumertag, const std::shared_ptr &consumer) { // install the consumer handler _consumers[consumertag] = consumer; + } - // should we become the current consumer? - if (active) _receiver = consumer; + /** + * Install the current consumer + * @param receiver The receiver object + */ + void install(const std::shared_ptr &receiver) + { + // store object as current receiver + _receiver = receiver; } /** @@ -684,23 +689,17 @@ public: } /** - * Process incoming delivery - * - * @param frame The frame to process + * Fetch the receiver for a specific consumer tag + * @param consumertag the consumer tag + * @return the receiver object */ - void process(BasicDeliverFrame &frame); + 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(); - - /** - * Mark the current consumer as done - */ - void complete(); + DeferredReceiver *receiver() const { return _receiver.get(); } /** * The channel class is its friend, thus can it instantiate this object diff --git a/include/amqpcpp/deferredconsumer.h b/include/amqpcpp/deferredconsumer.h index bc260d5..732980f 100644 --- a/include/amqpcpp/deferredconsumer.h +++ b/include/amqpcpp/deferredconsumer.h @@ -20,11 +20,16 @@ * Set up namespace */ namespace AMQP { + +/** + * Forward declararions + */ +class BasicDeliverFrame; /** * We extend from the default deferred and add extra functionality */ -class DeferredConsumer : public DeferredReceiver +class DeferredConsumer : public DeferredReceiver, public std::enable_shared_from_this { private: /** @@ -33,6 +38,13 @@ private: */ ConsumeCallback _consumeCallback; + /** + * 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 @@ -41,12 +53,10 @@ private: virtual const std::shared_ptr &reportSuccess(const std::string &name) override; /** - * Announce that a message has been received - * @param message The message to announce - * @param deliveryTag The delivery tag (for ack()ing) - * @param redelivered Is this a redelivered message + * Get reference to self to prevent that object falls out of scope + * @return std::shared_ptr */ - virtual void announce(const Message &message, uint64_t deliveryTag, bool redelivered) const override; + virtual std::shared_ptr lock() override { return shared_from_this(); } /** * The channel implementation may call our @@ -54,6 +64,7 @@ private: */ friend class ChannelImpl; friend class ConsumedMessage; + friend class BasicDeliverFrame; public: /** diff --git a/include/amqpcpp/deferredget.h b/include/amqpcpp/deferredget.h index 71fadbc..4997e93 100644 --- a/include/amqpcpp/deferredget.h +++ b/include/amqpcpp/deferredget.h @@ -27,7 +27,7 @@ namespace AMQP { * it grabs a self-pointer when the callback is running, otherwise the onFinalize() * is called before the actual message is consumed. */ -class DeferredGet : public DeferredReceiver +class DeferredGet : public DeferredReceiver, public std::enable_shared_from_this { private: /** @@ -57,12 +57,15 @@ private: virtual const std::shared_ptr &reportSuccess() const override; /** - * Announce that a message has been received - * @param message The message to announce - * @param deliveryTag The delivery tag (for ack()ing) - * @param redelivered Is this a redelivered message + * Get reference to self to prevent that object falls out of scope + * @return std::shared_ptr */ - virtual void announce(const Message &message, uint64_t deliveryTag, bool redelivered) const override; + virtual std::shared_ptr 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 diff --git a/include/amqpcpp/deferredreceiver.h b/include/amqpcpp/deferredreceiver.h index a315247..3943dc2 100644 --- a/include/amqpcpp/deferredreceiver.h +++ b/include/amqpcpp/deferredreceiver.h @@ -35,9 +35,7 @@ class BodyFrame; /** * Base class for deferred consumers */ -class DeferredReceiver : - public Deferred, - public std::enable_shared_from_this +class DeferredReceiver : public Deferred { private: /** @@ -46,20 +44,27 @@ private: */ uint64_t _bodySize = 0; + +protected: /** - * Process a delivery frame - * - * @param frame The frame to process + * 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 */ - void process(BasicDeliverFrame &frame); + 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 lock() = 0; /** - * Process a delivery frame from a get request - * - * @param frame The frame to process + * Indicate that a message was done */ - void process(BasicGetOKFrame &frame); + virtual void complete(); +private: /** * Process the message headers * @@ -74,27 +79,14 @@ private: */ void process(BodyFrame &frame); - /** - * Indicate that a message was done - */ - void complete(); - - /** - * Announce that a message has been received - * @param message The message to announce - * @param deliveryTag The delivery tag (for ack()ing) - * @param redelivered Is this a redelivered message - */ - virtual void announce(const Message &message, uint64_t deliveryTag, bool redelivered) const = 0; - /** * Frames may be processed */ friend class ChannelImpl; - friend class BasicDeliverFrame; friend class BasicGetOKFrame; friend class BasicHeaderFrame; friend class BodyFrame; + protected: /** * The delivery tag for the current message diff --git a/src/basicdeliverframe.h b/src/basicdeliverframe.h index 4d4c408..07dad11 100644 --- a/src/basicdeliverframe.h +++ b/src/basicdeliverframe.h @@ -1,7 +1,7 @@ /** * Class describing a basic deliver frame * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -16,6 +16,7 @@ #include "amqpcpp/stringfield.h" #include "amqpcpp/booleanset.h" #include "amqpcpp/connectionimpl.h" +#include "amqpcpp/deferredconsumer.h" /** * Set up namespace @@ -193,8 +194,14 @@ public: // channel does not exist if (!channel) return false; - // construct the message - channel->process(*this); + // 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; diff --git a/src/basicgetokframe.h b/src/basicgetokframe.h index e90ae5e..97bc7af 100644 --- a/src/basicgetokframe.h +++ b/src/basicgetokframe.h @@ -170,8 +170,8 @@ public: // channel does not exist if (!channel) return false; - // report success for the get operation - channel->reportSuccess(messageCount(), deliveryTag(), redelivered()); + // 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(); @@ -179,8 +179,8 @@ public: // check if we have a valid receiver if (receiver == nullptr) return false; - // pass on to consumer - receiver->process(*this); + // initialize the receiver for the upcoming message + receiver->initialize(_exchange, _routingKey); // done return true; diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index a5c17c6..95ae8f8 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -6,7 +6,6 @@ * @copyright 2014 - 2018 Copernica BV */ #include "includes.h" -#include "basicdeliverframe.h" #include "basicgetokframe.h" #include "basicreturnframe.h" #include "consumedmessage.h" @@ -816,41 +815,17 @@ void ChannelImpl::reportError(const char *message, bool notifyhandler) } /** - * Process incoming delivery - * - * @param frame The frame to process + * Get the current receiver for a given consumer tag + * @param consumertag the consumer frame + * @return DeferredConsumer */ -void ChannelImpl::process(BasicDeliverFrame &frame) +DeferredConsumer *ChannelImpl::consumer(const std::string &consumertag) const { - // find the consumer for this frame - auto iter = _consumers.find(frame.consumerTag()); - if (iter == _consumers.end()) return; - - // we are going to be receiving a message, store - // the handler for the incoming message - _receiver = iter->second; - - // let the consumer process the frame - _receiver->process(frame); -} - -/** - * Retrieve the current receiver handler - * - * @return The handler responsible for the current message - */ -DeferredReceiver *ChannelImpl::receiver() -{ - return _receiver.get(); -} - -/** - * Mark the current consumer as done - */ -void ChannelImpl::complete() -{ - // no more receiver - _receiver.reset(); + // look in the map + auto iter = _consumers.find(consumertag); + + // return the result + return iter == _consumers.end() ? nullptr : iter->second.get(); } /** diff --git a/src/consumedmessage.h b/src/consumedmessage.h index 01f3bcc..bba72c4 100644 --- a/src/consumedmessage.h +++ b/src/consumedmessage.h @@ -1,7 +1,7 @@ /** * Base class for a message implementation * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** diff --git a/src/deferredconsumer.cpp b/src/deferredconsumer.cpp index 221d089..1c8b83d 100644 --- a/src/deferredconsumer.cpp +++ b/src/deferredconsumer.cpp @@ -3,15 +3,34 @@ * * Implementation file for the DeferredConsumer class * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ #include "includes.h" +#include "basicdeliverframe.h" /** * Namespace */ namespace AMQP { +/** + * Process a delivery frame + * + * @param frame The frame to process + */ +void DeferredConsumer::process(BasicDeliverFrame &frame) +{ + // this object will handle all future frames with header and body data + _channel->install(shared_from_this()); + + // retrieve the delivery tag and whether we were redelivered + _deliveryTag = frame.deliveryTag(); + _redelivered = frame.redelivered(); + + // initialize the object for the next message + initialize(frame.exchange(), frame.routingKey()); +} + /** * Report success for frames that report start consumer operations * @param name Consumer tag that is started @@ -32,18 +51,6 @@ const std::shared_ptr &DeferredConsumer::reportSuccess(const std::stri return _next; } -/** - * Announce that a message was received - * @param message The message to announce - * @param deliveryTag The delivery tag (for ack()ing) - * @param redelivered Is this a redelivered message - */ -void DeferredConsumer::announce(const Message &message, uint64_t deliveryTag, bool redelivered) const -{ - // simply execute the message callback - _messageCallback(message, deliveryTag, redelivered); -} - /** * End namespace */ diff --git a/src/deferredget.cpp b/src/deferredget.cpp index dbd838b..c1bb6ab 100644 --- a/src/deferredget.cpp +++ b/src/deferredget.cpp @@ -4,7 +4,7 @@ * Implementation of the DeferredGet call * * @author Emiel Bruijntjes - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -19,20 +19,19 @@ namespace AMQP { /** * 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? */ const std::shared_ptr &DeferredGet::reportSuccess(uint32_t messagecount, uint64_t deliveryTag, bool redelivered) { + // install this object as the handler for the upcoming header and body frames + _channel->install(shared_from_this()); + // store delivery tag and redelivery status _deliveryTag = deliveryTag; _redelivered = redelivered; - // install ourselves in the channel - _channel->install("", shared_from_this(), true); - // report the size (note that this is the size _minus_ the message that is retrieved // (and for which the callback will be called later), so it could be zero) if (_sizeCallback) _sizeCallback(messagecount); @@ -58,27 +57,15 @@ const std::shared_ptr &DeferredGet::reportSuccess() const } /** - * Announce that a message has been received - * @param message The message to announce - * @param deliveryTag The delivery tag (for ack()ing) - * @param redelivered Is this a redelivered message + * Extended implementation of the complete method that is called when a message was fully received */ -void DeferredGet::announce(const Message &message, uint64_t deliveryTag, bool redelivered) const +void DeferredGet::complete() { - // monitor the channel - Monitor monitor{ _channel }; - - // the channel is now synchronized + // the channel is now synchronized, delayed frames may now be sent _channel->onSynchronized(); - - // simply execute the message callback - _messageCallback(std::move(message), deliveryTag, redelivered); - - // check if the channel is still valid - if (!monitor.valid()) return; - - // stop consuming now - _channel->uninstall({}); + + // pass on to normal implementation + DeferredReceiver::complete(); } /** diff --git a/src/deferredreceiver.cpp b/src/deferredreceiver.cpp index 22cedc5..52e3766 100644 --- a/src/deferredreceiver.cpp +++ b/src/deferredreceiver.cpp @@ -21,39 +21,17 @@ namespace AMQP { /** - * Process a delivery frame - * - * @param frame The frame to process + * Initialize the object: we are going to receive a message, next frames will be header and data + * @param exchange + * @param routingkey */ -void DeferredReceiver::process(BasicDeliverFrame &frame) +void DeferredReceiver::initialize(const std::string &exchange, const std::string &routingkey) { - // retrieve the delivery tag and whether we were redelivered - _deliveryTag = frame.deliveryTag(); - _redelivered = frame.redelivered(); - // anybody interested in the new message? - if (_beginCallback) _beginCallback(); + if (_beginCallback) _beginCallback(exchange, routingkey); - // do we have anybody interested in messages? - if (_messageCallback) _message.construct(frame.exchange(), frame.routingKey()); -} - -/** - * Process a delivery frame from a get request - * - * @param frame The frame to process - */ -void DeferredReceiver::process(BasicGetOKFrame &frame) -{ - // retrieve the delivery tag and whether we were redelivered - _deliveryTag = frame.deliveryTag(); - _redelivered = frame.redelivered(); - - // anybody interested in the new message? - if (_beginCallback) _beginCallback(); - - // do we have anybody interested in messages? - if (_messageCallback) _message.construct(frame.exchange(), frame.routingKey()); + // do we have anybody interested in messages? in that case we construct the message + if (_messageCallback) _message.construct(exchange, routingkey); } /** @@ -63,6 +41,9 @@ void DeferredReceiver::process(BasicGetOKFrame &frame) */ void DeferredReceiver::process(BasicHeaderFrame &frame) { + // make sure we stay in scope + auto self = lock(); + // store the body size _bodySize = frame.bodySize(); @@ -78,7 +59,7 @@ void DeferredReceiver::process(BasicHeaderFrame &frame) if (_headerCallback) _headerCallback(frame.metaData()); // no body data expected? then we are now complete - if (!_bodySize) complete(); + if (_bodySize == 0) complete(); } /** @@ -89,7 +70,7 @@ void DeferredReceiver::process(BasicHeaderFrame &frame) void DeferredReceiver::process(BodyFrame &frame) { // make sure we stay in scope - auto self = shared_from_this(); + auto self = lock(); // update the bytes still to receive _bodySize -= frame.payloadSize(); @@ -101,7 +82,7 @@ void DeferredReceiver::process(BodyFrame &frame) if (_message) _message->append(frame.payload(), frame.payloadSize()); // if all bytes were received we are now complete - if (!_bodySize) complete(); + if (_bodySize == 0) complete(); } /** @@ -109,30 +90,23 @@ void DeferredReceiver::process(BodyFrame &frame) */ void DeferredReceiver::complete() { - // make sure we stay in scope - auto self = shared_from_this(); - // also monitor the channel - Monitor monitor{ _channel }; + Monitor monitor(_channel); // do we have a message? - if (_message) - { - // announce the message - announce(*_message, _deliveryTag, _redelivered); - - // and destroy it - _message.reset(); - } + if (_message) _messageCallback(*_message, _deliveryTag, _redelivered); // do we have to inform anyone about completion? if (_completeCallback) _completeCallback(_deliveryTag, _redelivered); + + // for the next iteration we want a new message + _message.reset(); // do we still have a valid channel if (!monitor.valid()) return; - // we are now done executing - _channel->complete(); + // we are now done executing, so the channel can forget the current receiving object + _channel->install(nullptr); } /** From ef76876d67c3aa92647a7c7a909cb0fc8cacabd1 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 18:07:18 +0100 Subject: [PATCH 108/168] refactored code to make room for a deferredpublisher class (which will also use the deferredreceiver base class) --- include/amqpcpp/deferredconsumer.h | 6 +- include/amqpcpp/deferredextreceiver.h | 85 +++++++++++++++++++++++++++ include/amqpcpp/deferredget.h | 6 +- include/amqpcpp/deferredreceiver.h | 33 ++++------- src/consumedmessage.h | 5 ++ src/deferredextreceiver.cpp | 64 ++++++++++++++++++++ src/deferredget.cpp | 2 +- src/deferredreceiver.cpp | 27 --------- 8 files changed, 171 insertions(+), 57 deletions(-) create mode 100644 include/amqpcpp/deferredextreceiver.h create mode 100644 src/deferredextreceiver.cpp diff --git a/include/amqpcpp/deferredconsumer.h b/include/amqpcpp/deferredconsumer.h index 732980f..bd5e576 100644 --- a/include/amqpcpp/deferredconsumer.h +++ b/include/amqpcpp/deferredconsumer.h @@ -14,7 +14,7 @@ /** * Dependencies */ -#include "deferredreceiver.h" +#include "deferredextreceiver.h" /** * Set up namespace @@ -29,7 +29,7 @@ class BasicDeliverFrame; /** * We extend from the default deferred and add extra functionality */ -class DeferredConsumer : public DeferredReceiver, public std::enable_shared_from_this +class DeferredConsumer : public DeferredExtReceiver, public std::enable_shared_from_this { private: /** @@ -79,7 +79,7 @@ public: * @param failed are we already failed? */ DeferredConsumer(ChannelImpl *channel, bool failed = false) : - DeferredReceiver(failed, channel) {} + DeferredExtReceiver(failed, channel) {} public: /** diff --git a/include/amqpcpp/deferredextreceiver.h b/include/amqpcpp/deferredextreceiver.h new file mode 100644 index 0000000..af15bfe --- /dev/null +++ b/include/amqpcpp/deferredextreceiver.h @@ -0,0 +1,85 @@ +/** + * 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 DeferredPublisher (which can also receive returned + * messages, but not as a result of an explicit request) + * + * @author Emiel Bruijntjes + * @copyright 2018 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; + + + /** + * 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 + */ +} + diff --git a/include/amqpcpp/deferredget.h b/include/amqpcpp/deferredget.h index 4997e93..4ea8f1f 100644 --- a/include/amqpcpp/deferredget.h +++ b/include/amqpcpp/deferredget.h @@ -13,7 +13,7 @@ /** * Dependencies */ -#include "deferredreceiver.h" +#include "deferredextreceiver.h" /** * Set up namespace @@ -27,7 +27,7 @@ namespace AMQP { * it grabs a self-pointer when the callback is running, otherwise the onFinalize() * is called before the actual message is consumed. */ -class DeferredGet : public DeferredReceiver, public std::enable_shared_from_this +class DeferredGet : public DeferredExtReceiver, public std::enable_shared_from_this { private: /** @@ -87,7 +87,7 @@ public: * @param failed are we already failed? */ DeferredGet(ChannelImpl *channel, bool failed = false) : - DeferredReceiver(failed, channel) {} + DeferredExtReceiver(failed, channel) {} public: /** diff --git a/include/amqpcpp/deferredreceiver.h b/include/amqpcpp/deferredreceiver.h index 3943dc2..e4d4562 100644 --- a/include/amqpcpp/deferredreceiver.h +++ b/include/amqpcpp/deferredreceiver.h @@ -51,18 +51,18 @@ protected: * @param exchange the exchange to which the message was published * @param routingkey the routing key that was used to publish the message */ - void initialize(const std::string &exchange, const std::string &routingkey); + 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 lock() = 0; - + /** * Indicate that a message was done */ - virtual void complete(); + virtual void complete() = 0; private: /** @@ -88,18 +88,6 @@ private: friend class BodyFrame; 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; - /** * The channel to which the consumer is linked * @var ChannelImpl @@ -124,12 +112,6 @@ protected: */ DataCallback _dataCallback; - /** - * Callback for incoming messages - * @var MessageCallback - */ - MessageCallback _messageCallback; - /** * Callback for when a message was complete finished * @var CompleteCallback @@ -144,12 +126,17 @@ protected: /** * Constructor - * * @param failed Have we already failed? * @param channel The channel we are consuming on */ - DeferredReceiver(bool failed, ChannelImpl *channel) : Deferred(failed), _channel(channel) {} + DeferredReceiver(bool failed, ChannelImpl *channel) : + Deferred(failed), _channel(channel) {} + public: + /** + * Destructor + */ + virtual ~DeferredReceiver() = default; }; /** diff --git a/src/consumedmessage.h b/src/consumedmessage.h index bba72c4..21f4a6c 100644 --- a/src/consumedmessage.h +++ b/src/consumedmessage.h @@ -4,6 +4,11 @@ * @copyright 2014 - 2018 Copernica BV */ +/** + * Dependencies + */ +#include "basicdeliverframe.h" + /** * Set up namespace */ diff --git a/src/deferredextreceiver.cpp b/src/deferredextreceiver.cpp new file mode 100644 index 0000000..ca05185 --- /dev/null +++ b/src/deferredextreceiver.cpp @@ -0,0 +1,64 @@ +/** + * DeferredExtReceiver.cpp + * + * Implementation file for the DeferredExtReceiver class + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Dependencies + */ +#include "amqpcpp/deferredextreceiver.h" +#include "amqpcpp/channelimpl.h" + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * 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 + */ +void DeferredExtReceiver::initialize(const std::string &exchange, const std::string &routingkey) +{ + // call base + DeferredExtReceiver::initialize(exchange, routingkey); + + // do we have anybody interested in messages? in that case we construct the message + if (_messageCallback) _message.construct(exchange, routingkey); +} + +/** + * Indicate that a message was done + */ +void DeferredExtReceiver::complete() +{ + // also monitor the channel + Monitor monitor(_channel); + + // do we have a message? + if (_message) _messageCallback(*_message, _deliveryTag, _redelivered); + + // do we have to inform anyone about completion? + if (_completeCallback) _completeCallback(_deliveryTag, _redelivered); + + // for the next iteration we want a new message + _message.reset(); + + // do we still have a valid channel + if (!monitor.valid()) return; + + // we are now done executing, so the channel can forget the current receiving object + _channel->install(nullptr); +} + +/** + * End of namespace + */ +} + + diff --git a/src/deferredget.cpp b/src/deferredget.cpp index c1bb6ab..84a2337 100644 --- a/src/deferredget.cpp +++ b/src/deferredget.cpp @@ -65,7 +65,7 @@ void DeferredGet::complete() _channel->onSynchronized(); // pass on to normal implementation - DeferredReceiver::complete(); + DeferredExtReceiver::complete(); } /** diff --git a/src/deferredreceiver.cpp b/src/deferredreceiver.cpp index 52e3766..251258a 100644 --- a/src/deferredreceiver.cpp +++ b/src/deferredreceiver.cpp @@ -29,9 +29,6 @@ void DeferredReceiver::initialize(const std::string &exchange, const std::string { // anybody interested in the new message? if (_beginCallback) _beginCallback(exchange, routingkey); - - // do we have anybody interested in messages? in that case we construct the message - if (_messageCallback) _message.construct(exchange, routingkey); } /** @@ -85,30 +82,6 @@ void DeferredReceiver::process(BodyFrame &frame) if (_bodySize == 0) complete(); } -/** - * Indicate that a message was done - */ -void DeferredReceiver::complete() -{ - // also monitor the channel - Monitor monitor(_channel); - - // do we have a message? - if (_message) _messageCallback(*_message, _deliveryTag, _redelivered); - - // do we have to inform anyone about completion? - if (_completeCallback) _completeCallback(_deliveryTag, _redelivered); - - // for the next iteration we want a new message - _message.reset(); - - // do we still have a valid channel - if (!monitor.valid()) return; - - // we are now done executing, so the channel can forget the current receiving object - _channel->install(nullptr); -} - /** * End namespace */ From f39df772d32b2a826317e26f19f7ee0b6e4ab127 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 21:12:50 +0100 Subject: [PATCH 109/168] breaking changes: channel.publish() now returns a DeferredConsumer object on which callbacks can be installed for handling returned messages, channel.get().onSize() has a different behavior: it now reports the message size (and no longer the queue size), channel.get().onCount() has been added: it reports the queue size (this used to be the onSize() method), channel.consume().onSize() method has been added to find out the size of the upcoming message --- include/amqpcpp/deferredpublisher.h | 179 ++++++++++++++++++++++++++++ src/deferredpublisher.cpp | 45 +++++++ 2 files changed, 224 insertions(+) create mode 100644 include/amqpcpp/deferredpublisher.h create mode 100644 src/deferredpublisher.cpp diff --git a/include/amqpcpp/deferredpublisher.h b/include/amqpcpp/deferredpublisher.h new file mode 100644 index 0000000..04f151c --- /dev/null +++ b/include/amqpcpp/deferredpublisher.h @@ -0,0 +1,179 @@ +/** + * DeferredPublisher.h + * + * Class that is returned when channel::publish() is called, and that + * can be used to install callback methods that define how returned + * messages should be handled. + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class DeferredPublisher : public DeferredReceiver +{ +private: + + +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) : + DeferredReceiver(failed, channel) {} + +public: + /** + * Register a function to be called when a full message is returned + * @param callback the callback to execute + */ + DeferredConsumer &onReceived(const ReturnCallback &callback) + { + // store callback + _returnCallback = callback; + + // allow chaining + return *this; + } + + /** + * Alias for onReceived() (see above) + * @param callback the callback to execute + */ + DeferredConsumer &onMessage(const ReturnCallback &callback) + { + // store callback + _returnCallback = callback; + + // allow chaining + return *this; + } + + /** + * Alias for onReceived() (see above) + * @param callback the callback to execute + */ + DeferredConsumer &onReturned(const ReturnCallback &callback) + { + // store callback + _returnCallback = 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 + */ + DeferredConsumer &onBegin(const BeginCallback &callback) + { + // store callback + _beginCallback = 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 + */ + DeferredConsumer &onHeaders(const HeaderCallback &callback) + { + // store callback + _headerCallback = 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 + */ + DeferredConsumer &onData(const DataCallback &callback) + { + // store callback + _dataCallback = 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 + */ + DeferredConsumer &onComplete(const CompleteCallback &callback) + { + // store callback + _completeCallback = callback; + + // allow chaining + return *this; + } +}; + +/** + * End of namespace + */ +} + diff --git a/src/deferredpublisher.cpp b/src/deferredpublisher.cpp new file mode 100644 index 0000000..61af52a --- /dev/null +++ b/src/deferredpublisher.cpp @@ -0,0 +1,45 @@ +/** + * DeferredPublisher.cpp + * + * Implementation file for the DeferredPublisher class + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ +#include "includes.h" + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Indicate that a message was done + */ +void DeferredPublisher::complete() +{ + // also monitor the channel + Monitor monitor(_channel); + + // do we have a message? + if (_message) _bounceCallback(*_message, 0, ""); + + // do we have to inform anyone about completion? + if (_completeCallback) _completeCallback(); + + // for the next iteration we want a new message + _message.reset(); + + // do we still have a valid channel + if (!monitor.valid()) return; + + // we are now done executing, so the channel can forget the current receiving object + _channel->install(nullptr); +} + +/** + * End of namespace + */ +} + + From 1f3500cee89fc98e64894c30ff3c7b2dbcc5292d Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 21:12:53 +0100 Subject: [PATCH 110/168] breaking changes: channel.publish() now returns a DeferredConsumer object on which callbacks can be installed for handling returned messages, channel.get().onSize() has a different behavior: it now reports the message size (and no longer the queue size), channel.get().onCount() has been added: it reports the queue size (this used to be the onSize() method), channel.consume().onSize() method has been added to find out the size of the upcoming message --- include/amqpcpp/callbacks.h | 66 +++++++---- include/amqpcpp/channel.h | 24 +++- include/amqpcpp/channelimpl.h | 13 ++- include/amqpcpp/deferredconsumer.h | 57 +++++++++- include/amqpcpp/deferredextreceiver.h | 6 + include/amqpcpp/deferredget.h | 157 +++++++++++++++++--------- include/amqpcpp/deferredpublisher.h | 112 +++++++++++++----- include/amqpcpp/deferredreceiver.h | 16 +-- src/channelimpl.cpp | 20 ++-- src/deferredextreceiver.cpp | 4 +- src/deferredget.cpp | 4 +- src/deferredpublisher.cpp | 2 +- src/deferredreceiver.cpp | 5 +- src/includes.h | 1 + 14 files changed, 345 insertions(+), 142 deletions(-) diff --git a/include/amqpcpp/callbacks.h b/include/amqpcpp/callbacks.h index 67e3757..a2d5ec4 100644 --- a/include/amqpcpp/callbacks.h +++ b/include/amqpcpp/callbacks.h @@ -3,7 +3,7 @@ * * Class storing deferred callbacks of different type. * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -29,25 +29,53 @@ class Message; class MetaData; /** - * All the callbacks that are supported - * - * When someone registers a callback function for certain events, it should - * match one of the following signatures. + * Generic callbacks that are used by many deferred objects */ -using SuccessCallback = std::function; -using ErrorCallback = std::function; -using FinalizeCallback = std::function; -using EmptyCallback = std::function; -using BeginCallback = std::function; -using HeaderCallback = std::function; -using DataCallback = std::function; -using MessageCallback = std::function; -using CompleteCallback = std::function; -using QueueCallback = std::function; -using DeleteCallback = std::function; -using SizeCallback = std::function; -using ConsumeCallback = std::function; -using CancelCallback = std::function; +using SuccessCallback = std::function; +using ErrorCallback = std::function; +using FinalizeCallback = std::function; + +/** + * Declaring and deleting a queue + */ +using QueueCallback = std::function; +using DeleteCallback = std::function; + +/** + * When retrieving the size of a queue in some way + */ +using EmptyCallback = std::function; +using SizeCallback = std::function; + +/** + * Starting and stopping a consumer + */ +using ConsumeCallback = std::function; +using CancelCallback = std::function; + +/** + * Receiving messages, either via consume(), get() or as returned messages + * The following methods receive the returned message in multiple parts + */ +using StartCallback = std::function; +using HeaderCallback = std::function; +using DataCallback = std::function; +using DeliveredCallback = std::function; + +/** + * 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; +using ReturnedCallback = std::function; + +/** + * 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; +using BounceCallback = std::function; /** * End namespace diff --git a/include/amqpcpp/channel.h b/include/amqpcpp/channel.h index c41e0aa..2be43cd 100644 --- a/include/amqpcpp/channel.h +++ b/include/amqpcpp/channel.h @@ -341,17 +341,31 @@ public: /** * Publish a message to an exchange - * + * + * This method returns a reference to a DeferredPublisher object. You can use this returned + * object to install callbacks that are called when an undeliverable message is returned, or + * to set the callback that is called when the server confirms that the message was received. + * + * To enable handling returned messages, or to enable publisher-confirms, you must not only + * set the callback, but also pass in appropriate flags to enable this feature. If you do not + * pass in these flags, your callbacks will not be called. If you are not at all interested + * in returned messages or publish-confirms, you can ignore the flag and the returned object. + * + * Watch out: the channel returns the same DeferredPublisher object for all calls to the + * publish() method. This means that the callbacks that you install for the first published + * message are also used for subsequent messages _and_ it means that if you install a different + * callback for a later publish operation, it overwrites your earlier callbacks + * * @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 */ - bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation->publish(exchange, routingKey, envelope); } - bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation->publish(exchange, routingKey, Envelope(message.data(), message.size())); } - bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation->publish(exchange, routingKey, Envelope(message, size)); } - bool publish(const std::string &exchange, const std::string &routingKey, const char *message) { return _implementation->publish(exchange, routingKey, Envelope(message, strlen(message))); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation->publish(exchange, routingKey, envelope); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation->publish(exchange, routingKey, Envelope(message.data(), message.size())); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation->publish(exchange, routingKey, Envelope(message, size)); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message) { return _implementation->publish(exchange, routingKey, Envelope(message, strlen(message))); } /** * Set the Quality of Service (QOS) for this channel diff --git a/include/amqpcpp/channelimpl.h b/include/amqpcpp/channelimpl.h index 3f1577e..41d5742 100644 --- a/include/amqpcpp/channelimpl.h +++ b/include/amqpcpp/channelimpl.h @@ -44,6 +44,7 @@ class DeferredDelete; class DeferredCancel; class DeferredQueue; class DeferredGet; +class DeferredPublisher; class Connection; class Envelope; class Table; @@ -73,6 +74,12 @@ private: */ ErrorCallback _errorCallback; + /** + * Handler that deals with incoming messages as a result of publish operations + * @var std::shared_ptr + */ + std::shared_ptr _publisher; + /** * Handlers for all consumers that are active * @var std::map @@ -396,16 +403,16 @@ public: * 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. However, the AMQP-CPP - * library does not yet report such returned messages. + * 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 + * @return DeferredPublisher */ - bool publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope); + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope); /** * Set the Quality of Service (QOS) of the entire connection diff --git a/include/amqpcpp/deferredconsumer.h b/include/amqpcpp/deferredconsumer.h index bd5e576..c8030e7 100644 --- a/include/amqpcpp/deferredconsumer.h +++ b/include/amqpcpp/deferredconsumer.h @@ -68,8 +68,7 @@ private: public: /** - * Protected constructor that can only be called - * from within the channel implementation + * 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, @@ -167,15 +166,46 @@ public: * @param callback The callback to invoke * @return Same object for chaining */ - DeferredConsumer &onBegin(const BeginCallback &callback) + DeferredConsumer &onBegin(const StartCallback &callback) { // store callback - _beginCallback = callback; + _startCallback = 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 + */ + DeferredConsumer &onStart(const StartCallback &callback) + { + // store callback + _startCallback = 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 + */ + DeferredConsumer &onSize(const SizeCallback &callback) + { + // store callback + _sizeCallback = callback; + + // allow chaining + return *this; + } + /** * Register the function that is called when message headers come in * @@ -219,10 +249,25 @@ public: * @param callback The callback to invoke * @return Same object for chaining */ - DeferredConsumer &onComplete(const CompleteCallback &callback) + DeferredConsumer &onComplete(const DeliveredCallback &callback) { // store callback - _completeCallback = callback; + _deliveredCallback = 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 + */ + DeferredConsumer &onDelivered(const DeliveredCallback &callback) + { + // store callback + _deliveredCallback = callback; // allow chaining return *this; diff --git a/include/amqpcpp/deferredextreceiver.h b/include/amqpcpp/deferredextreceiver.h index af15bfe..3d427e3 100644 --- a/include/amqpcpp/deferredextreceiver.h +++ b/include/amqpcpp/deferredextreceiver.h @@ -50,6 +50,12 @@ protected: */ MessageCallback _messageCallback; + /** + * Callback for when a message was complete finished + * @var DeliveredCallback + */ + DeliveredCallback _deliveredCallback; + /** * Initialize the object to send out a message diff --git a/include/amqpcpp/deferredget.h b/include/amqpcpp/deferredget.h index 4ea8f1f..78332be 100644 --- a/include/amqpcpp/deferredget.h +++ b/include/amqpcpp/deferredget.h @@ -40,7 +40,7 @@ private: * Callback with the number of messages still in the queue * @var SizeCallback */ - SizeCallback _sizeCallback; + SizeCallback _countCallback; /** * Report success for a get operation @@ -90,58 +90,6 @@ public: DeferredExtReceiver(failed, channel) {} public: - /** - * Register the function to be called when a new message is expected - * - * @param callback The callback to invoke - * @return Same object for chaining - */ - DeferredGet &onBegin(const BeginCallback &callback) - { - // store callback - _beginCallback = 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 - */ - DeferredGet &onHeaders(const HeaderCallback &callback) - { - // store callback - _headerCallback = 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 - */ - DeferredGet &onData(const DataCallback &callback) - { - // store callback - _dataCallback = callback; - - // allow chaining - return *this; - } - /** * 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 @@ -198,13 +146,95 @@ public: } /** - * Register a function to be called when size information is known + * Register a function to be called when queue size information is known * @param callback the callback to execute */ + DeferredGet &onCount(const SizeCallback &callback) + { + // store callback + _countCallback = 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 + */ + DeferredGet &onBegin(const StartCallback &callback) + { + // store callback + _startCallback = 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 + */ + DeferredGet &onStart(const StartCallback &callback) + { + // store callback + _startCallback = 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 + */ DeferredGet &onSize(const SizeCallback &callback) { // store callback _sizeCallback = 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 + */ + DeferredGet &onHeaders(const HeaderCallback &callback) + { + // store callback + _headerCallback = 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 + */ + DeferredGet &onData(const DataCallback &callback) + { + // store callback + _dataCallback = callback; // allow chaining return *this; @@ -216,10 +246,25 @@ public: * @param callback The callback to invoke * @return Same object for chaining */ - DeferredGet &onComplete(const CompleteCallback &callback) + DeferredGet &onComplete(const DeliveredCallback &callback) { // store callback - _completeCallback = callback; + _deliveredCallback = 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 + */ + DeferredGet &onDelivered(const DeliveredCallback &callback) + { + // store callback + _deliveredCallback = callback; // allow chaining return *this; diff --git a/include/amqpcpp/deferredpublisher.h b/include/amqpcpp/deferredpublisher.h index 04f151c..77c2465 100644 --- a/include/amqpcpp/deferredpublisher.h +++ b/include/amqpcpp/deferredpublisher.h @@ -18,14 +18,58 @@ * Begin of namespace */ namespace AMQP { + +/** + * Forward declarations + */ +class ChannelImpl; /** * Class definition */ -class DeferredPublisher : public DeferredReceiver +class DeferredPublisher : public DeferredReceiver, public std::enable_shared_from_this { 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; + + /** + * Get reference to self to prevent that object falls out of scope + * @return std::shared_ptr + */ + virtual std::shared_ptr 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; public: /** @@ -38,7 +82,7 @@ public: * @param channel the channel implementation * @param failed are we already failed? */ - DeferredConsumer(ChannelImpl *channel, bool failed = false) : + DeferredPublisher(ChannelImpl *channel, bool failed = false) : DeferredReceiver(failed, channel) {} public: @@ -46,10 +90,10 @@ public: * Register a function to be called when a full message is returned * @param callback the callback to execute */ - DeferredConsumer &onReceived(const ReturnCallback &callback) + DeferredPublisher &onReceived(const BounceCallback &callback) { // store callback - _returnCallback = callback; + _bounceCallback = callback; // allow chaining return *this; @@ -59,10 +103,10 @@ public: * Alias for onReceived() (see above) * @param callback the callback to execute */ - DeferredConsumer &onMessage(const ReturnCallback &callback) + DeferredPublisher &onMessage(const BounceCallback &callback) { // store callback - _returnCallback = callback; + _bounceCallback = callback; // allow chaining return *this; @@ -72,36 +116,27 @@ public: * Alias for onReceived() (see above) * @param callback the callback to execute */ - DeferredConsumer &onReturned(const ReturnCallback &callback) + DeferredPublisher &onReturned(const BounceCallback &callback) { // store callback - _returnCallback = callback; + _bounceCallback = 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. + * Alias for onReceived() (see above) + * @param callback the callback to execute */ + DeferredPublisher &onBounced(const BounceCallback &callback) + { + // store callback + _bounceCallback = callback; + + // allow chaining + return *this; + } /** * Register the function that is called when the start frame of a new @@ -110,7 +145,7 @@ public: * @param callback The callback to invoke * @return Same object for chaining */ - DeferredConsumer &onBegin(const BeginCallback &callback) + DeferredPublisher &onBegin(const ReturnCallback &callback) { // store callback _beginCallback = callback; @@ -119,13 +154,28 @@ public: 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 + */ + DeferredPublisher &onSize(const SizeCallback &callback) + { + // store callback + _sizeCallback = 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 */ - DeferredConsumer &onHeaders(const HeaderCallback &callback) + DeferredPublisher &onHeaders(const HeaderCallback &callback) { // store callback _headerCallback = callback; @@ -147,7 +197,7 @@ public: * @param callback The callback to invoke for chunks of message data * @return Same object for chaining */ - DeferredConsumer &onData(const DataCallback &callback) + DeferredPublisher &onData(const DataCallback &callback) { // store callback _dataCallback = callback; @@ -162,7 +212,7 @@ public: * @param callback The callback to invoke * @return Same object for chaining */ - DeferredConsumer &onComplete(const CompleteCallback &callback) + DeferredPublisher &onComplete(const ReturnedCallback &callback) { // store callback _completeCallback = callback; diff --git a/include/amqpcpp/deferredreceiver.h b/include/amqpcpp/deferredreceiver.h index e4d4562..caa489b 100644 --- a/include/amqpcpp/deferredreceiver.h +++ b/include/amqpcpp/deferredreceiver.h @@ -96,9 +96,15 @@ protected: /** * Callback for new message - * @var BeginCallback + * @var StartCallback */ - BeginCallback _beginCallback; + StartCallback _startCallback; + + /** + * Callback that is called when size of the message is known + * @var SizeCallback + */ + SizeCallback _sizeCallback; /** * Callback for incoming headers @@ -112,12 +118,6 @@ protected: */ DataCallback _dataCallback; - /** - * Callback for when a message was complete finished - * @var CompleteCallback - */ - CompleteCallback _completeCallback; - /** * The message that we are currently receiving * @var stack_ptr diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index 95ae8f8..fff8873 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -451,26 +451,30 @@ DeferredDelete &ChannelImpl::removeQueue(const std::string &name, int flags) * @param envelope the full envelope to send * @param message the message to send * @param size size of the message + * @return DeferredPublisher */ -bool ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) +DeferredPublisher &ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { // we are going to send out multiple frames, each one will trigger a call to the handler, // which in turn could destruct the channel object, we need to monitor that Monitor monitor(this); // @todo do not copy the entire buffer to individual frames + + // make sure we have a deferred object to return + if (!_publisher) _publisher.reset(new DeferredPublisher(this)); // send the publish frame - if (!send(BasicPublishFrame(_id, exchange, routingKey))) return false; + if (!send(BasicPublishFrame(_id, exchange, routingKey))) return *_publisher; // channel still valid? - if (!monitor.valid()) return false; + if (!monitor.valid()) return *_publisher; // send header - if (!send(BasicHeaderFrame(_id, envelope))) return false; + if (!send(BasicHeaderFrame(_id, envelope))) return *_publisher; // channel and connection still valid? - if (!monitor.valid() || !_connection) return false; + if (!monitor.valid() || !_connection) return *_publisher; // the max payload size is the max frame size minus the bytes for headers and trailer uint32_t maxpayload = _connection->maxPayload(); @@ -487,10 +491,10 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin uint64_t chunksize = std::min(static_cast(maxpayload), bytesleft); // send out a body frame - if (!send(BodyFrame(_id, data + bytessent, (uint32_t)chunksize))) return false; + if (!send(BodyFrame(_id, data + bytessent, (uint32_t)chunksize))) return *_publisher; // channel still valid? - if (!monitor.valid()) return false; + if (!monitor.valid()) return *_publisher; // update counters bytessent += chunksize; @@ -498,7 +502,7 @@ bool ChannelImpl::publish(const std::string &exchange, const std::string &routin } // done - return true; + return *_publisher; } /** diff --git a/src/deferredextreceiver.cpp b/src/deferredextreceiver.cpp index ca05185..0f2f047 100644 --- a/src/deferredextreceiver.cpp +++ b/src/deferredextreceiver.cpp @@ -26,7 +26,7 @@ namespace AMQP { void DeferredExtReceiver::initialize(const std::string &exchange, const std::string &routingkey) { // call base - DeferredExtReceiver::initialize(exchange, routingkey); + DeferredReceiver::initialize(exchange, routingkey); // do we have anybody interested in messages? in that case we construct the message if (_messageCallback) _message.construct(exchange, routingkey); @@ -44,7 +44,7 @@ void DeferredExtReceiver::complete() if (_message) _messageCallback(*_message, _deliveryTag, _redelivered); // do we have to inform anyone about completion? - if (_completeCallback) _completeCallback(_deliveryTag, _redelivered); + if (_deliveredCallback) _deliveredCallback(_deliveryTag, _redelivered); // for the next iteration we want a new message _message.reset(); diff --git a/src/deferredget.cpp b/src/deferredget.cpp index 84a2337..e8985f1 100644 --- a/src/deferredget.cpp +++ b/src/deferredget.cpp @@ -34,7 +34,7 @@ const std::shared_ptr &DeferredGet::reportSuccess(uint32_t messagecoun // report the size (note that this is the size _minus_ the message that is retrieved // (and for which the callback will be called later), so it could be zero) - if (_sizeCallback) _sizeCallback(messagecount); + if (_countCallback) _countCallback(messagecount); // return next handler return _next; @@ -47,7 +47,7 @@ const std::shared_ptr &DeferredGet::reportSuccess(uint32_t messagecoun const std::shared_ptr &DeferredGet::reportSuccess() const { // report the size - if (_sizeCallback) _sizeCallback(0); + if (_countCallback) _countCallback(0); // check if a callback was set if (_emptyCallback) _emptyCallback(); diff --git a/src/deferredpublisher.cpp b/src/deferredpublisher.cpp index 61af52a..72843f1 100644 --- a/src/deferredpublisher.cpp +++ b/src/deferredpublisher.cpp @@ -22,7 +22,7 @@ void DeferredPublisher::complete() Monitor monitor(_channel); // do we have a message? - if (_message) _bounceCallback(*_message, 0, ""); + if (_message) _bounceCallback(*_message, _code, _description); // do we have to inform anyone about completion? if (_completeCallback) _completeCallback(); diff --git a/src/deferredreceiver.cpp b/src/deferredreceiver.cpp index 251258a..f4e246c 100644 --- a/src/deferredreceiver.cpp +++ b/src/deferredreceiver.cpp @@ -28,7 +28,7 @@ namespace AMQP { void DeferredReceiver::initialize(const std::string &exchange, const std::string &routingkey) { // anybody interested in the new message? - if (_beginCallback) _beginCallback(exchange, routingkey); + if (_startCallback) _startCallback(exchange, routingkey); } /** @@ -43,6 +43,9 @@ void DeferredReceiver::process(BasicHeaderFrame &frame) // store the body size _bodySize = frame.bodySize(); + + // is user interested in the size? + if (_sizeCallback) _sizeCallback(_bodySize); // do we have a message? if (_message) diff --git a/src/includes.h b/src/includes.h index 122f2d0..89ae392 100644 --- a/src/includes.h +++ b/src/includes.h @@ -68,6 +68,7 @@ #include "amqpcpp/callbacks.h" #include "amqpcpp/deferred.h" #include "amqpcpp/deferredconsumer.h" +#include "amqpcpp/deferredpublisher.h" #include "amqpcpp/deferredqueue.h" #include "amqpcpp/deferreddelete.h" #include "amqpcpp/deferredcancel.h" From 1ccd93cc5e15d3fd3582432c5ec65a2dc7cdb20a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 22:27:27 +0100 Subject: [PATCH 111/168] final step (although untested) for handling returned messages --- include/amqpcpp/channel.h | 21 ++++++++++++++++----- include/amqpcpp/channelimpl.h | 9 ++++++++- include/amqpcpp/deferredpublisher.h | 12 ++++++++++++ src/basicreturnframe.h | 21 ++++++++++++++++++--- src/channelimpl.cpp | 5 +++-- src/deferredpublisher.cpp | 26 +++++++++++++++++++++++++- 6 files changed, 82 insertions(+), 12 deletions(-) diff --git a/include/amqpcpp/channel.h b/include/amqpcpp/channel.h index 2be43cd..b42fc3a 100644 --- a/include/amqpcpp/channel.h +++ b/include/amqpcpp/channel.h @@ -342,6 +342,11 @@ public: /** * 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. + * * This method returns a reference to a DeferredPublisher object. You can use this returned * object to install callbacks that are called when an undeliverable message is returned, or * to set the callback that is called when the server confirms that the message was received. @@ -351,21 +356,27 @@ public: * pass in these flags, your callbacks will not be called. If you are not at all interested * in returned messages or publish-confirms, you can ignore the flag and the returned object. * - * Watch out: the channel returns the same DeferredPublisher object for all calls to the + * Watch out: the channel returns _the same_ DeferredPublisher object for all calls to the * publish() method. This means that the callbacks that you install for the first published * message are also used for subsequent messages _and_ it means that if you install a different * callback for a later publish operation, it overwrites your earlier callbacks * + * 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 */ - DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) { return _implementation->publish(exchange, routingKey, envelope); } - DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const std::string &message) { return _implementation->publish(exchange, routingKey, Envelope(message.data(), message.size())); } - DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size) { return _implementation->publish(exchange, routingKey, Envelope(message, size)); } - DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message) { return _implementation->publish(exchange, routingKey, Envelope(message, strlen(message))); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope, int flags = 0) { return _implementation->publish(exchange, routingKey, envelope, flags); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const std::string &message, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message.data(), message.size()), flags); } + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message, size), flags); } + DeferredPublisher &publish(const std::string &exchange, const std::string &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 diff --git a/include/amqpcpp/channelimpl.h b/include/amqpcpp/channelimpl.h index 41d5742..40d0603 100644 --- a/include/amqpcpp/channelimpl.h +++ b/include/amqpcpp/channelimpl.h @@ -410,9 +410,10 @@ public: * @param envelope the full envelope to send * @param message the message to send * @param size size of the message + * @param flags optional flags * @return DeferredPublisher */ - DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope); + DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope, int flags); /** * Set the Quality of Service (QOS) of the entire connection @@ -707,6 +708,12 @@ public: * @return The handler responsible for the current message */ DeferredReceiver *receiver() const { return _receiver.get(); } + + /** + * Retrieve the deferred publisher that handles returned messages + * @return The deferred publisher object + */ + DeferredPublisher *publisher() const { return _publisher.get(); } /** * The channel class is its friend, thus can it instantiate this object diff --git a/include/amqpcpp/deferredpublisher.h b/include/amqpcpp/deferredpublisher.h index 77c2465..3c9fb17 100644 --- a/include/amqpcpp/deferredpublisher.h +++ b/include/amqpcpp/deferredpublisher.h @@ -60,6 +60,13 @@ private: */ 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 @@ -70,6 +77,11 @@ private: * 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: /** diff --git a/src/basicreturnframe.h b/src/basicreturnframe.h index e140e9c..f14ff65 100644 --- a/src/basicreturnframe.h +++ b/src/basicreturnframe.h @@ -1,7 +1,7 @@ /** * Class describing a basic return frame * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -155,8 +155,23 @@ public: */ virtual bool process(ConnectionImpl *connection) override { - // we no longer support returned messages - return false; + // we need the appropriate channel + auto channel = connection->channel(this->channel()); + + // channel does not exist + if (!channel) return false; + + // get the current publisher + auto publisher = channel->publisher(); + + // if there is no deferred publisher, we can just as well stop + if (publisher == nullptr) return false; + + // initialize the object, because we're about to receive a message + publisher->process(*this); + + // done + return true; } }; diff --git a/src/channelimpl.cpp b/src/channelimpl.cpp index fff8873..053e479 100644 --- a/src/channelimpl.cpp +++ b/src/channelimpl.cpp @@ -451,9 +451,10 @@ DeferredDelete &ChannelImpl::removeQueue(const std::string &name, int flags) * @param envelope the full envelope to send * @param message the message to send * @param size size of the message + * @param flags * @return DeferredPublisher */ -DeferredPublisher &ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope) +DeferredPublisher &ChannelImpl::publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope, int flags) { // we are going to send out multiple frames, each one will trigger a call to the handler, // which in turn could destruct the channel object, we need to monitor that @@ -465,7 +466,7 @@ DeferredPublisher &ChannelImpl::publish(const std::string &exchange, const std:: if (!_publisher) _publisher.reset(new DeferredPublisher(this)); // send the publish frame - if (!send(BasicPublishFrame(_id, exchange, routingKey))) return *_publisher; + if (!send(BasicPublishFrame(_id, exchange, routingKey, (flags & mandatory) != 0, (flags & immediate) != 0))) return *_publisher; // channel still valid? if (!monitor.valid()) return *_publisher; diff --git a/src/deferredpublisher.cpp b/src/deferredpublisher.cpp index 72843f1..f25b167 100644 --- a/src/deferredpublisher.cpp +++ b/src/deferredpublisher.cpp @@ -7,12 +7,34 @@ * @copyright 2018 Copernica BV */ #include "includes.h" +#include "basicreturnframe.h" /** * Begin of namespace */ namespace AMQP { +/** + * Process a return frame + * + * @param frame The frame to process + */ +void DeferredPublisher::process(BasicReturnFrame &frame) +{ + // this object will handle all future frames with header and body data + _channel->install(shared_from_this()); + + // retrieve the delivery tag and whether we were redelivered + _code = frame.replyCode(); + _description = frame.replyText(); + + // notify user space of the begin of the returned message + if (_beginCallback) _beginCallback(_code, _description); + + // initialize the object for the next message + initialize(frame.exchange(), frame.routingKey()); +} + /** * Indicate that a message was done */ @@ -29,6 +51,9 @@ void DeferredPublisher::complete() // for the next iteration we want a new message _message.reset(); + + // the description can be thrown away too + _description.clear(); // do we still have a valid channel if (!monitor.valid()) return; @@ -42,4 +67,3 @@ void DeferredPublisher::complete() */ } - From 41239a1952d5a103e0e89b244ec5409d68626490 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 22:54:50 +0100 Subject: [PATCH 112/168] fixed to make returning message functional, and added test code --- include/amqpcpp.h | 1 + src/deferredpublisher.cpp | 3 +++ tests/address.cpp | 2 ++ tests/myconnection.cpp | 21 +++++++++++++++------ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/include/amqpcpp.h b/include/amqpcpp.h index 4d7735a..9d0e5a7 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -71,6 +71,7 @@ #include "amqpcpp/deferreddelete.h" #include "amqpcpp/deferredcancel.h" #include "amqpcpp/deferredget.h" +#include "amqpcpp/deferredpublisher.h" #include "amqpcpp/channelimpl.h" #include "amqpcpp/channel.h" #include "amqpcpp/login.h" diff --git a/src/deferredpublisher.cpp b/src/deferredpublisher.cpp index f25b167..b75e6df 100644 --- a/src/deferredpublisher.cpp +++ b/src/deferredpublisher.cpp @@ -33,6 +33,9 @@ void DeferredPublisher::process(BasicReturnFrame &frame) // initialize the object for the next message initialize(frame.exchange(), frame.routingKey()); + + // do we have anybody interested in messages? in that case we construct the message + if (_bounceCallback) _message.construct(frame.exchange(), frame.routingKey()); } /** diff --git a/tests/address.cpp b/tests/address.cpp index 2145931..0de9a15 100644 --- a/tests/address.cpp +++ b/tests/address.cpp @@ -18,6 +18,7 @@ * @param argv * @return int */ +/* int main(int argc, const char *argv[]) { // iterate over the arguments @@ -37,3 +38,4 @@ int main(int argc, const char *argv[]) // done return 0; } +*/ \ No newline at end of file diff --git a/tests/myconnection.cpp b/tests/myconnection.cpp index 96f3580..8875d02 100644 --- a/tests/myconnection.cpp +++ b/tests/myconnection.cpp @@ -30,7 +30,7 @@ MyConnection::MyConnection(const std::string &ip) : _socket(Event::MainLoop::instance(), this) { // start connecting - if (_socket.connect(Network::Ipv4Address(ip), 5672)) return; + if (_socket.connect(Dns::IpAddress(ip), 5672)) return; // failure onFailure(&_socket); @@ -96,21 +96,30 @@ void MyConnection::onConnected(Network::TcpSocket *socket) std::cout << "queue declared" << std::endl; // start consuming - _channel->consume("my_queue").onReceived([](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) { - std::cout << "received: " << message.message() << std::endl; + _channel->consume("my_queue").onReceived([this](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) { + std::cout << "consumed from exchange " << message.exchange() << " " << message.routingkey() << ": " << std::string(message.body(), message.bodySize()) << std::endl; + _channel->ack(deliveryTag); }); }); // declare an exchange - _channel->declareExchange().onSuccess([]() { + _channel->declareExchange("my_exchange", AMQP::direct).onSuccess([]() { std::cout << "exchange declared" << std::endl; }); // bind queue and exchange _channel->bindQueue("my_exchange", "my_queue", "key").onSuccess([this]() { std::cout << "queue bound to exchange" << std::endl; + + // callback for returns + auto callback = [](const AMQP::Message &message, int16_t code, const std::string &description) { - _channel->publish("my_exchange", "key", "just a message"); + std::cout << "message was returned: " << code << " " << description << ": " << std::string(message.body(), message.bodySize()) << std::endl; + + }; + + _channel->publish("my_exchange", "key", "just a message", AMQP::mandatory).onReturned(callback); + _channel->publish("my_exchange", "unknown key", "just another message", AMQP::mandatory).onReturned(callback); }); } @@ -156,7 +165,7 @@ void MyConnection::onData(Network::TcpSocket *socket, Network::Buffer *buffer) if (!_connection) return; // let the data be handled by the connection - size_t bytes = _connection->parse(buffer->data(), buffer->size()); + size_t bytes = _connection->parse(buffer->buffer(), buffer->size()); // shrink the buffer buffer->shrink(bytes); From 2a0a6f3fb71a0aa96a72f4550e9072604b989c28 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 23:13:07 +0100 Subject: [PATCH 113/168] update cmake stuff --- src/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6cc8e1a..a16912f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,7 +44,9 @@ add_sources( consumedmessage.h deferredcancel.cpp deferredconsumer.cpp - deferredconsumerbase.cpp + deferredreceiver.cpp + deferredextreceiver.cpp + deferredpublisher.cpp deferredget.cpp exchangebindframe.h exchangebindokframe.h From 52c71ac1688e99583fb3fcd7674f7d897b642496 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 1 Mar 2018 23:27:20 +0100 Subject: [PATCH 114/168] update readme --- README.md | 65 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 715e6a5..33538a9 100644 --- a/README.md +++ b/README.md @@ -799,44 +799,54 @@ in almost any form: ````c++ /** * Publish a message to an exchange - * - * The following flags can be used - * - * - mandatory if set, an unroutable message will be sent back to - * the client (currently not supported) - * - * - immediate if set, a message that could not immediately be consumed - * is returned to the client (currently not supported) - * - * If either of the two flags is set, and the message could not immediately - * be published, the message is returned by the server to the client. However, - * at this moment in time, the AMQP-CPP library does not support catching - * such returned messages. - * + * + * 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. + * + * This method returns a reference to a DeferredPublisher object. You can use + * this returned object to install callbacks that are called when an undeliverable + * message is returned, or to set the callback that is called when the server + * confirms that the message was received. + * + * To enable handling returned messages, or to enable publisher-confirms, you must + * not only set the callback, but also pass in appropriate flags to enable this + * feature. If you do not pass in these flags, your callbacks will not be called. + * If you are not at all interested in returned messages or publish-confirms, you + * can ignore the flag and the returned object. + * + * Watch out: the channel returns _the same_ DeferredPublisher object for all + * calls to the publish() method. This means that the callbacks that you install + * for the first published message are also used for subsequent messages _and_ + * it means that if you install a different callback for a later publish + * operation, it overwrites your earlier callbacks + * + * 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 flags optional flags (see above) * @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 &exchange, const std::string &routingKey, int flags, const AMQP::Envelope &envelope); -bool publish(const std::string &exchange, const std::string &routingKey, const AMQP::Envelope &envelope); -bool publish(const std::string &exchange, const std::string &routingKey, int flags, const std::string &message); -bool publish(const std::string &exchange, const std::string &routingKey, const std::string &message); -bool publish(const std::string &exchange, const std::string &routingKey, int flags, const char *message, size_t size); -bool publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size); +DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const Envelope &envelope, int flags = 0) { return _implementation->publish(exchange, routingKey, envelope, flags); } +DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const std::string &message, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message.data(), message.size()), flags); } +DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message, size_t size, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message, size), flags); } +DeferredPublisher &publish(const std::string &exchange, const std::string &routingKey, const char *message, int flags = 0) { return _implementation->publish(exchange, routingKey, Envelope(message, strlen(message)), flags); } ```` Published messages are normally not confirmed by the server, and the RabbitMQ will not send a report back to inform you whether the message was succesfully -published or not. Therefore the publish method does not return a Deferred -object. +published or not. But with the flags you can instruct RabbitMQ to send back +the message if it was undeliverable. -As long as no error is reported via the Channel::onError() method, you can safely -assume that your messages were delivered. - -This can of course be a problem when you are publishing many messages. If you get +You can also use transactions to ensure that your messages get delivered. +Let's say that you are publishing many messages in a row. If you get an error halfway through there is no way to know for sure how many messages made it to the broker and how many should be republished. If this is important, you can wrap the publish commands inside a transaction. In this case, if an error occurs, @@ -1002,7 +1012,6 @@ need additional attention: - ability to set up secure connections (or is this fully done on the IO level) - login with other protocols than login/password - publish confirms - - returned messages We also need to add more safety checks so that strange or invalid data from RabbitMQ does not break the library (although in reality RabbitMQ only sends From 86262b1024f8caab621857a6e298852a8fa3b48f Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 2 Mar 2018 08:36:00 +0100 Subject: [PATCH 115/168] update version number in makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9af8712..c4503c1 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ PREFIX ?= /usr INCLUDE_DIR = ${PREFIX}/include LIBRARY_DIR = ${PREFIX}/lib export LIBRARY_NAME = amqpcpp -export SONAME = 2.8 -export VERSION = 2.8.0 +export SONAME = 3.0 +export VERSION = 3.0.0 all: $(MAKE) -C src all From 79b1477ef4bbb019193abd022e7cebfa9092d119 Mon Sep 17 00:00:00 2001 From: Steven Geddis <35739851+lion10243@users.noreply.github.com> Date: Fri, 2 Mar 2018 11:44:38 +0100 Subject: [PATCH 116/168] place EXPORT keyword correctly --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b31f800..a3f75a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,8 +86,8 @@ endif() if(AMQP-CPP_BUILD_SHARED) # copy shared lib and its static counter part - install(TARGETS ${PROJECT_NAME} - ARCHIVE DESTINATION lib EXPORT ${PROJECT_NAME}Config + install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Config + ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION lib ) From b9047f5ea7bbcef2294841776f932b65b488d0ca Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Fri, 2 Mar 2018 12:14:43 +0100 Subject: [PATCH 117/168] added ampqs:// parsing to address --- include/amqpcpp/address.h | 42 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index 9e54779..65e17b6 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -6,7 +6,7 @@ * @author Emiel Bruijntjes * @copyright 2015 Copernica BV */ - + /** * Include guard */ @@ -23,6 +23,13 @@ namespace AMQP { class Address { private: + + /** + * The auth method + * @var std::string + */ + std::string _authmethod; + /** * Login data (username + password) * @var Login @@ -60,8 +67,10 @@ public: // position of the last byte const char *last = data + size; - // must start with amqp:// - if (size < 7 || strncmp(data, "amqp://", 7) != 0) throw std::runtime_error("AMQP address should start with \"amqp://\""); + // must start with amqp:// or ampqs:// + if (strncmp(data, "amqps://", 8) == 0) _authmethod = std::string("amqps://"); + else if (strncmp(data, "amqp://", 7) == 0) _authmethod = std::string("amqp://"); + else throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\""); // begin of the string was parsed data += 7; @@ -115,7 +124,7 @@ public: /** * Constructor to parse an address string - * The address should start with "amqp:// + * The address should start with amqp:// or amqps:// * @param data * @throws std::runtime_error */ @@ -129,12 +138,14 @@ public: /** * Constructor based on already known properties + * @param authmethod * @param host * @param port * @param login * @param vhost */ - Address(std::string host, uint16_t port, Login login, std::string vhost) : + Address(std::string authmethod, std::string host, uint16_t port, Login login, std::string vhost) : + _authmethod(std::move(authmethod)), _login(std::move(login)), _hostname(std::move(host)), _port(port), @@ -145,6 +156,15 @@ public: */ virtual ~Address() = default; + /** + * Expose the login data + * @return Login + */ + const std::string &authmethod() const + { + return _authmethod; + } + /** * Expose the login data * @return Login @@ -183,12 +203,12 @@ public: /** * Cast to a string - * @return std:string + * @return std::string */ operator std::string () const { // result object - std::string str("amqp://"); + std::string str(_authmethod); // append login str.append(_login.user()).append(":").append(_login.password()).append("@").append(_hostname); @@ -213,6 +233,9 @@ public: */ bool operator==(const Address &that) const { + // auth method should match + if (_authmethod != that._authmethod) return false; + // logins must match if (_login != that._login) return false; @@ -244,6 +267,9 @@ public: */ bool operator<(const Address &that) const { + // compare auth methods + if (_authmethod != that._authmethod) return _authmethod < that._authmethod; + // compare logins if (_login != that._login) return _login < that._login; @@ -269,7 +295,7 @@ public: friend std::ostream &operator<<(std::ostream &stream, const Address &address) { // start with the protocol and login - stream << "amqp://" << address._login; + stream << address._authmethod << address._login; // do we need a special portnumber? if (address._port != 5672) stream << ":" << address._port; From ab817384b1c507dbf3b10db565e2740f664766b4 Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Fri, 2 Mar 2018 12:54:09 +0100 Subject: [PATCH 118/168] fixed auth method prefix extra / char --- examples/libev.cpp | 5 +++-- include/amqpcpp/address.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index e4c428d..5893ca4 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -65,8 +65,9 @@ int main() MyHandler handler(loop); // make a connection - AMQP::TcpConnection connection(&handler, AMQP::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); diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index 65e17b6..951d3a2 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -73,7 +73,7 @@ public: else throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\""); // begin of the string was parsed - data += 7; + data += _authmethod.length(); // do we have a '@' to split user-data and hostname? const char *at = (const char *)memchr(data, '@', last - data); From 342268e50abac80ab5d399c2cba8e9759378af7b Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Fri, 2 Mar 2018 13:53:00 +0100 Subject: [PATCH 119/168] support for amqps:// addresses in the AMQP::Address class (although it does not yet make a real secure connection) --- include/amqpcpp/address.h | 37 ++++++++++++++++----------------- src/linux_tcp/tcpconnection.cpp | 2 +- src/linux_tcp/tcpresolver.h | 28 ++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index 951d3a2..f77255e 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -23,12 +23,11 @@ namespace AMQP { class Address { private: - /** * The auth method - * @var std::string + * @var bool */ - std::string _authmethod; + bool _secure; /** * Login data (username + password) @@ -68,12 +67,12 @@ public: const char *last = data + size; // must start with amqp:// or ampqs:// - if (strncmp(data, "amqps://", 8) == 0) _authmethod = std::string("amqps://"); - else if (strncmp(data, "amqp://", 7) == 0) _authmethod = std::string("amqp://"); + if (strncmp(data, "amqps://", 8) == 0) _secure = true; + else if (strncmp(data, "amqp://", 7) == 0) _secure = false; else throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\""); // begin of the string was parsed - data += _authmethod.length(); + data += _secure ? 8 : 7; // do we have a '@' to split user-data and hostname? const char *at = (const char *)memchr(data, '@', last - data); @@ -138,14 +137,14 @@ public: /** * Constructor based on already known properties - * @param authmethod * @param host * @param port * @param login * @param vhost + * @param secure */ - Address(std::string authmethod, std::string host, uint16_t port, Login login, std::string vhost) : - _authmethod(std::move(authmethod)), + 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), @@ -157,12 +156,12 @@ public: virtual ~Address() = default; /** - * Expose the login data - * @return Login + * Should we open a secure connection? + * @return bool */ - const std::string &authmethod() const + bool secure() const { - return _authmethod; + return _secure; } /** @@ -208,7 +207,7 @@ public: operator std::string () const { // result object - std::string str(_authmethod); + std::string str(_secure ? "amqps://" : "amqp://"); // append login str.append(_login.user()).append(":").append(_login.password()).append("@").append(_hostname); @@ -233,8 +232,8 @@ public: */ bool operator==(const Address &that) const { - // auth method should match - if (_authmethod != that._authmethod) return false; + // security setting should match + if (_secure != that._secure) return false; // logins must match if (_login != that._login) return false; @@ -267,8 +266,8 @@ public: */ bool operator<(const Address &that) const { - // compare auth methods - if (_authmethod != that._authmethod) return _authmethod < that._authmethod; + // compare auth methods (amqp comes before amqps) + if (_secure != that._secure) return !_secure; // compare logins if (_login != that._login) return _login < that._login; @@ -295,7 +294,7 @@ public: friend std::ostream &operator<<(std::ostream &stream, const Address &address) { // start with the protocol and login - stream << address._authmethod << address._login; + stream << (address._secure ? "amqps://" : "amqp://"); // do we need a special portnumber? if (address._port != 5672) stream << ":" << address._port; diff --git a/src/linux_tcp/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp index 6a1d8b0..f0663c3 100644 --- a/src/linux_tcp/tcpconnection.cpp +++ b/src/linux_tcp/tcpconnection.cpp @@ -24,7 +24,7 @@ namespace AMQP { * @param hostname The address to connect to */ TcpConnection::TcpConnection(TcpHandler *handler, const Address &address) : - _state(new TcpResolver(this, address.hostname(), address.port(), handler)), + _state(new TcpResolver(this, address.hostname(), address.port(), address.secure(), handler)), _connection(this, address.login(), address.vhost()) {} /** diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index de543b4..028baf0 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -39,6 +39,12 @@ private: */ std::string _hostname; + /** + * Should we be using a secure connection? + * @var bool + */ + bool _secure; + /** * The portnumber to connect to * @var uint16_t @@ -142,11 +148,13 @@ public: * @param connection Parent connection object * @param hostname The hostname for the lookup * @param portnumber The portnumber for the lookup + * @param secure Do we need a secure tls connection when ready? * @param handler User implemented handler object */ - TcpResolver(TcpConnection *connection, const std::string &hostname, uint16_t port, TcpHandler *handler) : + TcpResolver(TcpConnection *connection, const std::string &hostname, uint16_t port, bool secure, TcpHandler *handler) : TcpState(connection, handler), _hostname(hostname), + _secure(secure), _port(port) { // tell the event loop to monitor the filedescriptor of the pipe @@ -183,7 +191,14 @@ public: if (fd != _pipe.in() || !(flags & readable)) return this; // do we have a valid socket? - if (_socket >= 0) return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); + if (_socket >= 0) + { + // if we need a secure connection, we move to the tls handshake + //if (_secure) return new TcpSslHandshake(....); + + // otherwise we have a valid regular tcp connection + return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); + } // report error _handler->onError(_connection, _error.data()); @@ -202,7 +217,14 @@ public: _thread.join(); // do we have a valid socket? - if (_socket >= 0) return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); + if (_socket >= 0) + { + // if we need a secure connection, we move to the tls handshake + //if (_secure) return new TcpSslHandshake(....); + + // otherwise we have a valid regular tcp connection + return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); + } // report error _handler->onError(_connection, _error.data()); From 9c1a09a71175a198b2b0a11ffe30e53a6301d026 Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Fri, 2 Mar 2018 16:56:35 +0100 Subject: [PATCH 120/168] ssl connection WIP --- examples/libev.cpp | 3 +- src/linux_tcp/tcpresolver.h | 5 +- src/linux_tcp/tcpsslhandshake.h | 287 ++++++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 src/linux_tcp/tcpsslhandshake.h diff --git a/examples/libev.cpp b/examples/libev.cpp index 5893ca4..d299f37 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -67,10 +67,11 @@ int main() // make a connection 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](const std::string &name, uint32_t messagecount, uint32_t consumercount) { diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 028baf0..4a4a852 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -20,6 +20,7 @@ #include "tcpstate.h" #include "tcpclosed.h" #include "tcpconnected.h" +#include "tcpsslhandshake.h" #include /** @@ -194,7 +195,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - //if (_secure) return new TcpSslHandshake(....); + if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); @@ -220,7 +221,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - //if (_secure) return new TcpSslHandshake(....); + if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); diff --git a/src/linux_tcp/tcpsslhandshake.h b/src/linux_tcp/tcpsslhandshake.h new file mode 100644 index 0000000..2070c27 --- /dev/null +++ b/src/linux_tcp/tcpsslhandshake.h @@ -0,0 +1,287 @@ +/** + * TcpSslHandshake.h + * + * Implementation of the TCP state that is responsible for setting + * up the STARTTLS handshake. + * + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include "tcpoutbuffer.h" + +#include +#include + +#include + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class TcpSslHandshake : public TcpState, private Watchable +{ +private: + + /** + * SSL structure + * @var SSL + */ + SSL *_ssl; + + /** + * The socket file descriptor + * @var int + */ + int _socket; + + /** + * The outgoing buffer + * @var TcpOutBuffer + */ + TcpOutBuffer _out; + + + + /** + * Helper method to report an error + * @return TcpState* + */ + TcpState *reportError() + { + // we are no longer interested in any events for this socket + _handler->monitor(_connection, _socket, 0); + + // we have an error - report this to the user + _handler->onError(_connection, "failed to setup ssl connection"); + + // close the socket + close(_socket); + + // done, go to the closed state + return new TcpClosed(_connection, _handler); + } + + /** + * Construct the next state + * @param monitor Object that monitors whether connection still exists + * @return TcpState* + */ + TcpState *nextState(const Monitor &monitor) + { + // if the object is still in a valid state, we can move to the close-state, + // otherwise there is no point in moving to a next state + return monitor.valid() ? new TcpClosed(this) : nullptr; + } + + /** + * Wait until the socket is writable + * @return bool + */ + bool wait4writable() + { + // we need the fd-sets + fd_set readables, writables, exceptions; + + // initialize all the sets + FD_ZERO(&readables); + FD_ZERO(&writables); + FD_ZERO(&exceptions); + + // add the one socket + FD_SET(_socket, &writables); + + // wait for the socket + auto result = select(_socket + 1, &readables, &writables, &exceptions, nullptr); + + // check for success + return result == 0; + } + +public: + /** + * Constructor + * + * @todo catch the exception! + * + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param context SSL context + * @param buffer The buffer that was already built + * @param handler User-supplied handler object + * @throws std::runtime_error + */ + TcpSslHandshake(TcpConnection *connection, int socket, TcpOutBuffer &&buffer, TcpHandler *handler) : + TcpState(connection, handler), + _socket(socket), + _out(std::move(buffer)) + { + SSL_library_init(); + + // create ssl object + _ssl = SSL_new(SSL_CTX_new(TLS_client_method())); + + // leap out on error + if (_ssl == nullptr) throw std::runtime_error("ERROR: SSL structure is null"); + + // we will be using the ssl context as a client + // @todo check return value + SSL_set_connect_state(_ssl); + + // associate the ssl context with the socket filedescriptor + // @todo check return value + SSL_set_fd(_ssl, socket); + + // we are going to wait until the socket becomes writable before we start the handshake + _handler->monitor(_connection, _socket, writable); + } + + /** + * Destructor + */ + virtual ~TcpSslHandshake() noexcept + { + // close the socket + close(_socket); + } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } + + /** + * Process the filedescriptor in the object + * @param fd Filedescriptor that is active + * @param flags AMQP::readable and/or AMQP::writable + * @return New state object + */ + virtual TcpState *process(int fd, int flags) override + { + // must be the socket + if (fd != _socket) return this; + + // start the ssl handshake + int result = SSL_do_handshake(_ssl); + + // if the connection succeeds, we can move to the ssl-connected state + // @todo we need the sslconnected state object + if (result == 1) return this; // new TcpSslConnected(connection, socket, _ssl, std::move(_out), _handler); + + // if there is a failure, we must close down the connection + if (result == 0) return reportError(); + + // -1 was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // the handshake must be repeated when socket is readable, wait for that + std::cout << "wait for readability" << std::endl; + _handler->monitor(_connection, _socket, readable); + break; + + case SSL_ERROR_WANT_WRITE: + // the handshake must be repeated when socket is readable, wait for that + std::cout << "wait for writability" << std::endl; + _handler->monitor(_connection, _socket, writable); + break; + + default: + // @todo implement handling other error states + std::cout << "unknown error state " << error << std::endl; + // @todo we have to close the connection + return reportError(); + } + + // keep same object + return this; + } + + /** + * Send data over the connection + * @param buffer buffer to send + * @param size size of the buffer + */ + virtual void send(const char *buffer, size_t size) override + { + + // @todo because the handshake is still busy, outgoing data must be cached + + } + + /** + * Flush the connection, sent all buffered data to the socket + * @return TcpState new tcp state + */ + virtual TcpState *flush() override + { + // @todo implementation? + return nullptr; + } + + /** + * Report that heartbeat negotiation is going on + * @param heartbeat suggested heartbeat + * @return uint16_t accepted heartbeat + */ + virtual uint16_t reportNegotiate(uint16_t heartbeat) override + { + /* + * @todo what should we do here? + + // remember that we have to reallocated (_in member can not be accessed because it is moved away) + _reallocate = _connection->maxFrame(); + + // pass to base + return TcpState::reportNegotiate(heartbeat); + */ + + return 0; + } + + /** + * Report to the handler that the connection was nicely closed + */ + virtual void reportClosed() override + { + /* + + // we no longer have to monitor the socket + _handler->monitor(_connection, _socket, 0); + + // close the socket + close(_socket); + + // socket is closed now + _socket = -1; + + // copy the handler (if might destruct this object) + auto *handler = _handler; + + // reset member before the handler can make a mess of it + _handler = nullptr; + + // notify to handler + handler->onClosed(_connection); + */ + } +}; + +/** + * End of namespace + */ +} From e46dfcf3b8f90194f1deccffbf4fa9a68abc20aa Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Mon, 5 Mar 2018 17:29:37 +0100 Subject: [PATCH 121/168] work in progress on ssl support for the AMQP-CPP library --- examples/libev.cpp | 1 - include/amqpcpp/address.h | 11 +- src/linux_tcp/tcpconnected.h | 32 +--- src/linux_tcp/tcpinbuffer.h | 14 ++ src/linux_tcp/tcpoutbuffer.h | 82 +++++++-- src/linux_tcp/tcpresolver.h | 5 +- src/linux_tcp/tcpsslconnected.h | 290 ++++++++++++++++++++++++++++++++ src/linux_tcp/tcpsslhandshake.h | 200 ++++++++++++---------- src/linux_tcp/wait.h | 93 ++++++++++ 9 files changed, 584 insertions(+), 144 deletions(-) create mode 100644 src/linux_tcp/tcpsslconnected.h create mode 100644 src/linux_tcp/wait.h diff --git a/examples/libev.cpp b/examples/libev.cpp index d299f37..3922363 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -68,7 +68,6 @@ int main() AMQP::Address address("amqps://guest:guest@localhost/"); AMQP::TcpConnection connection(&handler, address); - // we need a channel too AMQP::TcpChannel channel(&connection); diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index f77255e..efccf8e 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -4,7 +4,7 @@ * An AMQP address in the "amqp://user:password@hostname:port/vhost" notation * * @author Emiel Bruijntjes - * @copyright 2015 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -66,10 +66,11 @@ public: // position of the last byte const char *last = data + size; - // must start with amqp:// or ampqs:// - if (strncmp(data, "amqps://", 8) == 0) _secure = true; - else if (strncmp(data, "amqp://", 7) == 0) _secure = false; - else throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\""); + // must start with ampqs:// to have a secure connection (and we also assign a different default port) + if ((_secure = strncmp(data, "amqps://", 8) == 0)) _port = 5671; + + // otherwise protocol must be amqp:// + else if (strncmp(data, "amqp://", 7) != 0) throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\""); // begin of the string was parsed data += _secure ? 8 : 7; diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index dd9a71e..c523183 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -5,7 +5,7 @@ * the hostname was resolved into an IP address * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -18,6 +18,7 @@ */ #include "tcpoutbuffer.h" #include "tcpinbuffer.h" +#include "wait.h" /** * Set up namespace @@ -83,30 +84,6 @@ private: return monitor.valid() ? new TcpClosed(this) : nullptr; } - /** - * Wait until the socket is writable - * @return bool - */ - bool wait4writable() - { - // we need the fd-sets - fd_set readables, writables, exceptions; - - // initialize all the sets - FD_ZERO(&readables); - FD_ZERO(&writables); - FD_ZERO(&exceptions); - - // add the one socket - FD_SET(_socket, &writables); - - // wait for the socket - auto result = select(_socket + 1, &readables, &writables, &exceptions, nullptr); - - // check for success - return result == 0; - } - public: /** * Constructor @@ -242,11 +219,14 @@ public: */ virtual TcpState *flush() override { + // create an object to wait for the filedescriptor to becomes active + Wait wait(_socket); + // keep running until the out buffer is empty while (_out) { // poll the socket, is it already writable? - if (!wait4writable()) return this; + if (!wait.writable()) return this; // socket is writable, send as much data as possible auto *newstate = process(_socket, writable); diff --git a/src/linux_tcp/tcpinbuffer.h b/src/linux_tcp/tcpinbuffer.h index 65971ec..d7ea09c 100644 --- a/src/linux_tcp/tcpinbuffer.h +++ b/src/linux_tcp/tcpinbuffer.h @@ -107,6 +107,20 @@ public: // done return result; } + + /** + * Receive data from a socket + * @param ssl ssl wrapped socket to read from + * @param expected number of bytes that the library expects + * @return ssize_t + */ + /* + ssize_t receivefrom(SSL *ssl, uint32_t expected) + { + // @todo implementation + return 0; + } + */ /** * Shrink the buffer (in practice this is always called with the full buffer size) diff --git a/src/linux_tcp/tcpoutbuffer.h b/src/linux_tcp/tcpoutbuffer.h index 186a9b4..4364c34 100644 --- a/src/linux_tcp/tcpoutbuffer.h +++ b/src/linux_tcp/tcpoutbuffer.h @@ -18,7 +18,7 @@ */ #include #include - + /** * FIONREAD on Solaris is defined elsewhere */ @@ -195,10 +195,36 @@ public: } } + /** + * Fill an iovec buffer + * @param buffers the buffers to be filled + * @param count number of buffers available + * @return size_t the number of buffers that were filled + */ + size_t fill(struct iovec buffers[], size_t count) const + { + // index counter + size_t index = 0; + + // iterate over the buffers + for (const auto &str : _buffers) + { + // fill buffer + buffers[index].iov_base = (void *)(index == 0 ? str.data() + _skip : str.data()); + buffers[index].iov_len = index == 0 ? str.size() - _skip : str.size(); + + // update counter for next iteration + if (++index >= count) return count; + } + + // done + return index; + } + /** * Send the buffer to a socket - * @param socket - * @return ssize_t + * @param socket the socket to send data to + * @return ssize_t number of bytes sent (or the same result as sendmsg() in case of an error) */ ssize_t sendto(int socket) { @@ -211,20 +237,6 @@ public: // we're going to fill a lot of buffers (64 should normally be enough) struct iovec buffer[64]; - // index counter - size_t index = 0; - - // iterate over the buffers - for (const auto &str : _buffers) - { - // fill buffer - buffer[index].iov_base = (void *)(index == 0 ? str.data() + _skip : str.data()); - buffer[index].iov_len = index == 0 ? str.size() - _skip : str.size(); - - // update counter for next iteration - if (++index >= 64) break; - } - // create the message header struct msghdr header; @@ -233,7 +245,10 @@ public: // save the buffers in the message header header.msg_iov = buffer; - header.msg_iovlen = index; + header.msg_iovlen = fill(buffer, 64); + + // do nothing if no buffers were filled + if (header.msg_iovlen == 0) break; // send the data auto result = sendmsg(socket, &header, AMQP_CPP_MSG_NOSIGNAL); @@ -251,6 +266,37 @@ public: // done return total; } + + /** + * Send the buffer to an SSL connection + * @param ssl the ssl context to send data to + * @return ssize_t number of bytes sent, or the return value of ssl_write + */ + /* + ssize_t sendto(SSL *ssl) + { + // we're going to fill a lot of buffers (for ssl only one buffer at a time can be sent) + struct iovec buffer[1]; + + // fill the buffers, and leap out if there is no data + auto buffers = fill(buffer, 1); + + std::cout << "buffercount = " << buffers << std::endl; + + if (buffers == 0) return 0; + + // send the data + auto result = SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len); + + // @todo do we have to move to the next buffer to prevent that this buffer is further filled? + + // on success we shrink the buffer + if (result > 0) shrink(result); + + // done + return result; + } + */ }; /** diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 4a4a852..008bbc6 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -20,7 +20,6 @@ #include "tcpstate.h" #include "tcpclosed.h" #include "tcpconnected.h" -#include "tcpsslhandshake.h" #include /** @@ -195,7 +194,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); + //if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); @@ -221,7 +220,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); + //if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); diff --git a/src/linux_tcp/tcpsslconnected.h b/src/linux_tcp/tcpsslconnected.h new file mode 100644 index 0000000..232fb0b --- /dev/null +++ b/src/linux_tcp/tcpsslconnected.h @@ -0,0 +1,290 @@ +/** + * TcpSslConnected.h + * + * The actual tcp connection over SSL + * + * @copyright 2018 copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include "tcpoutbuffer.h" +#include "tcpinbuffer.h" +#include "wait.h" +#include + +#include + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class TcpSslConnected: public TcpState, private Watchable +{ +private: + /** + * The SSL context + * @var SSL* + */ + SSL *_ssl; + + /** + * Socket file descriptor + * @var int + */ + int _socket; + + /** + * The outgoing buffer + * @var TcpBuffer + */ + TcpOutBuffer _out; + + /** + * The incoming buffer + * @var TcpInBuffer + */ + TcpInBuffer _in; + + /** + * Are we now busy with sending or receiving? + * @var enum + */ + enum { + state_idle, + state_sending, + state_receiving + } _state; + + + /** + * Helper method to report an error + * @return bool Was an error reported? + */ + bool reportError() + { + // some errors are ok and do not (necessarily) mean that we're disconnected + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false; + + // we have an error - report this to the user + _handler->onError(_connection, strerror(errno)); + + // done + return true; + } + + /** + * Construct the next state + * @param monitor Object that monitors whether connection still exists + * @return TcpState* + */ + TcpState *nextState(const Monitor &monitor) + { + // if the object is still in a valid state, we can move to the close-state, + // otherwise there is no point in moving to a next state + return monitor.valid() ? new TcpClosed(this) : nullptr; + } + + /** + * Proceed with the previous operation, possibly changing the monitor + * @return TcpState* + */ + TcpState *proceed() + { + // if we still have an outgoing buffer we want to send out data + if (_out) + { + // we still have a buffer with outgoing data + _state = state_sending; + + // let's wait until the socket becomes writable + _handler->monitor(_connection, _socket, readable); + } + else + { + // outgoing buffer is empty, we're idle again waiting for further input + _state = state_idle; + + // let's wait until the socket becomes readable + _handler->monitor(_connection, _socket, readable); + } + + // done + return this; + } + + /** + * Method to repeat the previous call + * @param result result of an earlier openssl operation + * @return TcpState* + */ + TcpState *repeat(int result) + { + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + std::cout << "error = " << error << std::endl; + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // the operation must be repeated when readable + std::cout << "want read" << std::endl; + + _handler->monitor(_connection, _socket, readable); + return this; + + case SSL_ERROR_WANT_WRITE: + // wait until socket becomes writable again + std::cout << "want write" << std::endl; + + _handler->monitor(_connection, _socket, writable); + return this; + + default: + std::cout << "something else" << std::endl; + + // @todo check how to handle this + return this; + } + } + + +public: + /** + * Constructor + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param ssl The SSL structure + * @param buffer The buffer that was already built + * @param handler User-supplied handler object + */ + TcpSslConnected(TcpConnection *connection, int socket, SSL *ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : + TcpState(connection, handler), + _ssl(ssl), + _socket(socket), + _out(std::move(buffer)), + _in(4096), + _state(_out ? state_sending : state_idle) + { + std::cout << "ssl-connected" << std::endl; + + // tell the handler to monitor the socket if there is an out + _handler->monitor(_connection, _socket, _state == state_sending ? writable : readable); + } + + /** + * Destructor + */ + virtual ~TcpSslConnected() noexcept + { + // skip if handler is already forgotten + if (_handler == nullptr) return; + + // we no longer have to monitor the socket + _handler->monitor(_connection, _socket, 0); + + // close the socket + close(_socket); + } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } + + /** + * Process the filedescriptor in the object + * @param fd The filedescriptor that is active + * @param flags AMQP::readable and/or AMQP::writable + * @return New implementation object + */ + virtual TcpState *process(int fd, int flags) + { + std::cout << "process call in ssl-connected" << std::endl; + + std::cout << fd << " - " << _socket << std::endl; + + + // the socket must be the one this connection writes to + if (fd != _socket) return this; + + // because the object might soon be destructed, we create a monitor to check this + Monitor monitor(this); + + // are we busy with sending or receiving data? + if (_state == state_sending) + { + std::cout << "busy sending" << std::endl; + + // try to send more data from the outgoing buffer + auto result = _out.sendto(_ssl); + + std::cout << "result = " << result << std::endl; + + // if this is a success, we may have to update the monitor + if (result > 0) return proceed(); + + // the operation failed, we may have to repeat our call + else return repeat(result); + } + else + { + // read data from ssl into the buffer + auto result = _in.receivefrom(_ssl, _connection->expected()); + + // if this is a success, we may have to update the monitor + // @todo also parse the buffer + if (result > 0) return proceed(); + + // the operation failed, we may have to repeat our call + else return repeat(result); + + + + // we're busy with receiving data + // @todo check this + + std::cout << "receive data" << std::endl; + + } + + // keep same object + return this; + } + + /** + * Send data over the connection + * @param buffer buffer to send + * @param size size of the buffer + */ + virtual void send(const char *buffer, size_t size) + { + // put the data in the outgoing buffer + _out.add(buffer, size); + + // if we're already busy with sending or receiving, we first have to wait + // for that operation to complete before we can move on + if (_state != state_idle) return; + + // let's wait until the socket becomes writable + _handler->monitor(_connection, _socket, writable); + } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/tcpsslhandshake.h b/src/linux_tcp/tcpsslhandshake.h index 2070c27..eccb860 100644 --- a/src/linux_tcp/tcpsslhandshake.h +++ b/src/linux_tcp/tcpsslhandshake.h @@ -16,10 +16,12 @@ * Dependencies */ #include "tcpoutbuffer.h" +#include "tcpsslconnected.h" +#include "wait.h" +#include #include #include - #include /** @@ -33,7 +35,12 @@ namespace AMQP { class TcpSslHandshake : public TcpState, private Watchable { private: - + /** + * SSL context + * @var SSL_CTX + */ + SSL_CTX *ctx; + /** * SSL structure * @var SSL @@ -53,7 +60,6 @@ private: TcpOutBuffer _out; - /** * Helper method to report an error * @return TcpState* @@ -67,7 +73,7 @@ private: _handler->onError(_connection, "failed to setup ssl connection"); // close the socket - close(_socket); + close(_socket); // done, go to the closed state return new TcpClosed(_connection, _handler); @@ -85,30 +91,6 @@ private: return monitor.valid() ? new TcpClosed(this) : nullptr; } - /** - * Wait until the socket is writable - * @return bool - */ - bool wait4writable() - { - // we need the fd-sets - fd_set readables, writables, exceptions; - - // initialize all the sets - FD_ZERO(&readables); - FD_ZERO(&writables); - FD_ZERO(&exceptions); - - // add the one socket - FD_SET(_socket, &writables); - - // wait for the socket - auto result = select(_socket + 1, &readables, &writables, &exceptions, nullptr); - - // check for success - return result == 0; - } - public: /** * Constructor @@ -126,22 +108,29 @@ public: TcpState(connection, handler), _socket(socket), _out(std::move(buffer)) - { + { + // init the SSL library SSL_library_init(); + // create ssl context + ctx = SSL_CTX_new(TLS_client_method()); + // create ssl object - _ssl = SSL_new(SSL_CTX_new(TLS_client_method())); - + _ssl = SSL_new(ctx); + // leap out on error if (_ssl == nullptr) throw std::runtime_error("ERROR: SSL structure is null"); - + // we will be using the ssl context as a client - // @todo check return value SSL_set_connect_state(_ssl); + // associate the ssl context with the socket filedescriptor - // @todo check return value - SSL_set_fd(_ssl, socket); + int set_fd_ret = SSL_set_fd(_ssl, socket); + if (set_fd_ret == 0) { + reportError(); + std::cout << "error while setting file descriptor" << std::endl; + } // we are going to wait until the socket becomes writable before we start the handshake _handler->monitor(_connection, _socket, writable); @@ -153,7 +142,8 @@ public: virtual ~TcpSslHandshake() noexcept { // close the socket - close(_socket); + // @todo only if really closed + //close(_socket); } /** @@ -175,38 +165,57 @@ public: // start the ssl handshake int result = SSL_do_handshake(_ssl); - + // if the connection succeeds, we can move to the ssl-connected state - // @todo we need the sslconnected state object - if (result == 1) return this; // new TcpSslConnected(connection, socket, _ssl, std::move(_out), _handler); + if (result == 1) return new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler); // if there is a failure, we must close down the connection - if (result == 0) return reportError(); - - // -1 was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); + if (result <= 0) + { + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // the handshake must be repeated when socket is readable, wait for that + std::cout << "wait for readability" << std::endl; + _handler->monitor(_connection, _socket, readable); + break; - // check the error - switch (error) { - case SSL_ERROR_WANT_READ: - // the handshake must be repeated when socket is readable, wait for that - std::cout << "wait for readability" << std::endl; - _handler->monitor(_connection, _socket, readable); - break; - - case SSL_ERROR_WANT_WRITE: - // the handshake must be repeated when socket is readable, wait for that - std::cout << "wait for writability" << std::endl; - _handler->monitor(_connection, _socket, writable); - break; - - default: - // @todo implement handling other error states - std::cout << "unknown error state " << error << std::endl; - // @todo we have to close the connection - return reportError(); + case SSL_ERROR_WANT_WRITE: + // the handshake must be repeated when socket is readable, wait for that + std::cout << "wait for writability" << std::endl; + _handler->monitor(_connection, _socket, writable); + break; + + case SSL_ERROR_WANT_ACCEPT: + // the BIO was not connected yet, the SSL function should be called again + std::cout << "wait for acceptability" << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + _handler->monitor(_connection, _socket, writable); + + break; + case SSL_ERROR_WANT_X509_LOOKUP: + std::cout << "SSL_ERROR_WANT_X509_LOOKUP" << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + _handler->monitor(_connection, _socket, writable); + + break; + case SSL_ERROR_SYSCALL: + std::cout << "SSL_ERROR_SYSCALL: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + _handler->monitor(_connection, _socket, writable); + + break; + case SSL_ERROR_SSL: + std::cout << "SSL_ERROR_SSL" << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + _handler->monitor(_connection, _socket, writable); + + break; + default: + std::cout << "unknown error state " << error << std::endl; + return reportError(); + } } - + // keep same object return this; } @@ -218,9 +227,8 @@ public: */ virtual void send(const char *buffer, size_t size) override { - - // @todo because the handshake is still busy, outgoing data must be cached - + // the handshake is still busy, outgoing data must be cached + _out.add(buffer, size); } /** @@ -229,37 +237,48 @@ public: */ virtual TcpState *flush() override { - // @todo implementation? - return nullptr; - } - - /** - * Report that heartbeat negotiation is going on - * @param heartbeat suggested heartbeat - * @return uint16_t accepted heartbeat - */ - virtual uint16_t reportNegotiate(uint16_t heartbeat) override - { - /* - * @todo what should we do here? + // create an object to wait for the filedescriptor to becomes active + Wait wait(_socket); - // remember that we have to reallocated (_in member can not be accessed because it is moved away) - _reallocate = _connection->maxFrame(); - - // pass to base - return TcpState::reportNegotiate(heartbeat); - */ - - return 0; - } + // keep looping + while (true) + { + // start the ssl handshake + int result = SSL_do_handshake(_ssl); + + // if the connection succeeds, we can move to the ssl-connected state + if (result == 1) return new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler); + + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // wait for the socket to become readable + if (!wait.readable()) return reportError(); + break; + + case SSL_ERROR_WANT_WRITE: + // wait for the socket to become writable + if (!wait.writable()) return reportError(); + break; + + default: + // report an error + return reportError(); + } + } + + // keep same object (we never reach this code) + return this; + } /** * Report to the handler that the connection was nicely closed */ virtual void reportClosed() override { - /* - // we no longer have to monitor the socket _handler->monitor(_connection, _socket, 0); @@ -277,7 +296,6 @@ public: // notify to handler handler->onClosed(_connection); - */ } }; diff --git a/src/linux_tcp/wait.h b/src/linux_tcp/wait.h new file mode 100644 index 0000000..ecd3817 --- /dev/null +++ b/src/linux_tcp/wait.h @@ -0,0 +1,93 @@ +/** + * Wait.h + * + * Class to wait for a socket to become readable and/or writable + * + * @copyright 2018 Copernica BV + */ + + /** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class Wait +{ +private: + /** + * Set with just one filedescriptor + * @var fd_set + */ + fd_set _set; + + /** + * The current socket // @todo what is it exactly? + * @var int + */ + int _socket; + +public: + /** + * Constructor + * @param fd the filedescriptor that we're waiting on + */ + Wait(int fd) + { + _socket = fd; + + // initialize the set + FD_ZERO(&_set); + + // add the one socket + FD_SET(_socket, &_set); + } + + /** + * Destructor + */ + virtual ~Wait() = default; + + /** + * Wait until the filedescriptor becomes readable + * @return bool + */ + bool readable() + { + // wait for the socket + return select(_socket + 1, &_set, nullptr, nullptr, nullptr) > 0; + } + + /** + * Wait until the filedescriptor becomes writable + * @return bool + */ + bool writable() + { + // wait for the socket + return select(_socket + 1, nullptr, &_set, nullptr, nullptr) > 0; + } + + /** + * Wait until a filedescriptor becomes active (readable or writable) + * @return bool + */ + bool active() + { + // wait for the socket + return select(_socket + 1, &_set, &_set, nullptr, nullptr) > 0; + } +}; + +/** + * End of namespace + */ +} + From fa5ef5318abdd1fbf6b283008cf9941eda84d427 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 5 Mar 2018 19:53:53 +0100 Subject: [PATCH 122/168] work in progress on dealing with secure connections --- examples/libev.cpp | 5 +- src/linux_tcp/tcpinbuffer.h | 2 - src/linux_tcp/tcpoutbuffer.h | 10 ++-- src/linux_tcp/tcpresolver.h | 62 ++++++++++++------------ src/linux_tcp/tcpssl.h | 85 ++++++++++++++++++++++++++++++++ src/linux_tcp/tcpsslconnected.h | 25 ---------- src/linux_tcp/tcpsslcontext.h | 86 +++++++++++++++++++++++++++++++++ src/linux_tcp/tcpsslhandshake.h | 48 ++++-------------- 8 files changed, 219 insertions(+), 104 deletions(-) create mode 100644 src/linux_tcp/tcpssl.h create mode 100644 src/linux_tcp/tcpsslcontext.h diff --git a/examples/libev.cpp b/examples/libev.cpp index 3922363..43cad39 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -63,7 +63,10 @@ int main() // handler for libev MyHandler handler(loop); - + + // init the SSL library + SSL_library_init(); + // make a connection AMQP::Address address("amqps://guest:guest@localhost/"); AMQP::TcpConnection connection(&handler, address); diff --git a/src/linux_tcp/tcpinbuffer.h b/src/linux_tcp/tcpinbuffer.h index d7ea09c..4487d82 100644 --- a/src/linux_tcp/tcpinbuffer.h +++ b/src/linux_tcp/tcpinbuffer.h @@ -114,13 +114,11 @@ public: * @param expected number of bytes that the library expects * @return ssize_t */ - /* ssize_t receivefrom(SSL *ssl, uint32_t expected) { // @todo implementation return 0; } - */ /** * Shrink the buffer (in practice this is always called with the full buffer size) diff --git a/src/linux_tcp/tcpoutbuffer.h b/src/linux_tcp/tcpoutbuffer.h index 4364c34..ab9e464 100644 --- a/src/linux_tcp/tcpoutbuffer.h +++ b/src/linux_tcp/tcpoutbuffer.h @@ -5,7 +5,7 @@ * output buffer. This is the implementation of that buffer * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -18,6 +18,7 @@ */ #include #include +#include /** * FIONREAD on Solaris is defined elsewhere @@ -272,7 +273,6 @@ public: * @param ssl the ssl context to send data to * @return ssize_t number of bytes sent, or the return value of ssl_write */ - /* ssize_t sendto(SSL *ssl) { // we're going to fill a lot of buffers (for ssl only one buffer at a time can be sent) @@ -281,22 +281,18 @@ public: // fill the buffers, and leap out if there is no data auto buffers = fill(buffer, 1); - std::cout << "buffercount = " << buffers << std::endl; - + // just to be sure we do this check if (buffers == 0) return 0; // send the data auto result = SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len); - // @todo do we have to move to the next buffer to prevent that this buffer is further filled? - // on success we shrink the buffer if (result > 0) shrink(result); // done return result; } - */ }; /** diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 008bbc6..231e5f8 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -5,7 +5,7 @@ * server, and to make the initial connection * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -20,6 +20,7 @@ #include "tcpstate.h" #include "tcpclosed.h" #include "tcpconnected.h" +#include "tcpsslhandshake.h" #include /** @@ -179,6 +180,31 @@ public: _thread.join(); } + /** + * Proceed to the next state + * @return TcpState * + */ + TcpState *proceed() + { + // do we have a valid socket? + if (_socket >= 0) + { + // if we need a secure connection, we move to the tls handshake + if (_secure) return new TcpSslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); + + // otherwise we have a valid regular tcp connection + return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); + } + else + { + // report error + _handler->onError(_connection, _error.data()); + + // create dummy implementation + return new TcpClosed(_connection, _handler); + } + } + /** * Wait for the resolver to be ready * @param fd The filedescriptor that is active @@ -190,21 +216,8 @@ public: // only works if the incoming pipe is readable if (fd != _pipe.in() || !(flags & readable)) return this; - // do we have a valid socket? - if (_socket >= 0) - { - // if we need a secure connection, we move to the tls handshake - //if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); - - // otherwise we have a valid regular tcp connection - return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); - } - - // report error - _handler->onError(_connection, _error.data()); - - // create dummy implementation - return new TcpClosed(_connection, _handler); + // proceed to the next state + return proceed(); } /** @@ -216,21 +229,8 @@ public: // just wait for the other thread to be ready _thread.join(); - // do we have a valid socket? - if (_socket >= 0) - { - // if we need a secure connection, we move to the tls handshake - //if (_secure) return new TcpSslHandshake(_connection, _socket, std::move(_buffer), _handler); - - // otherwise we have a valid regular tcp connection - return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); - } - - // report error - _handler->onError(_connection, _error.data()); - - // create dummy implementation - return new TcpClosed(_connection, _handler); + // proceed to the next state + return proceed(); } /** diff --git a/src/linux_tcp/tcpssl.h b/src/linux_tcp/tcpssl.h new file mode 100644 index 0000000..6e0d9f3 --- /dev/null +++ b/src/linux_tcp/tcpssl.h @@ -0,0 +1,85 @@ +/** + * TcpSsl.h + * + * Wrapper around a SSL pointer + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class TcpSsl +{ +private: + /** + * The wrapped object + * @var SSL* + */ + SSL *_ssl; + +public: + /** + * Constructor + * @param ctx + */ + TcpSsl(SSL_CTX *ctx) : _ssl(SSL_new(ctx)) + { + // report error + if (_ssl == nullptr) throw std::runtime_error("failed to construct ssl structure"); + } + + /** + * Wrapper constructor + * @param ssl + */ + TcpSsl(SSL *ssl) : _ssl(ssl) + { + // one more reference + // @todo fix this + //CRYPTO_add(_ssl); + } + + /** + * Copy constructor + * @param that + */ + TcpSsl(const TcpSsl &that) : _ssl(that._ssl) + { + // one more reference + // @todo fix this + //SSL_up_ref(_ssl); + } + + /** + * Destructor + */ + virtual ~TcpSsl() + { + // destruct object + SSL_free(_ssl); + } + + /** + * Cast to the SSL* + * @return SSL * + */ + operator SSL * () const { return _ssl; } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/tcpsslconnected.h b/src/linux_tcp/tcpsslconnected.h index 232fb0b..f52c6d0 100644 --- a/src/linux_tcp/tcpsslconnected.h +++ b/src/linux_tcp/tcpsslconnected.h @@ -19,8 +19,6 @@ #include "wait.h" #include -#include - /** * Set up namespace */ @@ -133,26 +131,19 @@ private: // error was returned, so we must investigate what is going on auto error = SSL_get_error(_ssl, result); - std::cout << "error = " << error << std::endl; - // check the error switch (error) { case SSL_ERROR_WANT_READ: // the operation must be repeated when readable - std::cout << "want read" << std::endl; - _handler->monitor(_connection, _socket, readable); return this; case SSL_ERROR_WANT_WRITE: // wait until socket becomes writable again - std::cout << "want write" << std::endl; - _handler->monitor(_connection, _socket, writable); return this; default: - std::cout << "something else" << std::endl; // @todo check how to handle this return this; @@ -177,8 +168,6 @@ public: _in(4096), _state(_out ? state_sending : state_idle) { - std::cout << "ssl-connected" << std::endl; - // tell the handler to monitor the socket if there is an out _handler->monitor(_connection, _socket, _state == state_sending ? writable : readable); } @@ -212,11 +201,6 @@ public: */ virtual TcpState *process(int fd, int flags) { - std::cout << "process call in ssl-connected" << std::endl; - - std::cout << fd << " - " << _socket << std::endl; - - // the socket must be the one this connection writes to if (fd != _socket) return this; @@ -226,13 +210,9 @@ public: // are we busy with sending or receiving data? if (_state == state_sending) { - std::cout << "busy sending" << std::endl; - // try to send more data from the outgoing buffer auto result = _out.sendto(_ssl); - std::cout << "result = " << result << std::endl; - // if this is a success, we may have to update the monitor if (result > 0) return proceed(); @@ -251,13 +231,8 @@ public: // the operation failed, we may have to repeat our call else return repeat(result); - - // we're busy with receiving data // @todo check this - - std::cout << "receive data" << std::endl; - } // keep same object diff --git a/src/linux_tcp/tcpsslcontext.h b/src/linux_tcp/tcpsslcontext.h new file mode 100644 index 0000000..9809b49 --- /dev/null +++ b/src/linux_tcp/tcpsslcontext.h @@ -0,0 +1,86 @@ +/** + * TcpSslContext.h + * + * Class to create and maintain a tcp ssl context + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class TcpSslContext +{ +private: + /** + * The wrapped context + * @var SSL_CTX + */ + SSL_CTX *_ctx; + +public: + /** + * Constructor + * @param method + * @throws std::runtime_error + */ + TcpSslContext(const SSL_METHOD *method) : _ctx(SSL_CTX_new(method)) + { + // report error + if (_ctx == nullptr) throw std::runtime_error("failed to construct ssl context"); + } + + /** + * Constructor that wraps around an existing context + * @param context + */ + TcpSslContext(SSL_CTX *context) : _ctx(context) + { + // increment refcount + // @todo fix this + //SSL_ctx_up_ref(context); + } + + /** + * Copy constructor + * @param that + */ + TcpSslContext(TcpSslContext &that) : _ctx(that._ctx) + { + // increment refcount + // @todo fix this + //SSL_ctx_up_ref(context); + } + + /** + * Destructor + */ + virtual ~TcpSslContext() + { + // free resource (this updates the refcount -1, and may destruct it) + SSL_CTX_free(_ctx); + } + + /** + * Cast to the actual context + * @return SSL_CTX * + */ + operator SSL_CTX * () { return _ctx; } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/tcpsslhandshake.h b/src/linux_tcp/tcpsslhandshake.h index eccb860..7943a67 100644 --- a/src/linux_tcp/tcpsslhandshake.h +++ b/src/linux_tcp/tcpsslhandshake.h @@ -18,11 +18,8 @@ #include "tcpoutbuffer.h" #include "tcpsslconnected.h" #include "wait.h" - -#include -#include -#include -#include +#include "tcpssl.h" +#include "tcpsslcontext.h" /** * Set up namespace @@ -35,17 +32,11 @@ namespace AMQP { class TcpSslHandshake : public TcpState, private Watchable { private: - /** - * SSL context - * @var SSL_CTX - */ - SSL_CTX *ctx; - /** * SSL structure * @var SSL */ - SSL *_ssl; + TcpSsl _ssl; /** * The socket file descriptor @@ -99,38 +90,26 @@ public: * * @param connection Parent TCP connection object * @param socket The socket filedescriptor + * @param hostname The hostname to connect to * @param context SSL context * @param buffer The buffer that was already built * @param handler User-supplied handler object * @throws std::runtime_error */ - TcpSslHandshake(TcpConnection *connection, int socket, TcpOutBuffer &&buffer, TcpHandler *handler) : + TcpSslHandshake(TcpConnection *connection, int socket, const std::string &hostname, TcpOutBuffer &&buffer, TcpHandler *handler) : TcpState(connection, handler), + _ssl(TcpSslContext(SSLv23_client_method())), _socket(socket), _out(std::move(buffer)) { - // init the SSL library - SSL_library_init(); - - // create ssl context - ctx = SSL_CTX_new(TLS_client_method()); - - // create ssl object - _ssl = SSL_new(ctx); - - // leap out on error - if (_ssl == nullptr) throw std::runtime_error("ERROR: SSL structure is null"); - // we will be using the ssl context as a client SSL_set_connect_state(_ssl); - + + // associate domain name with the connection + SSL_set_tlsext_host_name(_ssl, hostname.data()); // associate the ssl context with the socket filedescriptor - int set_fd_ret = SSL_set_fd(_ssl, socket); - if (set_fd_ret == 0) { - reportError(); - std::cout << "error while setting file descriptor" << std::endl; - } + if (SSL_set_fd(_ssl, socket) == 0) throw std::runtime_error("failed to associate filedescriptor with ssl socket"); // we are going to wait until the socket becomes writable before we start the handshake _handler->monitor(_connection, _socket, writable); @@ -179,39 +158,32 @@ public: switch (error) { case SSL_ERROR_WANT_READ: // the handshake must be repeated when socket is readable, wait for that - std::cout << "wait for readability" << std::endl; _handler->monitor(_connection, _socket, readable); break; case SSL_ERROR_WANT_WRITE: // the handshake must be repeated when socket is readable, wait for that - std::cout << "wait for writability" << std::endl; _handler->monitor(_connection, _socket, writable); break; case SSL_ERROR_WANT_ACCEPT: // the BIO was not connected yet, the SSL function should be called again - std::cout << "wait for acceptability" << ERR_error_string(ERR_get_error(), nullptr) << std::endl; _handler->monitor(_connection, _socket, writable); break; case SSL_ERROR_WANT_X509_LOOKUP: - std::cout << "SSL_ERROR_WANT_X509_LOOKUP" << ERR_error_string(ERR_get_error(), nullptr) << std::endl; _handler->monitor(_connection, _socket, writable); break; case SSL_ERROR_SYSCALL: - std::cout << "SSL_ERROR_SYSCALL: " << ERR_error_string(ERR_get_error(), nullptr) << std::endl; _handler->monitor(_connection, _socket, writable); break; case SSL_ERROR_SSL: - std::cout << "SSL_ERROR_SSL" << ERR_error_string(ERR_get_error(), nullptr) << std::endl; _handler->monitor(_connection, _socket, writable); break; default: - std::cout << "unknown error state " << error << std::endl; return reportError(); } } From 4a51c5dd60fca9107f5741049d3b0f24cf209b5a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 5 Mar 2018 20:08:09 +0100 Subject: [PATCH 123/168] improve ssl handshake methods --- src/linux_tcp/tcpsslhandshake.h | 119 +++++++++++++------------------- 1 file changed, 47 insertions(+), 72 deletions(-) diff --git a/src/linux_tcp/tcpsslhandshake.h b/src/linux_tcp/tcpsslhandshake.h index 7943a67..525cec5 100644 --- a/src/linux_tcp/tcpsslhandshake.h +++ b/src/linux_tcp/tcpsslhandshake.h @@ -51,6 +51,20 @@ private: TcpOutBuffer _out; + /** + * Report a new state + * @param state + * @return TcpState + */ + TcpState *nextstate(TcpState *state) + { + // forget the socket to prevent that it is closed by the destructor + _socket = -1; + + // done + return state; + } + /** * Helper method to report an error * @return TcpState* @@ -63,23 +77,22 @@ private: // we have an error - report this to the user _handler->onError(_connection, "failed to setup ssl connection"); - // close the socket - close(_socket); - // done, go to the closed state return new TcpClosed(_connection, _handler); } /** - * Construct the next state - * @param monitor Object that monitors whether connection still exists - * @return TcpState* + * Proceed with the handshake + * @param events the events to wait for on the socket + * @return TcpState */ - TcpState *nextState(const Monitor &monitor) + TcpState *proceed(int events) { - // if the object is still in a valid state, we can move to the close-state, - // otherwise there is no point in moving to a next state - return monitor.valid() ? new TcpClosed(this) : nullptr; + // tell the handler that we want to listen for certain events + _handler->monitor(_connection, _socket, events); + + // allow chaining + return this; } public: @@ -120,9 +133,11 @@ public: */ virtual ~TcpSslHandshake() noexcept { + // leap out if socket is invalidated + if (_socket < 0) return; + // close the socket - // @todo only if really closed - //close(_socket); + close(_socket); } /** @@ -146,50 +161,17 @@ public: int result = SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler); + if (result == 1) return nextstate(new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); - // if there is a failure, we must close down the connection - if (result <= 0) - { - // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); - // check the error - switch (error) { - case SSL_ERROR_WANT_READ: - // the handshake must be repeated when socket is readable, wait for that - _handler->monitor(_connection, _socket, readable); - break; - - case SSL_ERROR_WANT_WRITE: - // the handshake must be repeated when socket is readable, wait for that - _handler->monitor(_connection, _socket, writable); - break; - - case SSL_ERROR_WANT_ACCEPT: - // the BIO was not connected yet, the SSL function should be called again - _handler->monitor(_connection, _socket, writable); - - break; - case SSL_ERROR_WANT_X509_LOOKUP: - _handler->monitor(_connection, _socket, writable); - - break; - case SSL_ERROR_SYSCALL: - _handler->monitor(_connection, _socket, writable); - - break; - case SSL_ERROR_SSL: - _handler->monitor(_connection, _socket, writable); - - break; - default: - return reportError(); - } - } - - // keep same object - return this; + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: return proceed(readable); + case SSL_ERROR_WANT_WRITE: return proceed(readable | writable); + default: return reportError(); + } } /** @@ -219,31 +201,23 @@ public: int result = SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler); + if (result == 1) return nextstate(new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); // error was returned, so we must investigate what is going on auto error = SSL_get_error(_ssl, result); // check the error - switch (error) { - case SSL_ERROR_WANT_READ: - // wait for the socket to become readable - if (!wait.readable()) return reportError(); - break; - - case SSL_ERROR_WANT_WRITE: - // wait for the socket to become writable - if (!wait.writable()) return reportError(); - break; - - default: - // report an error - return reportError(); + switch (error) + { + // if openssl reports that socket readability or writability is needed, + // we wait for that until this situation is reached + case SSL_ERROR_WANT_READ: wait.readable(); break; + case SSL_ERROR_WANT_WRITE: wait.active(); break; + + // something is wrong, we proceed to the next state + default: return reportError(); } } - - // keep same object (we never reach this code) - return this; } /** @@ -275,3 +249,4 @@ public: * End of namespace */ } + From 463eed89c0c3691db2978eecde688d7d11ceb198 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 5 Mar 2018 22:24:19 +0100 Subject: [PATCH 124/168] work in progress on closing ssl connections --- include/amqpcpp/linux_tcp/tcpconnection.h | 1 + include/amqpcpp/monitor.h | 20 ++- src/linux_tcp/tcpconnected.h | 7 +- src/linux_tcp/tcpsslclose.h | 161 ++++++++++++++++++++++ src/linux_tcp/tcpsslconnected.h | 135 ++++++++++++++---- 5 files changed, 293 insertions(+), 31 deletions(-) create mode 100644 src/linux_tcp/tcpsslclose.h diff --git a/include/amqpcpp/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h index 681ecd5..a641576 100644 --- a/include/amqpcpp/linux_tcp/tcpconnection.h +++ b/include/amqpcpp/linux_tcp/tcpconnection.h @@ -100,6 +100,7 @@ private: /** * Classes that have access to private data */ + friend class TcpSslConnected; friend class TcpConnected; friend class TcpChannel; diff --git a/include/amqpcpp/monitor.h b/include/amqpcpp/monitor.h index 59564f9..efb788c 100644 --- a/include/amqpcpp/monitor.h +++ b/include/amqpcpp/monitor.h @@ -8,7 +8,7 @@ * 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 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -76,6 +76,24 @@ public: 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 diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index c523183..f710a05 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -180,7 +180,10 @@ public: _in = std::move(buffer); // do we have to reallocate? - if (_reallocate) { _in.reallocate(_reallocate); _reallocate = 0; } + if (_reallocate) _in.reallocate(_reallocate); + + // we can remove the reallocate instruction + _reallocate = 0; } // keep same object @@ -246,7 +249,7 @@ public: */ virtual uint16_t reportNegotiate(uint16_t heartbeat) override { - // remember that we have to reallocated (_in member can not be accessed because it is moved away) + // remember that we have to reallocate (_in member can not be accessed because it is moved away) _reallocate = _connection->maxFrame(); // pass to base diff --git a/src/linux_tcp/tcpsslclose.h b/src/linux_tcp/tcpsslclose.h new file mode 100644 index 0000000..e996c63 --- /dev/null +++ b/src/linux_tcp/tcpsslclose.h @@ -0,0 +1,161 @@ +/** + * TcpSslClose.h + * + * Class that takes care of the final handshake to close a SSL connection + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class TcpSslClose : public TcpState, private Watchable +{ +private: + /** + * The SSL context + * @var TcpSsl + */ + TcpSsl _ssl; + + /** + * Socket file descriptor + * @var int + */ + int _socket; + + + /** + * Proceed with the next operation after the previous operation was + * a success, possibly changing the filedescriptor-monitor + * @return TcpState* + */ + TcpState *proceed() + { + // construct monitor to prevent that we access members if object is destructed + Monitor monitor(this); + + // we're no longer interested in events + _handler->monitor(_connection, _socket, 0); + + // stop if object was destructed + if (!monitor) return nullptr; + + // close the socket + close(_socket); + + // forget the socket + _socket = -1; + + // go to the closed state + return new TcpClosed(_connection, _handler); + } + + /** + * Method to repeat the previous call + * @param result result of an earlier openssl operation + * @return TcpState* + */ + TcpState *repeat(int result) + { + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // the operation must be repeated when readable + _handler->monitor(_connection, _socket, readable); + return this; + + case SSL_ERROR_WANT_WRITE: + // wait until socket becomes writable again + _handler->monitor(_connection, _socket, readable | writable); + return this; + + default: + + // @todo check how to handle this + return this; + } + } + + +public: + /** + * Constructor + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param ssl The SSL structure + * @param handler User-supplied handler object + */ + TcpSslClose(TcpConnection *connection, int socket, const TcpSsl &ssl, TcpHandler *handler) : + TcpState(connection, handler), + _ssl(ssl), + _socket(socket) + { + // tell the handler to monitor the socket if there is an out + _handler->monitor(_connection, _socket, readable); + } + + /** + * Destructor + */ + virtual ~TcpSslClose() noexcept + { + // skip if handler is already forgotten + if (_handler == nullptr) return; + + // we no longer have to monitor the socket + _handler->monitor(_connection, _socket, 0); + + // close the socket + close(_socket); + } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } + + /** + * Process the filedescriptor in the object + * @param fd The filedescriptor that is active + * @param flags AMQP::readable and/or AMQP::writable + * @return New implementation object + */ + virtual TcpState *process(int fd, int flags) + { + // the socket must be the one this connection writes to + if (fd != _socket) return this; + + // because the object might soon be destructed, we create a monitor to check this + Monitor monitor(this); + + // close the connection + auto result = SSL_shutdown(_ssl); + + // if this is a success, we can proceed with the event loop + if (result > 0) return proceed(); + + // the operation failed, we may have to repeat our call + else return repeat(result); + } +}; + +/** + * End of namespace + */ +} \ No newline at end of file diff --git a/src/linux_tcp/tcpsslconnected.h b/src/linux_tcp/tcpsslconnected.h index f52c6d0..8ed4b55 100644 --- a/src/linux_tcp/tcpsslconnected.h +++ b/src/linux_tcp/tcpsslconnected.h @@ -17,7 +17,8 @@ #include "tcpoutbuffer.h" #include "tcpinbuffer.h" #include "wait.h" -#include +#include "tcpssl.h" +#include "tcpsslclose.h" /** * Set up namespace @@ -27,14 +28,14 @@ namespace AMQP { /** * Class definition */ -class TcpSslConnected: public TcpState, private Watchable +class TcpSslConnected : public TcpState, private Watchable { private: /** * The SSL context - * @var SSL* + * @var TcpSsl */ - SSL *_ssl; + TcpSsl _ssl; /** * Socket file descriptor @@ -63,6 +64,18 @@ private: state_sending, state_receiving } _state; + + /** + * Is the object already closed? + * @var bool + */ + bool _closed = false; + + /** + * Cached reallocation instruction + * @var size_t + */ + size_t _reallocate = 0; /** @@ -71,9 +84,6 @@ private: */ bool reportError() { - // some errors are ok and do not (necessarily) mean that we're disconnected - if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false; - // we have an error - report this to the user _handler->onError(_connection, strerror(errno)); @@ -94,7 +104,8 @@ private: } /** - * Proceed with the previous operation, possibly changing the monitor + * Proceed with the next operation after the previous operation was + * a success, possibly changing the filedescriptor-monitor * @return TcpState* */ TcpState *proceed() @@ -106,8 +117,16 @@ private: _state = state_sending; // let's wait until the socket becomes writable - _handler->monitor(_connection, _socket, readable); + _handler->monitor(_connection, _socket, readable | writable); } + else if (_closed) + { + // we forget the current handler to prevent that things are changed + _handler = nullptr; + + // start the state that closes the connection + return new TcpSslClose(_connection, _socket, _ssl, _handler); + } else { // outgoing buffer is empty, we're idle again waiting for further input @@ -140,7 +159,7 @@ private: case SSL_ERROR_WANT_WRITE: // wait until socket becomes writable again - _handler->monitor(_connection, _socket, writable); + _handler->monitor(_connection, _socket, readable | writable); return this; default: @@ -149,18 +168,53 @@ private: return this; } } - + + /** + * Parse the received buffer + * @param size + * @return TcpState + */ + TcpState *parse(size_t size) + { + // we need a local copy of the buffer - because it is possible that "this" + // object gets destructed halfway through the call to the parse() method + TcpInBuffer buffer(std::move(_in)); + + // because the object might soon be destructed, we create a monitor to check this + Monitor monitor(this); + + // parse the buffer + auto processed = _connection->parse(buffer); + + // "this" could be removed by now, check this + if (!monitor.valid()) return nullptr; + + // shrink buffer + buffer.shrink(processed); + + // restore the buffer as member + _in = std::move(buffer); + + // do we have to reallocate? + if (_reallocate) _in.reallocate(_reallocate); + + // we can remove the reallocate instruction + _reallocate = 0; + + // done + return this; + } public: /** - * Constructor - * @param connection Parent TCP connection object - * @param socket The socket filedescriptor - * @param ssl The SSL structure - * @param buffer The buffer that was already built - * @param handler User-supplied handler object + * Constructor + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param ssl The SSL structure + * @param buffer The buffer that was already built + * @param handler User-supplied handler object */ - TcpSslConnected(TcpConnection *connection, int socket, SSL *ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : + TcpSslConnected(TcpConnection *connection, int socket, const TcpSsl &ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : TcpState(connection, handler), _ssl(ssl), _socket(socket), @@ -213,7 +267,7 @@ public: // try to send more data from the outgoing buffer auto result = _out.sendto(_ssl); - // if this is a success, we may have to update the monitor + // if this is a success, we can proceed with the event loop if (result > 0) return proceed(); // the operation failed, we may have to repeat our call @@ -225,18 +279,11 @@ public: auto result = _in.receivefrom(_ssl, _connection->expected()); // if this is a success, we may have to update the monitor - // @todo also parse the buffer - if (result > 0) return proceed(); + if (result > 0) return parse(result); // the operation failed, we may have to repeat our call else return repeat(result); - - // we're busy with receiving data - // @todo check this } - - // keep same object - return this; } /** @@ -253,8 +300,40 @@ public: // for that operation to complete before we can move on if (_state != state_idle) return; + // object is now busy sending + _state = state_sending; + // let's wait until the socket becomes writable - _handler->monitor(_connection, _socket, writable); + _handler->monitor(_connection, _socket, readable | writable); + } + + /** + * Report that heartbeat negotiation is going on + * @param heartbeat suggested heartbeat + * @return uint16_t accepted heartbeat + */ + virtual uint16_t reportNegotiate(uint16_t heartbeat) override + { + // remember that we have to reallocate (_in member can not be accessed because it is moved away) + _reallocate = _connection->maxFrame(); + + // pass to base + return TcpState::reportNegotiate(heartbeat); + } + + /** + * Report to the handler that the connection was nicely closed + */ + virtual void reportClosed() override + { + // remember that the object is closed + _closed = true; + + // if the previous operation is still in progress + if (_state != state_idle) return; + + // wait until the connection is writable + _handler->monitor(_connection, _socket, writable); } }; From 72ffd5eb1adf2557548982ed533bb4397f1c971f Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 5 Mar 2018 22:25:58 +0100 Subject: [PATCH 125/168] disabled ssl for the time being --- src/linux_tcp/tcpresolver.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 231e5f8..70f85d7 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -20,7 +20,7 @@ #include "tcpstate.h" #include "tcpclosed.h" #include "tcpconnected.h" -#include "tcpsslhandshake.h" +//#include "tcpsslhandshake.h" #include /** @@ -190,7 +190,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - if (_secure) return new TcpSslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); + //if (_secure) return new TcpSslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); From d52dea795d0671664244e4fe6733f24ccd4e76e9 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Mon, 5 Mar 2018 22:27:08 +0100 Subject: [PATCH 126/168] fix compile error for libev example --- examples/.gitignore | 1 + examples/libev.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 examples/.gitignore diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..cba7efc --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +a.out diff --git a/examples/libev.cpp b/examples/libev.cpp index 43cad39..0600d92 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -65,7 +65,7 @@ int main() MyHandler handler(loop); // init the SSL library - SSL_library_init(); +// SSL_library_init(); // make a connection AMQP::Address address("amqps://guest:guest@localhost/"); From 69c615d0a4912f8750772401946219f3de84eeb8 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 08:39:42 +0100 Subject: [PATCH 127/168] removed tcp prefix from all ssl related classes --- include/amqpcpp/linux_tcp/tcpconnection.h | 2 +- src/linux_tcp/tcpresolver.h | 4 +- src/linux_tcp/tcpssl.h | 85 ------ src/linux_tcp/tcpsslclose.h | 161 ---------- src/linux_tcp/tcpsslconnected.h | 344 ---------------------- src/linux_tcp/tcpsslcontext.h | 86 ------ src/linux_tcp/tcpsslhandshake.h | 252 ---------------- 7 files changed, 3 insertions(+), 931 deletions(-) delete mode 100644 src/linux_tcp/tcpssl.h delete mode 100644 src/linux_tcp/tcpsslclose.h delete mode 100644 src/linux_tcp/tcpsslconnected.h delete mode 100644 src/linux_tcp/tcpsslcontext.h delete mode 100644 src/linux_tcp/tcpsslhandshake.h diff --git a/include/amqpcpp/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h index a641576..f7d3212 100644 --- a/include/amqpcpp/linux_tcp/tcpconnection.h +++ b/include/amqpcpp/linux_tcp/tcpconnection.h @@ -100,7 +100,7 @@ private: /** * Classes that have access to private data */ - friend class TcpSslConnected; + friend class SslConnected; friend class TcpConnected; friend class TcpChannel; diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 70f85d7..3c86afd 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -20,7 +20,7 @@ #include "tcpstate.h" #include "tcpclosed.h" #include "tcpconnected.h" -//#include "tcpsslhandshake.h" +//#include "sslhandshake.h" #include /** @@ -190,7 +190,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - //if (_secure) return new TcpSslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); + //if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); diff --git a/src/linux_tcp/tcpssl.h b/src/linux_tcp/tcpssl.h deleted file mode 100644 index 6e0d9f3..0000000 --- a/src/linux_tcp/tcpssl.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - * TcpSsl.h - * - * Wrapper around a SSL pointer - * - * @author Emiel Bruijntjes - * @copyright 2018 Copernica BV - */ - -/** - * Include guard - */ -#pragma once - -/** - * Begin of namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class TcpSsl -{ -private: - /** - * The wrapped object - * @var SSL* - */ - SSL *_ssl; - -public: - /** - * Constructor - * @param ctx - */ - TcpSsl(SSL_CTX *ctx) : _ssl(SSL_new(ctx)) - { - // report error - if (_ssl == nullptr) throw std::runtime_error("failed to construct ssl structure"); - } - - /** - * Wrapper constructor - * @param ssl - */ - TcpSsl(SSL *ssl) : _ssl(ssl) - { - // one more reference - // @todo fix this - //CRYPTO_add(_ssl); - } - - /** - * Copy constructor - * @param that - */ - TcpSsl(const TcpSsl &that) : _ssl(that._ssl) - { - // one more reference - // @todo fix this - //SSL_up_ref(_ssl); - } - - /** - * Destructor - */ - virtual ~TcpSsl() - { - // destruct object - SSL_free(_ssl); - } - - /** - * Cast to the SSL* - * @return SSL * - */ - operator SSL * () const { return _ssl; } -}; - -/** - * End of namespace - */ -} - diff --git a/src/linux_tcp/tcpsslclose.h b/src/linux_tcp/tcpsslclose.h deleted file mode 100644 index e996c63..0000000 --- a/src/linux_tcp/tcpsslclose.h +++ /dev/null @@ -1,161 +0,0 @@ -/** - * TcpSslClose.h - * - * Class that takes care of the final handshake to close a SSL connection - * - * @author Emiel Bruijntjes - * @copyright 2018 Copernica BV - */ - -/** - * Include guard - */ -#pragma once - -/** - * Begin of namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class TcpSslClose : public TcpState, private Watchable -{ -private: - /** - * The SSL context - * @var TcpSsl - */ - TcpSsl _ssl; - - /** - * Socket file descriptor - * @var int - */ - int _socket; - - - /** - * Proceed with the next operation after the previous operation was - * a success, possibly changing the filedescriptor-monitor - * @return TcpState* - */ - TcpState *proceed() - { - // construct monitor to prevent that we access members if object is destructed - Monitor monitor(this); - - // we're no longer interested in events - _handler->monitor(_connection, _socket, 0); - - // stop if object was destructed - if (!monitor) return nullptr; - - // close the socket - close(_socket); - - // forget the socket - _socket = -1; - - // go to the closed state - return new TcpClosed(_connection, _handler); - } - - /** - * Method to repeat the previous call - * @param result result of an earlier openssl operation - * @return TcpState* - */ - TcpState *repeat(int result) - { - // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); - - // check the error - switch (error) { - case SSL_ERROR_WANT_READ: - // the operation must be repeated when readable - _handler->monitor(_connection, _socket, readable); - return this; - - case SSL_ERROR_WANT_WRITE: - // wait until socket becomes writable again - _handler->monitor(_connection, _socket, readable | writable); - return this; - - default: - - // @todo check how to handle this - return this; - } - } - - -public: - /** - * Constructor - * @param connection Parent TCP connection object - * @param socket The socket filedescriptor - * @param ssl The SSL structure - * @param handler User-supplied handler object - */ - TcpSslClose(TcpConnection *connection, int socket, const TcpSsl &ssl, TcpHandler *handler) : - TcpState(connection, handler), - _ssl(ssl), - _socket(socket) - { - // tell the handler to monitor the socket if there is an out - _handler->monitor(_connection, _socket, readable); - } - - /** - * Destructor - */ - virtual ~TcpSslClose() noexcept - { - // skip if handler is already forgotten - if (_handler == nullptr) return; - - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - - // close the socket - close(_socket); - } - - /** - * The filedescriptor of this connection - * @return int - */ - virtual int fileno() const override { return _socket; } - - /** - * Process the filedescriptor in the object - * @param fd The filedescriptor that is active - * @param flags AMQP::readable and/or AMQP::writable - * @return New implementation object - */ - virtual TcpState *process(int fd, int flags) - { - // the socket must be the one this connection writes to - if (fd != _socket) return this; - - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - - // close the connection - auto result = SSL_shutdown(_ssl); - - // if this is a success, we can proceed with the event loop - if (result > 0) return proceed(); - - // the operation failed, we may have to repeat our call - else return repeat(result); - } -}; - -/** - * End of namespace - */ -} \ No newline at end of file diff --git a/src/linux_tcp/tcpsslconnected.h b/src/linux_tcp/tcpsslconnected.h deleted file mode 100644 index 8ed4b55..0000000 --- a/src/linux_tcp/tcpsslconnected.h +++ /dev/null @@ -1,344 +0,0 @@ -/** - * TcpSslConnected.h - * - * The actual tcp connection over SSL - * - * @copyright 2018 copernica BV - */ - -/** - * Include guard - */ -#pragma once - -/** - * Dependencies - */ -#include "tcpoutbuffer.h" -#include "tcpinbuffer.h" -#include "wait.h" -#include "tcpssl.h" -#include "tcpsslclose.h" - -/** - * Set up namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class TcpSslConnected : public TcpState, private Watchable -{ -private: - /** - * The SSL context - * @var TcpSsl - */ - TcpSsl _ssl; - - /** - * Socket file descriptor - * @var int - */ - int _socket; - - /** - * The outgoing buffer - * @var TcpBuffer - */ - TcpOutBuffer _out; - - /** - * The incoming buffer - * @var TcpInBuffer - */ - TcpInBuffer _in; - - /** - * Are we now busy with sending or receiving? - * @var enum - */ - enum { - state_idle, - state_sending, - state_receiving - } _state; - - /** - * Is the object already closed? - * @var bool - */ - bool _closed = false; - - /** - * Cached reallocation instruction - * @var size_t - */ - size_t _reallocate = 0; - - - /** - * Helper method to report an error - * @return bool Was an error reported? - */ - bool reportError() - { - // we have an error - report this to the user - _handler->onError(_connection, strerror(errno)); - - // done - return true; - } - - /** - * Construct the next state - * @param monitor Object that monitors whether connection still exists - * @return TcpState* - */ - TcpState *nextState(const Monitor &monitor) - { - // if the object is still in a valid state, we can move to the close-state, - // otherwise there is no point in moving to a next state - return monitor.valid() ? new TcpClosed(this) : nullptr; - } - - /** - * Proceed with the next operation after the previous operation was - * a success, possibly changing the filedescriptor-monitor - * @return TcpState* - */ - TcpState *proceed() - { - // if we still have an outgoing buffer we want to send out data - if (_out) - { - // we still have a buffer with outgoing data - _state = state_sending; - - // let's wait until the socket becomes writable - _handler->monitor(_connection, _socket, readable | writable); - } - else if (_closed) - { - // we forget the current handler to prevent that things are changed - _handler = nullptr; - - // start the state that closes the connection - return new TcpSslClose(_connection, _socket, _ssl, _handler); - } - else - { - // outgoing buffer is empty, we're idle again waiting for further input - _state = state_idle; - - // let's wait until the socket becomes readable - _handler->monitor(_connection, _socket, readable); - } - - // done - return this; - } - - /** - * Method to repeat the previous call - * @param result result of an earlier openssl operation - * @return TcpState* - */ - TcpState *repeat(int result) - { - // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); - - // check the error - switch (error) { - case SSL_ERROR_WANT_READ: - // the operation must be repeated when readable - _handler->monitor(_connection, _socket, readable); - return this; - - case SSL_ERROR_WANT_WRITE: - // wait until socket becomes writable again - _handler->monitor(_connection, _socket, readable | writable); - return this; - - default: - - // @todo check how to handle this - return this; - } - } - - /** - * Parse the received buffer - * @param size - * @return TcpState - */ - TcpState *parse(size_t size) - { - // we need a local copy of the buffer - because it is possible that "this" - // object gets destructed halfway through the call to the parse() method - TcpInBuffer buffer(std::move(_in)); - - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - - // parse the buffer - auto processed = _connection->parse(buffer); - - // "this" could be removed by now, check this - if (!monitor.valid()) return nullptr; - - // shrink buffer - buffer.shrink(processed); - - // restore the buffer as member - _in = std::move(buffer); - - // do we have to reallocate? - if (_reallocate) _in.reallocate(_reallocate); - - // we can remove the reallocate instruction - _reallocate = 0; - - // done - return this; - } - -public: - /** - * Constructor - * @param connection Parent TCP connection object - * @param socket The socket filedescriptor - * @param ssl The SSL structure - * @param buffer The buffer that was already built - * @param handler User-supplied handler object - */ - TcpSslConnected(TcpConnection *connection, int socket, const TcpSsl &ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : - TcpState(connection, handler), - _ssl(ssl), - _socket(socket), - _out(std::move(buffer)), - _in(4096), - _state(_out ? state_sending : state_idle) - { - // tell the handler to monitor the socket if there is an out - _handler->monitor(_connection, _socket, _state == state_sending ? writable : readable); - } - - /** - * Destructor - */ - virtual ~TcpSslConnected() noexcept - { - // skip if handler is already forgotten - if (_handler == nullptr) return; - - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - - // close the socket - close(_socket); - } - - /** - * The filedescriptor of this connection - * @return int - */ - virtual int fileno() const override { return _socket; } - - /** - * Process the filedescriptor in the object - * @param fd The filedescriptor that is active - * @param flags AMQP::readable and/or AMQP::writable - * @return New implementation object - */ - virtual TcpState *process(int fd, int flags) - { - // the socket must be the one this connection writes to - if (fd != _socket) return this; - - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - - // are we busy with sending or receiving data? - if (_state == state_sending) - { - // try to send more data from the outgoing buffer - auto result = _out.sendto(_ssl); - - // if this is a success, we can proceed with the event loop - if (result > 0) return proceed(); - - // the operation failed, we may have to repeat our call - else return repeat(result); - } - else - { - // read data from ssl into the buffer - auto result = _in.receivefrom(_ssl, _connection->expected()); - - // if this is a success, we may have to update the monitor - if (result > 0) return parse(result); - - // the operation failed, we may have to repeat our call - else return repeat(result); - } - } - - /** - * Send data over the connection - * @param buffer buffer to send - * @param size size of the buffer - */ - virtual void send(const char *buffer, size_t size) - { - // put the data in the outgoing buffer - _out.add(buffer, size); - - // if we're already busy with sending or receiving, we first have to wait - // for that operation to complete before we can move on - if (_state != state_idle) return; - - // object is now busy sending - _state = state_sending; - - // let's wait until the socket becomes writable - _handler->monitor(_connection, _socket, readable | writable); - } - - /** - * Report that heartbeat negotiation is going on - * @param heartbeat suggested heartbeat - * @return uint16_t accepted heartbeat - */ - virtual uint16_t reportNegotiate(uint16_t heartbeat) override - { - // remember that we have to reallocate (_in member can not be accessed because it is moved away) - _reallocate = _connection->maxFrame(); - - // pass to base - return TcpState::reportNegotiate(heartbeat); - } - - /** - * Report to the handler that the connection was nicely closed - */ - virtual void reportClosed() override - { - // remember that the object is closed - _closed = true; - - // if the previous operation is still in progress - if (_state != state_idle) return; - - // wait until the connection is writable - _handler->monitor(_connection, _socket, writable); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/linux_tcp/tcpsslcontext.h b/src/linux_tcp/tcpsslcontext.h deleted file mode 100644 index 9809b49..0000000 --- a/src/linux_tcp/tcpsslcontext.h +++ /dev/null @@ -1,86 +0,0 @@ -/** - * TcpSslContext.h - * - * Class to create and maintain a tcp ssl context - * - * @author Emiel Bruijntjes - * @copyright 2018 Copernica BV - */ - -/** - * Include guard - */ -#pragma once - -/** - * Begin of namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class TcpSslContext -{ -private: - /** - * The wrapped context - * @var SSL_CTX - */ - SSL_CTX *_ctx; - -public: - /** - * Constructor - * @param method - * @throws std::runtime_error - */ - TcpSslContext(const SSL_METHOD *method) : _ctx(SSL_CTX_new(method)) - { - // report error - if (_ctx == nullptr) throw std::runtime_error("failed to construct ssl context"); - } - - /** - * Constructor that wraps around an existing context - * @param context - */ - TcpSslContext(SSL_CTX *context) : _ctx(context) - { - // increment refcount - // @todo fix this - //SSL_ctx_up_ref(context); - } - - /** - * Copy constructor - * @param that - */ - TcpSslContext(TcpSslContext &that) : _ctx(that._ctx) - { - // increment refcount - // @todo fix this - //SSL_ctx_up_ref(context); - } - - /** - * Destructor - */ - virtual ~TcpSslContext() - { - // free resource (this updates the refcount -1, and may destruct it) - SSL_CTX_free(_ctx); - } - - /** - * Cast to the actual context - * @return SSL_CTX * - */ - operator SSL_CTX * () { return _ctx; } -}; - -/** - * End of namespace - */ -} - diff --git a/src/linux_tcp/tcpsslhandshake.h b/src/linux_tcp/tcpsslhandshake.h deleted file mode 100644 index 525cec5..0000000 --- a/src/linux_tcp/tcpsslhandshake.h +++ /dev/null @@ -1,252 +0,0 @@ -/** - * TcpSslHandshake.h - * - * Implementation of the TCP state that is responsible for setting - * up the STARTTLS handshake. - * - * @copyright 2018 Copernica BV - */ - -/** - * Include guard - */ -#pragma once - -/** - * Dependencies - */ -#include "tcpoutbuffer.h" -#include "tcpsslconnected.h" -#include "wait.h" -#include "tcpssl.h" -#include "tcpsslcontext.h" - -/** - * Set up namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class TcpSslHandshake : public TcpState, private Watchable -{ -private: - /** - * SSL structure - * @var SSL - */ - TcpSsl _ssl; - - /** - * The socket file descriptor - * @var int - */ - int _socket; - - /** - * The outgoing buffer - * @var TcpOutBuffer - */ - TcpOutBuffer _out; - - - /** - * Report a new state - * @param state - * @return TcpState - */ - TcpState *nextstate(TcpState *state) - { - // forget the socket to prevent that it is closed by the destructor - _socket = -1; - - // done - return state; - } - - /** - * Helper method to report an error - * @return TcpState* - */ - TcpState *reportError() - { - // we are no longer interested in any events for this socket - _handler->monitor(_connection, _socket, 0); - - // we have an error - report this to the user - _handler->onError(_connection, "failed to setup ssl connection"); - - // done, go to the closed state - return new TcpClosed(_connection, _handler); - } - - /** - * Proceed with the handshake - * @param events the events to wait for on the socket - * @return TcpState - */ - TcpState *proceed(int events) - { - // tell the handler that we want to listen for certain events - _handler->monitor(_connection, _socket, events); - - // allow chaining - return this; - } - -public: - /** - * Constructor - * - * @todo catch the exception! - * - * @param connection Parent TCP connection object - * @param socket The socket filedescriptor - * @param hostname The hostname to connect to - * @param context SSL context - * @param buffer The buffer that was already built - * @param handler User-supplied handler object - * @throws std::runtime_error - */ - TcpSslHandshake(TcpConnection *connection, int socket, const std::string &hostname, TcpOutBuffer &&buffer, TcpHandler *handler) : - TcpState(connection, handler), - _ssl(TcpSslContext(SSLv23_client_method())), - _socket(socket), - _out(std::move(buffer)) - { - // we will be using the ssl context as a client - SSL_set_connect_state(_ssl); - - // associate domain name with the connection - SSL_set_tlsext_host_name(_ssl, hostname.data()); - - // associate the ssl context with the socket filedescriptor - if (SSL_set_fd(_ssl, socket) == 0) throw std::runtime_error("failed to associate filedescriptor with ssl socket"); - - // we are going to wait until the socket becomes writable before we start the handshake - _handler->monitor(_connection, _socket, writable); - } - - /** - * Destructor - */ - virtual ~TcpSslHandshake() noexcept - { - // leap out if socket is invalidated - if (_socket < 0) return; - - // close the socket - close(_socket); - } - - /** - * The filedescriptor of this connection - * @return int - */ - virtual int fileno() const override { return _socket; } - - /** - * Process the filedescriptor in the object - * @param fd Filedescriptor that is active - * @param flags AMQP::readable and/or AMQP::writable - * @return New state object - */ - virtual TcpState *process(int fd, int flags) override - { - // must be the socket - if (fd != _socket) return this; - - // start the ssl handshake - int result = SSL_do_handshake(_ssl); - - // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); - - // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); - - // check the error - switch (error) { - case SSL_ERROR_WANT_READ: return proceed(readable); - case SSL_ERROR_WANT_WRITE: return proceed(readable | writable); - default: return reportError(); - } - } - - /** - * Send data over the connection - * @param buffer buffer to send - * @param size size of the buffer - */ - virtual void send(const char *buffer, size_t size) override - { - // the handshake is still busy, outgoing data must be cached - _out.add(buffer, size); - } - - /** - * Flush the connection, sent all buffered data to the socket - * @return TcpState new tcp state - */ - virtual TcpState *flush() override - { - // create an object to wait for the filedescriptor to becomes active - Wait wait(_socket); - - // keep looping - while (true) - { - // start the ssl handshake - int result = SSL_do_handshake(_ssl); - - // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new TcpSslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); - - // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); - - // check the error - switch (error) - { - // if openssl reports that socket readability or writability is needed, - // we wait for that until this situation is reached - case SSL_ERROR_WANT_READ: wait.readable(); break; - case SSL_ERROR_WANT_WRITE: wait.active(); break; - - // something is wrong, we proceed to the next state - default: return reportError(); - } - } - } - - /** - * Report to the handler that the connection was nicely closed - */ - virtual void reportClosed() override - { - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - - // close the socket - close(_socket); - - // socket is closed now - _socket = -1; - - // copy the handler (if might destruct this object) - auto *handler = _handler; - - // reset member before the handler can make a mess of it - _handler = nullptr; - - // notify to handler - handler->onClosed(_connection); - } -}; - -/** - * End of namespace - */ -} - From 69596e49dc2b1cd200011ed7453797bcb39e0ab0 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 08:40:44 +0100 Subject: [PATCH 128/168] removed tabs --- src/linux_tcp/sslconnected.h | 344 +++++++++++++++++++++++++++++++++++ src/linux_tcp/sslcontext.h | 86 +++++++++ src/linux_tcp/sslhandshake.h | 252 +++++++++++++++++++++++++ src/linux_tcp/sslshutdown.h | 161 ++++++++++++++++ src/linux_tcp/sslwrapper.h | 85 +++++++++ src/linux_tcp/tcpconnected.h | 4 +- src/linux_tcp/tcpinbuffer.h | 8 +- src/linux_tcp/tcpoutbuffer.h | 78 ++++---- src/linux_tcp/tcpresolver.h | 14 +- src/linux_tcp/wait.h | 110 +++++------ 10 files changed, 1035 insertions(+), 107 deletions(-) create mode 100644 src/linux_tcp/sslconnected.h create mode 100644 src/linux_tcp/sslcontext.h create mode 100644 src/linux_tcp/sslhandshake.h create mode 100644 src/linux_tcp/sslshutdown.h create mode 100644 src/linux_tcp/sslwrapper.h diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h new file mode 100644 index 0000000..1ad708b --- /dev/null +++ b/src/linux_tcp/sslconnected.h @@ -0,0 +1,344 @@ +/** + * SslConnected.h + * + * The actual tcp connection over SSL + * + * @copyright 2018 copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include "tcpoutbuffer.h" +#include "tcpinbuffer.h" +#include "wait.h" +#include "sslwrapper.h" +#include "sslshutdown.h" + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class SslConnected : public TcpState, private Watchable +{ +private: + /** + * The SSL structure + * @var SslWrapper + */ + SslWrapper _ssl; + + /** + * Socket file descriptor + * @var int + */ + int _socket; + + /** + * The outgoing buffer + * @var TcpBuffer + */ + TcpOutBuffer _out; + + /** + * The incoming buffer + * @var TcpInBuffer + */ + TcpInBuffer _in; + + /** + * Are we now busy with sending or receiving? + * @var enum + */ + enum { + state_idle, + state_sending, + state_receiving + } _state; + + /** + * Is the object already closed? + * @var bool + */ + bool _closed = false; + + /** + * Cached reallocation instruction + * @var size_t + */ + size_t _reallocate = 0; + + + /** + * Helper method to report an error + * @return bool Was an error reported? + */ + bool reportError() + { + // we have an error - report this to the user + _handler->onError(_connection, strerror(errno)); + + // done + return true; + } + + /** + * Construct the next state + * @param monitor Object that monitors whether connection still exists + * @return TcpState* + */ + TcpState *nextState(const Monitor &monitor) + { + // if the object is still in a valid state, we can move to the close-state, + // otherwise there is no point in moving to a next state + return monitor.valid() ? new TcpClosed(this) : nullptr; + } + + /** + * Proceed with the next operation after the previous operation was + * a success, possibly changing the filedescriptor-monitor + * @return TcpState* + */ + TcpState *proceed() + { + // if we still have an outgoing buffer we want to send out data + if (_out) + { + // we still have a buffer with outgoing data + _state = state_sending; + + // let's wait until the socket becomes writable + _handler->monitor(_connection, _socket, readable | writable); + } + else if (_closed) + { + // we forget the current handler to prevent that things are changed + _handler = nullptr; + + // start the state that closes the connection + return new SslShutdown(_connection, _socket, _ssl, _handler); + } + else + { + // outgoing buffer is empty, we're idle again waiting for further input + _state = state_idle; + + // let's wait until the socket becomes readable + _handler->monitor(_connection, _socket, readable); + } + + // done + return this; + } + + /** + * Method to repeat the previous call + * @param result result of an earlier openssl operation + * @return TcpState* + */ + TcpState *repeat(int result) + { + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // the operation must be repeated when readable + _handler->monitor(_connection, _socket, readable); + return this; + + case SSL_ERROR_WANT_WRITE: + // wait until socket becomes writable again + _handler->monitor(_connection, _socket, readable | writable); + return this; + + default: + + // @todo check how to handle this + return this; + } + } + + /** + * Parse the received buffer + * @param size + * @return TcpState + */ + TcpState *parse(size_t size) + { + // we need a local copy of the buffer - because it is possible that "this" + // object gets destructed halfway through the call to the parse() method + TcpInBuffer buffer(std::move(_in)); + + // because the object might soon be destructed, we create a monitor to check this + Monitor monitor(this); + + // parse the buffer + auto processed = _connection->parse(buffer); + + // "this" could be removed by now, check this + if (!monitor.valid()) return nullptr; + + // shrink buffer + buffer.shrink(processed); + + // restore the buffer as member + _in = std::move(buffer); + + // do we have to reallocate? + if (_reallocate) _in.reallocate(_reallocate); + + // we can remove the reallocate instruction + _reallocate = 0; + + // done + return this; + } + +public: + /** + * Constructor + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param ssl The SSL structure + * @param buffer The buffer that was already built + * @param handler User-supplied handler object + */ + SslConnected(TcpConnection *connection, int socket, const SslWrapper &ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : + TcpState(connection, handler), + _ssl(ssl), + _socket(socket), + _out(std::move(buffer)), + _in(4096), + _state(_out ? state_sending : state_idle) + { + // tell the handler to monitor the socket if there is an out + _handler->monitor(_connection, _socket, _state == state_sending ? writable : readable); + } + + /** + * Destructor + */ + virtual ~SslConnected() noexcept + { + // skip if handler is already forgotten + if (_handler == nullptr) return; + + // we no longer have to monitor the socket + _handler->monitor(_connection, _socket, 0); + + // close the socket + close(_socket); + } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } + + /** + * Process the filedescriptor in the object + * @param fd The filedescriptor that is active + * @param flags AMQP::readable and/or AMQP::writable + * @return New implementation object + */ + virtual TcpState *process(int fd, int flags) + { + // the socket must be the one this connection writes to + if (fd != _socket) return this; + + // because the object might soon be destructed, we create a monitor to check this + Monitor monitor(this); + + // are we busy with sending or receiving data? + if (_state == state_sending) + { + // try to send more data from the outgoing buffer + auto result = _out.sendto(_ssl); + + // if this is a success, we can proceed with the event loop + if (result > 0) return proceed(); + + // the operation failed, we may have to repeat our call + else return repeat(result); + } + else + { + // read data from ssl into the buffer + auto result = _in.receivefrom(_ssl, _connection->expected()); + + // if this is a success, we may have to update the monitor + if (result > 0) return parse(result); + + // the operation failed, we may have to repeat our call + else return repeat(result); + } + } + + /** + * Send data over the connection + * @param buffer buffer to send + * @param size size of the buffer + */ + virtual void send(const char *buffer, size_t size) + { + // put the data in the outgoing buffer + _out.add(buffer, size); + + // if we're already busy with sending or receiving, we first have to wait + // for that operation to complete before we can move on + if (_state != state_idle) return; + + // object is now busy sending + _state = state_sending; + + // let's wait until the socket becomes writable + _handler->monitor(_connection, _socket, readable | writable); + } + + /** + * Report that heartbeat negotiation is going on + * @param heartbeat suggested heartbeat + * @return uint16_t accepted heartbeat + */ + virtual uint16_t reportNegotiate(uint16_t heartbeat) override + { + // remember that we have to reallocate (_in member can not be accessed because it is moved away) + _reallocate = _connection->maxFrame(); + + // pass to base + return TcpState::reportNegotiate(heartbeat); + } + + /** + * Report to the handler that the connection was nicely closed + */ + virtual void reportClosed() override + { + // remember that the object is closed + _closed = true; + + // if the previous operation is still in progress + if (_state != state_idle) return; + + // wait until the connection is writable + _handler->monitor(_connection, _socket, writable); + } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/sslcontext.h b/src/linux_tcp/sslcontext.h new file mode 100644 index 0000000..a53c04d --- /dev/null +++ b/src/linux_tcp/sslcontext.h @@ -0,0 +1,86 @@ +/** + * SslContext.h + * + * Class to create and maintain a tcp ssl context + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class SslContext +{ +private: + /** + * The wrapped context + * @var SSL_CTX + */ + SSL_CTX *_ctx; + +public: + /** + * Constructor + * @param method + * @throws std::runtime_error + */ + SslContext(const SSL_METHOD *method) : _ctx(SSL_CTX_new(method)) + { + // report error + if (_ctx == nullptr) throw std::runtime_error("failed to construct ssl context"); + } + + /** + * Constructor that wraps around an existing context + * @param context + */ + SslContext(SSL_CTX *context) : _ctx(context) + { + // increment refcount + // @todo fix this + //SSL_ctx_up_ref(context); + } + + /** + * Copy constructor + * @param that + */ + SslContext(SslContext &that) : _ctx(that._ctx) + { + // increment refcount + // @todo fix this + //SSL_ctx_up_ref(context); + } + + /** + * Destructor + */ + virtual ~SslContext() + { + // free resource (this updates the refcount -1, and may destruct it) + SSL_CTX_free(_ctx); + } + + /** + * Cast to the actual context + * @return SSL_CTX * + */ + operator SSL_CTX * () { return _ctx; } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h new file mode 100644 index 0000000..b5e3870 --- /dev/null +++ b/src/linux_tcp/sslhandshake.h @@ -0,0 +1,252 @@ +/** + * SslHandshake.h + * + * Implementation of the TCP state that is responsible for setting + * up the STARTTLS handshake. + * + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include "tcpoutbuffer.h" +#include "sslconnected.h" +#include "wait.h" +#include "sslwrapper.h" +#include "sslcontext.h" + +/** + * Set up namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class SslHandshake : public TcpState, private Watchable +{ +private: + /** + * SSL structure + * @var SslWrapper + */ + SslWrapper _ssl; + + /** + * The socket file descriptor + * @var int + */ + int _socket; + + /** + * The outgoing buffer + * @var TcpOutBuffer + */ + TcpOutBuffer _out; + + + /** + * Report a new state + * @param state + * @return TcpState + */ + TcpState *nextstate(TcpState *state) + { + // forget the socket to prevent that it is closed by the destructor + _socket = -1; + + // done + return state; + } + + /** + * Helper method to report an error + * @return TcpState* + */ + TcpState *reportError() + { + // we are no longer interested in any events for this socket + _handler->monitor(_connection, _socket, 0); + + // we have an error - report this to the user + _handler->onError(_connection, "failed to setup ssl connection"); + + // done, go to the closed state + return new TcpClosed(_connection, _handler); + } + + /** + * Proceed with the handshake + * @param events the events to wait for on the socket + * @return TcpState + */ + TcpState *proceed(int events) + { + // tell the handler that we want to listen for certain events + _handler->monitor(_connection, _socket, events); + + // allow chaining + return this; + } + +public: + /** + * Constructor + * + * @todo catch the exception! + * + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param hostname The hostname to connect to + * @param context SSL context + * @param buffer The buffer that was already built + * @param handler User-supplied handler object + * @throws std::runtime_error + */ + SslHandshake(TcpConnection *connection, int socket, const std::string &hostname, TcpOutBuffer &&buffer, TcpHandler *handler) : + TcpState(connection, handler), + _ssl(SslContext(SSLv23_client_method())), + _socket(socket), + _out(std::move(buffer)) + { + // we will be using the ssl context as a client + SSL_set_connect_state(_ssl); + + // associate domain name with the connection + SSL_set_tlsext_host_name(_ssl, hostname.data()); + + // associate the ssl context with the socket filedescriptor + if (SSL_set_fd(_ssl, socket) == 0) throw std::runtime_error("failed to associate filedescriptor with ssl socket"); + + // we are going to wait until the socket becomes writable before we start the handshake + _handler->monitor(_connection, _socket, writable); + } + + /** + * Destructor + */ + virtual ~SslHandshake() noexcept + { + // leap out if socket is invalidated + if (_socket < 0) return; + + // close the socket + close(_socket); + } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } + + /** + * Process the filedescriptor in the object + * @param fd Filedescriptor that is active + * @param flags AMQP::readable and/or AMQP::writable + * @return New state object + */ + virtual TcpState *process(int fd, int flags) override + { + // must be the socket + if (fd != _socket) return this; + + // start the ssl handshake + int result = SSL_do_handshake(_ssl); + + // if the connection succeeds, we can move to the ssl-connected state + if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); + + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: return proceed(readable); + case SSL_ERROR_WANT_WRITE: return proceed(readable | writable); + default: return reportError(); + } + } + + /** + * Send data over the connection + * @param buffer buffer to send + * @param size size of the buffer + */ + virtual void send(const char *buffer, size_t size) override + { + // the handshake is still busy, outgoing data must be cached + _out.add(buffer, size); + } + + /** + * Flush the connection, sent all buffered data to the socket + * @return TcpState new tcp state + */ + virtual TcpState *flush() override + { + // create an object to wait for the filedescriptor to becomes active + Wait wait(_socket); + + // keep looping + while (true) + { + // start the ssl handshake + int result = SSL_do_handshake(_ssl); + + // if the connection succeeds, we can move to the ssl-connected state + if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); + + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) + { + // if openssl reports that socket readability or writability is needed, + // we wait for that until this situation is reached + case SSL_ERROR_WANT_READ: wait.readable(); break; + case SSL_ERROR_WANT_WRITE: wait.active(); break; + + // something is wrong, we proceed to the next state + default: return reportError(); + } + } + } + + /** + * Report to the handler that the connection was nicely closed + */ + virtual void reportClosed() override + { + // we no longer have to monitor the socket + _handler->monitor(_connection, _socket, 0); + + // close the socket + close(_socket); + + // socket is closed now + _socket = -1; + + // copy the handler (if might destruct this object) + auto *handler = _handler; + + // reset member before the handler can make a mess of it + _handler = nullptr; + + // notify to handler + handler->onClosed(_connection); + } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h new file mode 100644 index 0000000..4a0315e --- /dev/null +++ b/src/linux_tcp/sslshutdown.h @@ -0,0 +1,161 @@ +/** + * SslShutdown.h + * + * Class that takes care of the final handshake to close a SSL connection + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class SslShutdown : public TcpState, private Watchable +{ +private: + /** + * The SSL context + * @var SslWrapper + */ + SslWrapper _ssl; + + /** + * Socket file descriptor + * @var int + */ + int _socket; + + + /** + * Proceed with the next operation after the previous operation was + * a success, possibly changing the filedescriptor-monitor + * @return TcpState* + */ + TcpState *proceed() + { + // construct monitor to prevent that we access members if object is destructed + Monitor monitor(this); + + // we're no longer interested in events + _handler->monitor(_connection, _socket, 0); + + // stop if object was destructed + if (!monitor) return nullptr; + + // close the socket + close(_socket); + + // forget the socket + _socket = -1; + + // go to the closed state + return new TcpClosed(_connection, _handler); + } + + /** + * Method to repeat the previous call + * @param result result of an earlier openssl operation + * @return TcpState* + */ + TcpState *repeat(int result) + { + // error was returned, so we must investigate what is going on + auto error = SSL_get_error(_ssl, result); + + // check the error + switch (error) { + case SSL_ERROR_WANT_READ: + // the operation must be repeated when readable + _handler->monitor(_connection, _socket, readable); + return this; + + case SSL_ERROR_WANT_WRITE: + // wait until socket becomes writable again + _handler->monitor(_connection, _socket, readable | writable); + return this; + + default: + + // @todo check how to handle this + return this; + } + } + + +public: + /** + * Constructor + * @param connection Parent TCP connection object + * @param socket The socket filedescriptor + * @param ssl The SSL structure + * @param handler User-supplied handler object + */ + SslShutdown(TcpConnection *connection, int socket, const SslWrapper &ssl, TcpHandler *handler) : + TcpState(connection, handler), + _ssl(ssl), + _socket(socket) + { + // tell the handler to monitor the socket if there is an out + _handler->monitor(_connection, _socket, readable); + } + + /** + * Destructor + */ + virtual ~SslShutdown() noexcept + { + // skip if handler is already forgotten + if (_handler == nullptr) return; + + // we no longer have to monitor the socket + _handler->monitor(_connection, _socket, 0); + + // close the socket + close(_socket); + } + + /** + * The filedescriptor of this connection + * @return int + */ + virtual int fileno() const override { return _socket; } + + /** + * Process the filedescriptor in the object + * @param fd The filedescriptor that is active + * @param flags AMQP::readable and/or AMQP::writable + * @return New implementation object + */ + virtual TcpState *process(int fd, int flags) + { + // the socket must be the one this connection writes to + if (fd != _socket) return this; + + // because the object might soon be destructed, we create a monitor to check this + Monitor monitor(this); + + // close the connection + auto result = SSL_shutdown(_ssl); + + // if this is a success, we can proceed with the event loop + if (result > 0) return proceed(); + + // the operation failed, we may have to repeat our call + else return repeat(result); + } +}; + +/** + * End of namespace + */ +} diff --git a/src/linux_tcp/sslwrapper.h b/src/linux_tcp/sslwrapper.h new file mode 100644 index 0000000..5c88810 --- /dev/null +++ b/src/linux_tcp/sslwrapper.h @@ -0,0 +1,85 @@ +/** + * SslWrapper.h + * + * Wrapper around a SSL pointer + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class SslWrapper +{ +private: + /** + * The wrapped object + * @var SSL* + */ + SSL *_ssl; + +public: + /** + * Constructor + * @param ctx + */ + SslWrapper(SSL_CTX *ctx) : _ssl(SSL_new(ctx)) + { + // report error + if (_ssl == nullptr) throw std::runtime_error("failed to construct ssl structure"); + } + + /** + * Wrapper constructor + * @param ssl + */ + SslWrapper(SSL *ssl) : _ssl(ssl) + { + // one more reference + // @todo fix this + //CRYPTO_add(_ssl); + } + + /** + * Copy constructor + * @param that + */ + SslWrapper(const SslWrapper &that) : _ssl(that._ssl) + { + // one more reference + // @todo fix this + //SSL_up_ref(_ssl); + } + + /** + * Destructor + */ + virtual ~SslWrapper() + { + // destruct object + SSL_free(_ssl); + } + + /** + * Cast to the SSL* + * @return SSL * + */ + operator SSL * () const { return _ssl; } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index f710a05..670b3db 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -222,8 +222,8 @@ public: */ virtual TcpState *flush() override { - // create an object to wait for the filedescriptor to becomes active - Wait wait(_socket); + // create an object to wait for the filedescriptor to becomes active + Wait wait(_socket); // keep running until the out buffer is empty while (_out) diff --git a/src/linux_tcp/tcpinbuffer.h b/src/linux_tcp/tcpinbuffer.h index 4487d82..e535067 100644 --- a/src/linux_tcp/tcpinbuffer.h +++ b/src/linux_tcp/tcpinbuffer.h @@ -110,15 +110,15 @@ public: /** * Receive data from a socket - * @param ssl ssl wrapped socket to read from + * @param ssl ssl wrapped socket to read from * @param expected number of bytes that the library expects * @return ssize_t */ ssize_t receivefrom(SSL *ssl, uint32_t expected) { - // @todo implementation - return 0; - } + // @todo implementation + return 0; + } /** * Shrink the buffer (in practice this is always called with the full buffer size) diff --git a/src/linux_tcp/tcpoutbuffer.h b/src/linux_tcp/tcpoutbuffer.h index ab9e464..a619620 100644 --- a/src/linux_tcp/tcpoutbuffer.h +++ b/src/linux_tcp/tcpoutbuffer.h @@ -198,34 +198,34 @@ public: /** * Fill an iovec buffer - * @param buffers the buffers to be filled - * @param count number of buffers available - * @return size_t the number of buffers that were filled + * @param buffers the buffers to be filled + * @param count number of buffers available + * @return size_t the number of buffers that were filled */ size_t fill(struct iovec buffers[], size_t count) const { - // index counter - size_t index = 0; + // index counter + size_t index = 0; - // iterate over the buffers - for (const auto &str : _buffers) - { - // fill buffer - buffers[index].iov_base = (void *)(index == 0 ? str.data() + _skip : str.data()); - buffers[index].iov_len = index == 0 ? str.size() - _skip : str.size(); - - // update counter for next iteration - if (++index >= count) return count; - } - - // done - return index; - } + // iterate over the buffers + for (const auto &str : _buffers) + { + // fill buffer + buffers[index].iov_base = (void *)(index == 0 ? str.data() + _skip : str.data()); + buffers[index].iov_len = index == 0 ? str.size() - _skip : str.size(); + + // update counter for next iteration + if (++index >= count) return count; + } + + // done + return index; + } /** * Send the buffer to a socket - * @param socket the socket to send data to - * @return ssize_t number of bytes sent (or the same result as sendmsg() in case of an error) + * @param socket the socket to send data to + * @return ssize_t number of bytes sent (or the same result as sendmsg() in case of an error) */ ssize_t sendto(int socket) { @@ -270,28 +270,28 @@ public: /** * Send the buffer to an SSL connection - * @param ssl the ssl context to send data to - * @return ssize_t number of bytes sent, or the return value of ssl_write + * @param ssl the ssl context to send data to + * @return ssize_t number of bytes sent, or the return value of ssl_write */ ssize_t sendto(SSL *ssl) { - // we're going to fill a lot of buffers (for ssl only one buffer at a time can be sent) - struct iovec buffer[1]; - - // fill the buffers, and leap out if there is no data - auto buffers = fill(buffer, 1); - + // we're going to fill a lot of buffers (for ssl only one buffer at a time can be sent) + struct iovec buffer[1]; + + // fill the buffers, and leap out if there is no data + auto buffers = fill(buffer, 1); + // just to be sure we do this check - if (buffers == 0) return 0; - - // send the data - auto result = SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len); - - // on success we shrink the buffer - if (result > 0) shrink(result); - - // done - return result; + if (buffers == 0) return 0; + + // send the data + auto result = SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len); + + // on success we shrink the buffer + if (result > 0) shrink(result); + + // done + return result; } }; diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 3c86afd..7b194d2 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -149,7 +149,7 @@ public: * @param connection Parent connection object * @param hostname The hostname for the lookup * @param portnumber The portnumber for the lookup - * @param secure Do we need a secure tls connection when ready? + * @param secure Do we need a secure tls connection when ready? * @param handler User implemented handler object */ TcpResolver(TcpConnection *connection, const std::string &hostname, uint16_t port, bool secure, TcpHandler *handler) : @@ -189,12 +189,12 @@ public: // do we have a valid socket? if (_socket >= 0) { - // if we need a secure connection, we move to the tls handshake - //if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); - - // otherwise we have a valid regular tcp connection - return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); - } + // if we need a secure connection, we move to the tls handshake + //if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); + + // otherwise we have a valid regular tcp connection + return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); + } else { // report error diff --git a/src/linux_tcp/wait.h b/src/linux_tcp/wait.h index ecd3817..26e784e 100644 --- a/src/linux_tcp/wait.h +++ b/src/linux_tcp/wait.h @@ -1,9 +1,9 @@ /** - * Wait.h + * Wait.h * - * Class to wait for a socket to become readable and/or writable + * Class to wait for a socket to become readable and/or writable * - * @copyright 2018 Copernica BV + * @copyright 2018 Copernica BV */ /** @@ -12,82 +12,82 @@ #pragma once /** - * Begin of namespace + * Begin of namespace */ namespace AMQP { /** - * Class definition + * Class definition */ class Wait { private: - /** - * Set with just one filedescriptor - * @var fd_set - */ - fd_set _set; + /** + * Set with just one filedescriptor + * @var fd_set + */ + fd_set _set; - /** - * The current socket // @todo what is it exactly? - * @var int - */ - int _socket; - + /** + * The current socket // @todo what is it exactly? + * @var int + */ + int _socket; + public: - /** - * Constructor - * @param fd the filedescriptor that we're waiting on - */ - Wait(int fd) - { - _socket = fd; - + /** + * Constructor + * @param fd the filedescriptor that we're waiting on + */ + Wait(int fd) + { + _socket = fd; + // initialize the set FD_ZERO(&_set); // add the one socket FD_SET(_socket, &_set); - } - - /** - * Destructor - */ - virtual ~Wait() = default; - - /** - * Wait until the filedescriptor becomes readable - * @return bool - */ - bool readable() - { + } + + /** + * Destructor + */ + virtual ~Wait() = default; + + /** + * Wait until the filedescriptor becomes readable + * @return bool + */ + bool readable() + { // wait for the socket return select(_socket + 1, &_set, nullptr, nullptr, nullptr) > 0; - } - - /** - * Wait until the filedescriptor becomes writable - * @return bool - */ - bool writable() - { + } + + /** + * Wait until the filedescriptor becomes writable + * @return bool + */ + bool writable() + { // wait for the socket return select(_socket + 1, nullptr, &_set, nullptr, nullptr) > 0; - } - - /** - * Wait until a filedescriptor becomes active (readable or writable) - * @return bool - */ - bool active() - { + } + + /** + * Wait until a filedescriptor becomes active (readable or writable) + * @return bool + */ + bool active() + { // wait for the socket return select(_socket + 1, &_set, &_set, nullptr, nullptr) > 0; - } + } }; /** - * End of namespace + * End of namespace */ } From 5b8841c937b6b1cd446e818ffcf82d9940bad1fb Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 08:45:12 +0100 Subject: [PATCH 129/168] removed more tabs --- include/amqpcpp/address.h | 9 ++++++--- include/amqpcpp/endian.h | 2 +- include/amqpcpp/libboostasio.h | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index efccf8e..c8faaa1 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -67,9 +67,12 @@ public: const char *last = data + size; // must start with ampqs:// to have a secure connection (and we also assign a different default port) - if ((_secure = strncmp(data, "amqps://", 8) == 0)) _port = 5671; + _secure = strncmp(data, "amqps://", 8) == 0; - // otherwise protocol must be amqp:// + // default port changes for secure connections + if (_secure) _port = 5671; + + // otherwise protocol must be amqp:// else if (strncmp(data, "amqp://", 7) != 0) throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\""); // begin of the string was parsed @@ -142,7 +145,7 @@ public: * @param port * @param login * @param vhost - * @param secure + * @param secure */ Address(std::string host, uint16_t port, Login login, std::string vhost, bool secure = false) : _secure(secure), diff --git a/include/amqpcpp/endian.h b/include/amqpcpp/endian.h index f1adab9..72242a6 100644 --- a/include/amqpcpp/endian.h +++ b/include/amqpcpp/endian.h @@ -58,7 +58,7 @@ #include #pragma comment(lib,"Ws2_32.lib") -//# include +//# include #if BYTE_ORDER == LITTLE_ENDIAN diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index c6fb532..acdf538 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -176,7 +176,7 @@ protected: TcpConnection *const connection, const int fd) { - // Resolve any potential problems with dangling pointers + // Resolve any potential problems with dangling pointers // (remember we are using async). const std::shared_ptr apWatcher = awpWatcher.lock(); if (!apWatcher) { return; } @@ -210,7 +210,7 @@ protected: TcpConnection *const connection, const int fd) { - // Resolve any potential problems with dangling pointers + // Resolve any potential problems with dangling pointers // (remember we are using async). const std::shared_ptr apWatcher = awpWatcher.lock(); if (!apWatcher) { return; } @@ -498,7 +498,7 @@ protected: // construct a new pair (watcher/timer), and put it in the map const std::shared_ptr apWatcher = - std::make_shared(_ioservice, _strand, fd); + std::make_shared(_ioservice, _strand, fd); _watchers[fd] = apWatcher; From a025e6c1c99ea113cd2013a5d997241829e8edc4 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 08:46:17 +0100 Subject: [PATCH 130/168] breaking change: the linux-tcp module is no longer included by default, applications that rely on this now have to explicitly include amcpcpp/linux_tcp --- include/amqpcpp.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/amqpcpp.h b/include/amqpcpp.h index 9d0e5a7..39ec8f5 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -80,6 +80,3 @@ #include "amqpcpp/connectionimpl.h" #include "amqpcpp/connection.h" -// tcp level includes -#include "amqpcpp/linux_tcp.h" - From baa4450aa66f3003bf3126e55c33f2c0efcdc5df Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 09:44:24 +0100 Subject: [PATCH 131/168] update indentation --- src/linux_tcp/wait.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/linux_tcp/wait.h b/src/linux_tcp/wait.h index 26e784e..5db5242 100644 --- a/src/linux_tcp/wait.h +++ b/src/linux_tcp/wait.h @@ -30,7 +30,7 @@ private: /** * The current socket // @todo what is it exactly? - * @var int + * @var int */ int _socket; @@ -39,10 +39,8 @@ public: * Constructor * @param fd the filedescriptor that we're waiting on */ - Wait(int fd) + Wait(int fd) : _socket(fd) { - _socket = fd; - // initialize the set FD_ZERO(&_set); @@ -50,6 +48,12 @@ public: FD_SET(_socket, &_set); } + /** + * No copying + * @param that + */ + Wait(const Wait &that) = delete; + /** * Destructor */ From 3bb7714f3bb3a04d3ca627e0fdb776a711f65360 Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Tue, 6 Mar 2018 14:39:36 +0100 Subject: [PATCH 132/168] dynamically loading openssl funtions during runtime work in progress --- examples/libev.cpp | 2 +- src/linux_tcp/tcpinbuffer.h | 5 +++++ src/linux_tcp/tcpoutbuffer.h | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 0600d92..43cad39 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -65,7 +65,7 @@ int main() MyHandler handler(loop); // init the SSL library -// SSL_library_init(); + SSL_library_init(); // make a connection AMQP::Address address("amqps://guest:guest@localhost/"); diff --git a/src/linux_tcp/tcpinbuffer.h b/src/linux_tcp/tcpinbuffer.h index e535067..bd020e3 100644 --- a/src/linux_tcp/tcpinbuffer.h +++ b/src/linux_tcp/tcpinbuffer.h @@ -12,6 +12,11 @@ */ #pragma once +/** + * Dependencies + */ + #include + /** * Beginnig of namespace */ diff --git a/src/linux_tcp/tcpoutbuffer.h b/src/linux_tcp/tcpoutbuffer.h index a619620..8d18526 100644 --- a/src/linux_tcp/tcpoutbuffer.h +++ b/src/linux_tcp/tcpoutbuffer.h @@ -19,7 +19,6 @@ #include #include #include - /** * FIONREAD on Solaris is defined elsewhere */ From 8ba8ed17271145fb563ddf59c28d5e72fe62086c Mon Sep 17 00:00:00 2001 From: Tamas Elekes Date: Tue, 6 Mar 2018 14:40:46 +0100 Subject: [PATCH 133/168] dynamically loading openssl funtions during runtime work in progress --- src/linux_tcp/function.h | 176 +++++++++++++++++++++++++++++ src/linux_tcp/openssl.cpp | 228 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) create mode 100644 src/linux_tcp/function.h create mode 100644 src/linux_tcp/openssl.cpp diff --git a/src/linux_tcp/function.h b/src/linux_tcp/function.h new file mode 100644 index 0000000..586163f --- /dev/null +++ b/src/linux_tcp/function.h @@ -0,0 +1,176 @@ +/** + * Function.h + * + * When you want to retrieve a function from a dynamicallly loaded + * library, you normally use the dlsym() function for that. The + * Function object is a little more convenient: + * + * // open the library + * void *lib = dlopen("library.so"); + * + * // get the function object + * Function func magic_open(library, "my_function"); + * + * // call the function + * int result = func(123); + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include +#include + +/** + * Namespace + */ +namespace AMQP { + +/** + * Make the Function class a templated class + */ +template class Function {}; + +/** + * So that we can write a partial specialisation + * using a function prototype, indicating the + * prototype usable for the given callback + */ +template +class Function +{ +private: + /** + * Store pointer to the actual dynamically loaded + * function. This is done in a union because the + * result from a dlsym call is a void pointer which + * cannot be legally cast into a function pointer. + * + * We therefore always set the first type (which is + * void* making it legal) and, because all members + * in a union share the same address, we can then + * read the second type and actually call it. + * + * @var Callable + */ + union Callable { + + /** + * Property for getting and setting the return + * value from dlsym. This is always a void* + * @var void + */ + void *ptr; + + /** + * Property for executing the mapped function + * + * @param mixed,... function parameters + * @return mixed + * + * @var function + */ + T (*func)(Arguments...); + + + /** + * Constructor + */ + Callable() : ptr(nullptr) {} + + /** + * We may be moved + * + * @param callable the callable we are moving + */ + Callable(Callable&& callable) : + ptr(callable.ptr) + { + // the other callable no longer has a handle + callable.ptr = nullptr; + } + + /** + * Copy construtor + * @param callable the callable we are moving + */ + Callable(const Callable &callable) : + ptr(callable.ptr) {} + + /** + * Constructor + * + * @param function the mapped function + */ + Callable(void *function) : ptr(function) {} + + } _method; + +public: + /** + * Constructor + * @param library The library to load the function from + * @param name Name of the function + */ + Function(void *library, const char *name) : + _method(dlsym(library, name)) {} + + /** + * Destructor + */ + virtual ~Function() {} + + /** + * Is this a valid function or not? + * @return bool + */ + bool valid() const + { + return _method.ptr != nullptr; + } + + /** + * The library object can also be used as in a boolean context, + * for that there is an implementation of a casting operator, and + * the negate operator + * @return bool + */ + operator bool () const { return valid(); } + bool operator ! () const { return !valid(); } + + /** + * Test whether we are a valid object + * @param nullptr test if we are null + */ + bool operator==(std::nullptr_t /* nullptr */) const { return !valid(); } + bool operator!=(std::nullptr_t /* nullptr */) const { return valid(); } + + /** + * Invoke the function + * + * @param mixed,... + */ + T operator()(Arguments... parameters) const + { + // check whether we have a valid function + if (!valid()) throw std::bad_function_call(); + + // execute the method given all the parameters + return _method.func(std::forward(parameters)...); + } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp new file mode 100644 index 0000000..f885730 --- /dev/null +++ b/src/linux_tcp/openssl.cpp @@ -0,0 +1,228 @@ +/** + * OpenSSL.cpp + * + * Implementation file for the openssl.h header file + * + * @copyright 2018 Copernica BV + */ + +/** + * Dependencies + */ +// #include "includes.h" +#include "amqpcpp/linux_tcp/openssl.h" + +#include "function.h" +#include + +/** + * Begin of namespace + */ +namespace AMQP { namespace OpenSSL { + +/** + * Get the library handle + * @return void * + */ +static void *library() +{ + // stored pointer + static void *ptr = nullptr; + + // is it already opened? + if (ptr != nullptr) return ptr; + + // open ourselves + ptr = dlopen("libssl.so", RTLD_NOW); + if (!ptr) + { + std::cout << "Cannot load library: " << dlerror() << std::endl; + + // reset errors + dlerror(); + + return nullptr; + } + + return ptr; +} + +/** + * Initialize SSL library by registering algorithnms + */ + //~int SSL_library_init() + //~{ + //~// create function + //~static Function func(library(), "SSL_libarary_init"); + + //~// call the openssl function + //~func(); + //~} + +/** + * Create new SSL context + * @param SSL_METHOD can be of the following types: TLS_method(), TLS_server_method(), TLS_client_method() + * @return nullptr if failed pointer to object otherwise + */ +SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) +{ + // create a function + static Function func(library(), "SSL_CTX_new"); + + // call the openssl function + return func(method); +} + +/** + * Read data from an ssl socket + * @param ssl + * @param buf + * @param num + * @return int number of bytes read + */ +int SSL_read(SSL *ssl, void *buf, int num) +{ + // create a function + static Function func(library(), "SSL_read"); + + // call the openssl function + return func(ssl, buf, num); +} + +/** + * Read data from an ssl socket + * @param ssl + * @param buf + * @param num + * @return int number of bytes written + */ +int SSL_write(SSL *ssl, const void *buf, int num) +{ + // create a function + static Function func(library(), "SSL_write"); + + // call the openssl function + return func(ssl, buf, num); +} + +/** + * Connect the SSL object with a file descriptor + * @param ssl SSL object + * @param fd file descriptor + * @return int wether the operation succeeded or not + */ +int SSL_set_fd(SSL *ssl, int fd) +{ + // create a function + static Function func(library(), "SSL_set_fd"); + + // call the openssl function + return func(ssl, fd); +} + + +/** + * Free an allocated SSL structure + * @param ssl SSL object to be freed + * @return int wether the operation succeeded or not + */ +void SSL_free(SSL *ssl) +{ + // create a function + static Function func(library(), "SSL_free"); + + // call the openssl function + return func(ssl); + +} + +/** + * Create a new SSL structure for a connection + * @param ctx SSL context object + * @return SSL the created SSL oject based on th context + */ +SSL *SSL_new(SSL_CTX *ctx) +{ + // create a function + static Function func(library(), "SSL_new"); + + // call the openssl function + return func(ctx); +} + +/** + * Create a new SSL structure for a connection + * @param ctx SSL context object + * @return SSL the created SSL oject based on th context + */ +int SSL_up_ref(SSL *ssl) +{ + // create a function + static Function func(library(), "SSL_up_ref"); + + // call the openssl function + return func(ssl); +} + +/** + * Shut down a TLS/SSL shut down + * @param ssl SSL object to terminate + * @return int returns diagnostic values + */ +int SSL_shutdown(SSL *ssl) +{ + // create a function + static Function func(library(), "SSL_shutdown"); + + // call the openssl function + return func(ssl); +} + +/** + * Prepare SSL object to work in client or server mode + * @param ssl SSL object to set connect state on + */ +void SSL_set_connect_state(SSL *ssl) +{ + // create a function + static Function func(library(), "SSL_set_connect_state"); + + // call the openssl function + func(ssl); + +} + +/** + * Perform a TLS/SSL handshake + * @param ssl SSL object + * @return int returns diagnostic values + */ +int SSL_do_handshake(SSL *ssl) +{ + // create a function + static Function func(library(), "SSL_do_handshake"); + + // call the openssl function + return func(ssl); +} + +/** + * Obtain result code for TLS/SSL I/O operation + * @param ssl SSL object + * @param ret the returned diagnostic value of SSL calls + * @return int returns error values + */ +int SSL_get_error(const SSL *ssl, int ret) +{ + // create a function + static Function func(library(), "SSL_get_error"); + + // call the openssl function + return func(ssl, ret); +} + +/** + * End of namespace + */ +}} + From 25d5410b1328575679e77a11394c2928f2781f1c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 17:52:44 +0100 Subject: [PATCH 134/168] fixed some compile issues --- src/linux_tcp/function.h | 1 + src/linux_tcp/library.h | 64 ++++++++++ src/linux_tcp/openssl.cpp | 243 +++++++++++++++++--------------------- src/linux_tcp/openssl.h | 46 ++++++++ 4 files changed, 220 insertions(+), 134 deletions(-) create mode 100644 src/linux_tcp/library.h create mode 100644 src/linux_tcp/openssl.h diff --git a/src/linux_tcp/function.h b/src/linux_tcp/function.h index 586163f..b243643 100644 --- a/src/linux_tcp/function.h +++ b/src/linux_tcp/function.h @@ -29,6 +29,7 @@ */ #include #include +#include /** * Namespace diff --git a/src/linux_tcp/library.h b/src/linux_tcp/library.h new file mode 100644 index 0000000..7330eda --- /dev/null +++ b/src/linux_tcp/library.h @@ -0,0 +1,64 @@ +/** + * Library.h + * + * The Library class is a wrapper around dlopen() + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Begin of namespace + */ +namespace AMQP { + +/** + * Class definition + */ +class Library +{ +private: + /** + * The library handle + * @var void * + */ + void *_handle; + +public: + /** + * Constructor + */ + Library() : _handle(dlopen(nullptr, RTLD_NOW)) {} + + /** + * No copying + * @param that + */ + Library(const Library &that) = delete; + + /** + * Destructor + */ + virtual ~Library() + { + // close library + if (_handle) dlclose(_handle); + } + + /** + * Cast to the handle + * @return void * + */ + operator void * () { return _handle; } +}; + +/** + * End of namespace + */ +} + diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index f885730..8e9139d 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -1,19 +1,17 @@ /** - * OpenSSL.cpp + * OpenSSL.cpp * - * Implementation file for the openssl.h header file + * Implementation file for the openssl.h header file * - * @copyright 2018 Copernica BV + * @copyright 2018 Copernica BV */ /** - * Dependencies + * Dependencies */ -// #include "includes.h" -#include "amqpcpp/linux_tcp/openssl.h" - +#include "openssl.h" #include "function.h" -#include +#include "library.h" /** * Begin of namespace @@ -22,205 +20,182 @@ namespace AMQP { namespace OpenSSL { /** * Get the library handle - * @return void * + * @return void * */ static void *library() { - // stored pointer - static void *ptr = nullptr; - - // is it already opened? - if (ptr != nullptr) return ptr; - - // open ourselves - ptr = dlopen("libssl.so", RTLD_NOW); - if (!ptr) - { - std::cout << "Cannot load library: " << dlerror() << std::endl; - - // reset errors - dlerror(); + // create on instance + static Library instance; - return nullptr; - } - - return ptr; + // return the instance (it has a cast-to-void-ptr operator) + return instance; } /** - * Initialize SSL library by registering algorithnms - */ - //~int SSL_library_init() - //~{ - //~// create function - //~static Function func(library(), "SSL_libarary_init"); - - //~// call the openssl function - //~func(); - //~} - -/** - * Create new SSL context - * @param SSL_METHOD can be of the following types: TLS_method(), TLS_server_method(), TLS_client_method() - * @return nullptr if failed pointer to object otherwise + * Create new SSL context + * @param method SSL_METHOD can be of the following types: TLS_method(), TLS_server_method(), TLS_client_method() + * @return pointer to object */ SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) { - // create a function - static Function func(library(), "SSL_CTX_new"); - - // call the openssl function - return func(method); + // create a function + static Function func(library(), "SSL_CTX_new"); + + // call the openssl function + return func(method); } /** - * Read data from an ssl socket - * @param ssl - * @param buf - * @param num - * @return int number of bytes read + * Read data from an ssl socket + * @param ssl ssl structure + * @param buf buffer to read into + * @param num size of buffer + * @return int number of bytes read */ int SSL_read(SSL *ssl, void *buf, int num) { - // create a function - static Function func(library(), "SSL_read"); - - // call the openssl function - return func(ssl, buf, num); + // create a function + static Function func(library(), "SSL_read"); + + // call the openssl function + return func(ssl, buf, num); } /** - * Read data from an ssl socket - * @param ssl - * @param buf - * @param num - * @return int number of bytes written + * Read data from an ssl socket + * @param ssl ssl structure + * @param buf buffer to write + * @param num size of buffer + * @return int number of bytes written */ int SSL_write(SSL *ssl, const void *buf, int num) { - // create a function - static Function func(library(), "SSL_write"); - - // call the openssl function - return func(ssl, buf, num); + // create a function + static Function func(library(), "SSL_write"); + + // call the openssl function + return func(ssl, buf, num); } /** - * Connect the SSL object with a file descriptor - * @param ssl SSL object - * @param fd file descriptor - * @return int wether the operation succeeded or not + * Connect the SSL object with a file descriptor + * @param ssl SSL object + * @param fd file descriptor + * @return int wether the operation succeeded or not */ int SSL_set_fd(SSL *ssl, int fd) { - // create a function - static Function func(library(), "SSL_set_fd"); - - // call the openssl function - return func(ssl, fd); + // create a function + static Function func(library(), "SSL_set_fd"); + + // call the openssl function + return func(ssl, fd); } /** - * Free an allocated SSL structure - * @param ssl SSL object to be freed - * @return int wether the operation succeeded or not + * Free an allocated SSL structure + * @param ssl SSL object to be freed + * @return int wether the operation succeeded or not */ void SSL_free(SSL *ssl) { - // create a function - static Function func(library(), "SSL_free"); - - // call the openssl function - return func(ssl); - + // create a function + static Function func(library(), "SSL_free"); + + // call the openssl function + return func(ssl); } /** - * Create a new SSL structure for a connection - * @param ctx SSL context object - * @return SSL the created SSL oject based on th context + * Create a new SSL structure for a connection + * @param ctx SSL context object + * @return SSL the created SSL oject based on th context */ SSL *SSL_new(SSL_CTX *ctx) { - // create a function - static Function func(library(), "SSL_new"); - - // call the openssl function - return func(ctx); + // create a function + static Function func(library(), "SSL_new"); + + // call the openssl function + return func(ctx); } /** - * Create a new SSL structure for a connection - * @param ctx SSL context object - * @return SSL the created SSL oject based on th context + * Increment refcount for a ssl structure + * @param ctx SSL structure + * @return int 1 for success, 0 for failure */ int SSL_up_ref(SSL *ssl) { - // create a function - static Function func(library(), "SSL_up_ref"); - - // call the openssl function - return func(ssl); + // create a function + static Function func(library(), "SSL_up_ref"); + + // call the openssl function if it exists + if (func) return func(ssl); + + // @todo use our own implementation + + return 0; } /** - * Shut down a TLS/SSL shut down - * @param ssl SSL object to terminate - * @return int returns diagnostic values + * Shut down a TLS/SSL shut down + * @param ssl SSL object to terminate + * @return int returns diagnostic values */ int SSL_shutdown(SSL *ssl) { - // create a function - static Function func(library(), "SSL_shutdown"); - - // call the openssl function - return func(ssl); + // create a function + static Function func(library(), "SSL_shutdown"); + + // call the openssl function + return func(ssl); } /** - * Prepare SSL object to work in client or server mode - * @param ssl SSL object to set connect state on + * Prepare SSL object to work in client or server mode + * @param ssl SSL object to set connect state on */ void SSL_set_connect_state(SSL *ssl) { - // create a function - static Function func(library(), "SSL_set_connect_state"); - - // call the openssl function - func(ssl); + // create a function + static Function func(library(), "SSL_set_connect_state"); + + // call the openssl function + func(ssl); } /** - * Perform a TLS/SSL handshake - * @param ssl SSL object - * @return int returns diagnostic values + * Perform a TLS/SSL handshake + * @param ssl SSL object + * @return int returns diagnostic values */ int SSL_do_handshake(SSL *ssl) { - // create a function - static Function func(library(), "SSL_do_handshake"); - - // call the openssl function - return func(ssl); + // create a function + static Function func(library(), "SSL_do_handshake"); + + // call the openssl function + return func(ssl); } /** - * Obtain result code for TLS/SSL I/O operation - * @param ssl SSL object - * @param ret the returned diagnostic value of SSL calls - * @return int returns error values + * Obtain result code for TLS/SSL I/O operation + * @param ssl SSL object + * @param ret the returned diagnostic value of SSL calls + * @return int returns error values */ int SSL_get_error(const SSL *ssl, int ret) { - // create a function - static Function func(library(), "SSL_get_error"); - - // call the openssl function - return func(ssl, ret); + // create a function + static Function func(library(), "SSL_get_error"); + + // call the openssl function + return func(ssl, ret); } - + /** * End of namespace */ diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h new file mode 100644 index 0000000..f30359c --- /dev/null +++ b/src/linux_tcp/openssl.h @@ -0,0 +1,46 @@ +/** + * OpenSSL.h + * + * Header file in which we list all openssl functions in our own namespace + * that we call instead of the actual openssl functions. This allows us to + * intercept the calls and forward them to a dynamically loaded namespace + * + * @author Emiel Bruijntjes + * @copyright 2018 Copernica BV + */ + +/** + * Include guard + */ +#pragma once + +/** + * Dependencies + */ +#include + +/** + * Begin of namespace + */ +namespace Copernica { namespace OpenSSL { + +/** + * List of all methods that we need + */ +SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); +SSL *SSL_new(SSL_CTX *ctx); +int SSL_do_handshake(SSL *ssl); +int SSL_read(SSL *ssl, void *buf, int num); +int SSL_write(SSL *ssl, const void *buf, int num); +int SSL_shutdown(SSL *ssl); +int SSL_set_fd(SSL *ssl, int fd); +int SSL_get_error(const SSL *ssl, int ret); +int SSL_up_ref(SSL *ssl); +void SSL_set_connect_state(SSL *ssl); +void SSL_free(SSL *ssl); + +/** + * End of namespace + */ +}} + From 0ca9bc9dadfec778fb8b343d5777f518876bb190 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 18:07:34 +0100 Subject: [PATCH 135/168] added error in case openssl is missing --- examples/libev.cpp | 5 +++-- src/linux_tcp/openssl.h | 12 +++++++++--- src/linux_tcp/sslhandshake.h | 2 +- src/linux_tcp/tcpresolver.h | 9 +++++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 43cad39..4d909f6 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -4,7 +4,7 @@ * Test program to check AMQP functionality based on LibEV * * @author Emiel Bruijntjes - * @copyright 2015 - 2017 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -13,6 +13,7 @@ #include #include #include +#include /** * Custom handler @@ -65,7 +66,7 @@ int main() MyHandler handler(loop); // init the SSL library - SSL_library_init(); +// SSL_library_init(); // make a connection AMQP::Address address("amqps://guest:guest@localhost/"); diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index f30359c..9a21dc9 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -22,10 +22,16 @@ /** * Begin of namespace */ -namespace Copernica { namespace OpenSSL { - +namespace AMQP { namespace OpenSSL { + /** - * List of all methods that we need + * Function to check if openssl is loaded + * @return bool + */ +bool valid(); + +/** + * List of all wrapper methods that are in use inside AMQP-CPP */ SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); SSL *SSL_new(SSL_CTX *ctx); diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index b5e3870..fb8af3b 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -158,7 +158,7 @@ public: if (fd != _socket) return this; // start the ssl handshake - int result = SSL_do_handshake(_ssl); + int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 7b194d2..be45c4f 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -20,7 +20,8 @@ #include "tcpstate.h" #include "tcpclosed.h" #include "tcpconnected.h" -//#include "sslhandshake.h" +#include "openssl.h" +#include "sslhandshake.h" #include /** @@ -91,6 +92,9 @@ private: // prevent exceptions try { + // check if we support openssl in the first place + if (!OpenSSL::valid()) throw std::runtime_error("Secure connection cannot be established: the application has no access to openssl"); + // get address info AddressInfo addresses(_hostname.data(), _port); @@ -190,7 +194,8 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - //if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); + // @todo catch exception + if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection return new TcpConnected(_connection, _socket, std::move(_buffer), _handler); From 7aa7794e3e944895c0b1b4635669affc93800968 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 22:03:53 +0100 Subject: [PATCH 136/168] work in progress on ssl implementation --- examples/libev.cpp | 2 +- src/linux_tcp/function.h | 5 +- src/linux_tcp/library.h | 64 -------------------------- src/linux_tcp/openssl.cpp | 89 +++++++++++++++++++++++++++--------- src/linux_tcp/openssl.h | 3 ++ src/linux_tcp/sslconnected.h | 16 ++++++- src/linux_tcp/sslcontext.h | 4 +- src/linux_tcp/sslhandshake.h | 14 +++--- src/linux_tcp/sslshutdown.h | 4 +- src/linux_tcp/sslwrapper.h | 4 +- src/linux_tcp/tcpconnected.h | 2 +- src/linux_tcp/tcpinbuffer.h | 13 +++++- src/linux_tcp/tcpoutbuffer.h | 5 +- src/linux_tcp/tcpresolver.h | 2 +- 14 files changed, 118 insertions(+), 109 deletions(-) delete mode 100644 src/linux_tcp/library.h diff --git a/examples/libev.cpp b/examples/libev.cpp index 4d909f6..9ee0e2a 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -66,7 +66,7 @@ int main() MyHandler handler(loop); // init the SSL library -// SSL_library_init(); + SSL_library_init(); // make a connection AMQP::Address address("amqps://guest:guest@localhost/"); diff --git a/src/linux_tcp/function.h b/src/linux_tcp/function.h index b243643..fa1250d 100644 --- a/src/linux_tcp/function.h +++ b/src/linux_tcp/function.h @@ -119,11 +119,10 @@ private: public: /** * Constructor - * @param library The library to load the function from * @param name Name of the function */ - Function(void *library, const char *name) : - _method(dlsym(library, name)) {} + Function(const char *name) : + _method(dlsym(RTLD_DEFAULT, name)) {} /** * Destructor diff --git a/src/linux_tcp/library.h b/src/linux_tcp/library.h deleted file mode 100644 index 7330eda..0000000 --- a/src/linux_tcp/library.h +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Library.h - * - * The Library class is a wrapper around dlopen() - * - * @author Emiel Bruijntjes - * @copyright 2018 Copernica BV - */ - -/** - * Include guard - */ -#pragma once - -/** - * Begin of namespace - */ -namespace AMQP { - -/** - * Class definition - */ -class Library -{ -private: - /** - * The library handle - * @var void * - */ - void *_handle; - -public: - /** - * Constructor - */ - Library() : _handle(dlopen(nullptr, RTLD_NOW)) {} - - /** - * No copying - * @param that - */ - Library(const Library &that) = delete; - - /** - * Destructor - */ - virtual ~Library() - { - // close library - if (_handle) dlclose(_handle); - } - - /** - * Cast to the handle - * @return void * - */ - operator void * () { return _handle; } -}; - -/** - * End of namespace - */ -} - diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index 8e9139d..39bff69 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -11,7 +11,6 @@ */ #include "openssl.h" #include "function.h" -#include "library.h" /** * Begin of namespace @@ -19,16 +18,35 @@ namespace AMQP { namespace OpenSSL { /** - * Get the library handle - * @return void * + * Is the openssl library loaded? + * @return bool */ -static void *library() +bool valid() { - // create on instance - static Library instance; + // create a function + static Function func("SSL_CTX_new"); + + // we need a library + return func; +} + +/** + * Get the SSL_METHOD for outgoing connections + * @return SSL_METHOD * + */ +const SSL_METHOD *TLS_client_method() +{ + // create a function that loads the method + static Function func("TLS_client_method"); - // return the instance (it has a cast-to-void-ptr operator) - return instance; + // call the openssl function + if (func) return func(); + + // older openssl libraries do not have this function, so we try to load an other function + static Function old("SSLv23_client_method"); + + // call the old one + return old(); } /** @@ -39,7 +57,7 @@ static void *library() SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) { // create a function - static Function func(library(), "SSL_CTX_new"); + static Function func("SSL_CTX_new"); // call the openssl function return func(method); @@ -55,7 +73,7 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) int SSL_read(SSL *ssl, void *buf, int num) { // create a function - static Function func(library(), "SSL_read"); + static Function func("SSL_read"); // call the openssl function return func(ssl, buf, num); @@ -71,7 +89,7 @@ int SSL_read(SSL *ssl, void *buf, int num) int SSL_write(SSL *ssl, const void *buf, int num) { // create a function - static Function func(library(), "SSL_write"); + static Function func("SSL_write"); // call the openssl function return func(ssl, buf, num); @@ -86,12 +104,24 @@ int SSL_write(SSL *ssl, const void *buf, int num) int SSL_set_fd(SSL *ssl, int fd) { // create a function - static Function func(library(), "SSL_set_fd"); + static Function func("SSL_set_fd"); // call the openssl function return func(ssl, fd); } +/** + * Free an allocated ssl context + * @param ctx + */ +void SSL_CTX_free(SSL_CTX *ctx) +{ + // create a function + static Function func("SSL_CTX_free"); + + // call the openssl function + return func(ctx); +} /** * Free an allocated SSL structure @@ -101,7 +131,7 @@ int SSL_set_fd(SSL *ssl, int fd) void SSL_free(SSL *ssl) { // create a function - static Function func(library(), "SSL_free"); + static Function func("SSL_free"); // call the openssl function return func(ssl); @@ -115,7 +145,7 @@ void SSL_free(SSL *ssl) SSL *SSL_new(SSL_CTX *ctx) { // create a function - static Function func(library(), "SSL_new"); + static Function func("SSL_new"); // call the openssl function return func(ctx); @@ -129,7 +159,7 @@ SSL *SSL_new(SSL_CTX *ctx) int SSL_up_ref(SSL *ssl) { // create a function - static Function func(library(), "SSL_up_ref"); + static Function func("SSL_up_ref"); // call the openssl function if it exists if (func) return func(ssl); @@ -147,7 +177,7 @@ int SSL_up_ref(SSL *ssl) int SSL_shutdown(SSL *ssl) { // create a function - static Function func(library(), "SSL_shutdown"); + static Function func("SSL_shutdown"); // call the openssl function return func(ssl); @@ -160,7 +190,7 @@ int SSL_shutdown(SSL *ssl) void SSL_set_connect_state(SSL *ssl) { // create a function - static Function func(library(), "SSL_set_connect_state"); + static Function func("SSL_set_connect_state"); // call the openssl function func(ssl); @@ -175,7 +205,7 @@ void SSL_set_connect_state(SSL *ssl) int SSL_do_handshake(SSL *ssl) { // create a function - static Function func(library(), "SSL_do_handshake"); + static Function func("SSL_do_handshake"); // call the openssl function return func(ssl); @@ -183,18 +213,35 @@ int SSL_do_handshake(SSL *ssl) /** * Obtain result code for TLS/SSL I/O operation - * @param ssl SSL object - * @param ret the returned diagnostic value of SSL calls + * @param ssl SSL object + * @param ret the returned diagnostic value of SSL calls * @return int returns error values */ int SSL_get_error(const SSL *ssl, int ret) { // create a function - static Function func(library(), "SSL_get_error"); + static Function func("SSL_get_error"); // call the openssl function return func(ssl, ret); } + +/** + * Internal handling function for a ssl context + * @param ssl ssl context + * @param cmd command + * @param larg first arg + * @param parg second arg + * @return long + */ +long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg) +{ + // create a function + static Function func("SSL_ctrl"); + + // call the openssl function + return func(ssl, cmd, larg, parg); +} /** * End of namespace diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index 9a21dc9..387654d 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -33,6 +33,7 @@ bool valid(); /** * List of all wrapper methods that are in use inside AMQP-CPP */ +const SSL_METHOD *TLS_client_method(); SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); SSL *SSL_new(SSL_CTX *ctx); int SSL_do_handshake(SSL *ssl); @@ -43,7 +44,9 @@ int SSL_set_fd(SSL *ssl, int fd); int SSL_get_error(const SSL *ssl, int ret); int SSL_up_ref(SSL *ssl); void SSL_set_connect_state(SSL *ssl); +void SSL_CTX_free(SSL_CTX *ctx); void SSL_free(SSL *ssl); +long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg); /** * End of namespace diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 1ad708b..b6e5d21 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -148,7 +148,7 @@ private: TcpState *repeat(int result) { // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); + auto error = OpenSSL::SSL_get_error(_ssl, result); // check the error switch (error) { @@ -286,6 +286,20 @@ public: } } + /** + * Flush the connection, sent all buffered data to the socket + * @return TcpState new tcp state + */ + virtual TcpState *flush() override + { + // create an object to wait for the filedescriptor to becomes active + Wait wait(_socket); + + // @todo implementation + + return this; + } + /** * Send data over the connection * @param buffer buffer to send diff --git a/src/linux_tcp/sslcontext.h b/src/linux_tcp/sslcontext.h index a53c04d..2c2d8b3 100644 --- a/src/linux_tcp/sslcontext.h +++ b/src/linux_tcp/sslcontext.h @@ -35,7 +35,7 @@ public: * @param method * @throws std::runtime_error */ - SslContext(const SSL_METHOD *method) : _ctx(SSL_CTX_new(method)) + SslContext(const SSL_METHOD *method) : _ctx(OpenSSL::SSL_CTX_new(method)) { // report error if (_ctx == nullptr) throw std::runtime_error("failed to construct ssl context"); @@ -69,7 +69,7 @@ public: virtual ~SslContext() { // free resource (this updates the refcount -1, and may destruct it) - SSL_CTX_free(_ctx); + OpenSSL::SSL_CTX_free(_ctx); } /** diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index fb8af3b..4095664 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -111,18 +111,18 @@ public: */ SslHandshake(TcpConnection *connection, int socket, const std::string &hostname, TcpOutBuffer &&buffer, TcpHandler *handler) : TcpState(connection, handler), - _ssl(SslContext(SSLv23_client_method())), + _ssl(SslContext(OpenSSL::TLS_client_method())), _socket(socket), _out(std::move(buffer)) { // we will be using the ssl context as a client - SSL_set_connect_state(_ssl); + OpenSSL::SSL_set_connect_state(_ssl); // associate domain name with the connection - SSL_set_tlsext_host_name(_ssl, hostname.data()); + OpenSSL::SSL_ctrl(_ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void *)hostname.data()); // associate the ssl context with the socket filedescriptor - if (SSL_set_fd(_ssl, socket) == 0) throw std::runtime_error("failed to associate filedescriptor with ssl socket"); + if (OpenSSL::SSL_set_fd(_ssl, socket) == 0) throw std::runtime_error("failed to associate filedescriptor with ssl socket"); // we are going to wait until the socket becomes writable before we start the handshake _handler->monitor(_connection, _socket, writable); @@ -164,7 +164,7 @@ public: if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); + auto error = OpenSSL::SSL_get_error(_ssl, result); // check the error switch (error) { @@ -198,13 +198,13 @@ public: while (true) { // start the ssl handshake - int result = SSL_do_handshake(_ssl); + int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); + auto error = OpenSSL::SSL_get_error(_ssl, result); // check the error switch (error) diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h index 4a0315e..2361155 100644 --- a/src/linux_tcp/sslshutdown.h +++ b/src/linux_tcp/sslshutdown.h @@ -70,7 +70,7 @@ private: TcpState *repeat(int result) { // error was returned, so we must investigate what is going on - auto error = SSL_get_error(_ssl, result); + auto error = OpenSSL::SSL_get_error(_ssl, result); // check the error switch (error) { @@ -145,7 +145,7 @@ public: Monitor monitor(this); // close the connection - auto result = SSL_shutdown(_ssl); + auto result = OpenSSL::SSL_shutdown(_ssl); // if this is a success, we can proceed with the event loop if (result > 0) return proceed(); diff --git a/src/linux_tcp/sslwrapper.h b/src/linux_tcp/sslwrapper.h index 5c88810..6aa5edd 100644 --- a/src/linux_tcp/sslwrapper.h +++ b/src/linux_tcp/sslwrapper.h @@ -34,7 +34,7 @@ public: * Constructor * @param ctx */ - SslWrapper(SSL_CTX *ctx) : _ssl(SSL_new(ctx)) + SslWrapper(SSL_CTX *ctx) : _ssl(OpenSSL::SSL_new(ctx)) { // report error if (_ssl == nullptr) throw std::runtime_error("failed to construct ssl structure"); @@ -68,7 +68,7 @@ public: virtual ~SslWrapper() { // destruct object - SSL_free(_ssl); + OpenSSL::SSL_free(_ssl); } /** diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index 670b3db..4aeb8e8 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -225,7 +225,7 @@ public: // create an object to wait for the filedescriptor to becomes active Wait wait(_socket); - // keep running until the out buffer is empty + // keep running until the out buffer is not empty while (_out) { // poll the socket, is it already writable? diff --git a/src/linux_tcp/tcpinbuffer.h b/src/linux_tcp/tcpinbuffer.h index bd020e3..79b708f 100644 --- a/src/linux_tcp/tcpinbuffer.h +++ b/src/linux_tcp/tcpinbuffer.h @@ -121,8 +121,17 @@ public: */ ssize_t receivefrom(SSL *ssl, uint32_t expected) { - // @todo implementation - return 0; + // number of bytes to that still fit in the buffer + size_t bytes = expected - _size; + + // read data + auto result = OpenSSL::SSL_read(ssl, (void *)(_data + _size), bytes); + + // update total buffer size on success + if (result > 0) _size += result; + + // done + return result; } /** diff --git a/src/linux_tcp/tcpoutbuffer.h b/src/linux_tcp/tcpoutbuffer.h index 8d18526..31dbd27 100644 --- a/src/linux_tcp/tcpoutbuffer.h +++ b/src/linux_tcp/tcpoutbuffer.h @@ -18,7 +18,8 @@ */ #include #include -#include +#include "openssl.h" + /** * FIONREAD on Solaris is defined elsewhere */ @@ -284,7 +285,7 @@ public: if (buffers == 0) return 0; // send the data - auto result = SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len); + auto result = OpenSSL::SSL_write(ssl, buffer[0].iov_base, buffer[0].iov_len); // on success we shrink the buffer if (result > 0) shrink(result); diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index be45c4f..ee24e2d 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -93,7 +93,7 @@ private: try { // check if we support openssl in the first place - if (!OpenSSL::valid()) throw std::runtime_error("Secure connection cannot be established: the application has no access to openssl"); + if (!OpenSSL::valid()) throw std::runtime_error("Secure connection cannot be established: libssl.so cannot be loaded"); // get address info AddressInfo addresses(_hostname.data(), _port); From 8065cfe9400f6875f15a7496190bbb554e5e68db Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 Mar 2018 22:06:41 +0100 Subject: [PATCH 137/168] update docblock --- src/linux_tcp/function.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/linux_tcp/function.h b/src/linux_tcp/function.h index fa1250d..cb8bf89 100644 --- a/src/linux_tcp/function.h +++ b/src/linux_tcp/function.h @@ -1,15 +1,12 @@ /** * Function.h * - * When you want to retrieve a function from a dynamicallly loaded - * library, you normally use the dlsym() function for that. The - * Function object is a little more convenient: - * - * // open the library - * void *lib = dlopen("library.so"); + * When you want to call a function only if it is already linked into + * the program space. you would normally use the dlsym() function for + * that. Th Function object in this file is a little more convenient: * * // get the function object - * Function func magic_open(library, "my_function"); + * Function func func("example_function"); * * // call the function * int result = func(123); @@ -18,7 +15,6 @@ * @copyright 2018 Copernica BV */ - /** * Include guard */ From caa7277bb106a753fc800a50a98a21ea53e6411a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 7 Mar 2018 16:17:03 +0100 Subject: [PATCH 138/168] update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33538a9..d9684d7 100644 --- a/README.md +++ b/README.md @@ -102,8 +102,10 @@ and `make install` for that. When you compile an application that uses the AMQP-CPP library, do not forget to link with the library. For gcc and clang the linker flag is -lamqpcpp. If you use the fullblown version of AMQP-CPP (with the TCP module), you also -need to pass a -lpthread linker flag, because the TCP module uses a thread -for running an asynchronous and non-blocking DNS hostname lookup. +need to pass the -lpthread and -ldl linker flags, because the TCP module uses a +thread for running an asynchronous and non-blocking DNS hostname lookup, and it +optionally dynamically opens the openssl library if a secure connection to +RabbitMQ has to be set up. HOW TO USE AMQP-CPP From f23bcf19f15c348ad319af8473f8e4afaeddc601 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 10:02:42 +0100 Subject: [PATCH 139/168] improved docblocks in tcpstate.h header file, when an error or closed is reported to user space, the _handler variable is now reset to prevent that it will be used to report more than once (we still need to check if this does not trigger other errors), and the state object is no longer destructed after a reportClosed() call, so that it can clean up nicely (which we need to the tls shutdown anyway) --- include/amqpcpp/linux_tcp/tcpconnection.h | 2 +- src/linux_tcp/openssl.cpp | 14 ++++ src/linux_tcp/openssl.h | 1 + src/linux_tcp/sslconnected.h | 68 ++++++++++++++++--- src/linux_tcp/sslhandshake.h | 6 +- src/linux_tcp/tcpconnected.h | 11 ++- src/linux_tcp/tcpconnection.cpp | 23 +++---- src/linux_tcp/tcpresolver.h | 6 +- src/linux_tcp/tcpstate.h | 82 ++++++++++++++++++----- 9 files changed, 160 insertions(+), 53 deletions(-) diff --git a/include/amqpcpp/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h index f7d3212..72b43df 100644 --- a/include/amqpcpp/linux_tcp/tcpconnection.h +++ b/include/amqpcpp/linux_tcp/tcpconnection.h @@ -5,7 +5,7 @@ * IO between the client application and the RabbitMQ server. * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index 39bff69..234b2e7 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -211,6 +211,20 @@ int SSL_do_handshake(SSL *ssl) return func(ssl); } +/** + * Obtain shutdown statue for TLS/SSL I/O operation + * @param ssl SSL object + * @return int returns error values + */ +int SSL_get_shutdown(const SSL *ssl) +{ + // create a function + static Function func("SSL_get_shutdown"); + + // call the openssl function + return func(ssl); +} + /** * Obtain result code for TLS/SSL I/O operation * @param ssl SSL object diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index 387654d..bd67e5b 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -41,6 +41,7 @@ int SSL_read(SSL *ssl, void *buf, int num); int SSL_write(SSL *ssl, const void *buf, int num); int SSL_shutdown(SSL *ssl); int SSL_set_fd(SSL *ssl, int fd); +int SSL_get_shutdown(const SSL *ssl); int SSL_get_error(const SSL *ssl, int ret); int SSL_up_ref(SSL *ssl); void SSL_set_connect_state(SSL *ssl); diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index b6e5d21..5bbe81a 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -149,23 +149,54 @@ private: { // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); - + + // create a monitor because the handler could make things ugly + Monitor monitor(this); + // check the error switch (error) { case SSL_ERROR_WANT_READ: // the operation must be repeated when readable _handler->monitor(_connection, _socket, readable); - return this; + + // allow chaining + return monitor.valid() ? this : nullptr; case SSL_ERROR_WANT_WRITE: // wait until socket becomes writable again _handler->monitor(_connection, _socket, readable | writable); - return this; + + // allow chaining + return monitor.valid() ? this : nullptr; + + case SSL_ERROR_NONE: + // turns out no error occured, an no action has to be rescheduled + _handler->monitor(_connection, _socket, _out ? readable | writable : readable); + + // we're ready for the next instruction from userspace + _state = state_idle; + + // allow chaining + return monitor.valid() ? this : nullptr; default: + // is the peer trying to shutdown? (we dont expect this) + bool shutdown = OpenSSL::SSL_get_shutdown(_ssl); - // @todo check how to handle this - return this; + // send back a nice shutdown + if (shutdown) OpenSSL::SSL_shutdown(_ssl); + + // tell the handler + _handler->onError(_connection, "ssl error"); + + // no need to chain if object is already destructed + if (!monitor) return nullptr; + + // create a new new object + //return shutdown ? + + // allow chaining + return nullptr; //monitor.valid() ? new TcpClosed(this) : nullptr; } } @@ -258,9 +289,6 @@ public: // the socket must be the one this connection writes to if (fd != _socket) return this; - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - // are we busy with sending or receiving data? if (_state == state_sending) { @@ -288,14 +316,34 @@ public: /** * Flush the connection, sent all buffered data to the socket + * @param monitor Object to check if connection still exists * @return TcpState new tcp state */ - virtual TcpState *flush() override + virtual TcpState *flush(const Monitor &monitor) override { + // we are not going to do this is object is busy reading + if (_state == state_receiving) return this; + // create an object to wait for the filedescriptor to becomes active Wait wait(_socket); - // @todo implementation + // keep looping while we have an outgoing buffer + while (_out) + { + // try to send more data from the outgoing buffer + auto result = _out.sendto(_ssl); + + // go to the next state + auto *state = result > 0 ? proceed() : repeat(result); + + return state; + +// if (result > 0) return proceed(); +// +// // the operation failed, we may have to repeat our call +// else return repeat(result); + } + return this; } diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index 4095664..9a4be90 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -148,11 +148,12 @@ public: /** * Process the filedescriptor in the object + * @param monitor Object to check if connection still exists * @param fd Filedescriptor that is active * @param flags AMQP::readable and/or AMQP::writable * @return New state object */ - virtual TcpState *process(int fd, int flags) override + virtual TcpState *process(const Monitor &monitor, int fd, int flags) override { // must be the socket if (fd != _socket) return this; @@ -187,9 +188,10 @@ public: /** * Flush the connection, sent all buffered data to the socket + * @param monitor Object to check if connection still exists * @return TcpState new tcp state */ - virtual TcpState *flush() override + virtual TcpState *flush(const Monitor &monitor) override { // create an object to wait for the filedescriptor to becomes active Wait wait(_socket); diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index 4aeb8e8..bb0ac5d 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -128,18 +128,16 @@ public: /** * Process the filedescriptor in the object + * @param monitor Monitor to check if the object is still alive * @param fd Filedescriptor that is active * @param flags AMQP::readable and/or AMQP::writable * @return New state object */ - virtual TcpState *process(int fd, int flags) override + virtual TcpState *process(const Monitor &monitor, int fd, int flags) override { // must be the socket if (fd != _socket) return this; - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - // can we write more data to the socket? if (flags & writable) { @@ -218,9 +216,10 @@ public: /** * Flush the connection, sent all buffered data to the socket + * @param monitor Object to check if connection still lives * @return TcpState new tcp state */ - virtual TcpState *flush() override + virtual TcpState *flush(const Monitor &monitor) override { // create an object to wait for the filedescriptor to becomes active Wait wait(_socket); @@ -232,7 +231,7 @@ public: if (!wait.writable()) return this; // socket is writable, send as much data as possible - auto *newstate = process(_socket, writable); + auto *newstate = process(monitor, _socket, writable); // are we done if (newstate != this) return newstate; diff --git a/src/linux_tcp/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp index f0663c3..6aeefb8 100644 --- a/src/linux_tcp/tcpconnection.cpp +++ b/src/linux_tcp/tcpconnection.cpp @@ -4,7 +4,7 @@ * Implementation file for the TCP connection * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -12,6 +12,7 @@ */ #include "includes.h" #include "tcpresolver.h" +#include "tcpstate.h" /** * Set up namespace @@ -24,7 +25,7 @@ namespace AMQP { * @param hostname The address to connect to */ TcpConnection::TcpConnection(TcpHandler *handler, const Address &address) : - _state(new TcpResolver(this, address.hostname(), address.port(), address.secure(), handler)), +// _state(new TcpResolver(this, address.hostname(), address.port(), address.secure(), handler)), _connection(this, address.login(), address.vhost()) {} /** @@ -48,11 +49,11 @@ int TcpConnection::fileno() const */ void TcpConnection::process(int fd, int flags) { - // monitor the object for destruction - Monitor monitor{ this }; + // monitor the object for destruction, because you never know what the user + Monitor monitor(this); // pass on the the state, that returns a new impl - auto *result = _state->process(fd, flags); + auto *result = _state->process(monitor, fd, flags); // are we still valid if (!monitor.valid()) return; @@ -78,7 +79,7 @@ void TcpConnection::flush() while (true) { // flush the object - auto *newstate = _state->flush(); + auto *newstate = _state->flush(monitor); // done if object no longer exists if (!monitor.valid()) return; @@ -136,7 +137,7 @@ void TcpConnection::onError(Connection *connection, const char *message) auto ptr = std::move(_state); // object is now in a closed state - _state.reset(new TcpClosed(ptr.get())); + //_state.reset(new TcpClosed(ptr.get())); // tell the implementation to report the error ptr->reportError(message); @@ -158,14 +159,8 @@ void TcpConnection::onConnected(Connection *connection) */ void TcpConnection::onClosed(Connection *connection) { - // current object is going to be removed, but we have to keep it for a while - auto ptr = std::move(_state); - - // object is now in a closed state - _state.reset(new TcpClosed(ptr.get())); - // tell the implementation to report that connection is closed now - ptr->reportClosed(); + _state->reportClosed(); } /** diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index ee24e2d..93399e3 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -212,11 +212,12 @@ public: /** * Wait for the resolver to be ready + * @param monitor Object to check if connection still exists * @param fd The filedescriptor that is active * @param flags Flags to indicate that fd is readable and/or writable * @return New implementation object */ - virtual TcpState *process(int fd, int flags) override + virtual TcpState *process(const Monitor &monitor, int fd, int flags) override { // only works if the incoming pipe is readable if (fd != _pipe.in() || !(flags & readable)) return this; @@ -227,9 +228,10 @@ public: /** * Flush state / wait for the connection to complete + * @param monitor Object to check if connection still exists * @return New implementation object */ - virtual TcpState *flush() override + virtual TcpState *flush(const Monitor &monitor) override { // just wait for the other thread to be ready _thread.join(); diff --git a/src/linux_tcp/tcpstate.h b/src/linux_tcp/tcpstate.h index 93ddc21..2b5dd3f 100644 --- a/src/linux_tcp/tcpstate.h +++ b/src/linux_tcp/tcpstate.h @@ -36,6 +36,22 @@ protected: TcpHandler *_handler; protected: + /** + * Helper function to reset the handler, and to return the old handler object + * @return TcpHandler* User-supplied handler that was just reset + */ + TcpHandler *reset(TcpHandler *handler) + { + // remember old handler + auto *oldhandler = _handler; + + // install the new handler + _handler = handler; + + // return the old handler + return oldhandler; + } + /** * Protected constructor * @param connection Original TCP connection object @@ -64,12 +80,19 @@ public: virtual int fileno() const { return -1; } /** - * Process the filedescriptor in the object + * Process the filedescriptor in the object + * + * This method should return the handler object that will be responsible for + * all future readable/writable events for the file descriptor, or nullptr + * if the underlying connection object has already been destructed by the + * user and it would be pointless to set up a new handler. + * + * @param monitor Monitor that can be used to check if the tcp connection is still alive * @param fd The filedescriptor that is active * @param flags AMQP::readable and/or AMQP::writable * @return New implementation object */ - virtual TcpState *process(int fd, int flags) + virtual TcpState *process(const Monitor &monitor, int fd, int flags) { // default implementation does nothing and preserves same implementation return this; @@ -77,8 +100,8 @@ public: /** * Send data over the connection - * @param buffer buffer to send - * @param size size of the buffer + * @param buffer Buffer to send + * @param size Size of the buffer */ virtual void send(const char *buffer, size_t size) { @@ -86,7 +109,25 @@ public: } /** - * Report that heartbeat negotiation is going on + * Flush the connection, all outgoing operations should be completed. + * + * If the state changes during the operation, the new state object should + * be returned instead, or nullptr if the user has closed the connection + * in the meantime. If the connection object got destructed by a user space + * call, this method should return nullptr. A monitor object is pass in to + * allow the flush() method to check if the connection still exists. + * + * If this object returns a new state object (instead of "this"), the + * connection object will immediately proceed with calling flush() on that + * new state object too. + * + * @param monitor Monitor that can be used to check if the tcp connection is still alive + * @return TcpState New implementation object + */ + virtual TcpState *flush(const Monitor &monitor) { return this; } + + /** + * Report to the handler that heartbeat negotiation is going on * @param heartbeat suggested heartbeat * @return uint16_t accepted heartbeat */ @@ -97,25 +138,25 @@ public: } /** - * Flush the connection - * @return TcpState new implementation object - */ - virtual TcpState *flush() { return this; } - - /** - * Report to the handler that the object is in an error state + * Report to the handler that the object is in an error state. + * + * This is the last method to be called on the handler object, from now on + * the handler will no longer be called to report things to user space. + * The state object itself stays active, and further calls to process() + * may be possible. + * * @param error */ - void reportError(const char *error) + virtual void reportError(const char *error) { // pass to handler - _handler->onError(_connection, error); + reset(nullptr)->onError(_connection, error); } /** * Report that a heartbeat frame was received */ - void reportHeartbeat() + virtual void reportHeartbeat() { // pass to handler _handler->onHeartbeat(_connection); @@ -124,19 +165,24 @@ public: /** * Report to the handler that the connection is ready for use */ - void reportConnected() + virtual void reportConnected() { // pass to handler _handler->onConnected(_connection); } /** - * Report to the handler that the connection was nicely closed + * Report to the handler that the connection was correctly closed, after + * the user has called the Connection::close() method. The underlying TCP + * connection still has to be closed. + * + * This is the last method that is called on the object, from now on no + * other methods may be called on the _handler variable. */ virtual void reportClosed() { // pass to handler - _handler->onClosed(_connection); + reset(nullptr)->onClosed(_connection); } }; From 9a08c64ff7ca9f47b718c665c61a7dfa3088afcb Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 10:37:49 +0100 Subject: [PATCH 140/168] more elegant close procedure for tcp connections --- src/linux_tcp/tcpconnected.h | 95 +++++++++++++++++++++++++++--------- src/linux_tcp/tcpresolver.h | 2 +- src/linux_tcp/tcpstate.h | 20 +------- 3 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index bb0ac5d..f05d532 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -54,8 +54,36 @@ private: * @var size_t */ size_t _reallocate = 0; + + /** + * Have we already made the last report to the user (about an error or closed connection?) + * @var bool + */ + bool _finalized = false; + /** + * Close the connection + * @return bool + */ + bool close() + { + // do nothing if already closed + if (_socket < 0) return false; + + // and stop monitoring it + _handler->monitor(_connection, _socket, 0); + + // close the socket + ::close(_socket); + + // forget filedescriptor + _socket = -1; + + // done + return true; + } + /** * Helper method to report an error * @return bool Was an error reported? @@ -65,6 +93,16 @@ private: // some errors are ok and do not (necessarily) mean that we're disconnected if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false; + // connection can be closed now + close(); + + // if the user has already been notified, we do not have to do anything else + if (_finalized) return true; + + // update the _finalized member before we make the call to user space because + // the user space may destruct this object + _finalized = true; + // we have an error - report this to the user _handler->onError(_connection, strerror(errno)); @@ -110,14 +148,8 @@ public: */ virtual ~TcpConnected() noexcept { - // skip if handler is already forgotten - if (_handler == nullptr) return; - - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - // close the socket - close(_socket); + close(); } /** @@ -145,7 +177,7 @@ public: auto result = _out.sendto(_socket); // are we in an error state? - if (result < 0 && reportError()) return nextState(monitor); + if (result < 0 && reportError()) return nextState(monitor); // if buffer is empty by now, we no longer have to check for // writability, but only for readability @@ -255,28 +287,47 @@ public: return TcpState::reportNegotiate(heartbeat); } + /** + * Report to the handler that the object is in an error state. + * @param error + */ + virtual void reportError(const char *error) + { + // close the socket + close(); + + // if the user was already notified of an final state, we do not have to proceed + if (_finalized) return; + + // remember that this is the final call to user space + _finalized = true; + + // pass to handler + _handler->onError(_connection, error); + } + /** * Report to the handler that the connection was nicely closed + * This is the counter-part of the connection->close() call. */ virtual void reportClosed() override { - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); + // we will shutdown the socket in a very elegant way, we notify the peer + // that we will not be sending out more write operations + shutdown(_socket, SHUT_WR); + + // we still monitor the socket for readability to see if our close call was + // confirmed by the peer + _handler->monitor(_connection, _socket, readable); - // close the socket - close(_socket); + // if the user was already notified of an final state, we do not have to proceed + if (_finalized) return; - // socket is closed now - _socket = -1; + // remember that this is the final call to user space + _finalized = true; - // copy the handler (if might destruct this object) - auto *handler = _handler; - - // reset member before the handler can make a mess of it - _handler = nullptr; - - // notify to handler - handler->onClosed(_connection); + // pass to handler + _handler->onClosed(_connection); } }; diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 93399e3..50516aa 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -194,7 +194,7 @@ public: if (_socket >= 0) { // if we need a secure connection, we move to the tls handshake - // @todo catch exception + // @todo catch possible exception if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler); // otherwise we have a valid regular tcp connection diff --git a/src/linux_tcp/tcpstate.h b/src/linux_tcp/tcpstate.h index 2b5dd3f..1c3a280 100644 --- a/src/linux_tcp/tcpstate.h +++ b/src/linux_tcp/tcpstate.h @@ -36,22 +36,6 @@ protected: TcpHandler *_handler; protected: - /** - * Helper function to reset the handler, and to return the old handler object - * @return TcpHandler* User-supplied handler that was just reset - */ - TcpHandler *reset(TcpHandler *handler) - { - // remember old handler - auto *oldhandler = _handler; - - // install the new handler - _handler = handler; - - // return the old handler - return oldhandler; - } - /** * Protected constructor * @param connection Original TCP connection object @@ -150,7 +134,7 @@ public: virtual void reportError(const char *error) { // pass to handler - reset(nullptr)->onError(_connection, error); + _handler->onError(_connection, error); } /** @@ -182,7 +166,7 @@ public: virtual void reportClosed() { // pass to handler - reset(nullptr)->onClosed(_connection); + _handler->onClosed(_connection); } }; From cea5a54487aca62160bdea2f9caf405a86b8b818 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 10:44:42 +0100 Subject: [PATCH 141/168] fix bug: error about missing openssl was also reported when not even opening a secure connection --- examples/libev.cpp | 16 +++++++++++++--- src/linux_tcp/tcpconnection.cpp | 10 ++-------- src/linux_tcp/tcpresolver.h | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 9ee0e2a..2868e29 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -66,20 +66,30 @@ int main() MyHandler handler(loop); // init the SSL library - SSL_library_init(); +// SSL_library_init(); // make a connection - AMQP::Address address("amqps://guest:guest@localhost/"); + AMQP::Address address("amqp://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](const std::string &name, uint32_t messagecount, uint32_t consumercount) { + channel.declareQueue(AMQP::exclusive).onSuccess([&connection, &channel](const std::string &name, uint32_t messagecount, uint32_t consumercount) { // report the name of the temporary queue std::cout << "declared queue " << name << 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(); + }); }); // run the loop diff --git a/src/linux_tcp/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp index 6aeefb8..67b4f0f 100644 --- a/src/linux_tcp/tcpconnection.cpp +++ b/src/linux_tcp/tcpconnection.cpp @@ -25,7 +25,7 @@ namespace AMQP { * @param hostname The address to connect to */ TcpConnection::TcpConnection(TcpHandler *handler, const Address &address) : -// _state(new TcpResolver(this, address.hostname(), address.port(), address.secure(), handler)), + _state(new TcpResolver(this, address.hostname(), address.port(), address.secure(), handler)), _connection(this, address.login(), address.vhost()) {} /** @@ -133,14 +133,8 @@ void TcpConnection::onHeartbeat(Connection *connection) */ void TcpConnection::onError(Connection *connection, const char *message) { - // current object is going to be removed, but we have to keep it for a while - auto ptr = std::move(_state); - - // object is now in a closed state - //_state.reset(new TcpClosed(ptr.get())); - // tell the implementation to report the error - ptr->reportError(message); + _state->reportError(message); } /** diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 50516aa..97e8916 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -93,7 +93,7 @@ private: try { // check if we support openssl in the first place - if (!OpenSSL::valid()) throw std::runtime_error("Secure connection cannot be established: libssl.so cannot be loaded"); + if (_secure && !OpenSSL::valid()) throw std::runtime_error("Secure connection cannot be established: libssl.so cannot be loaded"); // get address info AddressInfo addresses(_hostname.data(), _port); From 25df834e744172f3c9f451c6a4c785b2ac07cc9c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 11:04:39 +0100 Subject: [PATCH 142/168] prevent ssl errors or reference count errors when copying around ssl objects --- src/linux_tcp/openssl.h | 1 - src/linux_tcp/sslconnected.h | 6 +++--- src/linux_tcp/sslcontext.h | 21 +++------------------ src/linux_tcp/sslhandshake.h | 4 ++-- src/linux_tcp/sslshutdown.h | 4 ++-- src/linux_tcp/sslwrapper.h | 30 ++++++++++++++---------------- 6 files changed, 24 insertions(+), 42 deletions(-) diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index bd67e5b..23c4ae3 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -43,7 +43,6 @@ int SSL_shutdown(SSL *ssl); int SSL_set_fd(SSL *ssl, int fd); int SSL_get_shutdown(const SSL *ssl); int SSL_get_error(const SSL *ssl, int ret); -int SSL_up_ref(SSL *ssl); void SSL_set_connect_state(SSL *ssl); void SSL_CTX_free(SSL_CTX *ctx); void SSL_free(SSL *ssl); diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 5bbe81a..9015e9f 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -125,7 +125,7 @@ private: _handler = nullptr; // start the state that closes the connection - return new SslShutdown(_connection, _socket, _ssl, _handler); + return new SslShutdown(_connection, _socket, std::move(_ssl), _handler); } else { @@ -245,9 +245,9 @@ public: * @param buffer The buffer that was already built * @param handler User-supplied handler object */ - SslConnected(TcpConnection *connection, int socket, const SslWrapper &ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : + SslConnected(TcpConnection *connection, int socket, SslWrapper &&ssl, TcpOutBuffer &&buffer, TcpHandler *handler) : TcpState(connection, handler), - _ssl(ssl), + _ssl(std::move(ssl)), _socket(socket), _out(std::move(buffer)), _in(4096), diff --git a/src/linux_tcp/sslcontext.h b/src/linux_tcp/sslcontext.h index 2c2d8b3..bcf8c90 100644 --- a/src/linux_tcp/sslcontext.h +++ b/src/linux_tcp/sslcontext.h @@ -42,26 +42,11 @@ public: } /** - * Constructor that wraps around an existing context - * @param context - */ - SslContext(SSL_CTX *context) : _ctx(context) - { - // increment refcount - // @todo fix this - //SSL_ctx_up_ref(context); - } - - /** - * Copy constructor + * Copy constructor is delete because the object is refcounted, + * and we do not have a decent way to update the refcount in openssl 1.0 * @param that */ - SslContext(SslContext &that) : _ctx(that._ctx) - { - // increment refcount - // @todo fix this - //SSL_ctx_up_ref(context); - } + SslContext(SslContext &that) = delete; /** * Destructor diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index 9a4be90..5bbde81 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -162,7 +162,7 @@ public: int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); + if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); @@ -203,7 +203,7 @@ public: int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new SslConnected(_connection, _socket, _ssl, std::move(_out), _handler)); + if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h index 2361155..bacc81e 100644 --- a/src/linux_tcp/sslshutdown.h +++ b/src/linux_tcp/sslshutdown.h @@ -100,9 +100,9 @@ public: * @param ssl The SSL structure * @param handler User-supplied handler object */ - SslShutdown(TcpConnection *connection, int socket, const SslWrapper &ssl, TcpHandler *handler) : + SslShutdown(TcpConnection *connection, int socket, SslWrapper &&ssl, TcpHandler *handler) : TcpState(connection, handler), - _ssl(ssl), + _ssl(std::move(ssl)), _socket(socket) { // tell the handler to monitor the socket if there is an out diff --git a/src/linux_tcp/sslwrapper.h b/src/linux_tcp/sslwrapper.h index 6aa5edd..79a8f7b 100644 --- a/src/linux_tcp/sslwrapper.h +++ b/src/linux_tcp/sslwrapper.h @@ -41,25 +41,20 @@ public: } /** - * Wrapper constructor - * @param ssl - */ - SslWrapper(SSL *ssl) : _ssl(ssl) - { - // one more reference - // @todo fix this - //CRYPTO_add(_ssl); - } - - /** - * Copy constructor + * Copy constructor is removed because openssl 1.0 has no way to up refcount + * (otherwise we could be safely copying objects around) * @param that */ - SslWrapper(const SslWrapper &that) : _ssl(that._ssl) + SslWrapper(const SslWrapper &that) = delete; + + /** + * Move constructor + * @param that + */ + SslWrapper(SslWrapper &&that) : _ssl(that._ssl) { - // one more reference - // @todo fix this - //SSL_up_ref(_ssl); + // invalidate other object + that._ssl = nullptr; } /** @@ -67,6 +62,9 @@ public: */ virtual ~SslWrapper() { + // do nothing if already moved away + if (_ssl == nullptr) return; + // destruct object OpenSSL::SSL_free(_ssl); } From 38d504d8a0b762e8c765d6057487c46a9d0a6f04 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 11:20:07 +0100 Subject: [PATCH 143/168] reportClosed() is never called when still busy with ssl handshake, so that method has been removed, and better handling of scenarion in which user space destructs connections in onError() implementation --- src/linux_tcp/sslhandshake.h | 40 +++++++++++------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index 5bbde81..d7518e1 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -67,18 +67,26 @@ private: /** * Helper method to report an error + * @param monitor * @return TcpState* */ - TcpState *reportError() + TcpState *reportError(const Monitor &monitor) { // we are no longer interested in any events for this socket _handler->monitor(_connection, _socket, 0); + // close the socket + close(_socket); + + // forget filedescriptor + _socket = -1; + // we have an error - report this to the user _handler->onError(_connection, "failed to setup ssl connection"); - // done, go to the closed state - return new TcpClosed(_connection, _handler); + // done, go to the closed state (plus check if connection still exists, because + // after the onError() call the user space program may have destructed that object) + return monitor.valid() ? new TcpClosed(this) : nullptr; } /** @@ -136,7 +144,7 @@ public: // leap out if socket is invalidated if (_socket < 0) return; - // close the socket + // the object got destructed without moving to a new state, this is normally close(_socket); } @@ -221,30 +229,6 @@ public: } } } - - /** - * Report to the handler that the connection was nicely closed - */ - virtual void reportClosed() override - { - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - - // close the socket - close(_socket); - - // socket is closed now - _socket = -1; - - // copy the handler (if might destruct this object) - auto *handler = _handler; - - // reset member before the handler can make a mess of it - _handler = nullptr; - - // notify to handler - handler->onClosed(_connection); - } }; /** From bc4db8d8feadc9da9226643cab6716d1fa637417 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 12:11:45 +0100 Subject: [PATCH 144/168] elegant shutdown for ssl connections --- src/linux_tcp/sslconnected.h | 146 +++++++++++++++++++++-------------- src/linux_tcp/sslhandshake.h | 4 +- src/linux_tcp/sslshutdown.h | 63 +++++++++------ src/linux_tcp/tcpconnected.h | 2 +- 4 files changed, 130 insertions(+), 85 deletions(-) diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 9015e9f..1acb052 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -66,10 +66,16 @@ private: } _state; /** - * Is the object already closed? + * Should we close the connection after we've finished all operations? * @var bool */ bool _closed = false; + + /** + * Have we reported the final instruction to the user? + * @var bool + */ + bool _finalized = false; /** * Cached reallocation instruction @@ -79,25 +85,37 @@ private: /** - * Helper method to report an error - * @return bool Was an error reported? + * Close the connection + * @return bool */ - bool reportError() + bool close() { - // we have an error - report this to the user - _handler->onError(_connection, strerror(errno)); + // do nothing if already closed + if (_socket < 0) return false; + + // and stop monitoring it + _handler->monitor(_connection, _socket, 0); + + // close the socket + ::close(_socket); + + // forget filedescriptor + _socket = -1; // done return true; } - + /** - * Construct the next state + * Construct the final state * @param monitor Object that monitors whether connection still exists * @return TcpState* */ - TcpState *nextState(const Monitor &monitor) + TcpState *finalstate(const Monitor &monitor) { + // close the socket if it is still open + close(); + // if the object is still in a valid state, we can move to the close-state, // otherwise there is no point in moving to a next state return monitor.valid() ? new TcpClosed(this) : nullptr; @@ -121,11 +139,11 @@ private: } else if (_closed) { - // we forget the current handler to prevent that things are changed - _handler = nullptr; + // we forget the current socket to prevent that it gets destructed + _socket = -1; // start the state that closes the connection - return new SslShutdown(_connection, _socket, std::move(_ssl), _handler); + return new SslShutdown(_connection, _socket, std::move(_ssl), _finalized, _handler); } else { @@ -141,18 +159,16 @@ private: } /** - * Method to repeat the previous call + * Method to repeat the previous call\ + * @param monitor monitor to check if connection object still exists * @param result result of an earlier openssl operation * @return TcpState* */ - TcpState *repeat(int result) + TcpState *repeat(const Monitor &monitor, int result) { // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); - // create a monitor because the handler could make things ugly - Monitor monitor(this); - // check the error switch (error) { case SSL_ERROR_WANT_READ: @@ -170,50 +186,42 @@ private: return monitor.valid() ? this : nullptr; case SSL_ERROR_NONE: - // turns out no error occured, an no action has to be rescheduled - _handler->monitor(_connection, _socket, _out ? readable | writable : readable); - // we're ready for the next instruction from userspace _state = state_idle; + // turns out no error occured, an no action has to be rescheduled + _handler->monitor(_connection, _socket, _out || _closed ? readable | writable : readable); + // allow chaining return monitor.valid() ? this : nullptr; default: - // is the peer trying to shutdown? (we dont expect this) - bool shutdown = OpenSSL::SSL_get_shutdown(_ssl); - - // send back a nice shutdown - if (shutdown) OpenSSL::SSL_shutdown(_ssl); - + // if we have already reported an error to user space, we can go to the final state right away + if (_finalized) return finalstate(monitor); + + // remember that we've sent out an error + _finalized = true; + // tell the handler _handler->onError(_connection, "ssl error"); - // no need to chain if object is already destructed - if (!monitor) return nullptr; - - // create a new new object - //return shutdown ? - - // allow chaining - return nullptr; //monitor.valid() ? new TcpClosed(this) : nullptr; + // go to the final state + return finalstate(monitor); } } /** * Parse the received buffer - * @param size + * @param monitor object to check the existance of the connection object + * @param size number of bytes available * @return TcpState */ - TcpState *parse(size_t size) + TcpState *parse(const Monitor &monitor, size_t size) { // we need a local copy of the buffer - because it is possible that "this" // object gets destructed halfway through the call to the parse() method TcpInBuffer buffer(std::move(_in)); - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - // parse the buffer auto processed = _connection->parse(buffer); @@ -227,13 +235,16 @@ private: _in = std::move(buffer); // do we have to reallocate? - if (_reallocate) _in.reallocate(_reallocate); + if (!_reallocate) return proceed(); + + // reallocate the buffer + _in.reallocate(_reallocate); // we can remove the reallocate instruction _reallocate = 0; // done - return this; + return proceed(); } public: @@ -254,7 +265,7 @@ public: _state(_out ? state_sending : state_idle) { // tell the handler to monitor the socket if there is an out - _handler->monitor(_connection, _socket, _state == state_sending ? writable : readable); + _handler->monitor(_connection, _socket, _state == state_sending ? readable | writable : readable); } /** @@ -262,14 +273,8 @@ public: */ virtual ~SslConnected() noexcept { - // skip if handler is already forgotten - if (_handler == nullptr) return; - - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - // close the socket - close(_socket); + close(); } /** @@ -279,12 +284,13 @@ public: virtual int fileno() const override { return _socket; } /** - * Process the filedescriptor in the object + * Process the filedescriptor in the object + * @param monitor Object that can be used to find out if connection object is still alive * @param fd The filedescriptor that is active * @param flags AMQP::readable and/or AMQP::writable * @return New implementation object */ - virtual TcpState *process(int fd, int flags) + virtual TcpState *process(const Monitor &monitor, int fd, int flags) override { // the socket must be the one this connection writes to if (fd != _socket) return this; @@ -299,7 +305,7 @@ public: if (result > 0) return proceed(); // the operation failed, we may have to repeat our call - else return repeat(result); + else return repeat(monitor, result); } else { @@ -307,10 +313,10 @@ public: auto result = _in.receivefrom(_ssl, _connection->expected()); // if this is a success, we may have to update the monitor - if (result > 0) return parse(result); + if (result > 0) return parse(monitor, result); // the operation failed, we may have to repeat our call - else return repeat(result); + else return repeat(monitor, result); } } @@ -334,7 +340,7 @@ public: auto result = _out.sendto(_ssl); // go to the next state - auto *state = result > 0 ? proceed() : repeat(result); + auto *state = result > 0 ? proceed() : repeat(monitor, result); return state; @@ -353,7 +359,7 @@ public: * @param buffer buffer to send * @param size size of the buffer */ - virtual void send(const char *buffer, size_t size) + virtual void send(const char *buffer, size_t size) override { // put the data in the outgoing buffer _out.add(buffer, size); @@ -382,20 +388,40 @@ public: // pass to base return TcpState::reportNegotiate(heartbeat); } + + /** + * Report a connection error + * @param error + */ + virtual void reportError(const char *error) override + { + // we want to start the elegant ssl shutdown procedure, so we call reportClosed() here too, + // because that function does exactly what we want to do here too + reportClosed(); + + // if the user was already notified of an final state, we do not have to proceed + if (_finalized) return; + + // remember that this is the final call to user space + _finalized = true; + + // pass to handler + _handler->onError(_connection, error); + } /** * Report to the handler that the connection was nicely closed */ virtual void reportClosed() override { - // remember that the object is closed + // remember that the object is going to be closed _closed = true; - // if the previous operation is still in progress + // if the previous operation is still in progress we can wait for that if (_state != state_idle) return; - // wait until the connection is writable - _handler->monitor(_connection, _socket, writable); + // wait until the connection is writable so that we can close it then + _handler->monitor(_connection, _socket, readable | writable); } }; diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index d7518e1..ec082a2 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -179,7 +179,7 @@ public: switch (error) { case SSL_ERROR_WANT_READ: return proceed(readable); case SSL_ERROR_WANT_WRITE: return proceed(readable | writable); - default: return reportError(); + default: return reportError(monitor); } } @@ -225,7 +225,7 @@ public: case SSL_ERROR_WANT_WRITE: wait.active(); break; // something is wrong, we proceed to the next state - default: return reportError(); + default: return reportError(monitor); } } } diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h index bacc81e..4131a52 100644 --- a/src/linux_tcp/sslshutdown.h +++ b/src/linux_tcp/sslshutdown.h @@ -34,40 +34,51 @@ private: * @var int */ int _socket; + + /** + * Have we already notified user space of connection end? + * @var bool + */ + bool _finalized; /** * Proceed with the next operation after the previous operation was * a success, possibly changing the filedescriptor-monitor + * @param monitor object to check if connection still exists * @return TcpState* */ - TcpState *proceed() + TcpState *proceed(const Monitor &monitor) { - // construct monitor to prevent that we access members if object is destructed - Monitor monitor(this); - // we're no longer interested in events _handler->monitor(_connection, _socket, 0); - // stop if object was destructed - if (!monitor) return nullptr; - // close the socket close(_socket); // forget the socket _socket = -1; - // go to the closed state - return new TcpClosed(_connection, _handler); + // if we have already told user space that connection is gone + if (_finalized) return new TcpClosed(this); + + // object will be finalized now + _finalized = true; + + // inform user space that the party is over + _handler->onClosed(_connection); + + // go to the final state (if not yet disconnected) + return monitor.valid() ? new TcpClosed(this) : nullptr; } /** * Method to repeat the previous call + * @param monitor object to check if connection still exists * @param result result of an earlier openssl operation * @return TcpState* */ - TcpState *repeat(int result) + TcpState *repeat(const Monitor &monitor, int result) { // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); @@ -85,9 +96,17 @@ private: return this; default: + // the shutdown failed, ignore this if user was already notified of an error + if (_finalized) return new TcpClosed(this); - // @todo check how to handle this - return this; + // object will be finalized now + _finalized = true; + + // inform user space that the party is over + _handler->onError(_connection, "ssl shutdown error"); + + // go to the final state (if not yet disconnected) + return monitor.valid() ? new TcpClosed(this) : nullptr; } } @@ -98,12 +117,14 @@ public: * @param connection Parent TCP connection object * @param socket The socket filedescriptor * @param ssl The SSL structure + * @param finalized Is the user already notified of connection end (onError() has been called) * @param handler User-supplied handler object */ - SslShutdown(TcpConnection *connection, int socket, SslWrapper &&ssl, TcpHandler *handler) : + SslShutdown(TcpConnection *connection, int socket, SslWrapper &&ssl, bool finalized, TcpHandler *handler) : TcpState(connection, handler), _ssl(std::move(ssl)), - _socket(socket) + _socket(socket), + _finalized(finalized) { // tell the handler to monitor the socket if there is an out _handler->monitor(_connection, _socket, readable); @@ -114,8 +135,8 @@ public: */ virtual ~SslShutdown() noexcept { - // skip if handler is already forgotten - if (_handler == nullptr) return; + // skip if socket is already gond + if (_socket < 0) return; // we no longer have to monitor the socket _handler->monitor(_connection, _socket, 0); @@ -132,26 +153,24 @@ public: /** * Process the filedescriptor in the object + * @param monitor Object to check if connection still exists * @param fd The filedescriptor that is active * @param flags AMQP::readable and/or AMQP::writable * @return New implementation object */ - virtual TcpState *process(int fd, int flags) + virtual TcpState *process(const Monitor &monitor, int fd, int flags) override { // the socket must be the one this connection writes to if (fd != _socket) return this; - // because the object might soon be destructed, we create a monitor to check this - Monitor monitor(this); - // close the connection auto result = OpenSSL::SSL_shutdown(_ssl); // if this is a success, we can proceed with the event loop - if (result > 0) return proceed(); + if (result > 0) return proceed(monitor); // the operation failed, we may have to repeat our call - else return repeat(result); + else return repeat(monitor, result); } }; diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index f05d532..bc04223 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -291,7 +291,7 @@ public: * Report to the handler that the object is in an error state. * @param error */ - virtual void reportError(const char *error) + virtual void reportError(const char *error) override { // close the socket close(); From 872d4e9a11f350bd7bb809cef328ca0f0ca7dcea Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 13:09:56 +0100 Subject: [PATCH 145/168] implemented flushing for ssl connections --- src/linux_tcp/sslconnected.h | 48 +++++++++++++++--------- src/linux_tcp/sslshutdown.h | 73 +++++++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 1acb052..6b773a4 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -161,14 +161,11 @@ private: /** * Method to repeat the previous call\ * @param monitor monitor to check if connection object still exists - * @param result result of an earlier openssl operation + * @param result result of an earlier SSL_get_error call * @return TcpState* */ - TcpState *repeat(const Monitor &monitor, int result) + TcpState *repeat(const Monitor &monitor, int error) { - // error was returned, so we must investigate what is going on - auto error = OpenSSL::SSL_get_error(_ssl, result); - // check the error switch (error) { case SSL_ERROR_WANT_READ: @@ -305,7 +302,7 @@ public: if (result > 0) return proceed(); // the operation failed, we may have to repeat our call - else return repeat(monitor, result); + else return repeat(monitor, OpenSSL::SSL_get_error(_ssl, result)); } else { @@ -316,7 +313,7 @@ public: if (result > 0) return parse(monitor, result); // the operation failed, we may have to repeat our call - else return repeat(monitor, result); + else return repeat(monitor, OpenSSL::SSL_get_error(_ssl, result)); } } @@ -339,18 +336,35 @@ public: // try to send more data from the outgoing buffer auto result = _out.sendto(_ssl); - // go to the next state - auto *state = result > 0 ? proceed() : repeat(monitor, result); - - return state; - -// if (result > 0) return proceed(); -// -// // the operation failed, we may have to repeat our call -// else return repeat(result); + // was this a success? + if (result > 0) + { + // parse the buffer + auto *nextstate = parse(monitor, result); + + // leap out if we move to a different state + if (nextstate != this) return nextstate; + } + else + { + // error was returned, so we must investigate what is going on + auto error = OpenSSL::SSL_get_error(_ssl, result); + + // get the next state given this error + auto *nextstate = repeat(monitor, error); + + // leap out if we move to a different state + if (nextstate != this) return nextstate; + + // check the type of error, and wait now + switch (error) { + case SSL_ERROR_WANT_READ: wait.readable(); break; + case SSL_ERROR_WANT_WRITE: wait.active(); break; + } + } } - + // done return this; } diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h index 4131a52..67da802 100644 --- a/src/linux_tcp/sslshutdown.h +++ b/src/linux_tcp/sslshutdown.h @@ -42,6 +42,32 @@ private: bool _finalized; + /** + * Report an error + * @param monitor object to check if connection still exists + * @return TcpState* + */ + TcpState *reporterror(const Monitor &monitor) + { + // close the socket + close(_socket); + + // forget the socket + _socket = -1; + + // if we have already told user space that connection is gone + if (_finalized) return new TcpClosed(this); + + // object will be finalized now + _finalized = true; + + // inform user space that the party is over + _handler->onError(_connection, "ssl shutdown error"); + + // go to the final state (if not yet disconnected) + return monitor.valid() ? new TcpClosed(this) : nullptr; + } + /** * Proceed with the next operation after the previous operation was * a success, possibly changing the filedescriptor-monitor @@ -96,17 +122,8 @@ private: return this; default: - // the shutdown failed, ignore this if user was already notified of an error - if (_finalized) return new TcpClosed(this); - - // object will be finalized now - _finalized = true; - - // inform user space that the party is over - _handler->onError(_connection, "ssl shutdown error"); - // go to the final state (if not yet disconnected) - return monitor.valid() ? new TcpClosed(this) : nullptr; + return reporterror(monitor); } } @@ -172,6 +189,42 @@ public: // the operation failed, we may have to repeat our call else return repeat(monitor, result); } + + /** + * Flush the connection, sent all buffered data to the socket + * @param monitor Object to check if connection still exists + * @return TcpState new tcp state + */ + virtual TcpState *flush(const Monitor &monitor) override + { + // create an object to wait for the filedescriptor to becomes active + Wait wait(_socket); + + // keep looping + while (true) + { + // close the connection + auto result = OpenSSL::SSL_shutdown(_ssl); + + // if this is a success, we can proceed with the event loop + if (result > 0) return proceed(monitor); + + // error was returned, so we must investigate what is going on + auto error = OpenSSL::SSL_get_error(_ssl, result); + + // check the error + switch (error) { + + // if openssl reports that socket readability or writability is needed, + // we wait for that until this situation is reached + case SSL_ERROR_WANT_READ: wait.readable(); break; + case SSL_ERROR_WANT_WRITE: wait.active(); break; + + // something is wrong, we proceed to the next state + default: return reporterror(monitor); + } + } + } }; /** From e39ca5b012254a16822363e0860a2ce0755f9312 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 13:36:56 +0100 Subject: [PATCH 146/168] ssl connection fixes --- examples/libev.cpp | 7 ++++- src/linux_tcp/sslconnected.h | 9 ++++--- src/linux_tcp/sslhandshake.h | 24 ++++++++--------- src/linux_tcp/sslshutdown.h | 51 ++++++++++++++++++++---------------- 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 2868e29..2188637 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -14,6 +14,7 @@ #include #include #include +#include /** * Custom handler @@ -66,7 +67,11 @@ int main() MyHandler handler(loop); // init the SSL library -// SSL_library_init(); +#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/"); diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 6b773a4..8f34f76 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -139,11 +139,14 @@ private: } else if (_closed) { + // start the state that closes the connection + auto *nextstate = new SslShutdown(_connection, _socket, std::move(_ssl), _finalized, _handler); + // we forget the current socket to prevent that it gets destructed _socket = -1; - // start the state that closes the connection - return new SslShutdown(_connection, _socket, std::move(_ssl), _finalized, _handler); + // report the next state + return nextstate; } else { @@ -263,7 +266,7 @@ public: { // tell the handler to monitor the socket if there is an out _handler->monitor(_connection, _socket, _state == state_sending ? readable | writable : readable); - } + } /** * Destructor diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index ec082a2..b593381 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -122,7 +122,7 @@ public: _ssl(SslContext(OpenSSL::TLS_client_method())), _socket(socket), _out(std::move(buffer)) - { + { // we will be using the ssl context as a client OpenSSL::SSL_set_connect_state(_ssl); @@ -168,13 +168,13 @@ public: // start the ssl handshake int result = OpenSSL::SSL_do_handshake(_ssl); - + // if the connection succeeds, we can move to the ssl-connected state if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); - + // check the error switch (error) { case SSL_ERROR_WANT_READ: return proceed(readable); @@ -217,15 +217,15 @@ public: auto error = OpenSSL::SSL_get_error(_ssl, result); // check the error - switch (error) - { - // if openssl reports that socket readability or writability is needed, - // we wait for that until this situation is reached - case SSL_ERROR_WANT_READ: wait.readable(); break; - case SSL_ERROR_WANT_WRITE: wait.active(); break; - - // something is wrong, we proceed to the next state - default: return reportError(monitor); + switch (error) { + + // if openssl reports that socket readability or writability is needed, + // we wait for that until this situation is reached + case SSL_ERROR_WANT_READ: wait.readable(); break; + case SSL_ERROR_WANT_WRITE: wait.active(); break; + + // something is wrong, we proceed to the next state + default: return reportError(monitor); } } } diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h index 67da802..7083ea1 100644 --- a/src/linux_tcp/sslshutdown.h +++ b/src/linux_tcp/sslshutdown.h @@ -42,6 +42,28 @@ private: bool _finalized; + /** + * Close the socket + * @return bool + */ + bool close() + { + // skip if already closed + if (_socket < 0) return false; + + // we're no longer interested in events + _handler->monitor(_connection, _socket, 0); + + // close the socket + ::close(_socket); + + // forget the socket + _socket = -1; + + // done + return true; + } + /** * Report an error * @param monitor object to check if connection still exists @@ -50,10 +72,7 @@ private: TcpState *reporterror(const Monitor &monitor) { // close the socket - close(_socket); - - // forget the socket - _socket = -1; + close(); // if we have already told user space that connection is gone if (_finalized) return new TcpClosed(this); @@ -76,14 +95,8 @@ private: */ TcpState *proceed(const Monitor &monitor) { - // we're no longer interested in events - _handler->monitor(_connection, _socket, 0); - // close the socket - close(_socket); - - // forget the socket - _socket = -1; + close(); // if we have already told user space that connection is gone if (_finalized) return new TcpClosed(this); @@ -108,7 +121,7 @@ private: { // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); - + // check the error switch (error) { case SSL_ERROR_WANT_READ: @@ -143,8 +156,8 @@ public: _socket(socket), _finalized(finalized) { - // tell the handler to monitor the socket if there is an out - _handler->monitor(_connection, _socket, readable); + // wait until the socket is accessible + _handler->monitor(_connection, _socket, readable | writable); } /** @@ -152,14 +165,8 @@ public: */ virtual ~SslShutdown() noexcept { - // skip if socket is already gond - if (_socket < 0) return; - - // we no longer have to monitor the socket - _handler->monitor(_connection, _socket, 0); - // close the socket - close(_socket); + close(); } /** @@ -182,7 +189,7 @@ public: // close the connection auto result = OpenSSL::SSL_shutdown(_ssl); - + // if this is a success, we can proceed with the event loop if (result > 0) return proceed(monitor); From c26005ddfb56ad4fa81c2f7a1bcf66a5a64351ba Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 14:00:44 +0100 Subject: [PATCH 147/168] work in progress on setting up tls connections to rabbitmq --- examples/libev.cpp | 2 +- src/linux_tcp/openssl.cpp | 16 ++++++++++++++++ src/linux_tcp/openssl.h | 1 + src/linux_tcp/sslcontext.h | 2 +- src/linux_tcp/sslwrapper.h | 3 +++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 2188637..8064fe7 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -74,7 +74,7 @@ int main() #endif // make a connection - AMQP::Address address("amqp://guest:guest@localhost/"); + AMQP::Address address("amqps://guest:guest@testmailsender1.copernica.nl/"); AMQP::TcpConnection connection(&handler, address); // we need a channel too diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index 234b2e7..655e17c 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -256,7 +256,23 @@ long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg) // call the openssl function return func(ssl, cmd, larg, parg); } + +/** + * Set the certificate file to be used by the connection + * @param ssl ssl structure + * @param file filename + * @param type type of file + * @return int + */ +int SSL_use_certificate_file(SSL *ssl, const char *file, int type) +{ + // create a function + static Function func("SSL_use_certificate_file"); + // call the openssl function + return func(ssl, file, type); +} + /** * End of namespace */ diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index 23c4ae3..9878ca1 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -43,6 +43,7 @@ int SSL_shutdown(SSL *ssl); int SSL_set_fd(SSL *ssl, int fd); int SSL_get_shutdown(const SSL *ssl); int SSL_get_error(const SSL *ssl, int ret); +int SSL_use_certificate_file(SSL *ssl, const char *file, int type); void SSL_set_connect_state(SSL *ssl); void SSL_CTX_free(SSL_CTX *ctx); void SSL_free(SSL *ssl); diff --git a/src/linux_tcp/sslcontext.h b/src/linux_tcp/sslcontext.h index bcf8c90..ab9dbc7 100644 --- a/src/linux_tcp/sslcontext.h +++ b/src/linux_tcp/sslcontext.h @@ -32,7 +32,7 @@ private: public: /** * Constructor - * @param method + * @param method the connect method to use * @throws std::runtime_error */ SslContext(const SSL_METHOD *method) : _ctx(OpenSSL::SSL_CTX_new(method)) diff --git a/src/linux_tcp/sslwrapper.h b/src/linux_tcp/sslwrapper.h index 79a8f7b..b72e861 100644 --- a/src/linux_tcp/sslwrapper.h +++ b/src/linux_tcp/sslwrapper.h @@ -33,11 +33,14 @@ public: /** * Constructor * @param ctx + * @param file */ SslWrapper(SSL_CTX *ctx) : _ssl(OpenSSL::SSL_new(ctx)) { // report error if (_ssl == nullptr) throw std::runtime_error("failed to construct ssl structure"); + + //OpenSSL::SSL_use_certificate_file(_ssl, "cert.pem", SSL_FILETYPE_PEM); } /** From 67056f6fd91b294c73a3426ca62b438014528861 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 8 Mar 2018 14:02:15 +0100 Subject: [PATCH 148/168] use localhost --- examples/libev.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 8064fe7..2188637 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -74,7 +74,7 @@ int main() #endif // make a connection - AMQP::Address address("amqps://guest:guest@testmailsender1.copernica.nl/"); + AMQP::Address address("amqp://guest:guest@localhost/"); AMQP::TcpConnection connection(&handler, address); // we need a channel too From 28b6c903e1cb66d6a108810dec7dd70713cd94d3 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 9 Mar 2018 13:55:49 +0100 Subject: [PATCH 149/168] finished implementing amqps:// support --- examples/libev.cpp | 1 + src/connectionimpl.cpp | 4 +- src/linux_tcp/openssl.cpp | 15 ++++ src/linux_tcp/openssl.h | 1 + src/linux_tcp/sslconnected.h | 128 ++++++++++++++++++++++++----------- src/linux_tcp/sslshutdown.h | 6 ++ 6 files changed, 112 insertions(+), 43 deletions(-) diff --git a/examples/libev.cpp b/examples/libev.cpp index 2188637..7bfd1af 100644 --- a/examples/libev.cpp +++ b/examples/libev.cpp @@ -75,6 +75,7 @@ int main() // 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 diff --git a/src/connectionimpl.cpp b/src/connectionimpl.cpp index 332e106..ab9f781 100644 --- a/src/connectionimpl.cpp +++ b/src/connectionimpl.cpp @@ -3,7 +3,7 @@ * * Implementation of an AMQP connection * - * @copyright 2014 - 2017 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ #include "includes.h" #include "protocolheaderframe.h" @@ -147,7 +147,7 @@ uint64_t ConnectionImpl::parse(const Buffer &buffer) // data we need for the next frame, otherwise we need at least 7 // bytes for processing the header of the next frame _expected = receivedFrame.header() ? (uint32_t)receivedFrame.totalSize() : 7; - + // we're ready for now return processed; } diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index 655e17c..f038284 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -110,6 +110,21 @@ int SSL_set_fd(SSL *ssl, int fd) return func(ssl, fd); } +/** + * The number of bytes availabe in the ssl struct that have been read + * from the socket, but that have not been returned the SSL_read() + * @param ssl SSL object + * @return int number of bytes + */ +int SSL_pending(const SSL *ssl) +{ + // create a function + static Function func("SSL_pending"); + + // call the openssl function + return func(ssl); +} + /** * Free an allocated ssl context * @param ctx diff --git a/src/linux_tcp/openssl.h b/src/linux_tcp/openssl.h index 9878ca1..e3461e2 100644 --- a/src/linux_tcp/openssl.h +++ b/src/linux_tcp/openssl.h @@ -40,6 +40,7 @@ int SSL_do_handshake(SSL *ssl); int SSL_read(SSL *ssl, void *buf, int num); int SSL_write(SSL *ssl, const void *buf, int num); int SSL_shutdown(SSL *ssl); +int SSL_pending(const SSL *ssl); int SSL_set_fd(SSL *ssl, int fd); int SSL_get_shutdown(const SSL *ssl); int SSL_get_error(const SSL *ssl, int ret); diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 8f34f76..7d4b90e 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -59,7 +59,7 @@ private: * Are we now busy with sending or receiving? * @var enum */ - enum { + enum State { state_idle, state_sending, state_receiving @@ -131,9 +131,6 @@ private: // if we still have an outgoing buffer we want to send out data if (_out) { - // we still have a buffer with outgoing data - _state = state_sending; - // let's wait until the socket becomes writable _handler->monitor(_connection, _socket, readable | writable); } @@ -150,9 +147,6 @@ private: } else { - // outgoing buffer is empty, we're idle again waiting for further input - _state = state_idle; - // let's wait until the socket becomes readable _handler->monitor(_connection, _socket, readable); } @@ -164,14 +158,18 @@ private: /** * Method to repeat the previous call\ * @param monitor monitor to check if connection object still exists + * @param state the state that we were in * @param result result of an earlier SSL_get_error call * @return TcpState* */ - TcpState *repeat(const Monitor &monitor, int error) + TcpState *repeat(const Monitor &monitor, enum State state, int error) { // check the error switch (error) { case SSL_ERROR_WANT_READ: + // remember state + _state = state; + // the operation must be repeated when readable _handler->monitor(_connection, _socket, readable); @@ -179,6 +177,9 @@ private: return monitor.valid() ? this : nullptr; case SSL_ERROR_WANT_WRITE: + // remember state + _state = state; + // wait until socket becomes writable again _handler->monitor(_connection, _socket, readable | writable); @@ -188,7 +189,7 @@ private: case SSL_ERROR_NONE: // we're ready for the next instruction from userspace _state = state_idle; - + // turns out no error occured, an no action has to be rescheduled _handler->monitor(_connection, _socket, _out || _closed ? readable | writable : readable); @@ -224,7 +225,7 @@ private: // parse the buffer auto processed = _connection->parse(buffer); - + // "this" could be removed by now, check this if (!monitor.valid()) return nullptr; @@ -235,7 +236,7 @@ private: _in = std::move(buffer); // do we have to reallocate? - if (!_reallocate) return proceed(); + if (!_reallocate) return this; // reallocate the buffer _in.reallocate(_reallocate); @@ -244,8 +245,60 @@ private: _reallocate = 0; // done + return this; + } + + /** + * Perform a write operation + * @param monitor + * @return TcpState* + */ + TcpState *write(const Monitor &monitor) + { + // assume default state + _state = state_idle; + + // try to send more data from the outgoing buffer + auto result = _out.sendto(_ssl); + + // if this is a success, we can proceed with the event loop + if (result > 0) return proceed(); + + // the operation failed, we may have to repeat our call + return repeat(monitor, state_sending, OpenSSL::SSL_get_error(_ssl, result)); + } + + /** + * Perform a receive operation + * @param monitor + * @return TcpState + */ + TcpState *receive(const Monitor &monitor) + { + // start a loop + do + { + // assume default state + _state = state_idle; + + // read data from ssl into the buffer + auto result = _in.receivefrom(_ssl, _connection->expected()); + + // if this is a failure, we are going to repeat the operation + if (result <= 0) return repeat(monitor, state_receiving, OpenSSL::SSL_get_error(_ssl, result)); + + // go process the received data + auto *nextstate = parse(monitor, result); + + // leap out if we moved to a different state + if (nextstate != this) return nextstate; + } + while (OpenSSL::SSL_pending(_ssl) > 0); + + // go to the next state return proceed(); } + public: /** @@ -295,29 +348,22 @@ public: // the socket must be the one this connection writes to if (fd != _socket) return this; - // are we busy with sending or receiving data? - if (_state == state_sending) - { - // try to send more data from the outgoing buffer - auto result = _out.sendto(_ssl); - - // if this is a success, we can proceed with the event loop - if (result > 0) return proceed(); - - // the operation failed, we may have to repeat our call - else return repeat(monitor, OpenSSL::SSL_get_error(_ssl, result)); - } - else - { - // read data from ssl into the buffer - auto result = _in.receivefrom(_ssl, _connection->expected()); - - // if this is a success, we may have to update the monitor - if (result > 0) return parse(monitor, result); - - // the operation failed, we may have to repeat our call - else return repeat(monitor, OpenSSL::SSL_get_error(_ssl, result)); - } + // if we were busy with a write operation, we have to repeat that + if (_state == state_sending) return write(monitor); + + // same is true for read operations, they should also be repeated + if (_state == state_receiving) return receive(monitor); + + // if the socket is readable, we are going to receive data + if (flags & readable) return receive(monitor); + + // socket is not readable (so it must be writable), do we have data to write? + if (_out) return write(monitor); + + // the only scenario in which we can end up here is the socket should be + // closed, but instead of moving to the shutdown-state right, we call proceed() + // because that function is a little more careful + return proceed(); } /** @@ -336,14 +382,17 @@ public: // keep looping while we have an outgoing buffer while (_out) { + // move to the idle-state + _state = state_idle; + // try to send more data from the outgoing buffer auto result = _out.sendto(_ssl); // was this a success? if (result > 0) { - // parse the buffer - auto *nextstate = parse(monitor, result); + // proceed to the next state + auto *nextstate = proceed(); // leap out if we move to a different state if (nextstate != this) return nextstate; @@ -353,8 +402,8 @@ public: // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); - // get the next state given this error - auto *nextstate = repeat(monitor, error); + // get the next state given the error + auto *nextstate = repeat(monitor, state_sending, error); // leap out if we move to a different state if (nextstate != this) return nextstate; @@ -385,9 +434,6 @@ public: // for that operation to complete before we can move on if (_state != state_idle) return; - // object is now busy sending - _state = state_sending; - // let's wait until the socket becomes writable _handler->monitor(_connection, _socket, readable | writable); } diff --git a/src/linux_tcp/sslshutdown.h b/src/linux_tcp/sslshutdown.h index 7083ea1..64791cf 100644 --- a/src/linux_tcp/sslshutdown.h +++ b/src/linux_tcp/sslshutdown.h @@ -190,6 +190,9 @@ public: // close the connection auto result = OpenSSL::SSL_shutdown(_ssl); + // on result==0 we need an additional call + while (result == 0) result = OpenSSL::SSL_shutdown(_ssl); + // if this is a success, we can proceed with the event loop if (result > 0) return proceed(monitor); @@ -212,6 +215,9 @@ public: { // close the connection auto result = OpenSSL::SSL_shutdown(_ssl); + + // on result==0 we need an additional call + while (result == 0) result = OpenSSL::SSL_shutdown(_ssl); // if this is a success, we can proceed with the event loop if (result > 0) return proceed(monitor); From d8bc58604d8f27641c80b6fb76d8ed57505bda96 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 9 Mar 2018 14:12:44 +0100 Subject: [PATCH 150/168] update README --- README.md | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d9684d7..a7c3659 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,10 @@ There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform por After building there are two relevant files to include when using the library. -File|Include when? -----|------------ -amqpcpp.h|Always -amqpcpp/linux_tcp.h|If using the Linux-only TCP module + File | Include when? +---------------------|-------------------------------------------------------- + amqpcpp.h | Always + amqpcpp/linux_tcp.h | If using the Linux-only TCP module On Windows you are required to define `NOMINMAX` when compiling code that includes public AMQP-CPP header files. @@ -86,12 +86,13 @@ cmake .. [-DAMQP-CPP_AMQBUILD_SHARED] [-DAMQP-CPP_LINUX_TCP] cmake --build .. --target install ``` -Option|Default|Meaning -------|-------|------- -AMQP-CPP_BUILD_SHARED|OFF|Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. -AMQP-CPP_LINUX_TCP|OFF|Should the Linux-only TCP module be built? + Option | Default | Meaning +-------------------------|---------|----------------------------------------------------------------------- + AMQP-CPP_BUILD_SHARED | OFF | Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. + AMQP-CPP_LINUX_TCP | OFF | Should the Linux-only TCP module be built? ## Make + Installing the library is as easy as running `make` and `make install`. This will install the full version of the AMQP-CPP, including the system specific TCP module. If you do not need the @@ -396,6 +397,32 @@ channel.declareQueue("my-queue"); channel.bindQueue("my-exchange", "my-queue", "my-routing-key"); ```` +SECURE CONNECTIONS +================== + +The TCP module of AMQP-CPP also supports setting up secure connections. If your +RabbitMQ server accepts SSL connections, you can specify the address to your +server using the amqps:// protocol: + +````c++ +// init the SSL library (this works for openssl 1.1, for openssl 1.0 use SSL_library_init()) +OPENSSL_init_ssl(0, NULL); + +// address of the server +AMQP::Address address("amqps://guest:guest@localhost/vhost"); + +// create a AMQP connection object +AMQP::TcpConnection connection(&myHandler, address); +```` + +There are two things to take care of if you want to create a secure connection: +(1) you must link your application with the -lssl flag, and (2) you must initialize +the openssl library by calling OPENSSL_init_ssl(). This initializating must take +place before you let you application connect to RabbitMQ. This is necessary +because AMQP-CPP needs access to the openssl library, which it needs for setting up +secure connections. + + EXISTING EVENT LOOPS ==================== From 4c2b8ff68e707269bfcb83e97f862066023ca315 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Fri, 9 Mar 2018 15:08:52 +0100 Subject: [PATCH 151/168] added ability to set the handle to the openssl library (so that programs that load openssl via dlopen() can still use amqp-cpp) --- include/amqpcpp.h | 2 +- include/amqpcpp/openssl.h | 37 ++++++++++++++++++++++ src/linux_tcp/function.h | 5 +-- src/linux_tcp/openssl.cpp | 64 ++++++++++++++++++++++++++------------- 4 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 include/amqpcpp/openssl.h diff --git a/include/amqpcpp.h b/include/amqpcpp.h index 39ec8f5..8079b1f 100644 --- a/include/amqpcpp.h +++ b/include/amqpcpp.h @@ -79,4 +79,4 @@ #include "amqpcpp/connectionhandler.h" #include "amqpcpp/connectionimpl.h" #include "amqpcpp/connection.h" - +#include "amqpcpp/openssl.h" diff --git a/include/amqpcpp/openssl.h b/include/amqpcpp/openssl.h new file mode 100644 index 0000000..f4ac1e1 --- /dev/null +++ b/include/amqpcpp/openssl.h @@ -0,0 +1,37 @@ +/** + * OpenSSL.h + * + * Function to set openssl features + * + * @author Emiel Bruijntjes + * @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 + */ +} + diff --git a/src/linux_tcp/function.h b/src/linux_tcp/function.h index cb8bf89..899f536 100644 --- a/src/linux_tcp/function.h +++ b/src/linux_tcp/function.h @@ -115,10 +115,11 @@ private: public: /** * Constructor + * @param handle Handle to access openssl * @param name Name of the function */ - Function(const char *name) : - _method(dlsym(RTLD_DEFAULT, name)) {} + Function(void *handle, const char *name) : + _method(dlsym(handle, name)) {} /** * Destructor diff --git a/src/linux_tcp/openssl.cpp b/src/linux_tcp/openssl.cpp index f038284..2afc378 100644 --- a/src/linux_tcp/openssl.cpp +++ b/src/linux_tcp/openssl.cpp @@ -11,11 +11,33 @@ */ #include "openssl.h" #include "function.h" +#include "amqpcpp/openssl.h" /** - * Begin of namespace + * The handle to access openssl (the result of dlopen("openssl.so")) + * By default we set this to RTLD_DEFAULT, so that AMQP-CPP checks the internal process space */ -namespace AMQP { namespace OpenSSL { +static void *handle = RTLD_DEFAULT; + +/** + * Just the AMQP namespace + */ +namespace AMQP { + +/** + * Function to set the openssl handle + * @param ptr + */ +void openssl(void *ptr) +{ + // store handle + handle = ptr; +} + +/** + * Begin of AMQP::OpenSSL namespace + */ +namespace OpenSSL { /** * Is the openssl library loaded? @@ -24,7 +46,7 @@ namespace AMQP { namespace OpenSSL { bool valid() { // create a function - static Function func("SSL_CTX_new"); + static Function func(handle, "SSL_CTX_new"); // we need a library return func; @@ -37,13 +59,13 @@ bool valid() const SSL_METHOD *TLS_client_method() { // create a function that loads the method - static Function func("TLS_client_method"); + static Function func(handle, "TLS_client_method"); // call the openssl function if (func) return func(); // older openssl libraries do not have this function, so we try to load an other function - static Function old("SSLv23_client_method"); + static Function old(handle, "SSLv23_client_method"); // call the old one return old(); @@ -57,7 +79,7 @@ const SSL_METHOD *TLS_client_method() SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) { // create a function - static Function func("SSL_CTX_new"); + static Function func(handle, "SSL_CTX_new"); // call the openssl function return func(method); @@ -73,7 +95,7 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *method) int SSL_read(SSL *ssl, void *buf, int num) { // create a function - static Function func("SSL_read"); + static Function func(handle, "SSL_read"); // call the openssl function return func(ssl, buf, num); @@ -89,7 +111,7 @@ int SSL_read(SSL *ssl, void *buf, int num) int SSL_write(SSL *ssl, const void *buf, int num) { // create a function - static Function func("SSL_write"); + static Function func(handle, "SSL_write"); // call the openssl function return func(ssl, buf, num); @@ -104,7 +126,7 @@ int SSL_write(SSL *ssl, const void *buf, int num) int SSL_set_fd(SSL *ssl, int fd) { // create a function - static Function func("SSL_set_fd"); + static Function func(handle, "SSL_set_fd"); // call the openssl function return func(ssl, fd); @@ -119,7 +141,7 @@ int SSL_set_fd(SSL *ssl, int fd) int SSL_pending(const SSL *ssl) { // create a function - static Function func("SSL_pending"); + static Function func(handle, "SSL_pending"); // call the openssl function return func(ssl); @@ -132,7 +154,7 @@ int SSL_pending(const SSL *ssl) void SSL_CTX_free(SSL_CTX *ctx) { // create a function - static Function func("SSL_CTX_free"); + static Function func(handle, "SSL_CTX_free"); // call the openssl function return func(ctx); @@ -146,7 +168,7 @@ void SSL_CTX_free(SSL_CTX *ctx) void SSL_free(SSL *ssl) { // create a function - static Function func("SSL_free"); + static Function func(handle, "SSL_free"); // call the openssl function return func(ssl); @@ -160,7 +182,7 @@ void SSL_free(SSL *ssl) SSL *SSL_new(SSL_CTX *ctx) { // create a function - static Function func("SSL_new"); + static Function func(handle, "SSL_new"); // call the openssl function return func(ctx); @@ -174,7 +196,7 @@ SSL *SSL_new(SSL_CTX *ctx) int SSL_up_ref(SSL *ssl) { // create a function - static Function func("SSL_up_ref"); + static Function func(handle, "SSL_up_ref"); // call the openssl function if it exists if (func) return func(ssl); @@ -192,7 +214,7 @@ int SSL_up_ref(SSL *ssl) int SSL_shutdown(SSL *ssl) { // create a function - static Function func("SSL_shutdown"); + static Function func(handle, "SSL_shutdown"); // call the openssl function return func(ssl); @@ -205,7 +227,7 @@ int SSL_shutdown(SSL *ssl) void SSL_set_connect_state(SSL *ssl) { // create a function - static Function func("SSL_set_connect_state"); + static Function func(handle, "SSL_set_connect_state"); // call the openssl function func(ssl); @@ -220,7 +242,7 @@ void SSL_set_connect_state(SSL *ssl) int SSL_do_handshake(SSL *ssl) { // create a function - static Function func("SSL_do_handshake"); + static Function func(handle, "SSL_do_handshake"); // call the openssl function return func(ssl); @@ -234,7 +256,7 @@ int SSL_do_handshake(SSL *ssl) int SSL_get_shutdown(const SSL *ssl) { // create a function - static Function func("SSL_get_shutdown"); + static Function func(handle, "SSL_get_shutdown"); // call the openssl function return func(ssl); @@ -249,7 +271,7 @@ int SSL_get_shutdown(const SSL *ssl) int SSL_get_error(const SSL *ssl, int ret) { // create a function - static Function func("SSL_get_error"); + static Function func(handle, "SSL_get_error"); // call the openssl function return func(ssl, ret); @@ -266,7 +288,7 @@ int SSL_get_error(const SSL *ssl, int ret) long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg) { // create a function - static Function func("SSL_ctrl"); + static Function func(handle, "SSL_ctrl"); // call the openssl function return func(ssl, cmd, larg, parg); @@ -282,7 +304,7 @@ long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg) int SSL_use_certificate_file(SSL *ssl, const char *file, int type) { // create a function - static Function func("SSL_use_certificate_file"); + static Function func(handle, "SSL_use_certificate_file"); // call the openssl function return func(ssl, file, type); From 367de51d7738a3742c865b0782345895a892113f Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 00:59:14 +0100 Subject: [PATCH 152/168] added method to intercept tls handshakes, and to verify certificates --- README.md | 103 ++++++++++++++++++++----- include/amqpcpp/linux_tcp/tcphandler.h | 37 ++++++++- src/linux_tcp/sslhandshake.h | 32 ++++++-- 3 files changed, 142 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index a7c3659..e235b25 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,14 @@ Then check out our other commercial and open source solutions: INSTALLING ========== -AMQP-CPP comes with an optional Linux-only TCP module that takes care of the network part required for the AMQP-CPP core library. If you use this module, you are required to link with `pthread`. -There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable, but the Makefile only works on Linux. Building of a shared library is currently not supported on Windows. +AMQP-CPP comes with an optional Linux-only TCP module that takes care of the +network part required for the AMQP-CPP core library. If you use this module, you +are required to link with `pthread` and `dl`. + +There are two methods to compile AMQP-CPP: CMake and Make. CMake is platform portable +and works on all systems, while the Makefile only works on Linux. Building of a shared +library is currently not supported on Windows. After building there are two relevant files to include when using the library. @@ -77,9 +82,14 @@ After building there are two relevant files to include when using the library. On Windows you are required to define `NOMINMAX` when compiling code that includes public AMQP-CPP header files. -## CMake -The CMake file supports both building and installing. You can choose not to use the install functionality, and instead manually use the build output at `bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: -``` bash +## Using cmake + +The CMake file supports both building and installing. You can choose not to use +the install functionality, and instead manually use the build output at `bin/`. Keep +in mind that the TCP module is only supported for Linux. An example install method +would be: + +```bash mkdir build cd build cmake .. [-DAMQP-CPP_AMQBUILD_SHARED] [-DAMQP-CPP_LINUX_TCP] @@ -91,22 +101,21 @@ cmake --build .. --target install AMQP-CPP_BUILD_SHARED | OFF | Static lib(ON) or shared lib(OFF)? Shared is not supported on Windows. AMQP-CPP_LINUX_TCP | OFF | Should the Linux-only TCP module be built? -## Make +## Using make -Installing the library is as easy -as running `make` and `make install`. This will install the full version of -the AMQP-CPP, including the system specific TCP module. If you do not need the -additional TCP module (because you take care of handling the network stuff -yourself), you can also compile a pure form of the library. Use `make pure` -and `make install` for that. +Compiling and installing AMQP-CPP with make is as easy as running `make` and +then `make install`. This will install the full version of AMQP-CPP, including +the system specific TCP module. If you do not need the additional TCP module +(because you take care of handling the network stuff yourself), you can also +compile a pure form of the library. Use `make pure` and `make install` for that. When you compile an application that uses the AMQP-CPP library, do not forget to link with the library. For gcc and clang the linker flag is -lamqpcpp. If you use the fullblown version of AMQP-CPP (with the TCP module), you also need to pass the -lpthread and -ldl linker flags, because the TCP module uses a thread for running an asynchronous and non-blocking DNS hostname lookup, and it -optionally dynamically opens the openssl library if a secure connection to -RabbitMQ has to be set up. +may dynamically look up functions from the openssl library if a secure connection +to RabbitMQ has to be set up. HOW TO USE AMQP-CPP @@ -408,7 +417,7 @@ server using the amqps:// protocol: // init the SSL library (this works for openssl 1.1, for openssl 1.0 use SSL_library_init()) OPENSSL_init_ssl(0, NULL); -// address of the server +// address of the server (secure!) AMQP::Address address("amqps://guest:guest@localhost/vhost"); // create a AMQP connection object @@ -416,11 +425,65 @@ AMQP::TcpConnection connection(&myHandler, address); ```` There are two things to take care of if you want to create a secure connection: -(1) you must link your application with the -lssl flag, and (2) you must initialize -the openssl library by calling OPENSSL_init_ssl(). This initializating must take -place before you let you application connect to RabbitMQ. This is necessary -because AMQP-CPP needs access to the openssl library, which it needs for setting up -secure connections. +(1) you must link your application with the -lssl flag (or use dlopen()), and (2) +you must initialize the openssl library by calling OPENSSL_init_ssl(). This +initializating must take place before you let you application connect to RabbitMQ. +This is necessary because AMQP-CPP needs access to the openssl library to set up +secure connections. It can only access this library if you have linked your +application with this library, or if you have loaded this library at runtime +using dlopen()). + +If you do not want to link your application with openssl, you can also load the +openssl library at runtime, and pass in the pointer to the handler to AMQP-CPP: + +````c++ +// dynamically open the openssl library +void *handle = dlopen("/path/to/openssl.so", RTLD_LAZY); + +// tell AMQP-CPP library where the handle to openssl can be found +AMQP::openssl(handle); + +// @todo call functions to initialize openssl, and create the AMQP connection +// (see exampe above) +```` + +By itself, AMQP-CPP does not check if the created TLS connection is sufficient +secure. Whether the certificate is expired, self-signed, missing or invalid: for +AMQP-CPP it all doesn't matter and the connection is simply permitted. If you +want to be more strict (for example: if you want to verify the server's certificate), +you must do this yourself by implementing the "onSecured()" method in your handler +object: + +````c++ +#include + +class MyTcpHandler : public AMQP::TcpHandler +{ + /** + * Method that is called right after the TLS connection has been created. + * In this method you can check the connection properties (like the certificate) + * and return false if you find it not secure enough + * @param connection the connection that has just completed the tls handshake + * @param ssl SSL structure from the openssl library + * @return bool true if connection is secure enough to start the AMQP protocol + */ + virtual bool onSecure(AMQP::TcpConnection *connection, const SSL *ssl) override + { + // @todo call functions from the openssl library to check the certificate, + // like SSL_get_peer_certificate() or SSL_get_verify_result(). + // For now we always allow the connection to proceed + return true; + } + + /** + * All other methods (like onConnected(), onError(), etc) are left out of this + * example, but would be here if this was an actual user space handler class. + */ +}; +```` + +The SSL pointer that is passed to the onSecured() method refers to the "SSL" +structure from the openssl library. EXISTING EVENT LOOPS diff --git a/include/amqpcpp/linux_tcp/tcphandler.h b/include/amqpcpp/linux_tcp/tcphandler.h index 5549425..9b0c87d 100644 --- a/include/amqpcpp/linux_tcp/tcphandler.h +++ b/include/amqpcpp/linux_tcp/tcphandler.h @@ -6,7 +6,7 @@ * class. * * @author Emiel Bruijntjes - * @copyright 2015 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -14,6 +14,11 @@ */ #pragma once +/** + * Dependencies + */ +#include + /** * Set up namespace */ @@ -35,9 +40,33 @@ public: */ virtual ~TcpHandler() = default; + /** + * 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) + { + // default implementation: do not inspect anything, just allow the connection + return true; + } + /** * Method that is called when the heartbeat frequency is negotiated - * between the server and the client. + * 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 @@ -51,7 +80,9 @@ public: } /** - * Method that is called when the TCP connection ends up in a connected state + * Method that is called when the AMQP connection ends up in a connected state + * This method is called after the TCP connection has been set up, the (optional) + * secure TLS connection, and the AMQP login handshake has been completed. * @param connection The TCP connection */ virtual void onConnected(TcpConnection *connection) {} diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index b593381..34d379e 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -53,16 +53,34 @@ private: /** * Report a new state - * @param state + * @param monitor * @return TcpState */ - TcpState *nextstate(TcpState *state) + TcpState *nextstate(const Monitor &monitor) { - // forget the socket to prevent that it is closed by the destructor + // check if the handler allows the connection + bool allowed = _handler->onSecured(_connection, _ssl); + + // leap out if the user space function destructed the object + if (!monitor.valid()) return nullptr; + + // copy the socket because we might forget it + auto socket = _socket; + + // forget the socket member to prevent that it is closed by the destructor _socket = -1; - // done - return state; + // if connection is allowed, we move to the next state + if (allowed) return new SslConnected(_connection, socket, std::move(_ssl), std::move(_out), _handler); + + // report that the connection is broken + _handler->onError(_connection, "TLS connection has been blocked by application level checks"); + + // the onError method could have destructed this object + if (!monitor.valid()) return nullptr; + + // shutdown the connection + return new SslShutdown(_connection, socket, std::move(_ssl), true, _handler); } /** @@ -170,7 +188,7 @@ public: int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); + if (result == 1) return nextstate(monitor); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); @@ -211,7 +229,7 @@ public: int result = OpenSSL::SSL_do_handshake(_ssl); // if the connection succeeds, we can move to the ssl-connected state - if (result == 1) return nextstate(new SslConnected(_connection, _socket, std::move(_ssl), std::move(_out), _handler)); + if (result == 1) return nextstate(monitor); // error was returned, so we must investigate what is going on auto error = OpenSSL::SSL_get_error(_ssl, result); From 1885a332a3ff154c6f70b95e9cace241fc0ebc38 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 01:01:01 +0100 Subject: [PATCH 153/168] small changes to README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e235b25..390d06b 100644 --- a/README.md +++ b/README.md @@ -433,8 +433,10 @@ secure connections. It can only access this library if you have linked your application with this library, or if you have loaded this library at runtime using dlopen()). -If you do not want to link your application with openssl, you can also load the -openssl library at runtime, and pass in the pointer to the handler to AMQP-CPP: +Linking openssl is the normal thing to do. You just have to add the `-lssl` flag +to your linker. If you however do not want to link your application with openssl, +you can also load the openssl library at runtime, and pass in the pointer to the +handle to AMQP-CPP: ````c++ // dynamically open the openssl library From 1ee18911ce38b11fb63e425097a6cdc3ede38324 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 10:20:52 +0100 Subject: [PATCH 154/168] fixed writing address to std::stream --- include/amqpcpp/address.h | 6 ++++++ include/amqpcpp/login.h | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index c8faaa1..32e00fb 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -299,6 +299,12 @@ public: { // 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 != 5672) stream << ":" << address._port; diff --git a/include/amqpcpp/login.h b/include/amqpcpp/login.h index b9276da..f805e18 100644 --- a/include/amqpcpp/login.h +++ b/include/amqpcpp/login.h @@ -3,7 +3,7 @@ * * This class combines login, password and vhost * - * @copyright 2014 Copernica BV + * @copyright 2014 - 2018 Copernica BV */ /** @@ -65,7 +65,25 @@ public: /** * Destructor */ - virtual ~Login() {} + 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 @@ -143,7 +161,7 @@ public: friend std::ostream &operator<<(std::ostream &stream, const Login &login) { // write username and password - return stream << login._user << "@" << login._password; + return stream << login._user << ":" << login._password; } }; From d81565fe6ab22a241fb672aa1a32754698dbc4e2 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 10:31:40 +0100 Subject: [PATCH 155/168] fixed that stream operator of address wrote the port number when the default amqps port was used --- include/amqpcpp/address.h | 24 +++++++++++++++++------- src/linux_tcp/sslhandshake.h | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index 32e00fb..8851954 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -27,7 +27,7 @@ private: * The auth method * @var bool */ - bool _secure; + bool _secure = false; /** * Login data (username + password) @@ -52,6 +52,16 @@ private: * @var std::string */ std::string _vhost; + + + /** + * The default port + * @return uint16_t + */ + uint16_t defaultport() const + { + return _secure ? 5671 : 5672; + } public: /** @@ -67,13 +77,13 @@ public: const char *last = data + size; // must start with ampqs:// to have a secure connection (and we also assign a different default port) - _secure = strncmp(data, "amqps://", 8) == 0; - - // default port changes for secure connections - if (_secure) _port = 5671; + 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://\""); + 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; @@ -307,7 +317,7 @@ public: stream << address._hostname; // do we need a special portnumber? - if (address._port != 5672) stream << ":" << address._port; + if (address._port != address.defaultport()) stream << ":" << address._port; // append default vhost stream << "/"; diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index 34d379e..caa1b4f 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -74,7 +74,7 @@ private: if (allowed) return new SslConnected(_connection, socket, std::move(_ssl), std::move(_out), _handler); // report that the connection is broken - _handler->onError(_connection, "TLS connection has been blocked by application level checks"); + _handler->onError(_connection, "TLS connection has been rejected"); // the onError method could have destructed this object if (!monitor.valid()) return nullptr; From 23cae9de84776d736c5c07fde7b74f8f2fa26dcd Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 10:32:48 +0100 Subject: [PATCH 156/168] fixed Address constructor --- include/amqpcpp/address.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/amqpcpp/address.h b/include/amqpcpp/address.h index 8851954..e9bab9a 100644 --- a/include/amqpcpp/address.h +++ b/include/amqpcpp/address.h @@ -80,7 +80,7 @@ public: 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://\""); + 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(); From 11786ff10be85f26bd690e1f9467395e43623ed1 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 14:55:23 +0100 Subject: [PATCH 157/168] update docblock in boostasio class to warn users about the possible low quality of the code --- include/amqpcpp/libboostasio.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index acdf538..613ff73 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -1,15 +1,20 @@ /** * LibBoostAsio.h * - * Implementation for the AMQP::TcpHandler that is optimized 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. + * 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 - * - * */ + /** * Include guard */ From d2b2d5af14f7a52071d0a0023fbcba717be7442c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 10 Mar 2018 14:55:57 +0100 Subject: [PATCH 158/168] shared-ptr has been turned into a unique-ptr for the tcp-state --- include/amqpcpp/linux_tcp/tcpconnection.h | 8 +++----- src/linux_tcp/tcpconnection.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/amqpcpp/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h index f7d3212..30e7c1b 100644 --- a/include/amqpcpp/linux_tcp/tcpconnection.h +++ b/include/amqpcpp/linux_tcp/tcpconnection.h @@ -5,7 +5,7 @@ * IO between the client application and the RabbitMQ server. * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -34,9 +34,7 @@ private: /** * The state of the TCP connection - this state objecs changes based on * the state of the connection (resolving, connected or closed) - * a shared pointer is used because we use a forward declaration, which isn't - * allowed in a unique pointer - * @var std::shared_ptr + * @var std::unique_ptr */ std::shared_ptr _state; @@ -116,7 +114,7 @@ public: /** * Destructor */ - virtual ~TcpConnection() noexcept {} + virtual ~TcpConnection() noexcept; /** * The filedescriptor that is used for this connection diff --git a/src/linux_tcp/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp index f0663c3..b1141b9 100644 --- a/src/linux_tcp/tcpconnection.cpp +++ b/src/linux_tcp/tcpconnection.cpp @@ -4,7 +4,7 @@ * Implementation file for the TCP connection * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -27,6 +27,11 @@ TcpConnection::TcpConnection(TcpHandler *handler, const Address &address) : _state(new TcpResolver(this, address.hostname(), address.port(), address.secure(), handler)), _connection(this, address.login(), address.vhost()) {} +/** + * Destructor + */ +TcpConnection::~TcpConnection() noexcept = default; + /** * The filedescriptor that is used for this connection * @return int From ec2109108022a8c9c04b936f27bd1b37c13e47b4 Mon Sep 17 00:00:00 2001 From: tilsche Date: Mon, 12 Mar 2018 10:46:32 +0100 Subject: [PATCH 159/168] Make sure ssl files are included in the library and add missing libraries for linking the examples --- examples/CMakeLists.txt | 6 +++--- src/linux_tcp/CMakeLists.txt | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index beee70e..edf6358 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,7 +8,7 @@ add_executable(amqpcpp_boost_example libboostasio.cpp) add_dependencies(amqpcpp_boost_example amqpcpp) -target_link_libraries(amqpcpp_boost_example amqpcpp boost_system pthread) +target_link_libraries(amqpcpp_boost_example amqpcpp boost_system pthread dl ssl) ################################### # Libev @@ -18,7 +18,7 @@ add_executable(amqpcpp_libev_example libev.cpp) add_dependencies(amqpcpp_libev_example amqpcpp) -target_link_libraries(amqpcpp_libev_example amqpcpp ev pthread) +target_link_libraries(amqpcpp_libev_example amqpcpp ev pthread dl ssl) ################################### @@ -29,4 +29,4 @@ add_executable(amqpcpp_libuv_example libuv.cpp) add_dependencies(amqpcpp_libuv_example amqpcpp) -target_link_libraries(amqpcpp_libuv_example amqpcpp uv pthread) \ No newline at end of file +target_link_libraries(amqpcpp_libuv_example amqpcpp uv pthread dl ssl) diff --git a/src/linux_tcp/CMakeLists.txt b/src/linux_tcp/CMakeLists.txt index 55d0e16..55e04c9 100644 --- a/src/linux_tcp/CMakeLists.txt +++ b/src/linux_tcp/CMakeLists.txt @@ -1,7 +1,13 @@ add_sources( addressinfo.h includes.h + openssl.cpp + openssl.h pipe.h + sslconnected.h + sslcontext.h + sslhandshake.h + sslwrapper.h tcpclosed.h tcpconnected.h tcpconnection.cpp From cf7d2e49ff0f581bd2f0ffcaf29d719b337f4b9b Mon Sep 17 00:00:00 2001 From: tilsche Date: Mon, 12 Mar 2018 10:32:09 +0100 Subject: [PATCH 160/168] allow for a clean shutdown of asio connections remove heartbeat timer on close make sure only weak references are stored in bindings even below C++17 --- include/amqpcpp/libboostasio.h | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/include/amqpcpp/libboostasio.h b/include/amqpcpp/libboostasio.h index 613ff73..c1492f5 100644 --- a/include/amqpcpp/libboostasio.h +++ b/include/amqpcpp/libboostasio.h @@ -36,9 +36,9 @@ // C++17 has 'weak_from_this()' support. #if __cplusplus >= 201701L -#define PTR_FROM_THIS weak_from_this +#define PTR_FROM_THIS(T) weak_from_this() #else -#define PTR_FROM_THIS shared_from_this +#define PTR_FROM_THIS(T) std::weak_ptr(shared_from_this()) #endif /** @@ -142,7 +142,7 @@ protected: this, _1, _2, - PTR_FROM_THIS(), + PTR_FROM_THIS(Watcher), connection, fd); return get_dispatch_wrapper(fn); @@ -160,7 +160,7 @@ protected: this, _1, _2, - PTR_FROM_THIS(), + PTR_FROM_THIS(Watcher), connection, fd); return get_dispatch_wrapper(fn); @@ -346,7 +346,7 @@ protected: const auto fn = boost::bind(&Timer::timeout, this, _1, - PTR_FROM_THIS(), + PTR_FROM_THIS(Timer), connection, timeout); @@ -583,6 +583,16 @@ public: * Destructor */ ~LibBoostAsioHandler() override = default; + + /** + * Make sure to stop the heartbeat timer after the connection is closed. + * Otherwise it will keep the service running forever. + */ + void onClosed(TcpConnection* connection) override + { + (void)connection; + _timer.reset(); + } }; From 8a6adae8e6c8bb6e5b1ffe32a6a196b8e9492816 Mon Sep 17 00:00:00 2001 From: tilsche Date: Mon, 12 Mar 2018 11:57:53 +0100 Subject: [PATCH 161/168] Remove work from libboostasio example as it keeps the service alive even after everything is closed. The example now terminates properly. --- examples/libboostasio.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/libboostasio.cpp b/examples/libboostasio.cpp index c71c9e3..d290382 100644 --- a/examples/libboostasio.cpp +++ b/examples/libboostasio.cpp @@ -30,9 +30,6 @@ int main() // note: we suggest use of 2 threads - normally one is fin (we are simply demonstrating thread safety). boost::asio::io_service service(4); - // create a work object to process our events. - boost::asio::io_service::work work(service); - // handler for libev AMQP::LibBoostAsioHandler handler(service); From 58d51a74ef17565bfbf2b8f881779d3e765e00fc Mon Sep 17 00:00:00 2001 From: Rafal Goslawski Date: Tue, 13 Mar 2018 12:16:11 +0100 Subject: [PATCH 162/168] Update CMakeLists.txt Bump soversion to 3.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a3f75a0..fc736c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,7 @@ if(AMQP-CPP_BUILD_SHARED) # create shared lib add_library(${PROJECT_NAME} SHARED ${SRCS}) # set shared lib version - set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 2.8) + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 3.0) else() # create static lib add_library(${PROJECT_NAME} STATIC ${SRCS}) From 8768ac9c67665c6270c6c8058be2bf2507203da8 Mon Sep 17 00:00:00 2001 From: Rinat Ibragimov Date: Sat, 17 Mar 2018 01:31:55 +0300 Subject: [PATCH 163/168] simplify inclusion as a subproject --- CMakeLists.txt | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc736c4..0b0f054 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ set (CMAKE_CXX_STANDARD 11) # ------------------------------------------------------------------------------------------------------ # set include/ as include directory -include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${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. @@ -69,7 +69,7 @@ endif() # ------------------------------------------------------------------------------------------------------ # set output directory -set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/bin) if(AMQP-CPP_BUILD_SHARED) # create shared lib diff --git a/README.md b/README.md index 390d06b..b908357 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ On Windows you are required to define `NOMINMAX` when compiling code that includ ## Using cmake The CMake file supports both building and installing. You can choose not to use -the install functionality, and instead manually use the build output at `bin/`. Keep +the install functionality, and instead manually use the build output at `build/bin/`. Keep in mind that the TCP module is only supported for Linux. An example install method would be: From 3e4a8defc374324dc94192b0244af95092df19be Mon Sep 17 00:00:00 2001 From: Chris Downs Date: Wed, 28 Mar 2018 12:53:48 -0500 Subject: [PATCH 164/168] Add missing linux_tcp header in examples The examples for implementing the AMQP::TcpHandler class do not include the linux_tcp header, causing the TcpHandler class to not be found. Unless it was mistakenly not included in amqpcpp.h, the examples should reflect the requirement that it be included when TcpHandler is used. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b908357..bd8df90 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,7 @@ the main event loop: ````c++ #include +#include class MyTcpHandler : public AMQP::TcpHandler { @@ -458,6 +459,7 @@ object: ````c++ #include +#include class MyTcpHandler : public AMQP::TcpHandler { From 8800d2917e7d0dc73b20f14104f1e65d0f0ae643 Mon Sep 17 00:00:00 2001 From: Mike Playle Date: Sun, 1 Apr 2018 10:54:05 +0100 Subject: [PATCH 165/168] Add method to return the amount of queued outgoing data --- include/amqpcpp/linux_tcp/tcpconnection.h | 6 ++++++ src/linux_tcp/sslconnected.h | 6 ++++++ src/linux_tcp/tcpconnected.h | 8 +++++++- src/linux_tcp/tcpconnection.cpp | 9 +++++++++ src/linux_tcp/tcpstate.h | 6 ++++++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/include/amqpcpp/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h index 30e7c1b..b096b47 100644 --- a/include/amqpcpp/linux_tcp/tcpconnection.h +++ b/include/amqpcpp/linux_tcp/tcpconnection.h @@ -122,6 +122,12 @@ public: */ int fileno() const; + /** + * The number of outgoing bytes queued on this connection. + * @return size_t + */ + size_t bytesQueued() const; + /** * Process the TCP connection * diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index 7d4b90e..ad56208 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -335,6 +335,12 @@ public: * @return int */ virtual int fileno() const override { return _socket; } + + /** + * The number of outgoing bytes queued on this connection. + * @return size_t + */ + virtual size_t bytesQueued() const { return _out.size(); } /** * Process the filedescriptor in the object diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index bc04223..83a1a4d 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -157,7 +157,13 @@ public: * @return int */ virtual int fileno() const override { return _socket; } - + + /** + * The number of outgoing bytes queued on this connection. + * @return size_t + */ + virtual size_t bytesQueued() const { return _out.size(); } + /** * Process the filedescriptor in the object * @param monitor Monitor to check if the object is still alive diff --git a/src/linux_tcp/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp index b57922b..504fcf7 100644 --- a/src/linux_tcp/tcpconnection.cpp +++ b/src/linux_tcp/tcpconnection.cpp @@ -43,6 +43,15 @@ int TcpConnection::fileno() const return _state->fileno(); } +/** + * The number of outgoing bytes queued on this connection. + * @return size_t + */ +size_t TcpConnection::bytesQueued() const +{ + return _state->bytesQueued(); +} + /** * Process the TCP connection * This method should be called when the filedescriptor that is registered diff --git a/src/linux_tcp/tcpstate.h b/src/linux_tcp/tcpstate.h index 1c3a280..c12a61c 100644 --- a/src/linux_tcp/tcpstate.h +++ b/src/linux_tcp/tcpstate.h @@ -63,6 +63,12 @@ public: */ virtual int fileno() const { return -1; } + /** + * The number of outgoing bytes queued on this connection. + * @return size_t + */ + virtual size_t bytesQueued() const { return 0; } + /** * Process the filedescriptor in the object * From b26058f3e274588abc6b23452f7b59a452fd0d71 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 1 Apr 2018 22:34:15 +0200 Subject: [PATCH 166/168] renamed bytesQueued() to queued() --- include/amqpcpp/linux_tcp/tcpconnection.h | 18 +++++++++--------- src/linux_tcp/sslconnected.h | 8 ++++---- src/linux_tcp/sslhandshake.h | 6 ++++++ src/linux_tcp/tcpconnected.h | 6 +++--- src/linux_tcp/tcpconnection.cpp | 6 +++--- src/linux_tcp/tcpresolver.h | 6 ++++++ src/linux_tcp/tcpstate.h | 4 ++-- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/include/amqpcpp/linux_tcp/tcpconnection.h b/include/amqpcpp/linux_tcp/tcpconnection.h index b096b47..15709a0 100644 --- a/include/amqpcpp/linux_tcp/tcpconnection.h +++ b/include/amqpcpp/linux_tcp/tcpconnection.h @@ -122,12 +122,6 @@ public: */ int fileno() const; - /** - * The number of outgoing bytes queued on this connection. - * @return size_t - */ - size_t bytesQueued() const; - /** * Process the TCP connection * @@ -161,7 +155,7 @@ public: } /** - * The max frame size + * 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 @@ -170,7 +164,7 @@ public: } /** - * The number of bytes that can best be passed to the next call to the parse() method + * The number of bytes that can best be passed to the next call to the parse() method. * @return uint32_t */ uint32_t expected() const @@ -179,7 +173,7 @@ public: } /** - * Return the amount of channels this connection has + * Return the number of channels this connection has. * @return std::size_t */ std::size_t channels() const @@ -187,6 +181,12 @@ public: // return the amount 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 diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index ad56208..fd980f0 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -337,11 +337,11 @@ public: virtual int fileno() const override { return _socket; } /** - * The number of outgoing bytes queued on this connection. - * @return size_t + * Number of bytes in the outgoing buffer + * @return std::size_t */ - virtual size_t bytesQueued() const { return _out.size(); } - + virtual std::size_t queued() const override { return _out.size(); } + /** * Process the filedescriptor in the object * @param monitor Object that can be used to find out if connection object is still alive diff --git a/src/linux_tcp/sslhandshake.h b/src/linux_tcp/sslhandshake.h index caa1b4f..064d181 100644 --- a/src/linux_tcp/sslhandshake.h +++ b/src/linux_tcp/sslhandshake.h @@ -171,6 +171,12 @@ public: * @return int */ virtual int fileno() const override { return _socket; } + + /** + * Number of bytes in the outgoing buffer + * @return std::size_t + */ + virtual std::size_t queued() const override { return _out.size(); } /** * Process the filedescriptor in the object diff --git a/src/linux_tcp/tcpconnected.h b/src/linux_tcp/tcpconnected.h index 83a1a4d..34b6c47 100644 --- a/src/linux_tcp/tcpconnected.h +++ b/src/linux_tcp/tcpconnected.h @@ -159,10 +159,10 @@ public: virtual int fileno() const override { return _socket; } /** - * The number of outgoing bytes queued on this connection. - * @return size_t + * Number of bytes in the outgoing buffer + * @return std::size_t */ - virtual size_t bytesQueued() const { return _out.size(); } + virtual std::size_t queued() const override { return _out.size(); } /** * Process the filedescriptor in the object diff --git a/src/linux_tcp/tcpconnection.cpp b/src/linux_tcp/tcpconnection.cpp index 504fcf7..b8f0cd1 100644 --- a/src/linux_tcp/tcpconnection.cpp +++ b/src/linux_tcp/tcpconnection.cpp @@ -45,11 +45,11 @@ int TcpConnection::fileno() const /** * The number of outgoing bytes queued on this connection. - * @return size_t + * @return std::size_t */ -size_t TcpConnection::bytesQueued() const +std::size_t TcpConnection::queued() const { - return _state->bytesQueued(); + return _state->queued(); } /** diff --git a/src/linux_tcp/tcpresolver.h b/src/linux_tcp/tcpresolver.h index 97e8916..5fc002a 100644 --- a/src/linux_tcp/tcpresolver.h +++ b/src/linux_tcp/tcpresolver.h @@ -184,6 +184,12 @@ public: _thread.join(); } + /** + * Number of bytes in the outgoing buffer + * @return std::size_t + */ + virtual std::size_t queued() const override { return _buffer.size(); } + /** * Proceed to the next state * @return TcpState * diff --git a/src/linux_tcp/tcpstate.h b/src/linux_tcp/tcpstate.h index c12a61c..c7660c9 100644 --- a/src/linux_tcp/tcpstate.h +++ b/src/linux_tcp/tcpstate.h @@ -4,7 +4,7 @@ * Base class / interface of the various states of the TCP connection * * @author Emiel Bruijntjes - * @copyright 2015 - 2016 Copernica BV + * @copyright 2015 - 2018 Copernica BV */ /** @@ -67,7 +67,7 @@ public: * The number of outgoing bytes queued on this connection. * @return size_t */ - virtual size_t bytesQueued() const { return 0; } + virtual std::size_t queued() const { return 0; } /** * Process the filedescriptor in the object From 69a201e741719415cb332b1aa0279c119b9dfe52 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 5 Apr 2018 11:10:55 +0200 Subject: [PATCH 167/168] optimizid dealing with ssl connections by not going back to the event loop that often, and prevented that object was staying in send state if it was endlessly sending data and not receiving anything, found this out when working on issue #207 --- include/amqpcpp/libevent.h | 1 + src/linux_tcp/sslconnected.h | 49 ++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/include/amqpcpp/libevent.h b/include/amqpcpp/libevent.h index 7621dcc..4d1d19a 100644 --- a/include/amqpcpp/libevent.h +++ b/include/amqpcpp/libevent.h @@ -20,6 +20,7 @@ */ #include #include +#include /** * Set up namespace diff --git a/src/linux_tcp/sslconnected.h b/src/linux_tcp/sslconnected.h index fd980f0..f745e36 100644 --- a/src/linux_tcp/sslconnected.h +++ b/src/linux_tcp/sslconnected.h @@ -250,30 +250,42 @@ private: /** * Perform a write operation - * @param monitor + * @param monitor object to check the existance of the connection object + * @param readable is the connection also readable and should we call a read operation afterwards? * @return TcpState* */ - TcpState *write(const Monitor &monitor) + TcpState *write(const Monitor &monitor, bool readable) { // assume default state _state = state_idle; - // try to send more data from the outgoing buffer - auto result = _out.sendto(_ssl); + // because the output buffer contains a lot of small buffers, we can do multiple + // operations till the buffer is empty (but only if the socket is not also + // readable, because then we want to read that data first instead of endless writes + do + { + // try to send more data from the outgoing buffer + auto result = _out.sendto(_ssl); + + // we may have to repeat the operation on failure + if (result > 0) continue; + + // the operation failed, we may have to repeat our call + return repeat(monitor, state_sending, OpenSSL::SSL_get_error(_ssl, result)); + } + while (_out && !readable); - // if this is a success, we can proceed with the event loop - if (result > 0) return proceed(); - - // the operation failed, we may have to repeat our call - return repeat(monitor, state_sending, OpenSSL::SSL_get_error(_ssl, result)); + // proceed with the read operation or the event loop + return readable ? receive(monitor, false) : proceed(); } /** * Perform a receive operation - * @param monitor + * @param monitor object to check the existance of the connection object + * @param writable is the socket writable, and should we start a write operation after this operation? * @return TcpState */ - TcpState *receive(const Monitor &monitor) + TcpState *receive(const Monitor &monitor, bool writable) { // start a loop do @@ -295,8 +307,8 @@ private: } while (OpenSSL::SSL_pending(_ssl) > 0); - // go to the next state - return proceed(); + // proceed with the write operation or the event loop + return writable && _out ? write(monitor, false) : proceed(); } @@ -355,16 +367,16 @@ public: if (fd != _socket) return this; // if we were busy with a write operation, we have to repeat that - if (_state == state_sending) return write(monitor); + if (_state == state_sending) return write(monitor, flags & readable); // same is true for read operations, they should also be repeated - if (_state == state_receiving) return receive(monitor); + if (_state == state_receiving) return receive(monitor, flags & writable); // if the socket is readable, we are going to receive data - if (flags & readable) return receive(monitor); + if (flags & readable) return receive(monitor, flags & writable); // socket is not readable (so it must be writable), do we have data to write? - if (_out) return write(monitor); + if (_out) return write(monitor, false); // the only scenario in which we can end up here is the socket should be // closed, but instead of moving to the shutdown-state right, we call proceed() @@ -435,7 +447,7 @@ public: { // put the data in the outgoing buffer _out.add(buffer, size); - + // if we're already busy with sending or receiving, we first have to wait // for that operation to complete before we can move on if (_state != state_idle) return; @@ -498,4 +510,3 @@ public: * End of namespace */ } - From 9077111f0343e35bed94ed761083ca37936b74fd Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 5 Apr 2018 11:13:52 +0200 Subject: [PATCH 168/168] update version number in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c4503c1..a53f610 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ INCLUDE_DIR = ${PREFIX}/include LIBRARY_DIR = ${PREFIX}/lib export LIBRARY_NAME = amqpcpp export SONAME = 3.0 -export VERSION = 3.0.0 +export VERSION = 3.0.1 all: $(MAKE) -C src all