diff --git a/Makefile.am b/Makefile.am index 2275c047..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 \ @@ -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 @@ -54,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 \ @@ -88,11 +89,8 @@ 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 -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/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/`. 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..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,8 +88,7 @@ - - + @@ -106,8 +105,7 @@ - - + diff --git a/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config b/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config index cb19c869..bc9ddb75 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config +++ b/builds/msvc/vs2013/libbitcoin-protocol-test/packages.config @@ -10,8 +10,7 @@ - - + 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 d9c4d3f1..5bae5ebc 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj @@ -68,7 +68,6 @@ - @@ -80,6 +79,7 @@ + @@ -97,6 +97,7 @@ + @@ -105,8 +106,7 @@ - - + @@ -115,8 +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/libbitcoin-protocol.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-protocol/libbitcoin-protocol.vcxproj.filters index 0bf5e920..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} - @@ -63,6 +57,9 @@ src + + src\zmq + @@ -110,5 +107,8 @@ include\bitcoin\protocol + + include\bitcoin\protocol\zmq + \ No newline at end of file diff --git a/builds/msvc/vs2013/libbitcoin-protocol/packages.config b/builds/msvc/vs2013/libbitcoin-protocol/packages.config index de9dea66..a7439362 100644 --- a/builds/msvc/vs2013/libbitcoin-protocol/packages.config +++ b/builds/msvc/vs2013/libbitcoin-protocol/packages.config @@ -1,8 +1,7 @@  - - + 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.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/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/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/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/include/bitcoin/protocol/zmq/authenticator.hpp b/include/bitcoin/protocol/zmq/authenticator.hpp index 5856d720..c2ae2efc 100644 --- a/include/bitcoin/protocol/zmq/authenticator.hpp +++ b/include/bitcoin/protocol/zmq/authenticator.hpp @@ -20,36 +20,56 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_AUTHENTICATOR_HPP +#include +#include #include -#include +#include #include #include +#include +#include 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 context { public: - authenticator(context& context); - authenticator(const authenticator&) = delete; - ~authenticator(); + /// A shared authenticator pointer. + typedef std::shared_ptr ptr; - operator const bool() const; + /// Start the ZAP monitor for the context. + /// There may be only one authenticator per process (otherwise bridge). + authenticator(threadpool& threadpool); - zauth_t* self(); + /// Start the ZAP monitor. + bool start(); - 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 the following public keys (whitelist). + void allow(const hash_digest& public_key); + + /// Allow clients with the following ip addresses (whitelist). + void allow(const config::authority& address); + + /// Allow clients with the following ip addresses (blacklist). + void deny(const config::authority& address); private: - zauth_t* self_; + void monitor(); + 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. + socket socket_; + poller poller_; + dispatcher dispatch_; + bool require_address_; + std::map keys_; + std::map adresses_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/certificate.hpp b/include/bitcoin/protocol/zmq/certificate.hpp index b6936cf5..9a00b7cd 100644 --- a/include/bitcoin/protocol/zmq/certificate.hpp +++ b/include/bitcoin/protocol/zmq/certificate.hpp @@ -21,39 +21,47 @@ #define LIBBITCOIN_PROTOCOL_ZMQ_CERTIFICATE_HPP #include -#include +#include #include -#include 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 { public: - certificate(); - certificate(zcert_t* self); - certificate(certificate&& other); - certificate(const std::string& filename); - certificate(const certificate&) = delete; - ~certificate(); + /// Construct a new certificate (can we inject randomness). + /// 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 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; - zcert_t* self(); + /// The public key base85 text. + const std::string& public_key() const; + + /// The private key base85 text. + const std::string& private_key() const; - 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); - std::string public_text() const; - void apply(socket& sock); +protected: + 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: - zcert_t* self_; + std::string public_; + std::string private_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/context.hpp b/include/bitcoin/protocol/zmq/context.hpp index 3b38cccb..205e9884 100644 --- a/include/bitcoin/protocol/zmq/context.hpp +++ b/include/bitcoin/protocol/zmq/context.hpp @@ -20,26 +20,45 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_CONTEXT_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_CONTEXT_HPP -#include +#include +#include +#include #include namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. 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. + virtual ~context(); + + /// This class is not copyable. context(const context&) = delete; - ~context(); + 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(); + + /// Stop all socket activity by closing the zeromq context. + bool stop(); private: - zctx_t* self_; + int32_t threads_; + void* self_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/frame.hpp b/include/bitcoin/protocol/zmq/frame.hpp new file mode 100644 index 00000000..5dacd554 --- /dev/null +++ b/include/bitcoin/protocol/zmq/frame.hpp @@ -0,0 +1,91 @@ +/* + * 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 { + +/// This class is not thread safe. +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(); + + /// Construct a frame with the specified payload (for sending). + frame(const data_chunk& data); + + /// Free the frame's allocated memory. + virtual ~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); + +private: + // zmq_msg_t alias, keeps zmq.h out of our headers. + typedef union + { + unsigned char alignment[64]; + void* pointer; + } zmq_msg; + + static bool initialize(zmq_msg& message, const data_chunk& data); + + bool set_more(socket& socket); + bool destroy(); + + bool more_; + const bool valid_; + zmq_msg message_; +}; + +} // namespace zmq +} // namespace protocol +} // namespace libbitcoin + +#endif + 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/message.hpp b/include/bitcoin/protocol/zmq/message.hpp index 6394aee5..2b1f7243 100644 --- a/include/bitcoin/protocol/zmq/message.hpp +++ b/include/bitcoin/protocol/zmq/message.hpp @@ -20,9 +20,8 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_MESSAGE_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_MESSAGE_HPP -#include -#include -#include +#include +#include #include #include @@ -30,19 +29,55 @@ namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. class BCP_API message { public: - void append(const data_chunk& part); - void append(data_chunk&& part); + /// Add an empty message part to the outgoing message. + void enqueue(); - const data_stack& parts() const; + /// Add an interable message part to the outgoing message. + template + void enqueue(const Iterable& value) + { + queue_.emplace(to_chunk(value)); + } - bool send(socket& sock); - bool receive(socket& sock); + /// Add a message part to the outgoing message. + template + void enqueue_little_endian(Unsigned value) + { + enqueue(to_little_endian(value)); + } + + /// Remove a message part from the top of the queue, empty if empty queue. + data_chunk dequeue_data(); + std::string dequeue_text(); + + /// 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 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& socket); + + /// Receve a message (clears the queue first). + bool receive(socket& socket); private: - data_stack parts_; + std::queue queue_; }; } // namespace zmq diff --git a/include/bitcoin/protocol/zmq/poller.hpp b/include/bitcoin/protocol/zmq/poller.hpp index 9cbd59fb..add6ea06 100644 --- a/include/bitcoin/protocol/zmq/poller.hpp +++ b/include/bitcoin/protocol/zmq/poller.hpp @@ -20,7 +20,9 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_POLLER_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_POLLER_HPP -#include +#include +#include +#include #include #include @@ -28,32 +30,52 @@ namespace libbitcoin { namespace protocol { namespace zmq { +/// This class is not thread safe. class BCP_API poller + : public enable_shared_from_base { public: - template - poller(SocketArgs&&... sockets); + /// A shared poller pointer. + typedef std::shared_ptr ptr; + + /// Construct an empty poller (sockets must be added). + poller(); + + /// This class is not copyable. poller(const poller&) = delete; - ~poller(); + void operator=(const poller&) = delete; - 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); - socket wait(int timeout); - bool expired(); - bool terminated(); + + /// Wait specified milliseconds for any socket to receive, -1 is forever. + socket::identifier wait(int32_t timeout_milliseconds); private: - zpoller_t* self_; + // zmq_pollitem_t alias, keeps zmq.h out of our headers. + typedef struct + { + void* socket; + file_descriptor fd; + short events; + short revents; + } zmq_pollitem; + + 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 f0f4ccc4..345db2df 100644 --- a/include/bitcoin/protocol/zmq/socket.hpp +++ b/include/bitcoin/protocol/zmq/socket.hpp @@ -20,40 +20,103 @@ #ifndef LIBBITCOIN_PROTOCOL_ZMQ_SOCKET_HPP #define LIBBITCOIN_PROTOCOL_ZMQ_SOCKET_HPP +#include +#include #include #include +#include #include 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. + enum class role + { + pair, + publisher, + subscriber, + requester, + replier, + dealer, + router, + puller, + pusher, + extended_publisher, + extended_subscriber, + streamer + }; + + /// A shared socket pointer. + typedef std::shared_ptr ptr; + + /// A locally unique idenfitier for this socket. + typedef intptr_t identifier; + socket(); - socket(void* self); - socket(socket&& other); + socket(void* zmq_socket); + socket(context& context, role socket_role); + + /// This class is not const copyable. socket(const socket&) = delete; - socket(context& context, int type); + void operator=(const socket&) = delete; + /// Free socket resources. + virtual ~socket(); + + /// True if there is an encapsultaed zeromq socket. operator const bool() const; - bool operator==(const socket& other) const; - bool operator!=(const socket& other) const; + /// The underlying zeromq socket. void* self(); - void* self() const; - void destroy(context& context); - int bind(const std::string& address); - int connect(const std::string& address); + /// The socket identifier is an opaue correlation idenfier. + identifier id() const; + + /// Transfer ownership of this socket to another. + void assign(socket&& other); + + /// 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); - void set_curve_server(); - void set_curve_serverkey(const std::string& key); - void set_zap_domain(const std::string& domain); + /// 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 the curve server. + bool set_curve_client(const std::string& server_public_key); + + /// Apply the specified public key to the socket. + bool set_public_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); + + /// Close the socket. + bool stop(); 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* socket_; + const int32_t send_buffer_; + const int32_t receive_buffer_; }; } // 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..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@ @@ -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/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/src/zmq/authenticator.cpp b/src/zmq/authenticator.cpp index eb4beccf..a29c5ee4 100644 --- a/src/zmq/authenticator.cpp +++ b/src/zmq/authenticator.cpp @@ -19,64 +19,220 @@ */ #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace libbitcoin { namespace protocol { namespace zmq { -authenticator::authenticator(context& context) - : self_(zauth_new(context.self())) -{ -} +#define NAME "authenticator" + +// ZAP endpoint, see: rfc.zeromq.org/spec:27/ZAP +static const auto zap_endpoint = "inproc://zeromq.zap.01"; + +static constexpr uint32_t polling_interval_milliseconds = 1; -authenticator::~authenticator() +authenticator::authenticator(threadpool& pool) + : socket_(*this, socket::role::router), + dispatch_(pool, NAME), + require_address_(false) { - BITCOIN_ASSERT(self_); - zauth_destroy(&self_); + // 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_); } -authenticator::operator const bool() const +bool authenticator::start() { - return self_ != nullptr; + 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_base())); + + return true; } -zauth_t* authenticator::self() +// github.com/zeromq/rfc/blob/master/src/spec_27.c +void authenticator::monitor() { - return self_; + // 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; + 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 + { + if (mechanism == "NULL") + { + 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 = "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."); + } + + DEBUG_ONLY(const auto stopped =) socket_.stop(); + BITCOIN_ASSERT_MSG(stopped, "Failed to close socket."); } -void authenticator::allow(const std::string& address) +bool authenticator::allowed(const hash_digest& public_key) const { - zauth_allow(self_, address.c_str()); + return !keys_.empty() || keys_.find(public_key) != keys_.end(); } -void authenticator::deny(const std::string& address) +bool authenticator::allowed(const std::string& ip_address) const { - zauth_deny(self_, address.c_str()); + return !require_address_ || adresses_.find(ip_address) != adresses_.end(); } -void authenticator::configure_plain(const std::string& domain, - const std::string& filename) +void authenticator::allow(const hash_digest& public_key) { - zauth_configure_plain(self_, domain.c_str(), filename.c_str()); + keys_.emplace(public_key, true); } -void authenticator::configure_curve(const std::string& domain, - const std::string& location) +void authenticator::allow(const config::authority& address) { - zauth_configure_curve(self_, domain.c_str(), location.c_str()); + require_address_ = true; + adresses_.emplace(address.to_hostname(), true); } -void authenticator::set_verbose(bool verbose) +void authenticator::deny(const config::authority& address) { -#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 + // 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 b1737727..3361a136 100644 --- a/src/zmq/certificate.cpp +++ b/src/zmq/certificate.cpp @@ -20,98 +20,100 @@ #include #include -#include -#include +#include +#include namespace libbitcoin { namespace protocol { namespace zmq { -certificate::certificate() - : self_(zcert_new()) -{ - // May be invalid (unlikely). -} -certificate::certificate(zcert_t* self) - : self_(self) -{ - // May be invalid. -} +static constexpr int32_t zmq_fail = -1; +static constexpr size_t zmq_encoded_key_size = 40; -certificate::certificate(certificate&& other) - : self_(other.self_) +certificate::certificate(bool setting) { - // May be invalid. - // Transfer of pointer ownership. - other.self_ = nullptr; + create(public_, private_, setting); } -certificate::certificate(const std::string& filename) - : self_(zcert_load(filename.c_str())) -{ - // May be invalid. -} -certificate::~certificate() +certificate::certificate(const hash_digest& private_key) { - reset(nullptr); -} + std::string base85_private_key; -certificate::operator const bool() const -{ - return self_ != nullptr; -} + // This cannot fail but we handle anyway. + if (!encode_base85(base85_private_key, private_key)) + return; -void certificate::reset(zcert_t* self) -{ - if (self_ != nullptr) - zcert_destroy(&self_); - - // May be invalid. - self_ = self; + // If we successfully derive the public then set the private. + if (derive(public_, base85_private_key)) + private_ = base85_private_key; } -void certificate::reset(const std::string& filename) +certificate::certificate(const std::string& base85_private_key) { - if (self_ != nullptr) - zcert_destroy(&self_); - - // May be invalid. - self_ = zcert_load(filename.c_str()); + // If we successfully derive the public then set the private. + if (derive(public_, base85_private_key)) + private_ = base85_private_key; } -zcert_t* certificate::self() +bool certificate::derive(std::string& out_public, + const std::string& private_key) { - return self_; -} + if (private_key.size() != zmq_encoded_key_size) + return false; -void certificate::set_meta(const std::string& name, const std::string& value) -{ - zcert_set_meta(self_, name.c_str(), value.c_str()); -} + char public_key[zmq_encoded_key_size + 1] = { 0 }; -int certificate::save(const std::string& filename) -{ - return zcert_save(self_, filename.c_str()); + if (zmq_curve_public(public_key, private_key.data()) == zmq_fail) + return false; + + out_public = public_key; + return true; } -int certificate::save_public(const std::string& filename) +bool certificate::create(std::string& out_public, std::string& out_private, + bool setting) { - return zcert_save_public(self_, filename.c_str()); + // TODO: update settings loader so this isn't necessary. + // BUGBUG: this limitation weakens security by reducing key space. + 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 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 false; + + if (!setting || (ok_setting(public_key) && ok_setting(private_key))) + { + out_public = public_key; + out_private = private_key; + return true; + } + } + + return false; } -int certificate::save_secret(const std::string& filename) +certificate::operator const bool() const { - return zcert_save_secret(self_, filename.c_str()); + return !public_.empty(); } -std::string certificate::public_text() const +const std::string& certificate::public_key() const { - return std::string(zcert_public_txt(self_)); + return public_; } -void certificate::apply(socket& sock) +const std::string& certificate::private_key() const { - zcert_apply(self_, sock.self()); + return private_; } } // namespace zmq diff --git a/src/zmq/context.cpp b/src/zmq/context.cpp index 16a33f3b..99ed656c 100644 --- a/src/zmq/context.cpp +++ b/src/zmq/context.cpp @@ -19,32 +19,37 @@ */ #include -#include +#include +#include #include namespace libbitcoin { namespace protocol { namespace zmq { +static constexpr int32_t zmq_fail = -1; +static constexpr int32_t zmq_io_threads = 1; + context::context() - : self_(zctx_new()) + : threads_(zmq_io_threads), + self_(zmq_init(threads_)) { - // 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 } context::~context() { - BITCOIN_ASSERT(self_); - zctx_destroy(&self_); + stop(); +} + +bool context::stop() +{ + if (self() == nullptr) + return true; + + // This aborts blocking operations but blocks here until either each socket + // in the context is explicitly closed. + // It is possible for this to fail do to signal interrupt. + return zmq_term(self_) != zmq_fail; } context::operator const bool() const @@ -52,7 +57,7 @@ context::operator const bool() const return self_ != nullptr; } -zctx_t* context::self() +void* context::self() { return self_; } diff --git a/src/zmq/frame.cpp b/src/zmq/frame.cpp new file mode 100644 index 00000000..6f666250 --- /dev/null +++ b/src/zmq/frame.cpp @@ -0,0 +1,128 @@ +/* + * 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() + : more_(false), valid_(initialize(message_, {})) +{ +} + +// Use for sending. +frame::frame(const data_chunk& data) + : more_(false), valid_(initialize(message_, data)) +{ +} + +frame::~frame() +{ + destroy(); +} + +// static +bool frame::initialize(zmq_msg& message, const data_chunk& data) +{ + const auto buffer = reinterpret_cast(&message); + + if (data.empty()) + return (zmq_msg_init(buffer) != zmq_fail); + + if (zmq_msg_init_size(buffer, data.size()) == zmq_fail) + return false; + + std::memcpy(zmq_msg_data(buffer), data.data(), data.size()); + return true; +} + +frame::operator const bool() const +{ + return valid_; +} + +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 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 }; +} + +bool frame::receive(socket& socket) +{ + if (!valid_) + return false; + + const auto buffer = reinterpret_cast(&message_); + return zmq_recvmsg(socket.self(), buffer, 0) != zmq_fail && + set_more(socket); +} + +bool frame::send(socket& socket, bool last) +{ + if (!valid_) + return false; + + const int flags = last ? 0 : ZMQ_SNDMORE; + const auto buffer = reinterpret_cast(&message_); + return zmq_sendmsg(socket.self(), buffer, flags) != zmq_fail; +} + +// private +bool frame::destroy() +{ + const auto buffer = reinterpret_cast(&message_); + return valid_ && (zmq_msg_close(buffer) != zmq_fail); +} + +} // namespace zmq +} // namespace protocol +} // namespace libbitcoin diff --git a/src/zmq/message.cpp b/src/zmq/message.cpp index f8c548e6..62ac6faf 100644 --- a/src/zmq/message.cpp +++ b/src/zmq/message.cpp @@ -19,68 +19,150 @@ */ #include -#include +#include #include - -static_assert (sizeof(byte) == sizeof(uint8_t), "Incorrect byte size"); +#include namespace libbitcoin { namespace protocol { namespace zmq { -void message::append(const data_chunk& part) +void message::enqueue() { - parts_.push_back(part); + queue_.emplace(data_chunk{}); } -void message::append(data_chunk&& part) +bool message::dequeue() { - parts_.emplace_back(std::move(part)); + if (queue_.empty()) + return false; + + queue_.pop(); + return true; } -const data_stack& message::parts() const +bool message::dequeue(uint32_t& value) { - return parts_; + 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; } -bool message::send(socket& sock) +bool message::dequeue(data_chunk& value) { - int flags = ZFRAME_MORE; - const auto last = parts_.end(); + if (queue_.empty()) + return false; + + value = dequeue_data(); + return true; +} - for (auto part = parts_.begin(); part != last; ++part) +bool message::dequeue(std::string& value) +{ + if (queue_.empty()) + return false; + + value = dequeue_text(); + return true; +} + +bool message::dequeue(hash_digest& value) +{ + if (queue_.empty()) + return false; + + const auto& front = queue_.front(); + + if (front.size() == hash_size) { - if (part == last - 1) - flags = 0; + 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{}; - auto frame = zframe_new(part->data(), part->size()); + const auto data = queue_.front(); + queue_.pop(); + return data; +} + +std::string message::dequeue_text() +{ + if (queue_.empty()) + return{}; + + const auto& front = queue_.front(); + const auto text = std::string(front.begin(), front.end()); + queue_.pop(); + return text; +} + +void message::clear() +{ + while (!queue_.empty()) + queue_.pop(); +} + +bool message::empty() const +{ + return queue_.empty(); +} + +size_t message::size() const +{ + return queue_.size(); +} + +bool message::send(socket& socket) +{ + auto count = queue_.size(); + + while (!queue_.empty()) + { + frame frame(queue_.front()); + queue_.pop(); - if (zframe_send(&frame, sock.self(), flags) == -1) + if (!frame.send(socket, --count == 0)) return false; } + BITCOIN_ASSERT(queue_.empty()); return true; } -bool message::receive(socket& sock) +bool message::receive(socket& socket) { + clear(); auto done = false; while (!done) { - auto frame = zframe_recv(sock.self()); + frame frame; - if (frame == nullptr) - { - zframe_destroy(&frame); + if (!frame.receive(socket)) 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); + queue_.emplace(frame.payload()); + done = !frame.more(); } return true; diff --git a/src/zmq/poller.cpp b/src/zmq/poller.cpp index 26718e26..09fe3195 100644 --- a/src/zmq/poller.cpp +++ b/src/zmq/poller.cpp @@ -19,7 +19,8 @@ */ #include -#include +#include +#include #include #include @@ -27,41 +28,66 @@ 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 +// Parameter fd is non-zmq socket (unused when socket is set). +void poller::add(socket& socket) { - return self_ != nullptr; + zmq_pollitem item; + item.socket = socket.self(); + item.fd = 0; + item.events = ZMQ_POLLIN; + item.revents = 0; + pollers_.push_back(item); } -zpoller_t* poller::self() -{ - return self_; -} +// 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 +// actually less (potentially 1000 or 1 second) on other platforms. +// And on non-windows platforms negative doesn't actually produce infinity. -void poller::add(socket& sock) +// 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) { - zpoller_add(self_, sock.self()); -} + const auto size = pollers_.size(); + BITCOIN_ASSERT(size <= max_int32); -socket poller::wait(int timeout) -{ - auto sock_ptr = zpoller_wait(self_, timeout); - return socket(sock_ptr); + const auto size32 = static_cast(size); + const auto data = reinterpret_cast(pollers_.data()); + auto signaled = zmq_poll(data, size32, timeout_milliseconds); + + if (signaled < 0) + { + terminated_ = true; + return 0; + } + + if (signaled == 0) + { + expired_ = true; + return 0; + } + + for (const auto& poller: pollers_) + if ((poller.revents & ZMQ_POLLIN) != 0) + return reinterpret_cast(poller.socket); + + return 0; } -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/src/zmq/socket.cpp b/src/zmq/socket.cpp index 543a118d..7148cd79 100644 --- a/src/zmq/socket.cpp +++ b/src/zmq/socket.cpp @@ -19,94 +19,164 @@ */ #include -#include +#include +#include +#include #include +#include namespace libbitcoin { namespace protocol { namespace zmq { +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() - : self_(nullptr) + : socket(nullptr) { } -socket::socket(void* self) - : self_(self) +// 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) { } -socket::socket(socket&& other) +socket::socket(context& context, role socket_role) + : socket(zmq_socket(context.self(), to_socket_type(socket_role))) { - BITCOIN_ASSERT(self_ == nullptr); - self_ = other.self_; - other.self_ = nullptr; + if (socket_ == nullptr) + return; + + if (!set(ZMQ_SNDHWM, send_buffer_) || !set(ZMQ_RCVHWM, receive_buffer_) || + !set(ZMQ_LINGER, zmq_linger_milliseconds)) + { + stop(); + } } -socket::socket(context& context, int type) +socket::~socket() { - self_ = zsocket_new(context.self(), type); + stop(); } -socket::operator const bool() const +int32_t socket::to_socket_type(role socket_role) { - return self_ != nullptr; + 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::operator==(const socket& other) const +bool socket::stop() { - return self_ == other.self_; + if (socket_ == nullptr) + return false; + + const auto closed = zmq_close(socket_) != zmq_fail; + socket_ = nullptr; + return closed; } -bool socket::operator!=(const socket& other) const +void socket::assign(socket&& other) { - return !(*this == other); + // Free any existing socket resources. + stop(); + + // Assume ownership of the other socket's state. + socket_ = other.socket_; + + // Don't destroy other socket's resource as it would destroy ours. + other.socket_ = nullptr; } -void* socket::self() +bool socket::bind(const std::string& address) { - return self_; + return zmq_bind(socket_, address.c_str()) != zmq_fail; } -void* socket::self() const +bool socket::connect(const std::string& address) { - return self_; + return zmq_connect(socket_, address.c_str()) != zmq_fail; } -void socket::destroy(context& context) +bool socket::set(int32_t option, int32_t value) { - BITCOIN_ASSERT(self_); - zsocket_destroy(context.self(), self_); + return zmq_setsockopt(socket_, option, &value, sizeof(value)) != zmq_fail; } -// format-security: format not a string literal and no format arguments. -int socket::bind(const std::string& address) +bool socket::set(int32_t option, const std::string& value) { - return zsocket_bind(self_, address.c_str()); + 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); } -// format-security: format not a string literal and no format arguments. -int socket::connect(const std::string& address) +bool socket::set_curve_server() { - static constexpr int zmq_no_linger = 0; + return set(ZMQ_CURVE_SERVER, zmq_true); +} - zsocket_set_linger(self_, zmq_no_linger); - return zsocket_connect(self_, address.c_str()); +bool socket::set_curve_client(const std::string& server_public_key) +{ + return set(ZMQ_CURVE_SERVERKEY, server_public_key); } -void socket::set_curve_server() +bool socket::set_public_key(const std::string& key) { - zsocket_set_curve_server(self_, 1); + return set(ZMQ_CURVE_PUBLICKEY, key); } -void socket::set_curve_serverkey(const std::string& key) +bool socket::set_private_key(const std::string& key) { - zsocket_set_curve_serverkey(self_, key.c_str()); + return set(ZMQ_CURVE_SECRETKEY, key); } -void socket::set_zap_domain(const std::string& domain) +bool socket::set_certificate(const certificate& certificate) +{ + return certificate && set_public_key(certificate.public_key()) && + set_private_key(certificate.private_key()); +} + +void* socket::self() +{ + return socket_; +} + +socket::identifier socket::id() const +{ + return reinterpret_cast(socket_); +} + +socket::operator const bool() const { - zsocket_set_zap_domain(self_, domain.c_str()); + return socket_ != nullptr; } } // namespace zmq 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 diff --git a/test/zmq/ironhouse2.cpp b/test/zmq/ironhouse2.cpp index 20b091f5..490ec015 100644 --- a/test/zmq/ironhouse2.cpp +++ b/test/zmq/ironhouse2.cpp @@ -19,94 +19,101 @@ */ #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 server_task(const std::string& server_private_key, + const std::string& client_public_key, + const config::authority& client_address) +{ + // Create a threadpool for the authenticator. + threadpool threadpool(1); + + // Establish the context's authentication whitelist. + zmq::authenticator authenticator(threadpool); + authenticator.allow(client_address); + authenticator.allow(client_public_key); + + // 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. + auto result = server.set_private_key(server_private_key); + assert(result); + result = server.set_curve_server(); + assert(result); + + // Bind the server to a tcp port on all local addresses. + result = server.bind("tcp://*:9000"); + assert(result); + + // Send the test message. + zmq::message message; + message.enqueue("helllo world!"); + result = message.send(server); + assert(result); + + // 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 an unauthenticated context for the client. + zmq::context context; + assert(context); + + // Bind a pull socket to the client context. + zmq::socket client(context, zmq::socket::role::puller); + assert(client); + + // 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); + + // Wait for the message, which signals the test was successful. + zmq::message message; + result = message.receive(client); + assert(result); + assert(message.dequeue_text() == "helllo world!"); + + puts("Ironhouse test OK"); +} + +int ironhouse2_example() +{ + static const auto localhost = config::authority("127.0.0.1"); + + // Create client and server certificates (generated secrets). + zmq::certificate client_cert; + 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 a client, allows 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; +} diff --git a/test/zmq/poller.cpp b/test/zmq/poller.cpp index 404e76bc..2e415e14 100644 --- a/test/zmq/poller.cpp +++ b/test/zmq/poller.cpp @@ -18,46 +18,57 @@ * along with this program. If not, see . */ #include -#include -#include #include using namespace bc; using namespace bc::protocol; -int main_disabled() +int poller_example() { zmq::context context; assert(context); // Create a few sockets - zmq::socket vent(context, ZMQ_PUSH); - int rc = vent.bind("tcp://*:9000"); - assert(rc != -1); + zmq::socket vent(context, zmq::socket::role::pusher); + auto result = vent.bind("tcp://*:9000"); + assert(result); - zmq::socket sink(context, ZMQ_PULL); - rc = sink.connect("tcp://localhost:9000"); - assert(rc != -1); + zmq::socket sink(context, zmq::socket::role::puller); + result = sink.connect("tcp://localhost:9000"); + assert(result); - 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(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); + const std::string hello = "Hello, World"; + + // Build and send the message. + zmq::message message; + message.enqueue(hello); + result = message.send(vent); + assert(result); // 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()); - assert(streq(message, "Hello, World")); + // Receive the message. + result = message.receive(sink); + assert(result); + + // Check the size. + assert(message.size() == 1); - free(message); + // Check the value. + const auto payload = message.dequeue_text(); + assert(payload == hello); return 0; }