From 20ea5f0e0e37be05f65aa0fea1d851de7e648136 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 16 May 2016 02:32:41 -0700 Subject: [PATCH 01/30] Add frame class, conver context/socket/message to raw zmq. --- Makefile.am | 2 + .../libbitcoin-protocol.vcxproj | 2 + .../libbitcoin-protocol.vcxproj.filters | 6 + include/bitcoin/protocol.hpp | 1 + include/bitcoin/protocol/zmq/frame.hpp | 60 +++++++++ include/bitcoin/protocol/zmq/message.hpp | 1 - include/bitcoin/protocol/zmq/socket.hpp | 8 +- src/zmq/context.cpp | 21 ++-- src/zmq/frame.cpp | 118 ++++++++++++++++++ src/zmq/message.cpp | 30 ++--- src/zmq/socket.cpp | 86 ++++++++----- 11 files changed, 268 insertions(+), 67 deletions(-) create mode 100644 include/bitcoin/protocol/zmq/frame.hpp create mode 100644 src/zmq/frame.cpp diff --git a/Makefile.am b/Makefile.am index 2275c047..b024f794 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,6 +43,7 @@ src_libbitcoin_protocol_la_SOURCES = \ src/zmq/authenticator.cpp \ src/zmq/certificate.cpp \ src/zmq/context.cpp \ + src/zmq/frame.cpp \ src/zmq/message.cpp \ src/zmq/poller.cpp \ src/zmq/socket.cpp @@ -88,6 +89,7 @@ include_bitcoin_protocol_zmq_HEADERS = \ include/bitcoin/protocol/zmq/authenticator.hpp \ include/bitcoin/protocol/zmq/certificate.hpp \ include/bitcoin/protocol/zmq/context.hpp \ + include/bitcoin/protocol/zmq/frame.hpp \ include/bitcoin/protocol/zmq/message.hpp \ include/bitcoin/protocol/zmq/poller.hpp \ include/bitcoin/protocol/zmq/socket.hpp diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj index d9c4d3f1..56d3a72e 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj @@ -80,6 +80,7 @@ + @@ -97,6 +98,7 @@ + diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters index 0bf5e920..a464496a 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters @@ -63,6 +63,9 @@ src + + src\zmq + @@ -110,5 +113,8 @@ include\bitcoin\protocol + + include\bitcoin\protocol\zmq + \ No newline at end of file diff --git a/include/bitcoin/protocol.hpp b/include/bitcoin/protocol.hpp index bbf54dd4..bee97704 100644 --- a/include/bitcoin/protocol.hpp +++ b/include/bitcoin/protocol.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp new file mode 100644 index 00000000..9434f359 --- /dev/null +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2011-2016 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin-protocol. + * + * libbitcoin-protocol is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License with + * additional permissions to the one published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. For more information see LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_PROTOCOL_ZMQ_FRAME_HPP +#define LIBBITCOIN_PROTOCOL_ZMQ_FRAME_HPP + +#include +#include +#include +#include + +namespace libbitcoin { +namespace protocol { +namespace zmq { + +class BCP_API frame +{ +public: + frame(); + frame(const data_chunk& data); + ~frame(); + + bool more() const; + data_chunk payload(); + + bool receive(socket& socket); + bool send(socket& socket, bool more); + bool destroy(); + +private: + static bool initialize(zmq_msg_t& message, const data_chunk& data); + bool set_more(socket& socket); + + bool more_; + const bool valid_; + zmq_msg_t message_; +}; + +} // namespace zmq +} // namespace protocol +} // namespace libbitcoin + +#endif + diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 6394aee5..1692ac20 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -22,7 +22,6 @@ #include #include -#include #include #include diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index f0f4ccc4..44da23ce 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -36,15 +36,15 @@ class BCP_API socket socket(socket&& other); socket(const socket&) = delete; socket(context& context, int type); + ~socket(); + + void* self(); + void* self() const; operator const bool() const; bool operator==(const socket& other) const; bool operator!=(const socket& other) const; - void* self(); - void* self() const; - - void destroy(context& context); int bind(const std::string& address); int connect(const std::string& address); diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index 16a33f3b..9158dcae 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -26,24 +26,23 @@ namespace libbitcoin { namespace protocol { namespace zmq { +// TODO: the member self_ will be a void*. +// TODO: Each socket should maintain a smart pointer reference to the context. +// TODO: When all sockets are closed the context is free to be destroyed. context::context() - : self_(zctx_new()) + : self_(zctx_new() /* zmq_init(self->iothreads) */) { - // Disable czmq signal handling. - zsys_handler_set(NULL); - -#ifdef _MSC_VER - // Hack to prevent czmq from writing to stdout/stderr on Windows. - // This will prevent authentication feedback, but also prevent crashes. - // It is necessary to prevent stdio when using our utf8-everywhere pattern. - // TODO: provide a FILE* here that we can direct to our own log/console. - zsys_set_logstream(NULL); -#endif + // TODO: configure iothreads, default 1 in zmq/zctx_new. + ////self_ = zmq_init(self->iothreads); } context::~context() { BITCOIN_ASSERT(self_); + + //// This will cause all related sockets to terminate. + ////zmq_term(self_); + zctx_destroy(&self_); } diff --git a/src/zmq/frame.cpp b/src/zmq/frame.cpp new file mode 100644 index 00000000..3880f201 --- /dev/null +++ b/src/zmq/frame.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2011-2016 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin-protocol. + * + * libbitcoin-protocol is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License with + * additional permissions to the one published by the Free Software + * Foundation, either version 3 of the License, or (at your option) + * any later version. For more information see LICENSE. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include + +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace protocol { +namespace zmq { + +static constexpr auto zmq_fail = -1; + +// Use for receiving. +frame::frame() + : frame({}) +{ +} + +// Use for sending. +frame::frame(const data_chunk& data) + : more_(false), valid_(initialize(message_, data)) +{ +} + +frame::~frame() +{ + DEBUG_ONLY(const auto result =) destroy(); + BITCOIN_ASSERT(result); +} + +// static +bool frame::initialize(zmq_msg_t& message, const data_chunk& data) +{ + if (data.empty()) + return (zmq_msg_init(&message) != zmq_fail); + + if (zmq_msg_init_size(&message, data.size()) == zmq_fail) + return false; + + std::memcpy(zmq_msg_data(&message), data.data(), data.size()); + return true; +} + +bool frame::more() const +{ + return more_; +} + +// private +bool frame::set_more(socket& socket) +{ + int more; + auto length = static_cast(sizeof(int)); + + if (zmq_getsockopt(socket.self(), ZMQ_RCVMORE, &more, &length) == zmq_fail) + return false; + + more_ = (more != 0); + return true; +} + +data_chunk frame::payload() +{ + const auto size = zmq_msg_size(&message_); + const auto data = zmq_msg_data(&message_); + const auto begin = static_cast(data); + return{ begin, begin + size }; +} + +bool frame::receive(socket& socket) +{ + if (!valid_) + return false; + + return zmq_recvmsg(socket.self(), &message_, 0) != zmq_fail && + set_more(socket); +} + +bool frame::send(socket& socket, bool last) +{ + if (!valid_) + return false; + + const int flags = last ? 0 : ZMQ_SNDMORE; + return zmq_sendmsg(socket.self(), &message_, flags) != zmq_fail; +} + +// private +bool frame::destroy() +{ + return valid_ && (zmq_msg_close(&message_) != zmq_fail); +} + +} // namespace zmq +} // namespace protocol +} // namespace libbitcoin diff --git a/src/zmq/message.cpp b/src/zmq/message.cpp index f8c548e6..b707bd01 100644 --- a/src/zmq/message.cpp +++ b/src/zmq/message.cpp @@ -19,10 +19,8 @@ */ #include -#include #include - -static_assert (sizeof(byte) == sizeof(uint8_t), "Incorrect byte size"); +#include namespace libbitcoin { namespace protocol { @@ -45,17 +43,13 @@ const data_stack& message::parts() const bool message::send(socket& sock) { - int flags = ZFRAME_MORE; - const auto last = parts_.end(); + auto count = parts_.size(); - for (auto part = parts_.begin(); part != last; ++part) + for (const auto& part: parts_) { - if (part == last - 1) - flags = 0; - - auto frame = zframe_new(part->data(), part->size()); + frame frame(part); - if (zframe_send(&frame, sock.self(), flags) == -1) + if (!frame.send(sock, --count == 0)) return false; } @@ -68,19 +62,13 @@ bool message::receive(socket& sock) while (!done) { - auto frame = zframe_recv(sock.self()); + frame frame; - if (frame == nullptr) - { - zframe_destroy(&frame); + if (!frame.receive(sock)) return false; - } - done = zframe_more(frame) == 0; - auto first = zframe_data(frame); - auto last = first + zframe_size(frame); - parts_.push_back({ first, last }); - zframe_destroy(&frame); + parts_.emplace_back(frame.payload()); + done = !frame.more(); } return true; diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 543a118d..97a4cc18 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -39,74 +39,100 @@ socket::socket(void* self) socket::socket(socket&& other) { BITCOIN_ASSERT(self_ == nullptr); + self_ = other.self_; other.self_ = nullptr; } -socket::socket(context& context, int type) +socket::~socket() { - self_ = zsocket_new(context.self(), type); + if (!self_) + return; + + // TODO: configure linger milliseconds, default -1 (infinite). + + ////auto rc = zmq_setsockopt(zocket, ZMQ_LINGER, &linger, sizeof(int)); + ////assert(rc == 0 || zmq_errno() == ETERM); + zsocket_set_linger(self_, 0); + + ////zmq_close (zocket); + ////zsocket_destroy(context.self(), self_); } -socket::operator const bool() const +// TODO: map zmq types to integer class enum. +socket::socket(context& context, int type) { - return self_ != nullptr; + // TODO: configure these int settings as uint16_t. + // TODO: configure sndhwm, default 0, 1000 in zctx_new. + // TODO: configure rcvhwm, default 0, 1000 in zctx_new. + + ////self_ = zmq_socket(context.self(), type); + ////auto rc = zmq_setsockopt(self_, ZMQ_SNDHWM, &sndhwm, sizeof(int)); + ////assert(rc == 0 || zmq_errno() == ETERM); + ////auto rc = zmq_setsockopt(self_, ZMQ_RCVHWM, &rcvhwm, sizeof(int)); + ////assert(rc == 0 || zmq_errno() == ETERM); + self_ = zsocket_new(context.self(), type); } -bool socket::operator==(const socket& other) const +// TODO: convert to bool. +int socket::bind(const std::string& address) { - return self_ == other.self_; + ////int port = zmq_bind (self, endpoint); + return zsocket_bind(self_, address.c_str()); } -bool socket::operator!=(const socket& other) const +// TODO: convert to bool. +int socket::connect(const std::string& address) { - return !(*this == other); + ////Returns 0 if the endpoint is valid, -1 if the connect failed. + ////zmq_connect (self, endpoint); + return zsocket_connect(self_, address.c_str()); } -void* socket::self() +void socket::set_curve_server() { - return self_; + ////int rc = zmq_setsockopt(zocket, ZMQ_CURVE_SERVER, &curve_server, sizeof(int)); + ////assert(rc == 0 || zmq_errno() == ETERM); + zsocket_set_curve_server(self_, 1); } -void* socket::self() const +void socket::set_curve_serverkey(const std::string& key) { - return self_; + ////int rc = zmq_setsockopt(zocket, ZMQ_CURVE_SERVERKEY, curve_serverkey, strlen(curve_serverkey)); + ////assert(rc == 0 || zmq_errno() == ETERM); + zsocket_set_curve_serverkey(self_, key.c_str()); } -void socket::destroy(context& context) +void socket::set_zap_domain(const std::string& domain) { - BITCOIN_ASSERT(self_); - zsocket_destroy(context.self(), self_); + ////int rc = zmq_setsockopt(zocket, ZMQ_ZAP_DOMAIN, zap_domain, strlen(zap_domain)); + ////assert(rc == 0 || zmq_errno() == ETERM); + zsocket_set_zap_domain(self_, domain.c_str()); } -// format-security: format not a string literal and no format arguments. -int socket::bind(const std::string& address) +void* socket::self() { - return zsocket_bind(self_, address.c_str()); + return self_; } -// format-security: format not a string literal and no format arguments. -int socket::connect(const std::string& address) +void* socket::self() const { - static constexpr int zmq_no_linger = 0; - - zsocket_set_linger(self_, zmq_no_linger); - return zsocket_connect(self_, address.c_str()); + return self_; } -void socket::set_curve_server() +socket::operator const bool() const { - zsocket_set_curve_server(self_, 1); + return self_ != nullptr; } -void socket::set_curve_serverkey(const std::string& key) +bool socket::operator==(const socket& other) const { - zsocket_set_curve_serverkey(self_, key.c_str()); + return self_ == other.self_; } -void socket::set_zap_domain(const std::string& domain) +bool socket::operator!=(const socket& other) const { - zsocket_set_zap_domain(self_, domain.c_str()); + return !(*this == other); } } // namespace zmq From 8689e325818fdc310673b720fb0767de4d68c66b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 16 May 2016 04:08:28 -0700 Subject: [PATCH 02/30] Simplify poller contruct and move poller from cmzq to zmq. --- Makefile.am | 4 -- .../libbitcoin-protocol.vcxproj | 1 - .../libbitcoin-protocol.vcxproj.filters | 6 -- .../bitcoin/protocol/zmq/authenticator.hpp | 5 +- include/bitcoin/protocol/zmq/certificate.hpp | 5 +- include/bitcoin/protocol/zmq/context.hpp | 5 +- include/bitcoin/protocol/zmq/frame.hpp | 21 +++++- include/bitcoin/protocol/zmq/impl/poller.ipp | 45 ------------- include/bitcoin/protocol/zmq/poller.hpp | 29 ++++++--- include/bitcoin/protocol/zmq/socket.hpp | 6 +- src/zmq/frame.cpp | 7 +- src/zmq/poller.cpp | 64 ++++++++++++++----- test/zmq/poller.cpp | 18 +++--- 13 files changed, 119 insertions(+), 97 deletions(-) delete mode 100644 include/bitcoin/protocol/zmq/impl/poller.ipp diff --git a/Makefile.am b/Makefile.am index b024f794..12970ce7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -94,7 +94,3 @@ include_bitcoin_protocol_zmq_HEADERS = \ include/bitcoin/protocol/zmq/poller.hpp \ include/bitcoin/protocol/zmq/socket.hpp -include_bitcoin_protocol_zmq_impldir = ${includedir}/bitcoin/protocol/zmq/impl -include_bitcoin_protocol_zmq_impl_HEADERS = \ - include/bitcoin/protocol/zmq/impl/poller.ipp - diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj index 56d3a72e..818c3938 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj @@ -68,7 +68,6 @@ - diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters index a464496a..6ffb549b 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters @@ -2,9 +2,6 @@ - - include\bitcoin\protocol\zmq\impl - @@ -25,9 +22,6 @@ {40d69ec6-5ffd-4db6-871f-a62f29929a8d} - - {e80fad4e-5768-4993-a464-b648693045eb} - diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index 5856d720..6255fdfd 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -33,9 +33,12 @@ class BCP_API authenticator { public: authenticator(context& context); - authenticator(const authenticator&) = delete; ~authenticator(); + /// This class is not copyable. + authenticator(const authenticator&) = delete; + void operator=(const authenticator&) = delete; + operator const bool() const; zauth_t* self(); diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index b6936cf5..ab6cb85e 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -36,9 +36,12 @@ class BCP_API certificate certificate(zcert_t* self); certificate(certificate&& other); certificate(const std::string& filename); - certificate(const certificate&) = delete; ~certificate(); + /// This class is not copyable. + certificate(const certificate&) = delete; + void operator=(const certificate&) = delete; + operator const bool() const; zcert_t* self(); diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index 3b38cccb..81addb58 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -31,9 +31,12 @@ class BCP_API context { public: context(); - context(const context&) = delete; ~context(); + /// This class is not copyable. + context(const context&) = delete; + void operator=(const context&) = delete; + operator const bool() const; zctx_t* self(); diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index 9434f359..f272a1a4 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -32,20 +32,39 @@ namespace zmq { class BCP_API frame { public: + + /// Construct a frame with no payload (for receiving). frame(); + + /// Construct a frame with the specified payload (for sending). frame(const data_chunk& data); + + /// Free the frame's allocated memory. ~frame(); + /// This class is not copyable. + frame(const frame&) = delete; + void operator=(const frame&) = delete; + + /// True if the construction was successful. + operator const bool() const; + + /// True if there is more data to receive. bool more() const; + + /// The initialized or received payload of the frame. data_chunk payload(); + /// Receive a frame on the socket. bool receive(socket& socket); + + /// Send a frame on the socket. bool send(socket& socket, bool more); - bool destroy(); private: static bool initialize(zmq_msg_t& message, const data_chunk& data); bool set_more(socket& socket); + bool destroy(); bool more_; const bool valid_; diff --git a/include/bitcoin/protocol/zmq/impl/poller.ipp b/include/bitcoin/protocol/zmq/impl/poller.ipp deleted file mode 100644 index b350fe6b..00000000 --- a/include/bitcoin/protocol/zmq/impl/poller.ipp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2011-2016 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin-protocol. - * - * libbitcoin-protocol is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License with - * additional permissions to the one published by the Free Software - * Foundation, either version 3 of the License, or (at your option) - * any later version. For more information see LICENSE. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include -#include - -namespace libbitcoin { -namespace protocol { -namespace zmq { - -template -poller::poller(SocketArgs&&... sockets) -{ - auto unmask = [](socket& sock) - { - return sock.self(); - }; - - self_ = zpoller_new(unmask(sockets)..., NULL); - BITCOIN_ASSERT(self_); - - // unmask generates an unused parameter warning when called as: - // zmq::poller::poller(SocketArgs&& ...) [with SocketArgs = {}] - UNUSED(unmask); -} - -} // namespace zmq -} // namespace protocol -} // namespace libbitcoin diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 9cbd59fb..60fa2b07 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -21,6 +21,7 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_POLLER_HPP #include +#include #include #include @@ -31,29 +32,37 @@ namespace zmq { class BCP_API poller { public: - template - poller(SocketArgs&&... sockets); + poller(); + + /// This class is not copyable. poller(const poller&) = delete; + void operator=(const poller&) = delete; + + /// Free poller resources. ~poller(); - operator const bool() const; + /// True if the timeout occurred. + bool expired() const; - zpoller_t* self(); + /// True if the connection is closed. + bool terminated() const; + /// Add a socket to be polled (not thread safe). void add(socket& sock); + + /// Wait specified MICROSECONDS for any socket to receive. socket wait(int timeout); - bool expired(); - bool terminated(); private: - zpoller_t* self_; + typedef std::vector pollers; + + bool expired_; + bool terminated_; + pollers pollers_; }; } // namespace zmq } // namespace protocol } // namespace libbitcoin -#include - #endif - diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index 44da23ce..bec62c6a 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -34,8 +34,12 @@ class BCP_API socket socket(); socket(void* self); socket(socket&& other); - socket(const socket&) = delete; socket(context& context, int type); + + /// This class is not copyable. + socket(const socket&) = delete; + void operator=(const socket&) = delete; + ~socket(); void* self(); diff --git a/src/zmq/frame.cpp b/src/zmq/frame.cpp index 3880f201..fb4583bd 100644 --- a/src/zmq/frame.cpp +++ b/src/zmq/frame.cpp @@ -34,7 +34,7 @@ static constexpr auto zmq_fail = -1; // Use for receiving. frame::frame() - : frame({}) + : more_(false), valid_(initialize(message_, {})) { } @@ -63,6 +63,11 @@ bool frame::initialize(zmq_msg_t& message, const data_chunk& data) return true; } +frame::operator const bool() const +{ + return valid_; +} + bool frame::more() const { return more_; diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index 26718e26..2448ad9c 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -19,6 +19,7 @@ */ #include +#include #include #include #include @@ -27,41 +28,70 @@ namespace libbitcoin { namespace protocol { namespace zmq { -poller::~poller() +poller::poller() + : expired_(false), + terminated_(false) { - BITCOIN_ASSERT(self_); - zpoller_destroy(&self_); } -poller::operator const bool() const +poller::~poller() { - return self_ != nullptr; } -zpoller_t* poller::self() +void poller::add(socket& socket) { - return self_; -} + zmq_pollitem_t item; -void poller::add(socket& sock) -{ - zpoller_add(self_, sock.self()); + // zmq socket. + item.socket = socket.self(); + + // non-zmq socket (unused when socket is set). + item.fd = 0; + + // flags. + item.events = ZMQ_POLLIN; + + // return events. + item.revents = 0; + + pollers_.push_back(item); } socket poller::wait(int timeout) { - auto sock_ptr = zpoller_wait(self_, timeout); - return socket(sock_ptr); + const auto size = pollers_.size(); + BITCOIN_ASSERT(size <= max_int32); + + const auto size32 = static_cast(size); + auto signaled = zmq_poll(pollers_.data(), size32, timeout); + + if (signaled < 0) + { + terminated_ = true; + return nullptr; + } + + if (signaled == 0) + { + expired_ = true; + return nullptr; + } + + for (const auto& poller: pollers_) + if ((poller.revents & ZMQ_POLLIN) != 0) + return poller.socket; + + return nullptr; } -bool poller::expired() +bool poller::expired() const { - return zpoller_expired(self_); + return expired_; } -bool poller::terminated() +bool poller::terminated() const { - return zpoller_terminated(self_); + return terminated_; } } // namespace zmq diff --git a/test/zmq/poller.cpp b/test/zmq/poller.cpp index 404e76bc..35540d94 100644 --- a/test/zmq/poller.cpp +++ b/test/zmq/poller.cpp @@ -32,22 +32,24 @@ int main_disabled() // Create a few sockets zmq::socket vent(context, ZMQ_PUSH); - int rc = vent.bind("tcp://*:9000"); - assert(rc != -1); + int result = vent.bind("tcp://*:9000"); + assert(result != -1); zmq::socket sink(context, ZMQ_PULL); - rc = sink.connect("tcp://localhost:9000"); - assert(rc != -1); + result = sink.connect("tcp://localhost:9000"); + assert(result != -1); zmq::socket bowl(context, ZMQ_PULL); zmq::socket dish(context, ZMQ_PULL); // Set-up poller. - zmq::poller poller(bowl, sink, dish); - assert(poller); + zmq::poller poller; + poller.add(bowl); + poller.add(sink); + poller.add(dish); - rc = zstr_send(vent.self(), "Hello, World"); - assert(rc != -1); + result = zstr_send(vent.self(), "Hello, World"); + assert(result != -1); // We expect a message only on the sink. auto which = poller.wait(-1); From 6f0aa8fb7290b4c06c68737cab99bebca310b1f5 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 16 May 2016 20:33:08 -0700 Subject: [PATCH 03/30] Convert socket from czmq to zmq, update others. --- include/bitcoin/protocol/zmq/frame.hpp | 2 + include/bitcoin/protocol/zmq/poller.hpp | 5 +- include/bitcoin/protocol/zmq/socket.hpp | 79 ++++++++++-- src/zmq/poller.cpp | 12 +- src/zmq/socket.cpp | 165 ++++++++++++++++-------- test/zmq/poller.cpp | 14 +- 6 files changed, 197 insertions(+), 80 deletions(-) diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index f272a1a4..a87ac271 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -68,6 +68,8 @@ class BCP_API frame bool more_; const bool valid_; + + // TODO: define this locally to avoid zmq.h in our includes. zmq_msg_t message_; }; diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 60fa2b07..809db433 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -32,6 +32,7 @@ namespace zmq { class BCP_API poller { public: + /// Construct an empty poller (sockets must be added). poller(); /// This class is not copyable. @@ -50,8 +51,8 @@ class BCP_API poller /// Add a socket to be polled (not thread safe). void add(socket& sock); - /// Wait specified MICROSECONDS for any socket to receive. - socket wait(int timeout); + /// Wait specified microsoconds for any socket to receive. + socket::identifier wait(int timeout_microsoconds); private: typedef std::vector pollers; diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index bec62c6a..2eec534f 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -20,6 +20,8 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_SOCKET_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_SOCKET_HPP +#include +#include #include #include #include @@ -31,33 +33,84 @@ namespace zmq { class BCP_API socket { public: + + /// The full set of socket roles defined by zeromq. + enum class role + { + pair, + publisher, + subscriber, + requester, + replier, + dealer, + router, + puller, + pusher, + extended_publisher, + extended_subscriber, + streamer + }; + + // A locally unique idenfitier for this socket. + typedef intptr_t identifier; + socket(); - socket(void* self); - socket(socket&& other); - socket(context& context, int type); + socket(void* zmq_socket); + socket(context& context, role socket_role); - /// This class is not copyable. + /// This class is not const copyable. socket(const socket&) = delete; void operator=(const socket&) = delete; + /// Free socket resources. ~socket(); - void* self(); - void* self() const; - + /// True if there is an encapsultaed zeromq socket. operator const bool() const; + + /// True if the encapsultaed zeromq sockets are the same. bool operator==(const socket& other) const; + + /// True if the encapsultaed zeromq sockets are not the same. bool operator!=(const socket& other) const; - int bind(const std::string& address); - int connect(const std::string& address); + /// The underlying zeromq socket. + void* self(); + void* self() const; + + /// The port to whicih the socket is bound, or zero. + uint16_t port() const; + + /// The socket identifier is an opaue correlation idenfier. + identifier id() const; + + /// Transfer ownership of this socket to another. + void assign(socket&& other); - void set_curve_server(); - void set_curve_serverkey(const std::string& key); - void set_zap_domain(const std::string& domain); + /// Bind the socket to the specified local address. + bool bind(const std::string& address); + + /// Connect the socket to the specified remote address. + bool connect(const std::string& address); + + bool set_curve_server(); + bool set_curve_serverkey(const std::string& key); + bool set_zap_domain(const std::string& domain); private: - void* self_; + static int to_socket_type(role socket_role); + + bool set(int32_t option, int32_t value); + bool set(int32_t option, const std::string& value); + void initialize(context& context, role socket_role); + bool destroy(); + + // The encapsulated zeromq socket. + void* socket_; + uint16_t port_; + const int32_t send_buffer_; + const int32_t receive_buffer_; + const int32_t linger_milliseconds_; }; } // namespace zmq diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index 2448ad9c..74bc5e1d 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -57,31 +57,31 @@ void poller::add(socket& socket) pollers_.push_back(item); } -socket poller::wait(int timeout) +socket::identifier poller::wait(int timeout_microsoconds) { const auto size = pollers_.size(); BITCOIN_ASSERT(size <= max_int32); const auto size32 = static_cast(size); - auto signaled = zmq_poll(pollers_.data(), size32, timeout); + auto signaled = zmq_poll(pollers_.data(), size32, timeout_microsoconds); if (signaled < 0) { terminated_ = true; - return nullptr; + return 0; } if (signaled == 0) { expired_ = true; - return nullptr; + return 0; } for (const auto& poller: pollers_) if ((poller.revents & ZMQ_POLLIN) != 0) - return poller.socket; + return reinterpret_cast(poller.socket); - return nullptr; + return 0; } bool poller::expired() const diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 97a4cc18..77de23fc 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -19,115 +19,176 @@ */ #include -#include +#include +#include +#include #include namespace libbitcoin { namespace protocol { namespace zmq { +static constexpr uint16_t no_port = 0; +static constexpr int32_t zmq_true = 1; +static constexpr int32_t zmq_fail = -1; +static constexpr int32_t zmq_forever = -1; +static constexpr int32_t zmq_send_buffer = 1000; +static constexpr int32_t zmq_receive_buffer = 1000; + socket::socket() - : self_(nullptr) + : socket(nullptr) { } -socket::socket(void* self) - : self_(self) +socket::socket(void* zmq_socket) + : socket_(zmq_socket), + port_(no_port), + send_buffer_(zmq_send_buffer), + receive_buffer_(zmq_receive_buffer), + linger_milliseconds_(zmq_forever) { } -socket::socket(socket&& other) +socket::socket(context& context, role socket_role) + : socket() { - BITCOIN_ASSERT(self_ == nullptr); - - self_ = other.self_; - other.self_ = nullptr; + initialize(context, socket_role); } socket::~socket() { - if (!self_) + DEBUG_ONLY(const auto result =) destroy(); + BITCOIN_ASSERT(result); +} + +int32_t socket::to_socket_type(role socket_role) +{ + switch (socket_role) + { + case role::pair: return ZMQ_PAIR; + case role::publisher: return ZMQ_PUB; + case role::subscriber: return ZMQ_SUB; + case role::requester: return ZMQ_REQ; + case role::replier: return ZMQ_REP; + case role::dealer: return ZMQ_DEALER; + case role::router: return ZMQ_ROUTER; + case role::puller: return ZMQ_PULL; + case role::pusher: return ZMQ_PUSH; + case role::extended_publisher: return ZMQ_XPUB; + case role::extended_subscriber: return ZMQ_XSUB; + case role::streamer: return ZMQ_STREAM; + default: return -1; + } +} + +bool socket::set(int32_t option, int value) +{ + return zmq_setsockopt(socket_, option, &value, sizeof(value)) != zmq_fail; +} + +bool socket::set(int32_t option, const std::string& value) +{ + const auto buffer = value.c_str(); + return zmq_setsockopt(socket_, option, buffer, value.size()) != zmq_fail; +} + +void socket::initialize(context& context, role socket_role) +{ + socket_ = zmq_socket(context.self(), to_socket_type(socket_role)); + + if (socket_ == nullptr) return; - // TODO: configure linger milliseconds, default -1 (infinite). + if (!set(ZMQ_SNDHWM, send_buffer_) || !set(ZMQ_RCVHWM, receive_buffer_)) + destroy(); +} - ////auto rc = zmq_setsockopt(zocket, ZMQ_LINGER, &linger, sizeof(int)); - ////assert(rc == 0 || zmq_errno() == ETERM); - zsocket_set_linger(self_, 0); +bool socket::destroy() +{ + if (socket_ == nullptr) + return false; - ////zmq_close (zocket); - ////zsocket_destroy(context.self(), self_); + const auto linger = set(ZMQ_LINGER, linger_milliseconds_); + const auto closed = zmq_close(socket_) != zmq_fail; + + // Always clear state even if the socket is leaked. + port_ = no_port; + socket_ = nullptr; + + return linger && closed; } -// TODO: map zmq types to integer class enum. -socket::socket(context& context, int type) +void socket::assign(socket&& other) { - // TODO: configure these int settings as uint16_t. - // TODO: configure sndhwm, default 0, 1000 in zctx_new. - // TODO: configure rcvhwm, default 0, 1000 in zctx_new. + // Free any existing socket resources. + destroy(); + + // Assume ownership of the other socket's state. + port_ = other.port_; + socket_ = other.socket_; - ////self_ = zmq_socket(context.self(), type); - ////auto rc = zmq_setsockopt(self_, ZMQ_SNDHWM, &sndhwm, sizeof(int)); - ////assert(rc == 0 || zmq_errno() == ETERM); - ////auto rc = zmq_setsockopt(self_, ZMQ_RCVHWM, &rcvhwm, sizeof(int)); - ////assert(rc == 0 || zmq_errno() == ETERM); - self_ = zsocket_new(context.self(), type); + // Don't destroy other socket's resource as it would destroy ours. + other.port_ = no_port; + other.socket_ = nullptr; } -// TODO: convert to bool. -int socket::bind(const std::string& address) +bool socket::bind(const std::string& address) { - ////int port = zmq_bind (self, endpoint); - return zsocket_bind(self_, address.c_str()); + // Returns zero if the bind fails, otherwise the port (ports are 16 bit). + const auto port = zmq_bind(socket_, address.c_str()); + port_ = static_cast(port); + return port_ != no_port; } -// TODO: convert to bool. -int socket::connect(const std::string& address) +bool socket::connect(const std::string& address) { - ////Returns 0 if the endpoint is valid, -1 if the connect failed. - ////zmq_connect (self, endpoint); - return zsocket_connect(self_, address.c_str()); + // String safety issue, API requires null termination. + return zmq_connect(socket_, address.c_str()) != zmq_fail; } -void socket::set_curve_server() +bool socket::set_curve_server() { - ////int rc = zmq_setsockopt(zocket, ZMQ_CURVE_SERVER, &curve_server, sizeof(int)); - ////assert(rc == 0 || zmq_errno() == ETERM); - zsocket_set_curve_server(self_, 1); + return set(ZMQ_CURVE_SERVER, zmq_true); } -void socket::set_curve_serverkey(const std::string& key) +bool socket::set_curve_serverkey(const std::string& key) { - ////int rc = zmq_setsockopt(zocket, ZMQ_CURVE_SERVERKEY, curve_serverkey, strlen(curve_serverkey)); - ////assert(rc == 0 || zmq_errno() == ETERM); - zsocket_set_curve_serverkey(self_, key.c_str()); + return set(ZMQ_CURVE_SERVERKEY, key); } -void socket::set_zap_domain(const std::string& domain) +bool socket::set_zap_domain(const std::string& domain) { - ////int rc = zmq_setsockopt(zocket, ZMQ_ZAP_DOMAIN, zap_domain, strlen(zap_domain)); - ////assert(rc == 0 || zmq_errno() == ETERM); - zsocket_set_zap_domain(self_, domain.c_str()); + return set(ZMQ_ZAP_DOMAIN, domain); } void* socket::self() { - return self_; + return socket_; } void* socket::self() const { - return self_; + return socket_; +} + +uint16_t socket::port() const +{ + return port_; +} + +socket::identifier socket::id() const +{ + return reinterpret_cast(socket_); } socket::operator const bool() const { - return self_ != nullptr; + return socket_ != nullptr; } bool socket::operator==(const socket& other) const { - return self_ == other.self_; + return socket_ == other.socket_; } bool socket::operator!=(const socket& other) const diff --git a/test/zmq/poller.cpp b/test/zmq/poller.cpp index 35540d94..7d93967c 100644 --- a/test/zmq/poller.cpp +++ b/test/zmq/poller.cpp @@ -31,16 +31,16 @@ int main_disabled() assert(context); // Create a few sockets - zmq::socket vent(context, ZMQ_PUSH); + zmq::socket vent(context, zmq::socket::role::pusher); int result = vent.bind("tcp://*:9000"); assert(result != -1); - zmq::socket sink(context, ZMQ_PULL); + zmq::socket sink(context, zmq::socket::role::puller); result = sink.connect("tcp://localhost:9000"); assert(result != -1); - zmq::socket bowl(context, ZMQ_PULL); - zmq::socket dish(context, ZMQ_PULL); + zmq::socket bowl(context, zmq::socket::role::puller); + zmq::socket dish(context, zmq::socket::role::puller); // Set-up poller. zmq::poller poller; @@ -52,12 +52,12 @@ int main_disabled() assert(result != -1); // We expect a message only on the sink. - auto which = poller.wait(-1); - assert(which == sink); + const auto id = poller.wait(-1); + assert(id == sink.id()); assert(!poller.expired()); assert(!poller.terminated()); - auto message = zstr_recv(which.self()); + auto message = zstr_recv(sink.self()); assert(streq(message, "Hello, World")); free(message); From 910671b2ffff694420f7b566bec0019eefedbf06 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 16 May 2016 21:30:01 -0700 Subject: [PATCH 04/30] Change context from czmq to zmq, add message clear, comments. --- include/bitcoin/protocol/zmq/context.hpp | 15 +- include/bitcoin/protocol/zmq/message.hpp | 15 +- include/bitcoin/protocol/zmq/poller.hpp | 7 +- include/bitcoin/protocol/zmq/socket.hpp | 6 - src/zmq/authenticator.cpp | 2 +- src/zmq/certificate.cpp | 7 - src/zmq/context.cpp | 29 ++-- src/zmq/message.cpp | 25 +++- src/zmq/poller.cpp | 7 +- src/zmq/socket.cpp | 10 -- test/zmq/ironhouse2.cpp | 173 ++++++++++++----------- test/zmq/poller.cpp | 30 ++-- 12 files changed, 179 insertions(+), 147 deletions(-) diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index 81addb58..ad028a10 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -20,7 +20,7 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_CONTEXT_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_CONTEXT_HPP -#include +#include #include namespace libbitcoin { @@ -30,19 +30,28 @@ namespace zmq { class BCP_API context { public: + + /// Construct a context. context(); + + /// Cause all sockets of this context to close. ~context(); /// This class is not copyable. context(const context&) = delete; void operator=(const context&) = delete; + /// True if the context construction was successful. operator const bool() const; - zctx_t* self(); + /// The underlying zeromq context. + void* self(); private: - zctx_t* self_; + bool destroy(); + + void* self_; + int32_t threads_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 1692ac20..7f978efa 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -20,8 +20,7 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_MESSAGE_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_MESSAGE_HPP -#include -#include +#include #include #include @@ -32,12 +31,22 @@ namespace zmq { class BCP_API message { public: - void append(const data_chunk& part); + + /// Add a message part to the outgoing message. void append(data_chunk&& part); + void append(const data_chunk& part); + void append(const std::string& part); + // Obtain the parts of the created or read message. const data_stack& parts() const; + /// Clear the stack of message parts. + void clear(); + + /// Send the message in parts. If a send fails the unsent parts remain. bool send(socket& sock); + + /// Receve a message (clears the stack first). bool receive(socket& sock); private: diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 809db433..3ce9f83d 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -20,8 +20,9 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_POLLER_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_POLLER_HPP -#include +#include #include +#include #include #include @@ -51,8 +52,8 @@ class BCP_API poller /// Add a socket to be polled (not thread safe). void add(socket& sock); - /// Wait specified microsoconds for any socket to receive. - socket::identifier wait(int timeout_microsoconds); + /// Wait specified microsoconds for any socket to receive, -1 is forever. + socket::identifier wait(int32_t timeout_microsoconds); private: typedef std::vector pollers; diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index 2eec534f..161d264e 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -68,12 +68,6 @@ class BCP_API socket /// True if there is an encapsultaed zeromq socket. operator const bool() const; - /// True if the encapsultaed zeromq sockets are the same. - bool operator==(const socket& other) const; - - /// True if the encapsultaed zeromq sockets are not the same. - bool operator!=(const socket& other) const; - /// The underlying zeromq socket. void* self(); void* self() const; diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index eb4beccf..d03cfadc 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -26,7 +26,7 @@ namespace protocol { namespace zmq { authenticator::authenticator(context& context) - : self_(zauth_new(context.self())) + : self_(nullptr /*zauth_new(context.self())*/) { } diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index b1737727..522ac223 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -30,26 +30,21 @@ namespace zmq { certificate::certificate() : self_(zcert_new()) { - // May be invalid (unlikely). } certificate::certificate(zcert_t* self) : self_(self) { - // May be invalid. } certificate::certificate(certificate&& other) : self_(other.self_) { - // May be invalid. - // Transfer of pointer ownership. other.self_ = nullptr; } certificate::certificate(const std::string& filename) : self_(zcert_load(filename.c_str())) { - // May be invalid. } certificate::~certificate() { @@ -66,7 +61,6 @@ void certificate::reset(zcert_t* self) if (self_ != nullptr) zcert_destroy(&self_); - // May be invalid. self_ = self; } @@ -75,7 +69,6 @@ void certificate::reset(const std::string& filename) if (self_ != nullptr) zcert_destroy(&self_); - // May be invalid. self_ = zcert_load(filename.c_str()); } diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index 9158dcae..a8e9f458 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -19,31 +19,40 @@ */ #include -#include +#include +#include #include namespace libbitcoin { namespace protocol { namespace zmq { -// TODO: the member self_ will be a void*. +static constexpr int32_t zmq_fail = -1; +static constexpr int32_t zmq_io_threads = 1; + // TODO: Each socket should maintain a smart pointer reference to the context. // TODO: When all sockets are closed the context is free to be destroyed. +// TODO: Add call to zmq_term on the context that causes all sockets to close. context::context() - : self_(zctx_new() /* zmq_init(self->iothreads) */) + : threads_(zmq_io_threads), + self_(zmq_init(threads_)) { - // TODO: configure iothreads, default 1 in zmq/zctx_new. - ////self_ = zmq_init(self->iothreads); } context::~context() { - BITCOIN_ASSERT(self_); + DEBUG_ONLY(const auto result =) destroy(); + BITCOIN_ASSERT(result); +} - //// This will cause all related sockets to terminate. - ////zmq_term(self_); +bool context::destroy() +{ + if (self_ == nullptr) + return true; - zctx_destroy(&self_); + // This will cause all related sockets to close and will block until + // all sockets open within context have been closed with zmq_close(). + return zmq_term(self_) != zmq_fail; } context::operator const bool() const @@ -51,7 +60,7 @@ context::operator const bool() const return self_ != nullptr; } -zctx_t* context::self() +void* context::self() { return self_; } diff --git a/src/zmq/message.cpp b/src/zmq/message.cpp index b707bd01..2a53c56f 100644 --- a/src/zmq/message.cpp +++ b/src/zmq/message.cpp @@ -19,6 +19,7 @@ */ #include +#include #include #include @@ -26,14 +27,19 @@ namespace libbitcoin { namespace protocol { namespace zmq { -void message::append(const data_chunk& part) +void message::append(data_chunk&& part) { - parts_.push_back(part); + parts_.emplace_back(std::move(part)); } -void message::append(data_chunk&& part) +void message::append(const std::string& part) { - parts_.emplace_back(std::move(part)); + append(data_chunk{ part.begin(), part.end() }); +} + +void message::append(const data_chunk& part) +{ + parts_.push_back(part); } const data_stack& message::parts() const @@ -41,24 +47,31 @@ const data_stack& message::parts() const return parts_; } +void message::clear() +{ + parts_.clear(); +} + bool message::send(socket& sock) { auto count = parts_.size(); - for (const auto& part: parts_) + for (auto it = parts_.begin(); it != parts_.end(); it = parts_.erase(it)) { - frame frame(part); + frame frame(*it); if (!frame.send(sock, --count == 0)) return false; } + BITCOIN_ASSERT(parts_.empty()); return true; } bool message::receive(socket& sock) { auto done = false; + clear(); while (!done) { diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index 74bc5e1d..3806105f 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -57,7 +57,10 @@ void poller::add(socket& socket) pollers_.push_back(item); } -socket::identifier poller::wait(int timeout_microsoconds) +// The timeout is typed as 'long' by zermq. This is 32 bit on windows and +// typically 64 bit on other platforms. So for consistency of config we +// limit the domain to 32 bit using int32_t. -1 signals infinite wait. +socket::identifier poller::wait(int32_t timeout_microsoconds) { const auto size = pollers_.size(); BITCOIN_ASSERT(size <= max_int32); diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 77de23fc..6e40a402 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -186,16 +186,6 @@ socket::operator const bool() const return socket_ != nullptr; } -bool socket::operator==(const socket& other) const -{ - return socket_ == other.socket_; -} - -bool socket::operator!=(const socket& other) const -{ - return !(*this == other); -} - } // namespace zmq } // namespace protocol } // namespace libbitcoin diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index 20b091f5..3aa2f771 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -19,94 +19,95 @@ */ #include #include -#include +#include +#include #include using namespace bc; using namespace bc::protocol; -////void client_task(const std::string& server_public_text) -////{ -//// // Load our persistent certificate from disk -//// zmq::certificate client_cert("client_cert.txt"); -//// assert(client_cert); -//// -//// // Create a pull socket. -//// zmq::context context; -//// assert(context); -//// zmq::socket client(context, ZMQ_PULL); -//// assert(client); -//// -//// // Configure the client to provide a certificate and use the server key. -//// client_cert.apply(client); -//// client.set_curve_serverkey(server_public_text); -//// -//// // Connect to the server. -//// int rc = client.connect("tcp://127.0.0.1:9000"); -//// assert(rc == 0); -//// -//// // Wait for our message, which signals the test was successful. -//// zmq::message msg; -//// msg.receive(client); -//// assert(msg.parts().size() == 1); -//// assert((msg.parts()[0] == data_chunk{ { 0xde, 0xad, 0xbe, 0xef } })); -//// -//// puts("Ironhouse test OK"); -////} -//// -////void server_task(zmq::certificate& server_cert) -////{ -//// zmq::context context; -//// assert(context); -//// -//// // Start the authenticator on the context and tell it authenticate clients -//// // via the certificates stored in the .curve directory. -//// zmq::authenticator authenticator(context); -//// authenticator.set_verbose(true); -//// authenticator.allow("127.0.0.1"); -//// authenticator.configure_curve("*", ".curve"); -//// -//// // Bind a push socket to the authenticated context. -//// zmq::socket server(context, ZMQ_PUSH); -//// assert(server); -//// server_cert.apply(server); -//// server.set_curve_server(); -//// int rc = server.bind("tcp://*:9000"); -//// assert(rc != -1); -//// -//// // Send our test message, just once -//// zmq::message msg; -//// msg.append({ { 0xde, 0xad, 0xbe, 0xef } }); -//// msg.send(server); -//// -//// zclock_sleep(200); -////} -//// -////int main() -////{ -//// // Create the certificate store directory. -//// int rc = zsys_dir_create(".curve"); -//// assert(rc == 0); -//// -//// // Create the client certificate. -//// zmq::certificate client_cert; -//// -//// // Save the client certificate. -//// rc = client_cert.save("client_cert.txt"); -//// assert(rc == 0); -//// -//// // Save the client public certificate. -//// rc = client_cert.save_public(".curve/test_cert.pub"); -//// assert(rc == 0); -//// -//// // Create the server certificate. -//// zmq::certificate server_cert; -//// -//// // Start the two detached threads, each with own ZeroMQ context. -//// std::thread server_thread(server_task, std::ref(server_cert)); -//// std::thread client_thread(client_task, server_cert.public_text()); -//// -//// client_thread.join(); -//// server_thread.join(); -//// return 0; -////} +void client_task(const std::string& server_public_text) +{ + // Load our persistent certificate from disk + zmq::certificate client_cert("client_cert.txt"); + assert(client_cert); + + // Create a pull socket. + zmq::context context; + assert(context); + zmq::socket client(context, zmq::socket::role::puller); + assert(client); + + // Configure the client to provide a certificate and use the server key. + client_cert.apply(client); + client.set_curve_serverkey(server_public_text); + + // Connect to the server. + const auto result = client.connect("tcp://127.0.0.1:9000"); + assert(result); + + // Wait for our message, which signals the test was successful. + zmq::message msg; + msg.receive(client); + assert(msg.parts().size() == 1); + assert((msg.parts()[0] == data_chunk{ { 0xde, 0xad, 0xbe, 0xef } })); + + puts("Ironhouse test OK"); +} + +void server_task(zmq::certificate& server_cert) +{ + zmq::context context; + assert(context); + + // Start the authenticator on the context and tell it authenticate clients + // via the certificates stored in the .curve directory. + zmq::authenticator authenticator(context); + authenticator.set_verbose(true); + authenticator.allow("127.0.0.1"); + authenticator.configure_curve("*", ".curve"); + + // Bind a push socket to the authenticated context. + zmq::socket server(context, zmq::socket::role::pusher); + assert(server); + server_cert.apply(server); + server.set_curve_server(); + const auto result = server.bind("tcp://*:9000"); + assert(result); + + // Send our test message, just once + zmq::message msg; + msg.append({ { 0xde, 0xad, 0xbe, 0xef } }); + msg.send(server); + + zclock_sleep(200); +} + +int main() +{ + // Create the certificate store directory. + auto result = boost::filesystem::create_directory(".curve"); + assert(result == 0); + + // Create the client certificate. + zmq::certificate client_cert; + + // Save the client certificate. + auto rc = client_cert.save("client_cert.txt"); + assert(rc == 0); + + // Save the client public certificate. + rc = client_cert.save_public(".curve/test_cert.pub"); + assert(rc == 0); + + // Create the server certificate. + zmq::certificate server_cert; + + // Start the two detached threads, each with own ZeroMQ context. + std::thread server_thread(server_task, std::ref(server_cert)); + std::thread client_thread(client_task, server_cert.public_text()); + + client_thread.join(); + server_thread.join(); + return 0; +} diff --git a/test/zmq/poller.cpp b/test/zmq/poller.cpp index 7d93967c..1bc67487 100644 --- a/test/zmq/poller.cpp +++ b/test/zmq/poller.cpp @@ -18,8 +18,6 @@ * along with this program. If not, see . */ #include -#include -#include #include using namespace bc; @@ -32,12 +30,12 @@ int main_disabled() // Create a few sockets zmq::socket vent(context, zmq::socket::role::pusher); - int result = vent.bind("tcp://*:9000"); - assert(result != -1); + auto result = vent.bind("tcp://*:9000"); + assert(result); zmq::socket sink(context, zmq::socket::role::puller); result = sink.connect("tcp://localhost:9000"); - assert(result != -1); + assert(result); zmq::socket bowl(context, zmq::socket::role::puller); zmq::socket dish(context, zmq::socket::role::puller); @@ -48,8 +46,13 @@ int main_disabled() poller.add(sink); poller.add(dish); - result = zstr_send(vent.self(), "Hello, World"); - assert(result != -1); + const std::string hello = "Hello, World"; + + // Build and send the message. + zmq::message message; + message.append(hello); + result = message.send(vent); + assert(result); // We expect a message only on the sink. const auto id = poller.wait(-1); @@ -57,9 +60,16 @@ int main_disabled() assert(!poller.expired()); assert(!poller.terminated()); - auto message = zstr_recv(sink.self()); - assert(streq(message, "Hello, World")); + // Receive the message. + result = message.receive(sink); + assert(result); + + // Check the size. + const auto& parts = message.parts(); + assert(parts.size() == 1); - free(message); + // Check the value. + const auto& part = parts[0]; + assert(std::equal(part.begin(), part.end(), hello.begin())); return 0; } From 5ed7cdc7fc1dbb7e7d6695d771b9b3f1f7420ff1 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 16 May 2016 21:35:23 -0700 Subject: [PATCH 05/30] Comments. --- include/bitcoin/protocol/zmq/context.hpp | 1 - include/bitcoin/protocol/zmq/frame.hpp | 1 - include/bitcoin/protocol/zmq/message.hpp | 1 - include/bitcoin/protocol/zmq/socket.hpp | 6 +++++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index ad028a10..ddd5459e 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -30,7 +30,6 @@ namespace zmq { class BCP_API context { public: - /// Construct a context. context(); diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index a87ac271..788162bf 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -32,7 +32,6 @@ namespace zmq { class BCP_API frame { public: - /// Construct a frame with no payload (for receiving). frame(); diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 7f978efa..14fcbb6d 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -31,7 +31,6 @@ namespace zmq { class BCP_API message { public: - /// Add a message part to the outgoing message. void append(data_chunk&& part); void append(const data_chunk& part); diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index 161d264e..73aad8e1 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -33,7 +33,6 @@ namespace zmq { class BCP_API socket { public: - /// The full set of socket roles defined by zeromq. enum class role { @@ -87,8 +86,13 @@ class BCP_API socket /// Connect the socket to the specified remote address. bool connect(const std::string& address); + /// Configure the socket as a curve server. bool set_curve_server(); + + /// Configure the socket as client to a curve server. bool set_curve_serverkey(const std::string& key); + + /// Sets the domain for ZAP (ZMQ RFC 27) authentication. bool set_zap_domain(const std::string& domain); private: From a4360ca7d8a838543b91605c4e897b505be946b0 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 17 May 2016 02:13:25 -0700 Subject: [PATCH 06/30] Disable cert/auth czmq, remove czmq dependency. --- Makefile.am | 8 ++-- .../libbitcoin-protocol-test.props | 12 ----- .../libbitcoin-protocol-test.vcxproj | 2 - .../libbitcoin-protocol-test/packages.config | 1 - .../libbitcoin-protocol.props | 16 ------- .../libbitcoin-protocol.vcxproj | 2 - .../libbitcoin-protocol/packages.config | 1 - configure.ac | 14 ------ .../bitcoin/protocol/zmq/authenticator.hpp | 12 ++--- include/bitcoin/protocol/zmq/certificate.hpp | 14 +++--- install.sh | 8 ---- libbitcoin-protocol.pc.in | 2 +- src/zmq/authenticator.cpp | 19 ++++---- src/zmq/certificate.cpp | 47 ++++++++----------- test/zmq/ironhouse2.cpp | 30 ++++++------ 15 files changed, 61 insertions(+), 127 deletions(-) diff --git a/Makefile.am b/Makefile.am index 12970ce7..925c10c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,8 +32,8 @@ doc_DATA = \ # src/libbitcoin-protocol.la => ${libdir} #------------------------------------------------------------------------------ lib_LTLIBRARIES = src/libbitcoin-protocol.la -src_libbitcoin_protocol_la_CPPFLAGS = -I${srcdir}/include ${zmq_CPPFLAGS} ${czmq_CPPFLAGS} ${bitcoin_CPPFLAGS} -src_libbitcoin_protocol_la_LIBADD = ${zmq_LIBS} ${czmq_LIBS} ${bitcoin_LIBS} +src_libbitcoin_protocol_la_CPPFLAGS = -I${srcdir}/include ${zmq_CPPFLAGS} ${bitcoin_CPPFLAGS} +src_libbitcoin_protocol_la_LIBADD = ${zmq_LIBS} ${bitcoin_LIBS} src_libbitcoin_protocol_la_SOURCES = \ src/converter.cpp \ src/interface.pb.cc \ @@ -55,8 +55,8 @@ if WITH_TESTS TESTS = libbitcoin_protocol_test_runner.sh check_PROGRAMS = test/libbitcoin_protocol_test -test_libbitcoin_protocol_test_CPPFLAGS = -I${srcdir}/include ${zmq_CPPFLAGS} ${czmq_CPPFLAGS} ${bitcoin_CPPFLAGS} -test_libbitcoin_protocol_test_LDADD = src/libbitcoin-protocol.la ${boost_unit_test_framework_LIBS} ${zmq_LIBS} ${czmq_LIBS} ${bitcoin_LIBS} +test_libbitcoin_protocol_test_CPPFLAGS = -I${srcdir}/include ${zmq_CPPFLAGS} ${bitcoin_CPPFLAGS} +test_libbitcoin_protocol_test_LDADD = src/libbitcoin-protocol.la ${boost_unit_test_framework_LIBS} ${zmq_LIBS} ${bitcoin_LIBS} test_libbitcoin_protocol_test_SOURCES = \ test/converter.cpp \ test/main.cpp \ diff --git a/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.props b/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.props index 30032bad..2d28c955 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.props +++ b/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.props @@ -29,30 +29,21 @@ dynamic - dynamic - dynamic dynamic - dynamic dynamic dynamic ltcg - ltcg - ltcg ltcg - ltcg ltcg ltcg static - static - static static - static static static @@ -62,10 +53,7 @@ - - - diff --git a/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj b/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj index 991d74f0..0c7f71e1 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj @@ -88,7 +88,6 @@ - @@ -106,7 +105,6 @@ - diff --git a/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config b/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config index cb19c869..82fedcd2 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config +++ b/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config @@ -10,7 +10,6 @@ - diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.props b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.props index 2568f8e1..1868c688 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.props +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.props @@ -36,33 +36,20 @@ - dynamic dynamic - dynamic - dynamic dynamic - dynamic dynamic - ltcg ltcg - ltcg - ltcg - ltcg ltcg - ltcg ltcg - static static - static - static static - static static @@ -71,10 +58,7 @@ - - - diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj index 818c3938..4456eac3 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj @@ -106,7 +106,6 @@ - @@ -116,7 +115,6 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - diff --git a/builds/msvc/vs2013/libbitcoin-protocol/packages.config b/builds/msvc/vs2013/libbitcoin-protocol/packages.config index de9dea66..cc76e323 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/packages.config +++ b/builds/msvc/vs2013/libbitcoin-protocol/packages.config @@ -1,7 +1,6 @@  - diff --git a/configure.ac b/configure.ac index cb0f1bac..162b48ed 100644 --- a/configure.ac +++ b/configure.ac @@ -130,14 +130,6 @@ AC_SUBST([zmq_CPPFLAGS], [${zmq_CFLAGS}]) AC_MSG_NOTICE([zmq_CPPFLAGS : ${zmq_CPPFLAGS}]) AC_MSG_NOTICE([zmq_LIBS : ${zmq_LIBS}]) -# Require czmq of at least version 3.0.0 and output ${czmq_CPPFLAGS/LIBS/PKG}. -#------------------------------------------------------------------------------ -PKG_CHECK_MODULES([czmq], [libczmq >= 3.0.0]) -AC_SUBST([czmq_PKG], ['libczmq >= 3.0.0']) -AC_SUBST([czmq_CPPFLAGS], [${czmq_CFLAGS}]) -AC_MSG_NOTICE([czmq_CPPFLAGS : ${czmq_CPPFLAGS}]) -AC_MSG_NOTICE([czmq_LIBS : ${czmq_LIBS}]) - # Require bitcoin of at least version 3.0.0 and output ${bitcoin_CPPFLAGS/LIBS/PKG}. #------------------------------------------------------------------------------ PKG_CHECK_MODULES([bitcoin], [libbitcoin >= 3.0.0]) @@ -193,12 +185,6 @@ AS_CASE([${CC}], [*gcc*], [AX_CHECK_COMPILE_FLAG([-Wno-deprecated-declarations], [CXXFLAGS="$CXXFLAGS -Wno-deprecated-declarations"])]) -# Clean up czmq non-literal string format warnings. -#------------------------------------------------------------------------------ -AS_CASE([${CC}], [*], - [AX_CHECK_COMPILE_FLAG([-Wformat-security], - [CXXFLAGS="$CXXFLAGS -Wformat-security"])]) - # Protect stack. #------------------------------------------------------------------------------ AS_CASE([${CC}], [*], diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index 6255fdfd..c05edacb 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -21,7 +21,7 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP #include -#include +#include #include #include @@ -41,18 +41,16 @@ class BCP_API authenticator operator const bool() const; - zauth_t* self(); + void* self(); void allow(const std::string& address); void deny(const std::string& address); - void configure_plain(const std::string& domain, - const std::string& filename); - void configure_curve(const std::string& domain, - const std::string& location); + void configure_plain(const std::string& domain, const std::string& filename); + void configure_curve(const std::string& domain, const std::string& location); void set_verbose(bool verbose); private: - zauth_t* self_; + void* self_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index ab6cb85e..c82c9b86 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -21,7 +21,7 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP #include -#include +#include #include #include @@ -33,7 +33,6 @@ class BCP_API certificate { public: certificate(); - certificate(zcert_t* self); certificate(certificate&& other); certificate(const std::string& filename); ~certificate(); @@ -44,19 +43,18 @@ class BCP_API certificate operator const bool() const; - zcert_t* self(); + void* self(); - void reset(zcert_t* self); void reset(const std::string& filename); void set_meta(const std::string& name, const std::string& value); - int save(const std::string& filename); - int save_public(const std::string& filename); - int save_secret(const std::string& filename); + bool save(const std::string& filename); + bool save_public(const std::string& filename); + bool save_secret(const std::string& filename); std::string public_text() const; void apply(socket& sock); private: - zcert_t* self_; + void* self_; }; } // namespace zmq diff --git a/install.sh b/install.sh index cad0ccf9..4554ea20 100755 --- a/install.sh +++ b/install.sh @@ -195,13 +195,6 @@ BOOST_OPTIONS=( "--with-thread" \ "--with-test") -# Define czmq options. -#------------------------------------------------------------------------------ -CZMQ_OPTIONS=( -"--disable-zmakecert" \ -"--disable-czmq_selftest" \ -"${with_pkgconfigdir}") - # Define secp256k1 options. #------------------------------------------------------------------------------ SECP256K1_OPTIONS=( @@ -670,7 +663,6 @@ build_all() { build_from_tarball_boost $BOOST_URL $BOOST_ARCHIVE bzip2 . $PARALLEL "$BUILD_BOOST" "${BOOST_OPTIONS[@]}" build_from_github zeromq libzmq master $PARALLEL ${ZMQ_OPTIONS[@]} "$@" - build_from_github zeromq czmq master $PARALLEL ${CZMQ_OPTIONS[@]} "$@" build_from_github libbitcoin secp256k1 version4 $PARALLEL ${SECP256K1_OPTIONS[@]} "$@" build_from_github libbitcoin libbitcoin master $PARALLEL ${BITCOIN_OPTIONS[@]} "$@" build_from_travis libbitcoin libbitcoin-protocol master $PARALLEL ${BITCOIN_PROTOCOL_OPTIONS[@]} "$@" diff --git a/libbitcoin-protocol.pc.in b/libbitcoin-protocol.pc.in index f4517d29..85e15dd7 100644 --- a/libbitcoin-protocol.pc.in +++ b/libbitcoin-protocol.pc.in @@ -25,7 +25,7 @@ Version: @PACKAGE_VERSION@ #============================================================================== # Dependencies that publish package configuration. #------------------------------------------------------------------------------ -Requires: libzmq >= 4.2.0 libczmq >= 3.0.0 libbitcoin >= 3.0.0 +Requires: libzmq >= 4.2.0 libbitcoin >= 3.0.0 # Include directory and any other required compiler flags. #------------------------------------------------------------------------------ diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index d03cfadc..2c7780f8 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -19,21 +19,22 @@ */ #include -#include +#include namespace libbitcoin { namespace protocol { namespace zmq { authenticator::authenticator(context& context) - : self_(nullptr /*zauth_new(context.self())*/) + : self_(nullptr) { + ////self_ = zauth_new(context.self()); } authenticator::~authenticator() { BITCOIN_ASSERT(self_); - zauth_destroy(&self_); + ////zauth_destroy(&self_); } authenticator::operator const bool() const @@ -41,31 +42,31 @@ authenticator::operator const bool() const return self_ != nullptr; } -zauth_t* authenticator::self() +void* authenticator::self() { return self_; } void authenticator::allow(const std::string& address) { - zauth_allow(self_, address.c_str()); + ////zauth_allow(self_, address.c_str()); } void authenticator::deny(const std::string& address) { - zauth_deny(self_, address.c_str()); + ////zauth_deny(self_, address.c_str()); } void authenticator::configure_plain(const std::string& domain, const std::string& filename) { - zauth_configure_plain(self_, domain.c_str(), filename.c_str()); + ////zauth_configure_plain(self_, domain.c_str(), filename.c_str()); } void authenticator::configure_curve(const std::string& domain, const std::string& location) { - zauth_configure_curve(self_, domain.c_str(), location.c_str()); + ////zauth_configure_curve(self_, domain.c_str(), location.c_str()); } void authenticator::set_verbose(bool verbose) @@ -75,7 +76,7 @@ void authenticator::set_verbose(bool verbose) // This will prevent authentication feedback, but also prevent crashes. // It is necessary to prevent stdio when using our utf8-everywhere pattern. // TODO: drop czmq and use latest zmq to avoid hadcoded stdio logging. - zauth_set_verbose(self_, verbose); + ////zauth_set_verbose(self_, verbose); #endif } diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index 522ac223..ce144448 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include namespace libbitcoin { @@ -28,12 +28,9 @@ namespace protocol { namespace zmq { certificate::certificate() - : self_(zcert_new()) -{ -} -certificate::certificate(zcert_t* self) - : self_(self) + : self_(nullptr) { + ////self_ = zcert_new(); } certificate::certificate(certificate&& other) @@ -43,9 +40,11 @@ certificate::certificate(certificate&& other) } certificate::certificate(const std::string& filename) - : self_(zcert_load(filename.c_str())) + : self_(nullptr) { + ////self_ = zcert_load(filename.c_str()); } + certificate::~certificate() { reset(nullptr); @@ -56,55 +55,47 @@ certificate::operator const bool() const return self_ != nullptr; } -void certificate::reset(zcert_t* self) -{ - if (self_ != nullptr) - zcert_destroy(&self_); - - self_ = self; -} - void certificate::reset(const std::string& filename) { - if (self_ != nullptr) - zcert_destroy(&self_); + ////if (self_ != nullptr) + //// zcert_destroy(&self_); - self_ = zcert_load(filename.c_str()); + ////self_ = zcert_load(filename.c_str()); } -zcert_t* certificate::self() +void* certificate::self() { return self_; } void certificate::set_meta(const std::string& name, const std::string& value) { - zcert_set_meta(self_, name.c_str(), value.c_str()); + ////zcert_set_meta(self_, name.c_str(), value.c_str()); } -int certificate::save(const std::string& filename) +bool certificate::save(const std::string& filename) { - return zcert_save(self_, filename.c_str()); + return false;////zcert_save(self_, filename.c_str()); } -int certificate::save_public(const std::string& filename) +bool certificate::save_public(const std::string& filename) { - return zcert_save_public(self_, filename.c_str()); + return false;//// zcert_save_public(self_, filename.c_str()); } -int certificate::save_secret(const std::string& filename) +bool certificate::save_secret(const std::string& filename) { - return zcert_save_secret(self_, filename.c_str()); + return false;//// zcert_save_secret(self_, filename.c_str()); } std::string certificate::public_text() const { - return std::string(zcert_public_txt(self_)); + return "";////std::string(zcert_public_txt(self_)); } void certificate::apply(socket& sock) { - zcert_apply(self_, sock.self()); + ////zcert_apply(self_, sock.self()); } } // namespace zmq diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index 3aa2f771..0369b1a8 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -32,9 +32,11 @@ void client_task(const std::string& server_public_text) zmq::certificate client_cert("client_cert.txt"); assert(client_cert); - // Create a pull socket. + // Create a context. zmq::context context; assert(context); + + // Create a pull socket. zmq::socket client(context, zmq::socket::role::puller); assert(client); @@ -47,10 +49,10 @@ void client_task(const std::string& server_public_text) assert(result); // Wait for our message, which signals the test was successful. - zmq::message msg; - msg.receive(client); - assert(msg.parts().size() == 1); - assert((msg.parts()[0] == data_chunk{ { 0xde, 0xad, 0xbe, 0xef } })); + zmq::message message; + message.receive(client); + assert(message.parts().size() == 1); + assert((message.parts()[0] == data_chunk{ { 0xde, 0xad, 0xbe, 0xef } })); puts("Ironhouse test OK"); } @@ -76,29 +78,29 @@ void server_task(zmq::certificate& server_cert) assert(result); // Send our test message, just once - zmq::message msg; - msg.append({ { 0xde, 0xad, 0xbe, 0xef } }); - msg.send(server); + zmq::message message; + message.append({ { 0xde, 0xad, 0xbe, 0xef } }); + message.send(server); - zclock_sleep(200); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); } int main() { // Create the certificate store directory. auto result = boost::filesystem::create_directory(".curve"); - assert(result == 0); + assert(result); // Create the client certificate. zmq::certificate client_cert; // Save the client certificate. - auto rc = client_cert.save("client_cert.txt"); - assert(rc == 0); + result = client_cert.save("client_cert.txt"); + assert(result); // Save the client public certificate. - rc = client_cert.save_public(".curve/test_cert.pub"); - assert(rc == 0); + result = client_cert.save_public(".curve/test_cert.pub"); + assert(result); // Create the server certificate. zmq::certificate server_cert; From cbc7f5c307adad1d9ad13a5c6d8af5d29d755705 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 17 May 2016 18:10:41 -0700 Subject: [PATCH 07/30] Complete zeromq interface declaration, auth and cert not implemented. --- include/bitcoin/protocol/define.hpp | 7 ++ .../bitcoin/protocol/zmq/authenticator.hpp | 23 ++++-- include/bitcoin/protocol/zmq/certificate.hpp | 50 ++++++++----- include/bitcoin/protocol/zmq/frame.hpp | 11 +-- include/bitcoin/protocol/zmq/message.hpp | 3 + include/bitcoin/protocol/zmq/poller.hpp | 16 +++- include/bitcoin/protocol/zmq/socket.hpp | 24 +++--- src/zmq/authenticator.cpp | 54 +++++-------- src/zmq/certificate.cpp | 75 +++++++++---------- src/zmq/frame.cpp | 24 +++--- src/zmq/message.cpp | 9 +++ src/zmq/poller.cpp | 7 +- src/zmq/socket.cpp | 70 ++++++++++------- test/zmq/ironhouse2.cpp | 36 +++++---- test/zmq/poller.cpp | 2 +- 15 files changed, 232 insertions(+), 179 deletions(-) diff --git a/include/bitcoin/protocol/define.hpp b/include/bitcoin/protocol/define.hpp index 8454984b..f05e8eb5 100644 --- a/include/bitcoin/protocol/define.hpp +++ b/include/bitcoin/protocol/define.hpp @@ -38,5 +38,12 @@ #define BCP_INTERNAL BC_HELPER_DLL_LOCAL #endif +#if defined _WIN32 + #include + typedef SOCKET file_descriptor; +#else + typedef int file_descriptor; +#endif + #endif diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index c05edacb..b75a2b5c 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -21,7 +21,8 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP #include -#include +#include +#include #include #include @@ -32,25 +33,31 @@ namespace zmq { class BCP_API authenticator { public: + /// Construct an instance. authenticator(context& context); + + /// Free authenticator resources. ~authenticator(); /// This class is not copyable. authenticator(const authenticator&) = delete; void operator=(const authenticator&) = delete; + /// True if the construction succeeded. operator const bool() const; - void* self(); + /// Allow clients with the following ip addresses (white list). + void allow(const config::authority& address); + + /// Allow clients with the following ip addresses (black list). + void deny(const config::authority& address); - void allow(const std::string& address); - void deny(const std::string& address); - void configure_plain(const std::string& domain, const std::string& filename); - void configure_curve(const std::string& domain, const std::string& location); - void set_verbose(bool verbose); + /// Allow clients with certificates in the following path (white list). + /// An empty path will disable (or not enable) client cert requirement. + bool certificates(const boost::filesystem::path& path); private: - void* self_; + void* authenticator_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index c82c9b86..877b860d 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -20,10 +20,11 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP +#include #include -#include +#include +#include #include -#include namespace libbitcoin { namespace protocol { @@ -32,29 +33,44 @@ namespace zmq { class BCP_API certificate { public: + typedef std::pair metadata; + + /// Contruct a new certificate (can we inject randomness). certificate(); - certificate(certificate&& other); - certificate(const std::string& filename); - ~certificate(); - /// This class is not copyable. - certificate(const certificate&) = delete; - void operator=(const certificate&) = delete; + /// Contruct a certificate from the specified path. + certificate(const boost::filesystem::path& path); + /// True if the certificate is valid. operator const bool() const; - void* self(); + /// The public key base85 text. + const std::string& public_key() const; + + /// The secret key base85 text. + const std::string& secret_key() const; + + /// Add medata to the certificate. + void add_metadata(const metadata& metadata); + + /// Add medata to the certificate. + void add_metadata(const std::string& name, const std::string& value); - void reset(const std::string& filename); - void set_meta(const std::string& name, const std::string& value); - bool save(const std::string& filename); - bool save_public(const std::string& filename); - bool save_secret(const std::string& filename); - std::string public_text() const; - void apply(socket& sock); + /// Export the public key to a certificate file. + bool export_public(const boost::filesystem::path& path); + + /// Export the secret key to a certificate file. + bool export_secret(const boost::filesystem::path& path); + + /// Load a certificate from the specified path (always replaces existing). + bool load(const boost::filesystem::path& path); private: - void* self_; + typedef std::map metadata_map; + + std::string public_; + std::string secret_; + metadata_map metadata_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index 788162bf..7d25f92c 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -20,7 +20,6 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_FRAME_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_FRAME_HPP -#include #include #include #include @@ -61,15 +60,17 @@ class BCP_API frame bool send(socket& socket, bool more); private: - static bool initialize(zmq_msg_t& message, const data_chunk& data); + // zmq_msg_t alias, keeps zmq.h out of our headers. + typedef union zmq_msg{ unsigned char alignment[64]; void* pointer; }; + + static bool initialize(zmq_msg& message, const data_chunk& data); + bool set_more(socket& socket); bool destroy(); bool more_; const bool valid_; - - // TODO: define this locally to avoid zmq.h in our includes. - zmq_msg_t message_; + zmq_msg message_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 14fcbb6d..0478b27f 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -39,6 +39,9 @@ class BCP_API message // Obtain the parts of the created or read message. const data_stack& parts() const; + // Obtain the first part as a text string, or empty if no parts. + std::string text() const; + /// Clear the stack of message parts. void clear(); diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 3ce9f83d..8ccc2d28 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -52,11 +51,20 @@ class BCP_API poller /// Add a socket to be polled (not thread safe). void add(socket& sock); - /// Wait specified microsoconds for any socket to receive, -1 is forever. - socket::identifier wait(int32_t timeout_microsoconds); + /// Wait specified microseconds for any socket to receive, -1 is forever. + socket::identifier wait(int32_t timeout_microseconds); private: - typedef std::vector pollers; + // zmq_pollitem_t alias, keeps zmq.h out of our headers. + typedef struct zmq_pollitem + { + void* socket; + file_descriptor fd; + short events; + short revents; + }; + + typedef std::vector pollers; bool expired_; bool terminated_; diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index 73aad8e1..eea6facf 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -21,9 +21,9 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_SOCKET_HPP #include -#include #include #include +#include #include namespace libbitcoin { @@ -86,24 +86,30 @@ class BCP_API socket /// Connect the socket to the specified remote address. bool connect(const std::string& address); - /// Configure the socket as a curve server. + /// Sets the domain for ZAP (ZMQ RFC 27) authentication. + bool set_authentication_domain(const std::string& domain); + + /// Configure the socket as a curve server (also set the secrety key). bool set_curve_server(); - /// Configure the socket as client to a curve server. - bool set_curve_serverkey(const std::string& key); + /// Configure the socket as client to the curve server. + bool set_curve_client(const std::string& server_public_key); - /// Sets the domain for ZAP (ZMQ RFC 27) authentication. - bool set_zap_domain(const std::string& domain); + /// Apply the specified public key to the socket. + bool set_public_key(const std::string& key); + + /// Apply the specified secret key to the socket. + bool set_secret_key(const std::string& key); + + /// Apply the keys of the specified certificate to the socket. + bool set_certificate(const certificate& certificate); private: static int to_socket_type(role socket_role); - bool set(int32_t option, int32_t value); bool set(int32_t option, const std::string& value); - void initialize(context& context, role socket_role); bool destroy(); - // The encapsulated zeromq socket. void* socket_; uint16_t port_; const int32_t send_buffer_; diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index 2c7780f8..9eac2096 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -19,65 +19,49 @@ */ #include -#include +#include +////#include +#include namespace libbitcoin { namespace protocol { namespace zmq { +using path = boost::filesystem::path; + authenticator::authenticator(context& context) - : self_(nullptr) + : authenticator_(nullptr) { - ////self_ = zauth_new(context.self()); + ////authenticator_ = zauth_new(context.self()); } authenticator::~authenticator() { - BITCOIN_ASSERT(self_); - ////zauth_destroy(&self_); + ////zauth_destroy(&authenticator_); } authenticator::operator const bool() const { - return self_ != nullptr; -} - -void* authenticator::self() -{ - return self_; -} - -void authenticator::allow(const std::string& address) -{ - ////zauth_allow(self_, address.c_str()); -} - -void authenticator::deny(const std::string& address) -{ - ////zauth_deny(self_, address.c_str()); + return authenticator_ != nullptr; } -void authenticator::configure_plain(const std::string& domain, - const std::string& filename) +void authenticator::allow(const config::authority& address) { - ////zauth_configure_plain(self_, domain.c_str(), filename.c_str()); + ////zauth_allow(authenticator_, address.c_str()); } -void authenticator::configure_curve(const std::string& domain, - const std::string& location) +void authenticator::deny(const config::authority& address) { - ////zauth_configure_curve(self_, domain.c_str(), location.c_str()); + ////zauth_deny(authenticator_, address.c_str()); } -void authenticator::set_verbose(bool verbose) +bool authenticator::certificates(const path& path) { -#ifndef _MSC_VER - // Hack to prevent czmq from writing to stdout/stderr on Windows. - // This will prevent authentication feedback, but also prevent crashes. - // It is necessary to prevent stdio when using our utf8-everywhere pattern. - // TODO: drop czmq and use latest zmq to avoid hadcoded stdio logging. - ////zauth_set_verbose(self_, verbose); -#endif + return false; + // Clear requriement if empty. + // Return false if the path cannot be created. + // Declares that a client cert must be matched (unless empty). + ////zauth_configure_curve(authenticator_, "*", path.c_str()); } } // namespace zmq diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index ce144448..68bc48af 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -20,82 +20,77 @@ #include #include -#include +#include +////#include #include namespace libbitcoin { namespace protocol { namespace zmq { -certificate::certificate() - : self_(nullptr) -{ - ////self_ = zcert_new(); -} - -certificate::certificate(certificate&& other) - : self_(other.self_) -{ - other.self_ = nullptr; -} +using path = boost::filesystem::path; -certificate::certificate(const std::string& filename) - : self_(nullptr) +// Always generates both keys. +// Loop until neither key's base85 encoding includes the # character. +certificate::certificate() { - ////self_ = zcert_load(filename.c_str()); + ////certificate_ = zcert_new(); } -certificate::~certificate() +// If the certificate is secret, generates the public key. +// If the certificate is public, does not set a secret key. +// If the file fails to parse then neither key is set (invalid). +certificate::certificate(const path& path) { - reset(nullptr); + /* bool */ load(path); } certificate::operator const bool() const { - return self_ != nullptr; -} - -void certificate::reset(const std::string& filename) -{ - ////if (self_ != nullptr) - //// zcert_destroy(&self_); - - ////self_ = zcert_load(filename.c_str()); + return !public_.empty(); } -void* certificate::self() +const std::string& certificate::public_key() const { - return self_; + return public_; } -void certificate::set_meta(const std::string& name, const std::string& value) +const std::string& certificate::secret_key() const { - ////zcert_set_meta(self_, name.c_str(), value.c_str()); + return secret_; } -bool certificate::save(const std::string& filename) +void certificate::add_metadata(const metadata& metadata) { - return false;////zcert_save(self_, filename.c_str()); + metadata_.emplace(metadata); + ////zcert_set_meta(certificate_, name.c_str(), value.c_str()); } -bool certificate::save_public(const std::string& filename) +void certificate::add_metadata(const std::string& name, + const std::string& value) { - return false;//// zcert_save_public(self_, filename.c_str()); + add_metadata({ name, value }); + ////zcert_set_meta(certificate_, name.c_str(), value.c_str()); } -bool certificate::save_secret(const std::string& filename) +// The public certificate always excludes an existing secret key. +bool certificate::export_public(const path& path) { - return false;//// zcert_save_secret(self_, filename.c_str()); + return false; + //// zcert_save_public(certificate_, filename.c_str()); } -std::string certificate::public_text() const +// The secret certificate always contains a public key as well. +bool certificate::export_secret(const path& path) { - return "";////std::string(zcert_public_txt(self_)); + return false; + //// zcert_save_secret(certificate_, filename.c_str()); } -void certificate::apply(socket& sock) +bool certificate::load(const path& path) { - ////zcert_apply(self_, sock.self()); + return false; + ////certificate_ = zcert_load(filename.c_str()); } } // namespace zmq diff --git a/src/zmq/frame.cpp b/src/zmq/frame.cpp index fb4583bd..18be671a 100644 --- a/src/zmq/frame.cpp +++ b/src/zmq/frame.cpp @@ -51,15 +51,17 @@ frame::~frame() } // static -bool frame::initialize(zmq_msg_t& message, const data_chunk& data) +bool frame::initialize(zmq_msg& message, const data_chunk& data) { + const auto buffer = reinterpret_cast(&message); + if (data.empty()) - return (zmq_msg_init(&message) != zmq_fail); + return (zmq_msg_init(buffer) != zmq_fail); - if (zmq_msg_init_size(&message, data.size()) == zmq_fail) + if (zmq_msg_init_size(buffer, data.size()) == zmq_fail) return false; - std::memcpy(zmq_msg_data(&message), data.data(), data.size()); + std::memcpy(zmq_msg_data(buffer), data.data(), data.size()); return true; } @@ -88,8 +90,9 @@ bool frame::set_more(socket& socket) data_chunk frame::payload() { - const auto size = zmq_msg_size(&message_); - const auto data = zmq_msg_data(&message_); + const auto buffer = reinterpret_cast(&message_); + const auto size = zmq_msg_size(buffer); + const auto data = zmq_msg_data(buffer); const auto begin = static_cast(data); return{ begin, begin + size }; } @@ -99,7 +102,8 @@ bool frame::receive(socket& socket) if (!valid_) return false; - return zmq_recvmsg(socket.self(), &message_, 0) != zmq_fail && + const auto buffer = reinterpret_cast(&message_); + return zmq_recvmsg(socket.self(), buffer, 0) != zmq_fail && set_more(socket); } @@ -109,13 +113,15 @@ bool frame::send(socket& socket, bool last) return false; const int flags = last ? 0 : ZMQ_SNDMORE; - return zmq_sendmsg(socket.self(), &message_, flags) != zmq_fail; + const auto buffer = reinterpret_cast(&message_); + return zmq_sendmsg(socket.self(), buffer, flags) != zmq_fail; } // private bool frame::destroy() { - return valid_ && (zmq_msg_close(&message_) != zmq_fail); + const auto buffer = reinterpret_cast(&message_); + return valid_ && (zmq_msg_close(buffer) != zmq_fail); } } // namespace zmq diff --git a/src/zmq/message.cpp b/src/zmq/message.cpp index 2a53c56f..7e20aea4 100644 --- a/src/zmq/message.cpp +++ b/src/zmq/message.cpp @@ -47,6 +47,15 @@ const data_stack& message::parts() const return parts_; } +std::string message::text() const +{ + if (parts_.empty()) + return{}; + + const auto& first = parts_.front(); + return std::string(first.begin(), first.end()); +} + void message::clear() { parts_.clear(); diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index 3806105f..47476795 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -40,7 +40,7 @@ poller::~poller() void poller::add(socket& socket) { - zmq_pollitem_t item; + zmq_pollitem item; // zmq socket. item.socket = socket.self(); @@ -60,13 +60,14 @@ void poller::add(socket& socket) // The timeout is typed as 'long' by zermq. This is 32 bit on windows and // typically 64 bit on other platforms. So for consistency of config we // limit the domain to 32 bit using int32_t. -1 signals infinite wait. -socket::identifier poller::wait(int32_t timeout_microsoconds) +socket::identifier poller::wait(int32_t timeout_microseconds) { const auto size = pollers_.size(); BITCOIN_ASSERT(size <= max_int32); const auto size32 = static_cast(size); - auto signaled = zmq_poll(pollers_.data(), size32, timeout_microsoconds); + const auto data = reinterpret_cast(pollers_.data()); + auto signaled = zmq_poll(data, size32, timeout_microseconds); if (signaled < 0) { diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 6e40a402..54934af4 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace libbitcoin { namespace protocol { @@ -50,9 +51,13 @@ socket::socket(void* zmq_socket) } socket::socket(context& context, role socket_role) - : socket() + : socket(zmq_socket(context.self(), to_socket_type(socket_role))) { - initialize(context, socket_role); + if (socket_ == nullptr) + return; + + if (!set(ZMQ_SNDHWM, send_buffer_) || !set(ZMQ_RCVHWM, receive_buffer_)) + destroy(); } socket::~socket() @@ -81,28 +86,6 @@ int32_t socket::to_socket_type(role socket_role) } } -bool socket::set(int32_t option, int value) -{ - return zmq_setsockopt(socket_, option, &value, sizeof(value)) != zmq_fail; -} - -bool socket::set(int32_t option, const std::string& value) -{ - const auto buffer = value.c_str(); - return zmq_setsockopt(socket_, option, buffer, value.size()) != zmq_fail; -} - -void socket::initialize(context& context, role socket_role) -{ - socket_ = zmq_socket(context.self(), to_socket_type(socket_role)); - - if (socket_ == nullptr) - return; - - if (!set(ZMQ_SNDHWM, send_buffer_) || !set(ZMQ_RCVHWM, receive_buffer_)) - destroy(); -} - bool socket::destroy() { if (socket_ == nullptr) @@ -142,23 +125,52 @@ bool socket::bind(const std::string& address) bool socket::connect(const std::string& address) { - // String safety issue, API requires null termination. return zmq_connect(socket_, address.c_str()) != zmq_fail; } +bool socket::set(int32_t option, int32_t value) +{ + return zmq_setsockopt(socket_, option, &value, sizeof(value)) != zmq_fail; +} + +bool socket::set(int32_t option, const std::string& value) +{ + if (value.empty()) + return true; + + const auto buffer = value.c_str(); + return zmq_setsockopt(socket_, option, buffer, value.size()) != zmq_fail; +} + +bool socket::set_authentication_domain(const std::string& domain) +{ + return set(ZMQ_ZAP_DOMAIN, domain); +} + bool socket::set_curve_server() { return set(ZMQ_CURVE_SERVER, zmq_true); } -bool socket::set_curve_serverkey(const std::string& key) +bool socket::set_curve_client(const std::string& server_public_key) { - return set(ZMQ_CURVE_SERVERKEY, key); + return set(ZMQ_CURVE_SERVERKEY, server_public_key); } -bool socket::set_zap_domain(const std::string& domain) +bool socket::set_public_key(const std::string& key) { - return set(ZMQ_ZAP_DOMAIN, domain); + return set(ZMQ_CURVE_PUBLICKEY, key); +} + +bool socket::set_secret_key(const std::string& key) +{ + return set(ZMQ_CURVE_SECRETKEY, key); +} + +bool socket::set_certificate(const certificate& certificate) +{ + return set_public_key(certificate.public_key()) && + set_secret_key(certificate.secret_key()); } void* socket::self() diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index 0369b1a8..bdf23f4e 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -19,19 +19,14 @@ */ #include #include -#include #include #include using namespace bc; using namespace bc::protocol; -void client_task(const std::string& server_public_text) +void client_task(const std::string& server_public_key) { - // Load our persistent certificate from disk - zmq::certificate client_cert("client_cert.txt"); - assert(client_cert); - // Create a context. zmq::context context; assert(context); @@ -40,9 +35,13 @@ void client_task(const std::string& server_public_text) zmq::socket client(context, zmq::socket::role::puller); assert(client); + // Load our persistent certificate from disk + zmq::certificate client_cert("client.sec"); + assert(client_cert); + // Configure the client to provide a certificate and use the server key. - client_cert.apply(client); - client.set_curve_serverkey(server_public_text); + client.set_certificate(client_cert); + client.set_curve_client(server_public_key); // Connect to the server. const auto result = client.connect("tcp://127.0.0.1:9000"); @@ -65,14 +64,13 @@ void server_task(zmq::certificate& server_cert) // Start the authenticator on the context and tell it authenticate clients // via the certificates stored in the .curve directory. zmq::authenticator authenticator(context); - authenticator.set_verbose(true); - authenticator.allow("127.0.0.1"); - authenticator.configure_curve("*", ".curve"); + authenticator.allow({ "127.0.0.1" }); + authenticator.certificates("certificates"); // Bind a push socket to the authenticated context. zmq::socket server(context, zmq::socket::role::pusher); assert(server); - server_cert.apply(server); + server.set_certificate(server_cert); server.set_curve_server(); const auto result = server.bind("tcp://*:9000"); assert(result); @@ -85,29 +83,29 @@ void server_task(zmq::certificate& server_cert) std::this_thread::sleep_for(std::chrono::milliseconds(200)); } -int main() +int ironhouse2_example() { // Create the certificate store directory. - auto result = boost::filesystem::create_directory(".curve"); + auto result = boost::filesystem::create_directory("certificates"); assert(result); // Create the client certificate. zmq::certificate client_cert; // Save the client certificate. - result = client_cert.save("client_cert.txt"); + result = client_cert.export_secret("client.sec"); assert(result); - // Save the client public certificate. - result = client_cert.save_public(".curve/test_cert.pub"); + // Save the client public certificate, for use by the server. + result = client_cert.export_public("certificates/client.pub"); assert(result); - // Create the server certificate. + // Create the server certificate (generated values, in memory only). zmq::certificate server_cert; // Start the two detached threads, each with own ZeroMQ context. std::thread server_thread(server_task, std::ref(server_cert)); - std::thread client_thread(client_task, server_cert.public_text()); + std::thread client_thread(client_task, server_cert.public_key()); client_thread.join(); server_thread.join(); diff --git a/test/zmq/poller.cpp b/test/zmq/poller.cpp index 1bc67487..a81da2e9 100644 --- a/test/zmq/poller.cpp +++ b/test/zmq/poller.cpp @@ -23,7 +23,7 @@ using namespace bc; using namespace bc::protocol; -int main_disabled() +int poller_example() { zmq::context context; assert(context); From 3f8333764d60c7c97b9901da9170aadba07042ef Mon Sep 17 00:00:00 2001 From: Eric Voskuil Date: Tue, 17 May 2016 18:26:27 -0700 Subject: [PATCH 08/30] Revise for version3 changes. --- README.md | 303 ++---------------------------------------------------- 1 file changed, 10 insertions(+), 293 deletions(-) diff --git a/README.md b/README.md index 7c7468be..cfacf379 100644 --- a/README.md +++ b/README.md @@ -2,301 +2,18 @@ [![Coverage Status](https://coveralls.io/repos/libbitcoin/libbitcoin-protocol/badge.svg)](https://coveralls.io/r/libbitcoin/libbitcoin-protocol) -# Bitcoin Server Protocol +# Libbitcoin Protocol -[swansontec](https://github.com/swansontec), [pmienk](https://github.com/pmienk), [evoskuil](https://github.com/evoskuil) +*Bitcoin Blockchain Query Protocol Library* -8/22/2014 +Note that you need g++ 4.8 or higher. For this reason Ubuntu 12.04 and older are not supported. Make sure you have installed [libbitcoin](https://github.com/libbitcoin/libbitcoin) beforehand according to its build instructions, as well as libzmq. -## Experimental - -This library is experimental and is no libbitcoin library currently depends on it. - -## Concepts - -A client's two major areas of interest are transaction discovery and transaction maintenance. Transaction discovery involves searching the blockchain for transactions that are of interest to the wallet. Once the client identifies the transactions it cares about, it needs to track their state as they become broadcast, confirmed, and so forth. Doing this generally involves tracking the state of the blockchain itself. - -## Goals - -- Provide support for optimal implementation of common bitcoin clients. - - [Full-Chain](https://bitcoin.it/wiki/Thin_Client_Security#Full-Chain_Clients) - - [Header-Only](https://bitcoin.it/wiki/Thin_Client_Security#Header-Only_Clients) (SPV) - - [Server-Trusting](https://bitcoin.it/wiki/Thin_Client_Security#Server-Trusting_Clients) (see below for details) - - Caching - - Stateless -- The server should not be required to maintain any session state. -- The client should not be required to provide any identifying information. -- The protocol should allow client privacy, leaving tradeoffs between privacy and performance to the caller. -- The protocol should be extensible while allowing backward and forward compatibility without version negotiation. -- The protocol should be defined in a formal [interface definition language](http://wikipedia.org/wiki/Interface_description_language) (IDL). -- The IDL should provide tooling for generation of client-server stubs in C/C++. -- The IDL tooling should implement marshalling in C/C++. - -## Wire Encoding - -The focus of this document is not the wire encoding, but the messaging semantics. The protocol may be encoded via any means. For example, it is possible to encode in [JSON](http://wikipedia.org/wiki/JSON) and serve up over [WebSockets](http://wikipedia.org/wiki/WebSocket). The initial libbitcoin implementation will likely encode using [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/proto) and a [ZeroMQ](http://zeromq.org) transport with [privacy](http://curvezmq.org/) and support for [onion routing](https://www.torproject.org). Additional protocols may be efficiently layered over ZMQ (e.g. using in-process communication). - -## Principles -### Privacy -Transaction queries, all of which would otherwise carry [taintable](https://bitcointalk.org/index.php?topic=92416.0) information, use `prefix` filters. This allows a caller to select its desired level of privacy to prevent correlation of transactions with the caller's IP address, or with each other. Fewer bits give more privacy at the expense of efficiency. Prefixes are defined for a bitcoin `address`, `stealth` addresses and `transaction` hashes. - -Note that without prefix filters transaction query taint cannot be avoided using onion routing alone unless each individual request is made on a distinct channel, as the requests can still be correlated to each other. - -Send/Verify calls are inherently compromising as they allow correlation of the caller's IP address with the transaction. This can only be avoided using an onion router, such as [Tor](https://www.torproject.org) or [I2P](https://geti2p.net/en), to proxy the communication to the server. - -Block header queries are not considered privacy-compromising with the exception that, without using onion routing, the caller exposes the calling IP address as hosting a bitcoin client. - -### Pagination -All queries use a pagination scheme. The caller specifies an optional starting point and an optional target for the desired number of results per page. The server returns results in whole-block increments of increasing block height. The server has the option to return a smaller result set than specified but always returns at least one block's worth of data (which may be an empty list if there is none to return) unless zero results per page is specified (in which case an empty list is returned). - - -### Block Correlation -The server always returns block height and hash as a `block_id` tuple to uniquely identify blocks. A caller may specify one, the other, or both of `block_id.hash` and `block_id.height`. The server validates whatever parts of `block_id` are specified, against each other and its current chain, returning an error if the request is off-chain. - -Each paginated result contains the height and hash of the **next** block not included in the result set, which a client can use for chaining page requests. The last page always includes any matching transactions from the transaction memory pool and includes the **top** `block_id` instead of the **next** `block_id`. - -If the **start** `block_id` is not specified the server returns results only from the memory pool. An unspecified page size allows the server to determine the page size, generally as large as practical. In the case of multiple query filters the server returns the union of results. An empty query returns all transactions. - -The presence of the **next** `block_id.hash` allows the server to detect the presence of an apparent block fork between two page requests. The server will return an error, and the client can restart its queries from an earlier point. The server will always return results that are consistent with respect to the ending block hash. - -The server signals the caller of a fork (or bad caller input input) by validating **start** `block_id` against the current chain, returning an error code if the specified `block_id` is not on the chain. There is no other possibility of pareseable input causing an error result (although server failures can produce errors). If a `tx_filter.bits` value that exceeds the length in bits of the corresponding `tx_filter.prefix` it is treated as a valid sentinel for "all bits" of the prefix. - -## Complex Types - -- libbitcoin::data_chunk bytes -- libbitcoin::hash_digest digest -- libbitcoin::block_header_type header -- libbitcoin::transaction_type tx -- enum {none | block | merkle} locations -- enum {hash | utxo | transaction} results -- enum {address | stealth | transaction} filters -- block - - header **header** - - list of branch **tree** (ordered) - - list of tx **transactions** (ordered) -- block_id - - uint32_t? **height** (default = unverified, use hash) - - digest? **hash** (default = unverified, use height) -- block_location - - block_id? **identity** (missing unless requested) - - list of digest **branch** (empty unless requested) -- filter - - filters **filter_type** (default = transaction) - - uint32_t? **bits** (default = all) - - bytes **prefix** -- output - - uint32_t **index** - - uint64_t **satoshis** - - bytes **script** -- tx_hash_result - - digest **hash** - - block_location **location** -- tx_result - - tx **transaction** - - block_location **location** -- utxo_result - - digest **hash** - - block_location **location** - - list of output **outputs** - -### Merkle Branch Encoding - -The transaction's [Merkle branch](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki#Partial_Merkle_branch_format) is encoded in `block_location.branch` as a hash list in depth-first order such that a properly-ordered combination with the hash of the corresponding transaction produces the [Merkle root](https://bitcoin.it/wiki/Protocol_specification#Merkle_Trees) of the corresponding block. - -### Prefix Filters - -In the case of filters the caller provides as prefix only the full or partial hash (byte-aligned). In other words, for addresses the prefix is against the RIPEMD160 hash payload, not the Base58Check encoding wrapper. Prefix comparisons for stealth addresses are independently documented. Address and transaction prefix comparisons are performed in a similar manner (TBD). - -## Messages - -### Blocks - -- Get Block Headers - - in?: block_id **start** (default = get block height) - - in?: uint32_t **results_per_page** (default = all, 0 = none) - - out: list of header **headers** (empty = zero pages requested) - - out?: block_id **next** (missing = last page) - - out?: block_id **top** (missing = not last page) -- Post Block - - in: block **block** -- Validate Block - - in: block **block** - -### Transactions - -- Get Transactions - - in?: block_id **start** (default = tx mempool only) - - in?: uint32_t **results_per_page** (default = all, 0 = start block only) - - in?: results **result_type** (default = hash) - - in?: locations **location_type** (default = none) - - in: list of filter **query** (empty = all) - - out: list of {tx_hash_result} **hashes** - - out: list of {utxo_result} **outputs** - - out: list of {tx_result} **transactions** - - out?: block_id **next** (missing = last page) - - out?: block_id **top** (missing = not last page) -- Post Transaction - - in: tx **transaction** -- Validate Transaction - - in: tx **transaction** - -> *Validate* calls are designed for client-side debugging. - -## Usage Examples - -Get the current block height, and nothing more: -```js -get_headers {} -``` -Determine if a particular block is still on the main chain: -```js -get_headers -{ - start = - { - height = 317792, - hash = 0x000000000000000018b01e93c7caaed765b7ff478f2dcc7ae6364bfcf97fe2f8 - }, - results_per_page = 0 -} -``` -Get all block headers, starting from the genesis block: -```js -get_headers -{ - start = { height = 0 } -} -``` -Get all block headers, starting where the previous query left off: -```js -get_headers -{ - start = - { - height = 317792, - hash = 0x000000000000000018b01e93c7caaed765b7ff478f2dcc7ae6364bfcf97fe2f8 - } -} -``` -Get all transaction hashes for a wallet with two addresses, starting at the genesis block, with a target page size of 10 transactions: -```js -get_transactions -{ - start = { height = 0 }, - query = - [ - { prefix = 0x21 }, - { bits = 12, prefix = 0x08b7 } - ], - results_per_page = 10 -} -``` -Get all transaction data for a wallet with two addresses, starting at a particular block: -```js -get_transactions -{ - start = - { - height = 317792, - hash = 0x000000000000000018b01e93c7caaed765b7ff478f2dcc7ae6364bfcf97fe2f8 - }, - query = - [ - { prefix = 0x21 }, - { bits = 12, prefix = 0x08b7 } - ], - result_type = transaction -} +```sh +$ ./autogen.sh +$ ./configure +$ make +$ sudo make install +$ sudo ldconfig ``` -Get all utxo's for a single address: -```js -get_transactions -{ - start = { height = 0 }, - query = [{ bits = 12, prefix = 0x08b7 }], - result_type = utxo -} -``` -Has my transaction been confirmed yet? -```js -get_transactions -{ - start = { height = 0 }, - query = - [ - { - filter_type = transaction, - prefix = 0x94b43df27e205d8a261531fe1fc0c2e5fc226a87e6a9e1c68ab9113eb36cbf4a - } - ], - result_type = hash - location_type = block -} -``` -Get all the transactions in a particular block: -```js -get_transactions -{ - start = - { - height = 317792, - hash = 0x000000000000000018b01e93c7caaed765b7ff478f2dcc7ae6364bfcf97fe2f8 - }, - results_per_page = 0 -} -``` -Get all stealth transactions in the mempool: -```js -get_transactions -{ - query = [{ filter_type = stealth }] -} -``` - -## Client Types - -This protocol supports four common client types: - -- Full-chain caching clients -- SPV caching clients -- Server-trusting caching clients -- Server-trusting stateless clients - -Caching clients store transactions locally, while stateless clients do not (although they might store addresses or other metadata). Aside from reducing latency and enabling verification, maintaining a local transaction database allows maintenance of per-transaction non-blockchain meta-data, such as categories, counterparty names, etc. - -SPV and full-chain clients can verify the contents of their transaction database by checking transaction hashes against block headers. Additionally, full-chain clients can verify that inputs connect to outputs. Server-trusting clients don't maintain the blockchain, so a malicious server can easily convince them that they have received non-existent payments. Correctly-written non-trusting clients are immune to this class of attack. - -Server-trusting caching clients are doubly-sensitive to attack, since they may receive and cache invalid data locally where it would persist until the cache is refreshed or a possibly if a blockchain fork is signalled. Stateless clients are somewhat less sensitive, since they will recover as soon as they connect to an honest server. - -There are realistic scenarios where a trusting client makes sense. For example, an e-commerce platform might run its own internal bitcoin servers. In this case there is no need to verify data coming from the same security zone. - -All caching client types stay up-to-date with the blockchain in a similar way. The client periodically polls for new blocks. Upon discovering a new block the client probes its cache for a chain fork. To do this, the client walks down the chain from its highest block, making server queries with the **start** `block_id` set to blocks along the chain. Each block that produces a failure response is identified as being on a pruned fork. Once the highest non-forked block in the cache is located the client queries the server in order to update the block information for all transactions (or addresses) that are associated with pruned blocks. - -Although SPV clients can verify information the server provides, only a full-chain client can detect missing transactions. Therefore SPV clients may still choose to periodically re-scan their addresses starting from the genesis block. Trusting clients have no reason to assume that new data would be any more reliable than old data, so automated periodic updates may actually be harmful. On the other hand, depending on the trust model, periodic updates can mitigate perpetuating errors, as previously described. - -## Old Obelisk Protocol - -The old obelisk protocol is included for comparison: - -### Fetch - -- blockchain.fetch_history(address, from_height) -- blockchain.fetch_transaction(tx_hash) -- blockchain.fetch_last_height() -- blockchain.fetch_block_header(height) -- blockchain.fetch_block_header(blk_hash) -- blockchain.fetch_transaction_index(tx_hash) -- blockchain.fetch_stealth(prefix, from_height) -- address.fetch_history(address, from_height) -- transaction_pool.fetch_transaction(tx_hash) - -### Send - -- transaction_pool.validate(tx_data) -- protocol.broadcast_transaction(tx_data) - -### Subscribe - -- address.subscribe(prefix) -These features are a subset of new protocol with the exception of address.subscribe, which has been removed. The subscription required per caller session-state to be maintained by the server. The new protocol is stateless and therefore requires callers to poll for the same information. +libbitcoin-protocol is now installed in `/usr/local/`. From 272d0ccb760162a16d432141d2e0051d5d3074ca Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 18 May 2016 01:27:26 -0700 Subject: [PATCH 09/30] Fix initialization ordering bug. --- include/bitcoin/protocol/zmq/context.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index ddd5459e..2e317df1 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -49,8 +49,8 @@ class BCP_API context private: bool destroy(); - void* self_; int32_t threads_; + void* self_; }; } // namespace zmq From 700109d5e612a4ce35032ea7d525f2fcf7bc8ce4 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 18 May 2016 01:27:52 -0700 Subject: [PATCH 10/30] Eliminate incorrect bind-port initialzation. --- include/bitcoin/protocol/zmq/socket.hpp | 4 ---- src/zmq/socket.cpp | 17 +---------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index eea6facf..e9e9b5fe 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -71,9 +71,6 @@ class BCP_API socket void* self(); void* self() const; - /// The port to whicih the socket is bound, or zero. - uint16_t port() const; - /// The socket identifier is an opaue correlation idenfier. identifier id() const; @@ -111,7 +108,6 @@ class BCP_API socket bool destroy(); void* socket_; - uint16_t port_; const int32_t send_buffer_; const int32_t receive_buffer_; const int32_t linger_milliseconds_; diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 54934af4..157e189d 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -29,7 +29,6 @@ namespace libbitcoin { namespace protocol { namespace zmq { -static constexpr uint16_t no_port = 0; static constexpr int32_t zmq_true = 1; static constexpr int32_t zmq_fail = -1; static constexpr int32_t zmq_forever = -1; @@ -43,7 +42,6 @@ socket::socket() socket::socket(void* zmq_socket) : socket_(zmq_socket), - port_(no_port), send_buffer_(zmq_send_buffer), receive_buffer_(zmq_receive_buffer), linger_milliseconds_(zmq_forever) @@ -93,9 +91,6 @@ bool socket::destroy() const auto linger = set(ZMQ_LINGER, linger_milliseconds_); const auto closed = zmq_close(socket_) != zmq_fail; - - // Always clear state even if the socket is leaked. - port_ = no_port; socket_ = nullptr; return linger && closed; @@ -107,20 +102,15 @@ void socket::assign(socket&& other) destroy(); // Assume ownership of the other socket's state. - port_ = other.port_; socket_ = other.socket_; // Don't destroy other socket's resource as it would destroy ours. - other.port_ = no_port; other.socket_ = nullptr; } bool socket::bind(const std::string& address) { - // Returns zero if the bind fails, otherwise the port (ports are 16 bit). - const auto port = zmq_bind(socket_, address.c_str()); - port_ = static_cast(port); - return port_ != no_port; + return zmq_bind(socket_, address.c_str()) != zmq_fail; } bool socket::connect(const std::string& address) @@ -183,11 +173,6 @@ void* socket::self() const return socket_; } -uint16_t socket::port() const -{ - return port_; -} - socket::identifier socket::id() const { return reinterpret_cast(socket_); From a94901cb654be61d8009363afde1a96cc38986e9 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 18 May 2016 14:30:01 -0700 Subject: [PATCH 11/30] Simplify certificate to trivial keypair generator and store. --- .../bitcoin/protocol/zmq/authenticator.hpp | 8 +-- include/bitcoin/protocol/zmq/certificate.hpp | 33 ++--------- include/bitcoin/protocol/zmq/socket.hpp | 4 +- src/zmq/authenticator.cpp | 17 +++--- src/zmq/certificate.cpp | 55 +++---------------- src/zmq/socket.cpp | 6 +- 6 files changed, 29 insertions(+), 94 deletions(-) diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index b75a2b5c..695e44f1 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -21,7 +21,6 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP #include -#include #include #include #include @@ -46,16 +45,15 @@ class BCP_API authenticator /// True if the construction succeeded. operator const bool() const; + /// Allow clients with the following public keys (white list). + void allow(const std::string& public_key); + /// Allow clients with the following ip addresses (white list). void allow(const config::authority& address); /// Allow clients with the following ip addresses (black list). void deny(const config::authority& address); - /// Allow clients with certificates in the following path (white list). - /// An empty path will disable (or not enable) client cert requirement. - bool certificates(const boost::filesystem::path& path); - private: void* authenticator_; }; diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index 877b860d..bd41301e 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -20,26 +20,23 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP -#include #include -#include -#include #include namespace libbitcoin { namespace protocol { namespace zmq { +/// A simplified "certificate" class to manage a curve key pair. +/// If valid the class always retains a consistent key pair. class BCP_API certificate { public: - typedef std::pair metadata; - /// Contruct a new certificate (can we inject randomness). certificate(); - /// Contruct a certificate from the specified path. - certificate(const boost::filesystem::path& path); + /// Contruct a certificate from a private key (generates public key). + certificate(const std::string& private_key); /// True if the certificate is valid. operator const bool() const; @@ -48,29 +45,11 @@ class BCP_API certificate const std::string& public_key() const; /// The secret key base85 text. - const std::string& secret_key() const; - - /// Add medata to the certificate. - void add_metadata(const metadata& metadata); - - /// Add medata to the certificate. - void add_metadata(const std::string& name, const std::string& value); - - /// Export the public key to a certificate file. - bool export_public(const boost::filesystem::path& path); - - /// Export the secret key to a certificate file. - bool export_secret(const boost::filesystem::path& path); - - /// Load a certificate from the specified path (always replaces existing). - bool load(const boost::filesystem::path& path); + const std::string& private_key() const; private: - typedef std::map metadata_map; - std::string public_; - std::string secret_; - metadata_map metadata_; + std::string private_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index e9e9b5fe..5d392bfb 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -95,8 +95,8 @@ class BCP_API socket /// Apply the specified public key to the socket. bool set_public_key(const std::string& key); - /// Apply the specified secret key to the socket. - bool set_secret_key(const std::string& key); + /// Apply the specified private key to the socket. + bool set_private_key(const std::string& key); /// Apply the keys of the specified certificate to the socket. bool set_certificate(const certificate& certificate); diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index 9eac2096..b107c47d 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -19,7 +19,7 @@ */ #include -#include +#include ////#include #include @@ -45,6 +45,12 @@ authenticator::operator const bool() const return authenticator_ != nullptr; } +void authenticator::allow(const std::string& public_key) +{ + // Declares that a client cert must be matched (unless empty). + ////zauth_configure_curve(authenticator_, "*", path.c_str()); +} + void authenticator::allow(const config::authority& address) { ////zauth_allow(authenticator_, address.c_str()); @@ -55,15 +61,6 @@ void authenticator::deny(const config::authority& address) ////zauth_deny(authenticator_, address.c_str()); } -bool authenticator::certificates(const path& path) -{ - return false; - // Clear requriement if empty. - // Return false if the path cannot be created. - // Declares that a client cert must be matched (unless empty). - ////zauth_configure_curve(authenticator_, "*", path.c_str()); -} - } // namespace zmq } // namespace protocol } // namespace libbitcoin diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index 68bc48af..19e709c1 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -20,29 +20,23 @@ #include #include -#include -////#include -#include +#include namespace libbitcoin { namespace protocol { namespace zmq { -using path = boost::filesystem::path; - -// Always generates both keys. +// TODO: // Loop until neither key's base85 encoding includes the # character. certificate::certificate() { - ////certificate_ = zcert_new(); } -// If the certificate is secret, generates the public key. -// If the certificate is public, does not set a secret key. -// If the file fails to parse then neither key is set (invalid). -certificate::certificate(const path& path) +// TODO: +// Validate the private key and create the public key from the private. +certificate::certificate(const std::string& private_key) { - /* bool */ load(path); + private_ = private_key; } certificate::operator const bool() const @@ -55,42 +49,9 @@ const std::string& certificate::public_key() const return public_; } -const std::string& certificate::secret_key() const -{ - return secret_; -} - -void certificate::add_metadata(const metadata& metadata) -{ - metadata_.emplace(metadata); - ////zcert_set_meta(certificate_, name.c_str(), value.c_str()); -} - -void certificate::add_metadata(const std::string& name, - const std::string& value) -{ - add_metadata({ name, value }); - ////zcert_set_meta(certificate_, name.c_str(), value.c_str()); -} - -// The public certificate always excludes an existing secret key. -bool certificate::export_public(const path& path) -{ - return false; - //// zcert_save_public(certificate_, filename.c_str()); -} - -// The secret certificate always contains a public key as well. -bool certificate::export_secret(const path& path) -{ - return false; - //// zcert_save_secret(certificate_, filename.c_str()); -} - -bool certificate::load(const path& path) +const std::string& certificate::private_key() const { - return false; - ////certificate_ = zcert_load(filename.c_str()); + return private_; } } // namespace zmq diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 157e189d..a4af0579 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -152,15 +152,15 @@ bool socket::set_public_key(const std::string& key) return set(ZMQ_CURVE_PUBLICKEY, key); } -bool socket::set_secret_key(const std::string& key) +bool socket::set_private_key(const std::string& key) { return set(ZMQ_CURVE_SECRETKEY, key); } bool socket::set_certificate(const certificate& certificate) { - return set_public_key(certificate.public_key()) && - set_secret_key(certificate.secret_key()); + return certificate && set_public_key(certificate.public_key()) && + set_private_key(certificate.private_key()); } void* socket::self() From 0fba325eebde20e8751832ad0afb31363bf988c1 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 18 May 2016 14:30:24 -0700 Subject: [PATCH 12/30] Update example based on certificate changes. --- test/zmq/ironhouse2.cpp | 107 +++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index bdf23f4e..14107800 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -19,94 +19,97 @@ */ #include #include -#include #include using namespace bc; using namespace bc::protocol; -void client_task(const std::string& server_public_key) +void server_task(const std::string& server_private_key, + const std::string& client_public_key, + const config::authority& client_address) { - // Create a context. + // Create a context for the server. zmq::context context; assert(context); - // Create a pull socket. - zmq::socket client(context, zmq::socket::role::puller); - assert(client); + // Establish the context's authentication whitelist. + zmq::authenticator authenticator(context); + authenticator.allow(client_address); + authenticator.allow(client_public_key); - // Load our persistent certificate from disk - zmq::certificate client_cert("client.sec"); - assert(client_cert); + // Create a push socket using the server's authenticated context. + zmq::socket server(context, zmq::socket::role::pusher); + assert(server); - // Configure the client to provide a certificate and use the server key. - client.set_certificate(client_cert); - client.set_curve_client(server_public_key); + // Configure the server to provide identity and require client identity. + auto result = server.set_private_key(server_private_key); + assert(result); + result = server.set_curve_server(); + assert(result); - // Connect to the server. - const auto result = client.connect("tcp://127.0.0.1:9000"); + // Bind the server to a tcp port on all local addresses. + result = server.bind("tcp://*:9000"); assert(result); - // Wait for our message, which signals the test was successful. + // Send the test message. zmq::message message; - message.receive(client); - assert(message.parts().size() == 1); - assert((message.parts()[0] == data_chunk{ { 0xde, 0xad, 0xbe, 0xef } })); + message.append("helllo world!"); + result = message.send(server); + assert(result); - puts("Ironhouse test OK"); + // Give client time to complete (hack). + std::this_thread::sleep_for(std::chrono::milliseconds(200)); } -void server_task(zmq::certificate& server_cert) +void client_task(const std::string& client_private_key, + const std::string& server_public_key) { + // Create a context for the client. zmq::context context; assert(context); - // Start the authenticator on the context and tell it authenticate clients - // via the certificates stored in the .curve directory. - zmq::authenticator authenticator(context); - authenticator.allow({ "127.0.0.1" }); - authenticator.certificates("certificates"); + // Bind a pull socket to the client context. + zmq::socket client(context, zmq::socket::role::puller); + assert(client); - // Bind a push socket to the authenticated context. - zmq::socket server(context, zmq::socket::role::pusher); - assert(server); - server.set_certificate(server_cert); - server.set_curve_server(); - const auto result = server.bind("tcp://*:9000"); + // Configure the client to provide identity and require server identity. + auto result = client.set_private_key(client_private_key); + assert(result); + result = client.set_curve_client(server_public_key); + assert(result); + + // Connect to the server's tcp port on the local host. + result = client.connect("tcp://127.0.0.1:9000"); assert(result); - // Send our test message, just once + // Wait for the message, which signals the test was successful. zmq::message message; - message.append({ { 0xde, 0xad, 0xbe, 0xef } }); - message.send(server); + result = message.receive(client); + assert(result); + assert(message.text() == "helllo world!"); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + puts("Ironhouse test OK"); } int ironhouse2_example() { - // Create the certificate store directory. - auto result = boost::filesystem::create_directory("certificates"); - assert(result); + static const auto localhost = config::authority("127.0.0.1"); - // Create the client certificate. + // Create client and server certificates (generated secrets). zmq::certificate client_cert; - - // Save the client certificate. - result = client_cert.export_secret("client.sec"); - assert(result); - - // Save the client public certificate, for use by the server. - result = client_cert.export_public("certificates/client.pub"); - assert(result); - - // Create the server certificate (generated values, in memory only). + assert(client_cert); zmq::certificate server_cert; + assert(server_cert); + + // Start a server, require the client cert and localhost address. + std::thread server_thread(server_task, server_cert.private_key(), + client_cert.public_key(), localhost); - // Start the two detached threads, each with own ZeroMQ context. - std::thread server_thread(server_task, std::ref(server_cert)); - std::thread client_thread(client_task, server_cert.public_key()); + // Start a client, allos connections only to server with cert. + std::thread client_thread(client_task, client_cert.private_key(), + server_cert.public_key()); + // Wait for thread completions. client_thread.join(); server_thread.join(); return 0; From 3fc02d4249235eb210a7a8630858e7e36301bd0d Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 19 May 2016 03:28:27 -0700 Subject: [PATCH 13/30] Upgrade to nuget libzmq-4.2.3. --- .../libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj | 4 ++-- builds/msvc/vs2013/libbitcoin-protocol-test/packages.config | 2 +- .../vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj | 4 ++-- builds/msvc/vs2013/libbitcoin-protocol/packages.config | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj b/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj index 0c7f71e1..ace3cb35 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol-test/libbitcoin-protocol-test.vcxproj @@ -88,7 +88,7 @@ - + @@ -105,7 +105,7 @@ - + diff --git a/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config b/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config index 82fedcd2..bc9ddb75 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config +++ b/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config @@ -10,7 +10,7 @@ - + diff --git a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj index 4456eac3..5bae5ebc 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj @@ -106,7 +106,7 @@ - + @@ -115,7 +115,7 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + diff --git a/builds/msvc/vs2013/libbitcoin-protocol/packages.config b/builds/msvc/vs2013/libbitcoin-protocol/packages.config index cc76e323..a7439362 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/packages.config +++ b/builds/msvc/vs2013/libbitcoin-protocol/packages.config @@ -1,7 +1,7 @@  - + From d3364fa42ab81857b773b0d81497c1e11d00500c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 19 May 2016 03:29:54 -0700 Subject: [PATCH 14/30] Integrate certificate generation and derivation. --- include/bitcoin/protocol/zmq/certificate.hpp | 8 ++- src/zmq/certificate.cpp | 52 ++++++++++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index bd41301e..a09739b2 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -32,10 +32,10 @@ namespace zmq { class BCP_API certificate { public: - /// Contruct a new certificate (can we inject randomness). + /// Construct a new certificate (can we inject randomness). certificate(); - /// Contruct a certificate from a private key (generates public key). + /// Construct a certificate from a private key (generates public key). certificate(const std::string& private_key); /// True if the certificate is valid. @@ -47,6 +47,10 @@ class BCP_API certificate /// The secret key base85 text. const std::string& private_key() const; +protected: + void create(std::string& out_public, std::string& out_private); + bool derive(std::string& out_public, const std::string& private_key); + private: std::string public_; std::string private_; diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index 19e709c1..fa730515 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -26,17 +26,59 @@ namespace libbitcoin { namespace protocol { namespace zmq { -// TODO: -// Loop until neither key's base85 encoding includes the # character. +static constexpr int32_t zmq_fail = -1; +static constexpr size_t zmq_encoded_key_size = 40; + certificate::certificate() { + create(public_, private_); +} + +void certificate::create(std::string& out_public, std::string& out_private) +{ + const auto valid_setting = [](const std::string& key) + { + return key.find_first_of('#') == std::string::npos; + }; + + // Loop until neither key's base85 encoding includes the # character. + // This ensures that the value can be used in libbitcoin settings files. + for (uint8_t iteration = 0; iteration <= max_uint8; iteration++) + { + char public_key[zmq_encoded_key_size + 1] = { 0 }; + char private_key[zmq_encoded_key_size + 1] = { 0 }; + + if (zmq_curve_keypair(public_key, private_key) == zmq_fail) + return; + + if (valid_setting(public_key) && !valid_setting(private_key)) + { + out_public = public_key; + out_private = private_key; + return; + } + } +} + +bool certificate::derive(std::string& out_public, + const std::string& private_key) +{ + if (private_key.size() != zmq_encoded_key_size) + return false; + + char public_key[zmq_encoded_key_size + 1] = { 0 }; + + if (zmq_curve_public(public_key, private_key.data()) == zmq_fail) + return false; + + out_public = public_key; + return true; } -// TODO: -// Validate the private key and create the public key from the private. certificate::certificate(const std::string& private_key) { - private_ = private_key; + if (derive(public_, private_key)) + private_ = private_key; } certificate::operator const bool() const From cfa637e2617a3e12835042d4580de2e3c4ceb7c6 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 19 May 2016 15:52:24 -0700 Subject: [PATCH 15/30] Change poll timeout from micro to milli (zmq docs incorrect). --- include/bitcoin/protocol/zmq/poller.hpp | 4 ++-- src/zmq/poller.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 8ccc2d28..253c5307 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -51,8 +51,8 @@ class BCP_API poller /// Add a socket to be polled (not thread safe). void add(socket& sock); - /// Wait specified microseconds for any socket to receive, -1 is forever. - socket::identifier wait(int32_t timeout_microseconds); + /// Wait specified milliseconds for any socket to receive, -1 is forever. + socket::identifier wait(int32_t timeout_milliseconds); private: // zmq_pollitem_t alias, keeps zmq.h out of our headers. diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index 47476795..cb9f8271 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -60,14 +60,14 @@ void poller::add(socket& socket) // The timeout is typed as 'long' by zermq. This is 32 bit on windows and // typically 64 bit on other platforms. So for consistency of config we // limit the domain to 32 bit using int32_t. -1 signals infinite wait. -socket::identifier poller::wait(int32_t timeout_microseconds) +socket::identifier poller::wait(int32_t timeout_milliseconds) { const auto size = pollers_.size(); BITCOIN_ASSERT(size <= max_int32); const auto size32 = static_cast(size); const auto data = reinterpret_cast(pollers_.data()); - auto signaled = zmq_poll(data, size32, timeout_microseconds); + auto signaled = zmq_poll(data, size32, timeout_milliseconds); if (signaled < 0) { From 08cdbb551414884ca51b8892b31951f03c485582 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 19 May 2016 15:54:00 -0700 Subject: [PATCH 16/30] Construct with generated cert if private key empty. --- src/zmq/certificate.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index fa730515..0f8b5ae1 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -34,6 +34,14 @@ certificate::certificate() create(public_, private_); } +certificate::certificate(const std::string& private_key) +{ + if (private_key.empty()) + create(public_, private_); + else if (derive(public_, private_key)) + private_ = private_key; +} + void certificate::create(std::string& out_public, std::string& out_private) { const auto valid_setting = [](const std::string& key) @@ -75,12 +83,6 @@ bool certificate::derive(std::string& out_public, return true; } -certificate::certificate(const std::string& private_key) -{ - if (derive(public_, private_key)) - private_ = private_key; -} - certificate::operator const bool() const { return !public_.empty(); From b6fcaa02442271cf95ac824dd57d5700503e5495 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 19 May 2016 15:54:32 -0700 Subject: [PATCH 17/30] Change linger to zero so that we don't need explicit socket close. --- src/zmq/context.cpp | 4 ++-- src/zmq/socket.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index a8e9f458..d04562f8 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -50,8 +50,8 @@ bool context::destroy() if (self_ == nullptr) return true; - // This will cause all related sockets to close and will block until - // all sockets open within context have been closed with zmq_close(). + // This terminates blocking operations but blocks until either each socket + // in the context is explicitly closed or its linger period is exceeded. return zmq_term(self_) != zmq_fail; } diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index a4af0579..315ac555 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -31,7 +31,6 @@ namespace zmq { static constexpr int32_t zmq_true = 1; static constexpr int32_t zmq_fail = -1; -static constexpr int32_t zmq_forever = -1; static constexpr int32_t zmq_send_buffer = 1000; static constexpr int32_t zmq_receive_buffer = 1000; @@ -40,11 +39,13 @@ socket::socket() { } +// zmq_term terminates blocking operations but blocks until either each socket +// in the context has been explicitly closed or the linger period is exceeded. socket::socket(void* zmq_socket) : socket_(zmq_socket), send_buffer_(zmq_send_buffer), receive_buffer_(zmq_receive_buffer), - linger_milliseconds_(zmq_forever) + linger_milliseconds_(0) { } From dfdf237e30a0d30cf34d39e464f59f9e7a3005d4 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 20 May 2016 03:09:00 -0700 Subject: [PATCH 18/30] Change exclusion symbol to version4. --- include/bitcoin/protocol/converter.hpp | 2 +- include/bitcoin/protocol/interface.pb.h | 2 +- include/bitcoin/protocol/packet.hpp | 4 +- include/bitcoin/protocol/primitives.hpp | 2 +- include/bitcoin/protocol/request_packet.hpp | 2 +- include/bitcoin/protocol/response_packet.hpp | 2 +- src/converter.cpp | 2 +- src/interface.pb.cc | 2 +- src/packet.cpp | 63 +++++++------------- src/request_packet.cpp | 2 +- src/response_packet.cpp | 2 +- test/converter.cpp | 2 +- 12 files changed, 32 insertions(+), 55 deletions(-) diff --git a/include/bitcoin/protocol/converter.hpp b/include/bitcoin/protocol/converter.hpp index 525f272a..38e6af0f 100644 --- a/include/bitcoin/protocol/converter.hpp +++ b/include/bitcoin/protocol/converter.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #ifndef LIBBITCOIN_PROTOCOL_CONVERSION_HPP #define LIBBITCOIN_PROTOCOL_CONVERSION_HPP diff --git a/include/bitcoin/protocol/interface.pb.h b/include/bitcoin/protocol/interface.pb.h index da6877be..5d05af87 100644 --- a/include/bitcoin/protocol/interface.pb.h +++ b/include/bitcoin/protocol/interface.pb.h @@ -1,7 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! // source: bitcoin/protocol/interface.proto -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #ifndef PROTOBUF_bitcoin_2fprotocol_2finterface_2eproto__INCLUDED #define PROTOBUF_bitcoin_2fprotocol_2finterface_2eproto__INCLUDED diff --git a/include/bitcoin/protocol/packet.hpp b/include/bitcoin/protocol/packet.hpp index 53b01d48..efce513c 100644 --- a/include/bitcoin/protocol/packet.hpp +++ b/include/bitcoin/protocol/packet.hpp @@ -39,9 +39,9 @@ class BCP_API packet void set_destination(const data_chunk& destination); bool receive(zmq::socket& socket); - bool receive(const std::shared_ptr& socket); + ////bool receive(const std::shared_ptr& socket); bool send(zmq::socket& socket); - bool send(const std::shared_ptr& socket); + ////bool send(const std::shared_ptr& socket); protected: virtual bool encode_payload(zmq::message& message) const = 0; diff --git a/include/bitcoin/protocol/primitives.hpp b/include/bitcoin/protocol/primitives.hpp index 133381c7..34051e71 100644 --- a/include/bitcoin/protocol/primitives.hpp +++ b/include/bitcoin/protocol/primitives.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #ifndef LIBBITCOIN_PROTOCOL_PRIMITIVES_HPP #define LIBBITCOIN_PROTOCOL_PRIMITIVES_HPP diff --git a/include/bitcoin/protocol/request_packet.hpp b/include/bitcoin/protocol/request_packet.hpp index f76abaca..13da9db2 100644 --- a/include/bitcoin/protocol/request_packet.hpp +++ b/include/bitcoin/protocol/request_packet.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #ifndef LIBBITCOIN_PROTOCOL_REQUEST_PACKET #define LIBBITCOIN_PROTOCOL_REQUEST_PACKET diff --git a/include/bitcoin/protocol/response_packet.hpp b/include/bitcoin/protocol/response_packet.hpp index b1dc6cb9..6ec81b0f 100644 --- a/include/bitcoin/protocol/response_packet.hpp +++ b/include/bitcoin/protocol/response_packet.hpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #ifndef LIBBITCOIN_PROTOCOL_RESPONSE_PACKET #define LIBBITCOIN_PROTOCOL_RESPONSE_PACKET diff --git a/src/converter.cpp b/src/converter.cpp index 2fef0c9e..850b6d7f 100644 --- a/src/converter.cpp +++ b/src/converter.cpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #include diff --git a/src/interface.pb.cc b/src/interface.pb.cc index f92464d2..3d80da19 100644 --- a/src/interface.pb.cc +++ b/src/interface.pb.cc @@ -1,7 +1,7 @@ // Generated by the protocol buffer compiler. DO NOT EDIT! // source: bitcoin/protocol/interface.proto -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION #include "bitcoin/protocol/interface.pb.h" diff --git a/src/packet.cpp b/src/packet.cpp index c7f37877..8b9591bf 100644 --- a/src/packet.cpp +++ b/src/packet.cpp @@ -49,65 +49,42 @@ bool packet::receive(zmq::socket& socket) { zmq::message message; - if (!message.receive(socket)) + if (!message.receive(socket) || message.empty()) return false; - const auto& parts = message.parts(); + // Optional - ROUTER sockets strip this. + if (message.size() > 2) + origin_ = message.dequeue_data(); - if (parts.size() == 0) - return true; + // Remove empty delimiter frame. + message.dequeue(); - auto it = parts.begin(); - - // Extract reply address(es) - if (parts.size() > 2) - { - origin_ = *it; - ++it; - } - - // Confirm empty delimiter frame - ++it; - - // Parse request payload from data frame - if (decode_payload(*it)) - ++it; - - BITCOIN_ASSERT(it == parts.end()); - return true; + return decode_payload(message.dequeue_data()) && message.empty(); } -bool packet::receive(const std::shared_ptr& socket) -{ - if (socket == nullptr) - return false; - - return receive(*(socket.get())); -} +////bool packet::receive(const std::shared_ptr& socket) +////{ +//// return (socket != nullptr) && receive(*(socket.get())); +////} bool packet::send(zmq::socket& socket) { zmq::message message; + // Optionally encode the destination. if (!destination_.empty()) - message.append(destination_); - - message.append({}); + message.enqueue(destination_); - if (!encode_payload(message)) - return false; + // Add empty delimiter frame. + message.enqueue(data_chunk{}); - message.send(socket); - return true; + return encode_payload(message) && message.send(socket); } -bool packet::send(const std::shared_ptr& socket) -{ - if (socket == nullptr) - return false; - - return send(*(socket.get())); -} +////bool packet::send(const std::shared_ptr& socket) +////{ +//// return (socket != nullptr) && send(*(socket.get())); +////} } } diff --git a/src/request_packet.cpp b/src/request_packet.cpp index 1079926e..5ae07cb3 100644 --- a/src/request_packet.cpp +++ b/src/request_packet.cpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #include diff --git a/src/response_packet.cpp b/src/response_packet.cpp index bb172340..80f5b9e7 100644 --- a/src/response_packet.cpp +++ b/src/response_packet.cpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #include diff --git a/test/converter.cpp b/test/converter.cpp index 9fef4f32..581c374a 100644 --- a/test/converter.cpp +++ b/test/converter.cpp @@ -17,7 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifdef LIBBITCOIN_VERSION3 +#ifdef LIBBITCOIN_VERSION4 #include #include From 5605fb116ed791bdf638d1b9fb24b742e88d051b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Fri, 20 May 2016 03:09:35 -0700 Subject: [PATCH 19/30] Implement ZAP authentication handler. --- .../bitcoin/protocol/zmq/authenticator.hpp | 46 ++++- include/bitcoin/protocol/zmq/message.hpp | 48 +++-- src/zmq/authenticator.cpp | 190 +++++++++++++++++- src/zmq/certificate.cpp | 3 + src/zmq/message.cpp | 118 ++++++++--- test/zmq/ironhouse2.cpp | 6 +- test/zmq/poller.cpp | 9 +- 7 files changed, 356 insertions(+), 64 deletions(-) diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index 695e44f1..470bee18 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -20,10 +20,17 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP +#include +#include +#include +#include #include +#include #include #include #include +#include +#include namespace libbitcoin { namespace protocol { @@ -32,30 +39,49 @@ namespace zmq { class BCP_API authenticator { public: - /// Construct an instance. + /// Start the ZAP monitor for the context. + /// There may be only one authenticator per process (otherwise bridge). authenticator(context& context); - /// Free authenticator resources. - ~authenticator(); - /// This class is not copyable. authenticator(const authenticator&) = delete; void operator=(const authenticator&) = delete; - /// True if the construction succeeded. + /// Stop the ZAP monitor. + ~authenticator(); + + /// True if the ZAP monitor started successfully. operator const bool() const; - /// Allow clients with the following public keys (white list). - void allow(const std::string& public_key); + /// Allow clients with the following public keys (whitelist). + void allow(const hash_digest& public_key); - /// Allow clients with the following ip addresses (white list). + /// Allow clients with the following ip addresses (whitelist). void allow(const config::authority& address); - /// Allow clients with the following ip addresses (black list). + /// Allow clients with the following ip addresses (blacklist). void deny(const config::authority& address); private: - void* authenticator_; + void monitor(); + bool allowed(const std::string& address); + bool allowed(const hash_digest& public_key); + + // These are not thread safe, they are protected by sequential access. + + poller poller_; + socket socket_; + std::shared_ptr thread_; + + bool require_key_; + std::map keys_; + + bool require_address_; + std::map adresses_; + + // These are thread safe. + std::atomic stopped_; + const uint32_t interval_milliseconds_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 0478b27f..d338bbd1 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -20,6 +20,7 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_MESSAGE_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_MESSAGE_HPP +#include #include #include #include @@ -31,28 +32,51 @@ namespace zmq { class BCP_API message { public: + /// Add an empty message part to the outgoing message. + void enqueue(); + + /// Add an interable message part to the outgoing message. + template + void enqueue(const Iterable& value) + { + queue_.emplace(to_chunk(value)); + } + /// Add a message part to the outgoing message. - void append(data_chunk&& part); - void append(const data_chunk& part); - void append(const std::string& part); + template + void enqueue_little_endian(Unsigned value) + { + enqueue(to_little_endian(value)); + } - // Obtain the parts of the created or read message. - const data_stack& parts() const; + /// Remove a message part from the top of the queue, empty if empty queue. + data_chunk dequeue_data(); + std::string dequeue_text(); - // Obtain the first part as a text string, or empty if no parts. - std::string text() const; + /// Remove a message part from the top of the queue, false if empty queue. + bool dequeue(); + bool dequeue(uint32_t& value); + bool dequeue(data_chunk& value); + bool dequeue(std::string& value); + bool dequeue(hash_digest& value); - /// Clear the stack of message parts. + /// Clear the queue of message parts. void clear(); + /// True if the queue is empty. + bool empty() const; + + /// The number of items on the queue. + size_t size() const; + /// Send the message in parts. If a send fails the unsent parts remain. - bool send(socket& sock); + bool send(socket& socket); - /// Receve a message (clears the stack first). - bool receive(socket& sock); + /// Receve a message (clears the queue first). + bool receive(socket& socket); private: - data_stack parts_; + std::queue queue_; }; } // namespace zmq diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index b107c47d..e0abaab6 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -19,46 +19,214 @@ */ #include +#include +#include +#include #include -////#include +#include +#include #include +#include +#include namespace libbitcoin { namespace protocol { namespace zmq { -using path = boost::filesystem::path; +// ZAP endpoint, see: rfc.zeromq.org/spec:27/ZAP +static const auto zap_enpoint = "inproc://zeromq.zap.01"; +static constexpr uint32_t polling_interval_milliseconds = 1; authenticator::authenticator(context& context) - : authenticator_(nullptr) + : socket_(context, socket::role::router), + require_key_(false), + require_address_(false), + stopped_(true), + interval_milliseconds_(polling_interval_milliseconds) { - ////authenticator_ = zauth_new(context.self()); + if (socket_.bind(zap_enpoint)) + { + poller_.add(socket_); + stopped_.store(false); + thread_ = std::make_shared( + std::bind(&authenticator::monitor, this)); + } } authenticator::~authenticator() { - ////zauth_destroy(&authenticator_); + if (!stopped_.load()) + { + stopped_.store(true); + thread_->join(); + } } authenticator::operator const bool() const { - return authenticator_ != nullptr; + return !stopped_.load(); } -void authenticator::allow(const std::string& public_key) +// Handle ZAP authentication requests. +// github.com/zeromq/rfc/blob/master/src/spec_27.c +void authenticator::monitor() { - // Declares that a client cert must be matched (unless empty). - ////zauth_configure_curve(authenticator_, "*", path.c_str()); + while (!stopped_) + { + // The id can be zero (none) or socket_ id. + if (poller_.wait(interval_milliseconds_) == socket_.id()) + { + // An address delimiter frame, which SHALL have a length of zero. + // The version frame, which SHALL contain the three octets "1.0". + // The request id, which MAY contain an opaque binary blob. + // The domain, which SHALL contain a string. + // The address, the origin network IP address. + // The identity, the connection Identity, if any. + // The mechanism, which SHALL contain a string. + // The credentials, which SHALL be zero or more opaque frames. + message request; + const auto received = request.receive(socket_); + BITCOIN_ASSERT_MSG(received, "Internal malformed ZAP request."); + + if (!received || request.size() < 8) + continue; + + const auto origin = request.dequeue_data(); + const auto delimiter = request.dequeue_data(); + + if (!delimiter.empty()) + continue; + + const auto version = request.dequeue_text(); + + if (version != "1.0") + continue; + + // TODO: use domain to associate socket (ZMQ_ZAP_DOMAIN)?? + const auto sequence = request.dequeue_text(); + /* const auto domain = */ request.dequeue_text(); + const auto address = request.dequeue_text(); + + if (!allowed(address)) + continue; + + // TODO: use domain to associate socket (ZMQ_ZAP_DOMAIN)?? + /* const auto identity = */ request.dequeue_text(); + const auto mechanism = request.dequeue_text(); + + std::string status_code; + std::string status_text; + std::string userid; + std::string metadata; + + if (mechanism == "NULL") + { + if (!request.empty()) + { + status_code = "400"; + status_text = "Incorrect parameter count for null."; + } + else + { + status_code = "200"; + status_text = "OK"; + userid = "anonymous"; + } + } + else if (mechanism == "PLAIN") + { + if (request.size() != 2) + { + status_code = "400"; + status_text = "Incorrect parameter count for plain."; + } + else + { + const auto username = request.dequeue_text(); + const auto password = request.dequeue_text(); + + status_code = "400"; + status_text = "Plain security mechanism not supported."; + } + } + else if (mechanism == "CURVE") + { + if (request.size() != 1) + { + status_code = "400"; + status_text = "Incorrect parameter count for curve."; + } + else + { + hash_digest public_key; + if (!request.dequeue(public_key) || !allowed(public_key)) + { + status_code = "400"; + status_text = "Invalid client public key."; + } + else + { + status_code = "200"; + status_text = "OK"; + userid = "unspecified"; + } + } + } + else + { + status_code = "400"; + status_text = "Security mechanism not supported."; + } + + // An address delimiter frame, which SHALL have a length of zero. + // The version frame, which SHALL contain the three octets "1.0". + // The request id, which MAY contain an opaque binary blob. + // The status code, which SHALL contain a string. + // The status text, which MAY contain a string. + // The user id, which SHALL contain a string. + // The metadata, which MAY contain a blob. + message response; + response.enqueue(origin); + response.enqueue(delimiter); + response.enqueue(version); + response.enqueue(sequence); + response.enqueue(status_code); + response.enqueue(status_text); + response.enqueue(userid); + response.enqueue(metadata); + + DEBUG_ONLY(const auto sent =) response.send(socket_); + BITCOIN_ASSERT_MSG(sent, "Failed to send ZAP response."); + } + } +} + +bool authenticator::allowed(const hash_digest& public_key) +{ + return !require_key_ || keys_.find(public_key) != keys_.end(); +} + +bool authenticator::allowed(const std::string& ip_address) +{ + return !require_address_ || adresses_.find(ip_address) != adresses_.end(); +} + +void authenticator::allow(const hash_digest& public_key) +{ + require_key_ = true; + keys_.emplace(public_key, true); } void authenticator::allow(const config::authority& address) { - ////zauth_allow(authenticator_, address.c_str()); + require_address_ = true; + adresses_.emplace(address.to_hostname(), true); } void authenticator::deny(const config::authority& address) { - ////zauth_deny(authenticator_, address.c_str()); + // Denial is effective independent of whitelisting. + adresses_.emplace(address.to_hostname(), false); } } // namespace zmq diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index 0f8b5ae1..e021a4c6 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -42,8 +42,11 @@ certificate::certificate(const std::string& private_key) private_ = private_key; } +// TODO: create a fast override that doesn't loop (for ephemeral keygen). void certificate::create(std::string& out_public, std::string& out_private) { + // TODO: update settings loader so this isn't necessary. + // BUGBUG: this limitation weakens security by reducing key space. const auto valid_setting = [](const std::string& key) { return key.find_first_of('#') == std::string::npos; diff --git a/src/zmq/message.cpp b/src/zmq/message.cpp index 7e20aea4..62ac6faf 100644 --- a/src/zmq/message.cpp +++ b/src/zmq/message.cpp @@ -27,69 +27,141 @@ namespace libbitcoin { namespace protocol { namespace zmq { -void message::append(data_chunk&& part) +void message::enqueue() { - parts_.emplace_back(std::move(part)); + queue_.emplace(data_chunk{}); } -void message::append(const std::string& part) +bool message::dequeue() { - append(data_chunk{ part.begin(), part.end() }); + if (queue_.empty()) + return false; + + queue_.pop(); + return true; +} + +bool message::dequeue(uint32_t& value) +{ + if (queue_.empty()) + return false; + + const auto& front = queue_.front(); + + if (front.size() == sizeof(uint32_t)) + { + value = from_little_endian_unsafe(front.begin()); + queue_.pop(); + return true; + } + + queue_.pop(); + return false; } -void message::append(const data_chunk& part) +bool message::dequeue(data_chunk& value) { - parts_.push_back(part); + if (queue_.empty()) + return false; + + value = dequeue_data(); + return true; } -const data_stack& message::parts() const +bool message::dequeue(std::string& value) { - return parts_; + if (queue_.empty()) + return false; + + value = dequeue_text(); + return true; } -std::string message::text() const +bool message::dequeue(hash_digest& value) { - if (parts_.empty()) + if (queue_.empty()) + return false; + + const auto& front = queue_.front(); + + if (front.size() == hash_size) + { + std::copy(front.begin(), front.end(), value.begin()); + queue_.pop(); + return true; + } + + queue_.pop(); + return false; +} + +data_chunk message::dequeue_data() +{ + if (queue_.empty()) + return{}; + + const auto data = queue_.front(); + queue_.pop(); + return data; +} + +std::string message::dequeue_text() +{ + if (queue_.empty()) return{}; - const auto& first = parts_.front(); - return std::string(first.begin(), first.end()); + const auto& front = queue_.front(); + const auto text = std::string(front.begin(), front.end()); + queue_.pop(); + return text; } void message::clear() { - parts_.clear(); + while (!queue_.empty()) + queue_.pop(); } -bool message::send(socket& sock) +bool message::empty() const { - auto count = parts_.size(); + return queue_.empty(); +} + +size_t message::size() const +{ + return queue_.size(); +} - for (auto it = parts_.begin(); it != parts_.end(); it = parts_.erase(it)) +bool message::send(socket& socket) +{ + auto count = queue_.size(); + + while (!queue_.empty()) { - frame frame(*it); + frame frame(queue_.front()); + queue_.pop(); - if (!frame.send(sock, --count == 0)) + if (!frame.send(socket, --count == 0)) return false; } - BITCOIN_ASSERT(parts_.empty()); + BITCOIN_ASSERT(queue_.empty()); return true; } -bool message::receive(socket& sock) +bool message::receive(socket& socket) { - auto done = false; clear(); + auto done = false; while (!done) { frame frame; - if (!frame.receive(sock)) + if (!frame.receive(socket)) return false; - parts_.emplace_back(frame.payload()); + queue_.emplace(frame.payload()); done = !frame.more(); } diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index 14107800..fcd5ea38 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -53,7 +53,7 @@ void server_task(const std::string& server_private_key, // Send the test message. zmq::message message; - message.append("helllo world!"); + message.enqueue("helllo world!"); result = message.send(server); assert(result); @@ -86,7 +86,7 @@ void client_task(const std::string& client_private_key, zmq::message message; result = message.receive(client); assert(result); - assert(message.text() == "helllo world!"); + assert(message.dequeue_text() == "helllo world!"); puts("Ironhouse test OK"); } @@ -105,7 +105,7 @@ int ironhouse2_example() std::thread server_thread(server_task, server_cert.private_key(), client_cert.public_key(), localhost); - // Start a client, allos connections only to server with cert. + // Start a client, allows connections only to server with cert. std::thread client_thread(client_task, client_cert.private_key(), server_cert.public_key()); diff --git a/test/zmq/poller.cpp b/test/zmq/poller.cpp index a81da2e9..2e415e14 100644 --- a/test/zmq/poller.cpp +++ b/test/zmq/poller.cpp @@ -50,7 +50,7 @@ int poller_example() // Build and send the message. zmq::message message; - message.append(hello); + message.enqueue(hello); result = message.send(vent); assert(result); @@ -65,11 +65,10 @@ int poller_example() assert(result); // Check the size. - const auto& parts = message.parts(); - assert(parts.size() == 1); + assert(message.size() == 1); // Check the value. - const auto& part = parts[0]; - assert(std::equal(part.begin(), part.end(), hello.begin())); + const auto payload = message.dequeue_text(); + assert(payload == hello); return 0; } From ba1f4214ae786004891f742e66450680cab899f6 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 21 May 2016 19:14:30 -0700 Subject: [PATCH 20/30] Update library metadata. --- libbitcoin-protocol.pc.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libbitcoin-protocol.pc.in b/libbitcoin-protocol.pc.in index 85e15dd7..f5aea571 100644 --- a/libbitcoin-protocol.pc.in +++ b/libbitcoin-protocol.pc.in @@ -16,7 +16,7 @@ includedir=@includedir@ # Metadata #============================================================================== Name: libbitcoin-protocol -Description: Bitcoin Blockchain Query Privacy Protocol +Description: Bitcoin Blockchain Query Protocol URL: https://github.com/libbitcoin/libbitcoin-protocol Version: @PACKAGE_VERSION@ From b0576e723b4d905282b51a3f512735d5c05bde14 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 23 May 2016 04:47:50 -0700 Subject: [PATCH 21/30] Updates for session management, comments. --- .../bitcoin/protocol/zmq/authenticator.hpp | 28 ++- include/bitcoin/protocol/zmq/certificate.hpp | 18 +- include/bitcoin/protocol/zmq/context.hpp | 9 +- include/bitcoin/protocol/zmq/frame.hpp | 8 +- include/bitcoin/protocol/zmq/message.hpp | 3 +- include/bitcoin/protocol/zmq/poller.hpp | 11 +- include/bitcoin/protocol/zmq/socket.hpp | 10 +- src/zmq/authenticator.cpp | 231 +++++++++--------- src/zmq/certificate.cpp | 71 +++--- src/zmq/poller.cpp | 22 +- 10 files changed, 233 insertions(+), 178 deletions(-) diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index 470bee18..feacf421 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -36,22 +36,31 @@ namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. +/// This class must be constructed as a shared pointer. class BCP_API authenticator + : public enable_shared_from_base { public: + /// A shared authenticator pointer. + typedef std::shared_ptr ptr; + /// Start the ZAP monitor for the context. /// There may be only one authenticator per process (otherwise bridge). - authenticator(context& context); + authenticator(context& context, threadpool& threadpool); /// This class is not copyable. authenticator(const authenticator&) = delete; void operator=(const authenticator&) = delete; + /// Start the ZAP monitor. + void authenticator::start(); + /// Stop the ZAP monitor. - ~authenticator(); + void authenticator::stop(); - /// True if the ZAP monitor started successfully. - operator const bool() const; + /// True if the ZAP monitor is stopped. + bool authenticator::stopped() const; /// Allow clients with the following public keys (whitelist). void allow(const hash_digest& public_key); @@ -64,19 +73,16 @@ class BCP_API authenticator private: void monitor(); - bool allowed(const std::string& address); - bool allowed(const hash_digest& public_key); + bool allowed(const std::string& address) const; + bool allowed(const hash_digest& public_key) const; // These are not thread safe, they are protected by sequential access. poller poller_; socket socket_; - std::shared_ptr thread_; - - bool require_key_; - std::map keys_; - + dispatcher dispatch_; bool require_address_; + std::map keys_; std::map adresses_; // These are thread safe. diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index a09739b2..77d9d6fc 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -21,8 +21,10 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP #include +#include #include +/// This class is not thread safe. namespace libbitcoin { namespace protocol { namespace zmq { @@ -33,10 +35,14 @@ class BCP_API certificate { public: /// Construct a new certificate (can we inject randomness). - certificate(); + /// The setting option reduces keyspace, disallowing '#' in text encoding. + certificate(bool setting=false); /// Construct a certificate from a private key (generates public key). - certificate(const std::string& private_key); + certificate(const hash_digest& private_key); + + /// Construct a certificate from base85 private key (generates public key). + certificate(const std::string& base85_private_key); /// True if the certificate is valid. operator const bool() const; @@ -44,12 +50,14 @@ class BCP_API certificate /// The public key base85 text. const std::string& public_key() const; - /// The secret key base85 text. + /// The private key base85 text. const std::string& private_key() const; protected: - void create(std::string& out_public, std::string& out_private); - bool derive(std::string& out_public, const std::string& private_key); + static bool derive(std::string& out_public, + const std::string& private_key); + static bool create(std::string& out_public, std::string& out_private, + bool setting); private: std::string public_; diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index 2e317df1..91fe5bf2 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -21,20 +21,27 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_CONTEXT_HPP #include +#include +#include #include +/// This class is not thread safe. namespace libbitcoin { namespace protocol { namespace zmq { class BCP_API context + : public enable_shared_from_base { public: + /// A shared context pointer. + typedef std::shared_ptr ptr; + /// Construct a context. context(); /// Cause all sockets of this context to close. - ~context(); + virtual ~context(); /// This class is not copyable. context(const context&) = delete; diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index 7d25f92c..5b03b18e 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -20,17 +20,23 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_FRAME_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_FRAME_HPP +#include #include #include #include +/// This class is not thread safe. namespace libbitcoin { namespace protocol { namespace zmq { class BCP_API frame + : public enable_shared_from_base { public: + /// A shared frame pointer. + typedef std::shared_ptr ptr; + /// Construct a frame with no payload (for receiving). frame(); @@ -38,7 +44,7 @@ class BCP_API frame frame(const data_chunk& data); /// Free the frame's allocated memory. - ~frame(); + virtual ~frame(); /// This class is not copyable. frame(const frame&) = delete; diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index d338bbd1..5376d406 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -28,7 +28,8 @@ namespace libbitcoin { namespace protocol { namespace zmq { - + +/// This class is not thread safe. class BCP_API message { public: diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 253c5307..ed232f47 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -21,6 +21,7 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_POLLER_HPP #include +#include #include #include #include @@ -28,10 +29,15 @@ namespace libbitcoin { namespace protocol { namespace zmq { - + +/// This class is not thread safe. class BCP_API poller + : public enable_shared_from_base { public: + /// A shared poller pointer. + typedef std::shared_ptr ptr; + /// Construct an empty poller (sockets must be added). poller(); @@ -39,9 +45,6 @@ class BCP_API poller poller(const poller&) = delete; void operator=(const poller&) = delete; - /// Free poller resources. - ~poller(); - /// True if the timeout occurred. bool expired() const; diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index 5d392bfb..1b906198 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -21,6 +21,7 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_SOCKET_HPP #include +#include #include #include #include @@ -30,7 +31,9 @@ namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. class BCP_API socket + : public enable_shared_from_base { public: /// The full set of socket roles defined by zeromq. @@ -50,7 +53,10 @@ class BCP_API socket streamer }; - // A locally unique idenfitier for this socket. + /// A shared socket pointer. + typedef std::shared_ptr ptr; + + /// A locally unique idenfitier for this socket. typedef intptr_t identifier; socket(); @@ -62,7 +68,7 @@ class BCP_API socket void operator=(const socket&) = delete; /// Free socket resources. - ~socket(); + virtual ~socket(); /// True if there is an encapsultaed zeromq socket. operator const bool() const; diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index e0abaab6..7a3b02c8 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -33,158 +33,170 @@ namespace libbitcoin { namespace protocol { namespace zmq { +#define NAME "authenticator" + // ZAP endpoint, see: rfc.zeromq.org/spec:27/ZAP -static const auto zap_enpoint = "inproc://zeromq.zap.01"; +static const auto zap_endpoint = "inproc://zeromq.zap.01"; + static constexpr uint32_t polling_interval_milliseconds = 1; -authenticator::authenticator(context& context) +authenticator::authenticator(context& context, threadpool& pool) : socket_(context, socket::role::router), - require_key_(false), + dispatch_(pool, NAME), require_address_(false), stopped_(true), interval_milliseconds_(polling_interval_milliseconds) { - if (socket_.bind(zap_enpoint)) - { - poller_.add(socket_); - stopped_.store(false); - thread_ = std::make_shared( - std::bind(&authenticator::monitor, this)); - } + BITCOIN_ASSERT(socket_); + + // The authenticator establishes a well-known endpoint. + // There may be only one such endpoint per process, tied to one context. + poller_.add(socket_); } -authenticator::~authenticator() +void authenticator::start() { - if (!stopped_.load()) - { - stopped_.store(true); - thread_->join(); - } + if (!socket_.bind(zap_endpoint)) + return; + + stopped_.store(false); + + // The dispatched thread closes when the monitor loop exits (stop). + dispatch_.concurrent( + std::bind(&authenticator::monitor, + shared_from_this())); +} + +/// This terminates the monitor, allowing the threadpool to join. +void authenticator::stop() +{ + stopped_.store(true); } -authenticator::operator const bool() const +bool authenticator::stopped() const { - return !stopped_.load(); + return stopped_.load(); } -// Handle ZAP authentication requests. // github.com/zeromq/rfc/blob/master/src/spec_27.c void authenticator::monitor() { - while (!stopped_) + while (!stopped()) { // The id can be zero (none) or socket_ id. if (poller_.wait(interval_milliseconds_) == socket_.id()) { - // An address delimiter frame, which SHALL have a length of zero. - // The version frame, which SHALL contain the three octets "1.0". - // The request id, which MAY contain an opaque binary blob. - // The domain, which SHALL contain a string. - // The address, the origin network IP address. - // The identity, the connection Identity, if any. - // The mechanism, which SHALL contain a string. - // The credentials, which SHALL be zero or more opaque frames. - message request; - const auto received = request.receive(socket_); - BITCOIN_ASSERT_MSG(received, "Internal malformed ZAP request."); - - if (!received || request.size() < 8) - continue; - - const auto origin = request.dequeue_data(); - const auto delimiter = request.dequeue_data(); - - if (!delimiter.empty()) - continue; - - const auto version = request.dequeue_text(); - - if (version != "1.0") - continue; - - // TODO: use domain to associate socket (ZMQ_ZAP_DOMAIN)?? - const auto sequence = request.dequeue_text(); - /* const auto domain = */ request.dequeue_text(); - const auto address = request.dequeue_text(); - - if (!allowed(address)) - continue; - - // TODO: use domain to associate socket (ZMQ_ZAP_DOMAIN)?? - /* const auto identity = */ request.dequeue_text(); - const auto mechanism = request.dequeue_text(); - + data_chunk origin; + data_chunk delimiter; + std::string version; + std::string sequence; std::string status_code; std::string status_text; std::string userid; std::string metadata; - if (mechanism == "NULL") + message request; + const auto received = request.receive(socket_); + + if (!received || request.size() < 8) { - if (!request.empty()) - { - status_code = "400"; - status_text = "Incorrect parameter count for null."; - } - else - { - status_code = "200"; - status_text = "OK"; - userid = "anonymous"; - } + status_code = "500"; + status_text = "Internal error."; } - else if (mechanism == "PLAIN") + else { - if (request.size() != 2) - { - status_code = "400"; - status_text = "Incorrect parameter count for plain."; - } - else + origin = request.dequeue_data(); + delimiter = request.dequeue_data(); + version = request.dequeue_text(); + sequence = request.dequeue_text(); + const auto domain = request.dequeue_text(); + const auto address = request.dequeue_text(); + const auto identity = request.dequeue_text(); + const auto mechanism = request.dequeue_text(); + + // Each socket on the authenticated context must set a domain. + if (origin.empty() || !delimiter.empty() || version != "1.0" || + sequence.empty() || domain.empty() || !identity.empty()) { - const auto username = request.dequeue_text(); - const auto password = request.dequeue_text(); - - status_code = "400"; - status_text = "Plain security mechanism not supported."; + status_code = "500"; + status_text = "Internal error."; } - } - else if (mechanism == "CURVE") - { - if (request.size() != 1) + else if (!allowed(address)) { + // Address restrictons are independent of mechanisms. status_code = "400"; - status_text = "Incorrect parameter count for curve."; + status_text = "Address not enabled for access."; } else { - hash_digest public_key; - if (!request.dequeue(public_key) || !allowed(public_key)) + if (mechanism == "NULL") { - status_code = "400"; - status_text = "Invalid client public key."; + if (request.size() != 0) + { + status_code = "400"; + status_text = "Incorrect NULL parameterization."; + } + else + { + ////status_code = "200"; + ////status_text = "OK"; + ////userid = "anonymous"; + status_code = "400"; + status_text = "NULL mechanism not allowed."; + } + } + else if (mechanism == "CURVE") + { + if (request.size() != 1) + { + status_code = "400"; + status_text = "Incorrect CURVE parameterization."; + } + else + { + hash_digest public_key; + + if (!request.dequeue(public_key)) + { + status_code = "400"; + status_text = "Invalid public key."; + } + else if (!allowed(public_key)) + { + status_code = "400"; + status_text = "Public key not authorized."; + } + else + { + status_code = "200"; + status_text = "OK"; + userid = "unspecified"; + } + } + } + else if (mechanism == "PLAIN") + { + if (request.size() != 2) + { + status_code = "400"; + status_text = "Incorrect PLAIN parameterization."; + } + else + { + ////userid = request.dequeue_text(); + ////const auto password = request.dequeue_text(); + status_code = "400"; + status_text = "PLAIN mechanism not allowed."; + } } else { - status_code = "200"; - status_text = "OK"; - userid = "unspecified"; + status_code = "400"; + status_text = "Security mechanism not supported."; } } } - else - { - status_code = "400"; - status_text = "Security mechanism not supported."; - } - // An address delimiter frame, which SHALL have a length of zero. - // The version frame, which SHALL contain the three octets "1.0". - // The request id, which MAY contain an opaque binary blob. - // The status code, which SHALL contain a string. - // The status text, which MAY contain a string. - // The user id, which SHALL contain a string. - // The metadata, which MAY contain a blob. message response; response.enqueue(origin); response.enqueue(delimiter); @@ -201,19 +213,18 @@ void authenticator::monitor() } } -bool authenticator::allowed(const hash_digest& public_key) +bool authenticator::allowed(const hash_digest& public_key) const { - return !require_key_ || keys_.find(public_key) != keys_.end(); + return !keys_.empty() || keys_.find(public_key) != keys_.end(); } -bool authenticator::allowed(const std::string& ip_address) +bool authenticator::allowed(const std::string& ip_address) const { return !require_address_ || adresses_.find(ip_address) != adresses_.end(); } void authenticator::allow(const hash_digest& public_key) { - require_key_ = true; keys_.emplace(public_key, true); } diff --git a/src/zmq/certificate.cpp b/src/zmq/certificate.cpp index e021a4c6..3361a136 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -21,6 +21,7 @@ #include #include +#include namespace libbitcoin { namespace protocol { @@ -29,61 +30,75 @@ namespace zmq { static constexpr int32_t zmq_fail = -1; static constexpr size_t zmq_encoded_key_size = 40; -certificate::certificate() +certificate::certificate(bool setting) { - create(public_, private_); + create(public_, private_, setting); } -certificate::certificate(const std::string& private_key) +certificate::certificate(const hash_digest& private_key) { - if (private_key.empty()) - create(public_, private_); - else if (derive(public_, private_key)) - private_ = private_key; + std::string base85_private_key; + + // This cannot fail but we handle anyway. + if (!encode_base85(base85_private_key, private_key)) + return; + + // If we successfully derive the public then set the private. + if (derive(public_, base85_private_key)) + private_ = base85_private_key; +} + +certificate::certificate(const std::string& base85_private_key) +{ + // If we successfully derive the public then set the private. + if (derive(public_, base85_private_key)) + private_ = base85_private_key; +} + +bool certificate::derive(std::string& out_public, + const std::string& private_key) +{ + if (private_key.size() != zmq_encoded_key_size) + return false; + + char public_key[zmq_encoded_key_size + 1] = { 0 }; + + if (zmq_curve_public(public_key, private_key.data()) == zmq_fail) + return false; + + out_public = public_key; + return true; } -// TODO: create a fast override that doesn't loop (for ephemeral keygen). -void certificate::create(std::string& out_public, std::string& out_private) +bool certificate::create(std::string& out_public, std::string& out_private, + bool setting) { // TODO: update settings loader so this isn't necessary. // BUGBUG: this limitation weakens security by reducing key space. - const auto valid_setting = [](const std::string& key) + const auto ok_setting = [](const std::string& key) { return key.find_first_of('#') == std::string::npos; }; // Loop until neither key's base85 encoding includes the # character. // This ensures that the value can be used in libbitcoin settings files. - for (uint8_t iteration = 0; iteration <= max_uint8; iteration++) + for (uint8_t attempt = 0; attempt <= max_uint8; attempt++) { char public_key[zmq_encoded_key_size + 1] = { 0 }; char private_key[zmq_encoded_key_size + 1] = { 0 }; if (zmq_curve_keypair(public_key, private_key) == zmq_fail) - return; + return false; - if (valid_setting(public_key) && !valid_setting(private_key)) + if (!setting || (ok_setting(public_key) && ok_setting(private_key))) { out_public = public_key; out_private = private_key; - return; + return true; } } -} -bool certificate::derive(std::string& out_public, - const std::string& private_key) -{ - if (private_key.size() != zmq_encoded_key_size) - return false; - - char public_key[zmq_encoded_key_size + 1] = { 0 }; - - if (zmq_curve_public(public_key, private_key.data()) == zmq_fail) - return false; - - out_public = public_key; - return true; + return false; } certificate::operator const bool() const diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index cb9f8271..09fe3195 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -34,32 +34,24 @@ poller::poller() { } -poller::~poller() -{ -} - +// Parameter fd is non-zmq socket (unused when socket is set). void poller::add(socket& socket) { zmq_pollitem item; - - // zmq socket. item.socket = socket.self(); - - // non-zmq socket (unused when socket is set). item.fd = 0; - - // flags. item.events = ZMQ_POLLIN; - - // return events. item.revents = 0; - pollers_.push_back(item); } +// BUGBUG: zeromq 4.2 has an overflow bug in timer parameterization. // The timeout is typed as 'long' by zermq. This is 32 bit on windows and -// typically 64 bit on other platforms. So for consistency of config we -// limit the domain to 32 bit using int32_t. -1 signals infinite wait. +// actually less (potentially 1000 or 1 second) on other platforms. +// And on non-windows platforms negative doesn't actually produce infinity. + +// For consistency of config we limit the domain to 32 bit using int32_t, +// where negative implies infinite wait. socket::identifier poller::wait(int32_t timeout_milliseconds) { const auto size = pollers_.size(); From 51f7bffc2fa404c8460b3c79cd6bd66ad9c85ebe Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 23 May 2016 20:54:37 -0700 Subject: [PATCH 22/30] Have authenticator generate its own context and expose it. --- .../bitcoin/protocol/zmq/authenticator.hpp | 18 +- include/bitcoin/protocol/zmq/context.hpp | 5 +- src/zmq/authenticator.cpp | 215 +++++++++--------- src/zmq/context.cpp | 9 +- 4 files changed, 120 insertions(+), 127 deletions(-) diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index feacf421..9587f436 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -47,20 +47,20 @@ class BCP_API authenticator /// Start the ZAP monitor for the context. /// There may be only one authenticator per process (otherwise bridge). - authenticator(context& context, threadpool& threadpool); + authenticator(threadpool& threadpool); /// This class is not copyable. authenticator(const authenticator&) = delete; void operator=(const authenticator&) = delete; - /// Start the ZAP monitor. - void authenticator::start(); + /// Get the context to which this authenticator applies. + zmq::context& context(); - /// Stop the ZAP monitor. - void authenticator::stop(); + /// Start the ZAP monitor. + void start(); - /// True if the ZAP monitor is stopped. - bool authenticator::stopped() const; + /// Stop the ZAP monitor and all sockets on the context. + void stop(); /// Allow clients with the following public keys (whitelist). void allow(const hash_digest& public_key); @@ -78,15 +78,15 @@ class BCP_API authenticator // These are not thread safe, they are protected by sequential access. - poller poller_; + zmq::context context_; socket socket_; + poller poller_; dispatcher dispatch_; bool require_address_; std::map keys_; std::map adresses_; // These are thread safe. - std::atomic stopped_; const uint32_t interval_milliseconds_; }; diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index 91fe5bf2..490c0654 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -53,9 +53,10 @@ class BCP_API context /// The underlying zeromq context. void* self(); -private: - bool destroy(); + /// Close the context, terminating all socket activity. + bool close(); +private: int32_t threads_; void* self_; }; diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index 7a3b02c8..da06e7f0 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -40,11 +40,11 @@ static const auto zap_endpoint = "inproc://zeromq.zap.01"; static constexpr uint32_t polling_interval_milliseconds = 1; -authenticator::authenticator(context& context, threadpool& pool) - : socket_(context, socket::role::router), +// The authenticator creates its own context, use to create secure sockets. +authenticator::authenticator(threadpool& pool) + : socket_(context_, socket::role::router), dispatch_(pool, NAME), require_address_(false), - stopped_(true), interval_milliseconds_(polling_interval_milliseconds) { BITCOIN_ASSERT(socket_); @@ -54,162 +54,157 @@ authenticator::authenticator(context& context, threadpool& pool) poller_.add(socket_); } +context& authenticator::context() +{ + return context_; +} + void authenticator::start() { if (!socket_.bind(zap_endpoint)) return; - stopped_.store(false); - // The dispatched thread closes when the monitor loop exits (stop). dispatch_.concurrent( std::bind(&authenticator::monitor, shared_from_this())); } -/// This terminates the monitor, allowing the threadpool to join. +/// This terminates the context, terminating all of its sockets. void authenticator::stop() { - stopped_.store(true); -} - -bool authenticator::stopped() const -{ - return stopped_.load(); + context_.close(); } // github.com/zeromq/rfc/blob/master/src/spec_27.c void authenticator::monitor() { - while (!stopped()) + // The id can be zero (terminate) or socket_ id. + while (poller_.wait(interval_milliseconds_) == socket_.id()) { - // The id can be zero (none) or socket_ id. - if (poller_.wait(interval_milliseconds_) == socket_.id()) + data_chunk origin; + data_chunk delimiter; + std::string version; + std::string sequence; + std::string status_code; + std::string status_text; + std::string userid; + std::string metadata; + + message request; + const auto received = request.receive(socket_); + + if (!received || request.size() < 8) { - data_chunk origin; - data_chunk delimiter; - std::string version; - std::string sequence; - std::string status_code; - std::string status_text; - std::string userid; - std::string metadata; - - message request; - const auto received = request.receive(socket_); - - if (!received || request.size() < 8) + status_code = "500"; + status_text = "Internal error."; + } + else + { + origin = request.dequeue_data(); + delimiter = request.dequeue_data(); + version = request.dequeue_text(); + sequence = request.dequeue_text(); + const auto domain = request.dequeue_text(); + const auto address = request.dequeue_text(); + const auto identity = request.dequeue_text(); + const auto mechanism = request.dequeue_text(); + + // Each socket on the authenticated context must set a domain. + if (origin.empty() || !delimiter.empty() || version != "1.0" || + sequence.empty() || domain.empty() || !identity.empty()) { status_code = "500"; status_text = "Internal error."; } + else if (!allowed(address)) + { + // Address restrictons are independent of mechanisms. + status_code = "400"; + status_text = "Address not enabled for access."; + } else { - origin = request.dequeue_data(); - delimiter = request.dequeue_data(); - version = request.dequeue_text(); - sequence = request.dequeue_text(); - const auto domain = request.dequeue_text(); - const auto address = request.dequeue_text(); - const auto identity = request.dequeue_text(); - const auto mechanism = request.dequeue_text(); - - // Each socket on the authenticated context must set a domain. - if (origin.empty() || !delimiter.empty() || version != "1.0" || - sequence.empty() || domain.empty() || !identity.empty()) - { - status_code = "500"; - status_text = "Internal error."; - } - else if (!allowed(address)) + if (mechanism == "NULL") { - // Address restrictons are independent of mechanisms. - status_code = "400"; - status_text = "Address not enabled for access."; + if (request.size() != 0) + { + status_code = "400"; + status_text = "Incorrect NULL parameterization."; + } + else + { + ////status_code = "200"; + ////status_text = "OK"; + ////userid = "anonymous"; + status_code = "400"; + status_text = "NULL mechanism not allowed."; + } } - else + else if (mechanism == "CURVE") { - if (mechanism == "NULL") + if (request.size() != 1) { - if (request.size() != 0) - { - status_code = "400"; - status_text = "Incorrect NULL parameterization."; - } - else - { - ////status_code = "200"; - ////status_text = "OK"; - ////userid = "anonymous"; - status_code = "400"; - status_text = "NULL mechanism not allowed."; - } + status_code = "400"; + status_text = "Incorrect CURVE parameterization."; } - else if (mechanism == "CURVE") + else { - if (request.size() != 1) + hash_digest public_key; + + if (!request.dequeue(public_key)) { status_code = "400"; - status_text = "Incorrect CURVE parameterization."; - } - else - { - hash_digest public_key; - - if (!request.dequeue(public_key)) - { - status_code = "400"; - status_text = "Invalid public key."; - } - else if (!allowed(public_key)) - { - status_code = "400"; - status_text = "Public key not authorized."; - } - else - { - status_code = "200"; - status_text = "OK"; - userid = "unspecified"; - } + status_text = "Invalid public key."; } - } - else if (mechanism == "PLAIN") - { - if (request.size() != 2) + else if (!allowed(public_key)) { status_code = "400"; - status_text = "Incorrect PLAIN parameterization."; + status_text = "Public key not authorized."; } else { - ////userid = request.dequeue_text(); - ////const auto password = request.dequeue_text(); - status_code = "400"; - status_text = "PLAIN mechanism not allowed."; + status_code = "200"; + status_text = "OK"; + userid = "unspecified"; } } + } + else if (mechanism == "PLAIN") + { + if (request.size() != 2) + { + status_code = "400"; + status_text = "Incorrect PLAIN parameterization."; + } else { + ////userid = request.dequeue_text(); + ////const auto password = request.dequeue_text(); status_code = "400"; - status_text = "Security mechanism not supported."; + status_text = "PLAIN mechanism not allowed."; } } + else + { + status_code = "400"; + status_text = "Security mechanism not supported."; + } } - - message response; - response.enqueue(origin); - response.enqueue(delimiter); - response.enqueue(version); - response.enqueue(sequence); - response.enqueue(status_code); - response.enqueue(status_text); - response.enqueue(userid); - response.enqueue(metadata); - - DEBUG_ONLY(const auto sent =) response.send(socket_); - BITCOIN_ASSERT_MSG(sent, "Failed to send ZAP response."); } + + message response; + response.enqueue(origin); + response.enqueue(delimiter); + response.enqueue(version); + response.enqueue(sequence); + response.enqueue(status_code); + response.enqueue(status_text); + response.enqueue(userid); + response.enqueue(metadata); + + DEBUG_ONLY(const auto sent =) response.send(socket_); + BITCOIN_ASSERT_MSG(sent, "Failed to send ZAP response."); } } diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index d04562f8..f3dfff3d 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -30,9 +30,6 @@ namespace zmq { static constexpr int32_t zmq_fail = -1; static constexpr int32_t zmq_io_threads = 1; -// TODO: Each socket should maintain a smart pointer reference to the context. -// TODO: When all sockets are closed the context is free to be destroyed. -// TODO: Add call to zmq_term on the context that causes all sockets to close. context::context() : threads_(zmq_io_threads), self_(zmq_init(threads_)) @@ -41,16 +38,16 @@ context::context() context::~context() { - DEBUG_ONLY(const auto result =) destroy(); + DEBUG_ONLY(const auto result =) close(); BITCOIN_ASSERT(result); } -bool context::destroy() +bool context::close() { if (self_ == nullptr) return true; - // This terminates blocking operations but blocks until either each socket + // This aborts blocking operations but blocks here until either each socket // in the context is explicitly closed or its linger period is exceeded. return zmq_term(self_) != zmq_fail; } From cf135540406c1dddefae6b26ceb4fee2bedeeddb Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 00:27:01 -0700 Subject: [PATCH 23/30] Fix invalid typedefs. --- include/bitcoin/protocol/zmq/frame.hpp | 6 +++++- include/bitcoin/protocol/zmq/poller.hpp | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index 5b03b18e..5ebc81b0 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -67,7 +67,11 @@ class BCP_API frame private: // zmq_msg_t alias, keeps zmq.h out of our headers. - typedef union zmq_msg{ unsigned char alignment[64]; void* pointer; }; + typedef union + { + unsigned char alignment[64]; + void* pointer; + } zmq_msg; static bool initialize(zmq_msg& message, const data_chunk& data); diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index ed232f47..fa53f7f4 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -59,13 +59,13 @@ class BCP_API poller private: // zmq_pollitem_t alias, keeps zmq.h out of our headers. - typedef struct zmq_pollitem + typedef struct { void* socket; file_descriptor fd; short events; short revents; - }; + } zmq_pollitem; typedef std::vector pollers; From 38c35ad4a25d601c0e1183912b2ead0dd9866c4b Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 00:59:56 -0700 Subject: [PATCH 24/30] Comments, whitespace. --- include/bitcoin/protocol/zmq/certificate.hpp | 2 +- include/bitcoin/protocol/zmq/frame.hpp | 2 +- include/bitcoin/protocol/zmq/message.hpp | 2 +- include/bitcoin/protocol/zmq/poller.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index 77d9d6fc..9a00b7cd 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -24,11 +24,11 @@ #include #include -/// This class is not thread safe. namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. /// A simplified "certificate" class to manage a curve key pair. /// If valid the class always retains a consistent key pair. class BCP_API certificate diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp index 5ebc81b0..5dacd554 100644 --- a/include/bitcoin/protocol/zmq/frame.hpp +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -25,11 +25,11 @@ #include #include -/// This class is not thread safe. namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. class BCP_API frame : public enable_shared_from_base { diff --git a/include/bitcoin/protocol/zmq/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 5376d406..2b1f7243 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -28,7 +28,7 @@ namespace libbitcoin { namespace protocol { namespace zmq { - + /// This class is not thread safe. class BCP_API message { diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index fa53f7f4..add6ea06 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -29,7 +29,7 @@ namespace libbitcoin { namespace protocol { namespace zmq { - + /// This class is not thread safe. class BCP_API poller : public enable_shared_from_base From c71ee96bec9ce91fd5b12a60b11c9f8e3f457d44 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 01:00:23 -0700 Subject: [PATCH 25/30] Rename context.close to .stop. --- include/bitcoin/protocol/zmq/context.hpp | 6 +++--- src/zmq/context.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index 490c0654..205e9884 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -25,11 +25,11 @@ #include #include -/// This class is not thread safe. namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. class BCP_API context : public enable_shared_from_base { @@ -53,8 +53,8 @@ class BCP_API context /// The underlying zeromq context. void* self(); - /// Close the context, terminating all socket activity. - bool close(); + /// Stop all socket activity by closing the zeromq context. + bool stop(); private: int32_t threads_; diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index f3dfff3d..edec54b2 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -38,13 +38,13 @@ context::context() context::~context() { - DEBUG_ONLY(const auto result =) close(); + DEBUG_ONLY(const auto result =) stop(); BITCOIN_ASSERT(result); } -bool context::close() +bool context::stop() { - if (self_ == nullptr) + if (self() == nullptr) return true; // This aborts blocking operations but blocks here until either each socket From 435f60463dc33dd86312b894eaf9b063107a12b7 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 01:00:37 -0700 Subject: [PATCH 26/30] Remove dead code. --- include/bitcoin/protocol/zmq/socket.hpp | 1 - src/zmq/socket.cpp | 5 ----- 2 files changed, 6 deletions(-) diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index 1b906198..cd9a9fb1 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -75,7 +75,6 @@ class BCP_API socket /// The underlying zeromq socket. void* self(); - void* self() const; /// The socket identifier is an opaue correlation idenfier. identifier id() const; diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index 315ac555..d4e020e4 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -169,11 +169,6 @@ void* socket::self() return socket_; } -void* socket::self() const -{ - return socket_; -} - socket::identifier socket::id() const { return reinterpret_cast(socket_); From 9f8ef7e1d00dde6a06bc5cd4d2fafea6697ab5d4 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 01:00:52 -0700 Subject: [PATCH 27/30] Derive authenticator from context. --- .../bitcoin/protocol/zmq/authenticator.hpp | 22 +--------- src/zmq/authenticator.cpp | 41 +++++++++---------- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index 9587f436..c2ae2efc 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -20,12 +20,9 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP -#include -#include #include #include #include -#include #include #include #include @@ -39,7 +36,7 @@ namespace zmq { /// This class is not thread safe. /// This class must be constructed as a shared pointer. class BCP_API authenticator - : public enable_shared_from_base + : public context { public: /// A shared authenticator pointer. @@ -49,18 +46,8 @@ class BCP_API authenticator /// There may be only one authenticator per process (otherwise bridge). authenticator(threadpool& threadpool); - /// This class is not copyable. - authenticator(const authenticator&) = delete; - void operator=(const authenticator&) = delete; - - /// Get the context to which this authenticator applies. - zmq::context& context(); - /// Start the ZAP monitor. - void start(); - - /// Stop the ZAP monitor and all sockets on the context. - void stop(); + bool start(); /// Allow clients with the following public keys (whitelist). void allow(const hash_digest& public_key); @@ -77,17 +64,12 @@ class BCP_API authenticator bool allowed(const hash_digest& public_key) const; // These are not thread safe, they are protected by sequential access. - - zmq::context context_; socket socket_; poller poller_; dispatcher dispatch_; bool require_address_; std::map keys_; std::map adresses_; - - // These are thread safe. - const uint32_t interval_milliseconds_; }; } // namespace zmq diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index da06e7f0..a40e54de 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -40,48 +40,45 @@ static const auto zap_endpoint = "inproc://zeromq.zap.01"; static constexpr uint32_t polling_interval_milliseconds = 1; -// The authenticator creates its own context, use to create secure sockets. authenticator::authenticator(threadpool& pool) - : socket_(context_, socket::role::router), + : socket_(*this, socket::role::router), dispatch_(pool, NAME), - require_address_(false), - interval_milliseconds_(polling_interval_milliseconds) + require_address_(false) { - BITCOIN_ASSERT(socket_); + // If the contained context is invalid so is the socket and bool override. + if (self() == nullptr) + return; // The authenticator establishes a well-known endpoint. // There may be only one such endpoint per process, tied to one context. poller_.add(socket_); } -context& authenticator::context() +bool authenticator::start() { - return context_; -} - -void authenticator::start() -{ - if (!socket_.bind(zap_endpoint)) - return; + if (self() == nullptr || !socket_.bind(zap_endpoint)) + return false; // The dispatched thread closes when the monitor loop exits (stop). dispatch_.concurrent( std::bind(&authenticator::monitor, - shared_from_this())); -} + shared_from_base())); -/// This terminates the context, terminating all of its sockets. -void authenticator::stop() -{ - context_.close(); + return true; } // github.com/zeromq/rfc/blob/master/src/spec_27.c void authenticator::monitor() { - // The id can be zero (terminate) or socket_ id. - while (poller_.wait(interval_milliseconds_) == socket_.id()) + // Ignore expired and keep looping, exiting the thread when terminated. + while (!poller_.terminated()) { + const auto socket_id = poller_.wait(polling_interval_milliseconds); + + // If the id doesn't match the poll is either terminated or expired. + if (socket_id == socket_.id()) + continue; + data_chunk origin; data_chunk delimiter; std::string version; @@ -203,7 +200,7 @@ void authenticator::monitor() response.enqueue(userid); response.enqueue(metadata); - DEBUG_ONLY(const auto sent =) response.send(socket_); + DEBUG_ONLY(const auto sent = ) response.send(socket_); BITCOIN_ASSERT_MSG(sent, "Failed to send ZAP response."); } } From 11752e142c375c494cec4a80109f4d95cebfcbb8 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 01:01:01 -0700 Subject: [PATCH 28/30] Fix up example code. --- test/zmq/ironhouse2.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index fcd5ea38..490ec015 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -28,17 +28,16 @@ void server_task(const std::string& server_private_key, const std::string& client_public_key, const config::authority& client_address) { - // Create a context for the server. - zmq::context context; - assert(context); + // Create a threadpool for the authenticator. + threadpool threadpool(1); // Establish the context's authentication whitelist. - zmq::authenticator authenticator(context); + zmq::authenticator authenticator(threadpool); authenticator.allow(client_address); authenticator.allow(client_public_key); - // Create a push socket using the server's authenticated context. - zmq::socket server(context, zmq::socket::role::pusher); + // Create a push socket using the authenticated context. + zmq::socket server(authenticator, zmq::socket::role::pusher); assert(server); // Configure the server to provide identity and require client identity. @@ -57,14 +56,18 @@ void server_task(const std::string& server_private_key, result = message.send(server); assert(result); - // Give client time to complete (hack). + // Give client time to complete (normally would have external hook here). std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // Stop the authentication context monitor and join the thread. + authenticator.stop(); + threadpool.join(); } void client_task(const std::string& client_private_key, const std::string& server_public_key) { - // Create a context for the client. + // Create an unauthenticated context for the client. zmq::context context; assert(context); From e88f728ba898c07f22571097dd333682c3134fdc Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 20:29:15 -0700 Subject: [PATCH 29/30] Fix inverted socket id condition. --- src/zmq/authenticator.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index a40e54de..a29c5ee4 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -76,7 +76,7 @@ void authenticator::monitor() const auto socket_id = poller_.wait(polling_interval_milliseconds); // If the id doesn't match the poll is either terminated or expired. - if (socket_id == socket_.id()) + if (socket_id != socket_.id()) continue; data_chunk origin; @@ -200,9 +200,12 @@ void authenticator::monitor() response.enqueue(userid); response.enqueue(metadata); - DEBUG_ONLY(const auto sent = ) response.send(socket_); + DEBUG_ONLY(const auto sent =) response.send(socket_); BITCOIN_ASSERT_MSG(sent, "Failed to send ZAP response."); } + + DEBUG_ONLY(const auto stopped =) socket_.stop(); + BITCOIN_ASSERT_MSG(stopped, "Failed to close socket."); } bool authenticator::allowed(const hash_digest& public_key) const From 24ea50c9e03c874504ac137515af4529c4b2b2ac Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 24 May 2016 20:30:02 -0700 Subject: [PATCH 30/30] Change to 10ms linger, comments, whitespace, no shutdown asserts. --- include/bitcoin/protocol/zmq/socket.hpp | 5 +++-- src/zmq/context.cpp | 6 +++--- src/zmq/frame.cpp | 3 +-- src/zmq/socket.cpp | 24 ++++++++++++------------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/bitcoin/protocol/zmq/socket.hpp b/include/bitcoin/protocol/zmq/socket.hpp index cd9a9fb1..345db2df 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -106,16 +106,17 @@ class BCP_API socket /// Apply the keys of the specified certificate to the socket. bool set_certificate(const certificate& certificate); + /// Close the socket. + bool stop(); + private: static int to_socket_type(role socket_role); bool set(int32_t option, int32_t value); bool set(int32_t option, const std::string& value); - bool destroy(); void* socket_; const int32_t send_buffer_; const int32_t receive_buffer_; - const int32_t linger_milliseconds_; }; } // namespace zmq diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index edec54b2..99ed656c 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -38,8 +38,7 @@ context::context() context::~context() { - DEBUG_ONLY(const auto result =) stop(); - BITCOIN_ASSERT(result); + stop(); } bool context::stop() @@ -48,7 +47,8 @@ bool context::stop() return true; // This aborts blocking operations but blocks here until either each socket - // in the context is explicitly closed or its linger period is exceeded. + // in the context is explicitly closed. + // It is possible for this to fail do to signal interrupt. return zmq_term(self_) != zmq_fail; } diff --git a/src/zmq/frame.cpp b/src/zmq/frame.cpp index 18be671a..6f666250 100644 --- a/src/zmq/frame.cpp +++ b/src/zmq/frame.cpp @@ -46,8 +46,7 @@ frame::frame(const data_chunk& data) frame::~frame() { - DEBUG_ONLY(const auto result =) destroy(); - BITCOIN_ASSERT(result); + destroy(); } // static diff --git a/src/zmq/socket.cpp b/src/zmq/socket.cpp index d4e020e4..7148cd79 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -33,6 +33,7 @@ static constexpr int32_t zmq_true = 1; static constexpr int32_t zmq_fail = -1; static constexpr int32_t zmq_send_buffer = 1000; static constexpr int32_t zmq_receive_buffer = 1000; +static constexpr int32_t zmq_linger_milliseconds = 10; socket::socket() : socket(nullptr) @@ -44,25 +45,26 @@ socket::socket() socket::socket(void* zmq_socket) : socket_(zmq_socket), send_buffer_(zmq_send_buffer), - receive_buffer_(zmq_receive_buffer), - linger_milliseconds_(0) + receive_buffer_(zmq_receive_buffer) { } socket::socket(context& context, role socket_role) - : socket(zmq_socket(context.self(), to_socket_type(socket_role))) + : socket(zmq_socket(context.self(), to_socket_type(socket_role))) { if (socket_ == nullptr) return; - if (!set(ZMQ_SNDHWM, send_buffer_) || !set(ZMQ_RCVHWM, receive_buffer_)) - destroy(); + if (!set(ZMQ_SNDHWM, send_buffer_) || !set(ZMQ_RCVHWM, receive_buffer_) || + !set(ZMQ_LINGER, zmq_linger_milliseconds)) + { + stop(); + } } socket::~socket() { - DEBUG_ONLY(const auto result =) destroy(); - BITCOIN_ASSERT(result); + stop(); } int32_t socket::to_socket_type(role socket_role) @@ -85,22 +87,20 @@ int32_t socket::to_socket_type(role socket_role) } } -bool socket::destroy() +bool socket::stop() { if (socket_ == nullptr) return false; - const auto linger = set(ZMQ_LINGER, linger_milliseconds_); const auto closed = zmq_close(socket_) != zmq_fail; socket_ = nullptr; - - return linger && closed; + return closed; } void socket::assign(socket&& other) { // Free any existing socket resources. - destroy(); + stop(); // Assume ownership of the other socket's state. socket_ = other.socket_;