From e1bed0dd55e15dd8b52f0eec40e0c50925b8a7b2 Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Wed, 7 Feb 2018 12:52:28 -0500 Subject: [PATCH 01/25] Implement a simple HTTP(s) and Websocket stack/interface for libbitcoin subscriptions and query services, which extend existing zeromq services. Allow mbedtls to be conditionally compiled, which allows optional SSL support. Re-generate build artifacts. --- .gitignore | 4 + Makefile.am | 22 + .../libbitcoin-server.vcxproj | 19 + .../libbitcoin-server.vcxproj.filters | 84 +- .../libbitcoin-server.vcxproj | 19 + .../libbitcoin-server.vcxproj.filters | 84 +- .../libbitcoin-server.vcxproj | 19 + .../libbitcoin-server.vcxproj.filters | 84 +- data/bs.cfg | 99 +- include/bitcoin/server.hpp | 6 + include/bitcoin/server/define.hpp | 3 +- include/bitcoin/server/server_node.hpp | 18 + include/bitcoin/server/settings.hpp | 62 +- include/bitcoin/server/web/block_socket.hpp | 58 + .../bitcoin/server/web/heartbeat_socket.hpp | 62 + include/bitcoin/server/web/json_string.hpp | 46 + include/bitcoin/server/web/query_socket.hpp | 72 + include/bitcoin/server/web/socket.hpp | 170 +++ .../bitcoin/server/web/transaction_socket.hpp | 63 + .../server/workers/notification_worker.hpp | 2 +- src/parser.cpp | 248 +++- src/server_node.cpp | 80 +- src/services/block_service.cpp | 4 +- src/services/heartbeat_service.cpp | 2 +- src/services/query_service.cpp | 2 +- src/services/transaction_service.cpp | 4 +- src/settings.cpp | 90 +- src/web/block_socket.cpp | 153 ++ src/web/heartbeat_socket.cpp | 145 ++ src/web/http/connection.cpp | 414 ++++++ src/web/http/connection.hpp | 123 ++ src/web/http/http.hpp | 369 +++++ src/web/http/manager.cpp | 1315 +++++++++++++++++ src/web/http/manager.hpp | 117 ++ src/web/http/utilities.cpp | 392 +++++ src/web/http/utilities.hpp | 57 + src/web/json_string.cpp | 119 ++ src/web/query_socket.cpp | 406 +++++ src/web/socket.cpp | 538 +++++++ src/web/transaction_socket.cpp | 151 ++ src/workers/authenticator.cpp | 4 +- src/workers/notification_worker.cpp | 4 +- 42 files changed, 5589 insertions(+), 144 deletions(-) create mode 100644 include/bitcoin/server/web/block_socket.hpp create mode 100644 include/bitcoin/server/web/heartbeat_socket.hpp create mode 100644 include/bitcoin/server/web/json_string.hpp create mode 100644 include/bitcoin/server/web/query_socket.hpp create mode 100644 include/bitcoin/server/web/socket.hpp create mode 100644 include/bitcoin/server/web/transaction_socket.hpp create mode 100644 src/web/block_socket.cpp create mode 100644 src/web/heartbeat_socket.cpp create mode 100644 src/web/http/connection.cpp create mode 100644 src/web/http/connection.hpp create mode 100644 src/web/http/http.hpp create mode 100644 src/web/http/manager.cpp create mode 100644 src/web/http/manager.hpp create mode 100644 src/web/http/utilities.cpp create mode 100644 src/web/http/utilities.hpp create mode 100644 src/web/json_string.cpp create mode 100644 src/web/query_socket.cpp create mode 100644 src/web/socket.cpp create mode 100644 src/web/transaction_socket.cpp diff --git a/.gitignore b/.gitignore index 7b8c2497..8a45f518 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ libobelisk.pc /src/bitcoin_server +build-libbitcoin-server +console/bs +libbitcoin-server.pc + diff --git a/Makefile.am b/Makefile.am index fdeb0dac..f8a42860 100644 --- a/Makefile.am +++ b/Makefile.am @@ -50,6 +50,19 @@ src_libbitcoin_server_la_SOURCES = \ src/services/heartbeat_service.cpp \ src/services/query_service.cpp \ src/services/transaction_service.cpp \ + src/web/block_socket.cpp \ + src/web/heartbeat_socket.cpp \ + src/web/json_string.cpp \ + src/web/query_socket.cpp \ + src/web/socket.cpp \ + src/web/transaction_socket.cpp \ + src/web/http/connection.cpp \ + src/web/http/connection.hpp \ + src/web/http/http.hpp \ + src/web/http/manager.cpp \ + src/web/http/manager.hpp \ + src/web/http/utilities.cpp \ + src/web/http/utilities.hpp \ src/workers/authenticator.cpp \ src/workers/notification_worker.cpp \ src/workers/query_worker.cpp @@ -119,6 +132,15 @@ include_bitcoin_server_services_HEADERS = \ include/bitcoin/server/services/query_service.hpp \ include/bitcoin/server/services/transaction_service.hpp +include_bitcoin_server_webdir = ${includedir}/bitcoin/server/web +include_bitcoin_server_web_HEADERS = \ + include/bitcoin/server/web/block_socket.hpp \ + include/bitcoin/server/web/heartbeat_socket.hpp \ + include/bitcoin/server/web/json_string.hpp \ + include/bitcoin/server/web/query_socket.hpp \ + include/bitcoin/server/web/socket.hpp \ + include/bitcoin/server/web/transaction_socket.hpp + include_bitcoin_server_workersdir = ${includedir}/bitcoin/server/workers include_bitcoin_server_workers_HEADERS = \ include/bitcoin/server/workers/authenticator.hpp \ diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj index 41b3dfee..c7b45f96 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj @@ -88,6 +88,15 @@ + + + + + + + + + @@ -111,9 +120,19 @@ + + + + + + + + + + diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters index 4e0bf3e3..e7473630 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -8,28 +8,31 @@ - {73CE0AC2-ECB2-4E8D-0000-000000000005} + {73CE0AC2-ECB2-4E8D-0000-000000000007} - {73CE0AC2-ECB2-4E8D-0000-000000000006} + {73CE0AC2-ECB2-4E8D-0000-000000000008} - {73CE0AC2-ECB2-4E8D-0000-000000000007} + {73CE0AC2-ECB2-4E8D-0000-000000000009} - {73CE0AC2-ECB2-4E8D-0000-000000000008} + {73CE0AC2-ECB2-4E8D-0000-00000000000A} - {73CE0AC2-ECB2-4E8D-0000-000000000009} + {73CE0AC2-ECB2-4E8D-0000-00000000000B} - {73CE0AC2-ECB2-4E8D-0000-00000000000A} + {73CE0AC2-ECB2-4E8D-0000-00000000000C} + + + {73CE0AC2-ECB2-4E8D-0000-00000000000D} - {73CE0AC2-ECB2-4E8D-0000-00000000000B} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} - {73CE0AC2-ECB2-4E8D-0000-00000000000C} + {73CE0AC2-ECB2-4E8D-0000-00000000000F} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -43,9 +46,15 @@ {73CE0AC2-ECB2-4E8D-0000-000000000003} - + {73CE0AC2-ECB2-4E8D-0000-000000000004} + + {73CE0AC2-ECB2-4E8D-0000-000000000006} + + + {73CE0AC2-ECB2-4E8D-0000-000000000005} + @@ -93,6 +102,33 @@ src + + src\web + + + src\web + + + src\web\http + + + src\web\http + + + src\web\http + + + src\web + + + src\web + + + src\web + + + src\web + src\workers @@ -158,6 +194,24 @@ include\bitcoin\server + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + include\bitcoin\server\workers @@ -167,6 +221,18 @@ include\bitcoin\server\workers + + src\web\http + + + src\web\http + + + src\web\http + + + src\web\http + resource diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj index 1c6659e4..afdab21d 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj @@ -88,6 +88,15 @@ + + + + + + + + + @@ -111,9 +120,19 @@ + + + + + + + + + + diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters index 9c1fe038..f04ca5e7 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -8,28 +8,31 @@ - {73CE0AC2-ECB2-4E8D-0000-000000000005} + {73CE0AC2-ECB2-4E8D-0000-000000000007} - {73CE0AC2-ECB2-4E8D-0000-000000000006} + {73CE0AC2-ECB2-4E8D-0000-000000000008} - {73CE0AC2-ECB2-4E8D-0000-000000000007} + {73CE0AC2-ECB2-4E8D-0000-000000000009} - {73CE0AC2-ECB2-4E8D-0000-000000000008} + {73CE0AC2-ECB2-4E8D-0000-00000000000A} - {73CE0AC2-ECB2-4E8D-0000-000000000009} + {73CE0AC2-ECB2-4E8D-0000-00000000000B} - {73CE0AC2-ECB2-4E8D-0000-00000000000A} + {73CE0AC2-ECB2-4E8D-0000-00000000000C} + + + {73CE0AC2-ECB2-4E8D-0000-00000000000D} - {73CE0AC2-ECB2-4E8D-0000-00000000000B} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} - {73CE0AC2-ECB2-4E8D-0000-00000000000C} + {73CE0AC2-ECB2-4E8D-0000-00000000000F} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -43,9 +46,15 @@ {73CE0AC2-ECB2-4E8D-0000-000000000003} - + {73CE0AC2-ECB2-4E8D-0000-000000000004} + + {73CE0AC2-ECB2-4E8D-0000-000000000006} + + + {73CE0AC2-ECB2-4E8D-0000-000000000005} + @@ -93,6 +102,33 @@ src + + src\web + + + src\web + + + src\web\http + + + src\web\http + + + src\web\http + + + src\web + + + src\web + + + src\web + + + src\web + src\workers @@ -158,6 +194,24 @@ include\bitcoin\server + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + include\bitcoin\server\workers @@ -167,6 +221,18 @@ include\bitcoin\server\workers + + src\web\http + + + src\web\http + + + src\web\http + + + src\web\http + resource diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj index 59c779cf..75bd4d3b 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj @@ -88,6 +88,15 @@ + + + + + + + + + @@ -111,9 +120,19 @@ + + + + + + + + + + diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters index 3956c781..7e43dc9a 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -8,28 +8,31 @@ - {73CE0AC2-ECB2-4E8D-0000-000000000005} + {73CE0AC2-ECB2-4E8D-0000-000000000007} - {73CE0AC2-ECB2-4E8D-0000-000000000006} + {73CE0AC2-ECB2-4E8D-0000-000000000008} - {73CE0AC2-ECB2-4E8D-0000-000000000007} + {73CE0AC2-ECB2-4E8D-0000-000000000009} - {73CE0AC2-ECB2-4E8D-0000-000000000008} + {73CE0AC2-ECB2-4E8D-0000-00000000000A} - {73CE0AC2-ECB2-4E8D-0000-000000000009} + {73CE0AC2-ECB2-4E8D-0000-00000000000B} - {73CE0AC2-ECB2-4E8D-0000-00000000000A} + {73CE0AC2-ECB2-4E8D-0000-00000000000C} + + + {73CE0AC2-ECB2-4E8D-0000-00000000000D} - {73CE0AC2-ECB2-4E8D-0000-00000000000B} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} - {73CE0AC2-ECB2-4E8D-0000-00000000000C} + {73CE0AC2-ECB2-4E8D-0000-00000000000F} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -43,9 +46,15 @@ {73CE0AC2-ECB2-4E8D-0000-000000000003} - + {73CE0AC2-ECB2-4E8D-0000-000000000004} + + {73CE0AC2-ECB2-4E8D-0000-000000000006} + + + {73CE0AC2-ECB2-4E8D-0000-000000000005} + @@ -93,6 +102,33 @@ src + + src\web + + + src\web + + + src\web\http + + + src\web\http + + + src\web\http + + + src\web + + + src\web + + + src\web + + + src\web + src\workers @@ -158,6 +194,24 @@ include\bitcoin\server + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + + + include\bitcoin\server\web + include\bitcoin\server\workers @@ -167,6 +221,18 @@ include\bitcoin\server\workers + + src\web\http + + + src\web\http + + + src\web\http + + + src\web\http + resource diff --git a/data/bs.cfg b/data/bs.cfg index bd57be0b..aba293f9 100644 --- a/data/bs.cfg +++ b/data/bs.cfg @@ -233,9 +233,9 @@ send_high_water = 100 receive_high_water = 100 # The time limit to complete the connection handshake, defaults to 30. handshake_seconds = 30 -# Disable public endpoints, defaults to false. +# Disable all public endpoints, defaults to false. secure_only = false -# The number of query worker threads per endpoint, defaults to 1 (0 disables service). +# The number of query worker threads, defaults to 1 (0 disables service). query_workers = 1 # The maximum number of query subscriptions, defaults to 1000 (0 disables subscribe). subscription_limit = 1000 @@ -247,30 +247,97 @@ heartbeat_service_seconds = 5 block_service_enabled = true # Enable the transaction publishing service, defaults to true. transaction_service_enabled = true +# Allowed client IP address, multiple entries allowed. +#client_address = 127.0.0.1 +# Blocked client IP address, multiple entries allowed. +#blacklist = 127.0.0.1 + +[websockets] +# The secure query websocket endpoint, defaults to 'tcp://*:9061'. +secure_query_endpoint = tcp://*:9061 +# The secure heartbeat websocket endpoint, defaults to 'tcp://*:9062'. +secure_heartbeat_endpoint = tcp://*:9062 +# The secure block publishing websocket endpoint, defaults to 'tcp://*:9063'. +secure_block_endpoint = tcp://*:9063 +# The secure transaction publishing websocket endpoint, defaults to 'tcp://*:9064'. +secure_transaction_endpoint = tcp://*:9064 +# The public query websocket endpoint, defaults to 'tcp://*:9071'. +public_query_endpoint = tcp://*:9071 +# The public heartbeat websocket endpoint, defaults to 'tcp://*:9072'. +public_heartbeat_endpoint = tcp://*:9072 +# The public block publishing websocket endpoint, defaults to 'tcp://*:9073'. +public_block_endpoint = tcp://*:9073 +# The public transaction publishing websocket endpoint, defaults to 'tcp://*:9074'. +public_transaction_endpoint = tcp://*:9074 +# Enable websocket endpoints, defaults to true. +enabled = true +# The optional directory for serving files via HTTP/S, defaults to 'web'. +root = web +# The SSL certificate authority file, defaults to 'ca.pem', enables secure endpoints. +ca_certificate = ca.pem +# The SSL private key file, defaults to 'key.pem', enables secure endpoints. +server_private_key = key.pem +# The SSL certificate file, defaults to 'server.pem', enables secure endpoints. +server_certificate = server.pem +# The SSL client certificates directory, defaults to 'clients'. +client_certificates = clients -# The secure query endpoint, defaults to 'tcp://*:9081'. +[zeromq] +# The secure query zeromq endpoint, defaults to 'tcp://*:9081'. secure_query_endpoint = tcp://*:9081 -# The secure heartbeat endpoint, defaults to 'tcp://*:9082'. +# The secure heartbeat zeromq endpoint, defaults to 'tcp://*:9082'. secure_heartbeat_endpoint = tcp://*:9082 -# The secure block publishing endpoint, defaults to 'tcp://*:9083'. +# The secure block publishing zeromq endpoint, defaults to 'tcp://*:9083'. secure_block_endpoint = tcp://*:9083 -# The secure transaction publishing endpoint, defaults to 'tcp://*:9084'. +# The secure transaction publishing zeromq endpoint, defaults to 'tcp://*:9084'. secure_transaction_endpoint = tcp://*:9084 - -# The public query endpoint, defaults to 'tcp://*:9091'. +# The public query zeromq endpoint, defaults to 'tcp://*:9091'. public_query_endpoint = tcp://*:9091 -# The public heartbeat endpoint, defaults to 'tcp://*:9092'. +# The public heartbeat zeromq endpoint, defaults to 'tcp://*:9092'. public_heartbeat_endpoint = tcp://*:9092 -# The public block publishing endpoint, defaults to 'tcp://*:9093'. +# The public block publishing zeromq endpoint, defaults to 'tcp://*:9093'. public_block_endpoint = tcp://*:9093 -# The public transaction publishing endpoint, defaults to 'tcp://*:9094'. +# The public transaction publishing zeromq endpoint, defaults to 'tcp://*:9094'. public_transaction_endpoint = tcp://*:9094 - # The Z85-encoded private key of the server, enables secure endpoints. #server_private_key = # Allowed Z85-encoded public key of the client, multiple entries allowed. #client_public_key = -# Allowed client IP address, multiple entries allowed. -#client_address = 127.0.0.1 -# Blocked client IP address, multiple entries allowed. -#blacklist = 127.0.0.1 + +[bitcoin] +# The difficulty retargeting factor, defaults to 4. +retargeting_factor = 4 +# The target block period in seconds, defaults to 600. +block_spacing_seconds = 600 +# The future timestamp allowance in seconds, defaults to 7200. +timestamp_limit_seconds = 7200 +# The difficulty retargeting period in seconds, defaults to 1209600. +retargeting_interval_seconds = 1209600 +# The proof of work limit, defaults to 486604799. +proof_of_work_limit = 486604799 +# The genesis block (defaults to mainnet). +genesis_block = 0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000 +# The number of new version blocks required for bip34 style soft fork activation, defaults to 750. +activation_threshold = 750 +# The number of new version blocks required for bip34 style soft fork enforcement, defaults to 950. +enforcement_threshold = 950 +# The number of blocks considered for bip34 style soft fork activation, defaults to 1000. +activation_sample = 1000 +# The block height to freeze the bip65 softfork as in bip90, defaults to 388381. +bip65_freeze = 388381 +# The block height to freeze the bip66 softfork as in bip90, defaults to 363725. +bip66_freeze = 363725 +# The block height to freeze the bip34 softfork as in bip90, defaults to 227931. +bip34_freeze = 227931 +# The activation time for bip16 in unix time, defaults to 1333238400. +bip16_activation_time = 1333238400 +# The hash:height checkpoint for bip34 activation, defaults to 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8:227931. +bip34_active_checkpoint = 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8:227931 +# The hash:height checkpoint for bip9 bit0 activation, defaults to 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5:419328. +bip9_bit0_active_checkpoint = 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5:419328 +# The hash:height checkpoint for bip9 bit1 activation, defaults to 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893:481824. +bip9_bit1_active_checkpoint = 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893:481824 +# The initial block subsidy in bitcoin, defaults to 50. +initial_block_subsidy_bitcoin = 50 +# The subsidy halving period in number of blocks, defaults to 210000. +subsidy_interval = 210000 diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index 615cf502..2694f784 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -33,6 +33,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include diff --git a/include/bitcoin/server/define.hpp b/include/bitcoin/server/define.hpp index ecd28b9a..7160dd5f 100644 --- a/include/bitcoin/server/define.hpp +++ b/include/bitcoin/server/define.hpp @@ -39,11 +39,12 @@ // Log name. #define LOG_SERVER "server" +#define LOG_SERVER_HTTP "http" // Avoid namespace conflict between boost::placeholders and std::placeholders. #define BOOST_BIND_NO_PLACEHOLDERS -// Include boost only here, so placeholders exclusion works. +#include #include #include #include diff --git a/include/bitcoin/server/server_node.hpp b/include/bitcoin/server/server_node.hpp index 7c5bce53..1368608a 100644 --- a/include/bitcoin/server/server_node.hpp +++ b/include/bitcoin/server/server_node.hpp @@ -31,6 +31,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -109,6 +113,8 @@ class BCS_API server_node authenticator authenticator_; query_service secure_query_service_; query_service public_query_service_; + + // Zeromq services heartbeat_service secure_heartbeat_service_; heartbeat_service public_heartbeat_service_; block_service secure_block_service_; @@ -117,6 +123,18 @@ class BCS_API server_node transaction_service public_transaction_service_; notification_worker secure_notification_worker_; notification_worker public_notification_worker_; + + // Websocket services +#ifdef WITH_MBEDTLS + query_socket secure_query_websockets_; + heartbeat_socket secure_heartbeat_websockets_; + block_socket secure_block_websockets_; + transaction_socket secure_transaction_websockets_; +#endif + query_socket public_query_websockets_; + heartbeat_socket public_heartbeat_websockets_; + block_socket public_block_websockets_; + transaction_socket public_transaction_websockets_; }; } // namespace server diff --git a/include/bitcoin/server/settings.hpp b/include/bitcoin/server/settings.hpp index 9112f71c..a14586b0 100644 --- a/include/bitcoin/server/settings.hpp +++ b/include/bitcoin/server/settings.hpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -30,7 +29,7 @@ namespace libbitcoin { namespace server { -/// Common database configuration settings, properties not thread safe. +/// Common server configuration settings, properties not thread safe. class BCS_API settings { public: @@ -40,36 +39,59 @@ class BCS_API settings /// Helpers. system::asio::duration heartbeat_interval() const; system::asio::duration subscription_expiration() const; - const system::config::endpoint& query_endpoint(bool secure) const; - const system::config::endpoint& heartbeat_endpoint(bool secure) const; - const system::config::endpoint& block_endpoint(bool secure) const; - const system::config::endpoint& transaction_endpoint(bool secure) const; + const system::config::endpoint& zeromq_query_endpoint(bool secure) const; + const system::config::endpoint& zeromq_heartbeat_endpoint(bool secure) const; + const system::config::endpoint& zeromq_block_endpoint(bool secure) const; + const system::config::endpoint& zeromq_transaction_endpoint(bool secure) const; - /// Properties. + const system::config::endpoint& websockets_query_endpoint(bool secure) const; + const system::config::endpoint& websockets_heartbeat_endpoint(bool secure) const; + const system::config::endpoint& websockets_block_endpoint(bool secure) const; + const system::config::endpoint& websockets_transaction_endpoint(bool secure) const; + + /// [server] bool priority; bool secure_only; - uint16_t query_workers; uint32_t subscription_limit; uint32_t subscription_expiration_minutes; uint32_t heartbeat_service_seconds; bool block_service_enabled; bool transaction_service_enabled; + system::config::authority::list client_addresses; + system::config::authority::list blacklists; - system::config::endpoint secure_query_endpoint; - system::config::endpoint secure_heartbeat_endpoint; - system::config::endpoint secure_block_endpoint; - system::config::endpoint secure_transaction_endpoint; + /// [websockets] + system::config::endpoint websockets_secure_query_endpoint; + system::config::endpoint websockets_secure_heartbeat_endpoint; + system::config::endpoint websockets_secure_block_endpoint; + system::config::endpoint websockets_secure_transaction_endpoint; - system::config::endpoint public_query_endpoint; - system::config::endpoint public_heartbeat_endpoint; - system::config::endpoint public_block_endpoint; - system::config::endpoint public_transaction_endpoint; + system::config::endpoint websockets_public_query_endpoint; + system::config::endpoint websockets_public_heartbeat_endpoint; + system::config::endpoint websockets_public_block_endpoint; + system::config::endpoint websockets_public_transaction_endpoint; - system::config::sodium server_private_key; - system::config::sodium::list client_public_keys; - system::config::authority::list client_addresses; - system::config::authority::list blacklists; + bool websockets_enabled; + boost::filesystem::path websockets_root; + boost::filesystem::path websockets_ca_certificate; + boost::filesystem::path websockets_server_private_key; + boost::filesystem::path websockets_server_certificate; + boost::filesystem::path websockets_client_certificates; + + /// [zeromq] + system::config::endpoint zeromq_secure_query_endpoint; + system::config::endpoint zeromq_secure_heartbeat_endpoint; + system::config::endpoint zeromq_secure_block_endpoint; + system::config::endpoint zeromq_secure_transaction_endpoint; + + system::config::endpoint zeromq_public_query_endpoint; + system::config::endpoint zeromq_public_heartbeat_endpoint; + system::config::endpoint zeromq_public_block_endpoint; + system::config::endpoint zeromq_public_transaction_endpoint; + + system::config::sodium zeromq_server_private_key; + system::config::sodium::list zeromq_client_public_keys; }; } // namespace server diff --git a/include/bitcoin/server/web/block_socket.hpp b/include/bitcoin/server/web/block_socket.hpp new file mode 100644 index 00000000..026e153d --- /dev/null +++ b/include/bitcoin/server/web/block_socket.hpp @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_BLOCK_SOCKET_HPP +#define LIBBITCOIN_SERVER_WEB_BLOCK_SOCKET_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace server { + +class server_node; + +// This class is thread safe. +// Subscribe to block acceptances into the long chain from a dedicated +// socket endpoint. +class BCS_API block_socket + : public socket +{ +public: + typedef std::shared_ptr ptr; + + /// Construct a block socket service endpoint. + block_socket(bc::protocol::zmq::authenticator& authenticator, + server_node& node, bool secure); + +protected: + // Implement the service. + virtual void work() override; + + virtual const config::endpoint& zeromq_endpoint() const override; + virtual const config::endpoint& websocket_endpoint() const override; + +private: + bool handle_block(bc::protocol::zmq::socket& subscriber); +}; + +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/heartbeat_socket.hpp b/include/bitcoin/server/web/heartbeat_socket.hpp new file mode 100644 index 00000000..3b2b7162 --- /dev/null +++ b/include/bitcoin/server/web/heartbeat_socket.hpp @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HEARTBEAT_SOCKET_HPP +#define LIBBITCOIN_SERVER_WEB_HEARTBEAT_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { + +class server_node; + +// This class is thread safe. +// Subscribe to a pulse from a dedicated socket endpoint. +class BCS_API heartbeat_socket + : public socket +{ +public: + typedef std::shared_ptr ptr; + + /// Construct a heartbeat socket service endpoint. + heartbeat_socket(bc::protocol::zmq::authenticator& authenticator, + server_node& node, bool secure); + +protected: + + // Implement the service. + virtual void work() override; + + virtual const config::endpoint& zeromq_endpoint() const override; + virtual const config::endpoint& websocket_endpoint() const override; + +private: + bool handle_heartbeat(bc::protocol::zmq::socket& subscriber); +}; + +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/json_string.hpp b/include/bitcoin/server/web/json_string.hpp new file mode 100644 index 00000000..37d7ac8b --- /dev/null +++ b/include/bitcoin/server/web/json_string.hpp @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_JSON_STRING_HPP +#define LIBBITCOIN_SERVER_WEB_JSON_STRING_HPP + +#include +#include + +namespace libbitcoin { +namespace server { +namespace web { + +// Object to JSON converters. +//----------------------------------------------------------------------------- + +std::string to_json(const boost::property_tree::ptree& tree); +std::string to_json(uint64_t height, uint32_t id); +std::string to_json(const int code, const std::string message, uint32_t id); +std::string to_json(const std::error_code& code, uint32_t id); +std::string to_json(const bc::chain::header& header, uint32_t id); +std::string to_json(const bc::chain::block& block, uint32_t id); +std::string to_json(const bc::chain::block& block, uint32_t height, + uint32_t id); +std::string to_json(const bc::chain::transaction& transaction, uint32_t id); + +} // namespace web +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/query_socket.hpp b/include/bitcoin/server/web/query_socket.hpp new file mode 100644 index 00000000..48c9afde --- /dev/null +++ b/include/bitcoin/server/web/query_socket.hpp @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_QUERY_SOCKET_HPP +#define LIBBITCOIN_SERVER_WEB_QUERY_SOCKET_HPP + +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { + +class server_node; + +// This class is thread safe. +// Submit queries and address subscriptions and receive address +// notifications on a dedicated socket endpoint. +class BCS_API query_socket + : public socket +{ +public: + typedef std::shared_ptr ptr; + + /// Construct a query socket service endpoint. + query_socket(bc::protocol::zmq::authenticator& authenticator, + server_node& node, bool secure); + +protected: + // Implement the socket. + virtual void work() override; + + virtual bool start_websocket_handler() override; + + // Initialize the query specific zmq socket. + virtual void handle_websockets() override; + + virtual const config::endpoint& zeromq_endpoint() const override; + virtual const config::endpoint& websocket_endpoint() const override; + virtual const std::shared_ptr service() + const override; + + const config::endpoint& query_endpoint() const; + +private: + bool handle_query(bc::protocol::zmq::socket& dealer); + + std::shared_ptr service_; +}; + +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/socket.hpp b/include/bitcoin/server/web/socket.hpp new file mode 100644 index 00000000..b7b8394f --- /dev/null +++ b/include/bitcoin/server/web/socket.hpp @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_SOCKET_HPP +#define LIBBITCOIN_SERVER_WEB_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_MBEDTLS +extern "C" +{ +int https_random(void*, uint8_t* buffer, size_t length); +} +#endif + +namespace libbitcoin { +namespace server { + +class server_node; + +// Forward declarations in the http namespace. +namespace http +{ +class manager; +class connection; +enum class event : uint8_t; +typedef std::shared_ptr connection_ptr; +} // namespace http + +class BCS_API socket + : public bc::protocol::zmq::worker +{ +public: + typedef http::connection_ptr connection_ptr; + + // Tracks websocket queries via the query_work_map. Used for + // matching websocket client requests to zmq query responses. + struct query_work_item + { + // Constructor provided for in-place construction. + query_work_item(uint32_t id, uint32_t correlation_id, + connection_ptr connection, const std::string& command, + const std::string& arguments) + : id(id), + correlation_id(correlation_id), + command(command), + arguments(arguments), + connection(connection) + { + } + + uint32_t id; + uint32_t correlation_id; + std::string command; + std::string arguments; + connection_ptr connection; + }; + + typedef std::function encode_handler; + + typedef std::function decode_handler; + + // Handles translation of incoming JSON to zmq protocol methods and + // converting the result back to JSON for web clients. + struct handlers + { + std::string command; + encode_handler encode; + decode_handler decode; + }; + + typedef std::unordered_map query_work_map; + typedef std::unordered_map + connection_work_map; + typedef std::unordered_map> + query_correlation_map; + typedef std::unordered_map handler_map; + + /// Construct a socket class. + socket(bc::protocol::zmq::authenticator& authenticator, server_node& node, + bool secure, const std::string& domain); + + /// Start the service. + bool start() override; + + size_t connection_count() const; + void add_connection(connection_ptr& connection); + void remove_connection(connection_ptr& connection); + void notify_query_work(connection_ptr& connection, + const std::string& method, const uint32_t id, + const std::string& parameters); + +protected: + // Initialize the websocket event loop and start a thread to poll events. + virtual bool start_websocket_handler(); + + // Terminate the websocket event loop. + virtual bool stop_websocket_handler(); + + virtual void handle_websockets(); + + virtual const config::endpoint& zeromq_endpoint() const = 0; + virtual const config::endpoint& websocket_endpoint() const = 0; + virtual const std::shared_ptr service() const; + + // Send a message to the websocket client. + void send(connection_ptr connection, const std::string& json); + + // Send a message to every connected websocket client. + void broadcast(const std::string& json); + + // The zmq socket operates on only this one thread. + bc::protocol::zmq::authenticator& authenticator_; + const bool secure_; + const std::string security_; + const bc::server::settings& server_settings_; + const bc::protocol::settings& protocol_settings_; + // handlers_ is effectively const. + handler_map handlers_; + + // For query socket, service() is used to retrieve the zmq socket + // connected to the query_socket service. This socket operates on + // only the below member thread_. + std::shared_ptr thread_; + std::promise thread_status_; + + // Used by the query_socket class. + uint32_t sequence_; + connection_work_map connections_; + query_correlation_map correlations_; + mutable upgrade_mutex correlation_lock_; + +private: + static bool handle_event(connection_ptr& connection, + const http::event event, const void* data); + + std::shared_ptr manager_; + const std::string domain_; + const boost::filesystem::path document_root_; +}; + +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/transaction_socket.hpp b/include/bitcoin/server/web/transaction_socket.hpp new file mode 100644 index 00000000..27a2a083 --- /dev/null +++ b/include/bitcoin/server/web/transaction_socket.hpp @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_TRANSACTION_SOCKET_HPP +#define LIBBITCOIN_SERVER_WEB_TRANSACTION_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { + +class server_node; + +// This class is thread safe. +// Subscribe to transaction acceptances into the transaction memory +// pool from a dedicated socket endpoint. +class BCS_API transaction_socket + : public socket +{ +public: + typedef std::shared_ptr ptr; + + /// Construct a transaction socket service endpoint. + transaction_socket(bc::protocol::zmq::authenticator& authenticator, + server_node& node, bool secure); + +protected: + + // Implement the service. + virtual void work() override; + + virtual const config::endpoint& zeromq_endpoint() const override; + virtual const config::endpoint& websocket_endpoint() const override; + +private: + bool handle_transaction(bc::protocol::zmq::socket& subscriber); +}; + +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/workers/notification_worker.hpp b/include/bitcoin/server/workers/notification_worker.hpp index abeefb20..dfbcdf32 100644 --- a/include/bitcoin/server/workers/notification_worker.hpp +++ b/include/bitcoin/server/workers/notification_worker.hpp @@ -32,7 +32,7 @@ #include #include -// Include after bitcoin.hpp (placeholders). +// Include after define.hpp (placeholders). #include #include diff --git a/src/parser.cpp b/src/parser.cpp index 7e1460ba..f39f9796 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -24,8 +24,6 @@ #include #include #include -#include -#include #include BC_DECLARE_CONFIG_DEFAULT_PATH("libbitcoin" / "bs.cfg") @@ -625,12 +623,12 @@ options_metadata parser::load_settings() ( "server.secure_only", value(&configured.server.secure_only), - "Disable public endpoints, defaults to false." + "Disable all public endpoints, defaults to false." ) ( "server.query_workers", value(&configured.server.query_workers), - "The number of query worker threads per endpoint, defaults to 1 (0 disables service)." + "The number of query worker threads, defaults to 1 (0 disables service)." ) ( "server.subscription_limit", @@ -650,72 +648,238 @@ options_metadata parser::load_settings() ( "server.block_service_enabled", value(&configured.server.block_service_enabled), - "Enable the block publishing service, defaults to false." + "Enable the block publishing service, defaults to true." ) ( "server.transaction_service_enabled", value(&configured.server.transaction_service_enabled), - "Enable the transaction publishing service, defaults to false." + "Enable the transaction publishing service, defaults to true." ) ( - "server.secure_query_endpoint", - value(&configured.server.secure_query_endpoint), - "The secure query endpoint, defaults to 'tcp://*:9081'." + "server.client_address", + value(&configured.server.client_addresses), + "Allowed client IP address, multiple entries allowed." ) ( - "server.secure_heartbeat_endpoint", - value(&configured.server.secure_heartbeat_endpoint), - "The secure heartbeat endpoint, defaults to 'tcp://*:9082'." + "server.blacklist", + value(&configured.server.blacklists), + "Blocked client IP address, multiple entries allowed." ) + + /* [websockets] */ ( - "server.secure_block_endpoint", - value(&configured.server.secure_block_endpoint), - "The secure block publishing endpoint, defaults to 'tcp://*:9083'." + "websockets.secure_query_endpoint", + value(&configured.server.websockets_secure_query_endpoint), + "The secure query websocket endpoint, defaults to 'tcp://*:9061'." ) ( - "server.secure_transaction_endpoint", - value(&configured.server.secure_transaction_endpoint), - "The secure transaction publishing endpoint, defaults to 'tcp://*:9084'." + "websockets.secure_heartbeat_endpoint", + value(&configured.server.websockets_secure_heartbeat_endpoint), + "The secure heartbeat websocket endpoint, defaults to 'tcp://*:9062'." ) ( - "server.public_query_endpoint", - value(&configured.server.public_query_endpoint), - "The public query endpoint, defaults to 'tcp://*:9091'." + "websockets.secure_block_endpoint", + value(&configured.server.websockets_secure_block_endpoint), + "The secure block publishing websocket endpoint, defaults to 'tcp://*:9063'." ) ( - "server.public_heartbeat_endpoint", - value(&configured.server.public_heartbeat_endpoint), - "The public heartbeat endpoint, defaults to 'tcp://*:9092'." + "websockets.secure_transaction_endpoint", + value(&configured.server.websockets_secure_transaction_endpoint), + "The secure transaction publishing websocket endpoint, defaults to 'tcp://*:9064'." ) ( - "server.public_block_endpoint", - value(&configured.server.public_block_endpoint), - "The public block publishing endpoint, defaults to 'tcp://*:9093'." + "websockets.public_query_endpoint", + value(&configured.server.websockets_public_query_endpoint), + "The public query websocket endpoint, defaults to 'tcp://*:9071'." ) ( - "server.public_transaction_endpoint", - value(&configured.server.public_transaction_endpoint), - "The public transaction publishing endpoint, defaults to 'tcp://*:9094'." + "websockets.public_heartbeat_endpoint", + value(&configured.server.websockets_public_heartbeat_endpoint), + "The public heartbeat websocket endpoint, defaults to 'tcp://*:9072'." ) ( - "server.server_private_key", - value(&configured.server.server_private_key), - "The Z85-encoded private key of the server, enables secure endpoints." + "websockets.public_block_endpoint", + value(&configured.server.websockets_public_block_endpoint), + "The public block publishing websocket endpoint, defaults to 'tcp://*:9073'." ) ( - "server.client_public_key", - value(&configured.server.client_public_keys), - "Allowed Z85-encoded public key of the client, multiple entries allowed." + "websockets.public_transaction_endpoint", + value(&configured.server.websockets_public_transaction_endpoint), + "The public transaction websocket publishing endpoint, defaults to 'tcp://*:9074'." ) ( - "server.client_address", - value(&configured.server.client_addresses), - "Allowed client IP address, multiple entries allowed." + "websockets.enabled", + value(&configured.server.websockets_enabled), + "Enable websocket endpoints, defaults to true." ) ( - "server.blacklist", - value(&configured.server.blacklists), - "Blocked client IP address, multiple entries allowed." + "websockets.root", + value(&configured.server.websockets_root), + "The optional directory for serving files via HTTP/S, defaults to 'web'." + ) + ( + "websockets.ca_certificate", + value(&configured.server.websockets_ca_certificate), + "The SSL certificate authority file, defaults to 'ca.pem', enables secure endpoints." + ) + ( + "websockets.server_private_key", + value(&configured.server.websockets_server_private_key), + "The SSL private key file, defaults to 'key.pem', enables secure endpoints." + ) + ( + "websockets.server_certificate", + value(&configured.server.websockets_server_certificate), + "The SSL certificate file, defaults to 'server.pem', enables secure endpoints." + ) + ( + "websockets.client_certificates", + value(&configured.server.websockets_client_certificates), + "The SSL client certificates directory, defaults to 'clients'." + ) + + /* [bitcoin] */ + ( + "bitcoin.retargeting_factor", + PROPERTY(uint32_t, configured.bitcoin.retargeting_factor), + "The difficulty retargeting factor, defaults to 4." + ) + ( + "bitcoin.block_spacing_seconds", + PROPERTY(uint32_t, configured.bitcoin.block_spacing_seconds), + "The target block period in seconds, defaults to 600." + ) + ( + "bitcoin.timestamp_limit_seconds", + value(&configured.bitcoin.timestamp_limit_seconds), + "The future timestamp allowance in seconds, defaults to 7200." + ) + ( + "bitcoin.retargeting_interval_seconds", + PROPERTY(uint32_t, configured.bitcoin.retargeting_interval_seconds), + "The difficulty retargeting period in seconds, defaults to 1209600." + ) + ( + "bitcoin.proof_of_work_limit", + value(&configured.bitcoin.proof_of_work_limit), + "The proof of work limit, defaults to 486604799." + ) + ( + "bitcoin.genesis_block", + value(&configured.bitcoin.genesis_block), + "The genesis block." + ) + ( + "bitcoin.activation_threshold", + value(&configured.bitcoin.activation_threshold), + "The number of new version blocks required for bip34 style soft fork activation, defaults to 750." + ) + ( + "bitcoin.enforcement_threshold", + value(&configured.bitcoin.enforcement_threshold), + "The number of new version blocks required for bip34 style soft fork enforcement, defaults to 950." + ) + ( + "bitcoin.activation_sample", + value(&configured.bitcoin.activation_sample), + "The number of blocks considered for bip34 style soft fork activation, defaults to 1000." + ) + ( + "bitcoin.bip65_freeze", + value(&configured.bitcoin.bip65_freeze), + "The block height to freeze the bip65 softfork as in bip90, defaults to 388381." + ) + ( + "bitcoin.bip66_freeze", + value(&configured.bitcoin.bip66_freeze), + "The block height to freeze the bip66 softfork as in bip90, defaults to 363725." + ) + ( + "bitcoin.bip34_freeze", + value(&configured.bitcoin.bip34_freeze), + "The block height to freeze the bip34 softfork as in bip90, defaults to 227931." + ) + ( + "bitcoin.bip16_activation_time", + value(&configured.bitcoin.bip16_activation_time), + "The activation time for bip16 in unix time, defaults to 1333238400." + ) + ( + "bitcoin.bip34_active_checkpoint", + value(&configured.bitcoin.bip34_active_checkpoint), + "The hash:height checkpoint for bip34 activation, defaults to 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8:227931." + ) + ( + "bitcoin.bip9_bit0_active_checkpoint", + value(&configured.bitcoin.bip9_bit0_active_checkpoint), + "The hash:height checkpoint for bip9 bit0 activation, defaults to 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5:419328." + ) + ( + "bitcoin.bip9_bit1_active_checkpoint", + value(&configured.bitcoin.bip9_bit1_active_checkpoint), + "The hash:height checkpoint for bip9 bit1 activation, defaults to 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893:481824." + ) + ( + "bitcoin.initial_block_subsidy_bitcoin", + PROPERTY(uint64_t, configured.bitcoin.initial_block_subsidy_bitcoin), + "The initial block subsidy in bitcoin, defaults to 50." + ) + ( + "bitcoin.subsidy_interval", + PROPERTY(uint64_t, configured.bitcoin.subsidy_interval), + "The subsidy halving period in number of blocks, defaults to 210000." + ) + + /* [zeromq] */ + ( + "zeromq.secure_query_endpoint", + value(&configured.server.zeromq_secure_query_endpoint), + "The secure query zeromq endpoint, defaults to 'tcp://*:9081'." + ) + ( + "zeromq.secure_heartbeat_endpoint", + value(&configured.server.zeromq_secure_heartbeat_endpoint), + "The secure heartbeat zeromq endpoint, defaults to 'tcp://*:9082'." + ) + ( + "zeromq.secure_block_endpoint", + value(&configured.server.zeromq_secure_block_endpoint), + "The secure block publishing zeromq endpoint, defaults to 'tcp://*:9083'." + ) + ( + "zeromq.secure_transaction_endpoint", + value(&configured.server.zeromq_secure_transaction_endpoint), + "The secure transaction publishing zeromq endpoint, defaults to 'tcp://*:9084'." + ) + ( + "zeromq.public_query_endpoint", + value(&configured.server.zeromq_public_query_endpoint), + "The public query zeromq endpoint, defaults to 'tcp://*:9091'." + ) + ( + "zeromq.public_heartbeat_endpoint", + value(&configured.server.zeromq_public_heartbeat_endpoint), + "The public heartbeat zeromq endpoint, defaults to 'tcp://*:9092'." + ) + ( + "zeromq.public_block_endpoint", + value(&configured.server.zeromq_public_block_endpoint), + "The public block publishing zeromq endpoint, defaults to 'tcp://*:9093'." + ) + ( + "zeromq.public_transaction_endpoint", + value(&configured.server.zeromq_public_transaction_endpoint), + "The public transaction publishing zeromq endpoint, defaults to 'tcp://*:9094'." + ) + ( + "zeromq.server_private_key", + value(&configured.server.zeromq_server_private_key), + "The Z85-encoded private key of the server, enables secure endpoints." + ) + ( + "zeromq.client_public_key", + value(&configured.server.zeromq_client_public_keys), + "Allowed Z85-encoded public key of the client, multiple entries allowed." ); return description; diff --git a/src/server_node.cpp b/src/server_node.cpp index a77c92c9..a87af54b 100644 --- a/src/server_node.cpp +++ b/src/server_node.cpp @@ -49,7 +49,17 @@ server_node::server_node(const configuration& configuration) secure_transaction_service_(authenticator_, *this, true), public_transaction_service_(authenticator_, *this, false), secure_notification_worker_(authenticator_, *this, true), - public_notification_worker_(authenticator_, *this, false) + public_notification_worker_(authenticator_, *this, false), +#ifdef WITH_MBEDTLS + secure_query_websockets_(authenticator_, *this, true), + secure_heartbeat_websockets_(authenticator_, *this, true), + secure_block_websockets_(authenticator_, *this, true), + secure_transaction_websockets_(authenticator_, *this, true), +#endif + public_query_websockets_(authenticator_, *this, false), + public_heartbeat_websockets_(authenticator_, *this, false), + public_block_websockets_(authenticator_, *this, false), + public_transaction_websockets_(authenticator_, *this, false) { } @@ -176,7 +186,7 @@ bool server_node::start_authenticator() const auto& settings = configuration_.server; // Subscriptions require the query service. - if ((!settings.server_private_key && settings.secure_only) || + if ((!settings.zeromq_server_private_key && settings.secure_only) || ((settings.query_workers == 0) && (settings.heartbeat_service_seconds == 0) && (!settings.block_service_enabled) && @@ -195,7 +205,7 @@ bool server_node::start_query_services() return true; // Start secure service, query workers and notification workers if enabled. - if (settings.server_private_key && + if (settings.zeromq_server_private_key && (!secure_query_service_.start() || !start_query_workers(true) || (settings.subscription_limit > 0 && !start_notification_workers(true)))) return false; @@ -206,6 +216,20 @@ bool server_node::start_query_services() (settings.subscription_limit > 0 && !start_notification_workers(false)))) return false; + if (settings.websockets_enabled) + { +#ifdef WITH_MBEDTLS + // Start secure service if enabled. + if (settings.zeromq_server_private_key && + !secure_query_websockets_.start()) + return false; +#endif + + // Start public service if enabled. + if (!settings.secure_only && !public_query_websockets_.start()) + return false; + } + return true; } @@ -217,13 +241,28 @@ bool server_node::start_heartbeat_services() return true; // Start secure service if enabled. - if (settings.server_private_key && !secure_heartbeat_service_.start()) + if (settings.zeromq_server_private_key && + !secure_heartbeat_service_.start()) return false; // Start public service if enabled. if (!settings.secure_only && !public_heartbeat_service_.start()) return false; + if (settings.websockets_enabled) + { +#ifdef WITH_MBEDTLS + // Start secure service if enabled. + if (settings.zeromq_server_private_key && + !secure_heartbeat_websockets_.start()) + return false; +#endif + + // Start public service if enabled. + if (!settings.secure_only && !public_heartbeat_websockets_.start()) + return false; + } + return true; } @@ -235,13 +274,27 @@ bool server_node::start_block_services() return true; // Start secure service if enabled. - if (settings.server_private_key && !secure_block_service_.start()) + if (settings.zeromq_server_private_key && !secure_block_service_.start()) return false; // Start public service if enabled. if (!settings.secure_only && !public_block_service_.start()) return false; + if (settings.websockets_enabled) + { +#ifdef WITH_MBEDTLS + // Start secure service if enabled. + if (settings.zeromq_server_private_key && + !secure_block_websockets_.start()) + return false; +#endif + + // Start public service if enabled. + if (!settings.secure_only && !public_block_websockets_.start()) + return false; + } + return true; } @@ -253,13 +306,28 @@ bool server_node::start_transaction_services() return true; // Start secure service if enabled. - if (settings.server_private_key && !secure_transaction_service_.start()) + if (settings.zeromq_server_private_key && + !secure_transaction_service_.start()) return false; // Start public service if enabled. if (!settings.secure_only && !public_transaction_service_.start()) return false; + if (settings.websockets_enabled) + { +#ifdef WITH_MBEDTLS + // Start secure service if enabled. + if (settings.zeromq_server_private_key && + !secure_transaction_websockets_.start()) + return false; +#endif + + // Start public service if enabled. + if (!settings.secure_only && !public_transaction_websockets_.start()) + return false; + } + return true; } diff --git a/src/services/block_service.cpp b/src/services/block_service.cpp index 86309da7..9b7484f8 100644 --- a/src/services/block_service.cpp +++ b/src/services/block_service.cpp @@ -48,7 +48,7 @@ block_service::block_service(zmq::authenticator& authenticator, settings_(node.server_settings()), external_(node.protocol_settings()), internal_(external_.send_high_water, external_.receive_high_water), - service_(settings_.block_endpoint(secure)), + service_(settings_.zeromq_block_endpoint(secure)), worker_(secure ? secure_worker : public_worker), authenticator_(authenticator), node_(node), @@ -154,7 +154,7 @@ bool block_service::handle_reorganization(const code& ec, size_t fork_height, LOG_WARNING(LOG_SERVER) << "Failure handling new block: " << ec.message(); - // Don't let a failure here prevent prevent future notifications. + // Don't let a failure here prevent future notifications. return true; } diff --git a/src/services/heartbeat_service.cpp b/src/services/heartbeat_service.cpp index 17055ff3..0d2e0d1c 100644 --- a/src/services/heartbeat_service.cpp +++ b/src/services/heartbeat_service.cpp @@ -42,7 +42,7 @@ heartbeat_service::heartbeat_service(zmq::authenticator& authenticator, security_(secure ? "secure" : "public"), settings_(node.server_settings()), external_(node.protocol_settings()), - service_(settings_.heartbeat_endpoint(secure)), + service_(settings_.zeromq_heartbeat_endpoint(secure)), authenticator_(authenticator), node_(node), diff --git a/src/services/query_service.cpp b/src/services/query_service.cpp index d25c4702..b4eda1f2 100644 --- a/src/services/query_service.cpp +++ b/src/services/query_service.cpp @@ -48,7 +48,7 @@ query_service::query_service(zmq::authenticator& authenticator, settings_(node.server_settings()), external_(node.protocol_settings()), internal_(external_.send_high_water, external_.receive_high_water), - service_(settings_.query_endpoint(secure)), + service_(settings_.zeromq_query_endpoint(secure)), worker_(secure ? secure_worker : public_worker), authenticator_(authenticator) { diff --git a/src/services/transaction_service.cpp b/src/services/transaction_service.cpp index f1270054..5d54c1f0 100644 --- a/src/services/transaction_service.cpp +++ b/src/services/transaction_service.cpp @@ -47,7 +47,7 @@ transaction_service::transaction_service(zmq::authenticator& authenticator, settings_(node.server_settings()), external_(node.protocol_settings()), internal_(external_.send_high_water, external_.receive_high_water), - service_(settings_.transaction_endpoint(secure)), + service_(settings_.zeromq_transaction_endpoint(secure)), worker_(secure ? secure_worker : public_worker), authenticator_(authenticator), node_(node), @@ -153,7 +153,7 @@ bool transaction_service::handle_transaction(const code& ec, LOG_WARNING(LOG_SERVER) << "Failure handling new transaction: " << ec.message(); - // Don't let a failure here prevent prevent future notifications. + // Don't let a failure here prevent future notifications. return true; } diff --git a/src/settings.cpp b/src/settings.cpp index 5a57c1a1..4562d492 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -36,15 +36,34 @@ settings::settings() block_service_enabled(true), transaction_service_enabled(true), - secure_query_endpoint("tcp://*:9081"), - secure_heartbeat_endpoint("tcp://*:9082"), - secure_block_endpoint("tcp://*:9083"), - secure_transaction_endpoint("tcp://*:9084"), - - public_query_endpoint("tcp://*:9091"), - public_heartbeat_endpoint("tcp://*:9092"), - public_block_endpoint("tcp://*:9093"), - public_transaction_endpoint("tcp://*:9094") + // [websockets] + websockets_secure_query_endpoint("tcp://*:9061"), + websockets_secure_heartbeat_endpoint("tcp://*:9062"), + websockets_secure_block_endpoint("tcp://*:9063"), + websockets_secure_transaction_endpoint("tcp://*:9064"), + + websockets_public_query_endpoint("tcp://*:9071"), + websockets_public_heartbeat_endpoint("tcp://*:9072"), + websockets_public_block_endpoint("tcp://*:9073"), + websockets_public_transaction_endpoint("tcp://*:9074"), + + websockets_enabled(true), + websockets_root("web"), + websockets_ca_certificate("ca.pem"), + websockets_server_private_key("key.pem"), + websockets_server_certificate("server.pem"), + websockets_client_certificates("clients"), + + // [zeromq] + zeromq_secure_query_endpoint("tcp://*:9081"), + zeromq_secure_heartbeat_endpoint("tcp://*:9082"), + zeromq_secure_block_endpoint("tcp://*:9083"), + zeromq_secure_transaction_endpoint("tcp://*:9084"), + + zeromq_public_query_endpoint("tcp://*:9091"), + zeromq_public_heartbeat_endpoint("tcp://*:9092"), + zeromq_public_block_endpoint("tcp://*:9093"), + zeromq_public_transaction_endpoint("tcp://*:9094") { } @@ -54,34 +73,63 @@ settings::settings(config::settings) { } -const config::endpoint& settings::query_endpoint(bool secure) const +duration settings::heartbeat_interval() const { - return secure ? secure_query_endpoint : public_query_endpoint; + return seconds(heartbeat_service_seconds); } -const config::endpoint& settings::heartbeat_endpoint(bool secure) const +duration settings::subscription_expiration() const { - return secure ? secure_heartbeat_endpoint : public_heartbeat_endpoint; + return minutes(subscription_expiration_minutes); } -const config::endpoint& settings::block_endpoint(bool secure) const +const config::endpoint& settings::websockets_query_endpoint(bool secure) const { - return secure ? secure_block_endpoint : public_block_endpoint; + return secure ? websockets_secure_query_endpoint : + websockets_public_query_endpoint; } -const config::endpoint& settings::transaction_endpoint(bool secure) const +const config::endpoint& settings::websockets_heartbeat_endpoint(bool secure) const { - return secure ? secure_transaction_endpoint : public_transaction_endpoint; + return secure ? websockets_secure_heartbeat_endpoint : + websockets_public_heartbeat_endpoint; } -duration settings::heartbeat_interval() const +const config::endpoint& settings::websockets_block_endpoint(bool secure) const { - return seconds(heartbeat_service_seconds); + return secure ? websockets_secure_block_endpoint : + websockets_public_block_endpoint; } -duration settings::subscription_expiration() const +const config::endpoint& settings::websockets_transaction_endpoint( + bool secure) const { - return minutes(subscription_expiration_minutes); + return secure ? websockets_secure_transaction_endpoint : + websockets_public_transaction_endpoint; +} + +const config::endpoint& settings::zeromq_query_endpoint(bool secure) const +{ + return secure ? zeromq_secure_query_endpoint : + zeromq_public_query_endpoint; +} + +const config::endpoint& settings::zeromq_heartbeat_endpoint(bool secure) const +{ + return secure ? zeromq_secure_heartbeat_endpoint : + zeromq_public_heartbeat_endpoint; +} + +const config::endpoint& settings::zeromq_block_endpoint(bool secure) const +{ + return secure ? zeromq_secure_block_endpoint : + zeromq_public_block_endpoint; +} + +const config::endpoint& settings::zeromq_transaction_endpoint(bool secure) const +{ + return secure ? zeromq_secure_transaction_endpoint : + zeromq_public_transaction_endpoint; } } // namespace server diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp new file mode 100644 index 00000000..93d5c618 --- /dev/null +++ b/src/web/block_socket.cpp @@ -0,0 +1,153 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 +#include +#include +#include + +namespace libbitcoin { +namespace server { + +using namespace std::placeholders; +using namespace bc::chain; +using namespace bc::protocol; +using role = zmq::socket::role; + +static const auto domain = "block"; +static constexpr auto poll_interval_milliseconds = 100u; + +block_socket::block_socket(zmq::authenticator& authenticator, + server_node& node, bool secure) + : socket(authenticator, node, secure, domain) +{ +} + +void block_socket::work() +{ + zmq::socket sub(authenticator_, role::subscriber, protocol_settings_); + + const auto endpoint = zeromq_endpoint().to_local(); + const auto ec = sub.connect(endpoint); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to connect to block service " << endpoint << ": " + << ec.message(); + return; + } + + if (!started(start_websocket_handler())) + { + LOG_ERROR(LOG_SERVER) + << "Failed to start " << security_ << " " << domain + << " websocket handler"; + return; + } + + // Hold a shared reference to the websocket thread_ so that we can + // properly call stop_websocket_handler on cleanup. + const auto thread_ref = thread_; + + zmq::poller poller; + poller.add(sub); + + while (!poller.terminated() && !stopped()) + { + if (poller.wait(poll_interval_milliseconds).contains(sub.id()) && + !handle_block(sub)) + break; + } + + const auto sub_stop = sub.stop(); + const auto websocket_stop = stop_websocket_handler(); + + if (!sub_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to disconnect " << security_ + << " block websocket service."; + + if (!websocket_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to stop " << security_ + << " block websocket handler"; + + finished(sub_stop && websocket_stop); +} + +// Called by this thread's work() method. +// Returns true to continue future notifications. +bool block_socket::handle_block(zmq::socket& subscriber) +{ + if (stopped()) + return false; + + zmq::message response; + subscriber.receive(response); + + static constexpr size_t block_message_size = 3; + if (response.empty() || response.size() != block_message_size) + { + LOG_WARNING(LOG_SERVER) + << "Failure handling block notification: invalid data"; + + // Don't let a failure here prevent future notifications. + return true; + } + + uint16_t sequence{}; + uint32_t height{}; + data_chunk block_data; + response.dequeue(sequence); + response.dequeue(height); + response.dequeue(block_data); + + // Format and send transaction to websocket subscribers. + const auto block = block::factory(block_data, true); + broadcast(web::to_json(block, height, sequence)); + + LOG_VERBOSE(LOG_SERVER) + << "Broadcasted " << security_ << " socket block [" + << height << "]"; + return true; +} + +const config::endpoint& block_socket::zeromq_endpoint() const +{ + // The Websocket to zeromq backend internally always uses the + // local public zeromq endpoint since it does not affect the + // external security of the websocket endpoint and impacts + // configuration and performance for no additional gain. + return server_settings_.zeromq_block_endpoint(false /* secure_ */); +} + +const config::endpoint& block_socket::websocket_endpoint() const +{ + return server_settings_.websockets_block_endpoint(secure_); +} + +} // namespace server +} // namespace libbitcoin diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp new file mode 100644 index 00000000..00b00638 --- /dev/null +++ b/src/web/heartbeat_socket.cpp @@ -0,0 +1,145 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 server { + +static const auto domain = "heartbeat"; +static constexpr auto poll_interval_milliseconds = 100u; + +using namespace bc::config; +using namespace bc::protocol; +using role = zmq::socket::role; + +heartbeat_socket::heartbeat_socket(zmq::authenticator& authenticator, + server_node& node, bool secure) + : socket(authenticator, node, secure, domain) +{ +} + +void heartbeat_socket::work() +{ + zmq::socket sub(authenticator_, role::subscriber, protocol_settings_); + + const auto endpoint = zeromq_endpoint().to_local(); + const auto ec = sub.connect(endpoint); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to connect to heartbeat service " << endpoint << ": " + << ec.message(); + return; + } + + if (!started(start_websocket_handler())) + { + LOG_ERROR(LOG_SERVER) + << "Failed to start " << security_ << " " << domain + << " websocket handler"; + return; + } + + // Hold a shared reference to the websocket thread_ so that we can + // properly call stop_websocket_handler on cleanup. + const auto thread_ref = thread_; + + zmq::poller poller; + poller.add(sub); + + while (!poller.terminated() && !stopped()) + { + if (poller.wait(poll_interval_milliseconds).contains(sub.id()) && + !handle_heartbeat(sub)) + break; + } + + const auto sub_stop = sub.stop(); + const auto websocket_stop = stop_websocket_handler(); + + if (!sub_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to disconnect " << security_ + << " hearbeat websocket service."; + + if (!websocket_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to stop " << security_ + << " heartbeat websocket handler"; + + finished(sub_stop && websocket_stop); +} + +// Called by this thread's work() method. +// Returns true to continue future notifications. +bool heartbeat_socket::handle_heartbeat(zmq::socket& subscriber) +{ + if (stopped()) + return false; + + zmq::message response; + subscriber.receive(response); + + static constexpr size_t heartbeat_message_size = 2; + if (response.empty() || response.size() != heartbeat_message_size) + { + LOG_WARNING(LOG_SERVER) + << "Failure handling heartbeat notification: invalid data."; + + // Don't let a failure here prevent future notifications. + return true; + } + + uint16_t sequence{}; + uint64_t height{}; + response.dequeue(sequence); + response.dequeue(height); + + broadcast(web::to_json(height, sequence)); + + LOG_VERBOSE(LOG_SERVER) + << "Broadcasted " << security_ << " socket heartbeat [" << height + << ", " << sequence << "]"; + return true; +} + +const config::endpoint& heartbeat_socket::zeromq_endpoint() const +{ + // The Websocket to zeromq backend internally always uses the + // local public zeromq endpoint since it does not affect the + // external security of the websocket endpoint and impacts + // configuration and performance for no additional gain. + return server_settings_.zeromq_heartbeat_endpoint(false /* secure_ */); +} + +const config::endpoint& heartbeat_socket::websocket_endpoint() const +{ + return server_settings_.websockets_heartbeat_endpoint(secure_); +} + +} // namespace server +} // namespace libbitcoin diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp new file mode 100644 index 00000000..f4470a5c --- /dev/null +++ b/src/web/http/connection.cpp @@ -0,0 +1,414 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 "connection.hpp" +#include "http.hpp" +#include "utilities.hpp" + +namespace libbitcoin { +namespace server { +namespace http { + +connection::connection() + : user_data_(nullptr), + state_(connection_state::unknown), + socket_(0), + address_{}, + last_active_(std::chrono::steady_clock::now()), + high_water_mark_(default_high_water_mark), + maximum_incoming_frame_length_(default_incoming_frame_length), + read_buffer_{}, + write_buffer_{}, + ssl_context_{}, + websocket_(false), + websocket_endpoint_{}, + json_rpc_(false), + file_transfer_{}, + websocket_transfer_{} +{ + write_buffer_.reserve(high_water_mark_); +} + +connection::connection(socket_connection connection, struct sockaddr_in& + address) + : user_data_(nullptr), + state_(connection_state::unknown), + socket_(connection), + address_(address), + last_active_(std::chrono::steady_clock::now()), + high_water_mark_(default_high_water_mark), + maximum_incoming_frame_length_(default_incoming_frame_length), + read_buffer_{}, + write_buffer_{}, + ssl_context_{}, + websocket_(false), + websocket_endpoint_{}, + json_rpc_(false), + file_transfer_{}, + websocket_transfer_{} +{ + write_buffer_.reserve(high_water_mark_); +} + +connection::~connection() +{ + if (!closed()) + close(); +} + +// May invalidate any buffered write data. +void connection::set_high_water_mark(int32_t high_water_mark) +{ + if (high_water_mark > 0) + { + high_water_mark_ = high_water_mark; + write_buffer_.reserve(high_water_mark); + write_buffer_.shrink_to_fit(); + } +} + +int32_t connection::high_water_mark() +{ + return high_water_mark_; +} + +void connection::set_maximum_incoming_frame_length(int32_t length) +{ + if (length > 0) + maximum_incoming_frame_length_ = length; +} + +int32_t connection::maximum_incoming_frame_length() +{ + return maximum_incoming_frame_length_; +} + +void connection::set_socket_non_blocking() +{ +#ifdef WIN32 + unsigned long non_blocking = 1; + ioctlsocket(socket_ , FIONBIO, &non_blocking); +#else + fcntl(socket_, F_SETFL, fcntl(socket_, F_GETFD) | O_NONBLOCK); +#endif +} + +struct sockaddr_in connection::address() +{ + return address_; +} + +bool connection::reuse_address() +{ + static constexpr int opt = 1; + return setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&opt), sizeof(opt)) != -1; +} + +connection_state connection::state() +{ + return state_; +} + +void connection::set_state(connection_state state) +{ + state_ = state; +} + +bool connection::closed() +{ + return state_ == connection_state::closed; +} + +int32_t connection::read() +{ + auto data = reinterpret_cast(read_buffer_.data()); +#ifdef WITH_MBEDTLS + bytes_read_ = (ssl_context_.enabled ? mbedtls_ssl_read( + &ssl_context_.context, data, maximum_read_length) : + recv(socket_, data, maximum_read_length, 0)); +#else + bytes_read_ = recv(socket_, data, maximum_read_length, 0); +#endif + return bytes_read_; +} + +int32_t connection::read_length() +{ + return bytes_read_; +} + +buffer& connection::read_buffer() +{ + return read_buffer_; +} + +data_buffer& connection::write_buffer() +{ + return write_buffer_; +} + +// This is effectively a blocking write call that does not buffer +// internally. +int32_t connection::do_write(const unsigned char* data, size_t length, + bool write_frame) +{ + write_method plaintext_write = [this](const unsigned char* data, + size_t length) + { + return static_cast(send(socket_, data, length, 0)); + }; + +#ifdef WITH_MBEDTLS + write_method ssl_write = [this](const unsigned char* data, size_t length) + { + return static_cast( + mbedtls_ssl_write(&ssl_context_.context, data, length)); + }; + + write_method writer = (ssl_context_.enabled ? ssl_write : plaintext_write); +#else + write_method writer = plaintext_write; +#endif + + if (write_frame) + { + const auto frame = generate_websocket_frame(length, + websocket_op::text); + const auto frame_data = reinterpret_cast(&frame); + + int32_t frame_write = writer(frame_data, frame.write_length); + if (frame_write < 0) + return frame_write; + } + + int32_t written = 0; + auto position = data; + auto remaining = length; + + do + { + written = writer(position, remaining); + if (written < 0) + { + const auto error = last_error(); + if (!would_block(error)) + { + LOG_WARNING(LOG_SERVER_HTTP) + << "do_write failed. requested " << remaining + << " and wrote " << written << ": " << error_string(); + return written; + } + + continue; + } + + position += written; + remaining -= written; + + } while (remaining != 0); + + return static_cast(position - data); +} + +int32_t connection::write(const std::string& buffer) +{ + return write(reinterpret_cast(buffer.c_str()), + buffer.size()); +} + +// This is a buffered write call so long as we're under the high +// water mark. +int32_t connection::write(const unsigned char* data, size_t length) +{ + const auto buffered_length = write_buffer_.size() + length + + (websocket_ ? sizeof(websocket_frame) : 0); + + // If we're currently at the hwm, issue blocking writes until + // we've cleared the buffered data and then write this current + // request. This is an expensive operation, but should be + // mostly avoidable with proper hwm tuning of your + // application. + if (buffered_length >= high_water_mark_) + { + // Drain the buffered data. + while (!write_buffer_.empty()) + { + const auto segment_length = std::min(static_cast( + transfer_buffer_length), write_buffer_.size()); + const auto written = do_write(reinterpret_cast( + write_buffer_.data()), segment_length, false); + + if (written < 0) + return written; + + if (!write_buffer_.empty()) + write_buffer_.erase(write_buffer_.begin(), write_buffer_.begin() + + written); + } + + // Perform this write in a blocking manner. + return do_write(data, length, websocket_); + } + + if (websocket_) + { + const auto frame = generate_websocket_frame(length, + websocket_op::text); + const auto frame_data = reinterpret_cast(&frame); + write_buffer_.insert(write_buffer_.end(), frame_data, frame_data + + frame.write_length); + } + + // Buffer this data for future writes (called from poll). + write_buffer_.insert(write_buffer_.end(), data, data + length); + return length; +} + +void connection::close() +{ + if (state_ == connection_state::closed) + return; + +#ifdef WITH_MBEDTLS + if (ssl_context_.enabled) + { + if (state_ != connection_state::listening) + mbedtls_ssl_free(&ssl_context_.context); + + mbedtls_pk_free(&ssl_context_.key); + mbedtls_x509_crt_free(&ssl_context_.certificate); + mbedtls_x509_crt_free(&ssl_context_.ca_certificate); + mbedtls_ssl_config_free(&ssl_context_.configuration); + ssl_context_.enabled = false; + } +#endif + + close_socket(socket_); + state_ = connection_state::closed; + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Closed socket " << this; +} + +socket_connection& connection::socket() +{ + return socket_; +} + +http::ssl& connection::ssl_context() +{ + return ssl_context_; +} + +bool connection::ssl_enabled() +{ + return ssl_context_.enabled; +} + +bool connection::websocket() +{ + return websocket_; +} + +void connection::set_websocket(bool websocket) +{ + websocket_ = websocket; +} + +const std::string& connection::websocket_endpoint() +{ + return websocket_endpoint_; +} + +void connection::set_websocket_endpoint(const std::string endpoint) +{ + websocket_endpoint_ = endpoint; +} + +bool connection::json_rpc() +{ + return json_rpc_; +} + +void connection::set_json_rpc(bool json_rpc) +{ + json_rpc_ = json_rpc; +} + +void* connection::user_data() +{ + return user_data_; +} + +void connection::set_user_data(void* user_data) +{ + user_data_ = user_data; +} + +http::file_transfer& connection::file_transfer() +{ + return file_transfer_; +} + +http::websocket_transfer& connection::websocket_transfer() +{ + return websocket_transfer_; +} + +bool connection::operator==(const connection& other) +{ + return user_data_ == other.user_data_ && socket_ == other.socket_; +} + +websocket_frame connection::generate_websocket_frame(size_t length, + websocket_op code) +{ + websocket_frame frame{}; + frame.flags = 0x80 | static_cast(code); + + auto start = &frame.length[0]; + if (length < 126) + { + frame.payload_length = length; + frame.write_length = 2; + } + else if (length < std::numeric_limits::max()) + { + auto data_length = static_cast(length); + *reinterpret_cast(start) = + boost::endian::endian_reverse(data_length); + frame.payload_length = 126; + frame.write_length = 4; // 2 + sizeof(uint16_t) + } + else + { + auto data_length = static_cast(length); + *reinterpret_cast(start) = + boost::endian::endian_reverse(data_length); + frame.payload_length = 127; + frame.write_length = 10; // 2 + sizeof(uint64_t) + } + + return frame; +} + +} // namespace http +} // namespace server +} // namespace libbitcoin diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp new file mode 100644 index 00000000..30689ccc --- /dev/null +++ b/src/web/http/connection.hpp @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_CONNECTION_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP + +#include +#include +#include + +#include "http.hpp" + +namespace libbitcoin { +namespace server { +namespace http { + +class connection; + +typedef std::shared_ptr connection_ptr; +typedef std::set connection_set; +typedef std::vector connection_list; +typedef std::function event_handler; + +// This class is instantiated from accepted/incoming HTTP clients. +// Initiating outgoing HTTP connections are not currently supported. +class connection +{ + public: + static const size_t maximum_read_length = (1 << 10); // 1 KB + static const size_t default_high_water_mark = (1 << 21); // 2 MB + static const size_t default_incoming_frame_length = (1 << 19); //512 KB + typedef std::function write_method; + + connection(); + connection(socket_connection connection, struct sockaddr_in& address); + ~connection(); + + void set_high_water_mark(int32_t high_water_mark); + int32_t high_water_mark(); + void set_maximum_incoming_frame_length(int32_t length); + int32_t maximum_incoming_frame_length(); + void set_user_data(void* user_data); + void* user_data(); + void set_socket_non_blocking(); + struct sockaddr_in address(); + bool reuse_address(); + connection_state state(); + void set_state(connection_state state); + bool closed(); + int32_t read_length(); + buffer& read_buffer(); + data_buffer& write_buffer(); + + int32_t read(); + int32_t write(const std::string& buffer); + // This is a buffered write call so long as we're under the high + // water mark. + int32_t write(const unsigned char* data, size_t length); + // This is a write call that does not buffer internally and keeps + // trying until an error is received, or the entire specified + // length is sent. + int32_t do_write(const unsigned char* data, size_t length, + bool write_frame); + void close(); + socket_connection& socket(); + http::ssl& ssl_context(); + bool ssl_enabled(); + bool websocket(); + void set_websocket(bool websocket); + bool json_rpc(); + void set_json_rpc(bool json_rpc); + // Websocket endpoints are HTTP specific endpoints such as '/'. + const std::string& websocket_endpoint(); + void set_websocket_endpoint(const std::string endpoint); + http::file_transfer& file_transfer(); + http::websocket_transfer& websocket_transfer(); + bool operator==(const connection& other); + + private: + websocket_frame generate_websocket_frame(size_t length, websocket_op code); + + void* user_data_; + connection_state state_; + socket_connection socket_; + struct sockaddr_in address_; + std::chrono::steady_clock::time_point last_active_; + size_t high_water_mark_; + size_t maximum_incoming_frame_length_; + buffer read_buffer_; + data_buffer write_buffer_; + int32_t bytes_read_; + ssl ssl_context_; + bool websocket_; + std::string websocket_endpoint_; + bool json_rpc_; + // Transfer states used for read continuations, particularly for + // when the read_buffer_ size is too small to hold all of the + // incoming data. + http::file_transfer file_transfer_; + http::websocket_transfer websocket_transfer_; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/src/web/http/http.hpp b/src/web/http/http.hpp new file mode 100644 index 00000000..0308667a --- /dev/null +++ b/src/web/http/http.hpp @@ -0,0 +1,369 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef WIN32 +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +// Explicitly use std::placeholders here for usage internally to the +// boost parsing helpers included from json_parser.hpp. +// See: https://svn.boost.org/trac10/ticket/12621 +#include +using namespace std::placeholders; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_MBEDTLS +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace libbitcoin { +namespace server { +namespace http { + +using namespace boost::property_tree; + +#ifdef WIN32 +typedef SOCKET sock_t; +typedef uint32_t in_addr_t; +#define close_socket closesocket +#else +typedef int sock_t; +#define close_socket ::close +#endif + +static const size_t sha1_hash_length = 20; +static const size_t default_buffer_length = 1 << 10; // 1KB +static const size_t transfer_buffer_length = 1 << 18; // 256KB + +#ifdef WITH_MBEDTLS +static const int default_ciphers[] = +{ + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + 0 +}; +#endif + +typedef std::vector data_buffer; +typedef std::array buffer; + +typedef sock_t socket_connection; + +typedef std::array sha1_hash; + +typedef std::vector string_list; +typedef std::unordered_map string_map; + +enum class connection_state +{ + error = -1, + connecting = 99, + connected = 100, + listening = 101, + ssl_handshake = 102, + closed = 103, + disconnect_immediately = 200, + unknown +}; + +enum class event : uint8_t +{ + read, + write, + listen, + accepted, + error, + closing, + websocket_frame, + websocket_control_frame, + json_rpc +}; + +enum class protocol_status : uint16_t +{ + switching = 101, + ok = 200, + created = 201, + accepted = 202, + no_content = 204, + multiple_choices = 300, + moved_permanently = 301, + moved_temporarily = 302, + not_modified = 304, + bad_request = 400, + unauthorized = 401, + forbidden = 403, + not_found = 404, + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503 +}; + +enum class websocket_op : uint8_t +{ + continuation = 0, + text = 1, + binary = 2, + close = 8, + ping = 9, + pong = 10, +}; + +struct websocket_message +{ + const std::string& endpoint; + const unsigned char* data; + int32_t size; + uint8_t flags; + websocket_op code; +}; + +#pragma pack(push, 1) +struct websocket_frame +{ + uint8_t flags; + uint8_t payload_length; + char length[8]; + uint8_t write_length; +}; +#pragma pack(pop) + +struct ssl +{ + bool enabled; + std::string hostname; +#ifdef WITH_MBEDTLS + mbedtls_ssl_context context; + mbedtls_ssl_config configuration; + mbedtls_pk_context key; + mbedtls_x509_crt certificate; + mbedtls_x509_crt ca_certificate; +#endif +}; + +struct bind_options +{ + void* user_data; + unsigned int flags; + std::string ssl_key; + std::string ssl_certificate; + std::string ssl_ca_certificate; + std::string ssl_cipers; +}; + +struct file_transfer +{ + bool in_progress; + FILE* descriptor; + size_t offset; + size_t length; +}; + +struct websocket_transfer +{ + bool in_progress; + size_t offset; + size_t length; + size_t header_length; + data_buffer mask; + data_buffer data; +}; + +struct http_request +{ + std::string find(const string_map& haystack, + const std::string& needle) const + { + auto it = haystack.find(needle); + if (it != haystack.end()) + return it->second; + + return {}; + } + + std::string header(std::string header) const + { + boost::algorithm::to_lower(header); + return find(headers, header); + } + + std::string parameter(std::string parameter) const + { + boost::algorithm::to_lower(parameter); + return find(parameters, parameter); + } + + std::string method; + std::string uri; + std::string protocol; + double protocol_version; + size_t message_length; + size_t content_length; + string_map headers; + string_map parameters; + bool upgrade_request; + bool json_rpc; + ptree json_tree; +}; + +struct http_reply +{ + static std::string to_string(protocol_status status) + { + typedef std::unordered_map status_map; + static const status_map status_strings = + { + { static_cast(protocol_status::switching), "HTTP/1.1 101 Switching Protocols\r\n" }, + { static_cast(protocol_status::ok), "HTTP/1.0 200 OK\r\n" }, + { static_cast(protocol_status::created), "HTTP/1.0 201 Created\r\n" }, + { static_cast(protocol_status::accepted), "HTTP/1.0 202 Accepted\r\n" }, + { static_cast(protocol_status::no_content), "HTTP/1.0 204 No Content\r\n" }, + { static_cast(protocol_status::multiple_choices), "HTTP/1.0 300 Multiple Choices\r\n" }, + { static_cast(protocol_status::moved_permanently), "HTTP/1.0 301 Moved Permanently\r\n" }, + { static_cast(protocol_status::moved_temporarily), "HTTP/1.0 302 Moved Temporarily\r\n" }, + { static_cast(protocol_status::not_modified), "HTTP/1.0 304 Not Modified\r\n" }, + { static_cast(protocol_status::bad_request), "HTTP/1.0 400 Bad Request\r\n" }, + { static_cast(protocol_status::unauthorized), "HTTP/1.0 401 Unauthorized\r\n" }, + { static_cast(protocol_status::forbidden), "HTTP/1.0 403 Forbidden\r\n" }, + { static_cast(protocol_status::not_found), "HTTP/1.0 404 Not Found\r\n" }, + { static_cast(protocol_status::internal_server_error), "HTTP/1.0 500 Internal Server Error\r\n" }, + { static_cast(protocol_status::not_implemented), "HTTP/1.0 501 Not Implemented\r\n" }, + { static_cast(protocol_status::bad_gateway), "HTTP/1.0 502 Bad Gateway\r\n" }, + { static_cast(protocol_status::service_unavailable), "HTTP/1.0 503 Service Unavailable\r\n" } + }; + + auto it = status_strings.find(static_cast(status)); + if (it != status_strings.end()) + return it->second; + + return {}; + } + + static std::string generate(protocol_status status, std::string mime_type, + size_t content_length, bool keep_alive) + { + static const size_t max_date_time_length = 32; + std::array time_buffer{}; + + time_t current_time = time(nullptr); + strftime(time_buffer.data(), time_buffer.size(), + "%a, %d %b %Y %H:%M:%S GMT", gmtime(¤t_time)); + + std::stringstream response; + response + << to_string(status) + << "Date: " << time_buffer.data() << "\r\n" + << "Accept-Ranges: none\r\n" + << "Connection: " << (keep_alive ? "keep-alive" : "close") + << "\r\n"; + + if (!mime_type.empty()) + response << "Content-Type: " << mime_type << "\r\n"; + + if (content_length > 0) + response << "Content-Length: " << content_length << "\r\n"; + + response << "\r\n"; + return response.str(); + } + + static std::string generate_upgrade(const std::string& key_response, + const std::string& protocol) + { + std::stringstream response; + response + << to_string(protocol_status::switching) + << "Upgrade: websocket\r\n" + << "Connection: Upgrade" << "\r\n"; + + if (!protocol.empty()) + response << protocol << "\r\n"; + + response << "Sec-WebSocket-Accept: " << key_response << "\r\n\r\n"; + return response.str(); + } + + protocol_status status; + string_map headers; + std::string content; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp new file mode 100644 index 00000000..89cd96d2 --- /dev/null +++ b/src/web/http/manager.cpp @@ -0,0 +1,1315 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 "http.hpp" +#include "utilities.hpp" +#include "manager.hpp" + +#ifdef WITH_MBEDTLS +extern "C" +{ +// Random data generator used by mbedtls for SSL. +int https_random(void*, uint8_t* buffer, size_t length); +} +#endif + +namespace libbitcoin { +namespace server { +namespace http { + +manager::manager(bool ssl, event_handler handler, + boost::filesystem::path document_root) +#ifdef WITH_MBEDTLS + : ssl_(ssl), +#else + : ssl_(false), +#endif + listening_(false), + user_data_(nullptr), + running_(false), + handler_(handler), + document_root_(document_root), + connections_{}, + maximum_incoming_frame_length_(1 << 19), // 512 KB + high_water_mark_(1 << 21), // 2 MB + backlog_(8) +{ +} + +manager::~manager() +{ + running_ = false; + listening_ = false; + connections_.clear(); + +#ifdef WIN32 + WSACleanup(); +#endif +} + +bool manager::initialize() +{ +#ifdef WIN32 + WSADATA wsa_data{}; + if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) + return false; +#endif + return true; +} + +void manager::set_maximum_incoming_frame_length(int32_t length) +{ + maximum_incoming_frame_length_ = length; + for (auto& connection: connections_) + connection->set_maximum_incoming_frame_length(length); +} + +int32_t manager::maximum_incoming_frame_length() +{ + return maximum_incoming_frame_length_; +} + +// May truncate any previously buffered write data on each connection. +void manager::set_high_water_mark(int32_t length) +{ + high_water_mark_ = length; + for (auto& connection: connections_) + connection->set_high_water_mark(length); +} + +int32_t manager::high_water_mark() +{ + return high_water_mark_; +} + +void manager::set_backlog(size_t backlog) +{ + backlog_ = backlog; +} + +bool manager::bind(std::string hostname, uint16_t port, + const bind_options& options) +{ + LOG_VERBOSE(LOG_SERVER_HTTP) + << (ssl_ ? "Secure" : "Public") << " bind called with host " << hostname + << " and port " << port; + port_ = port; + + static const auto address_length = + static_cast(sizeof(listener_address_)); + + std::memset(&listener_address_, 0, address_length); + listener_address_.sin_family = AF_INET; + listener_address_.sin_port = htons(port_); + listener_address_.sin_addr.s_addr = htonl(INADDR_ANY); + + listening_ = true; + user_data_ = options.user_data; + + listener_ = std::make_shared(); + listener_->socket() = ::socket(listener_address_.sin_family, + SOCK_STREAM, 0); + if (listener_->socket() == 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Socket failed with error " << last_error() << ": " + << error_string(); + return false; + } + +#ifdef WITH_MBEDTLS + if (ssl_) + { + if (!options.ssl_certificate.empty() || + !options.ssl_ca_certificate.empty()) + { + key_ = options.ssl_key; + certificate_ = options.ssl_certificate; + ca_certificate_ = options.ssl_ca_certificate; + } + + if (!initialize_ssl(listener_, listening_)) + return false; + + LOG_DEBUG(LOG_SERVER_HTTP) + << "SSL initialized for listener socket"; + } +#endif + + listener_->set_socket_non_blocking(); + listener_->reuse_address(); + + if (::bind(listener_->socket(), reinterpret_cast( + &listener_address_), address_length) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Bind failed with error " << last_error() << ": " + << error_string(); + listener_->close(); + return false; + } + + ::listen(listener_->socket(), backlog_); + + listener_->set_state(connection_state::listening); + add_connection(listener_); + + return true; +} + +bool manager::accept_connection() +{ + struct sockaddr_in remote_address; + socklen_t remote_address_size = sizeof(remote_address); + std::memset(&remote_address, 0, remote_address_size); + + sock_t socket{}; + + do + { + socket = ::accept(listener_->socket(), reinterpret_cast( + &remote_address), static_cast(&remote_address_size)); + + const auto error = last_error(); + if ((static_cast(socket) == + connection_state::error) && !would_block(error)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Accept call failed with " << error_string(); + return false; + } + else + { + break; + } + + } while (true); + +#ifdef SO_NOSIGPIPE + int no_sig_pipe = 1; + if (setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, + reinterpret_cast(&no_sig_pipe), sizeof(no_sig_pipe)) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to disable SIGPIPE"; + close_socket(socket); + return false; + } +#endif + + auto connection = std::make_shared(socket, + remote_address); + if (connection == nullptr) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to create new connection object"; + close_socket(socket); + return false; + } + +#ifdef WITH_MBEDTLS + if (ssl_) + { + if (!initialize_ssl(connection, false)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to initialize new SSL connection on port " << port_; + connection->close(); + return false; + } + + auto& context = connection->ssl_context().context; + mbedtls_ssl_set_bio( + &context, reinterpret_cast(connection.get()), + ssl_send, ssl_receive, nullptr); + + int error = ~0; + while (error != 0) + { + error = mbedtls_ssl_handshake_step(&context); + if (mbedtls_would_block(error)) + continue; + + if (error < 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "SSL handshake failed: " << error_string() + << "-- dropping accepted connection " << connection; + + connection->close(); + return false; + } + } + + BITCOIN_ASSERT(connection->ssl_context().enabled); + } +#endif + + // Set all per-connection variables, based on user-specified + // or otherwise default settings. + connection->set_state(connection_state::connected); + connection->set_maximum_incoming_frame_length( + maximum_incoming_frame_length_); + connection->set_high_water_mark(high_water_mark_); + connection->set_user_data(user_data_); + connection->set_socket_non_blocking(); + + add_connection(connection); + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Accepted " << (ssl_ ? "SSL" : "Plaintext") << " connection: " + << connection << " on port " << port_; + return true; +} + +void manager::add_connection(const connection_ptr& connection) +{ + connections_.push_back(connection); + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Added Connection [" << connection + << ", " << connections_.size() << " total]"; +} + +void manager::remove_connection(connection_ptr& connection) +{ + auto it = std::find( + connections_.begin(), connections_.end(), connection); + if (it != connections_.end()) + { + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Removing Connection [" << connection + << ", " << connections_.size() - 1 << " remaining]"; + connections_.erase(it); + } + else + { + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Cannot locate connection for removal"; + } +} + +size_t manager::connection_count() +{ + return connections_.size(); +} + +bool manager::ssl() +{ + return ssl_; +} + +bool manager::listening() +{ + return listening_; +} + +void manager::start() +{ + static const auto timeout_milliseconds = 10u; + + running_ = true; + while (running_) + run_once(timeout_milliseconds); +} + +void manager::run_once(size_t timeout_milliseconds) +{ + if (stopped()) + return; + + // Run any executable tasks the user queued that must be + // run inside this thread. + run_tasks(); + + // Monitor and process sockets. + poll(timeout_milliseconds); +} + +void manager::stop() +{ + running_ = false; +} + +bool manager::stopped() +{ + return !running_; +} + +void manager::execute(std::shared_ptr task) +{ + task_lock_.lock(); + tasks_.push_back(task); + task_lock_.unlock(); +} + +void manager::run_tasks() +{ + task_list tasks; + + task_lock_.lock(); + tasks_.swap(tasks); + task_lock_.unlock(); + + for (auto& task: tasks) + if (!task->run()) + handle_connection(task->connection(), event::error); +} + +// Portable select based implementation. +void manager::poll(size_t timeout_milliseconds) +{ + static const size_t maximum_items = FD_SETSIZE; + // Break up number of connections into N lists of a specified + // maximum size and call select for each of them, given a + // timeout of (timeout_milliseconds / N). + // + // This is a hack to work around limitations of the select + // system call. Note that you may also need to adjust the + // descriptor limit for this process in order for this to work + // properly. + // + // With very large connection counts and small specified + // timeout values, this poll method may very well exceed the + // specified timeout. + if (connections_.size() > maximum_items) + { + const size_t number_of_lists = + (connections_.size() / maximum_items) + 1; + const auto adjusted_timeout = + std::ceil(timeout_milliseconds / number_of_lists); + std::vector connection_lists; + connection_lists.reserve(number_of_lists); + for (size_t i = 0; i < number_of_lists; i++) + { + connection_list connection_list; + connection_list.reserve(maximum_items); + connection_lists.push_back(connection_list); + } + + for (size_t i = 0, list_index = 0; i < connections_.size(); i++) + { + connection_lists[list_index].push_back(connections_[i]); + if ((i > 0) && ((i - 1) % maximum_items) == 0) + list_index++; + } + + for (auto& connection_list: connection_lists) + select(adjusted_timeout, connection_list); + } + else + { + select(timeout_milliseconds, connections_); + } +} + +void manager::select(size_t timeout_milliseconds, connection_list& connections) +{ + static const int maximum_items = FD_SETSIZE; + connection_ptr static_sockets[maximum_items]{}; + connection_ptr* socket_list = static_sockets; + + // This limit must be honored by the caller. + BITCOIN_ASSERT(connections.size() <= maximum_items); + + struct timeval poll_interval{}; + poll_interval.tv_sec = timeout_milliseconds / 1000; + poll_interval.tv_usec = (timeout_milliseconds * 1000) - + (poll_interval.tv_sec * 100000); + + fd_set read_set; + fd_set write_set; + fd_set error_set; + + FD_ZERO(&read_set); + FD_ZERO(&write_set); + FD_ZERO(&error_set); + + size_t last_index = 0; + int max_descriptor = 0; + + connection_list pending_removal; + for (auto& connection: connections) + { + auto descriptor = connection->socket(); + if (connection == nullptr || connection->closed()) + continue; + + // Check if the descriptor is too high to monitor in our + // fd_set. + if (descriptor > maximum_items) + { + // Attempt to resolve this by looking for a lower + // available descriptor. + auto new_descriptor = dup(descriptor); + if (new_descriptor < descriptor && new_descriptor < maximum_items) + { + connection->socket() = new_descriptor; + close_socket(descriptor); + } + else + { + // Select cannot monitor this descriptor. + close_socket(new_descriptor); + LOG_ERROR(LOG_SERVER_HTTP) + << "Error: cannot monitor socket " << descriptor + << ", value is above" << maximum_items; + pending_removal.push_back(connection); + continue; + } + } + + if (connection->file_transfer().in_progress || + !connection->write_buffer().empty()) + FD_SET(descriptor, &write_set); + + FD_SET(descriptor, &read_set); + FD_SET(descriptor, &error_set); + + socket_list[last_index++] = connection; + if (descriptor > max_descriptor) + max_descriptor = descriptor; + } + + for (auto& connection: pending_removal) + handle_connection(connection, event::error); + + pending_removal.clear(); + + const auto num_events = ::select(max_descriptor + 1, &read_set, + &write_set, &error_set, &poll_interval); + if (num_events == 0) + return; + + if (num_events < 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Error: select failed: " << error_string() + << "max fd: " << max_descriptor; + return; + } + + for (size_t i = 0; i < last_index; i++) + { + auto& connection = socket_list[i]; + if (connection == nullptr || connection->closed()) + continue; + + const auto descriptor = connection->socket(); + if (FD_ISSET(descriptor, &error_set)) + { + pending_removal.push_back(connection); + continue; + } + + if (FD_ISSET(descriptor, &write_set)) + { + if (connection->file_transfer().in_progress && !transfer_file_data( + connection)) + { + pending_removal.push_back(connection); + continue; + } + + auto& write_buffer = connection->write_buffer(); + if (!write_buffer.empty()) + { + const auto segment_length = std::min(write_buffer.size(), + static_cast(transfer_buffer_length)); + const auto written = connection->do_write( + reinterpret_cast( + write_buffer.data()), segment_length, false); + + if (written < 0) + { + pending_removal.push_back(connection); + continue; + } + + write_buffer.erase(write_buffer.begin(), + write_buffer.begin() + written); + } + } + + if (FD_ISSET(descriptor, &read_set)) + { + if (connection->state() == connection_state::listening) + { + if (!handle_connection(connection, event::listen)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Terminating due to error on listening socket"; + stop(); + return; + } + + // After accepting a connection, break out of loop + // since we're iterating over a list that's just been + // updated. + break; + } + else + { + const auto read = connection->read(); + if ((read == 0) || ((read < 0) && !would_block(last_error()))) + pending_removal.push_back(connection); + else if (!handle_connection(connection, event::read)) + pending_removal.push_back(connection); + } + } + } + + for (auto& connection: pending_removal) + handle_connection(connection, event::error); +} + +bool manager::handle_connection(connection_ptr& connection, event current_event) +{ + switch (current_event) + { + case event::listen: + { + if (!accept_connection()) + { + // Don't let this accept failure stop the service. + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to accept new connection"; + break; + } + + // We could allow the user to know that a connection was + // accepted here, but we instead opt to notify them only + // after the connection is upgraded to a websocket. + return true; + } + + case event::read: + { + int32_t read_length = connection->read_length(); + if (read_length <= 0) + break; + + auto& buffer = connection->read_buffer(); + if (connection->websocket()) + { + auto& transfer = connection->websocket_transfer(); + if (transfer.in_progress) + { + transfer.data.insert(transfer.data.end(), buffer.data(), + buffer.data() + read_length); + transfer.offset += read_length; + assert(transfer.offset == transfer.data.size()); + + // Check for configuration violation (helps + // prevent DoS by filling RAM with unexpectedly + // large messages). + if (static_cast(transfer.offset) > + maximum_incoming_frame_length_) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Terminating due to exceeding the " + "maximum_incoming_frame_length"; + return false; + } + + if (transfer.offset != transfer.length) + return true; + } + + return handle_websocket(connection); + } + + const auto request = std::string(buffer.data(), read_length); + http_request out{}; + if (parse_http(request, out)) + { + // Check if we need to convert HTTP connection to + // websocket. + if (out.upgrade_request) + return upgrade_connection(connection, out); + + // Check if we need to mark HTTP connection as + // expecting a JSON-RPC reply. If so, we need to call + // the user handler to notify user that a new json_rpc + // connection was accepted so that they can track it. + connection->set_json_rpc(out.json_rpc); + if (out.json_rpc) + { + return handler_(connection, event::accepted, nullptr) && + handler_(connection, event::json_rpc, reinterpret_cast< + void*>(&out)); + } + + // Call user's event handler with the parsed http + // request. + return (handler_(connection, event::read, reinterpret_cast< + void*>(&out)) ? send_response(connection, out) : false); + } + else + { + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Failed to parse HTTP request from " << request; + return handle_connection(connection, event::error); + } + + break; + } + + case event::write: + { + // Should never get here since writes are handled + // elsewhere. + BITCOIN_ASSERT(false); + return false; + } + + case event::error: + case event::closing: + { + if ((connection == nullptr) || connection->closed()) + return false; + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Connection closing: " << connection; + handler_(connection, event::closing, nullptr); + remove_connection(connection); + connection->close(); + return false; + } + + default: + return false; + } + + return true; +} + +#ifdef WITH_MBEDTLS +// static +int manager::ssl_send(void* data, const unsigned char* buffer, size_t length) +{ + auto connection = reinterpret_cast(data); +#ifdef MSG_NOSIGNAL + int flags = MSG_NOSIGNAL; +#else + int flags = 0; +#endif + const auto sent = static_cast(send(connection->socket(), buffer, + length, flags)); + if (sent >= 0) + return sent; + + return ((would_block(sent) || sent == EINPROGRESS) ? + MBEDTLS_ERR_SSL_WANT_WRITE : -1); +} + +// static +int manager::ssl_receive(void* data, unsigned char* buffer, size_t length) +{ + auto connection = reinterpret_cast(data); + auto read = static_cast(recv(connection->socket(), buffer, length, 0)); + if (read >= 0) + return read; + + return ((would_block(read) || read == EINPROGRESS) ? + MBEDTLS_ERR_SSL_WANT_READ : -1); +} +#endif + +bool manager::transfer_file_data(connection_ptr& connection) +{ + std::array buffer{}; + + auto& file_transfer = connection->file_transfer(); + if (!file_transfer.in_progress) + return false; + + auto data = reinterpret_cast(buffer.data()); + auto amount_to_read = std::min(transfer_buffer_length, + file_transfer.length - file_transfer.offset); + + auto read = fread(data, sizeof(unsigned char), amount_to_read, + file_transfer.descriptor); + + auto success = ((read == amount_to_read) || + ((read < amount_to_read) && feof(file_transfer.descriptor))); + + if (!success) + { + if (file_transfer.in_progress) + { + fclose(file_transfer.descriptor); + file_transfer.in_progress = false; + file_transfer.offset = 0; + file_transfer.length = 0; + } + return false; + } + + int32_t written = connection->write(buffer.data(), read); + if (written < 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Write failed: requested write of " << read << " and wrote " + << written; + return false; + } + + file_transfer.offset += read; + if (file_transfer.offset == file_transfer.length) + { + if (file_transfer.in_progress) + { + fclose(file_transfer.descriptor); + file_transfer.in_progress = false; + file_transfer.offset = 0; + file_transfer.length = 0; + } + } + + return success; +} + +bool manager::send_http_file(connection_ptr& connection, + const std::string& path, bool keep_alive) +{ + auto& file_transfer = connection->file_transfer(); + if (!file_transfer.in_progress) + { + file_transfer.descriptor = fopen(path.c_str(), "r"); + if (file_transfer.descriptor == nullptr) + return false; + + file_transfer.in_progress = true; + file_transfer.offset = 0; + file_transfer.length = boost::filesystem::file_size(path); + + http_reply reply; + const auto response = reply.generate(protocol_status::ok, + mime_type(path), file_transfer.length, keep_alive); + + if (!connection->write(response)) + return false; + } + + // On future iterations, this is called directly from poll while + // the file transfer is in progress. + return transfer_file_data(connection); +} + + +bool manager::handle_websocket(connection_ptr& connection) +{ + int32_t read_length = connection->read_length(); + if (read_length <= 0) + return false; + + auto& buffer = connection->read_buffer(); + unsigned char* data = nullptr; + int32_t length = 0; + int32_t mask_length = 0; + int32_t data_length = 0; + int32_t header_length = 0; + + auto& transfer = connection->websocket_transfer(); + data = (transfer.in_progress ? + reinterpret_cast(transfer.data.data()) : + reinterpret_cast(buffer.data())); + + const uint8_t flags = data[0]; + const auto op_code = static_cast(flags & 0x0f); + const auto is_fragment = (flags & 0x80) == 0 || (flags & 0x0f) == 0; + const auto reassemble = + (transfer.in_progress && transfer.offset > 0) && is_fragment; + const auto final_fragment = (flags & 0x80) != 0; + + if (is_fragment) + { + // RFC6455 Fragments are not currently supported. + LOG_ERROR(LOG_SERVER_HTTP) + << "Websocket fragments are not supported"; + return false; + } + + if (read_length < 2) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Invalid websocket frame"; + return false; + } + + length = static_cast(data[1] & 0x7f); + mask_length = static_cast(data[1] & 0x80 ? 4 : 0); + if (mask_length == 0) + { + // RFC6455: "The server MUST close the connection upon + // receiving a frame that is not masked." + LOG_ERROR(LOG_SERVER_HTTP) + << "No mask included from client"; + return false; + } + + if ((length < 126) && (read_length >= mask_length)) + { + data_length = length; + header_length = 2 + mask_length; + } + else if ((length == 126) && (read_length >= 4 + mask_length)) + { + data_length = ntohs( + *reinterpret_cast(&data[2])); + header_length = 4 + mask_length; + } + else if (read_length >= 10 + mask_length) + { + data_length = boost::endian::endian_reverse( + *reinterpret_cast(&data[2])); + header_length = 10 + mask_length; + } + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "websocket data_frame flags: " << static_cast(flags) + << ", is_fragment: " << (is_fragment ? "true" : "false") + << ", reassemble: " << (reassemble ? "true" : "false") + << ", final_fragment: " << (final_fragment ? "true" : "false"); + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Incoming websocket data frame length: " << data_length + << ", read length: " << read_length; + + // If the entire frame payload isn't buffered, initiate state + // to track the transfer of this frame. + if ((data_length > read_length) && !transfer.in_progress) + { + // Check if this transfer exceeds the maximum incoming + // frame length. + if (data_length > maximum_incoming_frame_length_) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Terminating connection due to exceeding the " + << "maximum_incoming_frame_length"; + return false; + } + + transfer.in_progress = true; + transfer.header_length = header_length; + transfer.length = data_length + header_length; + + data_buffer tmp_data; + transfer.data.swap(tmp_data); + transfer.data.reserve(transfer.length); + transfer.data.insert(transfer.data.end(), buffer.data(), + buffer.data() + header_length); + + transfer.mask.clear(); + if (mask_length) + { + const auto mask_start = + data + transfer.header_length - mask_length; + transfer.mask.reserve(mask_length); + transfer.mask.insert(transfer.mask.end(), mask_start, + mask_start + mask_length); + } + + transfer.offset = transfer.data.size(); + + LOG_DEBUG(LOG_SERVER_HTTP) + << "Initiated frame transfer for a length of " << data_length; + return true; + } + else + { + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Data length: " << data_length << ", Header length: " + << header_length << ", Mask length: " << mask_length; + } + + const auto frame_length = header_length + data_length; + if ((frame_length < header_length) || (frame_length < data_length)) + return false; + + const auto event_type = + (flags & 0x8 ? event::websocket_control_frame : + event::websocket_frame); + + if (event_type == event::websocket_control_frame) + { + // XOR mask the payload using the client provided mask. + const auto mask_start = data + header_length - mask_length; + for(int32_t i = 0; i < data_length; i++) + data[i + header_length] ^= mask_start[i % 4]; + + websocket_message message + { + connection->websocket_endpoint(), data + header_length, + data_length, flags, static_cast(flags & 0x0f) + }; + + // Possible TODO: If the opcode is a ping, send response here. + if (message.code != http::websocket_op::close) + LOG_DEBUG(LOG_SERVER_HTTP) + << "Unhandled websocket op: " << to_string(message.code); + + // Call user handler for control frames. + auto status = handler_(connection, event_type, + reinterpret_cast(&message)); + + // Returning false here causes the connection to be removed. + if (message.code == http::websocket_op::close) + return false; + + return status; + } + + if (final_fragment && transfer.in_progress && + (transfer.offset == transfer.length)) + { + const auto mask_start = transfer.mask.data(); + const auto payload_length = + transfer.length - transfer.header_length; + for(size_t i = 0; i < payload_length; i++) + data[i + transfer.header_length] ^= mask_start[i % 4]; + + websocket_message message + { + connection->websocket_endpoint(), + data + transfer.header_length, + static_cast(transfer.data.size() - transfer.header_length), + flags, + static_cast(flags & 0x0f) + }; + + // Call user handler on last fragment with the entire message. + const auto status = handler_(connection, event_type, + reinterpret_cast(&message)); + + transfer.in_progress = false; + transfer.offset = 0; + transfer.length = 0; + transfer.header_length = 0; + transfer.mask.clear(); + transfer.data.clear(); + + return status; + } + else if (!reassemble && (flags & 0x0f) == + static_cast(websocket_op::close)) + { + LOG_DEBUG(LOG_SERVER_HTTP) + << "Closing websocket due to close op."; + return false; + } + else + { + // Check if we need to read again. + if (data_length > read_length) + return true; + + // Apply websocket mask (if required) before user handling. + if ((mask_length > 0) && (read_length > data_length)) + { + const auto mask_start = data + header_length - mask_length; + for(int32_t i = 0; i < data_length; i++) + data[i + header_length] ^= mask_start[i % 4]; + } + + websocket_message message + { + connection->websocket_endpoint(), + data + header_length, + data_length, + flags, + op_code + }; + + // Call user handler for non-fragmented frames. + return handler_(connection, event_type, + reinterpret_cast(&message)); + } + + return false; +} + +bool manager::send_response(connection_ptr& connection, + const http_request& request) +{ + static const std::vector index_files = + { + { "index.html" }, + { "index.htm" }, + { "index.shtml" } + }; + + auto path = document_root_; + if (request.uri == "/") + { + for (const auto& index: index_files) + { + const auto test_path = path / index; + if (boost::filesystem::exists(test_path)) + { + path = test_path; + break; + } + } + + if (path == document_root_) + return false; + } + else + { + path /= request.uri; + } + + auto keep_alive = + ((request.protocol.find("HTTP/1.0") == std::string::npos) || + (request.header(std::string("Connection")) == "keep-alive")); + + if (!boost::filesystem::exists(path)) + { + static const size_t max_date_time_length = 32; + std::array time_buffer{}; + + time_t current_time = time(nullptr); + strftime(time_buffer.data(), time_buffer.size(), + "%a, %d %b %Y %H:%M:%S GMT", gmtime(¤t_time)); + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Requested Path: " << path << " does not exist"; + static const std::string response = std::string( + "HTTP/1.1 404 Not Found\r\n" + "Date: ") + time_buffer.data() + std::string("\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 90\r\n\r\n" + "Page not found" + "The page was not found.\r\n\r\n"); + + connection->write(response); + return true; + } + + return send_http_file( + connection, path.generic_string(), keep_alive); +} + +bool manager::send_generated_reply(connection_ptr& connection, + protocol_status status) +{ + http_reply reply; + static std::string empty_mime_type{}; + return connection->write(reply.generate(status, empty_mime_type, 0, false)); +} + +bool manager::upgrade_connection(connection_ptr& connection, + const http_request& request) +{ + // Request MUST be GET and Protocol must be at least 1.1 + if ((request.method != "get") || (request.protocol_version >= 1.1f)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Rejecting upgrade request for method " << request.method + << "/Protocol " << request.protocol; + send_generated_reply(connection, protocol_status::bad_request); + return false; + } + + // Verify if origin is acceptable (contains either + // localhost, hostname, or ip address of current server) + if (!validate_origin(request.header("origin"))) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Rejecting upgrade request for origin: " + << request.header("origin"); + send_generated_reply(connection, protocol_status::forbidden); + return false; + } + + const auto version = request.header("sec-websocket-version"); + if (!version.empty() && version != "13") + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Rejecting upgrade request for version: " << version; + send_generated_reply(connection, protocol_status::bad_request); + return false; + } + + const auto key = request.header("sec-websocket-key"); + if (key.empty()) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Rejecting upgrade request due to missing sec-websocket-key"; + send_generated_reply(connection, protocol_status::bad_request); + return false; + } + + http_reply reply; + const auto key_response = websocket_key_response(key); + const auto protocol = request.header("sec-websocket-protocol"); + const auto response = reply.generate_upgrade(key_response, protocol); + + // This write is unbuffered since we're not a websocket yet and + // want to be sure it completes before changing our state to be + // upgraded to a websocket. + if (connection->do_write(reinterpret_cast( + response.c_str()), response.size(), false) != static_cast( + response.size())) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to upgrade connection due to a write failure"; + return false; + } + + connection->set_websocket(true); + connection->set_websocket_endpoint(request.uri); + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Upgraded connection " << connection << " for uri " << request.uri; + + // On upgrade, call the user handler so they can track this + // websocket. + return handler_(connection, event::accepted, nullptr); +} + +bool manager::validate_origin(const std::string origin) +{ + static const size_t max_hostname_length = 128; + std::array hostname{}; + + if (origin.empty()) + return false; + + if (gethostname(hostname.data(), max_hostname_length) != 0) + return false; + + const auto ip = resolve_hostname({ hostname.data() }); + struct in_addr address; + std::memcpy(&address, &ip, sizeof(address)); + + const auto ip_address = std::string(inet_ntoa(address)); + const auto hostname_string = std::string{ hostname.data() }; + + return ((origin.find("127.0.0.1") != std::string::npos) || + (origin.find(hostname_string) != std::string::npos) || + (origin.find("localhost") != std::string::npos)); +} + +bool manager::initialize_ssl(connection_ptr& connection, bool listener) +{ +#ifdef WITH_MBEDTLS + auto& context = connection->ssl_context(); + auto& configuration = context.configuration; + + mbedtls_ssl_init(&context.context); + if (listener) + { + mbedtls_ssl_config_init(&configuration); + if (mbedtls_ssl_config_defaults(&configuration, MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) + return false; + + // TLS 1.2 and up + mbedtls_ssl_conf_min_version(&configuration, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_conf_rng(&configuration, https_random, nullptr); + } + + if (!certificate_.empty()) + { + if (key_.empty()) + key_ = certificate_; + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Using cert " << certificate_ << " and key " << key_; + + mbedtls_pk_init(&context.key); + mbedtls_x509_crt_init(&context.certificate); + + if (mbedtls_x509_crt_parse_file(&context.certificate, + certificate_.c_str()) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to parse certificate: " << certificate_; + return false; + } + + if (mbedtls_pk_parse_keyfile(&context.key, key_.c_str(), nullptr) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to parse key: " << key_; + return false; + } + + if (mbedtls_ssl_conf_own_cert(&configuration, &context.certificate, + &context.key) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to set own certificate chain and private key"; + return false; + } + } + + if (!ca_certificate_.empty() && ca_certificate_ != "*") + { + mbedtls_x509_crt_init(&context.ca_certificate); + if (mbedtls_x509_crt_parse_file(&context.ca_certificate, + ca_certificate_.c_str()) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Failed to parse CA certificate: " << ca_certificate_; + return false; + } + + mbedtls_ssl_conf_ca_chain(&configuration, &context.ca_certificate, + nullptr); + mbedtls_ssl_conf_authmode(&configuration, MBEDTLS_SSL_VERIFY_REQUIRED); + } + + if (!listener) + { + if (mbedtls_ssl_setup(&context.context, + &listener_->ssl_context().configuration) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "SSL setup failed for " << connection; + return false; + } + + if (!context.hostname.empty() && mbedtls_ssl_set_hostname( + &context.context, context.hostname.c_str()) != 0) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "SSL set hostname failed for " << connection; + return false; + } + + mbedtls_ssl_session_reset(&context.context); + } + + mbedtls_ssl_conf_ciphersuites(&configuration, default_ciphers); + + context.enabled = true; + return context.enabled; +#else + return false; +#endif +} + +} // namespace http +} // namespace server +} // namespace libbitcoin diff --git a/src/web/http/manager.hpp b/src/web/http/manager.hpp new file mode 100644 index 00000000..95194036 --- /dev/null +++ b/src/web/http/manager.hpp @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_MANAGER_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_MANAGER_HPP + +#include "connection.hpp" + +namespace libbitcoin { +namespace server { +namespace http { + +class manager +{ + public: + class task + { + public: + virtual ~task() = default; + virtual bool run() = 0; + virtual connection_ptr& connection() = 0; + }; + + typedef std::vector> task_list; + + explicit manager(bool ssl, event_handler handler, + boost::filesystem::path document_root); + ~manager(); + + // Required on the Windows platform. + bool initialize(); + + void set_maximum_incoming_frame_length(int32_t length); + int32_t maximum_incoming_frame_length(); + // May invalidate any buffered write data on each connection. + void set_high_water_mark(int32_t length); + int32_t high_water_mark(); + void set_backlog(size_t backlog); + bool bind(std::string hostname, uint16_t port, const bind_options& options); + bool accept_connection(); + void add_connection(const connection_ptr& connection); + void remove_connection(connection_ptr& connection); + size_t connection_count(); + bool ssl(); + bool listening(); + + void start(); + void stop(); + bool stopped(); + void execute(std::shared_ptr task); + void run_tasks(); + + void poll(size_t timeout_milliseconds); + bool handle_connection(connection_ptr& connection, event current_event); + + private: +#ifdef WITH_MBEDTLS + // Passed to mbedtls for internal use only. + static int ssl_send(void* data, const unsigned char* buffer, size_t length); + static int ssl_receive(void* data, unsigned char* buffer, size_t length); +#endif + + void run_once(size_t timeout_milliseconds); + void select(size_t timeout_milliseconds, connection_list& sockets); + bool transfer_file_data(connection_ptr& connection); + bool send_http_file(connection_ptr& connection, const std::string& path, + bool keep_alive); + bool handle_websocket(connection_ptr& connection); + bool send_response(connection_ptr& connection, const http_request& request); + bool send_generated_reply(connection_ptr& connection, + protocol_status status); + bool upgrade_connection(connection_ptr& connection, + const http_request& request); + bool validate_origin(const std::string origin); + bool initialize_ssl(connection_ptr& connection, bool listener); + void remove_connections(); + + bool ssl_; + bool listening_; + void* user_data_; + bool running_; + std::string key_; + std::string certificate_; + std::string ca_certificate_; + event_handler handler_; + boost::filesystem::path document_root_; + connection_list connections_; + int32_t maximum_incoming_frame_length_; + int32_t high_water_mark_; + size_t backlog_; + connection_ptr listener_; + struct sockaddr_in listener_address_; + task_list tasks_; + std::mutex task_lock_; + uint16_t port_; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp new file mode 100644 index 00000000..1a93a5ae --- /dev/null +++ b/src/web/http/utilities.cpp @@ -0,0 +1,392 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 "http.hpp" +#include "utilities.hpp" + +namespace libbitcoin { +namespace server { +namespace http { + +std::string error_string() +{ +#ifdef WIN32 + LPVOID buffer{}; + LPVOID display_buffer{}; + DWORD dw = last_error(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + static_cast(&buffer), 0, nullptr); + + display_buffer = static_cast(LocalAlloc(LMEM_ZEROINIT, + (lstrlen(static_cast(buffer)) + + lstrlen(static_cast(lpszFunction)) + 40) * sizeof(TCHAR))); + + StringCchPrintf(static_cast(display_buffer), + LocalSize(display_buffer) / sizeof(TCHAR), + TEXT("%s failed with error %d: %s"), + lpszFunction, dw, buffer); + + const std::string error_string = { display_buffer }; + + LocalFree(buffer); + LocalFree(display_buffer); + + return error_string; +#else + return { strerror(last_error()) }; +#endif +} + +#ifdef WITH_MBEDTLS +std::string mbedtls_error_string(int error) +{ + static constexpr size_t error_buffer_length = 256; + std::array data{}; + mbedtls_strerror(error, data.data(), data.size()); + return { data.data() }; +} + +sha1_hash sha1(const std::string& input) +{ + sha1_hash out{}; + mbedtls_sha1_context context; + mbedtls_sha1_init(&context); + + const auto source = reinterpret_cast(input.c_str()); + if ((mbedtls_sha1_starts_ret(&context) == 0) && + (mbedtls_sha1_update_ret(&context, source, input.size()) == 0) && + (mbedtls_sha1_finish_ret(&context, out.data()) == 0)) + return out; + + return {}; +} +#endif + +std::string to_string(websocket_op code) +{ + static const std::unordered_map opcode_map = + { + { static_cast(websocket_op::continuation), "continue" }, + { static_cast(websocket_op::text), "text" }, + { static_cast(websocket_op::binary), "binary" }, + { static_cast(websocket_op::close), "close" }, + { static_cast(websocket_op::ping), "ping" }, + { static_cast(websocket_op::pong), "pong" } + }; + + auto it = opcode_map.find(static_cast(code)); + if (it != opcode_map.end()) + return it->second; + + return { "unknown" }; +} + +// Generates the RFC6455 handshake response described here: +// https://tools.ietf.org/html/rfc6455#section-1.3 +std::string websocket_key_response(const std::string& websocket_key) +{ + static const std::string rfc6455_guid = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +#ifdef WITH_MBEDTLS + // The required buffer size is for a base64 encoded sha1 hash (20 + // bytes in length). + static constexpr size_t key_buffer_length = 64; + std::array buffer{}; + + size_t processed_length = 0; + const auto input = websocket_key + rfc6455_guid; + const auto hash = sha1(input); + + if ((mbedtls_base64_encode(buffer.data(), buffer.size(), &processed_length, + hash.data(), sha1_hash_length) != 0) || (processed_length == 0)) + return {}; + + return { reinterpret_cast(buffer.data()), processed_length }; +#else + const auto input = websocket_key + rfc6455_guid; + const data_chunk input_data(input.begin(), input.end()); + const data_slice slice(bc::sha1_hash(input_data)); + return encode_base64(slice); +#endif +} + +bool is_json_request(const std::string& header_value) +{ + return header_value == "application/json-rpc" || + header_value == "application/json" || + header_value == "application/jsonrequest"; +} + +bool parse_http(const std::string& request, struct http_request& out) +{ + auto& headers = out.headers; + auto& parameters = out.parameters; + + out.message_length = request.size(); + + // Parse out the first line for: Method, URI, Protocol. + auto position = request.find("\r\n"); + if (position == std::string::npos) + return false; + + std::string method_line = request.substr(0, position); + string_list elements; + boost::split(elements, method_line, boost::is_any_of(" ")); + if (elements.size() != 3) + return false; + + for (auto& element: elements) + boost::algorithm::trim(element); + + // truncate the parameters from the uri (if any) + position = elements[1].find("?"); + if (position != std::string::npos) + elements[1] = elements[1].substr(0, position); + + boost::algorithm::to_lower(elements[0]); + boost::algorithm::to_lower(elements[2]); + + out.method = elements[0]; + out.uri = elements[1]; + out.protocol = elements[2]; + + position = out.protocol.find("/"); + if (position != std::string::npos) + { + const auto version = out.protocol.substr(position); + out.protocol_version = strtod(version.c_str(), nullptr); + } + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Parsing HTTP request: Method: " << out.method << ", Uri: " + << out.uri << ", Protocol: " << out.protocol; + + // Parse out remaining lines to build both the header map and + // parameter map. + string_list header_lines; + boost::split(header_lines, request, boost::is_any_of("\r\n")); + + auto parse_line = [&headers](const std::string& line) + { + if (line.empty()) + return; + + string_list elements; + boost::split(elements, line, boost::is_any_of(":")); + if (elements.size() == 2) + { + boost::algorithm::trim(elements[0]); + boost::algorithm::trim(elements[1]); + boost::algorithm::to_lower(elements[0]); + if (elements[0] != "sec-websocket-key") + boost::algorithm::to_lower(elements[1]); + headers[elements[0]] = elements[1]; + } + else if (elements.size() > 2) + { + std::stringstream ss; + for (size_t i = 0; i < elements.size(); i++) + { + boost::algorithm::trim(elements[i]); + if (elements[0] != "sec-websocket-key") + boost::algorithm::to_lower(elements[i]); + if (i > 0) + { + ss << elements[i]; + if (i != elements.size() -1) + ss << ":"; + } + } + + headers[elements[0]] = ss.str(); + } + }; + + std::for_each(header_lines.begin(), header_lines.end(), parse_line); + + // Parse passed parameters (if any) + position = method_line.find("?"); + if (position != std::string::npos) + { + auto parameter_string = method_line.substr(position + 1); + + string_list parameter_elements; + boost::split(parameter_elements, parameter_string, + boost::is_any_of("&")); + + auto parse_parameter = [¶meters](const std::string& line) + { + if (line.empty()) + return; + + auto line_end = line.find(" "); + const auto input_line = ((line_end == std::string::npos) ? line : + line.substr(0, line_end)); + + string_list elements; + boost::split(elements, input_line, boost::is_any_of("= ")); + if (elements.size() == 2) + { + boost::algorithm::trim(elements[0]); + boost::algorithm::trim(elements[1]); + boost::algorithm::to_lower(elements[0]); + boost::algorithm::to_lower(elements[1]); + parameters[elements[0]] = elements[1]; + } + }; + + std::for_each(parameter_elements.begin(), parameter_elements.end(), + parse_parameter); + } + + // Determine if this request contains the content length. + auto content_length = headers.find("content-length"); + out.content_length = (content_length != headers.end() ? + std::strtoul(content_length->second.c_str(), nullptr, 0) : 0); + + // Determine if this request is an upgrade request + auto connection = headers.find("connection"); + out.upgrade_request = ((connection != headers.end()) && + (connection->second.find("upgrade") != std::string::npos) && + (headers.find("sec-websocket-key") != headers.end())); + + // Determine if this request is a JSON-RPC request, or at least + // one that we support (i.e. non-standard; may not contain the + // required accept header nor the optional content-type header), + // via POST-only, etc. Instead we try to safely parse the data as + // JSON. + if (out.method == "post" && out.content_length > 0) + { + const auto json_request = request.substr(request.size() - + out.content_length, out.content_length); + LOG_VERBOSE(LOG_SERVER_HTTP) + << "POST content: " << json_request; + out.json_rpc = bc::property_tree(out.json_tree, json_request); + } + + return true; +} + +unsigned long resolve_hostname(const std::string& hostname) +{ + unsigned long address = 0; + +#ifdef WIN32 +#define get_address(host, address) *address = inet_addr(host) +#define validate_address(address) (address != INADDR_NONE) +#define host_info HOSTENT +#else +#define get_address(host, address) inet_pton(AF_INET, host, address) +#define validate_address(address) (address > 0) +#define host_info struct hostent +#endif + + // Resolve dotted ip address. + if (!validate_address(get_address(hostname.c_str(), &address))) + { + // Resolve host name. + const host_info *resolved = gethostbyname(hostname.c_str()); + if (resolved) + address = *(reinterpret_cast( + resolved->h_addr_list[0])); + } + +#undef get_address +#undef validate_address +#undef host_info + + return address; +} + +std::string mime_type(const std::string& filename) +{ + std::unordered_map mime_type_map = + { + { "html", "text/html" }, + { "htm", "text/html" }, + { "shtm", "text/html" }, + { "shtml", "text/html" }, + { "css", "text/css" }, + { "js", "application/x-javascript" }, + { "ico", "image/x-icon" }, + { "gif", "image/gif" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "png", "image/png" }, + { "svg", "image/svg+xml" }, + { "md", "text/plain" }, + { "txt", "text/plain" }, + { "torrent", "application/x-bittorrent" }, + { "wav", "audio/x-wav" }, + { "mp3", "audio/x-mp3" }, + { "mid", "audio/mid" }, + { "m3u", "audio/x-mpegurl" }, + { "ogg", "application/ogg" }, + { "ram", "audio/x-pn-realaudio" }, + { "xml", "text/xml" }, + { "ttf", "application/x-font-ttf" }, + { "json", "application/json" }, + { "xslt", "application/xml" }, + { "xsl", "application/xml" }, + { "ra", "audio/x-pn-realaudio" }, + { "doc", "application/msword" }, + { "exe", "application/octet-stream" }, + { "zip", "application/x-zip-compressed" }, + { "xls", "application/excel" }, + { "tgz", "application/x-tar-gz" }, + { "tar", "application/x-tar" }, + { "gz", "application/x-gunzip" }, + { "arj", "application/x-arj-compressed" }, + { "rar", "application/x-rar-compressed" }, + { "rtf", "application/rtf" }, + { "pdf", "application/pdf" }, + { "swf", "application/x-shockwave-flash" }, + { "mpg", "video/mpeg" }, + { "webm", "video/webm" }, + { "mpeg", "video/mpeg" }, + { "mov", "video/quicktime" }, + { "mp4", "video/mp4" }, + { "m4v", "video/x-m4v" }, + { "asf", "video/x-ms-asf" }, + { "avi", "video/x-msvideo" }, + { "bmp", "image/bmp" } + }; + + const auto dot_position = filename.rfind("."); + if (dot_position != std::string::npos) + { + const auto extension = filename.substr(dot_position + 1); + auto it = mime_type_map.find(extension); + if (it != mime_type_map.end()) + return it->second; + } + + return { "text/plain" }; +} + +} // namespace http +} // namespace server +} // namespace libbitcoin diff --git a/src/web/http/utilities.hpp b/src/web/http/utilities.hpp new file mode 100644 index 00000000..2a1f0f42 --- /dev/null +++ b/src/web/http/utilities.hpp @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_UTILITIES_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_UTILITIES_HPP + +namespace libbitcoin { +namespace server { +namespace http { + +#ifdef WIN32 +#define last_error() GetLastError() +#else +#define last_error() errno +#endif + +#ifdef WIN32 +#define would_block(x) (x == WSAEWOULDBLOCK) +#else +#define would_block(x) (x == EAGAIN || x == EWOULDBLOCK) +#endif + +#define mbedtls_would_block(x) \ +(x == MBEDTLS_ERR_SSL_WANT_READ || x == MBEDTLS_ERR_SSL_WANT_WRITE) + +std::string error_string(); +#ifdef WITH_MBEDTLS +std::string mbedtls_error_string(int error); +sha1_hash sha1(const std::string& input); +#endif +std::string to_string(websocket_op code); +std::string websocket_key_response(const std::string& websocket_key); +bool is_json_request(const std::string& header_value); +bool parse_http(const std::string& request, struct http_request& out); +unsigned long resolve_hostname(const std::string& hostname); +std::string mime_type(const std::string& filename); + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/src/web/json_string.cpp b/src/web/json_string.cpp new file mode 100644 index 00000000..1d219894 --- /dev/null +++ b/src/web/json_string.cpp @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 + +// Explicitly use std::placeholders here for usage internally to the +// boost parsing helpers included from json_parser.hpp. +// See: https://svn.boost.org/trac10/ticket/12621 +#include +using namespace std::placeholders; + +#include +#include + +#include + +namespace libbitcoin { +namespace server { +namespace web { +using namespace boost::property_tree; + +static constexpr auto json_encoding = true; + +// Object to JSON converters. +//----------------------------------------------------------------------------- + +std::string to_json(const ptree& tree) +{ + std::stringstream json_stream; + write_json(json_stream, tree); + return json_stream.str(); +} + +std::string to_json(const ptree& tree, uint32_t id) +{ + ptree result_tree; + result_tree.add_child("result", tree); + result_tree.put("id", id); + return to_json(result_tree); +} + +std::string to_json(uint64_t height, uint32_t id) +{ + ptree tree; + tree.put("result", height); + tree.put("id", id); + return to_json(tree); + // NOTE: The bc::property_tree call works fine, but the format is + // different than expected for json_rpc so eventually we need to + // separate out property_tree and json_rpc::property_tree, or + // something along the lines to make this a clear distinction. + /* return to_json(bc::property_tree(height, id)); */ +} + +std::string to_json(const int code, const std::string message, uint32_t id) +{ + ptree tree; + ptree error_tree; + error_tree.put("code", code); + error_tree.put("message", message); + tree.add_child("error", error_tree); + tree.put("id", id); + return to_json(tree); +} + +std::string to_json(const std::error_code& code, uint32_t id) +{ + ptree tree; + ptree error_tree; + error_tree.put("code", code.value()); + error_tree.put("message", code.message()); + tree.add_child("error", error_tree); + tree.put("id", id); + return to_json(tree); + /* return to_json(bc::property_tree(code, id)); */ +} + +std::string to_json(const bc::chain::header& header, uint32_t id) +{ + return to_json(bc::property_tree(bc::config::header(header)), id); +} + +std::string to_json(const bc::chain::block& block, uint32_t id) +{ + auto tree = bc::property_tree(block, json_encoding); + return to_json(tree, id); +} + +std::string to_json(const bc::chain::block& block, uint32_t /* height */, + uint32_t id) +{ + return to_json(bc::property_tree(bc::config::header(block.header())), id); +} + +std::string to_json(const bc::chain::transaction& transaction, + uint32_t id) +{ + return to_json(bc::property_tree(bc::config::transaction( + transaction), json_encoding), id); +} + +} // namespace web +} // namespace server +} // namespace libbitcoin diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp new file mode 100644 index 00000000..959f1515 --- /dev/null +++ b/src/web/query_socket.cpp @@ -0,0 +1,406 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 + +// Explicitly use std::placeholders here for usage internally to the +// boost parsing helpers included from json_parser.hpp. +// See: https://svn.boost.org/trac10/ticket/12621 +#include +using namespace std::placeholders; + +#include "../src/web/http/connection.hpp" + +namespace libbitcoin { +namespace server { + +using namespace bc::config; +using namespace bc::machine; +using namespace bc::protocol; +using role = zmq::socket::role; + +static const auto domain = "query"; +static constexpr auto poll_interval_milliseconds = 100u; + +query_socket::query_socket(zmq::authenticator& authenticator, + server_node& node, bool secure) + : socket(authenticator, node, secure, domain) +{ + // JSON to ZMQ request encoders. + //------------------------------------------------------------------------- + + const auto encode_empty = [](zmq::message& request, + const std::string& command, const std::string& /* arguments */, + uint32_t id) + { + request.enqueue(command); + request.enqueue_little_endian(id); + request.enqueue(data_chunk{}); + }; + + const auto encode_hash = [](zmq::message& request, + const std::string& command, const std::string& arguments, uint32_t id) + { + hash_digest hash; + DEBUG_ONLY(const auto result =) decode_hash(hash, arguments); + BITCOIN_ASSERT(result); + request.enqueue(command); + request.enqueue_little_endian(id); + request.enqueue(to_chunk(hash)); + }; + + // JSON to ZMQ response decoders. + //------------------------------------------------------------------------- + const auto decode_height = [this](const data_chunk& data, const uint32_t id, + connection_ptr connection) + { + data_source istream(data); + istream_reader source(istream); + const auto height = source.read_4_bytes_little_endian(); + send(connection, web::to_json(height, id)); + }; + + const auto decode_transaction = [this, &node](const data_chunk& data, + const uint32_t id, connection_ptr connection) + { + const auto witness = chain::script::is_enabled( + node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); + const auto transaction = chain::transaction::factory(data, true, + witness); + send(connection, web::to_json(transaction, id)); + }; + + const auto decode_block = [this, &node](const data_chunk& data, + const uint32_t id,connection_ptr connection) + { + const auto witness = chain::script::is_enabled( + node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); + const auto block = chain::block::factory(data, witness); + send(connection, web::to_json(block, id)); + }; + + const auto decode_block_header = [this](const data_chunk& data, + const uint32_t id,connection_ptr connection) + { + const auto header = chain::header::factory(data, true); + send(connection, web::to_json(header, id)); + }; + + handlers_["getblockcount"] = handlers + { + "blockchain.fetch_last_height", + encode_empty, + decode_height + }; + + handlers_["getrawtransaction"] = handlers + { + "transaction_pool.fetch_transaction", + encode_hash, + decode_transaction + }; + + handlers_["getblock"] = handlers + { + "blockchain.fetch_block", + encode_hash, + decode_block + }; + + handlers_["getblockheader"] = handlers + { + "blockchain.fetch_block_header", + encode_hash, + decode_block_header + }; +} + +void query_socket::work() +{ + zmq::socket dealer(authenticator_, role::dealer, protocol_settings_); + zmq::socket query_receiver(authenticator_, role::pair, protocol_settings_); + + auto ec = query_receiver.bind(query_endpoint()); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to bind " << security_ << " query internal socket: " + << ec.message(); + return; + } + + const auto endpoint = zeromq_endpoint().to_local(); + ec = dealer.connect(endpoint); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to connect to " << security_ << " query socket " + << endpoint << ": " << ec.message(); + return; + } + + if (!started(start_websocket_handler())) + { + LOG_ERROR(LOG_SERVER) + << "Failed to start " << security_ << " websocket handler: " + << ec.message(); + return; + } + + // Hold a shared reference to the websocket thread_ so that we can + // properly call stop_websocket_handler on cleanup. + const auto thread_ref = thread_; + + zmq::poller poller; + poller.add(dealer); + poller.add(query_receiver); + + while (!poller.terminated() && !stopped()) + { + const auto identifiers = poller.wait(poll_interval_milliseconds); + + if (identifiers.contains(query_receiver.id()) && + !forward(query_receiver, dealer)) + break; + + if (identifiers.contains(dealer.id()) && !handle_query(dealer)) + break; + } + + const auto query_stop = query_receiver.stop(); + const auto dealer_stop = dealer.stop(); + const auto websocket_stop = stop_websocket_handler(); + + if (!query_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to unbind " << security_ << " websocket query service."; + + if (!dealer_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to disconnect " << security_ << " query connection."; + + if (!websocket_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to stop " << security_ + << " query websocket handler"; + + finished(query_stop && dealer_stop && websocket_stop); +} + +// Called by this thread's zmq work() method. Returns true to +// continue future notifications. +// +// Correlation lock usage is required because it protects the shared +// correlation map of query ids, which is also used by the websocket +// thread event handler (e.g. remove_connection, notify_query_work). +bool query_socket::handle_query(zmq::socket& dealer) +{ + if (stopped()) + return false; + + zmq::message response; + auto ec = dealer.receive(response); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to receive response from dealer: " << ec.message(); + return true; + } + + static constexpr size_t query_message_size = 3; + if (response.empty() || response.size() != query_message_size) + { + LOG_WARNING(LOG_SERVER) + << "Failure handling query response: invalid data size."; + return true; + } + + uint32_t sequence{}; + data_chunk data; + std::string command; + + if (!response.dequeue(command) || !response.dequeue(sequence) || + !response.dequeue(data)) + { + LOG_WARNING(LOG_SERVER) + << "Failure handling query response: invalid data parts."; + return true; + } + + /////////////////////////////////////////////////////////////////////// + // Critical Section + correlation_lock_.lock_upgrade(); + + // Use internal sequence number to find connection and work id. + auto correlation = correlations_.find(sequence); + if (correlation == correlations_.end()) + { + correlation_lock_.unlock_upgrade(); + + // This will happen anytime the client disconnects before this + // handler is called. We can safely discard the result here. + LOG_DEBUG(LOG_SERVER) + << "Unmatched websocket query work item sequence: " << sequence; + return true; + } + + auto connection = correlation->second.first; + const auto id = correlation->second.second; + + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + correlation_lock_.unlock_upgrade_and_lock(); + correlations_.erase(correlation); + // TODO: Can this 2 step unlock be done atomically? + correlation_lock_.unlock_and_lock_upgrade(); + correlation_lock_.unlock_upgrade(); + /////////////////////////////////////////////////////////////////////// + + // Use connection to locate connection state. + auto it = connections_.find(connection); + if (it == connections_.end()) + { + LOG_ERROR(LOG_SERVER) + << "Query work completed for unknown connection"; + return true; + } + + // Use work id to locate the query work item. + auto& query_work_map = it->second; + auto query_work = query_work_map.find(id); + if (query_work == query_work_map.end()) + { + // This can happen anytime the client disconnects before this + // code is reached. We can safely discard the result here. + LOG_DEBUG(LOG_SERVER) + << "Unmatched websocket query work id: " << id; + return true; + } + + const auto work = query_work->second; + query_work_map.erase(query_work); + + BITCOIN_ASSERT(work.id == id); + BITCOIN_ASSERT(work.correlation_id == sequence); + + data_source istream(data); + istream_reader source(istream); + ec = source.read_error_code(); + if (ec) + { + send(work.connection, web::to_json(ec, id)); + return true; + } + + const auto handler = handlers_.find(work.command); + if (handler == handlers_.end()) + { + static constexpr auto error = bc::error::not_implemented; + send(work.connection, web::to_json(error, id)); + return true; + } + + // Decode response and send query output to websocket client. The + // websocket write is performed on the websocket thread via the + // task_sender. + const auto payload = source.read_bytes(); + handler->second.decode(payload, id, work.connection); + return true; +} + +const endpoint& query_socket::zeromq_endpoint() const +{ + // The Websocket to zeromq backend internally always uses the + // local public zeromq endpoint since it does not affect the + // external security of the websocket endpoint and impacts + // configuration and performance for no additional gain. + return server_settings_.zeromq_query_endpoint(false /* secure_ */); +} + +const endpoint& query_socket::websocket_endpoint() const +{ + return server_settings_.websockets_query_endpoint(secure_); +} + +const endpoint& query_socket::query_endpoint() const +{ + static const endpoint secure_query("inproc://secure_query_websockets"); + static const endpoint public_query("inproc://public_query_websockets"); + return secure_ ? secure_query : public_query; +} + +const std::shared_ptr query_socket::service() const +{ + return service_; +} + +// This method is run by the web socket thread. +void query_socket::handle_websockets() +{ + // A zmq socket must remain on its single thread. + service_ = std::make_shared(authenticator_, role::pair, + protocol_settings_); + + // Hold a reference to this service_ socket member by this thread + // method so that we can properly shutdown even if the + // query_socket object is destroyed. + const auto service_ref = service_; + const auto ec = service_ref->connect(query_endpoint()); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to connect " << security_ << " query sender socket: " + << ec.message(); + thread_status_.set_value(false); + return; + } + + // socket::handle_websockets does web socket initialization and + // runs the web event loop. Inside that loop, socket::poll + // eventually calls into the static handle_event callback, which + // calls socket::notify_query_work, which uses this 'service_' zmq + // socket for sending incoming requests and reading the json web + // responses. + socket::handle_websockets(); + + if (!service_ref->stop()) + { + LOG_ERROR(LOG_SERVER) + << "Failed to disconnect " << security_ << " query sender."; + } +} + +bool query_socket::start_websocket_handler() +{ + std::future status = thread_status_.get_future(); + thread_ = std::make_shared(&query_socket::handle_websockets, + this); + status.wait(); + return status.get(); +} + +} // namespace server +} // namespace libbitcoin diff --git a/src/web/socket.cpp b/src/web/socket.cpp new file mode 100644 index 00000000..0cad7759 --- /dev/null +++ b/src/web/socket.cpp @@ -0,0 +1,538 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 +#include + +#ifdef WITH_MBEDTLS +extern "C" +{ +int https_random(void*, uint8_t* buffer, size_t length) +{ + bc::data_chunk random; + random.reserve(length); + bc::pseudo_random_fill(random); + std::memcpy(buffer, reinterpret_cast(random.data()), length); + return 0; +} +} +#endif + +#include "../src/web/http/http.hpp" +#include "../src/web/http/utilities.hpp" +#include "../src/web/http/manager.hpp" +#include "../src/web/http/connection.hpp" + +namespace libbitcoin { +namespace server { + +using namespace asio; +using namespace bc::chain; +using namespace bc::protocol; +using namespace boost::filesystem; +using namespace boost::iostreams; +using namespace boost::property_tree; +using namespace http; +using role = zmq::socket::role; + +class task_sender : public http::manager::task +{ + public: + task_sender(connection_ptr connection, const std::string& data) + : connection_(connection), + data_(data) + { + } + + bool run() + { + if (connection_ == nullptr || connection_->closed()) + return false; + + if (connection_->json_rpc()) + { + static auto keep_alive = false; + + http_reply reply; + const auto response = reply.generate(protocol_status::ok, {}, + data_.size(), keep_alive) + data_; + LOG_VERBOSE(LOG_SERVER_HTTP) << "Writing JSON-RPC response: " << response; + return connection_->write(response) == static_cast( + response.size()); + } + + return connection_->write(data_) == static_cast(data_.size()); + } + + connection_ptr& connection() + { + return connection_; + } + + private: + connection_ptr connection_; + const std::string data_; +}; + +// The protocol message_size_limit is on the order of 1M. The maximum +// websocket frame size is set to a much smaller fraction of this +// because our websocket protocol implementation does not contain +// large incoming messages, and also to help avoid DoS attacks via +// large incoming messages. +static constexpr auto maximum_incoming_websocket_message_size = 4096u; + +// static +// Callback made internally via socket::poll on the web socket thread. +bool socket::handle_event(connection_ptr& connection, const http::event event, + const void* data) +{ + switch (event) + { + case http::event::accepted: + { + // This connection is newly accepted and is either an HTTP + // JSON-RPC connection, or an already upgraded websocket. + // Returning false here will cause the service to stop + // accepting new connections. + auto instance = static_cast(connection->user_data()); + BITCOIN_ASSERT(instance != nullptr); + instance->add_connection(connection); + + const auto connection_type = connection->json_rpc() ? "JSON-RPC" : + "Websocket"; + LOG_DEBUG(LOG_SERVER) + << connection_type << " client connection established [" + << connection << "] (" << instance->connection_count() << ")"; + break; + } + + case http::event::json_rpc: + { + // Process new incoming user json_rpc request. Returning + // false here will cause this connection to be closed. + auto instance = static_cast(connection->user_data()); + BITCOIN_ASSERT(instance != nullptr); + BITCOIN_ASSERT(data != nullptr); + const auto& request = *reinterpret_cast(data); + BITCOIN_ASSERT(request.json_rpc); + + // Use default-value version of get to avoid exceptions on + // invalid input. + const auto id = request.json_tree.get("id", 0); + const auto method = request.json_tree.get("method", ""); + std::string parameters{}; + + const auto child = request.json_tree.get_child("params"); + std::vector parameter_list; + for (auto& parameter: child) + parameter_list.push_back( + parameter.second.get_value()); + + // TODO: Support full parameter lists? + if (!parameter_list.empty()) + parameters = parameter_list[0]; + + LOG_VERBOSE(LOG_SERVER) + << "method " << method << ", parameters " << parameters + << ", id " << id; + + instance->notify_query_work(connection, method, id, parameters); + break; + } + + case http::event::websocket_frame: + { + // Process new incoming user websocket data. Returning + // false here will cause this connection to be closed. + auto instance = static_cast(connection->user_data()); + BITCOIN_ASSERT(instance != nullptr); + if (instance == nullptr) + return false; + BITCOIN_ASSERT(data != nullptr); + const auto& message = *reinterpret_cast( + data); + + ptree input_tree; + if (!bc::property_tree(input_tree, std::string( + reinterpret_cast(message.data), message.size))) + { + http_reply reply; + connection->write(reply.generate( + protocol_status::internal_server_error, {}, 0, false)); + return false; + } + + // Use default-value version of get to avoid exceptions on + // invalid input. + const auto id = input_tree.get("id", 0); + const auto method = input_tree.get("method", ""); + std::string parameters{}; + + const auto child = input_tree.get_child("params"); + std::vector parameter_list; + for (auto& parameter: child) + parameter_list.push_back( + parameter.second.get_value()); + + // TODO: Support full parameter lists? + if (!parameter_list.empty()) + parameters = parameter_list[0]; + + LOG_VERBOSE(LOG_SERVER) + << "method " << method << ", parameters " << parameters + << ", id " << id; + + instance->notify_query_work(connection, method, id, parameters); + break; + } + + case http::event::closing: + { + // This connection is going away after this handling. + auto instance = static_cast(connection->user_data()); + BITCOIN_ASSERT(instance != nullptr); + instance->remove_connection(connection); + + if (connection->websocket()) + { + const auto connection_type = connection->json_rpc() ? + "JSON-RPC" : "Websocket"; + LOG_DEBUG(LOG_SERVER) + << connection_type << " client disconnected [" << connection + << "] (" << instance->connection_count() << ")"; + } + + break; + } + + // No specific handling required for other events. + case http::event::read: + case http::event::error: + case http::event::websocket_control_frame: + default: + break; + } + + return true; +} + +socket::socket(zmq::authenticator& authenticator, server_node& node, + bool secure, const std::string& domain) + : worker(priority(node.server_settings().priority)), + authenticator_(authenticator), + secure_(secure), + security_(secure ? "secure" : "public"), + server_settings_(node.server_settings()), + protocol_settings_(node.protocol_settings()), + sequence_(0), + domain_(domain), + document_root_(node.server_settings().websockets_root) +{ +} + +bool socket::start() +{ + if (!exists(document_root_)) + { + LOG_ERROR(LOG_SERVER) + << "Configured HTTP root path '" << document_root_ + << "' does not exist."; + return false; + } + + if (secure_) + { + if (!exists(server_settings_.websockets_server_certificate)) + { + LOG_ERROR(LOG_SERVER) + << "Required server certificate '" + << server_settings_.websockets_server_certificate + << "' does not exist."; + return false; + } + + if (!exists(server_settings_.websockets_server_private_key)) + { + LOG_ERROR(LOG_SERVER) + << "Required server private key '" + << server_settings_.websockets_server_private_key + << "' does not exist."; + return false; + } + } + + return zmq::worker::start(); +} + +void socket::handle_websockets() +{ + const auto& endpoint = websocket_endpoint(); + http::bind_options options; + manager_ = std::make_shared(secure_, &socket::handle_event, + document_root_); + + if (manager_ == nullptr || !manager_->initialize()) + { + LOG_ERROR(LOG_SERVER) + << "Failed to initialize websocket manager"; + thread_status_.set_value(false); + return; + } + + if (secure_) + { + options.ssl_ca_certificate = + (exists(server_settings_.websockets_ca_certificate) ? + server_settings_.websockets_ca_certificate.generic_string() : "*"); + options.ssl_key = + server_settings_.websockets_server_private_key.generic_string(); + options.ssl_certificate = + server_settings_.websockets_server_certificate.generic_string(); + } + + options.user_data = static_cast(this); + if (!manager_->bind(endpoint.to_local().host(), endpoint.port(), options)) + { + LOG_ERROR(LOG_SERVER) + << "Failed to bind listener websocket to port " << endpoint.port(); + thread_status_.set_value(false); + return; + } + + LOG_INFO(LOG_SERVER) + << "Bound " << security_ << " " << domain_ << " websocket to port " + << endpoint.port(); + + manager_->set_maximum_incoming_frame_length( + maximum_incoming_websocket_message_size); + thread_status_.set_value(true); + + manager_->start(); +} + +const std::shared_ptr socket::service() const +{ + return nullptr; +} + +bool socket::start_websocket_handler() +{ + std::future status = thread_status_.get_future(); + thread_ = std::make_shared(&socket::handle_websockets, this); + status.wait(); + return status.get(); +} + +bool socket::stop_websocket_handler() +{ + BITCOIN_ASSERT(manager_ != nullptr); + manager_->stop(); + thread_->join(); + return true; +} + +size_t socket::connection_count() const +{ + return connections_.size(); +} + +// Called by the websocket handling thread via handle_event. +void socket::add_connection(connection_ptr& connection) +{ + BITCOIN_ASSERT(connections_.find(connection) == connections_.end()); + // Initialize a new query_work_map for this connection. + connections_[connection] = {}; +} + +// Called by the websocket handling thread via handle_event. +// Correlation lock usage is required because it protects the shared +// correlation map of ids, which can also used by the zmq service +// thread on response handling (i.e. query_socket::handle_query). +void socket::remove_connection(connection_ptr& connection) +{ + if (connections_.empty()) + return; + + auto it = connections_.find(connection); + if (it != connections_.end()) + { + // Tearing down a connection is O(n) where n is the amount of + // remaining outstanding queries. + + //////////////////////////////////////////////////////////////////// + // Critical Section + correlation_lock_.lock_upgrade(); + auto& query_work_map = it->second; + for (auto query_work: query_work_map) + { + const auto id = query_work.second.correlation_id; + auto correlation = correlations_.find(id); + if (correlation != correlations_.end()) + { + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + correlation_lock_.unlock_upgrade_and_lock(); + correlations_.erase(correlation); + correlation_lock_.unlock_and_lock_upgrade(); + } + } + correlation_lock_.unlock_upgrade(); + //////////////////////////////////////////////////////////////////// + + // Clear the query_work_map for this connection before removal. + query_work_map.clear(); + connections_.erase(it); + } +} + +// Called by the websocket handling thread via handle_event. +// Correlation lock usage is required because it protects the shared +// correlation map of ids, which is also used by the zmq service +// thread on query response handling. +// +// Errors write directly on the connection since this is called from +// the event_handler, which is called on the websocket thread. +void socket::notify_query_work(connection_ptr& connection, + const std::string& method, const uint32_t id, + const std::string& parameters) +{ + typedef std::pair error; + static const error invalid_request{ -32600, "Invalid Request." }; + static const error not_found{ -32601, "Method not found." }; + static const error internal_error{ -32603, "Internal error." }; + + auto send_error_reply = [&connection, id](const protocol_status status, + int code, const std::string message) + { + http_reply reply; + const auto error = web::to_json(code, message, id); + const auto response = reply.generate(status, {}, error.size(), false); + LOG_VERBOSE(LOG_SERVER) << error + response; + connection->write(error + response); + }; + + if (handlers_.empty()) + { + send_error_reply(protocol_status::service_unavailable, + invalid_request.first, invalid_request.second); + // This error will most commonly present when a client + // connects to one of the other websocket services (other than + // query_socket), so no handlers are available. + LOG_ERROR(LOG_SERVER) + << "No method handlers available; ensure you're connected to the " + << "query websocket service"; + return; + } + + const auto handler = handlers_.find(method); + if (handler == handlers_.end()) + { + send_error_reply(protocol_status::not_found, not_found.first, + not_found.second); + LOG_VERBOSE(LOG_SERVER) << "Method " << method << " not found"; + return; + } + + auto it = connections_.find(connection); + if (it == connections_.end()) + { + LOG_ERROR(LOG_SERVER) + << "Query work provided for unknown connection " << connection; + return; + } + + auto& query_work_map = it->second; + if (query_work_map.find(id) != query_work_map.end()) + { + send_error_reply(protocol_status::internal_server_error, + internal_error.first, internal_error.second); + return; + } + + query_work_map.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(id, sequence_, connection, method, parameters)); + + // Encode request based on query work and send to query_websocket. + zmq::message request; + handler->second.encode(request, handler->second.command, parameters, + sequence_); + + /////////////////////////////////////////////////////////////////////// + // Critical Section + correlation_lock_.lock(); + + // While each connection has its own id map (meaning correlation + // ids passed from the web client are unique on a per connection + // basis, potentially utilizing the full range available), we need + // an internal mapping that will allow us to correlate each zmq + // request/response pair with the connection and original id + // number that originated it. The client never sees this + // sequence_ value. + correlations_[sequence_++] = { connection, id }; + + correlation_lock_.unlock(); + /////////////////////////////////////////////////////////////////////// + + const auto ec = service()->send(request); + + if (ec) + { + LOG_WARNING(LOG_SERVER) + << "Query send failure: " << ec.message(); + send_error_reply(protocol_status::internal_server_error, + internal_error.first, internal_error.second); + return; + } +} + +// Sends json strings to websockets or connections waiting on json_rpc +// replies (only). +// +// By using a task_sender via the manager's execute method, we +// guarantee that the write is performed on the manager's websocket +// thread (at the expense of a copied json response payload). +void socket::send(connection_ptr connection, const std::string& json) +{ + if (connection == nullptr || connection->closed() || + (!connection->websocket() && !connection->json_rpc())) + return; + + manager_->execute(std::make_shared(connection, json)); +} + +// Sends json strings to all connected websockets. +void socket::broadcast(const std::string& json) +{ + auto sender = [this, &json](std::pair entry) + { + send(entry.first, json); + }; + + std::for_each(connections_.begin(), connections_.end(), sender); +} + +} // namespace server +} // namespace libbitcoin diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp new file mode 100644 index 00000000..4a171bf6 --- /dev/null +++ b/src/web/transaction_socket.cpp @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 +#include + +namespace libbitcoin { +namespace server { + +using namespace std::placeholders; +using namespace bc::chain; +using namespace bc::message; +using namespace bc::protocol; +using role = zmq::socket::role; + +static const auto domain = "transaction"; +static constexpr auto poll_interval_milliseconds = 100u; + +transaction_socket::transaction_socket(zmq::authenticator& authenticator, + server_node& node, bool secure) + : socket(authenticator, node, secure, domain) +{ +} + +void transaction_socket::work() +{ + zmq::socket sub(authenticator_, role::subscriber, protocol_settings_); + + const auto endpoint = zeromq_endpoint().to_local(); + const auto ec = sub.connect(endpoint); + + if (ec) + { + LOG_ERROR(LOG_SERVER) + << "Failed to connect to transaction service " << endpoint << ": " + << ec.message(); + return; + } + + if (!started(start_websocket_handler())) + { + LOG_ERROR(LOG_SERVER) + << "Failed to start " << security_ << " " << domain + << " websocket handler"; + return; + } + + // Hold a shared reference to the websocket thread_ so that we can + // properly call stop_websocket_handler on cleanup. + const auto thread_ref = thread_; + + zmq::poller poller; + poller.add(sub); + + while (!poller.terminated() && !stopped()) + { + if (poller.wait(poll_interval_milliseconds).contains(sub.id()) && + !handle_transaction(sub)) + break; + } + + const auto sub_stop = sub.stop(); + const auto websocket_stop = stop_websocket_handler(); + + if (!sub_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to disconnect " << security_ + << " transaction websocket service."; + + if (!websocket_stop) + LOG_ERROR(LOG_SERVER) + << "Failed to stop " << security_ + << " transaction websocket handler"; + + finished(sub_stop && websocket_stop); +} + +// Called by this thread's work() method. +// Returns true to continue future notifications. +bool transaction_socket::handle_transaction(zmq::socket& subscriber) +{ + if (stopped()) + return false; + + zmq::message response; + subscriber.receive(response); + + static constexpr size_t transaction_message_size = 2; + if (response.empty() || response.size() != transaction_message_size) + { + LOG_WARNING(LOG_SERVER) + << "Failure handling transaction notification: invalid data"; + + // Don't let a failure here prevent future notifications. + return true; + } + + uint16_t sequence{}; + data_chunk transaction_data; + response.dequeue(sequence); + response.dequeue(transaction_data); + + chain::transaction tx; + tx.from_data(transaction_data, true, true); + + broadcast(web::to_json(tx, sequence)); + + LOG_VERBOSE(LOG_SERVER) + << "Broadcasted " << security_ << " socket tx [" + << encode_hash(tx.hash()) << "]"; + return true; +} + +const config::endpoint& transaction_socket::zeromq_endpoint() const +{ + // The Websocket to zeromq backend internally always uses the + // local public zeromq endpoint since it does not affect the + // external security of the websocket endpoint and impacts + // configuration and performance for no additional gain. + return server_settings_.zeromq_transaction_endpoint(false /* secure_ */); +} + +const config::endpoint& transaction_socket::websocket_endpoint() const +{ + return server_settings_.websockets_transaction_endpoint(secure_); +} + +} // namespace server +} // namespace libbitcoin diff --git a/src/workers/authenticator.cpp b/src/workers/authenticator.cpp index 95df80e0..c55c518b 100644 --- a/src/workers/authenticator.cpp +++ b/src/workers/authenticator.cpp @@ -34,10 +34,10 @@ authenticator::authenticator(server_node& node) { const auto& settings = node.server_settings(); - set_private_key(settings.server_private_key); + set_private_key(settings.zeromq_server_private_key); // Secure clients are also affected by address restrictions. - for (const auto& public_key: settings.client_public_keys) + for (const auto& public_key: settings.zeromq_client_public_keys) { LOG_DEBUG(LOG_SERVER) << "Allow client public key [" << public_key << "]"; diff --git a/src/workers/notification_worker.cpp b/src/workers/notification_worker.cpp index 6e1bda38..b4276e51 100644 --- a/src/workers/notification_worker.cpp +++ b/src/workers/notification_worker.cpp @@ -67,7 +67,7 @@ notification_worker::notification_worker(zmq::authenticator& authenticator, // There is no unsubscribe so this class shouldn't be restarted. // Notifications are ordered by validation in node but thread safety is still -// required so that purge can run on a seperate time thread. +// required so that purge can run on a separate time thread. bool notification_worker::start() { // Subscribe to blockchain reorganizations. @@ -173,7 +173,7 @@ bool notification_worker::handle_reorganization(const code& ec, LOG_WARNING(LOG_SERVER) << "Failure handling new block: " << ec.message(); - // Don't let a failure here prevent prevent future notifications. + // Don't let a failure here prevent future notifications. return true; } From ec19ab228a647671a111d870b133ea5329e412bd Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Tue, 16 Oct 2018 21:00:51 -0500 Subject: [PATCH 02/25] Address reported MSVC++ 2017 warnings and errors --- src/interface/blockchain.cpp | 4 +-- src/interface/transaction_pool.cpp | 4 +-- src/web/http/connection.cpp | 44 ++++++++++++++++++++---------- src/web/http/connection.hpp | 18 ++++++------ src/web/http/http.hpp | 14 +++++----- src/web/http/manager.cpp | 30 ++++++++++++-------- src/web/http/manager.hpp | 4 +-- src/web/http/utilities.cpp | 17 ++---------- 8 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/interface/blockchain.cpp b/src/interface/blockchain.cpp index 5d74b129..50b05aad 100644 --- a/src/interface/blockchain.cpp +++ b/src/interface/blockchain.cpp @@ -588,7 +588,7 @@ void blockchain::stealth_transaction_hashes_fetched(const code& ec, } // Save to blockchain and announce to all connected peers. -void blockchain::broadcast(server_node& node, const message& request, +void blockchain::broadcast(server_node& /* node */, const message& request, send_handler handler) { const auto block = std::make_shared(); @@ -618,7 +618,7 @@ void blockchain::handle_broadcast(const code& ec, const message& request, handler(message(request, ec)); } -void blockchain::validate(server_node& node, const message& request, +void blockchain::validate(server_node& /* node */, const message& request, send_handler handler) { const auto block = std::make_shared(); diff --git a/src/interface/transaction_pool.cpp b/src/interface/transaction_pool.cpp index 89fdcbda..0f9f9a42 100644 --- a/src/interface/transaction_pool.cpp +++ b/src/interface/transaction_pool.cpp @@ -99,7 +99,7 @@ void transaction_pool::transaction_fetched(const code& ec, // Save to tx pool and announce to all connected peers. // FUTURE: conditionally subscribe to penetration notifications. -void transaction_pool::broadcast(server_node& node, const message& request, +void transaction_pool::broadcast(server_node& /* node */, const message& request, send_handler handler) { // TODO: re-implement. @@ -129,7 +129,7 @@ void transaction_pool::handle_broadcast(const code& ec, const message& request, handler(message(request, ec)); } -void transaction_pool::validate2(server_node& node, const message& request, +void transaction_pool::validate2(server_node& /* node */, const message& request, send_handler handler) { // TODO: re-implement. diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index f4470a5c..285fbf8a 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -139,7 +139,12 @@ bool connection::closed() int32_t connection::read() { +#ifdef WIN32 + auto data = reinterpret_cast(read_buffer_.data()); +#else auto data = reinterpret_cast(read_buffer_.data()); +#endif + #ifdef WITH_MBEDTLS bytes_read_ = (ssl_context_.enabled ? mbedtls_ssl_read( &ssl_context_.context, data, maximum_read_length) : @@ -167,25 +172,32 @@ data_buffer& connection::write_buffer() // This is effectively a blocking write call that does not buffer // internally. -int32_t connection::do_write(const unsigned char* data, size_t length, +int32_t connection::do_write(const unsigned char* data, int32_t length, bool write_frame) { write_method plaintext_write = [this](const unsigned char* data, - size_t length) + int32_t length) { - return static_cast(send(socket_, data, length, 0)); + return static_cast(send(socket_, +#ifdef WIN32 + reinterpret_cast(data), +#else + data, +#endif + length, 0)); }; #ifdef WITH_MBEDTLS - write_method ssl_write = [this](const unsigned char* data, size_t length) + write_method ssl_write = [this](const unsigned char* data, int32_t length) { return static_cast( mbedtls_ssl_write(&ssl_context_.context, data, length)); }; - write_method writer = (ssl_context_.enabled ? ssl_write : plaintext_write); + auto writer = static_cast(ssl_context_.enabled ? ssl_write : + plaintext_write); #else - write_method writer = plaintext_write; + auto writer = static_cast(plaintext_write); #endif if (write_frame) @@ -200,8 +212,8 @@ int32_t connection::do_write(const unsigned char* data, size_t length, } int32_t written = 0; - auto position = data; auto remaining = length; + auto position = data; do { @@ -225,7 +237,7 @@ int32_t connection::do_write(const unsigned char* data, size_t length, } while (remaining != 0); - return static_cast(position - data); + return static_cast(position - data); } int32_t connection::write(const std::string& buffer) @@ -236,9 +248,12 @@ int32_t connection::write(const std::string& buffer) // This is a buffered write call so long as we're under the high // water mark. -int32_t connection::write(const unsigned char* data, size_t length) +int32_t connection::write(const unsigned char* data, int32_t length) { - const auto buffered_length = write_buffer_.size() + length + + if (length < 0) + return length; + + const int32_t buffered_length = write_buffer_.size() + length + (websocket_ ? sizeof(websocket_frame) : 0); // If we're currently at the hwm, issue blocking writes until @@ -251,8 +266,8 @@ int32_t connection::write(const unsigned char* data, size_t length) // Drain the buffered data. while (!write_buffer_.empty()) { - const auto segment_length = std::min(static_cast( - transfer_buffer_length), write_buffer_.size()); + const auto segment_length = std::min(transfer_buffer_length, + static_cast(write_buffer_.size())); const auto written = do_write(reinterpret_cast( write_buffer_.data()), segment_length, false); @@ -377,16 +392,17 @@ bool connection::operator==(const connection& other) return user_data_ == other.user_data_ && socket_ == other.socket_; } -websocket_frame connection::generate_websocket_frame(size_t length, +websocket_frame connection::generate_websocket_frame(int32_t length, websocket_op code) { + BITCOIN_ASSERT(length > 0); websocket_frame frame{}; frame.flags = 0x80 | static_cast(code); auto start = &frame.length[0]; if (length < 126) { - frame.payload_length = length; + frame.payload_length = static_cast(length); frame.write_length = 2; } else if (length < std::numeric_limits::max()) diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index 30689ccc..735d4399 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -42,10 +42,10 @@ typedef std::function write_method; + static const int32_t maximum_read_length = (1 << 10); // 1 KB + static const int32_t default_high_water_mark = (1 << 21); // 2 MB + static const int32_t default_incoming_frame_length = (1 << 19); //512 KB + typedef std::function write_method; connection(); connection(socket_connection connection, struct sockaddr_in& address); @@ -71,11 +71,11 @@ class connection int32_t write(const std::string& buffer); // This is a buffered write call so long as we're under the high // water mark. - int32_t write(const unsigned char* data, size_t length); + int32_t write(const unsigned char* data, int32_t length); // This is a write call that does not buffer internally and keeps // trying until an error is received, or the entire specified // length is sent. - int32_t do_write(const unsigned char* data, size_t length, + int32_t do_write(const unsigned char* data, int32_t length, bool write_frame); void close(); socket_connection& socket(); @@ -93,15 +93,15 @@ class connection bool operator==(const connection& other); private: - websocket_frame generate_websocket_frame(size_t length, websocket_op code); + websocket_frame generate_websocket_frame(int32_t length, websocket_op code); void* user_data_; connection_state state_; socket_connection socket_; struct sockaddr_in address_; std::chrono::steady_clock::time_point last_active_; - size_t high_water_mark_; - size_t maximum_incoming_frame_length_; + int32_t high_water_mark_; + int32_t maximum_incoming_frame_length_; buffer read_buffer_; data_buffer write_buffer_; int32_t bytes_read_; diff --git a/src/web/http/http.hpp b/src/web/http/http.hpp index 0308667a..7c577025 100644 --- a/src/web/http/http.hpp +++ b/src/web/http/http.hpp @@ -92,8 +92,8 @@ typedef int sock_t; #endif static const size_t sha1_hash_length = 20; -static const size_t default_buffer_length = 1 << 10; // 1KB -static const size_t transfer_buffer_length = 1 << 18; // 256KB +static const int32_t default_buffer_length = 1 << 10; // 1KB +static const int32_t transfer_buffer_length = 1 << 18; // 256KB #ifdef WITH_MBEDTLS static const int default_ciphers[] = @@ -229,16 +229,16 @@ struct file_transfer { bool in_progress; FILE* descriptor; - size_t offset; - size_t length; + int32_t offset; + int32_t length; }; struct websocket_transfer { bool in_progress; - size_t offset; - size_t length; - size_t header_length; + int32_t offset; + int32_t length; + int32_t header_length; data_buffer mask; data_buffer data; }; diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 89cd96d2..8ebde5eb 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -100,7 +100,7 @@ int32_t manager::high_water_mark() return high_water_mark_; } -void manager::set_backlog(size_t backlog) +void manager::set_backlog(int32_t backlog) { backlog_ = backlog; } @@ -393,8 +393,8 @@ void manager::poll(size_t timeout_milliseconds) { const size_t number_of_lists = (connections_.size() / maximum_items) + 1; - const auto adjusted_timeout = - std::ceil(timeout_milliseconds / number_of_lists); + const auto adjusted_timeout = static_cast( + std::ceil(timeout_milliseconds / number_of_lists)); std::vector connection_lists; connection_lists.reserve(number_of_lists); for (size_t i = 0; i < number_of_lists; i++) @@ -430,9 +430,9 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) BITCOIN_ASSERT(connections.size() <= maximum_items); struct timeval poll_interval{}; - poll_interval.tv_sec = timeout_milliseconds / 1000; - poll_interval.tv_usec = (timeout_milliseconds * 1000) - - (poll_interval.tv_sec * 100000); + poll_interval.tv_sec = static_cast(timeout_milliseconds / 1000); + poll_interval.tv_usec = static_cast((timeout_milliseconds * 1000) - + (poll_interval.tv_sec * 100000)); fd_set read_set; fd_set write_set; @@ -448,7 +448,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) connection_list pending_removal; for (auto& connection: connections) { - auto descriptor = connection->socket(); + auto descriptor = static_cast(connection->socket()); if (connection == nullptr || connection->closed()) continue; @@ -456,6 +456,14 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) // fd_set. if (descriptor > maximum_items) { +#ifdef WIN32 + close_socket(descriptor); + LOG_ERROR(LOG_SERVER_HTTP) + << "Error: cannot monitor socket " << descriptor + << ", value is above" << maximum_items; + pending_removal.push_back(connection); + continue; +#else // Attempt to resolve this by looking for a lower // available descriptor. auto new_descriptor = dup(descriptor); @@ -474,6 +482,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) pending_removal.push_back(connection); continue; } +#endif } if (connection->file_transfer().in_progress || @@ -620,8 +629,7 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) // Check for configuration violation (helps // prevent DoS by filling RAM with unexpectedly // large messages). - if (static_cast(transfer.offset) > - maximum_incoming_frame_length_) + if (transfer.offset > maximum_incoming_frame_length_) { LOG_ERROR(LOG_SERVER_HTTP) << "Terminating due to exceeding the " @@ -745,7 +753,7 @@ bool manager::transfer_file_data(connection_ptr& connection) auto amount_to_read = std::min(transfer_buffer_length, file_transfer.length - file_transfer.offset); - auto read = fread(data, sizeof(unsigned char), amount_to_read, + int32_t read = fread(data, sizeof(unsigned char), amount_to_read, file_transfer.descriptor); auto success = ((read == amount_to_read) || @@ -983,7 +991,7 @@ bool manager::handle_websocket(connection_ptr& connection) const auto mask_start = transfer.mask.data(); const auto payload_length = transfer.length - transfer.header_length; - for(size_t i = 0; i < payload_length; i++) + for(int32_t i = 0; i < payload_length; i++) data[i + transfer.header_length] ^= mask_start[i % 4]; websocket_message message diff --git a/src/web/http/manager.hpp b/src/web/http/manager.hpp index 95194036..d22de65c 100644 --- a/src/web/http/manager.hpp +++ b/src/web/http/manager.hpp @@ -50,7 +50,7 @@ class manager // May invalidate any buffered write data on each connection. void set_high_water_mark(int32_t length); int32_t high_water_mark(); - void set_backlog(size_t backlog); + void set_backlog(int32_t backlog); bool bind(std::string hostname, uint16_t port, const bind_options& options); bool accept_connection(); void add_connection(const connection_ptr& connection); @@ -102,7 +102,7 @@ class manager connection_list connections_; int32_t maximum_incoming_frame_length_; int32_t high_water_mark_; - size_t backlog_; + int32_t backlog_; connection_ptr listener_; struct sockaddr_in listener_address_; task_list tasks_; diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index 1a93a5ae..8eb394b2 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -30,29 +30,16 @@ std::string error_string() { #ifdef WIN32 LPVOID buffer{}; - LPVOID display_buffer{}; DWORD dw = last_error(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - static_cast(&buffer), 0, nullptr); - - display_buffer = static_cast(LocalAlloc(LMEM_ZEROINIT, - (lstrlen(static_cast(buffer)) + - lstrlen(static_cast(lpszFunction)) + 40) * sizeof(TCHAR))); - - StringCchPrintf(static_cast(display_buffer), - LocalSize(display_buffer) / sizeof(TCHAR), - TEXT("%s failed with error %d: %s"), - lpszFunction, dw, buffer); - - const std::string error_string = { display_buffer }; + reinterpret_cast(&buffer), 0, nullptr); + const std::string error_string = { buffer }; LocalFree(buffer); - LocalFree(display_buffer); - return error_string; #else return { strerror(last_error()) }; From 44a5fad287edafec08db225e8960ba6b145022b3 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 18 Oct 2018 09:04:08 -0700 Subject: [PATCH 03/25] Websocket updates. --- src/web/http/connection.cpp | 233 ++++++++++-------------- src/web/http/connection.hpp | 122 +++++++------ src/web/http/http.hpp | 260 ++++++++++++++++---------- src/web/http/manager.cpp | 352 ++++++++++++++++++------------------ src/web/http/manager.hpp | 18 +- src/web/http/utilities.cpp | 140 +++++++------- src/web/http/utilities.hpp | 19 +- src/web/query_socket.cpp | 3 +- src/web/socket.cpp | 102 ++++++----- 9 files changed, 659 insertions(+), 590 deletions(-) diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 285fbf8a..9abcd53f 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -16,6 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +#include #include #include "connection.hpp" @@ -31,38 +32,35 @@ connection::connection() state_(connection_state::unknown), socket_(0), address_{}, - last_active_(std::chrono::steady_clock::now()), + last_active_(asio::steady_clock::now()), high_water_mark_(default_high_water_mark), maximum_incoming_frame_length_(default_incoming_frame_length), - read_buffer_{}, - write_buffer_{}, ssl_context_{}, - websocket_(false), websocket_endpoint_{}, json_rpc_(false), + websocket_(false), file_transfer_{}, - websocket_transfer_{} + websocket_transfer_{}, + bytes_read_(0) { write_buffer_.reserve(high_water_mark_); } -connection::connection(socket_connection connection, struct sockaddr_in& - address) +connection::connection(sock_t connection, struct sockaddr_in& address) : user_data_(nullptr), state_(connection_state::unknown), socket_(connection), address_(address), - last_active_(std::chrono::steady_clock::now()), + last_active_(asio::steady_clock::now()), high_water_mark_(default_high_water_mark), maximum_incoming_frame_length_(default_incoming_frame_length), - read_buffer_{}, - write_buffer_{}, ssl_context_{}, - websocket_(false), websocket_endpoint_{}, + websocket_(false), json_rpc_(false), file_transfer_{}, - websocket_transfer_{} + websocket_transfer_{}, + bytes_read_(0) { write_buffer_.reserve(high_water_mark_); } @@ -73,8 +71,23 @@ connection::~connection() close(); } +connection_state connection::state() const +{ + return state_; +} + +void connection::set_state(connection_state state) +{ + state_ = state; +} + +size_t connection::high_water_mark() const +{ + return high_water_mark_; +} + // May invalidate any buffered write data. -void connection::set_high_water_mark(int32_t high_water_mark) +void connection::set_high_water_mark(size_t high_water_mark) { if (high_water_mark > 0) { @@ -84,73 +97,56 @@ void connection::set_high_water_mark(int32_t high_water_mark) } } -int32_t connection::high_water_mark() +size_t connection::maximum_incoming_frame_length() const { - return high_water_mark_; + return maximum_incoming_frame_length_; } -void connection::set_maximum_incoming_frame_length(int32_t length) +void connection::set_maximum_incoming_frame_length(size_t length) { if (length > 0) maximum_incoming_frame_length_ = length; } -int32_t connection::maximum_incoming_frame_length() -{ - return maximum_incoming_frame_length_; -} - void connection::set_socket_non_blocking() { #ifdef WIN32 - unsigned long non_blocking = 1; - ioctlsocket(socket_ , FIONBIO, &non_blocking); + ULONG non_blocking = 1; + ioctlsocket(socket_, FIONBIO, &non_blocking); #else fcntl(socket_, F_SETFL, fcntl(socket_, F_GETFD) | O_NONBLOCK); #endif } -struct sockaddr_in connection::address() +sockaddr_in connection::address() const { return address_; } -bool connection::reuse_address() +bool connection::reuse_address() const { - static constexpr int opt = 1; + static constexpr uint32_t opt = 1; + + // reinterpret_cast required for Win32, otherwise nop. return setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&opt), sizeof(opt)) != -1; } -connection_state connection::state() -{ - return state_; -} - -void connection::set_state(connection_state state) -{ - state_ = state; -} - -bool connection::closed() +bool connection::closed() const { return state_ == connection_state::closed; } int32_t connection::read() { -#ifdef WIN32 - auto data = reinterpret_cast(read_buffer_.data()); -#else - auto data = reinterpret_cast(read_buffer_.data()); -#endif - #ifdef WITH_MBEDTLS - bytes_read_ = (ssl_context_.enabled ? mbedtls_ssl_read( - &ssl_context_.context, data, maximum_read_length) : - recv(socket_, data, maximum_read_length, 0)); + bytes_read_ = ssl_context_.enabled ? + mbedtls_ssl_read(&ssl_context_.context, data, maximum_read_length) : + recv(socket_, data, maximum_read_length, 0) #else - bytes_read_ = recv(socket_, data, maximum_read_length, 0); + // reinterpret_cast required for Win32, otherwise nop. + bytes_read_ = recv(socket_, reinterpret_cast(read_buffer_.data()), + maximum_read_length, 0); #endif return bytes_read_; } @@ -160,64 +156,61 @@ int32_t connection::read_length() return bytes_read_; } -buffer& connection::read_buffer() +http::read_buffer& connection::read_buffer() { return read_buffer_; } -data_buffer& connection::write_buffer() +data_chunk& connection::write_buffer() { return write_buffer_; } -// This is effectively a blocking write call that does not buffer -// internally. -int32_t connection::do_write(const unsigned char* data, int32_t length, - bool write_frame) +// This is effectively a blocking write call that does not buffer internally. +int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) { - write_method plaintext_write = [this](const unsigned char* data, - int32_t length) + const auto plaintext_write = [this](const uint8_t* data, size_t length) { - return static_cast(send(socket_, + // BUGBUG: must set errno for return error handling. + if (length > max_int32) + return -1; + #ifdef WIN32 - reinterpret_cast(data), + return send(socket_, reinterpret_cast(data), + static_cast(length), 0); #else - data, + return send(socket_, data, length, 0); #endif - length, 0)); }; #ifdef WITH_MBEDTLS - write_method ssl_write = [this](const unsigned char* data, int32_t length) + const auto ssl_write = [this](const uint8_t* data, size_t length) { - return static_cast( - mbedtls_ssl_write(&ssl_context_.context, data, length)); + // BUGBUG: handle MBEDTLS_ERR_SSL_WANT_WRITE + return mbedtls_ssl_write(&ssl_context_.context, data, length)); }; - auto writer = static_cast(ssl_context_.enabled ? ssl_write : - plaintext_write); + auto writer = ssl_context_.enabled ? ssl_write : plaintext_write; #else - auto writer = static_cast(plaintext_write); + auto writer = plaintext_write; #endif - if (write_frame) + if (frame) { - const auto frame = generate_websocket_frame(length, - websocket_op::text); - const auto frame_data = reinterpret_cast(&frame); + auto frame_data = websocket_frame::to_data(length, websocket_op::text); + const auto written = writer(frame_data.data(), frame_data.size()); - int32_t frame_write = writer(frame_data, frame.write_length); - if (frame_write < 0) - return frame_write; + if (written < 0) + return written; } - int32_t written = 0; auto remaining = length; auto position = data; do { - written = writer(position, remaining); + const auto written = writer(position, remaining); + if (written < 0) { const auto error = last_error(); @@ -229,6 +222,7 @@ int32_t connection::do_write(const unsigned char* data, int32_t length, return written; } + // BUGBUG: non-terminating loop , or does would_block prevent? continue; } @@ -240,43 +234,47 @@ int32_t connection::do_write(const unsigned char* data, int32_t length, return static_cast(position - data); } +int32_t connection::write(const data_chunk& buffer) +{ + return write(buffer.data(), buffer.size()); +} + int32_t connection::write(const std::string& buffer) { - return write(reinterpret_cast(buffer.c_str()), - buffer.size()); + const auto data = reinterpret_cast(buffer.data()); + return write(data, buffer.size()); } -// This is a buffered write call so long as we're under the high -// water mark. -int32_t connection::write(const unsigned char* data, int32_t length) +// This is a buffered write call if under the high water mark. +int32_t connection::write(const uint8_t* data, size_t length) { - if (length < 0) - return length; + if (length > max_int32) + return -1; - const int32_t buffered_length = write_buffer_.size() + length + - (websocket_ ? sizeof(websocket_frame) : 0); + static constexpr auto maximal_websocket_frame = 11u; + const auto buffered_length = write_buffer_.size() + length + + (websocket_ ? maximal_websocket_frame : 0); // If we're currently at the hwm, issue blocking writes until // we've cleared the buffered data and then write this current // request. This is an expensive operation, but should be - // mostly avoidable with proper hwm tuning of your - // application. + // mostly avoidable with proper hwm tuning of your application. if (buffered_length >= high_water_mark_) { // Drain the buffered data. while (!write_buffer_.empty()) { const auto segment_length = std::min(transfer_buffer_length, - static_cast(write_buffer_.size())); - const auto written = do_write(reinterpret_cast( - write_buffer_.data()), segment_length, false); + write_buffer_.size()); + const auto written = do_write(write_buffer_.data(), segment_length, + false); if (written < 0) return written; if (!write_buffer_.empty()) - write_buffer_.erase(write_buffer_.begin(), write_buffer_.begin() - + written); + write_buffer_.erase(write_buffer_.begin(), + write_buffer_.begin() + written); } // Perform this write in a blocking manner. @@ -285,16 +283,14 @@ int32_t connection::write(const unsigned char* data, int32_t length) if (websocket_) { - const auto frame = generate_websocket_frame(length, - websocket_op::text); - const auto frame_data = reinterpret_cast(&frame); - write_buffer_.insert(write_buffer_.end(), frame_data, frame_data + - frame.write_length); + auto frame_data = websocket_frame::to_data(length, websocket_op::text); + write_buffer_.insert(write_buffer_.end(), frame_data.begin(), + frame_data.end()); } // Buffer this data for future writes (called from poll). write_buffer_.insert(write_buffer_.end(), data, data + length); - return length; + return static_cast(length); } void connection::close() @@ -316,13 +312,13 @@ void connection::close() } #endif - close_socket(socket_); + CLOSE_SOCKET(socket_); state_ = connection_state::closed; LOG_VERBOSE(LOG_SERVER_HTTP) << "Closed socket " << this; } -socket_connection& connection::socket() +sock_t& connection::socket() { return socket_; } @@ -332,12 +328,12 @@ http::ssl& connection::ssl_context() return ssl_context_; } -bool connection::ssl_enabled() +bool connection::ssl_enabled() const { return ssl_context_.enabled; } -bool connection::websocket() +bool connection::websocket() const { return websocket_; } @@ -347,7 +343,7 @@ void connection::set_websocket(bool websocket) websocket_ = websocket; } -const std::string& connection::websocket_endpoint() +const std::string& connection::websocket_endpoint() const { return websocket_endpoint_; } @@ -357,7 +353,7 @@ void connection::set_websocket_endpoint(const std::string endpoint) websocket_endpoint_ = endpoint; } -bool connection::json_rpc() +bool connection::json_rpc() const { return json_rpc_; } @@ -392,39 +388,6 @@ bool connection::operator==(const connection& other) return user_data_ == other.user_data_ && socket_ == other.socket_; } -websocket_frame connection::generate_websocket_frame(int32_t length, - websocket_op code) -{ - BITCOIN_ASSERT(length > 0); - websocket_frame frame{}; - frame.flags = 0x80 | static_cast(code); - - auto start = &frame.length[0]; - if (length < 126) - { - frame.payload_length = static_cast(length); - frame.write_length = 2; - } - else if (length < std::numeric_limits::max()) - { - auto data_length = static_cast(length); - *reinterpret_cast(start) = - boost::endian::endian_reverse(data_length); - frame.payload_length = 126; - frame.write_length = 4; // 2 + sizeof(uint16_t) - } - else - { - auto data_length = static_cast(length); - *reinterpret_cast(start) = - boost::endian::endian_reverse(data_length); - frame.payload_length = 127; - frame.write_length = 10; // 2 + sizeof(uint64_t) - } - - return frame; -} - } // namespace http } // namespace server } // namespace libbitcoin diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index 735d4399..105c4130 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -19,7 +19,10 @@ #ifndef LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP #define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP +#include +#include #include +#include #include #include @@ -34,86 +37,101 @@ class connection; typedef std::shared_ptr connection_ptr; typedef std::set connection_set; typedef std::vector connection_list; -typedef std::function event_handler; +typedef std::function + event_handler; // This class is instantiated from accepted/incoming HTTP clients. // Initiating outgoing HTTP connections are not currently supported. class connection { public: - static const int32_t maximum_read_length = (1 << 10); // 1 KB - static const int32_t default_high_water_mark = (1 << 21); // 2 MB - static const int32_t default_incoming_frame_length = (1 << 19); //512 KB - typedef std::function write_method; + static const uint32_t maximum_read_length = (1 << 10); // 1 KB + static const uint32_t default_high_water_mark = (1 << 21); // 2 MB + static const uint32_t default_incoming_frame_length = (1 << 19); // 512 KB + typedef std::function write_method; connection(); - connection(socket_connection connection, struct sockaddr_in& address); + connection(sock_t connection, struct sockaddr_in& address); ~connection(); - void set_high_water_mark(int32_t high_water_mark); - int32_t high_water_mark(); - void set_maximum_incoming_frame_length(int32_t length); - int32_t maximum_incoming_frame_length(); - void set_user_data(void* user_data); - void* user_data(); - void set_socket_non_blocking(); - struct sockaddr_in address(); - bool reuse_address(); - connection_state state(); + // Properties. + // ------------------------------------------------------------------------ + + connection_state state() const; void set_state(connection_state state); - bool closed(); - int32_t read_length(); - buffer& read_buffer(); - data_buffer& write_buffer(); - int32_t read(); - int32_t write(const std::string& buffer); - // This is a buffered write call so long as we're under the high - // water mark. - int32_t write(const unsigned char* data, int32_t length); - // This is a write call that does not buffer internally and keeps - // trying until an error is received, or the entire specified - // length is sent. - int32_t do_write(const unsigned char* data, int32_t length, - bool write_frame); - void close(); - socket_connection& socket(); - http::ssl& ssl_context(); - bool ssl_enabled(); - bool websocket(); + void* user_data(); + void set_user_data(void* user_data); + + bool websocket() const; void set_websocket(bool websocket); - bool json_rpc(); + + bool json_rpc() const; void set_json_rpc(bool json_rpc); + + size_t high_water_mark() const; + void set_high_water_mark(size_t high_water_mark); + + size_t maximum_incoming_frame_length() const; + void set_maximum_incoming_frame_length(size_t length); + // Websocket endpoints are HTTP specific endpoints such as '/'. - const std::string& websocket_endpoint(); + const std::string& websocket_endpoint() const; void set_websocket_endpoint(const std::string endpoint); + + // Readers and Writers. + // ------------------------------------------------------------------------ + read_buffer& read_buffer(); + data_chunk& write_buffer(); + + // Signed integer results use negative range for error code. + int32_t read_length(); + int32_t read(); + int32_t write(const data_chunk& buffer); + int32_t write(const std::string& buffer); + int32_t write(const uint8_t* data, size_t length); + int32_t do_write(const uint8_t* data, size_t length, bool frame); + + // Other. + // ------------------------------------------------------------------------ + + void set_socket_non_blocking(); + struct sockaddr_in address() const; + bool reuse_address() const; + bool closed() const; + void close(); + sock_t& socket(); + http::ssl& ssl_context(); + bool ssl_enabled() const; http::file_transfer& file_transfer(); http::websocket_transfer& websocket_transfer(); + + // Operator overloads. + // ------------------------------------------------------------------------ + bool operator==(const connection& other); private: - websocket_frame generate_websocket_frame(int32_t length, websocket_op code); - void* user_data_; connection_state state_; - socket_connection socket_; - struct sockaddr_in address_; - std::chrono::steady_clock::time_point last_active_; - int32_t high_water_mark_; - int32_t maximum_incoming_frame_length_; - buffer read_buffer_; - data_buffer write_buffer_; - int32_t bytes_read_; + sock_t socket_; + sockaddr_in address_; + asio::time_point last_active_; + size_t high_water_mark_; + size_t maximum_incoming_frame_length_; ssl ssl_context_; - bool websocket_; std::string websocket_endpoint_; + bool websocket_; bool json_rpc_; - // Transfer states used for read continuations, particularly for - // when the read_buffer_ size is too small to hold all of the - // incoming data. + + // Transfer states used for read continuations, particularly for when the + // read_buffer_ size is too small to hold all of the incoming data. http::file_transfer file_transfer_; http::websocket_transfer websocket_transfer_; + + int32_t bytes_read_; + http::read_buffer read_buffer_; + data_chunk write_buffer_; }; } // namespace http diff --git a/src/web/http/http.hpp b/src/web/http/http.hpp index 7c577025..2ebed169 100644 --- a/src/web/http/http.hpp +++ b/src/web/http/http.hpp @@ -20,8 +20,10 @@ #define LIBBITCOIN_SERVER_WEB_HTTP_HPP #include +#include #include #include +#include #include #include #include @@ -30,70 +32,66 @@ #include #include #include - #include #ifdef WIN32 -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include #else -#include -#include -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include + #include + #include #endif // Explicitly use std::placeholders here for usage internally to the // boost parsing helpers included from json_parser.hpp. // See: https://svn.boost.org/trac10/ticket/12621 -#include -using namespace std::placeholders; +////// TODO: DO NOT USE 'using namespace' IN HEADER. +////using namespace std::placeholders; #include #include #include #include -#include #include #include -#include +#include #include #include #ifdef WITH_MBEDTLS -#include -#include -#include -#include -#include -#include -#include + #include + #include + #include + #include + #include + #include + #include #endif namespace libbitcoin { namespace server { namespace http { -using namespace boost::property_tree; - #ifdef WIN32 -typedef SOCKET sock_t; -typedef uint32_t in_addr_t; -#define close_socket closesocket + typedef SOCKET sock_t; + typedef uint32_t in_addr_t; + #define CLOSE_SOCKET closesocket #else -typedef int sock_t; -#define close_socket ::close + typedef int sock_t; + #define CLOSE_SOCKET ::close #endif static const size_t sha1_hash_length = 20; -static const int32_t default_buffer_length = 1 << 10; // 1KB -static const int32_t transfer_buffer_length = 1 << 18; // 256KB +static const size_t default_buffer_length = 1 << 10; // 1KB +static const size_t transfer_buffer_length = 1 << 18; // 256KB #ifdef WITH_MBEDTLS static const int default_ciphers[] = @@ -117,16 +115,14 @@ static const int default_ciphers[] = }; #endif -typedef std::vector data_buffer; -typedef std::array buffer; - -typedef sock_t socket_connection; - -typedef std::array sha1_hash; - +////typedef std::vector data_buffer; +typedef std::array read_buffer; +typedef std::array sha1_hash; typedef std::vector string_list; typedef std::unordered_map string_map; +// TODO: move each of these enum and struct declarations to own file. + enum class connection_state { error = -1, @@ -185,22 +181,102 @@ enum class websocket_op : uint8_t struct websocket_message { - const std::string& endpoint; - const unsigned char* data; - int32_t size; - uint8_t flags; - websocket_op code; + const std::string& endpoint; + const uint8_t* data; + size_t size; + uint8_t flags; + websocket_op code; }; -#pragma pack(push, 1) -struct websocket_frame +class websocket_frame { - uint8_t flags; - uint8_t payload_length; - char length[8]; - uint8_t write_length; +public: + websocket_frame() + : header_length_(0), data_length_(0) + { + } + + static data_chunk to_data(size_t length, websocket_op code) + { + if (length < 126) + { + return build_chunk( + { + to_array(0x80 | static_cast(code)), + to_array(static_cast(length)) + }); + } + else if (length < max_uint16) + { + return build_chunk( + { + to_array(0x80 | static_cast(code)), + to_array(uint8_t(126)), + to_big_endian(static_cast(length)) + }); + } + else + { + return build_chunk( + { + to_array(0x80 | static_cast(code)), + to_array(uint8_t(127)), + to_big_endian(static_cast(length)) + }); + } + } + + bool from_data(const uint8_t* data, size_t size) + { + static constexpr auto prefix_length = 2u; + static constexpr auto mask_length = 4u; + + header_length_ = 0; + data_length_ = 0; + + if (size < prefix_length || (data[1] & 0x80) == 0) + return false; + + const size_t length = (data[1] & 0x7f); + + if (size >= mask_length && length < 126u) + { + data_length_ = length; + header_length_ = prefix_length + mask_length; + } + else if (size >= prefix_length + sizeof(uint16_t) + mask_length && + length == 126u) + { + data_length_ = from_big_endian(&data[prefix_length], + &data[prefix_length + sizeof(uint16_t)]); + + header_length_ = prefix_length + sizeof(uint16_t) + mask_length; + } + else if (size >= prefix_length + sizeof(uint64_t) + mask_length) + { + data_length_ = from_big_endian(&data[prefix_length], + &data[prefix_length + sizeof(uint64_t)]); + + header_length_ = prefix_length + sizeof(uint64_t) + mask_length; + } + + return true; + } + + size_t header_length() const + { + return header_length_; + } + + size_t data_length() const + { + return data_length_; + } + +private: + size_t header_length_; + size_t data_length_; }; -#pragma pack(pop) struct ssl { @@ -218,7 +294,7 @@ struct ssl struct bind_options { void* user_data; - unsigned int flags; + uint32_t flags; std::string ssl_key; std::string ssl_certificate; std::string ssl_ca_certificate; @@ -229,30 +305,28 @@ struct file_transfer { bool in_progress; FILE* descriptor; - int32_t offset; - int32_t length; + size_t offset; + size_t length; }; struct websocket_transfer { bool in_progress; - int32_t offset; - int32_t length; - int32_t header_length; - data_buffer mask; - data_buffer data; + size_t offset; + size_t length; + size_t header_length; + data_chunk mask; + data_chunk data; }; -struct http_request +class http_request { +public: std::string find(const string_map& haystack, const std::string& needle) const { - auto it = haystack.find(needle); - if (it != haystack.end()) - return it->second; - - return {}; + const auto it = haystack.find(needle); + return it == haystack.end() ? std::string{} : it->second; } std::string header(std::string header) const @@ -277,40 +351,37 @@ struct http_request string_map parameters; bool upgrade_request; bool json_rpc; - ptree json_tree; + boost::property_tree::ptree json_tree; }; struct http_reply { static std::string to_string(protocol_status status) { - typedef std::unordered_map status_map; - static const status_map status_strings = + typedef std::unordered_map status_map; + static const status_map status_strings { - { static_cast(protocol_status::switching), "HTTP/1.1 101 Switching Protocols\r\n" }, - { static_cast(protocol_status::ok), "HTTP/1.0 200 OK\r\n" }, - { static_cast(protocol_status::created), "HTTP/1.0 201 Created\r\n" }, - { static_cast(protocol_status::accepted), "HTTP/1.0 202 Accepted\r\n" }, - { static_cast(protocol_status::no_content), "HTTP/1.0 204 No Content\r\n" }, - { static_cast(protocol_status::multiple_choices), "HTTP/1.0 300 Multiple Choices\r\n" }, - { static_cast(protocol_status::moved_permanently), "HTTP/1.0 301 Moved Permanently\r\n" }, - { static_cast(protocol_status::moved_temporarily), "HTTP/1.0 302 Moved Temporarily\r\n" }, - { static_cast(protocol_status::not_modified), "HTTP/1.0 304 Not Modified\r\n" }, - { static_cast(protocol_status::bad_request), "HTTP/1.0 400 Bad Request\r\n" }, - { static_cast(protocol_status::unauthorized), "HTTP/1.0 401 Unauthorized\r\n" }, - { static_cast(protocol_status::forbidden), "HTTP/1.0 403 Forbidden\r\n" }, - { static_cast(protocol_status::not_found), "HTTP/1.0 404 Not Found\r\n" }, - { static_cast(protocol_status::internal_server_error), "HTTP/1.0 500 Internal Server Error\r\n" }, - { static_cast(protocol_status::not_implemented), "HTTP/1.0 501 Not Implemented\r\n" }, - { static_cast(protocol_status::bad_gateway), "HTTP/1.0 502 Bad Gateway\r\n" }, - { static_cast(protocol_status::service_unavailable), "HTTP/1.0 503 Service Unavailable\r\n" } + { protocol_status::switching, "HTTP/1.1 101 Switching Protocols\r\n" }, + { protocol_status::ok, "HTTP/1.0 200 OK\r\n" }, + { protocol_status::created, "HTTP/1.0 201 Created\r\n" }, + { protocol_status::accepted, "HTTP/1.0 202 Accepted\r\n" }, + { protocol_status::no_content, "HTTP/1.0 204 No Content\r\n" }, + { protocol_status::multiple_choices, "HTTP/1.0 300 Multiple Choices\r\n" }, + { protocol_status::moved_permanently, "HTTP/1.0 301 Moved Permanently\r\n" }, + { protocol_status::moved_temporarily, "HTTP/1.0 302 Moved Temporarily\r\n" }, + { protocol_status::not_modified, "HTTP/1.0 304 Not Modified\r\n" }, + { protocol_status::bad_request, "HTTP/1.0 400 Bad Request\r\n" }, + { protocol_status::unauthorized, "HTTP/1.0 401 Unauthorized\r\n" }, + { protocol_status::forbidden, "HTTP/1.0 403 Forbidden\r\n" }, + { protocol_status::not_found, "HTTP/1.0 404 Not Found\r\n" }, + { protocol_status::internal_server_error, "HTTP/1.0 500 Internal Server Error\r\n" }, + { protocol_status::not_implemented, "HTTP/1.0 501 Not Implemented\r\n" }, + { protocol_status::bad_gateway, "HTTP/1.0 502 Bad Gateway\r\n" }, + { protocol_status::service_unavailable, "HTTP/1.0 503 Service Unavailable\r\n" } }; - auto it = status_strings.find(static_cast(status)); - if (it != status_strings.end()) - return it->second; - - return {}; + const auto it = status_strings.find(status); + return it == status_strings.end() ? std::string{} : it->second; } static std::string generate(protocol_status status, std::string mime_type, @@ -318,10 +389,11 @@ struct http_reply { static const size_t max_date_time_length = 32; std::array time_buffer{}; + const auto current_time = std::time(nullptr); - time_t current_time = time(nullptr); - strftime(time_buffer.data(), time_buffer.size(), - "%a, %d %b %Y %H:%M:%S GMT", gmtime(¤t_time)); + // BUGBUG: std::gmtime may not be thread safe. + std::strftime(time_buffer.data(), time_buffer.size(), + "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(¤t_time)); std::stringstream response; response diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 8ebde5eb..66bd048f 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -18,6 +18,10 @@ */ #include #include +#include +#ifdef WIN32 + #include +#endif #include "http.hpp" #include "utilities.hpp" @@ -52,6 +56,9 @@ manager::manager(bool ssl, event_handler handler, high_water_mark_(1 << 21), // 2 MB backlog_(8) { +#ifndef WITH_MBEDTLS + BITCOIN_ASSERT_MSG(!ssl, "Secure HTTP requires MBEDTLS library."); +#endif } manager::~manager() @@ -75,31 +82,31 @@ bool manager::initialize() return true; } -void manager::set_maximum_incoming_frame_length(int32_t length) +size_t manager::maximum_incoming_frame_length() +{ + return maximum_incoming_frame_length_; +} + +void manager::set_maximum_incoming_frame_length(size_t length) { maximum_incoming_frame_length_ = length; for (auto& connection: connections_) connection->set_maximum_incoming_frame_length(length); } -int32_t manager::maximum_incoming_frame_length() +size_t manager::high_water_mark() { - return maximum_incoming_frame_length_; + return high_water_mark_; } // May truncate any previously buffered write data on each connection. -void manager::set_high_water_mark(int32_t length) +void manager::set_high_water_mark(size_t length) { high_water_mark_ = length; for (auto& connection: connections_) connection->set_high_water_mark(length); } -int32_t manager::high_water_mark() -{ - return high_water_mark_; -} - void manager::set_backlog(int32_t backlog) { backlog_ = backlog; @@ -109,14 +116,11 @@ bool manager::bind(std::string hostname, uint16_t port, const bind_options& options) { LOG_VERBOSE(LOG_SERVER_HTTP) - << (ssl_ ? "Secure" : "Public") << " bind called with host " << hostname - << " and port " << port; + << (ssl_ ? "Secure" : "Public") << " bind called with host " + << hostname << " and port " << port; port_ = port; - static const auto address_length = - static_cast(sizeof(listener_address_)); - - std::memset(&listener_address_, 0, address_length); + std::memset(&listener_address_, 0, sizeof(listener_address_)); listener_address_.sin_family = AF_INET; listener_address_.sin_port = htons(port_); listener_address_.sin_addr.s_addr = htonl(INADDR_ANY); @@ -125,8 +129,9 @@ bool manager::bind(std::string hostname, uint16_t port, user_data_ = options.user_data; listener_ = std::make_shared(); - listener_->socket() = ::socket(listener_address_.sin_family, - SOCK_STREAM, 0); + listener_->socket() = ::socket(listener_address_.sin_family, SOCK_STREAM, + 0); + if (listener_->socket() == 0) { LOG_ERROR(LOG_SERVER_HTTP) @@ -157,8 +162,8 @@ bool manager::bind(std::string hostname, uint16_t port, listener_->set_socket_non_blocking(); listener_->reuse_address(); - if (::bind(listener_->socket(), reinterpret_cast( - &listener_address_), address_length) != 0) + if (::bind(listener_->socket(), reinterpret_cast( + &listener_address_), sizeof(listener_address_)) != 0) { LOG_ERROR(LOG_SERVER_HTTP) << "Bind failed with error " << last_error() << ": " @@ -171,22 +176,21 @@ bool manager::bind(std::string hostname, uint16_t port, listener_->set_state(connection_state::listening); add_connection(listener_); - return true; } bool manager::accept_connection() { struct sockaddr_in remote_address; - socklen_t remote_address_size = sizeof(remote_address); - std::memset(&remote_address, 0, remote_address_size); + std::memset(&remote_address, 0, sizeof(remote_address)); sock_t socket{}; + auto address_size = static_cast(sizeof(remote_address)); do { socket = ::accept(listener_->socket(), reinterpret_cast( - &remote_address), static_cast(&remote_address_size)); + &remote_address), &address_size); const auto error = last_error(); if ((static_cast(socket) == @@ -210,7 +214,7 @@ bool manager::accept_connection() { LOG_ERROR(LOG_SERVER_HTTP) << "Failed to disable SIGPIPE"; - close_socket(socket); + CLOSE_SOCKET(socket); return false; } #endif @@ -221,7 +225,7 @@ bool manager::accept_connection() { LOG_ERROR(LOG_SERVER_HTTP) << "Failed to create new connection object"; - close_socket(socket); + CLOSE_SOCKET(socket); return false; } @@ -237,8 +241,7 @@ bool manager::accept_connection() } auto& context = connection->ssl_context().context; - mbedtls_ssl_set_bio( - &context, reinterpret_cast(connection.get()), + mbedtls_ssl_set_bio(&context, reinterpret_cast(connection.get()), ssl_send, ssl_receive, nullptr); int error = ~0; @@ -283,20 +286,23 @@ bool manager::accept_connection() void manager::add_connection(const connection_ptr& connection) { connections_.push_back(connection); + LOG_VERBOSE(LOG_SERVER_HTTP) - << "Added Connection [" << connection - << ", " << connections_.size() << " total]"; + << "Added Connection [" << connection << ", " + << connections_.size() << " total]"; } void manager::remove_connection(connection_ptr& connection) { - auto it = std::find( - connections_.begin(), connections_.end(), connection); + const auto it = std::find(connections_.begin(), connections_.end(), + connection); + if (it != connections_.end()) { LOG_VERBOSE(LOG_SERVER_HTTP) - << "Removing Connection [" << connection - << ", " << connections_.size() - 1 << " remaining]"; + << "Removing Connection [" << connection << ", " + << connections_.size() - 1 << " remaining]"; + connections_.erase(it); } else @@ -335,8 +341,7 @@ void manager::run_once(size_t timeout_milliseconds) if (stopped()) return; - // Run any executable tasks the user queued that must be - // run inside this thread. + // Run any tasks the user queued that must be run inside this thread. run_tasks(); // Monitor and process sockets. @@ -392,7 +397,7 @@ void manager::poll(size_t timeout_milliseconds) if (connections_.size() > maximum_items) { const size_t number_of_lists = - (connections_.size() / maximum_items) + 1; + (connections_.size() / maximum_items) + 1u; const auto adjusted_timeout = static_cast( std::ceil(timeout_milliseconds / number_of_lists)); std::vector connection_lists; @@ -429,7 +434,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) // This limit must be honored by the caller. BITCOIN_ASSERT(connections.size() <= maximum_items); - struct timeval poll_interval{}; + timeval poll_interval{}; poll_interval.tv_sec = static_cast(timeout_milliseconds / 1000); poll_interval.tv_usec = static_cast((timeout_milliseconds * 1000) - (poll_interval.tv_sec * 100000)); @@ -457,7 +462,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) if (descriptor > maximum_items) { #ifdef WIN32 - close_socket(descriptor); + CLOSE_SOCKET(descriptor); LOG_ERROR(LOG_SERVER_HTTP) << "Error: cannot monitor socket " << descriptor << ", value is above" << maximum_items; @@ -470,12 +475,12 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) if (new_descriptor < descriptor && new_descriptor < maximum_items) { connection->socket() = new_descriptor; - close_socket(descriptor); + CLOSE_SOCKET(descriptor); } else { // Select cannot monitor this descriptor. - close_socket(new_descriptor); + CLOSE_SOCKET(new_descriptor); LOG_ERROR(LOG_SERVER_HTTP) << "Error: cannot monitor socket " << descriptor << ", value is above" << maximum_items; @@ -504,6 +509,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) const auto num_events = ::select(max_descriptor + 1, &read_set, &write_set, &error_set, &poll_interval); + if (num_events == 0) return; @@ -540,11 +546,8 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) auto& write_buffer = connection->write_buffer(); if (!write_buffer.empty()) { - const auto segment_length = std::min(write_buffer.size(), - static_cast(transfer_buffer_length)); - const auto written = connection->do_write( - reinterpret_cast( - write_buffer.data()), segment_length, false); + const auto segment_length = std::min(write_buffer.size(), transfer_buffer_length); + const auto written = connection->do_write(write_buffer.data(), segment_length, false); if (written < 0) { @@ -569,9 +572,8 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) return; } - // After accepting a connection, break out of loop - // since we're iterating over a list that's just been - // updated. + // After accepting a connection, break out of loop since we're + // iterating over a list that's just been updated. break; } else @@ -611,10 +613,11 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) case event::read: { - int32_t read_length = connection->read_length(); - if (read_length <= 0) + const auto read_result = connection->read_length(); + if (read_result <= 0) break; + const auto read_length = static_cast(read_result); auto& buffer = connection->read_buffer(); if (connection->websocket()) { @@ -622,9 +625,10 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) if (transfer.in_progress) { transfer.data.insert(transfer.data.end(), buffer.data(), - buffer.data() + read_length); + buffer.data() + read_length); + transfer.offset += read_length; - assert(transfer.offset == transfer.data.size()); + BITCOIN_ASSERT(transfer.offset == transfer.data.size()); // Check for configuration violation (helps // prevent DoS by filling RAM with unexpectedly @@ -644,12 +648,12 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) return handle_websocket(connection); } - const auto request = std::string(buffer.data(), read_length); + const std::string request{ buffer.begin(), buffer.begin() + read_length }; + http_request out{}; - if (parse_http(request, out)) + if (parse_http(out, request)) { - // Check if we need to convert HTTP connection to - // websocket. + // Check if we need to convert HTTP connection to websocket. if (out.upgrade_request) return upgrade_connection(connection, out); @@ -711,7 +715,7 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) #ifdef WITH_MBEDTLS // static -int manager::ssl_send(void* data, const unsigned char* buffer, size_t length) +int manager::ssl_send(void* data, const uint8_t* buffer, size_t length) { auto connection = reinterpret_cast(data); #ifdef MSG_NOSIGNAL @@ -729,7 +733,7 @@ int manager::ssl_send(void* data, const unsigned char* buffer, size_t length) } // static -int manager::ssl_receive(void* data, unsigned char* buffer, size_t length) +int manager::ssl_receive(void* data, uint8_t* buffer, size_t length) { auto connection = reinterpret_cast(data); auto read = static_cast(recv(connection->socket(), buffer, length, 0)); @@ -743,21 +747,20 @@ int manager::ssl_receive(void* data, unsigned char* buffer, size_t length) bool manager::transfer_file_data(connection_ptr& connection) { - std::array buffer{}; - + std::array buffer; auto& file_transfer = connection->file_transfer(); + if (!file_transfer.in_progress) return false; - auto data = reinterpret_cast(buffer.data()); auto amount_to_read = std::min(transfer_buffer_length, file_transfer.length - file_transfer.offset); - int32_t read = fread(data, sizeof(unsigned char), amount_to_read, + const auto read = fread(buffer.data(), sizeof(uint8_t), amount_to_read, file_transfer.descriptor); - auto success = ((read == amount_to_read) || - ((read < amount_to_read) && feof(file_transfer.descriptor))); + auto success = (read == amount_to_read || + (read < amount_to_read && feof(file_transfer.descriptor))); if (!success) { @@ -768,19 +771,22 @@ bool manager::transfer_file_data(connection_ptr& connection) file_transfer.offset = 0; file_transfer.length = 0; } + return false; } - int32_t written = connection->write(buffer.data(), read); - if (written < 0) + const auto written = connection->write(buffer.data(), read); + success = (written >= 0 && static_cast(written) == read); + + if (!success) { LOG_ERROR(LOG_SERVER_HTTP) - << "Write failed: requested write of " << read << " and wrote " - << written; + << "Write failed: requested " << read << " and wrote " << written; return false; } file_transfer.offset += read; + if (file_transfer.offset == file_transfer.length) { if (file_transfer.in_progress) @@ -799,6 +805,7 @@ bool manager::send_http_file(connection_ptr& connection, const std::string& path, bool keep_alive) { auto& file_transfer = connection->file_transfer(); + if (!file_transfer.in_progress) { file_transfer.descriptor = fopen(path.c_str(), "r"); @@ -825,30 +832,27 @@ bool manager::send_http_file(connection_ptr& connection, bool manager::handle_websocket(connection_ptr& connection) { - int32_t read_length = connection->read_length(); - if (read_length <= 0) + const auto read_result = connection->read_length(); + + if (read_result <= 0) return false; - auto& buffer = connection->read_buffer(); - unsigned char* data = nullptr; - int32_t length = 0; - int32_t mask_length = 0; - int32_t data_length = 0; - int32_t header_length = 0; + size_t length = 0; + size_t mask_length = 0; + size_t data_length = 0; + size_t header_length = 0; + auto& buffer = connection->read_buffer(); auto& transfer = connection->websocket_transfer(); - data = (transfer.in_progress ? - reinterpret_cast(transfer.data.data()) : - reinterpret_cast(buffer.data())); + const auto data = transfer.in_progress ? transfer.data.data() : + buffer.data(); - const uint8_t flags = data[0]; + const auto flags = data[0]; + const auto final = (flags & 0x80) != 0; const auto op_code = static_cast(flags & 0x0f); - const auto is_fragment = (flags & 0x80) == 0 || (flags & 0x0f) == 0; - const auto reassemble = - (transfer.in_progress && transfer.offset > 0) && is_fragment; - const auto final_fragment = (flags & 0x80) != 0; + const auto fragment = !final|| op_code == websocket_op::continuation; - if (is_fragment) + if (fragment) { // RFC6455 Fragments are not currently supported. LOG_ERROR(LOG_SERVER_HTTP) @@ -856,6 +860,8 @@ bool manager::handle_websocket(connection_ptr& connection) return false; } + const auto read_length = static_cast(read_result); + if (read_length < 2) { LOG_ERROR(LOG_SERVER_HTTP) @@ -863,50 +869,54 @@ bool manager::handle_websocket(connection_ptr& connection) return false; } - length = static_cast(data[1] & 0x7f); - mask_length = static_cast(data[1] & 0x80 ? 4 : 0); + length = (data[1] & 0x7f); + mask_length = (data[1] & 0x80 ? 4u : 0u); + if (mask_length == 0) { - // RFC6455: "The server MUST close the connection upon - // receiving a frame that is not masked." + // RFC6455: "The server MUST close the connection upon receiving a + // frame that is not masked." LOG_ERROR(LOG_SERVER_HTTP) << "No mask included from client"; return false; } - if ((length < 126) && (read_length >= mask_length)) + if (length < 126 && read_length >= mask_length) { data_length = length; - header_length = 2 + mask_length; + header_length = 2u + mask_length; } - else if ((length == 126) && (read_length >= 4 + mask_length)) + else if (length == 126 && read_length >= 2u + sizeof(uint16_t) + mask_length) { - data_length = ntohs( - *reinterpret_cast(&data[2])); - header_length = 4 + mask_length; + // BUGBUG: endianness, frame requires a deserializer. + ////data_length = ntohs(*reinterpret_cast(&data[2])); + header_length = 2u + sizeof(uint16_t) + mask_length; } - else if (read_length >= 10 + mask_length) + else if (read_length >= 2u + sizeof(uint64_t) + mask_length) { - data_length = boost::endian::endian_reverse( - *reinterpret_cast(&data[2])); - header_length = 10 + mask_length; + // BUGBUG: endianness, frame requires a deserializer. + ////data_length = boost::endian::endian_reverse( + //// *reinterpret_cast(&data[2])); + header_length = 2u + sizeof(uint64_t) + mask_length; } + const auto reassemble = transfer.in_progress && transfer.offset > 0 && + fragment; + LOG_VERBOSE(LOG_SERVER_HTTP) << "websocket data_frame flags: " << static_cast(flags) - << ", is_fragment: " << (is_fragment ? "true" : "false") + << ", is_fragment: " << (fragment ? "true" : "false") << ", reassemble: " << (reassemble ? "true" : "false") - << ", final_fragment: " << (final_fragment ? "true" : "false"); + << ", final_fragment: " << (final ? "true" : "false"); LOG_VERBOSE(LOG_SERVER_HTTP) << "Incoming websocket data frame length: " << data_length << ", read length: " << read_length; - // If the entire frame payload isn't buffered, initiate state - // to track the transfer of this frame. - if ((data_length > read_length) && !transfer.in_progress) + // If the entire frame payload isn't buffered, initiate state to track the + // transfer of this frame. + if (data_length > read_length && !transfer.in_progress) { - // Check if this transfer exceeds the maximum incoming - // frame length. + // Check if this transfer exceeds the maximum incoming frame length. if (data_length > maximum_incoming_frame_length_) { LOG_ERROR(LOG_SERVER_HTTP) @@ -919,22 +929,15 @@ bool manager::handle_websocket(connection_ptr& connection) transfer.header_length = header_length; transfer.length = data_length + header_length; - data_buffer tmp_data; - transfer.data.swap(tmp_data); + data_chunk data; + transfer.data.swap(data); transfer.data.reserve(transfer.length); transfer.data.insert(transfer.data.end(), buffer.data(), - buffer.data() + header_length); - - transfer.mask.clear(); - if (mask_length) - { - const auto mask_start = - data + transfer.header_length - mask_length; - transfer.mask.reserve(mask_length); - transfer.mask.insert(transfer.mask.end(), mask_start, - mask_start + mask_length); - } + buffer.data() + header_length); + const auto non_mask_length = transfer.header_length - mask_length; + const auto mask_start = data.data() + non_mask_length; + transfer.mask = { mask_start, mask_start + mask_length }; transfer.offset = transfer.data.size(); LOG_DEBUG(LOG_SERVER_HTTP) @@ -949,24 +952,27 @@ bool manager::handle_websocket(connection_ptr& connection) } const auto frame_length = header_length + data_length; - if ((frame_length < header_length) || (frame_length < data_length)) + if (frame_length < header_length || frame_length < data_length) return false; - const auto event_type = - (flags & 0x8 ? event::websocket_control_frame : - event::websocket_frame); + const auto event_type = flags & 0x8 ? + event::websocket_control_frame : event::websocket_frame; if (event_type == event::websocket_control_frame) { // XOR mask the payload using the client provided mask. const auto mask_start = data + header_length - mask_length; - for(int32_t i = 0; i < data_length; i++) - data[i + header_length] ^= mask_start[i % 4]; + + for (size_t index = 0; index < data_length; ++index) + data[index + header_length] ^= mask_start[index % 4]; websocket_message message { - connection->websocket_endpoint(), data + header_length, - data_length, flags, static_cast(flags & 0x0f) + connection->websocket_endpoint(), + data + header_length, + data_length, + flags, + static_cast(flags & 0x0f) }; // Possible TODO: If the opcode is a ping, send response here. @@ -975,8 +981,7 @@ bool manager::handle_websocket(connection_ptr& connection) << "Unhandled websocket op: " << to_string(message.code); // Call user handler for control frames. - auto status = handler_(connection, event_type, - reinterpret_cast(&message)); + const auto status = handler_(connection, event_type, &message); // Returning false here causes the connection to be removed. if (message.code == http::websocket_op::close) @@ -985,27 +990,25 @@ bool manager::handle_websocket(connection_ptr& connection) return status; } - if (final_fragment && transfer.in_progress && - (transfer.offset == transfer.length)) + if (final && transfer.in_progress && transfer.offset == transfer.length) { const auto mask_start = transfer.mask.data(); - const auto payload_length = - transfer.length - transfer.header_length; - for(int32_t i = 0; i < payload_length; i++) - data[i + transfer.header_length] ^= mask_start[i % 4]; + const auto payload_length = transfer.length - transfer.header_length; + + for (size_t index = 0; index < payload_length; ++index) + data[index + transfer.header_length] ^= mask_start[index % 4]; websocket_message message { connection->websocket_endpoint(), data + transfer.header_length, - static_cast(transfer.data.size() - transfer.header_length), + transfer.data.size() - transfer.header_length, flags, static_cast(flags & 0x0f) }; // Call user handler on last fragment with the entire message. - const auto status = handler_(connection, event_type, - reinterpret_cast(&message)); + const auto status = handler_(connection, event_type, &message); transfer.in_progress = false; transfer.offset = 0; @@ -1033,8 +1036,8 @@ bool manager::handle_websocket(connection_ptr& connection) if ((mask_length > 0) && (read_length > data_length)) { const auto mask_start = data + header_length - mask_length; - for(int32_t i = 0; i < data_length; i++) - data[i + header_length] ^= mask_start[i % 4]; + for(size_t index = 0; index < data_length; ++index) + data[index + header_length] ^= mask_start[index % 4]; } websocket_message message @@ -1047,8 +1050,7 @@ bool manager::handle_websocket(connection_ptr& connection) }; // Call user handler for non-fragmented frames. - return handler_(connection, event_type, - reinterpret_cast(&message)); + return handler_(connection, event_type, &message); } return false; @@ -1057,16 +1059,17 @@ bool manager::handle_websocket(connection_ptr& connection) bool manager::send_response(connection_ptr& connection, const http_request& request) { - static const std::vector index_files = + auto path = document_root_; + + if (request.uri == "/") + { + static const std::vector index_files { { "index.html" }, { "index.htm" }, { "index.shtml" } }; - auto path = document_root_; - if (request.uri == "/") - { for (const auto& index: index_files) { const auto test_path = path / index; @@ -1082,25 +1085,25 @@ bool manager::send_response(connection_ptr& connection, } else { + // BUGBUG: sanitize path to guard against information leak. path /= request.uri; } - auto keep_alive = - ((request.protocol.find("HTTP/1.0") == std::string::npos) || - (request.header(std::string("Connection")) == "keep-alive")); - if (!boost::filesystem::exists(path)) { + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Requested Path: " << path << " does not exist"; + + // TODO: move time formatter to independent utility. static const size_t max_date_time_length = 32; - std::array time_buffer{}; + std::array time_buffer; + const auto current_time = std::time(nullptr); - time_t current_time = time(nullptr); - strftime(time_buffer.data(), time_buffer.size(), - "%a, %d %b %Y %H:%M:%S GMT", gmtime(¤t_time)); + // BUGBUG: std::gmtime may not be thread safe. + std::strftime(time_buffer.data(), time_buffer.size(), + "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(¤t_time)); - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Requested Path: " << path << " does not exist"; - static const std::string response = std::string( + const auto response = std::string( "HTTP/1.1 404 Not Found\r\n" "Date: ") + time_buffer.data() + std::string("\r\n" "Content-Type: text/html\r\n" @@ -1112,16 +1115,18 @@ bool manager::send_response(connection_ptr& connection, return true; } - return send_http_file( - connection, path.generic_string(), keep_alive); + const auto keep_alive = + ((request.protocol.find("HTTP/1.0") == std::string::npos) || + (request.header(std::string("Connection")) == "keep-alive")); + + return send_http_file(connection, path.generic_string(), keep_alive); } bool manager::send_generated_reply(connection_ptr& connection, protocol_status status) { http_reply reply; - static std::string empty_mime_type{}; - return connection->write(reply.generate(status, empty_mime_type, 0, false)); + return connection->write(reply.generate(status, {}, 0, false)); } bool manager::upgrade_connection(connection_ptr& connection, @@ -1174,7 +1179,7 @@ bool manager::upgrade_connection(connection_ptr& connection, // This write is unbuffered since we're not a websocket yet and // want to be sure it completes before changing our state to be // upgraded to a websocket. - if (connection->do_write(reinterpret_cast( + if (connection->do_write(reinterpret_cast( response.c_str()), response.size(), false) != static_cast( response.size())) { @@ -1189,32 +1194,25 @@ bool manager::upgrade_connection(connection_ptr& connection, LOG_VERBOSE(LOG_SERVER_HTTP) << "Upgraded connection " << connection << " for uri " << request.uri; - // On upgrade, call the user handler so they can track this - // websocket. + // On upgrade, call the user handler so they can track this websocket. return handler_(connection, event::accepted, nullptr); } bool manager::validate_origin(const std::string origin) { - static const size_t max_hostname_length = 128; - std::array hostname{}; - if (origin.empty()) return false; + if ((origin.find("127.0.0.1") != std::string::npos) || + (origin.find("localhost") != std::string::npos)) + return true; + + static const auto max_hostname_length = 253; + std::array hostname; if (gethostname(hostname.data(), max_hostname_length) != 0) return false; - const auto ip = resolve_hostname({ hostname.data() }); - struct in_addr address; - std::memcpy(&address, &ip, sizeof(address)); - - const auto ip_address = std::string(inet_ntoa(address)); - const auto hostname_string = std::string{ hostname.data() }; - - return ((origin.find("127.0.0.1") != std::string::npos) || - (origin.find(hostname_string) != std::string::npos) || - (origin.find("localhost") != std::string::npos)); + return (origin.find(std::string{ hostname.data() }) != std::string::npos); } bool manager::initialize_ssl(connection_ptr& connection, bool listener) diff --git a/src/web/http/manager.hpp b/src/web/http/manager.hpp index d22de65c..94a7bfcb 100644 --- a/src/web/http/manager.hpp +++ b/src/web/http/manager.hpp @@ -45,11 +45,13 @@ class manager // Required on the Windows platform. bool initialize(); - void set_maximum_incoming_frame_length(int32_t length); - int32_t maximum_incoming_frame_length(); + size_t maximum_incoming_frame_length(); + void set_maximum_incoming_frame_length(size_t length); + // May invalidate any buffered write data on each connection. - void set_high_water_mark(int32_t length); - int32_t high_water_mark(); + size_t high_water_mark(); + void set_high_water_mark(size_t length); + void set_backlog(int32_t backlog); bool bind(std::string hostname, uint16_t port, const bind_options& options); bool accept_connection(); @@ -71,8 +73,8 @@ class manager private: #ifdef WITH_MBEDTLS // Passed to mbedtls for internal use only. - static int ssl_send(void* data, const unsigned char* buffer, size_t length); - static int ssl_receive(void* data, unsigned char* buffer, size_t length); + static int ssl_send(void* data, const uint8_t* buffer, size_t length); + static int ssl_receive(void* data, uint8_t* buffer, size_t length); #endif void run_once(size_t timeout_milliseconds); @@ -100,8 +102,8 @@ class manager event_handler handler_; boost::filesystem::path document_root_; connection_list connections_; - int32_t maximum_incoming_frame_length_; - int32_t high_water_mark_; + size_t maximum_incoming_frame_length_; + size_t high_water_mark_; int32_t backlog_; connection_ptr listener_; struct sockaddr_in listener_address_; diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index 8eb394b2..d4b51437 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -16,6 +16,9 @@ * 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 @@ -26,23 +29,26 @@ namespace libbitcoin { namespace server { namespace http { +// TODO: std::strerror is not required to be thread safe. +// TODO: Win32 FormatMessage requires unicode to utf-8 conversion. std::string error_string() { #ifdef WIN32 - LPVOID buffer{}; - DWORD dw = last_error(); - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&buffer), 0, nullptr); - - const std::string error_string = { buffer }; - LocalFree(buffer); - return error_string; + WCHAR wide[MAX_PATH]; + const auto error = ::GetLastError(); + const auto flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + const auto language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); + + if (::FormatMessageW(flags, nullptr, error, language, wide, MAX_PATH, + nullptr) == 0) + { + // TODO: incorporate `error`. + return "Format error message failed."; + } + + return to_utf8(wide); #else - return { strerror(last_error()) }; + return { strerror(errno) }; #endif } @@ -61,7 +67,7 @@ sha1_hash sha1(const std::string& input) mbedtls_sha1_context context; mbedtls_sha1_init(&context); - const auto source = reinterpret_cast(input.c_str()); + const auto source = reinterpret_cast(input.c_str()); if ((mbedtls_sha1_starts_ret(&context) == 0) && (mbedtls_sha1_update_ret(&context, source, input.size()) == 0) && (mbedtls_sha1_finish_ret(&context, out.data()) == 0)) @@ -73,25 +79,23 @@ sha1_hash sha1(const std::string& input) std::string to_string(websocket_op code) { - static const std::unordered_map opcode_map = + static const std::string unknown = "unknown"; + static const std::unordered_map opcode_map { - { static_cast(websocket_op::continuation), "continue" }, - { static_cast(websocket_op::text), "text" }, - { static_cast(websocket_op::binary), "binary" }, - { static_cast(websocket_op::close), "close" }, - { static_cast(websocket_op::ping), "ping" }, - { static_cast(websocket_op::pong), "pong" } + { websocket_op::continuation, "continue" }, + { websocket_op::text, "text" }, + { websocket_op::binary, "binary" }, + { websocket_op::close, "close" }, + { websocket_op::ping, "ping" }, + { websocket_op::pong, "pong" } }; - auto it = opcode_map.find(static_cast(code)); - if (it != opcode_map.end()) - return it->second; - - return { "unknown" }; + auto it = opcode_map.find(code); + return it == opcode_map.end() ? unknown : it->second; } // Generates the RFC6455 handshake response described here: -// https://tools.ietf.org/html/rfc6455#section-1.3 +// tools.ietf.org/html/rfc6455#section-1.3 std::string websocket_key_response(const std::string& websocket_key) { static const std::string rfc6455_guid = @@ -101,7 +105,7 @@ std::string websocket_key_response(const std::string& websocket_key) // The required buffer size is for a base64 encoded sha1 hash (20 // bytes in length). static constexpr size_t key_buffer_length = 64; - std::array buffer{}; + std::array buffer{}; size_t processed_length = 0; const auto input = websocket_key + rfc6455_guid; @@ -122,12 +126,13 @@ std::string websocket_key_response(const std::string& websocket_key) bool is_json_request(const std::string& header_value) { - return header_value == "application/json-rpc" || + return + header_value == "application/json-rpc" || header_value == "application/json" || header_value == "application/jsonrequest"; } -bool parse_http(const std::string& request, struct http_request& out) +bool parse_http(http_request& out, const std::string& request) { auto& headers = out.headers; auto& parameters = out.parameters; @@ -188,27 +193,31 @@ bool parse_http(const std::string& request, struct http_request& out) boost::algorithm::trim(elements[0]); boost::algorithm::trim(elements[1]); boost::algorithm::to_lower(elements[0]); + if (elements[0] != "sec-websocket-key") boost::algorithm::to_lower(elements[1]); + headers[elements[0]] = elements[1]; } else if (elements.size() > 2) { - std::stringstream ss; + std::stringstream buffer; for (size_t i = 0; i < elements.size(); i++) { boost::algorithm::trim(elements[i]); + if (elements[0] != "sec-websocket-key") boost::algorithm::to_lower(elements[i]); + if (i > 0) { - ss << elements[i]; + buffer << elements[i]; if (i != elements.size() -1) - ss << ":"; + buffer << ":"; } } - headers[elements[0]] = ss.str(); + headers[elements[0]] = buffer.str(); } }; @@ -229,7 +238,7 @@ bool parse_http(const std::string& request, struct http_request& out) if (line.empty()) return; - auto line_end = line.find(" "); + const auto line_end = line.find(" "); const auto input_line = ((line_end == std::string::npos) ? line : line.substr(0, line_end)); @@ -269,44 +278,45 @@ bool parse_http(const std::string& request, struct http_request& out) { const auto json_request = request.substr(request.size() - out.content_length, out.content_length); + LOG_VERBOSE(LOG_SERVER_HTTP) << "POST content: " << json_request; + out.json_rpc = bc::property_tree(out.json_tree, json_request); } return true; } -unsigned long resolve_hostname(const std::string& hostname) -{ - unsigned long address = 0; - -#ifdef WIN32 -#define get_address(host, address) *address = inet_addr(host) -#define validate_address(address) (address != INADDR_NONE) -#define host_info HOSTENT -#else -#define get_address(host, address) inet_pton(AF_INET, host, address) -#define validate_address(address) (address > 0) -#define host_info struct hostent -#endif - - // Resolve dotted ip address. - if (!validate_address(get_address(hostname.c_str(), &address))) - { - // Resolve host name. - const host_info *resolved = gethostbyname(hostname.c_str()); - if (resolved) - address = *(reinterpret_cast( - resolved->h_addr_list[0])); - } - -#undef get_address -#undef validate_address -#undef host_info - - return address; -} +////unsigned long resolve_hostname(const std::string& hostname) +////{ +//// unsigned long address = 0; +//// +////#ifdef WIN32 +//// #define GET_ADDRESS(host, address) *address = ::inet_addr(host) +//// #define VALIDATE_ADDRESS(address) (address != INADDR_NONE) +////#else +//// #define GET_ADDRESS(host, address) inet_pton(AF_INET, host, address) +//// #define VALIDATE_ADDRESS(address) (address > 0) +////#endif +//// +//// // Resolve dotted ip address. +//// if (!VALIDATE_ADDRESS(GET_ADDRESS(hostname.c_str(), &address))) +//// { +//// // Resolve host name. +//// const auto* resolved = gethostbyname(hostname.c_str()); +//// +//// if (resolved != nullptr) +//// address = *(reinterpret_cast( +//// resolved->h_addr_list[0])); +//// } +//// +////#undef get_address +////#undef validate_address +////#undef host_info +//// +//// return address; +////} std::string mime_type(const std::string& filename) { diff --git a/src/web/http/utilities.hpp b/src/web/http/utilities.hpp index 2a1f0f42..2e1a14e7 100644 --- a/src/web/http/utilities.hpp +++ b/src/web/http/utilities.hpp @@ -30,24 +30,25 @@ namespace http { #endif #ifdef WIN32 -#define would_block(x) (x == WSAEWOULDBLOCK) + #define would_block(value) (value == WSAEWOULDBLOCK) #else -#define would_block(x) (x == EAGAIN || x == EWOULDBLOCK) + #define would_block(value) (value == EAGAIN || value == EWOULDBLOCK) #endif -#define mbedtls_would_block(x) \ -(x == MBEDTLS_ERR_SSL_WANT_READ || x == MBEDTLS_ERR_SSL_WANT_WRITE) +#define mbedtls_would_block(value) \ + (value == MBEDTLS_ERR_SSL_WANT_READ || value == MBEDTLS_ERR_SSL_WANT_WRITE) -std::string error_string(); #ifdef WITH_MBEDTLS -std::string mbedtls_error_string(int error); -sha1_hash sha1(const std::string& input); + std::string mbedtls_error_string(int error); + sha1_hash sha1(const std::string& input); #endif + +std::string error_string(); std::string to_string(websocket_op code); std::string websocket_key_response(const std::string& websocket_key); bool is_json_request(const std::string& header_value); -bool parse_http(const std::string& request, struct http_request& out); -unsigned long resolve_hostname(const std::string& hostname); +bool parse_http(http_request& out, const std::string& request); +////unsigned long resolve_hostname(const std::string& hostname); std::string mime_type(const std::string& filename); } // namespace http diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 959f1515..3a3870d5 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -323,8 +323,7 @@ bool query_socket::handle_query(zmq::socket& dealer) } // Decode response and send query output to websocket client. The - // websocket write is performed on the websocket thread via the - // task_sender. + // websocket write is performed on the websocket thread via task_sender. const auto payload = source.read_bytes(); handler->second.decode(payload, id, work.connection); return true; diff --git a/src/web/socket.cpp b/src/web/socket.cpp index 0cad7759..f56d57b3 100644 --- a/src/web/socket.cpp +++ b/src/web/socket.cpp @@ -32,8 +32,7 @@ extern "C" { int https_random(void*, uint8_t* buffer, size_t length) { - bc::data_chunk random; - random.reserve(length); + bc::data_chunk random(length); bc::pseudo_random_fill(random); std::memcpy(buffer, reinterpret_cast(random.data()), length); return 0; @@ -58,12 +57,13 @@ using namespace boost::property_tree; using namespace http; using role = zmq::socket::role; -class task_sender : public http::manager::task +// Local class. +class task_sender + : public http::manager::task { public: task_sender(connection_ptr connection, const std::string& data) - : connection_(connection), - data_(data) + : connection_(connection), data_(data) { } @@ -74,16 +74,17 @@ class task_sender : public http::manager::task if (connection_->json_rpc()) { - static auto keep_alive = false; - http_reply reply; - const auto response = reply.generate(protocol_status::ok, {}, - data_.size(), keep_alive) + data_; - LOG_VERBOSE(LOG_SERVER_HTTP) << "Writing JSON-RPC response: " << response; - return connection_->write(response) == static_cast( - response.size()); + const auto header = reply.generate(protocol_status::ok, {}, + data_.size(), false); + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Writing JSON-RPC response: " << header; + + return connection_->write(header) == static_cast(header.size()); } + // BUGBUG: unguarded narrowing cast. return connection_->write(data_) == static_cast(data_.size()); } @@ -139,15 +140,14 @@ bool socket::handle_event(connection_ptr& connection, const http::event event, const auto& request = *reinterpret_cast(data); BITCOIN_ASSERT(request.json_rpc); - // Use default-value version of get to avoid exceptions on - // invalid input. + // Use default-value get to avoid exceptions on invalid input. const auto id = request.json_tree.get("id", 0); const auto method = request.json_tree.get("method", ""); std::string parameters{}; const auto child = request.json_tree.get_child("params"); std::vector parameter_list; - for (auto& parameter: child) + for (const auto& parameter: child) parameter_list.push_back( parameter.second.get_value()); @@ -165,19 +165,18 @@ bool socket::handle_event(connection_ptr& connection, const http::event event, case http::event::websocket_frame: { - // Process new incoming user websocket data. Returning - // false here will cause this connection to be closed. + // Process new incoming user websocket data. Returning false + // will cause this connection to be closed. auto instance = static_cast(connection->user_data()); - BITCOIN_ASSERT(instance != nullptr); if (instance == nullptr) return false; + BITCOIN_ASSERT(data != nullptr); - const auto& message = *reinterpret_cast( - data); + auto message = reinterpret_cast(data); ptree input_tree; - if (!bc::property_tree(input_tree, std::string( - reinterpret_cast(message.data), message.size))) + if (!bc::property_tree(input_tree, + { message->data, message->data + message->size })) { http_reply reply; connection->write(reply.generate( @@ -185,15 +184,14 @@ bool socket::handle_event(connection_ptr& connection, const http::event event, return false; } - // Use default-value version of get to avoid exceptions on - // invalid input. + // Use default-value get to avoid exceptions on invalid input. const auto id = input_tree.get("id", 0); const auto method = input_tree.get("method", ""); std::string parameters{}; const auto child = input_tree.get_child("params"); std::vector parameter_list; - for (auto& parameter: child) + for (const auto& parameter: child) parameter_list.push_back( parameter.second.get_value()); @@ -356,15 +354,18 @@ bool socket::stop_websocket_handler() size_t socket::connection_count() const { + // BUGBUG: use of connections_ in this method is not thread safe. return connections_.size(); } // Called by the websocket handling thread via handle_event. void socket::add_connection(connection_ptr& connection) { + // BUGBUG: use of connections_ in this method is not thread safe. BITCOIN_ASSERT(connections_.find(connection) == connections_.end()); + // Initialize a new query_work_map for this connection. - connections_[connection] = {}; + connections_[connection].clear(); } // Called by the websocket handling thread via handle_event. @@ -373,33 +374,38 @@ void socket::add_connection(connection_ptr& connection) // thread on response handling (i.e. query_socket::handle_query). void socket::remove_connection(connection_ptr& connection) { + // BUGBUG: use of connections_ in this method is not thread safe. if (connections_.empty()) return; - auto it = connections_.find(connection); + const auto it = connections_.find(connection); if (it != connections_.end()) { // Tearing down a connection is O(n) where n is the amount of // remaining outstanding queries. - //////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// // Critical Section correlation_lock_.lock_upgrade(); + auto& query_work_map = it->second; - for (auto query_work: query_work_map) + for (const auto& query_work: query_work_map) { - const auto id = query_work.second.correlation_id; - auto correlation = correlations_.find(id); + const auto correlation = correlations_.find( + query_work.second.correlation_id); + if (correlation != correlations_.end()) { - // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ correlation_lock_.unlock_upgrade_and_lock(); + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ correlations_.erase(correlation); + // ------------------------------------------------------------ correlation_lock_.unlock_and_lock_upgrade(); } } + correlation_lock_.unlock_upgrade(); - //////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// // Clear the query_work_map for this connection before removal. query_work_map.clear(); @@ -418,7 +424,7 @@ void socket::notify_query_work(connection_ptr& connection, const std::string& method, const uint32_t id, const std::string& parameters) { - typedef std::pair error; + typedef std::pair error; static const error invalid_request{ -32600, "Invalid Request." }; static const error not_found{ -32601, "Method not found." }; static const error internal_error{ -32603, "Internal error." }; @@ -455,6 +461,8 @@ void socket::notify_query_work(connection_ptr& connection, return; } + // BUGBUG: use of connections_ in this method is not thread safe. + // BUGBUG: this includes modification of query_work_map below. auto it = connections_.find(connection); if (it == connections_.end()) { @@ -480,21 +488,20 @@ void socket::notify_query_work(connection_ptr& connection, handler->second.encode(request, handler->second.command, parameters, sequence_); - /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// // Critical Section correlation_lock_.lock(); - // While each connection has its own id map (meaning correlation - // ids passed from the web client are unique on a per connection - // basis, potentially utilizing the full range available), we need - // an internal mapping that will allow us to correlate each zmq - // request/response pair with the connection and original id - // number that originated it. The client never sees this - // sequence_ value. + // While each connection has its own id map (meaning correlation ids passed + // from the web client are unique on a per connection basis, potentially + // utilizing the full range available), we need an internal mapping that + // will allow us to correlate each zmq request/response pair with the + // connection and original id number that originated it. The client never + // sees this sequence_ value. correlations_[sequence_++] = { connection, id }; correlation_lock_.unlock(); - /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// const auto ec = service()->send(request); @@ -510,16 +517,14 @@ void socket::notify_query_work(connection_ptr& connection, // Sends json strings to websockets or connections waiting on json_rpc // replies (only). -// -// By using a task_sender via the manager's execute method, we -// guarantee that the write is performed on the manager's websocket -// thread (at the expense of a copied json response payload). void socket::send(connection_ptr connection, const std::string& json) { if (connection == nullptr || connection->closed() || (!connection->websocket() && !connection->json_rpc())) return; - + // By using a task_sender via the manager's execute method, we guarantee + // that the write is performed on the manager's websocket thread (at the + // expense of copied json send and response payloads). manager_->execute(std::make_shared(connection, json)); } @@ -531,6 +536,7 @@ void socket::broadcast(const std::string& json) send(entry.first, json); }; + // BUGBUG: use of connections_ in this method is not thread safe. std::for_each(connections_.begin(), connections_.end(), sender); } From ec0b2e494eadf5cba77f32b8cc1e9e0a04e9c67a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 20 Oct 2018 19:50:48 -0700 Subject: [PATCH 04/25] Websocket updates WIP --- include/bitcoin/server/web/block_socket.hpp | 3 +- include/bitcoin/server/web/json_string.hpp | 15 +- include/bitcoin/server/web/socket.hpp | 11 +- .../bitcoin/server/web/transaction_socket.hpp | 3 +- src/web/http/connection.cpp | 77 +-- src/web/http/connection.hpp | 28 +- src/web/http/http.hpp | 138 +++-- src/web/http/manager.cpp | 494 +++++++----------- src/web/http/manager.hpp | 94 ++-- src/web/http/utilities.cpp | 8 +- src/web/json_string.cpp | 54 +- src/web/socket.cpp | 102 ++-- 12 files changed, 445 insertions(+), 582 deletions(-) diff --git a/include/bitcoin/server/web/block_socket.hpp b/include/bitcoin/server/web/block_socket.hpp index 026e153d..b2aa11c9 100644 --- a/include/bitcoin/server/web/block_socket.hpp +++ b/include/bitcoin/server/web/block_socket.hpp @@ -29,8 +29,7 @@ namespace server { class server_node; // This class is thread safe. -// Subscribe to block acceptances into the long chain from a dedicated -// socket endpoint. +// Subscribe to block acceptances from a dedicated socket endpoint. class BCS_API block_socket : public socket { diff --git a/include/bitcoin/server/web/json_string.hpp b/include/bitcoin/server/web/json_string.hpp index 37d7ac8b..503fd751 100644 --- a/include/bitcoin/server/web/json_string.hpp +++ b/include/bitcoin/server/web/json_string.hpp @@ -19,8 +19,11 @@ #ifndef LIBBITCOIN_SERVER_WEB_JSON_STRING_HPP #define LIBBITCOIN_SERVER_WEB_JSON_STRING_HPP +#include +#include #include #include +#include namespace libbitcoin { namespace server { @@ -31,13 +34,11 @@ namespace web { std::string to_json(const boost::property_tree::ptree& tree); std::string to_json(uint64_t height, uint32_t id); -std::string to_json(const int code, const std::string message, uint32_t id); -std::string to_json(const std::error_code& code, uint32_t id); -std::string to_json(const bc::chain::header& header, uint32_t id); -std::string to_json(const bc::chain::block& block, uint32_t id); -std::string to_json(const bc::chain::block& block, uint32_t height, - uint32_t id); -std::string to_json(const bc::chain::transaction& transaction, uint32_t id); +std::string to_json(const code& code, uint32_t id); +std::string to_json(const chain::header& header, uint32_t id); +std::string to_json(const chain::block& block, uint32_t id); +std::string to_json(const chain::block& block, uint32_t height, uint32_t id); +std::string to_json(const chain::transaction& transaction, uint32_t id); } // namespace web } // namespace server diff --git a/include/bitcoin/server/web/socket.hpp b/include/bitcoin/server/web/socket.hpp index b7b8394f..d643cd09 100644 --- a/include/bitcoin/server/web/socket.hpp +++ b/include/bitcoin/server/web/socket.hpp @@ -109,11 +109,10 @@ class BCS_API socket bool start() override; size_t connection_count() const; - void add_connection(connection_ptr& connection); - void remove_connection(connection_ptr& connection); - void notify_query_work(connection_ptr& connection, - const std::string& method, const uint32_t id, - const std::string& parameters); + void add_connection(connection_ptr connection); + void remove_connection(connection_ptr connection); + void notify_query_work(connection_ptr connection, + const std::string& method, uint32_t id, const std::string& parameters); protected: // Initialize the websocket event loop and start a thread to poll events. @@ -156,7 +155,7 @@ class BCS_API socket mutable upgrade_mutex correlation_lock_; private: - static bool handle_event(connection_ptr& connection, + static bool handle_event(connection_ptr connection, const http::event event, const void* data); std::shared_ptr manager_; diff --git a/include/bitcoin/server/web/transaction_socket.hpp b/include/bitcoin/server/web/transaction_socket.hpp index 27a2a083..74b147f9 100644 --- a/include/bitcoin/server/web/transaction_socket.hpp +++ b/include/bitcoin/server/web/transaction_socket.hpp @@ -33,8 +33,7 @@ namespace server { class server_node; // This class is thread safe. -// Subscribe to transaction acceptances into the transaction memory -// pool from a dedicated socket endpoint. +// Subscribe to tx acceptances into the pool from a dedicated socket endpoint. class BCS_API transaction_socket : public socket { diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 9abcd53f..7a7ff232 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -17,6 +17,7 @@ * along with this program. If not, see . */ #include +#include #include #include "connection.hpp" @@ -27,33 +28,20 @@ namespace libbitcoin { namespace server { namespace http { +static constexpr size_t maximum_read_length = 1024; +static constexpr size_t high_water_mark = 2 * 1024 * 1024; + connection::connection() - : user_data_(nullptr), - state_(connection_state::unknown), - socket_(0), - address_{}, - last_active_(asio::steady_clock::now()), - high_water_mark_(default_high_water_mark), - maximum_incoming_frame_length_(default_incoming_frame_length), - ssl_context_{}, - websocket_endpoint_{}, - json_rpc_(false), - websocket_(false), - file_transfer_{}, - websocket_transfer_{}, - bytes_read_(0) + : connection(0, sockaddr_in{}) { - write_buffer_.reserve(high_water_mark_); } -connection::connection(sock_t connection, struct sockaddr_in& address) +connection::connection(sock_t connection, const sockaddr_in& address) : user_data_(nullptr), state_(connection_state::unknown), socket_(connection), address_(address), last_active_(asio::steady_clock::now()), - high_water_mark_(default_high_water_mark), - maximum_incoming_frame_length_(default_incoming_frame_length), ssl_context_{}, websocket_endpoint_{}, websocket_(false), @@ -62,7 +50,7 @@ connection::connection(sock_t connection, struct sockaddr_in& address) websocket_transfer_{}, bytes_read_(0) { - write_buffer_.reserve(high_water_mark_); + write_buffer_.reserve(high_water_mark); } connection::~connection() @@ -81,33 +69,6 @@ void connection::set_state(connection_state state) state_ = state; } -size_t connection::high_water_mark() const -{ - return high_water_mark_; -} - -// May invalidate any buffered write data. -void connection::set_high_water_mark(size_t high_water_mark) -{ - if (high_water_mark > 0) - { - high_water_mark_ = high_water_mark; - write_buffer_.reserve(high_water_mark); - write_buffer_.shrink_to_fit(); - } -} - -size_t connection::maximum_incoming_frame_length() const -{ - return maximum_incoming_frame_length_; -} - -void connection::set_maximum_incoming_frame_length(size_t length) -{ - if (length > 0) - maximum_incoming_frame_length_ = length; -} - void connection::set_socket_non_blocking() { #ifdef WIN32 @@ -166,6 +127,18 @@ data_chunk& connection::write_buffer() return write_buffer_; } + +int32_t connection::do_write(const data_chunk& buffer, bool frame) +{ + return do_write(buffer.data(), buffer.size(), frame); +} + +int32_t connection::do_write(const std::string& buffer, bool frame) +{ + const auto data = reinterpret_cast(buffer.data()); + return do_write(data, buffer.size(), frame); +} + // This is effectively a blocking write call that does not buffer internally. int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) { @@ -222,7 +195,7 @@ int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) return written; } - // BUGBUG: non-terminating loop , or does would_block prevent? + // BUGBUG: non-terminating loop, or does would_block prevent? continue; } @@ -251,21 +224,21 @@ int32_t connection::write(const uint8_t* data, size_t length) if (length > max_int32) return -1; - static constexpr auto maximal_websocket_frame = 11u; - const auto buffered_length = write_buffer_.size() + length + - (websocket_ ? maximal_websocket_frame : 0); + const auto frame_size = websocket_ ? websocket_frame::maximal_size : 0; + const auto buffered_length = write_buffer_.size() + length + frame_size; // If we're currently at the hwm, issue blocking writes until // we've cleared the buffered data and then write this current // request. This is an expensive operation, but should be // mostly avoidable with proper hwm tuning of your application. - if (buffered_length >= high_water_mark_) + if (buffered_length >= high_water_mark) { // Drain the buffered data. while (!write_buffer_.empty()) { const auto segment_length = std::min(transfer_buffer_length, write_buffer_.size()); + const auto written = do_write(write_buffer_.data(), segment_length, false); @@ -348,7 +321,7 @@ const std::string& connection::websocket_endpoint() const return websocket_endpoint_; } -void connection::set_websocket_endpoint(const std::string endpoint) +void connection::set_websocket_endpoint(const std::string& endpoint) { websocket_endpoint_ = endpoint; } diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index 105c4130..64ae12bd 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -37,7 +37,7 @@ class connection; typedef std::shared_ptr connection_ptr; typedef std::set connection_set; typedef std::vector connection_list; -typedef std::function +typedef std::function event_handler; // This class is instantiated from accepted/incoming HTTP clients. @@ -45,13 +45,10 @@ typedef std::function class connection { public: - static const uint32_t maximum_read_length = (1 << 10); // 1 KB - static const uint32_t default_high_water_mark = (1 << 21); // 2 MB - static const uint32_t default_incoming_frame_length = (1 << 19); // 512 KB typedef std::function write_method; connection(); - connection(sock_t connection, struct sockaddr_in& address); + connection(sock_t connection, const sockaddr_in& address); ~connection(); // Properties. @@ -69,34 +66,33 @@ class connection bool json_rpc() const; void set_json_rpc(bool json_rpc); - size_t high_water_mark() const; - void set_high_water_mark(size_t high_water_mark); - - size_t maximum_incoming_frame_length() const; - void set_maximum_incoming_frame_length(size_t length); - // Websocket endpoints are HTTP specific endpoints such as '/'. const std::string& websocket_endpoint() const; - void set_websocket_endpoint(const std::string endpoint); + void set_websocket_endpoint(const std::string& endpoint); // Readers and Writers. // ------------------------------------------------------------------------ + // Signed integer results overload negative range for error code. + read_buffer& read_buffer(); data_chunk& write_buffer(); - // Signed integer results use negative range for error code. - int32_t read_length(); int32_t read(); + int32_t read_length(); + int32_t write(const data_chunk& buffer); int32_t write(const std::string& buffer); int32_t write(const uint8_t* data, size_t length); + + int32_t do_write(const data_chunk& buffer, bool frame); + int32_t do_write(const std::string& buffer, bool frame); int32_t do_write(const uint8_t* data, size_t length, bool frame); // Other. // ------------------------------------------------------------------------ void set_socket_non_blocking(); - struct sockaddr_in address() const; + sockaddr_in address() const; bool reuse_address() const; bool closed() const; void close(); @@ -117,8 +113,6 @@ class connection sock_t socket_; sockaddr_in address_; asio::time_point last_active_; - size_t high_water_mark_; - size_t maximum_incoming_frame_length_; ssl ssl_context_; std::string websocket_endpoint_; bool websocket_; diff --git a/src/web/http/http.hpp b/src/web/http/http.hpp index 2ebed169..edeee6ac 100644 --- a/src/web/http/http.hpp +++ b/src/web/http/http.hpp @@ -50,12 +50,6 @@ #include #endif -// Explicitly use std::placeholders here for usage internally to the -// boost parsing helpers included from json_parser.hpp. -// See: https://svn.boost.org/trac10/ticket/12621 -////// TODO: DO NOT USE 'using namespace' IN HEADER. -////using namespace std::placeholders; - #include #include #include @@ -191,18 +185,20 @@ struct websocket_message class websocket_frame { public: - websocket_frame() - : header_length_(0), data_length_(0) + static const size_t maximal_size = 11; + + websocket_frame(const uint8_t* data, size_t size) { + from_data(data, size); } static data_chunk to_data(size_t length, websocket_op code) { - if (length < 126) + if (length < 0x7e) { return build_chunk( { - to_array(0x80 | static_cast(code)), + to_array(uint8_t(0x80) | static_cast(code)), to_array(static_cast(length)) }); } @@ -210,8 +206,8 @@ class websocket_frame { return build_chunk( { - to_array(0x80 | static_cast(code)), - to_array(uint8_t(126)), + to_array(uint8_t(0x80) | static_cast(code)), + to_array(uint8_t(0x7e)), to_big_endian(static_cast(length)) }); } @@ -219,63 +215,111 @@ class websocket_frame { return build_chunk( { - to_array(0x80 | static_cast(code)), - to_array(uint8_t(127)), + to_array(uint8_t(0x80) | static_cast(code)), + to_array(uint8_t(0x7f)), to_big_endian(static_cast(length)) }); } } - bool from_data(const uint8_t* data, size_t size) + operator bool() const { - static constexpr auto prefix_length = 2u; - static constexpr auto mask_length = 4u; - - header_length_ = 0; - data_length_ = 0; - - if (size < prefix_length || (data[1] & 0x80) == 0) - return false; + return valid_; + } - const size_t length = (data[1] & 0x7f); + bool final() const + { + return (flags_ & 0x80) != 0; + } - if (size >= mask_length && length < 126u) - { - data_length_ = length; - header_length_ = prefix_length + mask_length; - } - else if (size >= prefix_length + sizeof(uint16_t) + mask_length && - length == 126u) - { - data_length_ = from_big_endian(&data[prefix_length], - &data[prefix_length + sizeof(uint16_t)]); + bool fragment() const + { + return !final() || op_code() == websocket_op::continuation; + } - header_length_ = prefix_length + sizeof(uint16_t) + mask_length; - } - else if (size >= prefix_length + sizeof(uint64_t) + mask_length) - { - data_length_ = from_big_endian(&data[prefix_length], - &data[prefix_length + sizeof(uint64_t)]); + event event_type() const + { + return (flags_ & 0x08) ? event::websocket_control_frame : + event::websocket_frame; + } - header_length_ = prefix_length + sizeof(uint64_t) + mask_length; - } + websocket_op op_code() const + { + return static_cast(flags_ & 0x0f); + } - return true; + uint8_t flags() const + { + return flags_; } size_t header_length() const { - return header_length_; + return header_; } size_t data_length() const { - return data_length_; + return data_; + } + + size_t mask_length() const + { + return valid_ ? mask_ : 0; } private: - size_t header_length_; - size_t data_length_; + void from_data(const uint8_t* data, size_t read_length) + { + static constexpr size_t prefix = 2; + static constexpr size_t prefix16 = prefix + sizeof(uint16_t); + static constexpr size_t prefix64 = prefix + sizeof(uint64_t); + + valid_ = false; + + // Invalid websocket frame (too small). + if (read_length < 2) + return; + + flags_ = data[0]; + header_ = 0; + data_ = 0; + + // Invalid websocket frame (unmasked). + if ((data[1] & 0x80) == 0) + return; + + const size_t length = (data[1] & 0x7f); + + if (read_length >= mask_ && length < 0x7e) + { + header_ = prefix + mask_; + data_ = length; + } + else if (read_length >= prefix16 + mask_ && length == 0x7e) + { + header_ = prefix16 + mask_; + data_ = from_big_endian(&data[prefix], + &data[prefix16]); + } + else if (read_length >= prefix64 + mask_) + { + header_ = prefix64 + mask_; + data_ = from_big_endian(&data[prefix], + &data[prefix64]); + } + + valid_ = true; + return; + } + +private: + static const size_t mask_ = 4; + + bool valid_; + uint8_t flags_; + size_t header_; + size_t data_; }; struct ssl diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 66bd048f..10c96534 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -16,16 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +#include "manager.hpp" + +#include +#include +#include #include #include -#include -#ifdef WIN32 - #include -#endif - #include "http.hpp" #include "utilities.hpp" -#include "manager.hpp" #ifdef WITH_MBEDTLS extern "C" @@ -39,22 +38,24 @@ namespace libbitcoin { namespace server { namespace http { -manager::manager(bool ssl, event_handler handler, - boost::filesystem::path document_root) -#ifdef WITH_MBEDTLS +// The protocol message_size_limit is on the order of 1M. The maximum +// websocket frame size is set to a much smaller fraction of this because our +// websocket protocol implementation does not contain large incoming messages, +// and also to help avoid DoS attacks via large incoming messages. +static constexpr size_t maximum_incoming_message_size = 4 * 1024; +static constexpr size_t timeout_milliseconds = 10; +static constexpr size_t maximum_backlog = 8; +static constexpr size_t maximum_connections = FD_SETSIZE; + +manager::manager(bool ssl, event_handler handler, path document_root) : ssl_(ssl), -#else - : ssl_(false), -#endif + running_(false), listening_(false), + initialized_(false), + port_(0), user_data_(nullptr), - running_(false), handler_(handler), - document_root_(document_root), - connections_{}, - maximum_incoming_frame_length_(1 << 19), // 512 KB - high_water_mark_(1 << 21), // 2 MB - backlog_(8) + document_root_(document_root) { #ifndef WITH_MBEDTLS BITCOIN_ASSERT_MSG(!ssl, "Secure HTTP requires MBEDTLS library."); @@ -63,62 +64,43 @@ manager::manager(bool ssl, event_handler handler, manager::~manager() { - running_ = false; - listening_ = false; - connections_.clear(); - #ifdef WIN32 - WSACleanup(); + if (initialized_) + ::WSACleanup(); #endif } +// Initialize is not thread safe. bool manager::initialize() { #ifdef WIN32 - WSADATA wsa_data{}; - if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) + WSADATA wsa_data; + static constexpr auto winsock_version = MAKEWORD(2, 2); + if (::WSAStartup(winsock_version, &wsa_data) != 0) return false; -#endif - return true; -} -size_t manager::maximum_incoming_frame_length() -{ - return maximum_incoming_frame_length_; -} - -void manager::set_maximum_incoming_frame_length(size_t length) -{ - maximum_incoming_frame_length_ = length; - for (auto& connection: connections_) - connection->set_maximum_incoming_frame_length(length); -} - -size_t manager::high_water_mark() -{ - return high_water_mark_; + initialized_ = true; + return LOBYTE(wsa_data.wVersion) == 2 && HIBYTE(wsa_data.wVersion) == 2; +#else + return true; +#endif } -// May truncate any previously buffered write data on each connection. -void manager::set_high_water_mark(size_t length) +// Bind is not thread safe. +bool manager::bind(const config::endpoint& address, + const bind_options& options) { - high_water_mark_ = length; - for (auto& connection: connections_) - connection->set_high_water_mark(length); -} + if (address.host() != "*") + { + LOG_INFO(LOG_SERVER_HTTP) + << "Failed to bind to named host (unsupported): " << address; + return false; + } -void manager::set_backlog(int32_t backlog) -{ - backlog_ = backlog; -} + port_ = address.port(); -bool manager::bind(std::string hostname, uint16_t port, - const bind_options& options) -{ LOG_VERBOSE(LOG_SERVER_HTTP) - << (ssl_ ? "Secure" : "Public") << " bind called with host " - << hostname << " and port " << port; - port_ = port; + << (ssl_ ? "Secure" : "Public") << " bind to port " << port_; std::memset(&listener_address_, 0, sizeof(listener_address_)); listener_address_.sin_family = AF_INET; @@ -129,8 +111,7 @@ bool manager::bind(std::string hostname, uint16_t port, user_data_ = options.user_data; listener_ = std::make_shared(); - listener_->socket() = ::socket(listener_address_.sin_family, SOCK_STREAM, - 0); + listener_->socket() = ::socket(listener_address_.sin_family, SOCK_STREAM, 0); if (listener_->socket() == 0) { @@ -140,7 +121,6 @@ bool manager::bind(std::string hostname, uint16_t port, return false; } -#ifdef WITH_MBEDTLS if (ssl_) { if (!options.ssl_certificate.empty() || @@ -153,11 +133,7 @@ bool manager::bind(std::string hostname, uint16_t port, if (!initialize_ssl(listener_, listening_)) return false; - - LOG_DEBUG(LOG_SERVER_HTTP) - << "SSL initialized for listener socket"; } -#endif listener_->set_socket_non_blocking(); listener_->reuse_address(); @@ -172,7 +148,7 @@ bool manager::bind(std::string hostname, uint16_t port, return false; } - ::listen(listener_->socket(), backlog_); + ::listen(listener_->socket(), maximum_backlog); listener_->set_state(connection_state::listening); add_connection(listener_); @@ -181,36 +157,26 @@ bool manager::bind(std::string hostname, uint16_t port, bool manager::accept_connection() { - struct sockaddr_in remote_address; + sockaddr_in remote_address{ 0, 0, 0, 0 }; std::memset(&remote_address, 0, sizeof(remote_address)); - - sock_t socket{}; auto address_size = static_cast(sizeof(remote_address)); + auto socket = ::accept(listener_->socket(), reinterpret_cast( + &remote_address), &address_size); - do - { - socket = ::accept(listener_->socket(), reinterpret_cast( - &remote_address), &address_size); - - const auto error = last_error(); - if ((static_cast(socket) == - connection_state::error) && !would_block(error)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Accept call failed with " << error_string(); - return false; - } - else - { - break; - } + const auto error = last_error(); - } while (true); + if ((static_cast(socket) == connection_state::error) && + !would_block(error)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Accept call failed with " << error_string(); + return false; + } #ifdef SO_NOSIGPIPE int no_sig_pipe = 1; - if (setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, - reinterpret_cast(&no_sig_pipe), sizeof(no_sig_pipe)) != 0) + if (setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, + sizeof(no_sig_pipe)) != 0) { LOG_ERROR(LOG_SERVER_HTTP) << "Failed to disable SIGPIPE"; @@ -219,9 +185,9 @@ bool manager::accept_connection() } #endif - auto connection = std::make_shared(socket, - remote_address); - if (connection == nullptr) + auto connection = std::make_shared(socket, remote_address); + + if (!connection) { LOG_ERROR(LOG_SERVER_HTTP) << "Failed to create new connection object"; @@ -266,12 +232,8 @@ bool manager::accept_connection() } #endif - // Set all per-connection variables, based on user-specified - // or otherwise default settings. + // Set all per-connection variables. connection->set_state(connection_state::connected); - connection->set_maximum_incoming_frame_length( - maximum_incoming_frame_length_); - connection->set_high_water_mark(high_water_mark_); connection->set_user_data(user_data_); connection->set_socket_non_blocking(); @@ -283,7 +245,7 @@ bool manager::accept_connection() return true; } -void manager::add_connection(const connection_ptr& connection) +void manager::add_connection(const connection_ptr connection) { connections_.push_back(connection); @@ -292,7 +254,7 @@ void manager::add_connection(const connection_ptr& connection) << connections_.size() << " total]"; } -void manager::remove_connection(connection_ptr& connection) +void manager::remove_connection(connection_ptr connection) { const auto it = std::find(connections_.begin(), connections_.end(), connection); @@ -307,30 +269,29 @@ void manager::remove_connection(connection_ptr& connection) } else { + // TODO: this should never happend (hard failure). LOG_VERBOSE(LOG_SERVER_HTTP) << "Cannot locate connection for removal"; } } -size_t manager::connection_count() +size_t manager::connection_count() const { return connections_.size(); } -bool manager::ssl() +bool manager::ssl() const { return ssl_; } -bool manager::listening() +bool manager::listening() const { return listening_; } void manager::start() { - static const auto timeout_milliseconds = 10u; - running_ = true; while (running_) run_once(timeout_milliseconds); @@ -341,7 +302,7 @@ void manager::run_once(size_t timeout_milliseconds) if (stopped()) return; - // Run any tasks the user queued that must be run inside this thread. + // Run any tasks the user queued tasks that must be run inside this thread. run_tasks(); // Monitor and process sockets. @@ -350,70 +311,73 @@ void manager::run_once(size_t timeout_milliseconds) void manager::stop() { - running_ = false; + running_.store(false); } -bool manager::stopped() +bool manager::stopped() const { - return !running_; + return !running_.load(); } -void manager::execute(std::shared_ptr task) +void manager::execute(task_ptr task) { - task_lock_.lock(); + // Critical Section. + /////////////////////////////////////////////////////////////////////////// + unique_lock lock(task_mutex_); tasks_.push_back(task); - task_lock_.unlock(); + /////////////////////////////////////////////////////////////////////////// } void manager::run_tasks() { task_list tasks; - task_lock_.lock(); + // Critical Section. + /////////////////////////////////////////////////////////////////////////// + task_mutex_.lock(); tasks_.swap(tasks); - task_lock_.unlock(); + task_mutex_.unlock(); + /////////////////////////////////////////////////////////////////////////// - for (auto& task: tasks) + for (const auto task: tasks) if (!task->run()) handle_connection(task->connection(), event::error); } // Portable select based implementation. +// Break up number of connections into N lists of a specified +// maximum size and call select for each of them, given a +// timeout of (timeout_milliseconds / N). +// This is a hack to work around limitations of the select +// system call. Note that you may also need to adjust the +// descriptor limit for this process in order for this to work +// properly. +// With very large connection counts and small specified +// timeout values, this poll method may very well exceed the +// specified timeout. void manager::poll(size_t timeout_milliseconds) { - static const size_t maximum_items = FD_SETSIZE; - // Break up number of connections into N lists of a specified - // maximum size and call select for each of them, given a - // timeout of (timeout_milliseconds / N). - // - // This is a hack to work around limitations of the select - // system call. Note that you may also need to adjust the - // descriptor limit for this process in order for this to work - // properly. - // - // With very large connection counts and small specified - // timeout values, this poll method may very well exceed the - // specified timeout. - if (connections_.size() > maximum_items) + if (connections_.size() > maximum_connections) { - const size_t number_of_lists = - (connections_.size() / maximum_items) + 1u; + const auto number_of_lists = + (connections_.size() / maximum_connections) + 1u; const auto adjusted_timeout = static_cast( std::ceil(timeout_milliseconds / number_of_lists)); std::vector connection_lists; connection_lists.reserve(number_of_lists); - for (size_t i = 0; i < number_of_lists; i++) + + for (auto it = 0; it < number_of_lists; it++) { connection_list connection_list; - connection_list.reserve(maximum_items); + connection_list.reserve(maximum_connections); connection_lists.push_back(connection_list); } - for (size_t i = 0, list_index = 0; i < connections_.size(); i++) + for (size_t it = 0, index = 0; it < connections_.size(); it++) { - connection_lists[list_index].push_back(connections_[i]); - if ((i > 0) && ((i - 1) % maximum_items) == 0) - list_index++; + connection_lists[index].push_back(connections_[it]); + if ((it > 0) && ((it - 1) % maximum_connections) == 0) + index++; } for (auto& connection_list: connection_lists) @@ -427,14 +391,13 @@ void manager::poll(size_t timeout_milliseconds) void manager::select(size_t timeout_milliseconds, connection_list& connections) { - static const int maximum_items = FD_SETSIZE; - connection_ptr static_sockets[maximum_items]{}; - connection_ptr* socket_list = static_sockets; + // TODO: use std::array or std::vector. + connection_ptr socket_list[maximum_connections]; // This limit must be honored by the caller. - BITCOIN_ASSERT(connections.size() <= maximum_items); + BITCOIN_ASSERT(connections.size() <= maximum_connections); - timeval poll_interval{}; + timeval poll_interval; poll_interval.tv_sec = static_cast(timeout_milliseconds / 1000); poll_interval.tv_usec = static_cast((timeout_milliseconds * 1000) - (poll_interval.tv_sec * 100000)); @@ -448,31 +411,33 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) FD_ZERO(&error_set); size_t last_index = 0; - int max_descriptor = 0; + sock_t max_descriptor = 0; connection_list pending_removal; - for (auto& connection: connections) + for (const auto connection: connections) { - auto descriptor = static_cast(connection->socket()); - if (connection == nullptr || connection->closed()) + if (!connection || connection->closed()) continue; - // Check if the descriptor is too high to monitor in our - // fd_set. - if (descriptor > maximum_items) + const auto descriptor = connection->socket(); + + // TODO: how does this relation make sense? + // Check if the descriptor is too high to monitor in our fd_set. + if (descriptor > maximum_connections) { #ifdef WIN32 CLOSE_SOCKET(descriptor); LOG_ERROR(LOG_SERVER_HTTP) << "Error: cannot monitor socket " << descriptor - << ", value is above" << maximum_items; + << ", value is above" << maximum_connections; pending_removal.push_back(connection); continue; #else // Attempt to resolve this by looking for a lower // available descriptor. auto new_descriptor = dup(descriptor); - if (new_descriptor < descriptor && new_descriptor < maximum_items) + if (new_descriptor < descriptor && + new_descriptor < maximum_connections) { connection->socket() = new_descriptor; CLOSE_SOCKET(descriptor); @@ -483,7 +448,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) CLOSE_SOCKET(new_descriptor); LOG_ERROR(LOG_SERVER_HTTP) << "Error: cannot monitor socket " << descriptor - << ", value is above" << maximum_items; + << ", value is above" << maximum_connections; pending_removal.push_back(connection); continue; } @@ -502,13 +467,22 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) max_descriptor = descriptor; } - for (auto& connection: pending_removal) + for (const auto connection: pending_removal) handle_connection(connection, event::error); pending_removal.clear(); - const auto num_events = ::select(max_descriptor + 1, &read_set, - &write_set, &error_set, &poll_interval); + // Guard ::select(max_descriptor + 1, ...) + if (max_descriptor > static_cast(max_int32 - 1)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Error: select fd overflow: " << max_descriptor; + return; + } + + const auto fd_count = static_cast(max_descriptor + 1); + const auto num_events = ::select(fd_count, &read_set, &write_set, + &error_set, &poll_interval); if (num_events == 0) return; @@ -521,10 +495,10 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) return; } - for (size_t i = 0; i < last_index; i++) + for (size_t index = 0; index < last_index; index++) { - auto& connection = socket_list[i]; - if (connection == nullptr || connection->closed()) + const auto connection = socket_list[index]; + if (!connection || connection->closed()) continue; const auto descriptor = connection->socket(); @@ -536,8 +510,8 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) if (FD_ISSET(descriptor, &write_set)) { - if (connection->file_transfer().in_progress && !transfer_file_data( - connection)) + if (connection->file_transfer().in_progress && + !transfer_file_data(connection)) { pending_removal.push_back(connection); continue; @@ -546,8 +520,10 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) auto& write_buffer = connection->write_buffer(); if (!write_buffer.empty()) { - const auto segment_length = std::min(write_buffer.size(), transfer_buffer_length); - const auto written = connection->do_write(write_buffer.data(), segment_length, false); + const auto segment_length = std::min(write_buffer.size(), + transfer_buffer_length); + const auto written = connection->do_write(write_buffer.data(), + segment_length, false); if (written < 0) { @@ -587,11 +563,11 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) } } - for (auto& connection: pending_removal) + for (const auto connection: pending_removal) handle_connection(connection, event::error); } -bool manager::handle_connection(connection_ptr& connection, event current_event) +bool manager::handle_connection(connection_ptr connection, event current_event) { switch (current_event) { @@ -630,14 +606,11 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) transfer.offset += read_length; BITCOIN_ASSERT(transfer.offset == transfer.data.size()); - // Check for configuration violation (helps - // prevent DoS by filling RAM with unexpectedly - // large messages). - if (transfer.offset > maximum_incoming_frame_length_) + // Check for configuration violation (DoS protection). + if (transfer.offset > maximum_incoming_message_size) { LOG_ERROR(LOG_SERVER_HTTP) - << "Terminating due to exceeding the " - "maximum_incoming_frame_length"; + << "Terminating connection due to excessive frame length."; return false; } @@ -650,29 +623,30 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) const std::string request{ buffer.begin(), buffer.begin() + read_length }; - http_request out{}; + http_request out; if (parse_http(out, request)) { + // Check if we need to convert HTTP connection to websocket. if (out.upgrade_request) return upgrade_connection(connection, out); - // Check if we need to mark HTTP connection as - // expecting a JSON-RPC reply. If so, we need to call - // the user handler to notify user that a new json_rpc - // connection was accepted so that they can track it. + // Check if we need to mark HTTP connection as expecting a + // JSON-RPC reply. If so, we need to call the user handler + // to notify user that a new json_rpc connection was accepted + // so that they can track it. connection->set_json_rpc(out.json_rpc); + const auto request = reinterpret_cast(&out); + if (out.json_rpc) { return handler_(connection, event::accepted, nullptr) && - handler_(connection, event::json_rpc, reinterpret_cast< - void*>(&out)); + handler_(connection, event::json_rpc, request); } - // Call user's event handler with the parsed http - // request. - return (handler_(connection, event::read, reinterpret_cast< - void*>(&out)) ? send_response(connection, out) : false); + // Call user's event handler with the parsed http request. + return handler_(connection, event::read, request) && + send_response(connection, out); } else { @@ -686,8 +660,7 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) case event::write: { - // Should never get here since writes are handled - // elsewhere. + // Should never get here since writes are handled elsewhere. BITCOIN_ASSERT(false); return false; } @@ -695,7 +668,7 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) case event::error: case event::closing: { - if ((connection == nullptr) || connection->closed()) + if (!connection || connection->closed()) return false; LOG_VERBOSE(LOG_SERVER_HTTP) @@ -715,7 +688,7 @@ bool manager::handle_connection(connection_ptr& connection, event current_event) #ifdef WITH_MBEDTLS // static -int manager::ssl_send(void* data, const uint8_t* buffer, size_t length) +int32_t manager::ssl_send(void* data, const uint8_t* buffer, size_t length) { auto connection = reinterpret_cast(data); #ifdef MSG_NOSIGNAL @@ -733,19 +706,19 @@ int manager::ssl_send(void* data, const uint8_t* buffer, size_t length) } // static -int manager::ssl_receive(void* data, uint8_t* buffer, size_t length) +int32_t manager::ssl_receive(void* data, uint8_t* buffer, size_t length) { auto connection = reinterpret_cast(data); auto read = static_cast(recv(connection->socket(), buffer, length, 0)); if (read >= 0) return read; - return ((would_block(read) || read == EINPROGRESS) ? - MBEDTLS_ERR_SSL_WANT_READ : -1); + return (would_block(read) || read == EINPROGRESS) ? + MBEDTLS_ERR_SSL_WANT_READ : -1; } #endif -bool manager::transfer_file_data(connection_ptr& connection) +bool manager::transfer_file_data(connection_ptr connection) { std::array buffer; auto& file_transfer = connection->file_transfer(); @@ -756,8 +729,8 @@ bool manager::transfer_file_data(connection_ptr& connection) auto amount_to_read = std::min(transfer_buffer_length, file_transfer.length - file_transfer.offset); - const auto read = fread(buffer.data(), sizeof(uint8_t), amount_to_read, - file_transfer.descriptor); + const auto read = std::fread(buffer.data(), sizeof(uint8_t), + amount_to_read, file_transfer.descriptor); auto success = (read == amount_to_read || (read < amount_to_read && feof(file_transfer.descriptor))); @@ -801,7 +774,7 @@ bool manager::transfer_file_data(connection_ptr& connection) return success; } -bool manager::send_http_file(connection_ptr& connection, +bool manager::send_http_file(connection_ptr connection, const std::string& path, bool keep_alive) { auto& file_transfer = connection->file_transfer(); @@ -829,99 +802,44 @@ bool manager::send_http_file(connection_ptr& connection, return transfer_file_data(connection); } - -bool manager::handle_websocket(connection_ptr& connection) +bool manager::handle_websocket(connection_ptr connection) { - const auto read_result = connection->read_length(); - - if (read_result <= 0) - return false; - - size_t length = 0; - size_t mask_length = 0; - size_t data_length = 0; - size_t header_length = 0; - + const auto read_length = static_cast(connection->read_length()); auto& buffer = connection->read_buffer(); auto& transfer = connection->websocket_transfer(); - const auto data = transfer.in_progress ? transfer.data.data() : - buffer.data(); - - const auto flags = data[0]; - const auto final = (flags & 0x80) != 0; - const auto op_code = static_cast(flags & 0x0f); - const auto fragment = !final|| op_code == websocket_op::continuation; - - if (fragment) - { - // RFC6455 Fragments are not currently supported. - LOG_ERROR(LOG_SERVER_HTTP) - << "Websocket fragments are not supported"; - return false; - } + auto data = transfer.in_progress ? transfer.data.data() : buffer.data(); - const auto read_length = static_cast(read_result); + websocket_frame frame(data, read_length); - if (read_length < 2) + // Websocket fragments are not supported. + if (!frame || frame.fragment()) { LOG_ERROR(LOG_SERVER_HTTP) - << "Invalid websocket frame"; + << "Invalid websocket frame."; return false; } - length = (data[1] & 0x7f); - mask_length = (data[1] & 0x80 ? 4u : 0u); - - if (mask_length == 0) - { - // RFC6455: "The server MUST close the connection upon receiving a - // frame that is not masked." - LOG_ERROR(LOG_SERVER_HTTP) - << "No mask included from client"; - return false; - } - - if (length < 126 && read_length >= mask_length) - { - data_length = length; - header_length = 2u + mask_length; - } - else if (length == 126 && read_length >= 2u + sizeof(uint16_t) + mask_length) - { - // BUGBUG: endianness, frame requires a deserializer. - ////data_length = ntohs(*reinterpret_cast(&data[2])); - header_length = 2u + sizeof(uint16_t) + mask_length; - } - else if (read_length >= 2u + sizeof(uint64_t) + mask_length) - { - // BUGBUG: endianness, frame requires a deserializer. - ////data_length = boost::endian::endian_reverse( - //// *reinterpret_cast(&data[2])); - header_length = 2u + sizeof(uint64_t) + mask_length; - } - - const auto reassemble = transfer.in_progress && transfer.offset > 0 && - fragment; + const auto flags = frame.flags(); + const auto final = frame.final(); + const auto op_code = frame.op_code(); + const auto event_type = frame.event_type(); + const auto mask_length = frame.mask_length(); + const auto data_length = frame.data_length(); + const auto header_length = frame.header_length(); LOG_VERBOSE(LOG_SERVER_HTTP) - << "websocket data_frame flags: " << static_cast(flags) - << ", is_fragment: " << (fragment ? "true" : "false") - << ", reassemble: " << (reassemble ? "true" : "false") - << ", final_fragment: " << (final ? "true" : "false"); - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Incoming websocket data frame length: " << data_length + << "Websocket data_frame flags: " << flags + << ", final_fragment: " << (final ? "true" : "false") << ", read length: " << read_length; - // If the entire frame payload isn't buffered, initiate state to track the - // transfer of this frame. + // If full frame payload isn't buffered, initiate state to track transfer. if (data_length > read_length && !transfer.in_progress) { // Check if this transfer exceeds the maximum incoming frame length. - if (data_length > maximum_incoming_frame_length_) + if (data_length > maximum_incoming_message_size) { LOG_ERROR(LOG_SERVER_HTTP) - << "Terminating connection due to exceeding the " - << "maximum_incoming_frame_length"; + << "Terminating connection due to excessive frame length."; return false; } @@ -955,9 +873,6 @@ bool manager::handle_websocket(connection_ptr& connection) if (frame_length < header_length || frame_length < data_length) return false; - const auto event_type = flags & 0x8 ? - event::websocket_control_frame : event::websocket_frame; - if (event_type == event::websocket_control_frame) { // XOR mask the payload using the client provided mask. @@ -972,7 +887,7 @@ bool manager::handle_websocket(connection_ptr& connection) data + header_length, data_length, flags, - static_cast(flags & 0x0f) + op_code }; // Possible TODO: If the opcode is a ping, send response here. @@ -1004,7 +919,7 @@ bool manager::handle_websocket(connection_ptr& connection) data + transfer.header_length, transfer.data.size() - transfer.header_length, flags, - static_cast(flags & 0x0f) + op_code }; // Call user handler on last fragment with the entire message. @@ -1019,8 +934,7 @@ bool manager::handle_websocket(connection_ptr& connection) return status; } - else if (!reassemble && (flags & 0x0f) == - static_cast(websocket_op::close)) + else if (op_code == websocket_op::close) { LOG_DEBUG(LOG_SERVER_HTTP) << "Closing websocket due to close op."; @@ -1036,7 +950,7 @@ bool manager::handle_websocket(connection_ptr& connection) if ((mask_length > 0) && (read_length > data_length)) { const auto mask_start = data + header_length - mask_length; - for(size_t index = 0; index < data_length; ++index) + for (size_t index = 0; index < data_length; ++index) data[index + header_length] ^= mask_start[index % 4]; } @@ -1056,7 +970,7 @@ bool manager::handle_websocket(connection_ptr& connection) return false; } -bool manager::send_response(connection_ptr& connection, +bool manager::send_response(connection_ptr connection, const http_request& request) { auto path = document_root_; @@ -1122,14 +1036,14 @@ bool manager::send_response(connection_ptr& connection, return send_http_file(connection, path.generic_string(), keep_alive); } -bool manager::send_generated_reply(connection_ptr& connection, +bool manager::send_generated_reply(connection_ptr connection, protocol_status status) { http_reply reply; return connection->write(reply.generate(status, {}, 0, false)); } -bool manager::upgrade_connection(connection_ptr& connection, +bool manager::upgrade_connection(connection_ptr connection, const http_request& request) { // Request MUST be GET and Protocol must be at least 1.1 @@ -1142,8 +1056,8 @@ bool manager::upgrade_connection(connection_ptr& connection, return false; } - // Verify if origin is acceptable (contains either - // localhost, hostname, or ip address of current server) + // Verify if origin is acceptable (contains either localhost, hostname, + // or ip address of current server) if (!validate_origin(request.header("origin"))) { LOG_ERROR(LOG_SERVER_HTTP) @@ -1198,24 +1112,13 @@ bool manager::upgrade_connection(connection_ptr& connection, return handler_(connection, event::accepted, nullptr); } -bool manager::validate_origin(const std::string origin) +bool manager::validate_origin(const std::string& origin) { - if (origin.empty()) - return false; - - if ((origin.find("127.0.0.1") != std::string::npos) || - (origin.find("localhost") != std::string::npos)) - return true; - - static const auto max_hostname_length = 253; - std::array hostname; - if (gethostname(hostname.data(), max_hostname_length) != 0) - return false; - - return (origin.find(std::string{ hostname.data() }) != std::string::npos); + // TODO: test against configured set. + return true; } -bool manager::initialize_ssl(connection_ptr& connection, bool listener) +bool manager::initialize_ssl(connection_ptr connection, bool listener) { #ifdef WITH_MBEDTLS auto& context = connection->ssl_context(); @@ -1309,6 +1212,9 @@ bool manager::initialize_ssl(connection_ptr& connection, bool listener) mbedtls_ssl_conf_ciphersuites(&configuration, default_ciphers); + LOG_DEBUG(LOG_SERVER_HTTP) + << "SSL initialized for listener socket"; + context.enabled = true; return context.enabled; #else diff --git a/src/web/http/manager.hpp b/src/web/http/manager.hpp index 94a7bfcb..a53f8c8e 100644 --- a/src/web/http/manager.hpp +++ b/src/web/http/manager.hpp @@ -19,6 +19,10 @@ #ifndef LIBBITCOIN_SERVER_WEB_HTTP_MANAGER_HPP #define LIBBITCOIN_SERVER_WEB_HTTP_MANAGER_HPP +#include +#include +#include +#include #include "connection.hpp" namespace libbitcoin { @@ -33,83 +37,79 @@ class manager public: virtual ~task() = default; virtual bool run() = 0; - virtual connection_ptr& connection() = 0; + virtual connection_ptr connection() = 0; }; - typedef std::vector> task_list; + typedef boost::filesystem::path path; + typedef std::shared_ptr task_ptr; + typedef std::vector task_list; - explicit manager(bool ssl, event_handler handler, - boost::filesystem::path document_root); + manager(bool ssl, event_handler handler, path document_root); ~manager(); - // Required on the Windows platform. bool initialize(); + bool bind(const config::endpoint& address, const bind_options& options); - size_t maximum_incoming_frame_length(); - void set_maximum_incoming_frame_length(size_t length); - - // May invalidate any buffered write data on each connection. - size_t high_water_mark(); - void set_high_water_mark(size_t length); - - void set_backlog(int32_t backlog); - bool bind(std::string hostname, uint16_t port, const bind_options& options); + // Connections. bool accept_connection(); - void add_connection(const connection_ptr& connection); - void remove_connection(connection_ptr& connection); - size_t connection_count(); - bool ssl(); - bool listening(); + void add_connection(const connection_ptr connection); + void remove_connection(connection_ptr connection); + size_t connection_count() const; + + bool ssl() const; + bool listening() const; + bool stopped() const; void start(); void stop(); - bool stopped(); - void execute(std::shared_ptr task); + void execute(task_ptr task); void run_tasks(); void poll(size_t timeout_milliseconds); - bool handle_connection(connection_ptr& connection, event current_event); + bool handle_connection(connection_ptr connection, event current_event); private: #ifdef WITH_MBEDTLS // Passed to mbedtls for internal use only. - static int ssl_send(void* data, const uint8_t* buffer, size_t length); - static int ssl_receive(void* data, uint8_t* buffer, size_t length); + static int32_t ssl_send(void* data, const uint8_t* buffer, size_t length); + static int32_t ssl_receive(void* data, uint8_t* buffer, size_t length); #endif void run_once(size_t timeout_milliseconds); void select(size_t timeout_milliseconds, connection_list& sockets); - bool transfer_file_data(connection_ptr& connection); - bool send_http_file(connection_ptr& connection, const std::string& path, - bool keep_alive); - bool handle_websocket(connection_ptr& connection); - bool send_response(connection_ptr& connection, const http_request& request); - bool send_generated_reply(connection_ptr& connection, - protocol_status status); - bool upgrade_connection(connection_ptr& connection, - const http_request& request); - bool validate_origin(const std::string origin); - bool initialize_ssl(connection_ptr& connection, bool listener); - void remove_connections(); - - bool ssl_; - bool listening_; + bool transfer_file_data(connection_ptr connection); + bool send_http_file(connection_ptr connection, const std::string& path, bool keep_alive); + bool handle_websocket(connection_ptr connection); + bool send_response(connection_ptr connection, const http_request& request); + bool send_generated_reply(connection_ptr connection, protocol_status status); + bool upgrade_connection(connection_ptr connection, const http_request& request); + bool validate_origin(const std::string& origin); + bool initialize_ssl(connection_ptr connection, bool listener); + + // These are thread safe. + const bool ssl_; + std::atomic running_; + std::atomic listening_; + + // Initialize is not thread safe. + bool initialized_; + + // Bind is not thread safe. + uint16_t port_; + void* user_data_; - bool running_; std::string key_; std::string certificate_; std::string ca_certificate_; event_handler handler_; - boost::filesystem::path document_root_; + path document_root_; connection_list connections_; - size_t maximum_incoming_frame_length_; - size_t high_water_mark_; - int32_t backlog_; connection_ptr listener_; - struct sockaddr_in listener_address_; + sockaddr_in listener_address_; + + // This is protected by mutex. task_list tasks_; - std::mutex task_lock_; - uint16_t port_; + shared_mutex task_mutex_; }; } // namespace http diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index d4b51437..179e6a20 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -102,8 +102,7 @@ std::string websocket_key_response(const std::string& websocket_key) "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; #ifdef WITH_MBEDTLS - // The required buffer size is for a base64 encoded sha1 hash (20 - // bytes in length). + // The buffer is a base64 encoded sha1 hash (20 bytes in length). static constexpr size_t key_buffer_length = 64; std::array buffer{}; @@ -118,9 +117,8 @@ std::string websocket_key_response(const std::string& websocket_key) return { reinterpret_cast(buffer.data()), processed_length }; #else const auto input = websocket_key + rfc6455_guid; - const data_chunk input_data(input.begin(), input.end()); - const data_slice slice(bc::sha1_hash(input_data)); - return encode_base64(slice); + const data_chunk input_data{ input.begin(), input.end() }; + return encode_base64(bc::sha1_hash(input_data)); #endif } diff --git a/src/web/json_string.cpp b/src/web/json_string.cpp index 1d219894..ed9dbb56 100644 --- a/src/web/json_string.cpp +++ b/src/web/json_string.cpp @@ -18,23 +18,19 @@ */ #include -// Explicitly use std::placeholders here for usage internally to the -// boost parsing helpers included from json_parser.hpp. -// See: https://svn.boost.org/trac10/ticket/12621 -#include -using namespace std::placeholders; - +#include +#include +#include #include #include - +#include #include namespace libbitcoin { namespace server { namespace web { -using namespace boost::property_tree; -static constexpr auto json_encoding = true; +using namespace boost::property_tree; // Object to JSON converters. //----------------------------------------------------------------------------- @@ -60,25 +56,15 @@ std::string to_json(uint64_t height, uint32_t id) tree.put("result", height); tree.put("id", id); return to_json(tree); - // NOTE: The bc::property_tree call works fine, but the format is + + // TODO: The bc::property_tree call works fine, but the format is // different than expected for json_rpc so eventually we need to // separate out property_tree and json_rpc::property_tree, or // something along the lines to make this a clear distinction. - /* return to_json(bc::property_tree(height, id)); */ -} - -std::string to_json(const int code, const std::string message, uint32_t id) -{ - ptree tree; - ptree error_tree; - error_tree.put("code", code); - error_tree.put("message", message); - tree.add_child("error", error_tree); - tree.put("id", id); - return to_json(tree); + //// return to_json(property_tree(height, id)); } -std::string to_json(const std::error_code& code, uint32_t id) +std::string to_json(const code& code, uint32_t id) { ptree tree; ptree error_tree; @@ -87,31 +73,27 @@ std::string to_json(const std::error_code& code, uint32_t id) tree.add_child("error", error_tree); tree.put("id", id); return to_json(tree); - /* return to_json(bc::property_tree(code, id)); */ + //// return to_json(property_tree(code, id)); } -std::string to_json(const bc::chain::header& header, uint32_t id) +std::string to_json(const chain::header& header, uint32_t id) { - return to_json(bc::property_tree(bc::config::header(header)), id); + return to_json(property_tree(config::header(header)), id); } -std::string to_json(const bc::chain::block& block, uint32_t id) +std::string to_json(const chain::block& block, uint32_t id) { - auto tree = bc::property_tree(block, json_encoding); - return to_json(tree, id); + return to_json(property_tree(block, true), id); } -std::string to_json(const bc::chain::block& block, uint32_t /* height */, - uint32_t id) +std::string to_json(const chain::block& block, uint32_t, uint32_t id) { - return to_json(bc::property_tree(bc::config::header(block.header())), id); + return to_json(property_tree(config::header(block.header())), id); } -std::string to_json(const bc::chain::transaction& transaction, - uint32_t id) +std::string to_json(const chain::transaction& transaction, uint32_t id) { - return to_json(bc::property_tree(bc::config::transaction( - transaction), json_encoding), id); + return to_json(property_tree(config::transaction(transaction), true), id); } } // namespace web diff --git a/src/web/socket.cpp b/src/web/socket.cpp index f56d57b3..178d7906 100644 --- a/src/web/socket.cpp +++ b/src/web/socket.cpp @@ -69,7 +69,7 @@ class task_sender bool run() { - if (connection_ == nullptr || connection_->closed()) + if (!connection_ || connection_->closed()) return false; if (connection_->json_rpc()) @@ -81,6 +81,7 @@ class task_sender LOG_VERBOSE(LOG_SERVER_HTTP) << "Writing JSON-RPC response: " << header; + // BUGBUG: unguarded narrowing cast. return connection_->write(header) == static_cast(header.size()); } @@ -88,7 +89,7 @@ class task_sender return connection_->write(data_) == static_cast(data_.size()); } - connection_ptr& connection() + connection_ptr connection() { return connection_; } @@ -98,16 +99,9 @@ class task_sender const std::string data_; }; -// The protocol message_size_limit is on the order of 1M. The maximum -// websocket frame size is set to a much smaller fraction of this -// because our websocket protocol implementation does not contain -// large incoming messages, and also to help avoid DoS attacks via -// large incoming messages. -static constexpr auto maximum_incoming_websocket_message_size = 4096u; - // static // Callback made internally via socket::poll on the web socket thread. -bool socket::handle_event(connection_ptr& connection, const http::event event, +bool socket::handle_event(connection_ptr connection, const http::event event, const void* data) { switch (event) @@ -184,10 +178,10 @@ bool socket::handle_event(connection_ptr& connection, const http::event event, return false; } - // Use default-value get to avoid exceptions on invalid input. + // Use default value get to avoid exceptions on invalid input. const auto id = input_tree.get("id", 0); const auto method = input_tree.get("method", ""); - std::string parameters{}; + std::string parameters; const auto child = input_tree.get_child("params"); std::vector parameter_list; @@ -216,11 +210,10 @@ bool socket::handle_event(connection_ptr& connection, const http::event event, if (connection->websocket()) { - const auto connection_type = connection->json_rpc() ? - "JSON-RPC" : "Websocket"; + const auto type = connection->json_rpc() ? "JSON-RPC" : "Websocket"; LOG_DEBUG(LOG_SERVER) - << connection_type << " client disconnected [" << connection - << "] (" << instance->connection_count() << ")"; + << type << " client disconnected [" << connection << "] (" + << instance->connection_count() << ")"; } break; @@ -287,12 +280,11 @@ bool socket::start() void socket::handle_websockets() { - const auto& endpoint = websocket_endpoint(); http::bind_options options; manager_ = std::make_shared(secure_, &socket::handle_event, document_root_); - if (manager_ == nullptr || !manager_->initialize()) + if (!manager_ || !manager_->initialize()) { LOG_ERROR(LOG_SERVER) << "Failed to initialize websocket manager"; @@ -302,43 +294,34 @@ void socket::handle_websockets() if (secure_) { - options.ssl_ca_certificate = - (exists(server_settings_.websockets_ca_certificate) ? - server_settings_.websockets_ca_certificate.generic_string() : "*"); - options.ssl_key = - server_settings_.websockets_server_private_key.generic_string(); - options.ssl_certificate = - server_settings_.websockets_server_certificate.generic_string(); + // Specified and not found CA cert be a failure condition. + // TODO: defer string conversion to ssl internals, keep paths here. + options.ssl_key = server_settings_.websockets_server_private_key.generic_string(); + options.ssl_certificate = server_settings_.websockets_server_certificate.generic_string(); + options.ssl_ca_certificate = server_settings_.websockets_ca_certificate.generic_string(); } options.user_data = static_cast(this); - if (!manager_->bind(endpoint.to_local().host(), endpoint.port(), options)) + if (!manager_->bind(websocket_endpoint(), options)) { - LOG_ERROR(LOG_SERVER) - << "Failed to bind listener websocket to port " << endpoint.port(); thread_status_.set_value(false); return; } - LOG_INFO(LOG_SERVER) - << "Bound " << security_ << " " << domain_ << " websocket to port " - << endpoint.port(); - - manager_->set_maximum_incoming_frame_length( - maximum_incoming_websocket_message_size); thread_status_.set_value(true); - manager_->start(); } const std::shared_ptr socket::service() const { + // TODO: implement. + BITCOIN_ASSERT_MSG(false, "not implemented"); return nullptr; } bool socket::start_websocket_handler() { - std::future status = thread_status_.get_future(); + auto status = thread_status_.get_future(); thread_ = std::make_shared(&socket::handle_websockets, this); status.wait(); return status.get(); @@ -346,7 +329,7 @@ bool socket::start_websocket_handler() bool socket::stop_websocket_handler() { - BITCOIN_ASSERT(manager_ != nullptr); + BITCOIN_ASSERT(manager_); manager_->stop(); thread_->join(); return true; @@ -359,7 +342,7 @@ size_t socket::connection_count() const } // Called by the websocket handling thread via handle_event. -void socket::add_connection(connection_ptr& connection) +void socket::add_connection(connection_ptr connection) { // BUGBUG: use of connections_ in this method is not thread safe. BITCOIN_ASSERT(connections_.find(connection) == connections_.end()); @@ -372,7 +355,7 @@ void socket::add_connection(connection_ptr& connection) // Correlation lock usage is required because it protects the shared // correlation map of ids, which can also used by the zmq service // thread on response handling (i.e. query_socket::handle_query). -void socket::remove_connection(connection_ptr& connection) +void socket::remove_connection(connection_ptr connection) { // BUGBUG: use of connections_ in this method is not thread safe. if (connections_.empty()) @@ -420,20 +403,14 @@ void socket::remove_connection(connection_ptr& connection) // // Errors write directly on the connection since this is called from // the event_handler, which is called on the websocket thread. -void socket::notify_query_work(connection_ptr& connection, - const std::string& method, const uint32_t id, - const std::string& parameters) +void socket::notify_query_work(connection_ptr connection, + const std::string& method, uint32_t id, const std::string& parameters) { - typedef std::pair error; - static const error invalid_request{ -32600, "Invalid Request." }; - static const error not_found{ -32601, "Method not found." }; - static const error internal_error{ -32603, "Internal error." }; - - auto send_error_reply = [&connection, id](const protocol_status status, - int code, const std::string message) + const auto send_error_reply = [=](protocol_status status, + const bc::code& ec) { http_reply reply; - const auto error = web::to_json(code, message, id); + const auto error = web::to_json(ec, id); const auto response = reply.generate(status, {}, error.size(), false); LOG_VERBOSE(LOG_SERVER) << error + response; connection->write(error + response); @@ -442,22 +419,15 @@ void socket::notify_query_work(connection_ptr& connection, if (handlers_.empty()) { send_error_reply(protocol_status::service_unavailable, - invalid_request.first, invalid_request.second); - // This error will most commonly present when a client - // connects to one of the other websocket services (other than - // query_socket), so no handlers are available. - LOG_ERROR(LOG_SERVER) - << "No method handlers available; ensure you're connected to the " - << "query websocket service"; + bc::error::http_invalid_request); return; } const auto handler = handlers_.find(method); if (handler == handlers_.end()) { - send_error_reply(protocol_status::not_found, not_found.first, - not_found.second); - LOG_VERBOSE(LOG_SERVER) << "Method " << method << " not found"; + send_error_reply(protocol_status::not_found, + bc::error::http_method_not_found); return; } @@ -475,7 +445,7 @@ void socket::notify_query_work(connection_ptr& connection, if (query_work_map.find(id) != query_work_map.end()) { send_error_reply(protocol_status::internal_server_error, - internal_error.first, internal_error.second); + bc::error::http_internal_error); return; } @@ -507,21 +477,19 @@ void socket::notify_query_work(connection_ptr& connection, if (ec) { - LOG_WARNING(LOG_SERVER) - << "Query send failure: " << ec.message(); send_error_reply(protocol_status::internal_server_error, - internal_error.first, internal_error.second); + bc::error::http_internal_error); return; } } -// Sends json strings to websockets or connections waiting on json_rpc -// replies (only). +// Sends json to websockets/connections waiting on json_rpc replies (only). void socket::send(connection_ptr connection, const std::string& json) { - if (connection == nullptr || connection->closed() || + if (!connection || connection->closed() || (!connection->websocket() && !connection->json_rpc())) return; + // By using a task_sender via the manager's execute method, we guarantee // that the write is performed on the manager's websocket thread (at the // expense of copied json send and response payloads). From 1c4f6a84476e54680eb77592eda4880d00631793 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 02:30:51 -0700 Subject: [PATCH 05/25] Use path parser, use _MSC_VER for vc++ identification, style. --- src/web/http/connection.cpp | 4 +- src/web/http/http.hpp | 4 +- src/web/http/manager.cpp | 20 ++--- src/web/http/manager.hpp | 2 +- src/web/http/utilities.cpp | 149 ++++++++++++++---------------------- src/web/http/utilities.hpp | 13 ++-- 6 files changed, 83 insertions(+), 109 deletions(-) diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 7a7ff232..4e1f35d2 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -71,7 +71,7 @@ void connection::set_state(connection_state state) void connection::set_socket_non_blocking() { -#ifdef WIN32 +#ifdef _MSC_VER ULONG non_blocking = 1; ioctlsocket(socket_, FIONBIO, &non_blocking); #else @@ -148,7 +148,7 @@ int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) if (length > max_int32) return -1; -#ifdef WIN32 +#ifdef _MSC_VER return send(socket_, reinterpret_cast(data), static_cast(length), 0); #else diff --git a/src/web/http/http.hpp b/src/web/http/http.hpp index edeee6ac..2951d710 100644 --- a/src/web/http/http.hpp +++ b/src/web/http/http.hpp @@ -34,7 +34,7 @@ #include #include -#ifdef WIN32 +#ifdef _MSC_VER #include #include #include @@ -74,7 +74,7 @@ namespace libbitcoin { namespace server { namespace http { -#ifdef WIN32 +#ifdef _MSC_VER typedef SOCKET sock_t; typedef uint32_t in_addr_t; #define CLOSE_SOCKET closesocket diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 10c96534..a2fe59d8 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -64,7 +64,7 @@ manager::manager(bool ssl, event_handler handler, path document_root) manager::~manager() { -#ifdef WIN32 +#ifdef _MSC_VER if (initialized_) ::WSACleanup(); #endif @@ -73,7 +73,7 @@ manager::~manager() // Initialize is not thread safe. bool manager::initialize() { -#ifdef WIN32 +#ifdef _MSC_VER WSADATA wsa_data; static constexpr auto winsock_version = MAKEWORD(2, 2); if (::WSAStartup(winsock_version, &wsa_data) != 0) @@ -425,7 +425,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) // Check if the descriptor is too high to monitor in our fd_set. if (descriptor > maximum_connections) { -#ifdef WIN32 +#ifdef _MSC_VER CLOSE_SOCKET(descriptor); LOG_ERROR(LOG_SERVER_HTTP) << "Error: cannot monitor socket " << descriptor @@ -433,8 +433,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) pending_removal.push_back(connection); continue; #else - // Attempt to resolve this by looking for a lower - // available descriptor. + // Attempt to resolve this by seeking a lower available descriptor. auto new_descriptor = dup(descriptor); if (new_descriptor < descriptor && new_descriptor < maximum_connections) @@ -774,14 +773,17 @@ bool manager::transfer_file_data(connection_ptr connection) return success; } -bool manager::send_http_file(connection_ptr connection, - const std::string& path, bool keep_alive) +bool manager::send_http_file(connection_ptr connection, const path& path, + bool keep_alive) { auto& file_transfer = connection->file_transfer(); if (!file_transfer.in_progress) { - file_transfer.descriptor = fopen(path.c_str(), "r"); + // BUGBUG: UTF8 string passed to Windows ANSI parameter. + const auto file = path.generic_string().c_str(); + + file_transfer.descriptor = fopen(file, "r"); if (file_transfer.descriptor == nullptr) return false; @@ -1033,7 +1035,7 @@ bool manager::send_response(connection_ptr connection, ((request.protocol.find("HTTP/1.0") == std::string::npos) || (request.header(std::string("Connection")) == "keep-alive")); - return send_http_file(connection, path.generic_string(), keep_alive); + return send_http_file(connection, path, keep_alive); } bool manager::send_generated_reply(connection_ptr connection, diff --git a/src/web/http/manager.hpp b/src/web/http/manager.hpp index a53f8c8e..53610735 100644 --- a/src/web/http/manager.hpp +++ b/src/web/http/manager.hpp @@ -78,7 +78,7 @@ class manager void run_once(size_t timeout_milliseconds); void select(size_t timeout_milliseconds, connection_list& sockets); bool transfer_file_data(connection_ptr connection); - bool send_http_file(connection_ptr connection, const std::string& path, bool keep_alive); + bool send_http_file(connection_ptr connection, const path& path, bool keep_alive); bool handle_websocket(connection_ptr connection); bool send_response(connection_ptr connection, const http_request& request); bool send_generated_reply(connection_ptr connection, protocol_status status); diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index 179e6a20..26d8c54a 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -30,10 +32,9 @@ namespace server { namespace http { // TODO: std::strerror is not required to be thread safe. -// TODO: Win32 FormatMessage requires unicode to utf-8 conversion. std::string error_string() { -#ifdef WIN32 +#ifdef _MSC_VER WCHAR wide[MAX_PATH]; const auto error = ::GetLastError(); const auto flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; @@ -53,7 +54,7 @@ std::string error_string() } #ifdef WITH_MBEDTLS -std::string mbedtls_error_string(int error) +std::string mbedtls_error_string(int32_t error) { static constexpr size_t error_buffer_length = 256; std::array data{}; @@ -63,7 +64,7 @@ std::string mbedtls_error_string(int error) sha1_hash sha1(const std::string& input) { - sha1_hash out{}; + sha1_hash out; mbedtls_sha1_context context; mbedtls_sha1_init(&context); @@ -104,7 +105,7 @@ std::string websocket_key_response(const std::string& websocket_key) #ifdef WITH_MBEDTLS // The buffer is a base64 encoded sha1 hash (20 bytes in length). static constexpr size_t key_buffer_length = 64; - std::array buffer{}; + std::array buffer; size_t processed_length = 0; const auto input = websocket_key + rfc6455_guid; @@ -286,96 +287,64 @@ bool parse_http(http_request& out, const std::string& request) return true; } -////unsigned long resolve_hostname(const std::string& hostname) -////{ -//// unsigned long address = 0; -//// -////#ifdef WIN32 -//// #define GET_ADDRESS(host, address) *address = ::inet_addr(host) -//// #define VALIDATE_ADDRESS(address) (address != INADDR_NONE) -////#else -//// #define GET_ADDRESS(host, address) inet_pton(AF_INET, host, address) -//// #define VALIDATE_ADDRESS(address) (address > 0) -////#endif -//// -//// // Resolve dotted ip address. -//// if (!VALIDATE_ADDRESS(GET_ADDRESS(hostname.c_str(), &address))) -//// { -//// // Resolve host name. -//// const auto* resolved = gethostbyname(hostname.c_str()); -//// -//// if (resolved != nullptr) -//// address = *(reinterpret_cast( -//// resolved->h_addr_list[0])); -//// } -//// -////#undef get_address -////#undef validate_address -////#undef host_info -//// -//// return address; -////} - -std::string mime_type(const std::string& filename) +std::string mime_type(const boost::filesystem::path& path) { - std::unordered_map mime_type_map = + static const std::unordered_map map { - { "html", "text/html" }, - { "htm", "text/html" }, - { "shtm", "text/html" }, - { "shtml", "text/html" }, - { "css", "text/css" }, - { "js", "application/x-javascript" }, - { "ico", "image/x-icon" }, - { "gif", "image/gif" }, - { "jpg", "image/jpeg" }, - { "jpeg", "image/jpeg" }, - { "png", "image/png" }, - { "svg", "image/svg+xml" }, - { "md", "text/plain" }, - { "txt", "text/plain" }, - { "torrent", "application/x-bittorrent" }, - { "wav", "audio/x-wav" }, - { "mp3", "audio/x-mp3" }, - { "mid", "audio/mid" }, - { "m3u", "audio/x-mpegurl" }, - { "ogg", "application/ogg" }, - { "ram", "audio/x-pn-realaudio" }, - { "xml", "text/xml" }, - { "ttf", "application/x-font-ttf" }, - { "json", "application/json" }, - { "xslt", "application/xml" }, - { "xsl", "application/xml" }, - { "ra", "audio/x-pn-realaudio" }, - { "doc", "application/msword" }, - { "exe", "application/octet-stream" }, - { "zip", "application/x-zip-compressed" }, - { "xls", "application/excel" }, - { "tgz", "application/x-tar-gz" }, - { "tar", "application/x-tar" }, - { "gz", "application/x-gunzip" }, - { "arj", "application/x-arj-compressed" }, - { "rar", "application/x-rar-compressed" }, - { "rtf", "application/rtf" }, - { "pdf", "application/pdf" }, - { "swf", "application/x-shockwave-flash" }, - { "mpg", "video/mpeg" }, - { "webm", "video/webm" }, - { "mpeg", "video/mpeg" }, - { "mov", "video/quicktime" }, - { "mp4", "video/mp4" }, - { "m4v", "video/x-m4v" }, - { "asf", "video/x-ms-asf" }, - { "avi", "video/x-msvideo" }, - { "bmp", "image/bmp" } + { ".html", "text/html" }, + { ".htm", "text/html" }, + { ".shtm", "text/html" }, + { ".shtml", "text/html" }, + { ".css", "text/css" }, + { ".js", "application/x-javascript" }, + { ".ico", "image/x-icon" }, + { ".gif", "image/gif" }, + { ".jpg", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, + { ".png", "image/png" }, + { ".svg", "image/svg+xml" }, + { ".md", "text/plain" }, + { ".txt", "text/plain" }, + { ".torrent", "application/x-bittorrent" }, + { ".wav", "audio/x-wav" }, + { ".mp3", "audio/x-mp3" }, + { ".mid", "audio/mid" }, + { ".m3u", "audio/x-mpegurl" }, + { ".ogg", "application/ogg" }, + { ".ram", "audio/x-pn-realaudio" }, + { ".xml", "text/xml" }, + { ".ttf", "application/x-font-ttf" }, + { ".json", "application/json" }, + { ".xslt", "application/xml" }, + { ".xsl", "application/xml" }, + { ".ra", "audio/x-pn-realaudio" }, + { ".doc", "application/msword" }, + { ".exe", "application/octet-stream" }, + { ".zip", "application/x-zip-compressed" }, + { ".xls", "application/excel" }, + { ".tgz", "application/x-tar-gz" }, + { ".tar", "application/x-tar" }, + { ".gz", "application/x-gunzip" }, + { ".arj", "application/x-arj-compressed" }, + { ".rar", "application/x-rar-compressed" }, + { ".rtf", "application/rtf" }, + { ".pdf", "application/pdf" }, + { ".swf", "application/x-shockwave-flash" }, + { ".mpg", "video/mpeg" }, + { ".webm", "video/webm" }, + { ".mpeg", "video/mpeg" }, + { ".mov", "video/quicktime" }, + { ".mp4", "video/mp4" }, + { ".m4v", "video/x-m4v" }, + { ".asf", "video/x-ms-asf" }, + { ".avi", "video/x-msvideo" }, + { ".bmp", "image/bmp" } }; - const auto dot_position = filename.rfind("."); - if (dot_position != std::string::npos) + if (path.has_extension()) { - const auto extension = filename.substr(dot_position + 1); - auto it = mime_type_map.find(extension); - if (it != mime_type_map.end()) + const auto it = map.find(path.extension().generic_string()); + if (it != map.end()) return it->second; } diff --git a/src/web/http/utilities.hpp b/src/web/http/utilities.hpp index 2e1a14e7..6be749f0 100644 --- a/src/web/http/utilities.hpp +++ b/src/web/http/utilities.hpp @@ -23,13 +23,17 @@ namespace libbitcoin { namespace server { namespace http { -#ifdef WIN32 +#include +#include +#include "http.hpp" + +#ifdef _MSC_VER #define last_error() GetLastError() #else #define last_error() errno #endif -#ifdef WIN32 +#ifdef _MSC_VER #define would_block(value) (value == WSAEWOULDBLOCK) #else #define would_block(value) (value == EAGAIN || value == EWOULDBLOCK) @@ -39,7 +43,7 @@ namespace http { (value == MBEDTLS_ERR_SSL_WANT_READ || value == MBEDTLS_ERR_SSL_WANT_WRITE) #ifdef WITH_MBEDTLS - std::string mbedtls_error_string(int error); + std::string mbedtls_error_string(int32_t error); sha1_hash sha1(const std::string& input); #endif @@ -48,8 +52,7 @@ std::string to_string(websocket_op code); std::string websocket_key_response(const std::string& websocket_key); bool is_json_request(const std::string& header_value); bool parse_http(http_request& out, const std::string& request); -////unsigned long resolve_hostname(const std::string& hostname); -std::string mime_type(const std::string& filename); +std::string mime_type(const boost::filesystem::path& path); } // namespace http } // namespace server From fd9991ccb304a1f8c91f30a9fe9fd60be173f35f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 02:53:38 -0700 Subject: [PATCH 06/25] Use actual header size, sort headers, style, whitespace. --- src/web/http/connection.cpp | 26 +++++++++++++------------- src/web/http/http.hpp | 19 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 4e1f35d2..fd99da91 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -127,7 +127,6 @@ data_chunk& connection::write_buffer() return write_buffer_; } - int32_t connection::do_write(const data_chunk& buffer, bool frame) { return do_write(buffer.data(), buffer.size(), frame); @@ -170,8 +169,8 @@ int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) if (frame) { - auto frame_data = websocket_frame::to_data(length, websocket_op::text); - const auto written = writer(frame_data.data(), frame_data.size()); + auto header = websocket_frame::to_header(length, websocket_op::text); + const auto written = writer(header.data(), header.size()); if (written < 0) return written; @@ -221,16 +220,16 @@ int32_t connection::write(const std::string& buffer) // This is a buffered write call if under the high water mark. int32_t connection::write(const uint8_t* data, size_t length) { + // TODO: set errno. if (length > max_int32) return -1; - const auto frame_size = websocket_ ? websocket_frame::maximal_size : 0; - const auto buffered_length = write_buffer_.size() + length + frame_size; + const auto frame = websocket_frame::to_header(length, websocket_op::text); + const auto buffered_length = write_buffer_.size() + frame.size() + length; - // If we're currently at the hwm, issue blocking writes until - // we've cleared the buffered data and then write this current - // request. This is an expensive operation, but should be - // mostly avoidable with proper hwm tuning of your application. + // If currently at the hwm, issue blocking writes until buffer is cleared + // and then write this current request. This is expensive but should be + // mostly avoidable with proper hwm tuning. if (buffered_length >= high_water_mark) { // Drain the buffered data. @@ -245,6 +244,7 @@ int32_t connection::write(const uint8_t* data, size_t length) if (written < 0) return written; + // Costly buffer rewrite. if (!write_buffer_.empty()) write_buffer_.erase(write_buffer_.begin(), write_buffer_.begin() + written); @@ -256,12 +256,12 @@ int32_t connection::write(const uint8_t* data, size_t length) if (websocket_) { - auto frame_data = websocket_frame::to_data(length, websocket_op::text); - write_buffer_.insert(write_buffer_.end(), frame_data.begin(), - frame_data.end()); + // Buffer header for future writes (called from poll). + auto frame = websocket_frame::to_header(length, websocket_op::text); + write_buffer_.insert(write_buffer_.end(), frame.begin(), frame.end()); } - // Buffer this data for future writes (called from poll). + // Buffer data for future writes (called from poll). write_buffer_.insert(write_buffer_.end(), data, data + length); return static_cast(length); } diff --git a/src/web/http/http.hpp b/src/web/http/http.hpp index 2951d710..3a718ec7 100644 --- a/src/web/http/http.hpp +++ b/src/web/http/http.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -32,14 +33,13 @@ #include #include #include -#include #ifdef _MSC_VER + #include + #include + #include #include #include - #include - #include - #include #else #include #include @@ -50,10 +50,10 @@ #include #endif -#include #include #include #include +#include #include #include #include @@ -88,7 +88,7 @@ static const size_t default_buffer_length = 1 << 10; // 1KB static const size_t transfer_buffer_length = 1 << 18; // 256KB #ifdef WITH_MBEDTLS -static const int default_ciphers[] = +static const int32_t default_ciphers[] = { MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256, @@ -185,14 +185,12 @@ struct websocket_message class websocket_frame { public: - static const size_t maximal_size = 11; - websocket_frame(const uint8_t* data, size_t size) { from_data(data, size); } - static data_chunk to_data(size_t length, websocket_op code) + static data_chunk to_header(size_t length, websocket_op code) { if (length < 0x7e) { @@ -398,8 +396,9 @@ class http_request boost::property_tree::ptree json_tree; }; -struct http_reply +class http_reply { +public: static std::string to_string(protocol_status status) { typedef std::unordered_map status_map; From a94af8d186c077364ab79441e75ce32490da8344 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 03:48:24 -0700 Subject: [PATCH 07/25] Use explicit locks vs unconditional scoping. --- src/workers/notification_worker.cpp | 50 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/workers/notification_worker.cpp b/src/workers/notification_worker.cpp index b4276e51..0dc657a7 100644 --- a/src/workers/notification_worker.cpp +++ b/src/workers/notification_worker.cpp @@ -431,23 +431,22 @@ void notification_worker::purge() // Accumulate removals, send expiration notifications outside locks. std::vector expires; - if (true) - { - /////////////////////////////////////////////////////////////////////// - // Critical Section - unique_lock lock(address_mutex_); + /////////////////////////////////////////////////////////////////////////// + // Critical Section + address_mutex_.lock(); - auto& right = address_subscriptions_.right; + auto& address = address_subscriptions_.right; - for (auto it = right.begin(); it != right.end() && - it->first.updated() < cutoff; it = right.erase(it)) - { - it->first.increment(); - expires.push_back(it->first); - } - /////////////////////////////////////////////////////////////////////// + for (auto it = address.begin(); it != address.end() && + it->first.updated() < cutoff; it = address.erase(it)) + { + it->first.increment(); + expires.push_back(it->first); } + address_mutex_.unlock(); + /////////////////////////////////////////////////////////////////////////// + // Send failure is logged in send. if (dealer) for (auto& expire: expires) @@ -456,23 +455,22 @@ void notification_worker::purge() expires.clear(); - if (true) - { - /////////////////////////////////////////////////////////////////////// - // Critical Section - unique_lock lock(stealth_mutex_); + /////////////////////////////////////////////////////////////////////////// + // Critical Section + stealth_mutex_.lock(); - auto& right = stealth_subscriptions_.right; + auto& stealth = stealth_subscriptions_.right; - for (auto it = right.begin(); it != right.end() && - it->first.updated() < cutoff; it = right.erase(it)) - { - it->first.increment(); - expires.push_back(it->first); - } - /////////////////////////////////////////////////////////////////////// + for (auto it = stealth.begin(); it != stealth.end() && + it->first.updated() < cutoff; it = stealth.erase(it)) + { + it->first.increment(); + expires.push_back(it->first); } + stealth_mutex_.unlock(); + /////////////////////////////////////////////////////////////////////////// + // Send failure is logged in send. if (dealer) for (auto& expire: expires) From af86acc533a05e61c25368c8f019b2d9170c4e90 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 03:50:43 -0700 Subject: [PATCH 08/25] Simplify correlations unlock, comments. --- src/web/query_socket.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 3a3870d5..46841f0f 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -250,7 +250,7 @@ bool query_socket::handle_query(zmq::socket& dealer) return true; } - /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// // Critical Section correlation_lock_.lock_upgrade(); @@ -259,9 +259,9 @@ bool query_socket::handle_query(zmq::socket& dealer) if (correlation == correlations_.end()) { correlation_lock_.unlock_upgrade(); - - // This will happen anytime the client disconnects before this - // handler is called. We can safely discard the result here. + //--------------------------------------------------------------------- + // This will happen anytime the client disconnects before this handler + // is called. We can safely discard the result here. LOG_DEBUG(LOG_SERVER) << "Unmatched websocket query work item sequence: " << sequence; return true; @@ -269,14 +269,11 @@ bool query_socket::handle_query(zmq::socket& dealer) auto connection = correlation->second.first; const auto id = correlation->second.second; - - // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ correlation_lock_.unlock_upgrade_and_lock(); correlations_.erase(correlation); - // TODO: Can this 2 step unlock be done atomically? - correlation_lock_.unlock_and_lock_upgrade(); - correlation_lock_.unlock_upgrade(); - /////////////////////////////////////////////////////////////////////// + correlation_lock_.unlock(); + /////////////////////////////////////////////////////////////////////////// // Use connection to locate connection state. auto it = connections_.find(connection); From e5a7e398dfc70297952bc2fe716ecea0a1282d2f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 03:51:55 -0700 Subject: [PATCH 09/25] Drop high water messages, simplify writes, fix cast, comments. --- src/web/http/connection.cpp | 75 +++++++++++-------------------------- src/web/http/connection.hpp | 6 +-- src/web/http/manager.cpp | 20 +++++----- 3 files changed, 35 insertions(+), 66 deletions(-) diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index fd99da91..4a09423d 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -127,24 +127,23 @@ data_chunk& connection::write_buffer() return write_buffer_; } -int32_t connection::do_write(const data_chunk& buffer, bool frame) +int32_t connection::unbuffered_write(const data_chunk& buffer) { - return do_write(buffer.data(), buffer.size(), frame); + return unbuffered_write(buffer.data(), buffer.size()); } -int32_t connection::do_write(const std::string& buffer, bool frame) +int32_t connection::unbuffered_write(const std::string& buffer) { const auto data = reinterpret_cast(buffer.data()); - return do_write(data, buffer.size(), frame); + return unbuffered_write(data, buffer.size()); } -// This is effectively a blocking write call that does not buffer internally. -int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) +int32_t connection::unbuffered_write(const uint8_t* data, size_t length) { const auto plaintext_write = [this](const uint8_t* data, size_t length) { // BUGBUG: must set errno for return error handling. - if (length > max_int32) + if (length > static_cast(max_int32)) return -1; #ifdef _MSC_VER @@ -167,15 +166,6 @@ int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) auto writer = plaintext_write; #endif - if (frame) - { - auto header = websocket_frame::to_header(length, websocket_op::text); - const auto written = writer(header.data(), header.size()); - - if (written < 0) - return written; - } - auto remaining = length; auto position = data; @@ -189,7 +179,7 @@ int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) if (!would_block(error)) { LOG_WARNING(LOG_SERVER_HTTP) - << "do_write failed. requested " << remaining + << "Unbuffered write failed. requested " << remaining << " and wrote " << written << ": " << error_string(); return written; } @@ -203,6 +193,7 @@ int32_t connection::do_write(const uint8_t* data, size_t length, bool frame) } while (remaining != 0); + // TODO: isn't this always length? return static_cast(position - data); } @@ -217,51 +208,27 @@ int32_t connection::write(const std::string& buffer) return write(data, buffer.size()); } -// This is a buffered write call if under the high water mark. +// If high water would be exceeded new messages are silently dropped. int32_t connection::write(const uint8_t* data, size_t length) { - // TODO: set errno. - if (length > max_int32) + // BUGBUG: must set errno for return error handling. + if (length > static_cast(max_int32)) return -1; - const auto frame = websocket_frame::to_header(length, websocket_op::text); - const auto buffered_length = write_buffer_.size() + frame.size() + length; - - // If currently at the hwm, issue blocking writes until buffer is cleared - // and then write this current request. This is expensive but should be - // mostly avoidable with proper hwm tuning. - if (buffered_length >= high_water_mark) - { - // Drain the buffered data. - while (!write_buffer_.empty()) - { - const auto segment_length = std::min(transfer_buffer_length, - write_buffer_.size()); - - const auto written = do_write(write_buffer_.data(), segment_length, - false); - - if (written < 0) - return written; - - // Costly buffer rewrite. - if (!write_buffer_.empty()) - write_buffer_.erase(write_buffer_.begin(), - write_buffer_.begin() + written); - } - - // Perform this write in a blocking manner. - return do_write(data, length, websocket_); - } + const auto header = websocket_ ? websocket_frame::to_header(length, + websocket_op::text) : data_chunk{}; + const auto buffer_size = write_buffer_.size() + header.size() + length; - if (websocket_) + if (buffer_size > high_water_mark) { - // Buffer header for future writes (called from poll). - auto frame = websocket_frame::to_header(length, websocket_op::text); - write_buffer_.insert(write_buffer_.end(), frame.begin(), frame.end()); + LOG_VERBOSE(LOG_SERVER_HTTP) + << "High water exceeded, " << length << "byte message dropped."; + return static_cast(length); } - // Buffer data for future writes (called from poll). + // TODO: this is very inefficient, use circular buffer. + // Buffer header and data for future writes (called from poll). + write_buffer_.insert(write_buffer_.end(), header.begin(), header.end()); write_buffer_.insert(write_buffer_.end(), data, data + length); return static_cast(length); } diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index 64ae12bd..646725e7 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -84,9 +84,9 @@ class connection int32_t write(const std::string& buffer); int32_t write(const uint8_t* data, size_t length); - int32_t do_write(const data_chunk& buffer, bool frame); - int32_t do_write(const std::string& buffer, bool frame); - int32_t do_write(const uint8_t* data, size_t length, bool frame); + int32_t unbuffered_write(const data_chunk& buffer); + int32_t unbuffered_write(const std::string& buffer); + int32_t unbuffered_write(const uint8_t* data, size_t length); // Other. // ------------------------------------------------------------------------ diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index a2fe59d8..7b707eee 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -521,8 +521,9 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) { const auto segment_length = std::min(write_buffer.size(), transfer_buffer_length); - const auto written = connection->do_write(write_buffer.data(), - segment_length, false); + + const auto written = connection->unbuffered_write( + write_buffer.data(), segment_length); if (written < 0) { @@ -530,6 +531,7 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) continue; } + // TODO: this is very inefficient, use circular buffer. write_buffer.erase(write_buffer.begin(), write_buffer.begin() + written); } @@ -1092,12 +1094,12 @@ bool manager::upgrade_connection(connection_ptr connection, const auto protocol = request.header("sec-websocket-protocol"); const auto response = reply.generate_upgrade(key_response, protocol); - // This write is unbuffered since we're not a websocket yet and - // want to be sure it completes before changing our state to be - // upgraded to a websocket. - if (connection->do_write(reinterpret_cast( - response.c_str()), response.size(), false) != static_cast( - response.size())) + // Unbuffered since not websocket yet and must complete before upgrade. + const auto result = connection->unbuffered_write(response); + + // TODO: why have both result conditions if both are a simple error? + // TODO: there must be a case where uneuqal response is not an error. + if (result < 0 || static_cast(result) != response.size()) { LOG_ERROR(LOG_SERVER_HTTP) << "Failed to upgrade connection due to a write failure"; @@ -1110,7 +1112,7 @@ bool manager::upgrade_connection(connection_ptr connection, LOG_VERBOSE(LOG_SERVER_HTTP) << "Upgraded connection " << connection << " for uri " << request.uri; - // On upgrade, call the user handler so they can track this websocket. + // On upgrade, call the user handler so it can track this websocket. return handler_(connection, event::accepted, nullptr); } From 75e732b095089919dbdce033093785bf39518b58 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 04:56:43 -0700 Subject: [PATCH 10/25] Clarify names, simplify start, style, comments. --- include/bitcoin/server/web/socket.hpp | 4 +-- src/web/http/connection.cpp | 1 - src/web/http/manager.cpp | 17 +++++------ src/web/query_socket.cpp | 26 ++++++++--------- src/web/socket.cpp | 41 +++++++++++++-------------- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/include/bitcoin/server/web/socket.hpp b/include/bitcoin/server/web/socket.hpp index d643cd09..1c88d111 100644 --- a/include/bitcoin/server/web/socket.hpp +++ b/include/bitcoin/server/web/socket.hpp @@ -146,11 +146,11 @@ class BCS_API socket // connected to the query_socket service. This socket operates on // only the below member thread_. std::shared_ptr thread_; - std::promise thread_status_; + std::promise socket_started_; // Used by the query_socket class. uint32_t sequence_; - connection_work_map connections_; + connection_work_map work_; query_correlation_map correlations_; mutable upgrade_mutex correlation_lock_; diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 4a09423d..25005ea9 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -43,7 +43,6 @@ connection::connection(sock_t connection, const sockaddr_in& address) address_(address), last_active_(asio::steady_clock::now()), ssl_context_{}, - websocket_endpoint_{}, websocket_(false), json_rpc_(false), file_transfer_{}, diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 7b707eee..499d244c 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -345,16 +345,13 @@ void manager::run_tasks() } // Portable select based implementation. -// Break up number of connections into N lists of a specified -// maximum size and call select for each of them, given a -// timeout of (timeout_milliseconds / N). -// This is a hack to work around limitations of the select -// system call. Note that you may also need to adjust the -// descriptor limit for this process in order for this to work -// properly. -// With very large connection counts and small specified -// timeout values, this poll method may very well exceed the -// specified timeout. +// Break up number of connections into N lists of a specified maximum size and +// call select for each of them, given a timeout of (timeout_milliseconds / N). +// This is a hack to work around limitations of the select system call. Note +// that you may also need to adjust the descriptor limit for this process in +// order for this to work properly. +// With very large connection counts and small specified timeout values, this +// poll method may very well exceed the specified timeout. void manager::poll(size_t timeout_milliseconds) { if (connections_.size() > maximum_connections) diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 46841f0f..dd999eb5 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -276,8 +276,8 @@ bool query_socket::handle_query(zmq::socket& dealer) /////////////////////////////////////////////////////////////////////////// // Use connection to locate connection state. - auto it = connections_.find(connection); - if (it == connections_.end()) + auto it = work_.find(connection); + if (it == work_.end()) { LOG_ERROR(LOG_SERVER) << "Query work completed for unknown connection"; @@ -360,8 +360,8 @@ void query_socket::handle_websockets() protocol_settings_); // Hold a reference to this service_ socket member by this thread - // method so that we can properly shutdown even if the - // query_socket object is destroyed. + // method so that we can properly shutdown even if the query_socket + // object is destroyed. const auto service_ref = service_; const auto ec = service_ref->connect(query_endpoint()); @@ -370,16 +370,15 @@ void query_socket::handle_websockets() LOG_ERROR(LOG_SERVER) << "Failed to connect " << security_ << " query sender socket: " << ec.message(); - thread_status_.set_value(false); + socket_started_.set_value(false); return; } - // socket::handle_websockets does web socket initialization and - // runs the web event loop. Inside that loop, socket::poll - // eventually calls into the static handle_event callback, which - // calls socket::notify_query_work, which uses this 'service_' zmq - // socket for sending incoming requests and reading the json web - // responses. + // socket::handle_websockets does web socket initialization and runs the + // web event loop. Inside that loop, socket::poll eventually calls into + // the static handle_event callback, which calls socket::notify_query_work, + // which uses this 'service_' zmq socket for sending incoming requests and + // reading the json web responses. socket::handle_websockets(); if (!service_ref->stop()) @@ -391,11 +390,10 @@ void query_socket::handle_websockets() bool query_socket::start_websocket_handler() { - std::future status = thread_status_.get_future(); + auto& started = socket_started_.get_future(); thread_ = std::make_shared(&query_socket::handle_websockets, this); - status.wait(); - return status.get(); + return started.get(); } } // namespace server diff --git a/src/web/socket.cpp b/src/web/socket.cpp index 178d7906..8bb9db28 100644 --- a/src/web/socket.cpp +++ b/src/web/socket.cpp @@ -288,13 +288,13 @@ void socket::handle_websockets() { LOG_ERROR(LOG_SERVER) << "Failed to initialize websocket manager"; - thread_status_.set_value(false); + socket_started_.set_value(false); return; } if (secure_) { - // Specified and not found CA cert be a failure condition. + // Specified and not found CA cert should be a failure condition. // TODO: defer string conversion to ssl internals, keep paths here. options.ssl_key = server_settings_.websockets_server_private_key.generic_string(); options.ssl_certificate = server_settings_.websockets_server_certificate.generic_string(); @@ -304,11 +304,11 @@ void socket::handle_websockets() options.user_data = static_cast(this); if (!manager_->bind(websocket_endpoint(), options)) { - thread_status_.set_value(false); + socket_started_.set_value(false); return; } - thread_status_.set_value(true); + socket_started_.set_value(true); manager_->start(); } @@ -321,9 +321,8 @@ const std::shared_ptr socket::service() const bool socket::start_websocket_handler() { - auto status = thread_status_.get_future(); + auto status = socket_started_.get_future(); thread_ = std::make_shared(&socket::handle_websockets, this); - status.wait(); return status.get(); } @@ -337,18 +336,18 @@ bool socket::stop_websocket_handler() size_t socket::connection_count() const { - // BUGBUG: use of connections_ in this method is not thread safe. - return connections_.size(); + // BUGBUG: use of work_ in this method is not thread safe. + return work_.size(); } // Called by the websocket handling thread via handle_event. void socket::add_connection(connection_ptr connection) { - // BUGBUG: use of connections_ in this method is not thread safe. - BITCOIN_ASSERT(connections_.find(connection) == connections_.end()); + // BUGBUG: use of work_ in this method is not thread safe. + BITCOIN_ASSERT(work_.find(connection) == work_.end()); // Initialize a new query_work_map for this connection. - connections_[connection].clear(); + work_[connection].clear(); } // Called by the websocket handling thread via handle_event. @@ -357,12 +356,12 @@ void socket::add_connection(connection_ptr connection) // thread on response handling (i.e. query_socket::handle_query). void socket::remove_connection(connection_ptr connection) { - // BUGBUG: use of connections_ in this method is not thread safe. - if (connections_.empty()) + // BUGBUG: use of work_ in this method is not thread safe. + if (work_.empty()) return; - const auto it = connections_.find(connection); - if (it != connections_.end()) + const auto it = work_.find(connection); + if (it != work_.end()) { // Tearing down a connection is O(n) where n is the amount of // remaining outstanding queries. @@ -392,7 +391,7 @@ void socket::remove_connection(connection_ptr connection) // Clear the query_work_map for this connection before removal. query_work_map.clear(); - connections_.erase(it); + work_.erase(it); } } @@ -431,10 +430,10 @@ void socket::notify_query_work(connection_ptr connection, return; } - // BUGBUG: use of connections_ in this method is not thread safe. + // BUGBUG: use of work_ in this method is not thread safe. // BUGBUG: this includes modification of query_work_map below. - auto it = connections_.find(connection); - if (it == connections_.end()) + auto it = work_.find(connection); + if (it == work_.end()) { LOG_ERROR(LOG_SERVER) << "Query work provided for unknown connection " << connection; @@ -504,8 +503,8 @@ void socket::broadcast(const std::string& json) send(entry.first, json); }; - // BUGBUG: use of connections_ in this method is not thread safe. - std::for_each(connections_.begin(), connections_.end(), sender); + // BUGBUG: use of work_ in this method is not thread safe. + std::for_each(work_.begin(), work_.end(), sender); } } // namespace server From 7b379d241a8dcfff886e4f8fd984d62570a2043f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 05:10:28 -0700 Subject: [PATCH 11/25] Resolve name confusion re: connection and socket websocket_endpoint. --- include/bitcoin/server/web/socket.hpp | 1 + src/web/http/connection.cpp | 8 ++++---- src/web/http/connection.hpp | 8 ++++---- src/web/http/manager.cpp | 8 ++++---- src/web/socket.cpp | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/bitcoin/server/web/socket.hpp b/include/bitcoin/server/web/socket.hpp index 1c88d111..43c0fd2e 100644 --- a/include/bitcoin/server/web/socket.hpp +++ b/include/bitcoin/server/web/socket.hpp @@ -139,6 +139,7 @@ class BCS_API socket const std::string security_; const bc::server::settings& server_settings_; const bc::protocol::settings& protocol_settings_; + // handlers_ is effectively const. handler_map handlers_; diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 25005ea9..96a2be5d 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -282,14 +282,14 @@ void connection::set_websocket(bool websocket) websocket_ = websocket; } -const std::string& connection::websocket_endpoint() const +const std::string& connection::uri() const { - return websocket_endpoint_; + return uri_; } -void connection::set_websocket_endpoint(const std::string& endpoint) +void connection::set_uri(const std::string& uri) { - websocket_endpoint_ = endpoint; + uri_ = uri; } bool connection::json_rpc() const diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index 646725e7..d225be07 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -66,9 +66,9 @@ class connection bool json_rpc() const; void set_json_rpc(bool json_rpc); - // Websocket endpoints are HTTP specific endpoints such as '/'. - const std::string& websocket_endpoint() const; - void set_websocket_endpoint(const std::string& endpoint); + // The connection endpoint is a request uri, such as '/'. + const std::string& uri() const; + void set_uri(const std::string& uri); // Readers and Writers. // ------------------------------------------------------------------------ @@ -114,7 +114,7 @@ class connection sockaddr_in address_; asio::time_point last_active_; ssl ssl_context_; - std::string websocket_endpoint_; + std::string uri_; bool websocket_; bool json_rpc_; diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 499d244c..cf947e44 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -884,7 +884,7 @@ bool manager::handle_websocket(connection_ptr connection) websocket_message message { - connection->websocket_endpoint(), + connection->uri(), data + header_length, data_length, flags, @@ -916,7 +916,7 @@ bool manager::handle_websocket(connection_ptr connection) websocket_message message { - connection->websocket_endpoint(), + connection->uri(), data + transfer.header_length, transfer.data.size() - transfer.header_length, flags, @@ -957,7 +957,7 @@ bool manager::handle_websocket(connection_ptr connection) websocket_message message { - connection->websocket_endpoint(), + connection->uri(), data + header_length, data_length, flags, @@ -1104,7 +1104,7 @@ bool manager::upgrade_connection(connection_ptr connection, } connection->set_websocket(true); - connection->set_websocket_endpoint(request.uri); + connection->set_uri(request.uri); LOG_VERBOSE(LOG_SERVER_HTTP) << "Upgraded connection " << connection << " for uri " << request.uri; diff --git a/src/web/socket.cpp b/src/web/socket.cpp index 8bb9db28..3cfec7b9 100644 --- a/src/web/socket.cpp +++ b/src/web/socket.cpp @@ -314,7 +314,7 @@ void socket::handle_websockets() const std::shared_ptr socket::service() const { - // TODO: implement. + // TODO: implement? BITCOIN_ASSERT_MSG(false, "not implemented"); return nullptr; } From 1a46444eb0fc19bf5cf04216b1c7f2f68228cacb Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 11:29:24 -0700 Subject: [PATCH 12/25] Comments, headers. --- src/web/http/connection.hpp | 2 +- src/web/socket.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index d225be07..e0a691eb 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -19,12 +19,12 @@ #ifndef LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP #define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP -#include #include #include #include #include #include +#include #include "http.hpp" diff --git a/src/web/socket.cpp b/src/web/socket.cpp index 3cfec7b9..fd3f8c0b 100644 --- a/src/web/socket.cpp +++ b/src/web/socket.cpp @@ -99,6 +99,7 @@ class task_sender const std::string data_; }; +// TODO: eliminate the use of weak and untyped pointer to pass self here. // static // Callback made internally via socket::poll on the web socket thread. bool socket::handle_event(connection_ptr connection, const http::event event, From ed4951dbde6c542af856e26bf20563cb7eda1707 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sun, 21 Oct 2018 11:36:59 -0700 Subject: [PATCH 13/25] Move supporting/base web files to http subdirectory. --- Makefile.am | 11 +++++--- .../libbitcoin-server.vcxproj | 8 +++--- .../libbitcoin-server.vcxproj.filters | 27 ++++++++++--------- .../libbitcoin-server.vcxproj | 8 +++--- .../libbitcoin-server.vcxproj.filters | 27 ++++++++++--------- .../libbitcoin-server.vcxproj | 8 +++--- .../libbitcoin-server.vcxproj.filters | 27 ++++++++++--------- include/bitcoin/server.hpp | 4 +-- include/bitcoin/server/web/block_socket.hpp | 2 +- .../bitcoin/server/web/heartbeat_socket.hpp | 2 +- .../server/web/{ => http}/json_string.hpp | 0 .../bitcoin/server/web/{ => http}/socket.hpp | 0 include/bitcoin/server/web/query_socket.hpp | 2 +- .../bitcoin/server/web/transaction_socket.hpp | 2 +- src/web/block_socket.cpp | 2 +- src/web/heartbeat_socket.cpp | 2 +- src/web/http/connection.hpp | 1 + src/web/{ => http}/json_string.cpp | 2 +- src/web/{ => http}/socket.cpp | 4 +-- src/web/query_socket.cpp | 2 +- src/web/transaction_socket.cpp | 2 +- 21 files changed, 78 insertions(+), 65 deletions(-) rename include/bitcoin/server/web/{ => http}/json_string.hpp (100%) rename include/bitcoin/server/web/{ => http}/socket.hpp (100%) rename src/web/{ => http}/json_string.cpp (98%) rename src/web/{ => http}/socket.cpp (99%) diff --git a/Makefile.am b/Makefile.am index f8a42860..8af82e9e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,15 +52,15 @@ src_libbitcoin_server_la_SOURCES = \ src/services/transaction_service.cpp \ src/web/block_socket.cpp \ src/web/heartbeat_socket.cpp \ - src/web/json_string.cpp \ src/web/query_socket.cpp \ - src/web/socket.cpp \ src/web/transaction_socket.cpp \ src/web/http/connection.cpp \ src/web/http/connection.hpp \ src/web/http/http.hpp \ + src/web/http/json_string.cpp \ src/web/http/manager.cpp \ src/web/http/manager.hpp \ + src/web/http/socket.cpp \ src/web/http/utilities.cpp \ src/web/http/utilities.hpp \ src/workers/authenticator.cpp \ @@ -136,11 +136,14 @@ include_bitcoin_server_webdir = ${includedir}/bitcoin/server/web include_bitcoin_server_web_HEADERS = \ include/bitcoin/server/web/block_socket.hpp \ include/bitcoin/server/web/heartbeat_socket.hpp \ - include/bitcoin/server/web/json_string.hpp \ include/bitcoin/server/web/query_socket.hpp \ - include/bitcoin/server/web/socket.hpp \ include/bitcoin/server/web/transaction_socket.hpp +include_bitcoin_server_web_httpdir = ${includedir}/bitcoin/server/web/http +include_bitcoin_server_web_http_HEADERS = \ + include/bitcoin/server/web/http/json_string.hpp \ + include/bitcoin/server/web/http/socket.hpp + include_bitcoin_server_workersdir = ${includedir}/bitcoin/server/workers include_bitcoin_server_workers_HEADERS = \ include/bitcoin/server/workers/authenticator.hpp \ diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj index c7b45f96..677530f2 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj @@ -91,11 +91,11 @@ + + - - @@ -122,9 +122,9 @@ - + + - diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters index e7473630..8aadb3d8 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -28,11 +28,14 @@ {73CE0AC2-ECB2-4E8D-0000-00000000000D} + + {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} - {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-000000000001} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -111,21 +114,21 @@ src\web\http + + src\web\http + src\web\http - + src\web\http - - src\web + + src\web\http src\web - - src\web - src\web @@ -200,13 +203,13 @@ include\bitcoin\server\web - - include\bitcoin\server\web + + include\bitcoin\server\web\http - - include\bitcoin\server\web + + include\bitcoin\server\web\http - + include\bitcoin\server\web diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj index afdab21d..d7591a9f 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj @@ -91,11 +91,11 @@ + + - - @@ -122,9 +122,9 @@ - + + - diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters index f04ca5e7..ec265bb2 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -28,11 +28,14 @@ {73CE0AC2-ECB2-4E8D-0000-00000000000D} + + {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} - {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-000000000001} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -111,21 +114,21 @@ src\web\http + + src\web\http + src\web\http - + src\web\http - - src\web + + src\web\http src\web - - src\web - src\web @@ -200,13 +203,13 @@ include\bitcoin\server\web - - include\bitcoin\server\web + + include\bitcoin\server\web\http - - include\bitcoin\server\web + + include\bitcoin\server\web\http - + include\bitcoin\server\web diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj index 75bd4d3b..be01205c 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj @@ -91,11 +91,11 @@ + + - - @@ -122,9 +122,9 @@ - + + - diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters index 7e43dc9a..2d75eee4 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -28,11 +28,14 @@ {73CE0AC2-ECB2-4E8D-0000-00000000000D} + + {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} - {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-000000000001} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -111,21 +114,21 @@ src\web\http + + src\web\http + src\web\http - + src\web\http - - src\web + + src\web\http src\web - - src\web - src\web @@ -200,13 +203,13 @@ include\bitcoin\server\web - - include\bitcoin\server\web + + include\bitcoin\server\web\http - - include\bitcoin\server\web + + include\bitcoin\server\web\http - + include\bitcoin\server\web diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index 2694f784..aea5ccfa 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -35,10 +35,10 @@ #include #include #include -#include #include -#include #include +#include +#include #include #include #include diff --git a/include/bitcoin/server/web/block_socket.hpp b/include/bitcoin/server/web/block_socket.hpp index b2aa11c9..ea8f2c48 100644 --- a/include/bitcoin/server/web/block_socket.hpp +++ b/include/bitcoin/server/web/block_socket.hpp @@ -21,7 +21,7 @@ #include #include -#include +#include namespace libbitcoin { namespace server { diff --git a/include/bitcoin/server/web/heartbeat_socket.hpp b/include/bitcoin/server/web/heartbeat_socket.hpp index 3b2b7162..0f741ad2 100644 --- a/include/bitcoin/server/web/heartbeat_socket.hpp +++ b/include/bitcoin/server/web/heartbeat_socket.hpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include namespace libbitcoin { namespace server { diff --git a/include/bitcoin/server/web/json_string.hpp b/include/bitcoin/server/web/http/json_string.hpp similarity index 100% rename from include/bitcoin/server/web/json_string.hpp rename to include/bitcoin/server/web/http/json_string.hpp diff --git a/include/bitcoin/server/web/socket.hpp b/include/bitcoin/server/web/http/socket.hpp similarity index 100% rename from include/bitcoin/server/web/socket.hpp rename to include/bitcoin/server/web/http/socket.hpp diff --git a/include/bitcoin/server/web/query_socket.hpp b/include/bitcoin/server/web/query_socket.hpp index 48c9afde..1865c471 100644 --- a/include/bitcoin/server/web/query_socket.hpp +++ b/include/bitcoin/server/web/query_socket.hpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include namespace libbitcoin { namespace server { diff --git a/include/bitcoin/server/web/transaction_socket.hpp b/include/bitcoin/server/web/transaction_socket.hpp index 74b147f9..a9c536f0 100644 --- a/include/bitcoin/server/web/transaction_socket.hpp +++ b/include/bitcoin/server/web/transaction_socket.hpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include namespace libbitcoin { namespace server { diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index 93d5c618..e5c163db 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include namespace libbitcoin { namespace server { diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index 00b00638..e3726aea 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include namespace libbitcoin { namespace server { diff --git a/src/web/http/connection.hpp b/src/web/http/connection.hpp index e0a691eb..a3a00d5c 100644 --- a/src/web/http/connection.hpp +++ b/src/web/http/connection.hpp @@ -99,6 +99,7 @@ class connection sock_t& socket(); http::ssl& ssl_context(); bool ssl_enabled() const; + http::file_transfer& file_transfer(); http::websocket_transfer& websocket_transfer(); diff --git a/src/web/json_string.cpp b/src/web/http/json_string.cpp similarity index 98% rename from src/web/json_string.cpp rename to src/web/http/json_string.cpp index ed9dbb56..0140b68f 100644 --- a/src/web/json_string.cpp +++ b/src/web/http/json_string.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include +#include #include #include diff --git a/src/web/socket.cpp b/src/web/http/socket.cpp similarity index 99% rename from src/web/socket.cpp rename to src/web/http/socket.cpp index fd3f8c0b..f337f722 100644 --- a/src/web/socket.cpp +++ b/src/web/http/socket.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include +#include #include #include @@ -25,7 +25,7 @@ #include #include #include -#include +#include #ifdef WITH_MBEDTLS extern "C" diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index dd999eb5..6a712b95 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include // Explicitly use std::placeholders here for usage internally to the // boost parsing helpers included from json_parser.hpp. diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index 4a171bf6..1ce43743 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include namespace libbitcoin { namespace server { From b9cf9a3ca6d5e44d0043aa91cd1a968702316ad7 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 22 Oct 2018 02:08:23 -0700 Subject: [PATCH 14/25] Remove use of local src/web/http includes (WIP). --- Makefile.am | 10 ++-- .../libbitcoin-server.vcxproj | 8 +-- .../libbitcoin-server.vcxproj.filters | 24 ++++---- .../libbitcoin-server.vcxproj | 8 +-- .../libbitcoin-server.vcxproj.filters | 24 ++++---- .../libbitcoin-server.vcxproj | 8 +-- .../libbitcoin-server.vcxproj.filters | 24 ++++---- include/bitcoin/server.hpp | 4 ++ .../bitcoin/server}/web/http/connection.hpp | 5 +- .../bitcoin/server}/web/http/http.hpp | 56 ++++++++++--------- .../bitcoin/server/web/http/json_string.hpp | 7 ++- .../bitcoin/server}/web/http/manager.hpp | 3 +- include/bitcoin/server/web/http/socket.hpp | 20 +++---- .../bitcoin/server}/web/http/utilities.hpp | 12 ++-- src/web/http/connection.cpp | 9 +-- src/web/http/manager.cpp | 7 ++- src/web/http/socket.cpp | 11 ++-- src/web/http/utilities.cpp | 12 ++-- src/web/query_socket.cpp | 9 +-- 19 files changed, 130 insertions(+), 131 deletions(-) rename {src => include/bitcoin/server}/web/http/connection.hpp (97%) rename {src => include/bitcoin/server}/web/http/http.hpp (92%) rename {src => include/bitcoin/server}/web/http/manager.hpp (97%) rename {src => include/bitcoin/server}/web/http/utilities.hpp (87%) diff --git a/Makefile.am b/Makefile.am index 8af82e9e..351efd2b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -55,14 +55,10 @@ src_libbitcoin_server_la_SOURCES = \ src/web/query_socket.cpp \ src/web/transaction_socket.cpp \ src/web/http/connection.cpp \ - src/web/http/connection.hpp \ - src/web/http/http.hpp \ src/web/http/json_string.cpp \ src/web/http/manager.cpp \ - src/web/http/manager.hpp \ src/web/http/socket.cpp \ src/web/http/utilities.cpp \ - src/web/http/utilities.hpp \ src/workers/authenticator.cpp \ src/workers/notification_worker.cpp \ src/workers/query_worker.cpp @@ -141,8 +137,12 @@ include_bitcoin_server_web_HEADERS = \ include_bitcoin_server_web_httpdir = ${includedir}/bitcoin/server/web/http include_bitcoin_server_web_http_HEADERS = \ + include/bitcoin/server/web/http/connection.hpp \ + include/bitcoin/server/web/http/http.hpp \ include/bitcoin/server/web/http/json_string.hpp \ - include/bitcoin/server/web/http/socket.hpp + include/bitcoin/server/web/http/manager.hpp \ + include/bitcoin/server/web/http/socket.hpp \ + include/bitcoin/server/web/http/utilities.hpp include_bitcoin_server_workersdir = ${includedir}/bitcoin/server/workers include_bitcoin_server_workers_HEADERS = \ diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj index 677530f2..dd252a6e 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj @@ -122,17 +122,17 @@ + + + + - - - - diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters index 8aadb3d8..c34f0747 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -203,12 +203,24 @@ include\bitcoin\server\web + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web @@ -224,18 +236,6 @@ include\bitcoin\server\workers - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - resource diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj index d7591a9f..06d0af5a 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj @@ -122,17 +122,17 @@ + + + + - - - - diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters index ec265bb2..e3d9e234 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -203,12 +203,24 @@ include\bitcoin\server\web + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web @@ -224,18 +236,6 @@ include\bitcoin\server\workers - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - resource diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj index be01205c..e6b9b58d 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj @@ -122,17 +122,17 @@ + + + + - - - - diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters index 2d75eee4..3c2dd6e2 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -203,12 +203,24 @@ include\bitcoin\server\web + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web @@ -224,18 +236,6 @@ include\bitcoin\server\workers - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - resource diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index aea5ccfa..6c1a5cad 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -37,8 +37,12 @@ #include #include #include +#include +#include #include +#include #include +#include #include #include #include diff --git a/src/web/http/connection.hpp b/include/bitcoin/server/web/http/connection.hpp similarity index 97% rename from src/web/http/connection.hpp rename to include/bitcoin/server/web/http/connection.hpp index a3a00d5c..4336d055 100644 --- a/src/web/http/connection.hpp +++ b/include/bitcoin/server/web/http/connection.hpp @@ -20,13 +20,13 @@ #define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP #include +#include #include #include #include #include #include - -#include "http.hpp" +#include namespace libbitcoin { namespace server { @@ -34,6 +34,7 @@ namespace http { class connection; +// TODO: make internal connection typedefs. typedef std::shared_ptr connection_ptr; typedef std::set connection_set; typedef std::vector connection_list; diff --git a/src/web/http/http.hpp b/include/bitcoin/server/web/http/http.hpp similarity index 92% rename from src/web/http/http.hpp rename to include/bitcoin/server/web/http/http.hpp index 3a718ec7..c0db6a30 100644 --- a/src/web/http/http.hpp +++ b/include/bitcoin/server/web/http/http.hpp @@ -16,20 +16,21 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifndef LIBBITCOIN_SERVER_WEB_HTTP_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_HPP +#ifndef LIBBITCOIN_SERVER_WEB_HTTP_HTTP_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_HTTP_HPP #include +#include #include -#include #include +#include #include -#include #include #include #include #include #include +#include #include #include #include @@ -70,6 +71,8 @@ #include #endif +#include + namespace libbitcoin { namespace server { namespace http { @@ -83,33 +86,32 @@ namespace http { #define CLOSE_SOCKET ::close #endif +#ifdef WITH_MBEDTLS + static const int32_t default_ciphers[] = + { + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + 0 + }; +#endif + static const size_t sha1_hash_length = 20; static const size_t default_buffer_length = 1 << 10; // 1KB static const size_t transfer_buffer_length = 1 << 18; // 256KB -#ifdef WITH_MBEDTLS -static const int32_t default_ciphers[] = -{ - MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, - MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - 0 -}; -#endif - -////typedef std::vector data_buffer; typedef std::array read_buffer; typedef std::array sha1_hash; typedef std::vector string_list; diff --git a/include/bitcoin/server/web/http/json_string.hpp b/include/bitcoin/server/web/http/json_string.hpp index 503fd751..00efb2bb 100644 --- a/include/bitcoin/server/web/http/json_string.hpp +++ b/include/bitcoin/server/web/http/json_string.hpp @@ -16,14 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifndef LIBBITCOIN_SERVER_WEB_JSON_STRING_HPP -#define LIBBITCOIN_SERVER_WEB_JSON_STRING_HPP +#ifndef LIBBITCOIN_SERVER_WEB_HTTP_JSON_STRING_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_JSON_STRING_HPP #include +#include +#include #include #include #include -#include namespace libbitcoin { namespace server { diff --git a/src/web/http/manager.hpp b/include/bitcoin/server/web/http/manager.hpp similarity index 97% rename from src/web/http/manager.hpp rename to include/bitcoin/server/web/http/manager.hpp index 53610735..6c92c6c9 100644 --- a/src/web/http/manager.hpp +++ b/include/bitcoin/server/web/http/manager.hpp @@ -23,7 +23,7 @@ #include #include #include -#include "connection.hpp" +#include namespace libbitcoin { namespace server { @@ -43,6 +43,7 @@ class manager typedef boost::filesystem::path path; typedef std::shared_ptr task_ptr; typedef std::vector task_list; + typedef std::shared_ptr ptr; manager(bool ssl, event_handler handler, path document_root); ~manager(); diff --git a/include/bitcoin/server/web/http/socket.hpp b/include/bitcoin/server/web/http/socket.hpp index 43c0fd2e..f6313f4d 100644 --- a/include/bitcoin/server/web/http/socket.hpp +++ b/include/bitcoin/server/web/http/socket.hpp @@ -16,18 +16,21 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#ifndef LIBBITCOIN_SERVER_WEB_SOCKET_HPP -#define LIBBITCOIN_SERVER_WEB_SOCKET_HPP +#ifndef LIBBITCOIN_SERVER_WEB_HTTP_SOCKET_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_SOCKET_HPP #include #include +#include #include #include #include #include #include -#include #include +#include +#include +#include #ifdef WITH_MBEDTLS extern "C" @@ -41,15 +44,6 @@ namespace server { class server_node; -// Forward declarations in the http namespace. -namespace http -{ -class manager; -class connection; -enum class event : uint8_t; -typedef std::shared_ptr connection_ptr; -} // namespace http - class BCS_API socket : public bc::protocol::zmq::worker { @@ -159,7 +153,7 @@ class BCS_API socket static bool handle_event(connection_ptr connection, const http::event event, const void* data); - std::shared_ptr manager_; + http::manager::ptr manager_; const std::string domain_; const boost::filesystem::path document_root_; }; diff --git a/src/web/http/utilities.hpp b/include/bitcoin/server/web/http/utilities.hpp similarity index 87% rename from src/web/http/utilities.hpp rename to include/bitcoin/server/web/http/utilities.hpp index 6be749f0..6fbf3542 100644 --- a/src/web/http/utilities.hpp +++ b/include/bitcoin/server/web/http/utilities.hpp @@ -25,12 +25,12 @@ namespace http { #include #include -#include "http.hpp" +#include #ifdef _MSC_VER -#define last_error() GetLastError() + #define last_error() GetLastError() #else -#define last_error() errno + #define last_error() errno #endif #ifdef _MSC_VER @@ -39,12 +39,12 @@ namespace http { #define would_block(value) (value == EAGAIN || value == EWOULDBLOCK) #endif -#define mbedtls_would_block(value) \ - (value == MBEDTLS_ERR_SSL_WANT_READ || value == MBEDTLS_ERR_SSL_WANT_WRITE) - #ifdef WITH_MBEDTLS std::string mbedtls_error_string(int32_t error); sha1_hash sha1(const std::string& input); + #define mbedtls_would_block(value) \ + (value == MBEDTLS_ERR_SSL_WANT_READ || \ + value == MBEDTLS_ERR_SSL_WANT_WRITE) #endif std::string error_string(); diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 96a2be5d..b7af4c44 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -16,13 +16,14 @@ * 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 "connection.hpp" -#include "http.hpp" -#include "utilities.hpp" +#include +#include namespace libbitcoin { namespace server { diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index cf947e44..ed783cfc 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -16,15 +16,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -#include "manager.hpp" +#include #include #include #include +#include #include #include -#include "http.hpp" -#include "utilities.hpp" +#include +#include #ifdef WITH_MBEDTLS extern "C" diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp index f337f722..4bb47a28 100644 --- a/src/web/http/socket.cpp +++ b/src/web/http/socket.cpp @@ -20,12 +20,15 @@ #include #include - #include #include #include #include +#include +#include #include +#include +#include #ifdef WITH_MBEDTLS extern "C" @@ -40,11 +43,6 @@ int https_random(void*, uint8_t* buffer, size_t length) } #endif -#include "../src/web/http/http.hpp" -#include "../src/web/http/utilities.hpp" -#include "../src/web/http/manager.hpp" -#include "../src/web/http/connection.hpp" - namespace libbitcoin { namespace server { @@ -241,6 +239,7 @@ socket::socket(zmq::authenticator& authenticator, server_node& node, protocol_settings_(node.protocol_settings()), sequence_(0), domain_(domain), + manager_(nullptr), document_root_(node.server_settings().websockets_root) { } diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index 26d8c54a..e103be3c 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -16,16 +16,18 @@ * 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 #include #include +#include -#include "http.hpp" -#include "utilities.hpp" +// TODO: determine why this include must follow http.hpp. +#include namespace libbitcoin { namespace server { @@ -57,7 +59,7 @@ std::string error_string() std::string mbedtls_error_string(int32_t error) { static constexpr size_t error_buffer_length = 256; - std::array data{}; + std::array data; mbedtls_strerror(error, data.data(), data.size()); return { data.data() }; } diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 6a712b95..c37eb0f8 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -21,16 +21,9 @@ #include #include #include +#include #include -// Explicitly use std::placeholders here for usage internally to the -// boost parsing helpers included from json_parser.hpp. -// See: https://svn.boost.org/trac10/ticket/12621 -#include -using namespace std::placeholders; - -#include "../src/web/http/connection.hpp" - namespace libbitcoin { namespace server { From 37efcff1fcc8625cdb316694b46f5dcf2979acc6 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 22 Oct 2018 04:31:39 -0700 Subject: [PATCH 15/25] Style and comments. --- include/bitcoin/server/web/http/connection.hpp | 4 ++-- include/bitcoin/server/web/http/manager.hpp | 6 +++--- src/web/block_socket.cpp | 4 ++-- src/web/heartbeat_socket.cpp | 4 ++-- src/web/http/manager.cpp | 16 ++++++++++++++-- src/web/http/socket.cpp | 11 ++++++++--- src/web/query_socket.cpp | 2 +- src/web/transaction_socket.cpp | 2 +- 8 files changed, 33 insertions(+), 16 deletions(-) diff --git a/include/bitcoin/server/web/http/connection.hpp b/include/bitcoin/server/web/http/connection.hpp index 4336d055..92ca6832 100644 --- a/include/bitcoin/server/web/http/connection.hpp +++ b/include/bitcoin/server/web/http/connection.hpp @@ -45,7 +45,7 @@ typedef std::function // Initiating outgoing HTTP connections are not currently supported. class connection { - public: +public: typedef std::function write_method; connection(); @@ -109,7 +109,7 @@ class connection bool operator==(const connection& other); - private: +private: void* user_data_; connection_state state_; sock_t socket_; diff --git a/include/bitcoin/server/web/http/manager.hpp b/include/bitcoin/server/web/http/manager.hpp index 6c92c6c9..fc71e992 100644 --- a/include/bitcoin/server/web/http/manager.hpp +++ b/include/bitcoin/server/web/http/manager.hpp @@ -31,7 +31,7 @@ namespace http { class manager { - public: +public: class task { public: @@ -53,7 +53,7 @@ class manager // Connections. bool accept_connection(); - void add_connection(const connection_ptr connection); + void add_connection(connection_ptr connection); void remove_connection(connection_ptr connection); size_t connection_count() const; @@ -69,7 +69,7 @@ class manager void poll(size_t timeout_milliseconds); bool handle_connection(connection_ptr connection, event current_event); - private: +private: #ifdef WITH_MBEDTLS // Passed to mbedtls for internal use only. static int32_t ssl_send(void* data, const uint8_t* buffer, size_t length); diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index e5c163db..e4f66b3c 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -118,8 +118,8 @@ bool block_socket::handle_block(zmq::socket& subscriber) return true; } - uint16_t sequence{}; - uint32_t height{}; + uint16_t sequence; + uint32_t height; data_chunk block_data; response.dequeue(sequence); response.dequeue(height); diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index e3726aea..d5441d94 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -114,8 +114,8 @@ bool heartbeat_socket::handle_heartbeat(zmq::socket& subscriber) return true; } - uint16_t sequence{}; - uint64_t height{}; + uint16_t sequence; + uint64_t height; response.dequeue(sequence); response.dequeue(height); diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index ed783cfc..a47033ee 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -112,7 +112,11 @@ bool manager::bind(const config::endpoint& address, user_data_ = options.user_data; listener_ = std::make_shared(); + + // asio::acceptor.open(endpoint.protocol()); + // ************************************************************************ listener_->socket() = ::socket(listener_address_.sin_family, SOCK_STREAM, 0); + // ************************************************************************ if (listener_->socket() == 0) { @@ -132,13 +136,17 @@ bool manager::bind(const config::endpoint& address, ca_certificate_ = options.ssl_ca_certificate; } + // The default context object for the listener socket is initialized. if (!initialize_ssl(listener_, listening_)) return false; } - listener_->set_socket_non_blocking(); + //// asio::acceptor.set_option(reuse_address); listener_->reuse_address(); + listener_->set_socket_non_blocking(); + //// asio::acceptor.bind(address); + // ************************************************************************ if (::bind(listener_->socket(), reinterpret_cast( &listener_address_), sizeof(listener_address_)) != 0) { @@ -148,8 +156,12 @@ bool manager::bind(const config::endpoint& address, listener_->close(); return false; } + // ************************************************************************ + //// asio::acceptor.listen(asio::max_connections); + // ************************************************************************ ::listen(listener_->socket(), maximum_backlog); + // ************************************************************************ listener_->set_state(connection_state::listening); add_connection(listener_); @@ -246,7 +258,7 @@ bool manager::accept_connection() return true; } -void manager::add_connection(const connection_ptr connection) +void manager::add_connection(connection_ptr connection) { connections_.push_back(connection); diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp index 4bb47a28..cae71071 100644 --- a/src/web/http/socket.cpp +++ b/src/web/http/socket.cpp @@ -281,6 +281,8 @@ bool socket::start() void socket::handle_websockets() { http::bind_options options; + + // This starts up the listener for the socket. manager_ = std::make_shared(secure_, &socket::handle_event, document_root_); @@ -296,9 +298,12 @@ void socket::handle_websockets() { // Specified and not found CA cert should be a failure condition. // TODO: defer string conversion to ssl internals, keep paths here. - options.ssl_key = server_settings_.websockets_server_private_key.generic_string(); - options.ssl_certificate = server_settings_.websockets_server_certificate.generic_string(); - options.ssl_ca_certificate = server_settings_.websockets_ca_certificate.generic_string(); + options.ssl_key = server_settings_. + websockets_server_private_key.generic_string(); + options.ssl_certificate = server_settings_. + websockets_server_certificate.generic_string(); + options.ssl_ca_certificate = server_settings_. + websockets_ca_certificate.generic_string(); } options.user_data = static_cast(this); diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index c37eb0f8..6150b893 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -231,7 +231,7 @@ bool query_socket::handle_query(zmq::socket& dealer) return true; } - uint32_t sequence{}; + uint32_t sequence; data_chunk data; std::string command; diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index 1ce43743..027db089 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -117,7 +117,7 @@ bool transaction_socket::handle_transaction(zmq::socket& subscriber) return true; } - uint16_t sequence{}; + uint16_t sequence; data_chunk transaction_data; response.dequeue(sequence); response.dequeue(transaction_data); From 354fbfdc1732beb9763b91d3d722c7d0c5b1268c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Mon, 22 Oct 2018 16:07:11 -0700 Subject: [PATCH 16/25] Conform to class-file and namespace conventions. --- Makefile.am | 14 +- .../libbitcoin-server.vcxproj | 12 + .../libbitcoin-server.vcxproj.filters | 36 ++ .../libbitcoin-server.vcxproj | 12 + .../libbitcoin-server.vcxproj.filters | 36 ++ .../libbitcoin-server.vcxproj | 12 + .../libbitcoin-server.vcxproj.filters | 36 ++ include/bitcoin/server.hpp | 12 + include/bitcoin/server/web/block_socket.hpp | 2 +- .../bitcoin/server/web/heartbeat_socket.hpp | 2 +- .../bitcoin/server/web/http/bind_options.hpp | 44 ++ .../bitcoin/server/web/http/connection.hpp | 11 +- .../server/web/http/connection_state.hpp | 42 ++ include/bitcoin/server/web/http/event.hpp | 45 ++ .../bitcoin/server/web/http/file_transfer.hpp | 42 ++ include/bitcoin/server/web/http/http.hpp | 394 +----------------- .../bitcoin/server/web/http/http_reply.hpp | 122 ++++++ .../bitcoin/server/web/http/http_request.hpp | 74 ++++ .../bitcoin/server/web/http/json_string.hpp | 23 +- include/bitcoin/server/web/http/manager.hpp | 8 +- .../server/web/http/protocol_status.hpp | 53 +++ include/bitcoin/server/web/http/socket.hpp | 25 +- include/bitcoin/server/web/http/ssl.hpp | 47 +++ include/bitcoin/server/web/http/utilities.hpp | 25 +- .../server/web/http/websocket_frame.hpp | 176 ++++++++ .../server/web/http/websocket_message.hpp | 45 ++ .../bitcoin/server/web/http/websocket_op.hpp | 42 ++ .../server/web/http/websocket_transfer.hpp | 45 ++ include/bitcoin/server/web/query_socket.hpp | 2 +- .../bitcoin/server/web/transaction_socket.hpp | 2 +- src/web/block_socket.cpp | 12 +- src/web/heartbeat_socket.cpp | 12 +- src/web/http/connection.cpp | 2 + src/web/http/json_string.cpp | 4 +- src/web/http/manager.cpp | 18 +- src/web/http/socket.cpp | 38 +- src/web/http/utilities.cpp | 71 ++-- src/web/query_socket.cpp | 19 +- src/web/transaction_socket.cpp | 12 +- 39 files changed, 1111 insertions(+), 518 deletions(-) create mode 100644 include/bitcoin/server/web/http/bind_options.hpp create mode 100644 include/bitcoin/server/web/http/connection_state.hpp create mode 100644 include/bitcoin/server/web/http/event.hpp create mode 100644 include/bitcoin/server/web/http/file_transfer.hpp create mode 100644 include/bitcoin/server/web/http/http_reply.hpp create mode 100644 include/bitcoin/server/web/http/http_request.hpp create mode 100644 include/bitcoin/server/web/http/protocol_status.hpp create mode 100644 include/bitcoin/server/web/http/ssl.hpp create mode 100644 include/bitcoin/server/web/http/websocket_frame.hpp create mode 100644 include/bitcoin/server/web/http/websocket_message.hpp create mode 100644 include/bitcoin/server/web/http/websocket_op.hpp create mode 100644 include/bitcoin/server/web/http/websocket_transfer.hpp diff --git a/Makefile.am b/Makefile.am index 351efd2b..9be8d411 100644 --- a/Makefile.am +++ b/Makefile.am @@ -137,12 +137,24 @@ include_bitcoin_server_web_HEADERS = \ include_bitcoin_server_web_httpdir = ${includedir}/bitcoin/server/web/http include_bitcoin_server_web_http_HEADERS = \ + include/bitcoin/server/web/http/bind_options.hpp \ include/bitcoin/server/web/http/connection.hpp \ + include/bitcoin/server/web/http/connection_state.hpp \ + include/bitcoin/server/web/http/event.hpp \ + include/bitcoin/server/web/http/file_transfer.hpp \ include/bitcoin/server/web/http/http.hpp \ + include/bitcoin/server/web/http/http_reply.hpp \ + include/bitcoin/server/web/http/http_request.hpp \ include/bitcoin/server/web/http/json_string.hpp \ include/bitcoin/server/web/http/manager.hpp \ + include/bitcoin/server/web/http/protocol_status.hpp \ include/bitcoin/server/web/http/socket.hpp \ - include/bitcoin/server/web/http/utilities.hpp + include/bitcoin/server/web/http/ssl.hpp \ + include/bitcoin/server/web/http/utilities.hpp \ + include/bitcoin/server/web/http/websocket_frame.hpp \ + include/bitcoin/server/web/http/websocket_message.hpp \ + include/bitcoin/server/web/http/websocket_op.hpp \ + include/bitcoin/server/web/http/websocket_transfer.hpp include_bitcoin_server_workersdir = ${includedir}/bitcoin/server/workers include_bitcoin_server_workers_HEADERS = \ diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj index dd252a6e..5bb3abad 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj @@ -122,12 +122,24 @@ + + + + + + + + + + + + diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters index c34f0747..bcf0a39a 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -203,24 +203,60 @@ include\bitcoin\server\web + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj index 06d0af5a..23d91d59 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj @@ -122,12 +122,24 @@ + + + + + + + + + + + + diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters index e3d9e234..63519d6c 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -203,24 +203,60 @@ include\bitcoin\server\web + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj index e6b9b58d..fc5453b0 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj @@ -122,12 +122,24 @@ + + + + + + + + + + + + diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters index 3c2dd6e2..a6b78d91 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -203,24 +203,60 @@ include\bitcoin\server\web + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web\http include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + include\bitcoin\server\web\http + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + + + include\bitcoin\server\web\http + include\bitcoin\server\web diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index 6c1a5cad..52950cb8 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -37,12 +37,24 @@ #include #include #include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include #include +#include #include +#include +#include +#include +#include #include #include #include diff --git a/include/bitcoin/server/web/block_socket.hpp b/include/bitcoin/server/web/block_socket.hpp index ea8f2c48..828d296d 100644 --- a/include/bitcoin/server/web/block_socket.hpp +++ b/include/bitcoin/server/web/block_socket.hpp @@ -31,7 +31,7 @@ class server_node; // This class is thread safe. // Subscribe to block acceptances from a dedicated socket endpoint. class BCS_API block_socket - : public socket + : public http::socket { public: typedef std::shared_ptr ptr; diff --git a/include/bitcoin/server/web/heartbeat_socket.hpp b/include/bitcoin/server/web/heartbeat_socket.hpp index 0f741ad2..948ff031 100644 --- a/include/bitcoin/server/web/heartbeat_socket.hpp +++ b/include/bitcoin/server/web/heartbeat_socket.hpp @@ -35,7 +35,7 @@ class server_node; // This class is thread safe. // Subscribe to a pulse from a dedicated socket endpoint. class BCS_API heartbeat_socket - : public socket + : public http::socket { public: typedef std::shared_ptr ptr; diff --git a/include/bitcoin/server/web/http/bind_options.hpp b/include/bitcoin/server/web/http/bind_options.hpp new file mode 100644 index 00000000..96c24d24 --- /dev/null +++ b/include/bitcoin/server/web/http/bind_options.hpp @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_BIND_OPTIONS_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_BIND_OPTIONS_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +struct BCS_API bind_options +{ + void* user_data; + uint32_t flags; + std::string ssl_key; + std::string ssl_certificate; + std::string ssl_ca_certificate; + std::string ssl_cipers; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/connection.hpp b/include/bitcoin/server/web/http/connection.hpp index 92ca6832..30cb023c 100644 --- a/include/bitcoin/server/web/http/connection.hpp +++ b/include/bitcoin/server/web/http/connection.hpp @@ -26,7 +26,13 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include namespace libbitcoin { namespace server { @@ -38,12 +44,11 @@ class connection; typedef std::shared_ptr connection_ptr; typedef std::set connection_set; typedef std::vector connection_list; -typedef std::function - event_handler; +typedef std::function event_handler; // This class is instantiated from accepted/incoming HTTP clients. // Initiating outgoing HTTP connections are not currently supported. -class connection +class BCS_API connection { public: typedef std::function write_method; diff --git a/include/bitcoin/server/web/http/connection_state.hpp b/include/bitcoin/server/web/http/connection_state.hpp new file mode 100644 index 00000000..af5c4b7c --- /dev/null +++ b/include/bitcoin/server/web/http/connection_state.hpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_CONNECTION_STATE_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_STATE_HPP + +namespace libbitcoin { +namespace server { +namespace http { + +enum class connection_state +{ + error = -1, + connecting = 99, + connected = 100, + listening = 101, + ssl_handshake = 102, + closed = 103, + disconnect_immediately = 200, + unknown +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/event.hpp b/include/bitcoin/server/web/http/event.hpp new file mode 100644 index 00000000..1f7058dc --- /dev/null +++ b/include/bitcoin/server/web/http/event.hpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_EVENT_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_EVENT_HPP + +#include + +namespace libbitcoin { +namespace server { +namespace http { + +enum class event : uint8_t +{ + read, + write, + listen, + accepted, + error, + closing, + websocket_frame, + websocket_control_frame, + json_rpc +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/file_transfer.hpp b/include/bitcoin/server/web/http/file_transfer.hpp new file mode 100644 index 00000000..b93b300b --- /dev/null +++ b/include/bitcoin/server/web/http/file_transfer.hpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_FILE_TRANSFER_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_FILE_TRANSFER_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +struct BCS_API file_transfer +{ + bool in_progress; + FILE* descriptor; + size_t offset; + size_t length; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/http.hpp b/include/bitcoin/server/web/http/http.hpp index c0db6a30..b0f5e330 100644 --- a/include/bitcoin/server/web/http/http.hpp +++ b/include/bitcoin/server/web/http/http.hpp @@ -22,19 +22,11 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include #include +// Centrally including headers here. #ifdef _MSC_VER #include #include @@ -51,16 +43,7 @@ #include #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include - +// Centrally including headers here. #ifdef WITH_MBEDTLS #include #include @@ -71,8 +54,6 @@ #include #endif -#include - namespace libbitcoin { namespace server { namespace http { @@ -82,7 +63,7 @@ namespace http { typedef uint32_t in_addr_t; #define CLOSE_SOCKET closesocket #else - typedef int sock_t; + typedef uint32_t sock_t; #define CLOSE_SOCKET ::close #endif @@ -108,377 +89,12 @@ namespace http { }; #endif -static const size_t sha1_hash_length = 20; -static const size_t default_buffer_length = 1 << 10; // 1KB -static const size_t transfer_buffer_length = 1 << 18; // 256KB +static const size_t default_buffer_length = 1 * 1024; +static const size_t transfer_buffer_length = 256 * 1024; typedef std::array read_buffer; -typedef std::array sha1_hash; -typedef std::vector string_list; typedef std::unordered_map string_map; -// TODO: move each of these enum and struct declarations to own file. - -enum class connection_state -{ - error = -1, - connecting = 99, - connected = 100, - listening = 101, - ssl_handshake = 102, - closed = 103, - disconnect_immediately = 200, - unknown -}; - -enum class event : uint8_t -{ - read, - write, - listen, - accepted, - error, - closing, - websocket_frame, - websocket_control_frame, - json_rpc -}; - -enum class protocol_status : uint16_t -{ - switching = 101, - ok = 200, - created = 201, - accepted = 202, - no_content = 204, - multiple_choices = 300, - moved_permanently = 301, - moved_temporarily = 302, - not_modified = 304, - bad_request = 400, - unauthorized = 401, - forbidden = 403, - not_found = 404, - internal_server_error = 500, - not_implemented = 501, - bad_gateway = 502, - service_unavailable = 503 -}; - -enum class websocket_op : uint8_t -{ - continuation = 0, - text = 1, - binary = 2, - close = 8, - ping = 9, - pong = 10, -}; - -struct websocket_message -{ - const std::string& endpoint; - const uint8_t* data; - size_t size; - uint8_t flags; - websocket_op code; -}; - -class websocket_frame -{ -public: - websocket_frame(const uint8_t* data, size_t size) - { - from_data(data, size); - } - - static data_chunk to_header(size_t length, websocket_op code) - { - if (length < 0x7e) - { - return build_chunk( - { - to_array(uint8_t(0x80) | static_cast(code)), - to_array(static_cast(length)) - }); - } - else if (length < max_uint16) - { - return build_chunk( - { - to_array(uint8_t(0x80) | static_cast(code)), - to_array(uint8_t(0x7e)), - to_big_endian(static_cast(length)) - }); - } - else - { - return build_chunk( - { - to_array(uint8_t(0x80) | static_cast(code)), - to_array(uint8_t(0x7f)), - to_big_endian(static_cast(length)) - }); - } - } - - operator bool() const - { - return valid_; - } - - bool final() const - { - return (flags_ & 0x80) != 0; - } - - bool fragment() const - { - return !final() || op_code() == websocket_op::continuation; - } - - event event_type() const - { - return (flags_ & 0x08) ? event::websocket_control_frame : - event::websocket_frame; - } - - websocket_op op_code() const - { - return static_cast(flags_ & 0x0f); - } - - uint8_t flags() const - { - return flags_; - } - - size_t header_length() const - { - return header_; - } - - size_t data_length() const - { - return data_; - } - - size_t mask_length() const - { - return valid_ ? mask_ : 0; - } - -private: - void from_data(const uint8_t* data, size_t read_length) - { - static constexpr size_t prefix = 2; - static constexpr size_t prefix16 = prefix + sizeof(uint16_t); - static constexpr size_t prefix64 = prefix + sizeof(uint64_t); - - valid_ = false; - - // Invalid websocket frame (too small). - if (read_length < 2) - return; - - flags_ = data[0]; - header_ = 0; - data_ = 0; - - // Invalid websocket frame (unmasked). - if ((data[1] & 0x80) == 0) - return; - - const size_t length = (data[1] & 0x7f); - - if (read_length >= mask_ && length < 0x7e) - { - header_ = prefix + mask_; - data_ = length; - } - else if (read_length >= prefix16 + mask_ && length == 0x7e) - { - header_ = prefix16 + mask_; - data_ = from_big_endian(&data[prefix], - &data[prefix16]); - } - else if (read_length >= prefix64 + mask_) - { - header_ = prefix64 + mask_; - data_ = from_big_endian(&data[prefix], - &data[prefix64]); - } - - valid_ = true; - return; - } - -private: - static const size_t mask_ = 4; - - bool valid_; - uint8_t flags_; - size_t header_; - size_t data_; -}; - -struct ssl -{ - bool enabled; - std::string hostname; -#ifdef WITH_MBEDTLS - mbedtls_ssl_context context; - mbedtls_ssl_config configuration; - mbedtls_pk_context key; - mbedtls_x509_crt certificate; - mbedtls_x509_crt ca_certificate; -#endif -}; - -struct bind_options -{ - void* user_data; - uint32_t flags; - std::string ssl_key; - std::string ssl_certificate; - std::string ssl_ca_certificate; - std::string ssl_cipers; -}; - -struct file_transfer -{ - bool in_progress; - FILE* descriptor; - size_t offset; - size_t length; -}; - -struct websocket_transfer -{ - bool in_progress; - size_t offset; - size_t length; - size_t header_length; - data_chunk mask; - data_chunk data; -}; - -class http_request -{ -public: - std::string find(const string_map& haystack, - const std::string& needle) const - { - const auto it = haystack.find(needle); - return it == haystack.end() ? std::string{} : it->second; - } - - std::string header(std::string header) const - { - boost::algorithm::to_lower(header); - return find(headers, header); - } - - std::string parameter(std::string parameter) const - { - boost::algorithm::to_lower(parameter); - return find(parameters, parameter); - } - - std::string method; - std::string uri; - std::string protocol; - double protocol_version; - size_t message_length; - size_t content_length; - string_map headers; - string_map parameters; - bool upgrade_request; - bool json_rpc; - boost::property_tree::ptree json_tree; -}; - -class http_reply -{ -public: - static std::string to_string(protocol_status status) - { - typedef std::unordered_map status_map; - static const status_map status_strings - { - { protocol_status::switching, "HTTP/1.1 101 Switching Protocols\r\n" }, - { protocol_status::ok, "HTTP/1.0 200 OK\r\n" }, - { protocol_status::created, "HTTP/1.0 201 Created\r\n" }, - { protocol_status::accepted, "HTTP/1.0 202 Accepted\r\n" }, - { protocol_status::no_content, "HTTP/1.0 204 No Content\r\n" }, - { protocol_status::multiple_choices, "HTTP/1.0 300 Multiple Choices\r\n" }, - { protocol_status::moved_permanently, "HTTP/1.0 301 Moved Permanently\r\n" }, - { protocol_status::moved_temporarily, "HTTP/1.0 302 Moved Temporarily\r\n" }, - { protocol_status::not_modified, "HTTP/1.0 304 Not Modified\r\n" }, - { protocol_status::bad_request, "HTTP/1.0 400 Bad Request\r\n" }, - { protocol_status::unauthorized, "HTTP/1.0 401 Unauthorized\r\n" }, - { protocol_status::forbidden, "HTTP/1.0 403 Forbidden\r\n" }, - { protocol_status::not_found, "HTTP/1.0 404 Not Found\r\n" }, - { protocol_status::internal_server_error, "HTTP/1.0 500 Internal Server Error\r\n" }, - { protocol_status::not_implemented, "HTTP/1.0 501 Not Implemented\r\n" }, - { protocol_status::bad_gateway, "HTTP/1.0 502 Bad Gateway\r\n" }, - { protocol_status::service_unavailable, "HTTP/1.0 503 Service Unavailable\r\n" } - }; - - const auto it = status_strings.find(status); - return it == status_strings.end() ? std::string{} : it->second; - } - - static std::string generate(protocol_status status, std::string mime_type, - size_t content_length, bool keep_alive) - { - static const size_t max_date_time_length = 32; - std::array time_buffer{}; - const auto current_time = std::time(nullptr); - - // BUGBUG: std::gmtime may not be thread safe. - std::strftime(time_buffer.data(), time_buffer.size(), - "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(¤t_time)); - - std::stringstream response; - response - << to_string(status) - << "Date: " << time_buffer.data() << "\r\n" - << "Accept-Ranges: none\r\n" - << "Connection: " << (keep_alive ? "keep-alive" : "close") - << "\r\n"; - - if (!mime_type.empty()) - response << "Content-Type: " << mime_type << "\r\n"; - - if (content_length > 0) - response << "Content-Length: " << content_length << "\r\n"; - - response << "\r\n"; - return response.str(); - } - - static std::string generate_upgrade(const std::string& key_response, - const std::string& protocol) - { - std::stringstream response; - response - << to_string(protocol_status::switching) - << "Upgrade: websocket\r\n" - << "Connection: Upgrade" << "\r\n"; - - if (!protocol.empty()) - response << protocol << "\r\n"; - - response << "Sec-WebSocket-Accept: " << key_response << "\r\n\r\n"; - return response.str(); - } - - protocol_status status; - string_map headers; - std::string content; -}; - } // namespace http } // namespace server } // namespace libbitcoin diff --git a/include/bitcoin/server/web/http/http_reply.hpp b/include/bitcoin/server/web/http/http_reply.hpp new file mode 100644 index 00000000..5c661680 --- /dev/null +++ b/include/bitcoin/server/web/http/http_reply.hpp @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_HTTP_REPLY_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_HTTP_REPLY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +// TODO: move implementation to cpp. +class BCS_API http_reply +{ +public: + static std::string to_string(protocol_status status) + { + typedef std::unordered_map status_map; + static const status_map status_strings + { + { protocol_status::switching, "HTTP/1.1 101 Switching Protocols\r\n" }, + { protocol_status::ok, "HTTP/1.0 200 OK\r\n" }, + { protocol_status::created, "HTTP/1.0 201 Created\r\n" }, + { protocol_status::accepted, "HTTP/1.0 202 Accepted\r\n" }, + { protocol_status::no_content, "HTTP/1.0 204 No Content\r\n" }, + { protocol_status::multiple_choices, "HTTP/1.0 300 Multiple Choices\r\n" }, + { protocol_status::moved_permanently, "HTTP/1.0 301 Moved Permanently\r\n" }, + { protocol_status::moved_temporarily, "HTTP/1.0 302 Moved Temporarily\r\n" }, + { protocol_status::not_modified, "HTTP/1.0 304 Not Modified\r\n" }, + { protocol_status::bad_request, "HTTP/1.0 400 Bad Request\r\n" }, + { protocol_status::unauthorized, "HTTP/1.0 401 Unauthorized\r\n" }, + { protocol_status::forbidden, "HTTP/1.0 403 Forbidden\r\n" }, + { protocol_status::not_found, "HTTP/1.0 404 Not Found\r\n" }, + { protocol_status::internal_server_error, "HTTP/1.0 500 Internal Server Error\r\n" }, + { protocol_status::not_implemented, "HTTP/1.0 501 Not Implemented\r\n" }, + { protocol_status::bad_gateway, "HTTP/1.0 502 Bad Gateway\r\n" }, + { protocol_status::service_unavailable, "HTTP/1.0 503 Service Unavailable\r\n" } + }; + + const auto it = status_strings.find(status); + return it == status_strings.end() ? std::string{} : it->second; + } + + static std::string generate(protocol_status status, std::string mime_type, + size_t content_length, bool keep_alive) + { + static const size_t max_date_time_length = 32; + std::array time_buffer; + const auto current_time = std::time(nullptr); + + // BUGBUG: std::gmtime may not be thread safe. + std::strftime(time_buffer.data(), time_buffer.size(), + "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(¤t_time)); + + std::stringstream response; + response + << to_string(status) + << "Date: " << time_buffer.data() << "\r\n" + << "Accept-Ranges: none\r\n" + << "Connection: " << (keep_alive ? "keep-alive" : "close") + << "\r\n"; + + if (!mime_type.empty()) + response << "Content-Type: " << mime_type << "\r\n"; + + if (content_length > 0) + response << "Content-Length: " << content_length << "\r\n"; + + response << "\r\n"; + return response.str(); + } + + static std::string generate_upgrade(const std::string& key_response, + const std::string& protocol) + { + std::stringstream response; + response + << to_string(protocol_status::switching) + << "Upgrade: websocket\r\n" + << "Connection: Upgrade" << "\r\n"; + + if (!protocol.empty()) + response << protocol << "\r\n"; + + response << "Sec-WebSocket-Accept: " << key_response << "\r\n\r\n"; + return response.str(); + } + + protocol_status status; + string_map headers; + std::string content; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/http_request.hpp b/include/bitcoin/server/web/http/http_request.hpp new file mode 100644 index 00000000..4c75468d --- /dev/null +++ b/include/bitcoin/server/web/http/http_request.hpp @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_HTTP_REQUEST_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_HTTP_REQUEST_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +// TODO: move implementation to cpp. +class BCS_API http_request +{ +public: + std::string find(const string_map& haystack, + const std::string& needle) const + { + const auto it = haystack.find(needle); + return it == haystack.end() ? std::string{} : it->second; + } + + std::string header(std::string header) const + { + boost::algorithm::to_lower(header); + return find(headers, header); + } + + std::string parameter(std::string parameter) const + { + boost::algorithm::to_lower(parameter); + return find(parameters, parameter); + } + + std::string method; + std::string uri; + std::string protocol; + double protocol_version; + size_t message_length; + size_t content_length; + string_map headers; + string_map parameters; + bool upgrade_request; + bool json_rpc; + boost::property_tree::ptree json_tree; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/json_string.hpp b/include/bitcoin/server/web/http/json_string.hpp index 00efb2bb..56304f24 100644 --- a/include/bitcoin/server/web/http/json_string.hpp +++ b/include/bitcoin/server/web/http/json_string.hpp @@ -21,27 +21,28 @@ #include #include +#include #include #include -#include -#include namespace libbitcoin { namespace server { -namespace web { +namespace http { // Object to JSON converters. //----------------------------------------------------------------------------- -std::string to_json(const boost::property_tree::ptree& tree); -std::string to_json(uint64_t height, uint32_t id); -std::string to_json(const code& code, uint32_t id); -std::string to_json(const chain::header& header, uint32_t id); -std::string to_json(const chain::block& block, uint32_t id); -std::string to_json(const chain::block& block, uint32_t height, uint32_t id); -std::string to_json(const chain::transaction& transaction, uint32_t id); +BCS_API std::string to_json(const boost::property_tree::ptree& tree); +BCS_API std::string to_json(uint64_t height, uint32_t id); +BCS_API std::string to_json(const code& code, uint32_t id); +BCS_API std::string to_json(const chain::header& header, uint32_t id); +BCS_API std::string to_json(const chain::block& block, uint32_t id); +BCS_API std::string to_json(const chain::block& block, uint32_t height, + uint32_t id); +BCS_API std::string to_json(const chain::transaction& transaction, + uint32_t id); -} // namespace web +} // namespace http } // namespace server } // namespace libbitcoin diff --git a/include/bitcoin/server/web/http/manager.hpp b/include/bitcoin/server/web/http/manager.hpp index fc71e992..1583dd00 100644 --- a/include/bitcoin/server/web/http/manager.hpp +++ b/include/bitcoin/server/web/http/manager.hpp @@ -20,16 +20,22 @@ #define LIBBITCOIN_SERVER_WEB_HTTP_MANAGER_HPP #include +#include +#include #include #include #include +#include +#include #include +#include +#include namespace libbitcoin { namespace server { namespace http { -class manager +class BCS_API manager { public: class task diff --git a/include/bitcoin/server/web/http/protocol_status.hpp b/include/bitcoin/server/web/http/protocol_status.hpp new file mode 100644 index 00000000..89a2a14a --- /dev/null +++ b/include/bitcoin/server/web/http/protocol_status.hpp @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_PROTOCOL_STATUS_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_PROTOCOL_STATUS_HPP + +#include + +namespace libbitcoin { +namespace server { +namespace http { + +enum class protocol_status : uint16_t +{ + switching = 101, + ok = 200, + created = 201, + accepted = 202, + no_content = 204, + multiple_choices = 300, + moved_permanently = 301, + moved_temporarily = 302, + not_modified = 304, + bad_request = 400, + unauthorized = 401, + forbidden = 403, + not_found = 404, + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503 +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/socket.hpp b/include/bitcoin/server/web/http/socket.hpp index f6313f4d..87fa64fe 100644 --- a/include/bitcoin/server/web/http/socket.hpp +++ b/include/bitcoin/server/web/http/socket.hpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -35,20 +37,23 @@ #ifdef WITH_MBEDTLS extern "C" { -int https_random(void*, uint8_t* buffer, size_t length); +uint32_t https_random(void*, uint8_t* buffer, size_t length); } #endif namespace libbitcoin { namespace server { +// TODO: eliminate server_node dependency, for move to protocol. class server_node; +namespace http { + class BCS_API socket : public bc::protocol::zmq::worker { public: - typedef http::connection_ptr connection_ptr; + typedef connection_ptr connection_ptr; // Tracks websocket queries via the query_work_map. Used for // matching websocket client requests to zmq query responses. @@ -74,10 +79,10 @@ class BCS_API socket }; typedef std::function encode_handler; + const std::string&, uint32_t)> encode_handler; - typedef std::function decode_handler; + typedef std::function + decode_handler; // Handles translation of incoming JSON to zmq protocol methods and // converting the result back to JSON for web clients. @@ -97,7 +102,7 @@ class BCS_API socket /// Construct a socket class. socket(bc::protocol::zmq::authenticator& authenticator, server_node& node, - bool secure, const std::string& domain); + bool secure); /// Start the service. bool start() override; @@ -150,14 +155,14 @@ class BCS_API socket mutable upgrade_mutex correlation_lock_; private: - static bool handle_event(connection_ptr connection, - const http::event event, const void* data); + static bool handle_event(connection_ptr connection, event event, + const void* data); - http::manager::ptr manager_; - const std::string domain_; + manager::ptr manager_; const boost::filesystem::path document_root_; }; +} // namespace http } // namespace server } // namespace libbitcoin diff --git a/include/bitcoin/server/web/http/ssl.hpp b/include/bitcoin/server/web/http/ssl.hpp new file mode 100644 index 00000000..9b9704e3 --- /dev/null +++ b/include/bitcoin/server/web/http/ssl.hpp @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_SSL_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_SSL_HPP + +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +struct ssl +{ + bool enabled; + std::string hostname; +#ifdef WITH_MBEDTLS + mbedtls_ssl_context context; + mbedtls_ssl_config configuration; + mbedtls_pk_context key; + mbedtls_x509_crt certificate; + mbedtls_x509_crt ca_certificate; +#endif +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/utilities.hpp b/include/bitcoin/server/web/http/utilities.hpp index 6fbf3542..25c7faa0 100644 --- a/include/bitcoin/server/web/http/utilities.hpp +++ b/include/bitcoin/server/web/http/utilities.hpp @@ -19,13 +19,16 @@ #ifndef LIBBITCOIN_SERVER_WEB_HTTP_UTILITIES_HPP #define LIBBITCOIN_SERVER_WEB_HTTP_UTILITIES_HPP -namespace libbitcoin { -namespace server { -namespace http { - #include #include +#include #include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { #ifdef _MSC_VER #define last_error() GetLastError() @@ -41,18 +44,18 @@ namespace http { #ifdef WITH_MBEDTLS std::string mbedtls_error_string(int32_t error); - sha1_hash sha1(const std::string& input); + ////short_hash sha1(const std::string& input); #define mbedtls_would_block(value) \ (value == MBEDTLS_ERR_SSL_WANT_READ || \ value == MBEDTLS_ERR_SSL_WANT_WRITE) #endif -std::string error_string(); -std::string to_string(websocket_op code); -std::string websocket_key_response(const std::string& websocket_key); -bool is_json_request(const std::string& header_value); -bool parse_http(http_request& out, const std::string& request); -std::string mime_type(const boost::filesystem::path& path); +BCS_API std::string error_string(); +BCS_API std::string op_to_string(websocket_op code); +BCS_API std::string websocket_key_response(const std::string& websocket_key); +BCS_API bool is_json_request(const std::string& header_value); +BCS_API bool parse_http(http_request& out, const std::string& request); +BCS_API std::string mime_type(const boost::filesystem::path& path); } // namespace http } // namespace server diff --git a/include/bitcoin/server/web/http/websocket_frame.hpp b/include/bitcoin/server/web/http/websocket_frame.hpp new file mode 100644 index 00000000..6af0b975 --- /dev/null +++ b/include/bitcoin/server/web/http/websocket_frame.hpp @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_WEBSOCKET_FRAME_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_FRAME_HPP + +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +// TODO: move implementation to cpp. +class BCS_API websocket_frame +{ +public: + websocket_frame(const uint8_t* data, size_t size) + { + from_data(data, size); + } + + static data_chunk to_header(size_t length, websocket_op code) + { + if (length < 0x7e) + { + return build_chunk( + { + to_array(uint8_t(0x80) | static_cast(code)), + to_array(static_cast(length)) + }); + } + else if (length < max_uint16) + { + return build_chunk( + { + to_array(uint8_t(0x80) | static_cast(code)), + to_array(uint8_t(0x7e)), + to_big_endian(static_cast(length)) + }); + } + else + { + return build_chunk( + { + to_array(uint8_t(0x80) | static_cast(code)), + to_array(uint8_t(0x7f)), + to_big_endian(static_cast(length)) + }); + } + } + + operator bool() const + { + return valid_; + } + + bool final() const + { + return (flags_ & 0x80) != 0; + } + + bool fragment() const + { + return !final() || op_code() == websocket_op::continuation; + } + + event event_type() const + { + return (flags_ & 0x08) ? event::websocket_control_frame : + event::websocket_frame; + } + + websocket_op op_code() const + { + return static_cast(flags_ & 0x0f); + } + + uint8_t flags() const + { + return flags_; + } + + size_t header_length() const + { + return header_; + } + + size_t data_length() const + { + return data_; + } + + size_t mask_length() const + { + return valid_ ? mask_ : 0; + } + +private: + void from_data(const uint8_t* data, size_t read_length) + { + static constexpr size_t prefix = 2; + static constexpr size_t prefix16 = prefix + sizeof(uint16_t); + static constexpr size_t prefix64 = prefix + sizeof(uint64_t); + + valid_ = false; + + // Invalid websocket frame (too small). + if (read_length < 2) + return; + + flags_ = data[0]; + header_ = 0; + data_ = 0; + + // Invalid websocket frame (unmasked). + if ((data[1] & 0x80) == 0) + return; + + const size_t length = (data[1] & 0x7f); + + if (read_length >= mask_ && length < 0x7e) + { + header_ = prefix + mask_; + data_ = length; + } + else if (read_length >= prefix16 + mask_ && length == 0x7e) + { + header_ = prefix16 + mask_; + data_ = from_big_endian(&data[prefix], + &data[prefix16]); + } + else if (read_length >= prefix64 + mask_) + { + header_ = prefix64 + mask_; + data_ = from_big_endian(&data[prefix], + &data[prefix64]); + } + + valid_ = true; + return; + } + +private: + static const size_t mask_ = 4; + + bool valid_; + uint8_t flags_; + size_t header_; + size_t data_; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/websocket_message.hpp b/include/bitcoin/server/web/http/websocket_message.hpp new file mode 100644 index 00000000..9406d351 --- /dev/null +++ b/include/bitcoin/server/web/http/websocket_message.hpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_WEBSOCKET_MESSAGE_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_MESSAGE_HPP + +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +struct BCS_API websocket_message +{ + const std::string& endpoint; + const uint8_t* data; + size_t size; + uint8_t flags; + websocket_op code; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/websocket_op.hpp b/include/bitcoin/server/web/http/websocket_op.hpp new file mode 100644 index 00000000..270e0fd4 --- /dev/null +++ b/include/bitcoin/server/web/http/websocket_op.hpp @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_WEBSOCKET_OP_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_OP_HPP + +#include + +namespace libbitcoin { +namespace server { +namespace http { + +enum class websocket_op : uint8_t +{ + continuation = 0, + text = 1, + binary = 2, + close = 8, + ping = 9, + pong = 10, +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/http/websocket_transfer.hpp b/include/bitcoin/server/web/http/websocket_transfer.hpp new file mode 100644 index 00000000..51d7c4ec --- /dev/null +++ b/include/bitcoin/server/web/http/websocket_transfer.hpp @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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_SERVER_WEB_HTTP_WEBSOCKET_TRANSFER_HPP +#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_TRANSFER_HPP + +#include +#include +#include +#include + +namespace libbitcoin { +namespace server { +namespace http { + +struct BCS_API websocket_transfer +{ + bool in_progress; + size_t offset; + size_t length; + size_t header_length; + data_chunk mask; + data_chunk data; +}; + +} // namespace http +} // namespace server +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/server/web/query_socket.hpp b/include/bitcoin/server/web/query_socket.hpp index 1865c471..48a7eed0 100644 --- a/include/bitcoin/server/web/query_socket.hpp +++ b/include/bitcoin/server/web/query_socket.hpp @@ -35,7 +35,7 @@ class server_node; // Submit queries and address subscriptions and receive address // notifications on a dedicated socket endpoint. class BCS_API query_socket - : public socket + : public http::socket { public: typedef std::shared_ptr ptr; diff --git a/include/bitcoin/server/web/transaction_socket.hpp b/include/bitcoin/server/web/transaction_socket.hpp index a9c536f0..45774ecb 100644 --- a/include/bitcoin/server/web/transaction_socket.hpp +++ b/include/bitcoin/server/web/transaction_socket.hpp @@ -35,7 +35,7 @@ class server_node; // This class is thread safe. // Subscribe to tx acceptances into the pool from a dedicated socket endpoint. class BCS_API transaction_socket - : public socket + : public http::socket { public: typedef std::shared_ptr ptr; diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index e4f66b3c..1a6bfd03 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -34,14 +34,14 @@ namespace server { using namespace std::placeholders; using namespace bc::chain; using namespace bc::protocol; +using namespace http; using role = zmq::socket::role; -static const auto domain = "block"; static constexpr auto poll_interval_milliseconds = 100u; block_socket::block_socket(zmq::authenticator& authenticator, server_node& node, bool secure) - : socket(authenticator, node, secure, domain) + : http::socket(authenticator, node, secure) { } @@ -63,8 +63,7 @@ void block_socket::work() if (!started(start_websocket_handler())) { LOG_ERROR(LOG_SERVER) - << "Failed to start " << security_ << " " << domain - << " websocket handler"; + << "Failed to start " << security_ << " block websocket handler."; return; } @@ -92,8 +91,7 @@ void block_socket::work() if (!websocket_stop) LOG_ERROR(LOG_SERVER) - << "Failed to stop " << security_ - << " block websocket handler"; + << "Failed to stop " << security_ << " block websocket handler."; finished(sub_stop && websocket_stop); } @@ -127,7 +125,7 @@ bool block_socket::handle_block(zmq::socket& subscriber) // Format and send transaction to websocket subscribers. const auto block = block::factory(block_data, true); - broadcast(web::to_json(block, height, sequence)); + broadcast(to_json(block, height, sequence)); LOG_VERBOSE(LOG_SERVER) << "Broadcasted " << security_ << " socket block [" diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index d5441d94..dc5d362d 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -28,16 +28,16 @@ namespace libbitcoin { namespace server { -static const auto domain = "heartbeat"; static constexpr auto poll_interval_milliseconds = 100u; using namespace bc::config; using namespace bc::protocol; +using namespace http; using role = zmq::socket::role; heartbeat_socket::heartbeat_socket(zmq::authenticator& authenticator, server_node& node, bool secure) - : socket(authenticator, node, secure, domain) + : http::socket(authenticator, node, secure) { } @@ -59,8 +59,8 @@ void heartbeat_socket::work() if (!started(start_websocket_handler())) { LOG_ERROR(LOG_SERVER) - << "Failed to start " << security_ << " " << domain - << " websocket handler"; + << "Failed to start " << security_ + << " heartbeat websocket handler."; return; } @@ -89,7 +89,7 @@ void heartbeat_socket::work() if (!websocket_stop) LOG_ERROR(LOG_SERVER) << "Failed to stop " << security_ - << " heartbeat websocket handler"; + << " heartbeat websocket handler."; finished(sub_stop && websocket_stop); } @@ -119,7 +119,7 @@ bool heartbeat_socket::handle_heartbeat(zmq::socket& subscriber) response.dequeue(sequence); response.dequeue(height); - broadcast(web::to_json(height, sequence)); + broadcast(to_json(height, sequence)); LOG_VERBOSE(LOG_SERVER) << "Broadcasted " << security_ << " socket heartbeat [" << height diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index b7af4c44..53619043 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include namespace libbitcoin { namespace server { diff --git a/src/web/http/json_string.cpp b/src/web/http/json_string.cpp index 0140b68f..bb0782e5 100644 --- a/src/web/http/json_string.cpp +++ b/src/web/http/json_string.cpp @@ -28,7 +28,7 @@ namespace libbitcoin { namespace server { -namespace web { +namespace http { using namespace boost::property_tree; @@ -96,6 +96,6 @@ std::string to_json(const chain::transaction& transaction, uint32_t id) return to_json(property_tree(config::transaction(transaction), true), id); } -} // namespace web +} // namespace http } // namespace server } // namespace libbitcoin diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index a47033ee..8d07f5eb 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -20,18 +20,24 @@ #include #include +#include #include -#include +#include +#include #include #include #include +#include #include +#include +#include +#include #ifdef WITH_MBEDTLS extern "C" { // Random data generator used by mbedtls for SSL. -int https_random(void*, uint8_t* buffer, size_t length); +uint32_t https_random(void*, uint8_t* buffer, size_t length); } #endif @@ -905,15 +911,17 @@ bool manager::handle_websocket(connection_ptr connection) }; // Possible TODO: If the opcode is a ping, send response here. - if (message.code != http::websocket_op::close) + if (message.code != websocket_op::close) + { LOG_DEBUG(LOG_SERVER_HTTP) - << "Unhandled websocket op: " << to_string(message.code); + << "Unhandled websocket op: " << op_to_string(message.code); + } // Call user handler for control frames. const auto status = handler_(connection, event_type, &message); // Returning false here causes the connection to be removed. - if (message.code == http::websocket_op::close) + if (message.code == websocket_op::close) return false; return status; diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp index cae71071..dfabfe65 100644 --- a/src/web/http/socket.cpp +++ b/src/web/http/socket.cpp @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -26,14 +27,16 @@ #include #include #include +#include #include #include #include +#include #ifdef WITH_MBEDTLS extern "C" { -int https_random(void*, uint8_t* buffer, size_t length) +uint32_t https_random(void*, uint8_t* buffer, size_t length) { bc::data_chunk random(length); bc::pseudo_random_fill(random); @@ -45,6 +48,7 @@ int https_random(void*, uint8_t* buffer, size_t length) namespace libbitcoin { namespace server { +namespace http { using namespace asio; using namespace bc::chain; @@ -57,9 +61,9 @@ using role = zmq::socket::role; // Local class. class task_sender - : public http::manager::task + : public manager::task { - public: +public: task_sender(connection_ptr connection, const std::string& data) : connection_(connection), data_(data) { @@ -92,7 +96,7 @@ class task_sender return connection_; } - private: +private: connection_ptr connection_; const std::string data_; }; @@ -100,12 +104,12 @@ class task_sender // TODO: eliminate the use of weak and untyped pointer to pass self here. // static // Callback made internally via socket::poll on the web socket thread. -bool socket::handle_event(connection_ptr connection, const http::event event, +bool socket::handle_event(connection_ptr connection, event event, const void* data) { switch (event) { - case http::event::accepted: + case event::accepted: { // This connection is newly accepted and is either an HTTP // JSON-RPC connection, or an already upgraded websocket. @@ -123,7 +127,7 @@ bool socket::handle_event(connection_ptr connection, const http::event event, break; } - case http::event::json_rpc: + case event::json_rpc: { // Process new incoming user json_rpc request. Returning // false here will cause this connection to be closed. @@ -156,7 +160,7 @@ bool socket::handle_event(connection_ptr connection, const http::event event, break; } - case http::event::websocket_frame: + case event::websocket_frame: { // Process new incoming user websocket data. Returning false // will cause this connection to be closed. @@ -200,7 +204,7 @@ bool socket::handle_event(connection_ptr connection, const http::event event, break; } - case http::event::closing: + case event::closing: { // This connection is going away after this handling. auto instance = static_cast(connection->user_data()); @@ -219,9 +223,9 @@ bool socket::handle_event(connection_ptr connection, const http::event event, } // No specific handling required for other events. - case http::event::read: - case http::event::error: - case http::event::websocket_control_frame: + case event::read: + case event::error: + case event::websocket_control_frame: default: break; } @@ -230,7 +234,7 @@ bool socket::handle_event(connection_ptr connection, const http::event event, } socket::socket(zmq::authenticator& authenticator, server_node& node, - bool secure, const std::string& domain) + bool secure) : worker(priority(node.server_settings().priority)), authenticator_(authenticator), secure_(secure), @@ -238,7 +242,6 @@ socket::socket(zmq::authenticator& authenticator, server_node& node, server_settings_(node.server_settings()), protocol_settings_(node.protocol_settings()), sequence_(0), - domain_(domain), manager_(nullptr), document_root_(node.server_settings().websockets_root) { @@ -280,10 +283,10 @@ bool socket::start() void socket::handle_websockets() { - http::bind_options options; + bind_options options; // This starts up the listener for the socket. - manager_ = std::make_shared(secure_, &socket::handle_event, + manager_ = std::make_shared(secure_, &socket::handle_event, document_root_); if (!manager_ || !manager_->initialize()) @@ -414,7 +417,7 @@ void socket::notify_query_work(connection_ptr connection, const bc::code& ec) { http_reply reply; - const auto error = web::to_json(ec, id); + const auto error = to_json(ec, id); const auto response = reply.generate(status, {}, error.size(), false); LOG_VERBOSE(LOG_SERVER) << error + response; connection->write(error + response); @@ -512,5 +515,6 @@ void socket::broadcast(const std::string& json) std::for_each(work_.begin(), work_.end(), sender); } +} // namespace http } // namespace server } // namespace libbitcoin diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index e103be3c..0c3bfa19 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -16,24 +16,25 @@ * 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 #include #include #include - -// TODO: determine why this include must follow http.hpp. -#include +#include +#include namespace libbitcoin { namespace server { namespace http { -// TODO: std::strerror is not required to be thread safe. +// BUGBUG: std::strerror is not required to be thread safe. std::string error_string() { #ifdef _MSC_VER @@ -64,23 +65,23 @@ std::string mbedtls_error_string(int32_t error) return { data.data() }; } -sha1_hash sha1(const std::string& input) -{ - sha1_hash out; - mbedtls_sha1_context context; - mbedtls_sha1_init(&context); - - const auto source = reinterpret_cast(input.c_str()); - if ((mbedtls_sha1_starts_ret(&context) == 0) && - (mbedtls_sha1_update_ret(&context, source, input.size()) == 0) && - (mbedtls_sha1_finish_ret(&context, out.data()) == 0)) - return out; - - return {}; -} +////short_hash sha1(const std::string& input) +////{ +//// short_hash out; +//// mbedtls_sha1_context context; +//// mbedtls_sha1_init(&context); +//// +//// const auto source = reinterpret_cast(input.data()); +//// if ((mbedtls_sha1_starts_ret(&context) == 0) && +//// (mbedtls_sha1_update_ret(&context, source, input.size()) == 0) && +//// (mbedtls_sha1_finish_ret(&context, out.data()) == 0)) +//// return out; +//// +//// return {}; +////} #endif -std::string to_string(websocket_op code) +std::string op_to_string(websocket_op code) { static const std::string unknown = "unknown"; static const std::unordered_map opcode_map @@ -104,25 +105,25 @@ std::string websocket_key_response(const std::string& websocket_key) static const std::string rfc6455_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -#ifdef WITH_MBEDTLS - // The buffer is a base64 encoded sha1 hash (20 bytes in length). - static constexpr size_t key_buffer_length = 64; - std::array buffer; - - size_t processed_length = 0; - const auto input = websocket_key + rfc6455_guid; - const auto hash = sha1(input); - - if ((mbedtls_base64_encode(buffer.data(), buffer.size(), &processed_length, - hash.data(), sha1_hash_length) != 0) || (processed_length == 0)) - return {}; - - return { reinterpret_cast(buffer.data()), processed_length }; -#else +// TODO: why are there two implementations of this? +////#ifdef WITH_MBEDTLS +//// // The buffer is a base64 encoded sha1 hash (20 bytes in length). +//// static constexpr size_t key_buffer_length = 64; +//// std::array buffer; +//// +//// size_t processed_length = 0; +//// const auto hash = sha1(websocket_key + rfc6455_guid); +//// +//// if ((mbedtls_base64_encode(buffer.data(), buffer.size(), &processed_length, +//// hash.data(), hash.size()) != 0) || (processed_length == 0)) +//// return {}; +//// +//// return { reinterpret_cast(buffer.data()), processed_length }; +////#else const auto input = websocket_key + rfc6455_guid; const data_chunk input_data{ input.begin(), input.end() }; return encode_base64(bc::sha1_hash(input_data)); -#endif +////#endif } bool is_json_request(const std::string& header_value) diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 6150b893..e96b3df8 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -30,14 +30,14 @@ namespace server { using namespace bc::config; using namespace bc::machine; using namespace bc::protocol; +using namespace http; using role = zmq::socket::role; -static const auto domain = "query"; static constexpr auto poll_interval_milliseconds = 100u; query_socket::query_socket(zmq::authenticator& authenticator, server_node& node, bool secure) - : socket(authenticator, node, secure, domain) + : http::socket(authenticator, node, secure) { // JSON to ZMQ request encoders. //------------------------------------------------------------------------- @@ -70,7 +70,7 @@ query_socket::query_socket(zmq::authenticator& authenticator, data_source istream(data); istream_reader source(istream); const auto height = source.read_4_bytes_little_endian(); - send(connection, web::to_json(height, id)); + send(connection, to_json(height, id)); }; const auto decode_transaction = [this, &node](const data_chunk& data, @@ -80,7 +80,7 @@ query_socket::query_socket(zmq::authenticator& authenticator, node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); const auto transaction = chain::transaction::factory(data, true, witness); - send(connection, web::to_json(transaction, id)); + send(connection, to_json(transaction, id)); }; const auto decode_block = [this, &node](const data_chunk& data, @@ -89,14 +89,14 @@ query_socket::query_socket(zmq::authenticator& authenticator, const auto witness = chain::script::is_enabled( node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); const auto block = chain::block::factory(data, witness); - send(connection, web::to_json(block, id)); + send(connection, to_json(block, id)); }; const auto decode_block_header = [this](const data_chunk& data, const uint32_t id,connection_ptr connection) { const auto header = chain::header::factory(data, true); - send(connection, web::to_json(header, id)); + send(connection, to_json(header, id)); }; handlers_["getblockcount"] = handlers @@ -196,8 +196,7 @@ void query_socket::work() if (!websocket_stop) LOG_ERROR(LOG_SERVER) - << "Failed to stop " << security_ - << " query websocket handler"; + << "Failed to stop " << security_ << " query websocket handler."; finished(query_stop && dealer_stop && websocket_stop); } @@ -300,7 +299,7 @@ bool query_socket::handle_query(zmq::socket& dealer) ec = source.read_error_code(); if (ec) { - send(work.connection, web::to_json(ec, id)); + send(work.connection, to_json(ec, id)); return true; } @@ -308,7 +307,7 @@ bool query_socket::handle_query(zmq::socket& dealer) if (handler == handlers_.end()) { static constexpr auto error = bc::error::not_implemented; - send(work.connection, web::to_json(error, id)); + send(work.connection, to_json(error, id)); return true; } diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index 027db089..cc359831 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -33,14 +33,14 @@ using namespace std::placeholders; using namespace bc::chain; using namespace bc::message; using namespace bc::protocol; +using namespace http; using role = zmq::socket::role; -static const auto domain = "transaction"; static constexpr auto poll_interval_milliseconds = 100u; transaction_socket::transaction_socket(zmq::authenticator& authenticator, server_node& node, bool secure) - : socket(authenticator, node, secure, domain) + : http::socket(authenticator, node, secure) { } @@ -62,8 +62,8 @@ void transaction_socket::work() if (!started(start_websocket_handler())) { LOG_ERROR(LOG_SERVER) - << "Failed to start " << security_ << " " << domain - << " websocket handler"; + << "Failed to start " << security_ + << " transaction websocket handler."; return; } @@ -92,7 +92,7 @@ void transaction_socket::work() if (!websocket_stop) LOG_ERROR(LOG_SERVER) << "Failed to stop " << security_ - << " transaction websocket handler"; + << " transaction websocket handler."; finished(sub_stop && websocket_stop); } @@ -125,7 +125,7 @@ bool transaction_socket::handle_transaction(zmq::socket& subscriber) chain::transaction tx; tx.from_data(transaction_data, true, true); - broadcast(web::to_json(tx, sequence)); + broadcast(to_json(tx, sequence)); LOG_VERBOSE(LOG_SERVER) << "Broadcasted " << security_ << " socket tx [" From 7ff0872a425f6e188a498cc281b67327d6d87e2f Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 23 Oct 2018 04:53:08 -0700 Subject: [PATCH 17/25] Don't hold zmq authenticator in sockets, simplify socket. --- include/bitcoin/server/web/block_socket.hpp | 4 +- .../bitcoin/server/web/heartbeat_socket.hpp | 4 +- include/bitcoin/server/web/http/socket.hpp | 66 ++++++++----------- include/bitcoin/server/web/query_socket.hpp | 4 +- .../bitcoin/server/web/transaction_socket.hpp | 4 +- src/web/block_socket.cpp | 8 +-- src/web/heartbeat_socket.cpp | 8 +-- src/web/http/socket.cpp | 10 ++- src/web/query_socket.cpp | 12 ++-- src/web/transaction_socket.cpp | 6 +- 10 files changed, 55 insertions(+), 71 deletions(-) diff --git a/include/bitcoin/server/web/block_socket.hpp b/include/bitcoin/server/web/block_socket.hpp index 828d296d..8ba95ca9 100644 --- a/include/bitcoin/server/web/block_socket.hpp +++ b/include/bitcoin/server/web/block_socket.hpp @@ -37,8 +37,8 @@ class BCS_API block_socket typedef std::shared_ptr ptr; /// Construct a block socket service endpoint. - block_socket(bc::protocol::zmq::authenticator& authenticator, - server_node& node, bool secure); + block_socket(bc::protocol::zmq::context& context, server_node& node + , bool secure); protected: // Implement the service. diff --git a/include/bitcoin/server/web/heartbeat_socket.hpp b/include/bitcoin/server/web/heartbeat_socket.hpp index 948ff031..bb6fd6d8 100644 --- a/include/bitcoin/server/web/heartbeat_socket.hpp +++ b/include/bitcoin/server/web/heartbeat_socket.hpp @@ -41,8 +41,8 @@ class BCS_API heartbeat_socket typedef std::shared_ptr ptr; /// Construct a heartbeat socket service endpoint. - heartbeat_socket(bc::protocol::zmq::authenticator& authenticator, - server_node& node, bool secure); + heartbeat_socket(bc::protocol::zmq::context& context, server_node& node, + bool secure); protected: diff --git a/include/bitcoin/server/web/http/socket.hpp b/include/bitcoin/server/web/http/socket.hpp index 87fa64fe..9fab152a 100644 --- a/include/bitcoin/server/web/http/socket.hpp +++ b/include/bitcoin/server/web/http/socket.hpp @@ -53,67 +53,53 @@ class BCS_API socket : public bc::protocol::zmq::worker { public: - typedef connection_ptr connection_ptr; + /// Construct a socket class. + socket(bc::protocol::zmq::context& context, server_node& node, + bool secure); + + /// Start the service. + bool start() override; - // Tracks websocket queries via the query_work_map. Used for - // matching websocket client requests to zmq query responses. + size_t connection_count() const; + void add_connection(connection_ptr connection); + void remove_connection(connection_ptr connection); + void notify_query_work(connection_ptr connection, + const std::string& method, uint32_t id, const std::string& parameters); + +protected: + // Tracks websocket queries via the query_work_map. Used for matching + // websocket client requests to zmq query responses. struct query_work_item { - // Constructor provided for in-place construction. - query_work_item(uint32_t id, uint32_t correlation_id, - connection_ptr connection, const std::string& command, - const std::string& arguments) - : id(id), - correlation_id(correlation_id), - command(command), - arguments(arguments), - connection(connection) - { - } - uint32_t id; uint32_t correlation_id; + connection_ptr connection; std::string command; std::string arguments; - connection_ptr connection; }; - typedef std::function encode_handler; - - typedef std::function - decode_handler; - // Handles translation of incoming JSON to zmq protocol methods and // converting the result back to JSON for web clients. struct handlers { + typedef std::function encode_handler; + typedef std::function decode_handler; + std::string command; encode_handler encode; decode_handler decode; }; - typedef std::unordered_map query_work_map; - typedef std::unordered_map - connection_work_map; + typedef std::unordered_map handler_map; typedef std::unordered_map> query_correlation_map; - typedef std::unordered_map handler_map; - - /// Construct a socket class. - socket(bc::protocol::zmq::authenticator& authenticator, server_node& node, - bool secure); - - /// Start the service. - bool start() override; - size_t connection_count() const; - void add_connection(connection_ptr connection); - void remove_connection(connection_ptr connection); - void notify_query_work(connection_ptr connection, - const std::string& method, uint32_t id, const std::string& parameters); + typedef std::unordered_map query_work_map; + typedef std::unordered_map + connection_work_map; -protected: // Initialize the websocket event loop and start a thread to poll events. virtual bool start_websocket_handler(); @@ -133,7 +119,7 @@ class BCS_API socket void broadcast(const std::string& json); // The zmq socket operates on only this one thread. - bc::protocol::zmq::authenticator& authenticator_; + bc::protocol::zmq::context& context_; const bool secure_; const std::string security_; const bc::server::settings& server_settings_; diff --git a/include/bitcoin/server/web/query_socket.hpp b/include/bitcoin/server/web/query_socket.hpp index 48a7eed0..22c32361 100644 --- a/include/bitcoin/server/web/query_socket.hpp +++ b/include/bitcoin/server/web/query_socket.hpp @@ -41,8 +41,8 @@ class BCS_API query_socket typedef std::shared_ptr ptr; /// Construct a query socket service endpoint. - query_socket(bc::protocol::zmq::authenticator& authenticator, - server_node& node, bool secure); + query_socket(bc::protocol::zmq::context& context, server_node& node, + bool secure); protected: // Implement the socket. diff --git a/include/bitcoin/server/web/transaction_socket.hpp b/include/bitcoin/server/web/transaction_socket.hpp index 45774ecb..17753575 100644 --- a/include/bitcoin/server/web/transaction_socket.hpp +++ b/include/bitcoin/server/web/transaction_socket.hpp @@ -41,8 +41,8 @@ class BCS_API transaction_socket typedef std::shared_ptr ptr; /// Construct a transaction socket service endpoint. - transaction_socket(bc::protocol::zmq::authenticator& authenticator, - server_node& node, bool secure); + transaction_socket(bc::protocol::zmq::context& context, server_node& node, + bool secure); protected: diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index 1a6bfd03..a2ba86e4 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -39,15 +39,15 @@ using role = zmq::socket::role; static constexpr auto poll_interval_milliseconds = 100u; -block_socket::block_socket(zmq::authenticator& authenticator, - server_node& node, bool secure) - : http::socket(authenticator, node, secure) +block_socket::block_socket(zmq::context& context, server_node& node, + bool secure) + : http::socket(context, node, secure) { } void block_socket::work() { - zmq::socket sub(authenticator_, role::subscriber, protocol_settings_); + zmq::socket sub(context_, role::subscriber, protocol_settings_); const auto endpoint = zeromq_endpoint().to_local(); const auto ec = sub.connect(endpoint); diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index dc5d362d..24b84103 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -35,15 +35,15 @@ using namespace bc::protocol; using namespace http; using role = zmq::socket::role; -heartbeat_socket::heartbeat_socket(zmq::authenticator& authenticator, - server_node& node, bool secure) - : http::socket(authenticator, node, secure) +heartbeat_socket::heartbeat_socket(zmq::context& context, server_node& node, + bool secure) + : http::socket(context, node, secure) { } void heartbeat_socket::work() { - zmq::socket sub(authenticator_, role::subscriber, protocol_settings_); + zmq::socket sub(context_, role::subscriber, protocol_settings_); const auto endpoint = zeromq_endpoint().to_local(); const auto ec = sub.connect(endpoint); diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp index dfabfe65..f40b8b66 100644 --- a/src/web/http/socket.cpp +++ b/src/web/http/socket.cpp @@ -233,10 +233,9 @@ bool socket::handle_event(connection_ptr connection, event event, return true; } -socket::socket(zmq::authenticator& authenticator, server_node& node, - bool secure) +socket::socket(zmq::context& context, server_node& node, bool secure) : worker(priority(node.server_settings().priority)), - authenticator_(authenticator), + context_(context), secure_(secure), security_(secure ? "secure" : "public"), server_settings_(node.server_settings()), @@ -456,9 +455,8 @@ void socket::notify_query_work(connection_ptr connection, return; } - query_work_map.emplace(std::piecewise_construct, - std::forward_as_tuple(id), - std::forward_as_tuple(id, sequence_, connection, method, parameters)); + query_work_map.emplace(id, + query_work_item{ id, sequence_, connection, method, parameters }); // Encode request based on query work and send to query_websocket. zmq::message request; diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index e96b3df8..183bc187 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -35,9 +35,9 @@ using role = zmq::socket::role; static constexpr auto poll_interval_milliseconds = 100u; -query_socket::query_socket(zmq::authenticator& authenticator, - server_node& node, bool secure) - : http::socket(authenticator, node, secure) +query_socket::query_socket(zmq::context& context, server_node& node, + bool secure) + : http::socket(context, node, secure) { // JSON to ZMQ request encoders. //------------------------------------------------------------------------- @@ -130,8 +130,8 @@ query_socket::query_socket(zmq::authenticator& authenticator, void query_socket::work() { - zmq::socket dealer(authenticator_, role::dealer, protocol_settings_); - zmq::socket query_receiver(authenticator_, role::pair, protocol_settings_); + zmq::socket dealer(context_, role::dealer, protocol_settings_); + zmq::socket query_receiver(context_, role::pair, protocol_settings_); auto ec = query_receiver.bind(query_endpoint()); @@ -348,7 +348,7 @@ const std::shared_ptr query_socket::service() const void query_socket::handle_websockets() { // A zmq socket must remain on its single thread. - service_ = std::make_shared(authenticator_, role::pair, + service_ = std::make_shared(context_, role::pair, protocol_settings_); // Hold a reference to this service_ socket member by this thread diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index cc359831..bc645f73 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -38,15 +38,15 @@ using role = zmq::socket::role; static constexpr auto poll_interval_milliseconds = 100u; -transaction_socket::transaction_socket(zmq::authenticator& authenticator, +transaction_socket::transaction_socket(zmq::context& context, server_node& node, bool secure) - : http::socket(authenticator, node, secure) + : http::socket(context, node, secure) { } void transaction_socket::work() { - zmq::socket sub(authenticator_, role::subscriber, protocol_settings_); + zmq::socket sub(context_, role::subscriber, protocol_settings_); const auto endpoint = zeromq_endpoint().to_local(); const auto ec = sub.connect(endpoint); From 4f9b52325838491d8665763ad3263ebca1c1b0c3 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 23 Oct 2018 04:58:01 -0700 Subject: [PATCH 18/25] Simplify websocket_frame. --- include/bitcoin/server/web/http/websocket_frame.hpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/include/bitcoin/server/web/http/websocket_frame.hpp b/include/bitcoin/server/web/http/websocket_frame.hpp index 6af0b975..57733295 100644 --- a/include/bitcoin/server/web/http/websocket_frame.hpp +++ b/include/bitcoin/server/web/http/websocket_frame.hpp @@ -35,6 +35,7 @@ class BCS_API websocket_frame { public: websocket_frame(const uint8_t* data, size_t size) + : valid_(false), flags_(0), header_(0), data_(0) { from_data(data, size); } @@ -122,20 +123,12 @@ class BCS_API websocket_frame static constexpr size_t prefix16 = prefix + sizeof(uint16_t); static constexpr size_t prefix64 = prefix + sizeof(uint64_t); - valid_ = false; - // Invalid websocket frame (too small). - if (read_length < 2) - return; - - flags_ = data[0]; - header_ = 0; - data_ = 0; - // Invalid websocket frame (unmasked). - if ((data[1] & 0x80) == 0) + if (read_length < 2 || (data[1] & 0x80) == 0) return; + flags_ = data[0]; const size_t length = (data[1] & 0x7f); if (read_length >= mask_ && length < 0x7e) From c07573abf3ee9fdce1bd76be95844ef0c6e95ee2 Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Fri, 19 Oct 2018 18:22:14 -0500 Subject: [PATCH 19/25] Websocket updates. --- include/bitcoin/server/web/http/utilities.hpp | 3 +- src/server_node.cpp | 4 +- src/web/http/connection.cpp | 33 +++++++++++----- src/web/http/manager.cpp | 2 +- src/web/http/utilities.cpp | 39 +++---------------- 5 files changed, 33 insertions(+), 48 deletions(-) diff --git a/include/bitcoin/server/web/http/utilities.hpp b/include/bitcoin/server/web/http/utilities.hpp index 25c7faa0..43b46aef 100644 --- a/include/bitcoin/server/web/http/utilities.hpp +++ b/include/bitcoin/server/web/http/utilities.hpp @@ -38,13 +38,14 @@ namespace http { #ifdef _MSC_VER #define would_block(value) (value == WSAEWOULDBLOCK) + #define WOULD_BLOCK WSAEWOULDBLOCK #else #define would_block(value) (value == EAGAIN || value == EWOULDBLOCK) + #define WOULD_BLOCK EWOULDBLOCK #endif #ifdef WITH_MBEDTLS std::string mbedtls_error_string(int32_t error); - ////short_hash sha1(const std::string& input); #define mbedtls_would_block(value) \ (value == MBEDTLS_ERR_SSL_WANT_READ || \ value == MBEDTLS_ERR_SSL_WANT_WRITE) diff --git a/src/server_node.cpp b/src/server_node.cpp index a87af54b..5966a743 100644 --- a/src/server_node.cpp +++ b/src/server_node.cpp @@ -117,10 +117,10 @@ void server_node::handle_running(const code& , result_handler handler) // The stop handler is already stopped but the authenticator context gets // started, allowing services to stop. The registration of services with // the stop handler invokes the registered handlers immediately, invoking - // stop o nthe services. The services are running and don't stop... + // stop on the services. The services are running and don't stop... // notification_worker, query_service and authenticator service. // The authenticator is already stopped (before it started) so there will - // be no context stop to stop the services, specifically the relays. + // be no context to stop the services, specifically the relays. if (!start_services()) { handler(error::operation_failed); diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 53619043..1e663886 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -21,6 +21,12 @@ #include #include #include +/* #include */ + +/* // Explicitly use std::placeholders here for usage internally to the */ +/* // boost parsing helpers included from json_parser.hpp. */ +/* // See: https://svn.boost.org/trac10/ticket/12621 */ +/* using namespace std::placeholders; */ #include #include #include @@ -102,14 +108,19 @@ bool connection::closed() const int32_t connection::read() { +#ifdef WIN32 + // reinterpret_cast required for Win32, otherwise nop. + auto data = reinterpret_cast(read_buffer_.data()); +#else + auto data = read_buffer_.data(); +#endif + #ifdef WITH_MBEDTLS - bytes_read_ = ssl_context_.enabled ? + bytes_read_ = (ssl_context_.enabled ? mbedtls_ssl_read(&ssl_context_.context, data, maximum_read_length) : - recv(socket_, data, maximum_read_length, 0) + recv(socket_, data, maximum_read_length, 0)); #else - // reinterpret_cast required for Win32, otherwise nop. - bytes_read_ = recv(socket_, reinterpret_cast(read_buffer_.data()), - maximum_read_length, 0); + bytes_read_ = recv(socket_, data, maximum_read_length, 0); #endif return bytes_read_; } @@ -152,20 +163,22 @@ int32_t connection::unbuffered_write(const uint8_t* data, size_t length) return send(socket_, reinterpret_cast(data), static_cast(length), 0); #else - return send(socket_, data, length, 0); + return static_cast(send(socket_, data, length, 0)); #endif }; #ifdef WITH_MBEDTLS const auto ssl_write = [this](const uint8_t* data, size_t length) { - // BUGBUG: handle MBEDTLS_ERR_SSL_WANT_WRITE - return mbedtls_ssl_write(&ssl_context_.context, data, length)); + int32_t value = mbedtls_ssl_write(&ssl_context_.context, data, length); + return mbedtls_would_block(value) ? WOULD_BLOCK : value; }; - auto writer = ssl_context_.enabled ? ssl_write : plaintext_write; + auto writer = ssl_context_.enabled ? + static_cast(ssl_write) : + static_cast(plaintext_write); #else - auto writer = plaintext_write; + auto writer = static_cast(plaintext_write); #endif auto remaining = length; diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 8d07f5eb..d2e8699d 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -775,7 +775,7 @@ bool manager::transfer_file_data(connection_ptr connection) return false; } - file_transfer.offset += read; + file_transfer.offset += written; if (file_transfer.offset == file_transfer.length) { diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index 0c3bfa19..8b237706 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -64,27 +64,13 @@ std::string mbedtls_error_string(int32_t error) mbedtls_strerror(error, data.data(), data.size()); return { data.data() }; } - -////short_hash sha1(const std::string& input) -////{ -//// short_hash out; -//// mbedtls_sha1_context context; -//// mbedtls_sha1_init(&context); -//// -//// const auto source = reinterpret_cast(input.data()); -//// if ((mbedtls_sha1_starts_ret(&context) == 0) && -//// (mbedtls_sha1_update_ret(&context, source, input.size()) == 0) && -//// (mbedtls_sha1_finish_ret(&context, out.data()) == 0)) -//// return out; -//// -//// return {}; -////} #endif std::string op_to_string(websocket_op code) { static const std::string unknown = "unknown"; - static const std::unordered_map opcode_map + static const std::unordered_map opcode_map { { websocket_op::continuation, "continue" }, { websocket_op::text, "text" }, @@ -105,25 +91,10 @@ std::string websocket_key_response(const std::string& websocket_key) static const std::string rfc6455_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -// TODO: why are there two implementations of this? -////#ifdef WITH_MBEDTLS -//// // The buffer is a base64 encoded sha1 hash (20 bytes in length). -//// static constexpr size_t key_buffer_length = 64; -//// std::array buffer; -//// -//// size_t processed_length = 0; -//// const auto hash = sha1(websocket_key + rfc6455_guid); -//// -//// if ((mbedtls_base64_encode(buffer.data(), buffer.size(), &processed_length, -//// hash.data(), hash.size()) != 0) || (processed_length == 0)) -//// return {}; -//// -//// return { reinterpret_cast(buffer.data()), processed_length }; -////#else const auto input = websocket_key + rfc6455_guid; - const data_chunk input_data{ input.begin(), input.end() }; - return encode_base64(bc::sha1_hash(input_data)); -////#endif + const data_chunk input_data(input.begin(), input.end()); + const data_slice slice(bc::sha1_hash(input_data)); + return encode_base64(slice); } bool is_json_request(const std::string& header_value) From bd0c14d7b10ad836b574af77dedbea1b5a241adb Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Wed, 24 Oct 2018 07:22:10 -0500 Subject: [PATCH 20/25] Add configurable websocket origins to settings --- include/bitcoin/server/settings.hpp | 1 + src/parser.cpp | 5 +++++ src/settings.cpp | 1 + 3 files changed, 7 insertions(+) diff --git a/include/bitcoin/server/settings.hpp b/include/bitcoin/server/settings.hpp index a14586b0..3bf592aa 100644 --- a/include/bitcoin/server/settings.hpp +++ b/include/bitcoin/server/settings.hpp @@ -78,6 +78,7 @@ class BCS_API settings boost::filesystem::path websockets_server_private_key; boost::filesystem::path websockets_server_certificate; boost::filesystem::path websockets_client_certificates; + config::endpoint::list websockets_origins; /// [zeromq] system::config::endpoint zeromq_secure_query_endpoint; diff --git a/src/parser.cpp b/src/parser.cpp index f39f9796..c10d52ca 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -737,6 +737,11 @@ options_metadata parser::load_settings() value(&configured.server.websockets_client_certificates), "The SSL client certificates directory, defaults to 'clients'." ) + ( + "websockets.origin", + value(&configured.server.websockets_origins), + "A websocket origin, multiple entries allowed." + ) /* [bitcoin] */ ( diff --git a/src/settings.cpp b/src/settings.cpp index 4562d492..79849577 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -53,6 +53,7 @@ settings::settings() websockets_server_private_key("key.pem"), websockets_server_certificate("server.pem"), websockets_client_certificates("clients"), + websockets_origins{}, // [zeromq] zeromq_secure_query_endpoint("tcp://*:9081"), From 41abba18ea6fea6f2994b7f2d4f06fb2025e8979 Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Fri, 26 Oct 2018 06:56:41 -0500 Subject: [PATCH 21/25] Websocket updates and comments. --- .../bitcoin/server/web/http/bind_options.hpp | 7 +- .../bitcoin/server/web/http/connection.hpp | 2 +- .../bitcoin/server/web/http/http_reply.hpp | 13 ++- .../bitcoin/server/web/http/http_request.hpp | 14 +++ include/bitcoin/server/web/http/manager.hpp | 6 +- include/bitcoin/server/web/http/socket.hpp | 2 +- src/parser.cpp | 2 +- src/settings.cpp | 2 +- src/web/block_socket.cpp | 4 + src/web/heartbeat_socket.cpp | 4 + src/web/http/connection.cpp | 11 +-- src/web/http/json_string.cpp | 6 ++ src/web/http/manager.cpp | 53 ++++++++--- src/web/http/socket.cpp | 88 ++++++++++++------- src/web/http/utilities.cpp | 14 ++- src/web/query_socket.cpp | 6 +- src/web/transaction_socket.cpp | 4 + 17 files changed, 171 insertions(+), 67 deletions(-) diff --git a/include/bitcoin/server/web/http/bind_options.hpp b/include/bitcoin/server/web/http/bind_options.hpp index 96c24d24..7edade75 100644 --- a/include/bitcoin/server/web/http/bind_options.hpp +++ b/include/bitcoin/server/web/http/bind_options.hpp @@ -31,10 +31,9 @@ struct BCS_API bind_options { void* user_data; uint32_t flags; - std::string ssl_key; - std::string ssl_certificate; - std::string ssl_ca_certificate; - std::string ssl_cipers; + boost::filesystem::path ssl_key; + boost::filesystem::path ssl_certificate; + boost::filesystem::path ssl_ca_certificate; }; } // namespace http diff --git a/include/bitcoin/server/web/http/connection.hpp b/include/bitcoin/server/web/http/connection.hpp index 30cb023c..92b5aa15 100644 --- a/include/bitcoin/server/web/http/connection.hpp +++ b/include/bitcoin/server/web/http/connection.hpp @@ -80,7 +80,7 @@ class BCS_API connection // ------------------------------------------------------------------------ // Signed integer results overload negative range for error code. - read_buffer& read_buffer(); + http::read_buffer& read_buffer(); data_chunk& write_buffer(); int32_t read(); diff --git a/include/bitcoin/server/web/http/http_reply.hpp b/include/bitcoin/server/web/http/http_reply.hpp index 5c661680..d3047d62 100644 --- a/include/bitcoin/server/web/http/http_reply.hpp +++ b/include/bitcoin/server/web/http/http_reply.hpp @@ -32,14 +32,23 @@ namespace libbitcoin { namespace server { namespace http { - + // TODO: move implementation to cpp. class BCS_API http_reply { public: static std::string to_string(protocol_status status) { - typedef std::unordered_map status_map; + struct protocol_status_hasher + { + size_t operator()(const protocol_status& status) const + { + return std::hash{}(static_cast(status)); + } + }; + + typedef std::unordered_map status_map; static const status_map status_strings { { protocol_status::switching, "HTTP/1.1 101 Switching Protocols\r\n" }, diff --git a/include/bitcoin/server/web/http/http_request.hpp b/include/bitcoin/server/web/http/http_request.hpp index 4c75468d..0596fa65 100644 --- a/include/bitcoin/server/web/http/http_request.hpp +++ b/include/bitcoin/server/web/http/http_request.hpp @@ -35,6 +35,20 @@ namespace http { class BCS_API http_request { public: + http_request() + : method({}), + uri({}), + protocol({}), + protocol_version(0.0f), + message_length(0), + content_length(0), + headers({}), + parameters({}), + upgrade_request(false), + json_rpc(false) + { + } + std::string find(const string_map& haystack, const std::string& needle) const { diff --git a/include/bitcoin/server/web/http/manager.hpp b/include/bitcoin/server/web/http/manager.hpp index 1583dd00..49ae0faf 100644 --- a/include/bitcoin/server/web/http/manager.hpp +++ b/include/bitcoin/server/web/http/manager.hpp @@ -105,9 +105,9 @@ class BCS_API manager uint16_t port_; void* user_data_; - std::string key_; - std::string certificate_; - std::string ca_certificate_; + path key_; + path certificate_; + path ca_certificate_; event_handler handler_; path document_root_; connection_list connections_; diff --git a/include/bitcoin/server/web/http/socket.hpp b/include/bitcoin/server/web/http/socket.hpp index 9fab152a..ba593743 100644 --- a/include/bitcoin/server/web/http/socket.hpp +++ b/include/bitcoin/server/web/http/socket.hpp @@ -141,7 +141,7 @@ class BCS_API socket mutable upgrade_mutex correlation_lock_; private: - static bool handle_event(connection_ptr connection, event event, + static bool handle_event(connection_ptr connection, http::event event, const void* data); manager::ptr manager_; diff --git a/src/parser.cpp b/src/parser.cpp index c10d52ca..00c1d6a3 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -720,7 +720,7 @@ options_metadata parser::load_settings() ( "websockets.ca_certificate", value(&configured.server.websockets_ca_certificate), - "The SSL certificate authority file, defaults to 'ca.pem', enables secure endpoints." + "The SSL certificate authority file, defaults to '', enables secure endpoints." ) ( "websockets.server_private_key", diff --git a/src/settings.cpp b/src/settings.cpp index 79849577..cc676e4c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -49,7 +49,7 @@ settings::settings() websockets_enabled(true), websockets_root("web"), - websockets_ca_certificate("ca.pem"), + websockets_ca_certificate(""), websockets_server_private_key("key.pem"), websockets_server_certificate("server.pem"), websockets_client_certificates("clients"), diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index a2ba86e4..f17050e9 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -67,6 +67,10 @@ void block_socket::work() return; } + LOG_INFO(LOG_SERVER) + << "Bound " << security_ << " websocket block service to " + << websocket_endpoint(); + // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index 24b84103..8e6fa99d 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -64,6 +64,10 @@ void heartbeat_socket::work() return; } + LOG_INFO(LOG_SERVER) + << "Bound " << security_ << " websocket heartbeat service to " + << websocket_endpoint(); + // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp index 1e663886..bba3fc9f 100644 --- a/src/web/http/connection.cpp +++ b/src/web/http/connection.cpp @@ -18,15 +18,12 @@ */ #include -#include +#ifdef _MSC_VER #include -#include -/* #include */ +#endif -/* // Explicitly use std::placeholders here for usage internally to the */ -/* // boost parsing helpers included from json_parser.hpp. */ -/* // See: https://svn.boost.org/trac10/ticket/12621 */ -/* using namespace std::placeholders; */ +#include +#include #include #include #include diff --git a/src/web/http/json_string.cpp b/src/web/http/json_string.cpp index bb0782e5..b390c921 100644 --- a/src/web/http/json_string.cpp +++ b/src/web/http/json_string.cpp @@ -21,6 +21,12 @@ #include #include #include +#include + +// Explicitly use std::placeholders here for usage internally to the +// boost parsing helpers included from json_parser.hpp. +// See: https://svn.boost.org/trac10/ticket/12621 +using namespace std::placeholders; #include #include #include diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index d2e8699d..144e2f0d 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -37,7 +37,9 @@ extern "C" { // Random data generator used by mbedtls for SSL. -uint32_t https_random(void*, uint8_t* buffer, size_t length); +int https_random(void*, unsigned char* buffer, size_t length); +// The below signature does not build on gcc: +/* uint32_t https_random(void*, uint8_t* buffer, size_t length); */ } #endif @@ -61,6 +63,9 @@ manager::manager(bool ssl, event_handler handler, path document_root) initialized_(false), port_(0), user_data_(nullptr), + key_{}, + certificate_{}, + ca_certificate_{}, handler_(handler), document_root_(document_root) { @@ -94,8 +99,7 @@ bool manager::initialize() } // Bind is not thread safe. -bool manager::bind(const config::endpoint& address, - const bind_options& options) +bool manager::bind(const config::endpoint& address, const bind_options& options) { if (address.host() != "*") { @@ -134,12 +138,23 @@ bool manager::bind(const config::endpoint& address, if (ssl_) { - if (!options.ssl_certificate.empty() || - !options.ssl_ca_certificate.empty()) + if (!options.ssl_certificate.empty()) { - key_ = options.ssl_key; - certificate_ = options.ssl_certificate; - ca_certificate_ = options.ssl_ca_certificate; + key_ = options.ssl_key.generic_string(); + certificate_ = options.ssl_certificate.generic_string(); + } + + if (!options.ssl_ca_certificate.empty()) + { + // Specified and not found CA cert is a failure condition. + if (!exists(options.ssl_ca_certificate)) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Specified CA certificate does not exist"; + return false; + } + + ca_certificate_ = options.ssl_ca_certificate.generic_string(); } // The default context object for the listener socket is initialized. @@ -382,7 +397,7 @@ void manager::poll(size_t timeout_milliseconds) std::vector connection_lists; connection_lists.reserve(number_of_lists); - for (auto it = 0; it < number_of_lists; it++) + for (size_t it = 0; it < number_of_lists; it++) { connection_list connection_list; connection_list.reserve(maximum_connections); @@ -437,8 +452,18 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) const auto descriptor = connection->socket(); - // TODO: how does this relation make sense? // Check if the descriptor is too high to monitor in our fd_set. + // + // maximum_connections should be set to FD_SETSIZE. Since the + // select call tracks sockets in a 0-indexed bit field for + // file descriptors, any descriptor greater than FD_SETSIZE + // cannot be monitored in the bit field provided by the + // (select) mechanism. This means that if a single descriptor + // is greater than this value, it has to be closed since it + // can't be monitored, unless it can be dup'd (which asks the + // kernel to copy and return the descriptor mappings into the + // lowest-numbered unused descriptor). If that dup attempt + // fails to lower the value, close the connection. if (descriptor > maximum_connections) { #ifdef _MSC_VER @@ -449,8 +474,9 @@ void manager::select(size_t timeout_milliseconds, connection_list& connections) pending_removal.push_back(connection); continue; #else - // Attempt to resolve this by seeking a lower available descriptor. - auto new_descriptor = dup(descriptor); + // If the dup system call is supported, attempt to resolve + // this by seeking a lower available descriptor. + sock_t new_descriptor = dup(descriptor); if (new_descriptor < descriptor && new_descriptor < maximum_connections) { @@ -1134,7 +1160,7 @@ bool manager::upgrade_connection(connection_ptr connection, return handler_(connection, event::accepted, nullptr); } -bool manager::validate_origin(const std::string& origin) +bool manager::validate_origin(const std::string& /* origin */) { // TODO: test against configured set. return true; @@ -1232,6 +1258,7 @@ bool manager::initialize_ssl(connection_ptr connection, bool listener) mbedtls_ssl_session_reset(&context.context); } + // TODO: Allow ciphers to be caller specified mbedtls_ssl_conf_ciphersuites(&configuration, default_ciphers); LOG_DEBUG(LOG_SERVER_HTTP) diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp index f40b8b66..c170f52b 100644 --- a/src/web/http/socket.cpp +++ b/src/web/http/socket.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ using namespace boost::filesystem; using namespace boost::iostreams; using namespace boost::property_tree; using namespace http; +using http_event = http::event; using role = zmq::socket::role; // Local class. @@ -74,21 +76,20 @@ class task_sender if (!connection_ || connection_->closed()) return false; - if (connection_->json_rpc()) - { - http_reply reply; - const auto header = reply.generate(protocol_status::ok, {}, - data_.size(), false); + if (!connection_->json_rpc()) + // BUGBUG: unguarded narrowing cast. + return connection_->write(data_) == static_cast(data_.size()); - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Writing JSON-RPC response: " << header; + http_reply reply; + const auto response = reply.generate(protocol_status::ok, {}, + data_.size(), false) + data_; - // BUGBUG: unguarded narrowing cast. - return connection_->write(header) == static_cast(header.size()); - } + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Writing JSON-RPC response: " << response; // BUGBUG: unguarded narrowing cast. - return connection_->write(data_) == static_cast(data_.size()); + return connection_->write(response) == + static_cast(response.size()); } connection_ptr connection() @@ -104,12 +105,12 @@ class task_sender // TODO: eliminate the use of weak and untyped pointer to pass self here. // static // Callback made internally via socket::poll on the web socket thread. -bool socket::handle_event(connection_ptr connection, event event, +bool socket::handle_event(connection_ptr connection, http_event event, const void* data) { switch (event) { - case event::accepted: + case http_event::accepted: { // This connection is newly accepted and is either an HTTP // JSON-RPC connection, or an already upgraded websocket. @@ -127,7 +128,7 @@ bool socket::handle_event(connection_ptr connection, event event, break; } - case event::json_rpc: + case http_event::json_rpc: { // Process new incoming user json_rpc request. Returning // false here will cause this connection to be closed. @@ -140,15 +141,23 @@ bool socket::handle_event(connection_ptr connection, event event, // Use default-value get to avoid exceptions on invalid input. const auto id = request.json_tree.get("id", 0); const auto method = request.json_tree.get("method", ""); - std::string parameters{}; - const auto child = request.json_tree.get_child("params"); + if (request.json_tree.count("params") == 0) + { + http_reply reply; + connection->write(reply.generate( + protocol_status::bad_request, {}, 0, false)); + return false; + } + std::vector parameter_list; + const auto child = request.json_tree.get_child("params"); for (const auto& parameter: child) parameter_list.push_back( parameter.second.get_value()); // TODO: Support full parameter lists? + std::string parameters{}; if (!parameter_list.empty()) parameters = parameter_list[0]; @@ -160,7 +169,7 @@ bool socket::handle_event(connection_ptr connection, event event, break; } - case event::websocket_frame: + case http_event::websocket_frame: { // Process new incoming user websocket data. Returning false // will cause this connection to be closed. @@ -204,7 +213,7 @@ bool socket::handle_event(connection_ptr connection, event event, break; } - case event::closing: + case http_event::closing: { // This connection is going away after this handling. auto instance = static_cast(connection->user_data()); @@ -223,9 +232,9 @@ bool socket::handle_event(connection_ptr connection, event event, } // No specific handling required for other events. - case event::read: - case event::error: - case event::websocket_control_frame: + case http_event::read: + case http_event::error: + case http_event::websocket_control_frame: default: break; } @@ -258,6 +267,16 @@ bool socket::start() if (secure_) { + if (!server_settings_.websockets_ca_certificate.generic_string().empty() && + !exists(server_settings_.websockets_server_certificate)) + { + LOG_ERROR(LOG_SERVER) + << "Requested CA certificate '" + << server_settings_.websockets_ca_certificate + << "' does not exist."; + return false; + } + if (!exists(server_settings_.websockets_server_certificate)) { LOG_ERROR(LOG_SERVER) @@ -298,14 +317,10 @@ void socket::handle_websockets() if (secure_) { - // Specified and not found CA cert should be a failure condition. - // TODO: defer string conversion to ssl internals, keep paths here. - options.ssl_key = server_settings_. - websockets_server_private_key.generic_string(); - options.ssl_certificate = server_settings_. - websockets_server_certificate.generic_string(); - options.ssl_ca_certificate = server_settings_. - websockets_ca_certificate.generic_string(); + options.ssl_key = server_settings_.websockets_server_private_key; + options.ssl_certificate = + server_settings_.websockets_server_certificate; + options.ssl_ca_certificate = server_settings_.websockets_ca_certificate; } options.user_data = static_cast(this); @@ -319,9 +334,17 @@ void socket::handle_websockets() manager_->start(); } +// NOTE: query_socket is the only service that should implement this +// by returning something other than nullptr. +// +// The reason it's needed is so that socket::notify_query_work (which +// is called from handle_event in the web thread via +// handle_websockets) can retrieve the zmq socket within the query +// socket service (created on the same websocket thread) in order to +// send incoming requests to the internally connected zmq +// query_service. No other socket/service class requires this access. const std::shared_ptr socket::service() const { - // TODO: implement? BITCOIN_ASSERT_MSG(false, "not implemented"); return nullptr; } @@ -488,7 +511,8 @@ void socket::notify_query_work(connection_ptr connection, } } -// Sends json to websockets/connections waiting on json_rpc replies (only). +// Sends json strings to the specified web or json_rpc socket (does nothing if +// neither). void socket::send(connection_ptr connection, const std::string& json) { if (!connection || connection->closed() || @@ -501,7 +525,7 @@ void socket::send(connection_ptr connection, const std::string& json) manager_->execute(std::make_shared(connection, json)); } -// Sends json strings to all connected websockets. +// Sends json strings to all connected web and json_rpc sockets. void socket::broadcast(const std::string& json) { auto sender = [this, &json](std::pair entry) diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp index 8b237706..083ac574 100644 --- a/src/web/http/utilities.cpp +++ b/src/web/http/utilities.cpp @@ -21,7 +21,11 @@ #include #include #include -#include + +#ifdef _MSC_VER + #include +#endif + #include #include #include @@ -68,6 +72,14 @@ std::string mbedtls_error_string(int32_t error) std::string op_to_string(websocket_op code) { + struct websocket_op_hasher + { + size_t operator()(const websocket_op& status) const + { + return std::hash{}(static_cast(status)); + } + }; + static const std::string unknown = "unknown"; static const std::unordered_map opcode_map diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 183bc187..1dec04b5 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -162,6 +162,10 @@ void query_socket::work() return; } + LOG_INFO(LOG_SERVER) + << "Bound " << security_ << " websocket query service to " + << websocket_endpoint(); + // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; @@ -382,7 +386,7 @@ void query_socket::handle_websockets() bool query_socket::start_websocket_handler() { - auto& started = socket_started_.get_future(); + auto started = socket_started_.get_future(); thread_ = std::make_shared(&query_socket::handle_websockets, this); return started.get(); diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index bc645f73..452d73e4 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -67,6 +67,10 @@ void transaction_socket::work() return; } + LOG_INFO(LOG_SERVER) + << "Bound " << security_ << " websocket transaction service to " + << websocket_endpoint(); + // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; From 8cb6bf94d127db1baf7003ab4347be5e4b00f51a Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Fri, 26 Oct 2018 13:39:46 -0500 Subject: [PATCH 22/25] Remove the need for websocket state locking by no longer sharing web thread state with the zmq thread, instead providing a mechanism where all web state is managed by the web thread exclusively. Require that valid origins be specified in configuration. --- data/bs.cfg | 2 + include/bitcoin/server/web/http/manager.hpp | 12 +- include/bitcoin/server/web/http/socket.hpp | 65 ++++--- src/parser.cpp | 110 ++---------- src/web/http/manager.cpp | 28 ++- src/web/http/socket.cpp | 188 ++++++++++++++++---- src/web/query_socket.cpp | 129 +++++--------- 7 files changed, 277 insertions(+), 257 deletions(-) diff --git a/data/bs.cfg b/data/bs.cfg index aba293f9..f246fe84 100644 --- a/data/bs.cfg +++ b/data/bs.cfg @@ -281,6 +281,8 @@ server_private_key = key.pem server_certificate = server.pem # The SSL client certificates directory, defaults to 'clients'. client_certificates = clients +# An acceptable websocket origin, multiple entries allowed. +origin = http://localhost:9071 [zeromq] # The secure query zeromq endpoint, defaults to 'tcp://*:9081'. diff --git a/include/bitcoin/server/web/http/manager.hpp b/include/bitcoin/server/web/http/manager.hpp index 49ae0faf..41dde486 100644 --- a/include/bitcoin/server/web/http/manager.hpp +++ b/include/bitcoin/server/web/http/manager.hpp @@ -50,8 +50,11 @@ class BCS_API manager typedef std::shared_ptr task_ptr; typedef std::vector task_list; typedef std::shared_ptr ptr; + typedef std::function handler; + typedef std::vector origin_list; - manager(bool ssl, event_handler handler, path document_root); + manager(bool ssl, event_handler handler, path document_root, + const origin_list origins); ~manager(); bool initialize(); @@ -68,6 +71,9 @@ class BCS_API manager bool stopped() const; void start(); + // Same as start above, but allows a callback to be called after + // each iteration of run_once. + void start(handler callback); void stop(); void execute(task_ptr task); void run_tasks(); @@ -82,7 +88,7 @@ class BCS_API manager static int32_t ssl_receive(void* data, uint8_t* buffer, size_t length); #endif - void run_once(size_t timeout_milliseconds); + void run_once(); void select(size_t timeout_milliseconds, connection_list& sockets); bool transfer_file_data(connection_ptr connection); bool send_http_file(connection_ptr connection, const path& path, bool keep_alive); @@ -117,6 +123,8 @@ class BCS_API manager // This is protected by mutex. task_list tasks_; shared_mutex task_mutex_; + + const origin_list origins_; }; } // namespace http diff --git a/include/bitcoin/server/web/http/socket.hpp b/include/bitcoin/server/web/http/socket.hpp index ba593743..d97d107e 100644 --- a/include/bitcoin/server/web/http/socket.hpp +++ b/include/bitcoin/server/web/http/socket.hpp @@ -53,31 +53,16 @@ class BCS_API socket : public bc::protocol::zmq::worker { public: - /// Construct a socket class. - socket(bc::protocol::zmq::context& context, server_node& node, - bool secure); - - /// Start the service. - bool start() override; - - size_t connection_count() const; - void add_connection(connection_ptr connection); - void remove_connection(connection_ptr connection); - void notify_query_work(connection_ptr connection, - const std::string& method, uint32_t id, const std::string& parameters); - -protected: - // Tracks websocket queries via the query_work_map. Used for matching - // websocket client requests to zmq query responses. - struct query_work_item + class query_response_task { - uint32_t id; - uint32_t correlation_id; - connection_ptr connection; - std::string command; - std::string arguments; + public: + virtual ~query_response_task() = default; + virtual bool run() = 0; }; + typedef std::shared_ptr query_response_task_ptr; + typedef std::vector query_response_task_list; + // Handles translation of incoming JSON to zmq protocol methods and // converting the result back to JSON for web clients. struct handlers @@ -93,6 +78,18 @@ class BCS_API socket }; typedef std::unordered_map handler_map; + + // Tracks websocket queries via the query_work_map. Used for matching + // websocket client requests to zmq query responses. + struct query_work_item + { + uint32_t id; + uint32_t correlation_id; + connection_ptr connection; + std::string command; + std::string arguments; + }; + typedef std::unordered_map> query_correlation_map; @@ -100,6 +97,24 @@ class BCS_API socket typedef std::unordered_map connection_work_map; + /// Construct a socket class. + socket(bc::protocol::zmq::context& context, server_node& node, + bool secure); + + /// Start the service. + bool start() override; + + void queue_response(uint32_t sequence, const data_chunk& data, + const std::string& command); + bool send_query_responses(); + + size_t connection_count() const; + void add_connection(connection_ptr connection); + void remove_connection(connection_ptr connection); + void notify_query_work(connection_ptr connection, + const std::string& method, uint32_t id, const std::string& parameters); + +protected: // Initialize the websocket event loop and start a thread to poll events. virtual bool start_websocket_handler(); @@ -138,14 +153,18 @@ class BCS_API socket uint32_t sequence_; connection_work_map work_; query_correlation_map correlations_; - mutable upgrade_mutex correlation_lock_; private: static bool handle_event(connection_ptr connection, http::event event, const void* data); manager::ptr manager_; + const config::endpoint::list origins_; const boost::filesystem::path document_root_; + + // This is protected by mutex. + query_response_task_list query_response_tasks_; + shared_mutex query_response_task_mutex_; }; } // namespace http diff --git a/src/parser.cpp b/src/parser.cpp index 00c1d6a3..a5617211 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include BC_DECLARE_CONFIG_DEFAULT_PATH("libbitcoin" / "bs.cfg") @@ -602,20 +604,20 @@ options_metadata parser::load_settings() value(&configured.database.index_addresses), "Enable payment and stealth address indexing, defaults to true." ) - /* Internally this is protocol, but application to server is more intuitive. */ ( + /* Internally this is protocol, but application to server is more intuitive. */ "server.send_high_water", value(&configured.protocol.send_high_water), "Drop messages at this outgoing backlog level, defaults to 100." ) - /* Internally this is protocol, but application to server is more intuitive. */ ( + /* Internally this is protocol, but application to server is more intuitive. */ "server.receive_high_water", value(&configured.protocol.receive_high_water), "Drop messages at this incoming backlog level, defaults to 100." ) - /* Internally this is protocol, but application to server is more intuitive. */ ( + /* Internally this is protocol, but application to server is more intuitive. */ "server.handshake_seconds", value(&configured.protocol.handshake_seconds), "The time limit to complete the connection handshake, defaults to 30." @@ -623,12 +625,12 @@ options_metadata parser::load_settings() ( "server.secure_only", value(&configured.server.secure_only), - "Disable all public endpoints, defaults to false." + "Disable public endpoints, defaults to false." ) ( "server.query_workers", value(&configured.server.query_workers), - "The number of query worker threads, defaults to 1 (0 disables service)." + "The number of query worker threads per endpoint, defaults to 1 (0 disables service)." ) ( "server.subscription_limit", @@ -648,12 +650,12 @@ options_metadata parser::load_settings() ( "server.block_service_enabled", value(&configured.server.block_service_enabled), - "Enable the block publishing service, defaults to true." + "Enable the block publishing service, defaults to false." ) ( "server.transaction_service_enabled", value(&configured.server.transaction_service_enabled), - "Enable the transaction publishing service, defaults to true." + "Enable the transaction publishing service, defaults to false." ) ( "server.client_address", @@ -740,99 +742,7 @@ options_metadata parser::load_settings() ( "websockets.origin", value(&configured.server.websockets_origins), - "A websocket origin, multiple entries allowed." - ) - - /* [bitcoin] */ - ( - "bitcoin.retargeting_factor", - PROPERTY(uint32_t, configured.bitcoin.retargeting_factor), - "The difficulty retargeting factor, defaults to 4." - ) - ( - "bitcoin.block_spacing_seconds", - PROPERTY(uint32_t, configured.bitcoin.block_spacing_seconds), - "The target block period in seconds, defaults to 600." - ) - ( - "bitcoin.timestamp_limit_seconds", - value(&configured.bitcoin.timestamp_limit_seconds), - "The future timestamp allowance in seconds, defaults to 7200." - ) - ( - "bitcoin.retargeting_interval_seconds", - PROPERTY(uint32_t, configured.bitcoin.retargeting_interval_seconds), - "The difficulty retargeting period in seconds, defaults to 1209600." - ) - ( - "bitcoin.proof_of_work_limit", - value(&configured.bitcoin.proof_of_work_limit), - "The proof of work limit, defaults to 486604799." - ) - ( - "bitcoin.genesis_block", - value(&configured.bitcoin.genesis_block), - "The genesis block." - ) - ( - "bitcoin.activation_threshold", - value(&configured.bitcoin.activation_threshold), - "The number of new version blocks required for bip34 style soft fork activation, defaults to 750." - ) - ( - "bitcoin.enforcement_threshold", - value(&configured.bitcoin.enforcement_threshold), - "The number of new version blocks required for bip34 style soft fork enforcement, defaults to 950." - ) - ( - "bitcoin.activation_sample", - value(&configured.bitcoin.activation_sample), - "The number of blocks considered for bip34 style soft fork activation, defaults to 1000." - ) - ( - "bitcoin.bip65_freeze", - value(&configured.bitcoin.bip65_freeze), - "The block height to freeze the bip65 softfork as in bip90, defaults to 388381." - ) - ( - "bitcoin.bip66_freeze", - value(&configured.bitcoin.bip66_freeze), - "The block height to freeze the bip66 softfork as in bip90, defaults to 363725." - ) - ( - "bitcoin.bip34_freeze", - value(&configured.bitcoin.bip34_freeze), - "The block height to freeze the bip34 softfork as in bip90, defaults to 227931." - ) - ( - "bitcoin.bip16_activation_time", - value(&configured.bitcoin.bip16_activation_time), - "The activation time for bip16 in unix time, defaults to 1333238400." - ) - ( - "bitcoin.bip34_active_checkpoint", - value(&configured.bitcoin.bip34_active_checkpoint), - "The hash:height checkpoint for bip34 activation, defaults to 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8:227931." - ) - ( - "bitcoin.bip9_bit0_active_checkpoint", - value(&configured.bitcoin.bip9_bit0_active_checkpoint), - "The hash:height checkpoint for bip9 bit0 activation, defaults to 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5:419328." - ) - ( - "bitcoin.bip9_bit1_active_checkpoint", - value(&configured.bitcoin.bip9_bit1_active_checkpoint), - "The hash:height checkpoint for bip9 bit1 activation, defaults to 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893:481824." - ) - ( - "bitcoin.initial_block_subsidy_bitcoin", - PROPERTY(uint64_t, configured.bitcoin.initial_block_subsidy_bitcoin), - "The initial block subsidy in bitcoin, defaults to 50." - ) - ( - "bitcoin.subsidy_interval", - PROPERTY(uint64_t, configured.bitcoin.subsidy_interval), - "The subsidy halving period in number of blocks, defaults to 210000." + "An acceptable websocket origin, multiple entries allowed." ) /* [zeromq] */ diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp index 144e2f0d..91c2e4fb 100644 --- a/src/web/http/manager.cpp +++ b/src/web/http/manager.cpp @@ -56,7 +56,8 @@ static constexpr size_t timeout_milliseconds = 10; static constexpr size_t maximum_backlog = 8; static constexpr size_t maximum_connections = FD_SETSIZE; -manager::manager(bool ssl, event_handler handler, path document_root) +manager::manager(bool ssl, event_handler handler, path document_root, + const origin_list origins) : ssl_(ssl), running_(false), listening_(false), @@ -67,7 +68,8 @@ manager::manager(bool ssl, event_handler handler, path document_root) certificate_{}, ca_certificate_{}, handler_(handler), - document_root_(document_root) + document_root_(document_root), + origins_(origins) { #ifndef WITH_MBEDTLS BITCOIN_ASSERT_MSG(!ssl, "Secure HTTP requires MBEDTLS library."); @@ -328,15 +330,25 @@ void manager::start() { running_ = true; while (running_) - run_once(timeout_milliseconds); + run_once(); } -void manager::run_once(size_t timeout_milliseconds) +void manager::start(handler callback) +{ + running_ = true; + while (running_) + { + run_once(); + callback(); + } +} + +void manager::run_once() { if (stopped()) return; - // Run any tasks the user queued tasks that must be run inside this thread. + // Run any user queued tasks that must be run inside this thread. run_tasks(); // Monitor and process sockets. @@ -1160,10 +1172,10 @@ bool manager::upgrade_connection(connection_ptr connection, return handler_(connection, event::accepted, nullptr); } -bool manager::validate_origin(const std::string& /* origin */) +bool manager::validate_origin(const std::string& origin) { - // TODO: test against configured set. - return true; + return std::find(origins_.begin(), origins_.end(), origin) != + origins_.end(); } bool manager::initialize_ssl(connection_ptr connection, bool listener) diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp index c170f52b..435bec0d 100644 --- a/src/web/http/socket.cpp +++ b/src/web/http/socket.cpp @@ -102,6 +102,110 @@ class task_sender const std::string data_; }; +// Local class. +// +// The purpose of this class is to handle previously received zmq +// responses and write them to the requesting websocket. +// +// The run() method is only called from send_query_responses on the +// web thread. With this guarantee in mind, no locking of any state +// is required. +class query_response_task_sender + : public socket::query_response_task +{ +public: + query_response_task_sender(uint32_t sequence, const data_chunk& data, + const std::string& command, const socket::handler_map& handlers, + socket::connection_work_map& work, + socket::query_correlation_map& correlations) + : sequence_(sequence), + data_(data), + command_(command), + handlers_(handlers), + work_(work), + correlations_(correlations) + { + } + + bool run() + { + // Use internal sequence number to find connection and work id. + auto correlation = correlations_.find(sequence_); + if (correlation == correlations_.end()) + { + // This will happen anytime the client disconnects before this handler + // is called. We can safely discard the result here. + LOG_DEBUG(LOG_SERVER) + << "Unmatched websocket query work item sequence: " << sequence_; + return true; + } + + auto connection = correlation->second.first; + const auto id = correlation->second.second; + correlations_.erase(correlation); + + // Use connection to locate connection state. + auto it = work_.find(connection); + if (it == work_.end()) + { + LOG_ERROR(LOG_SERVER) + << "Query work completed for unknown connection"; + return true; + } + + // Use work id to locate the query work item. + auto& query_work_map = it->second; + auto query_work = query_work_map.find(id); + if (query_work == query_work_map.end()) + { + // This can happen anytime the client disconnects before this + // code is reached. We can safely discard the result here. + LOG_DEBUG(LOG_SERVER) + << "Unmatched websocket query work id: " << id; + return true; + } + + const auto work = query_work->second; + query_work_map.erase(query_work); + + BITCOIN_ASSERT(work.id == id); + BITCOIN_ASSERT(work.connection == connection); + BITCOIN_ASSERT(work.correlation_id == sequence); + + data_source istream(data_); + istream_reader source(istream); + const auto ec = source.read_error_code(); + if (ec) + { + work.connection->write(to_json(ec, id)); + return true; + } + + const auto handler = handlers_.find(work.command); + if (handler == handlers_.end()) + { + static constexpr auto error = bc::error::not_implemented; + work.connection->write(to_json(error, id)); + return true; + } + + // Decode response and send query output to websocket client. + // The websocket write is performed directly (running on the + // websocket thread). + const auto payload = source.read_bytes(); + handler->second.decode(payload, id, work.connection); + return true; + } + +private: + const uint32_t sequence_; + const data_chunk data_; + const std::string command_; + const socket::handler_map& handlers_; + socket::connection_work_map& work_; + socket::query_correlation_map& correlations_; +}; + // TODO: eliminate the use of weak and untyped pointer to pass self here. // static // Callback made internally via socket::poll on the web socket thread. @@ -251,6 +355,7 @@ socket::socket(zmq::context& context, server_node& node, bool secure) protocol_settings_(node.protocol_settings()), sequence_(0), manager_(nullptr), + origins_(node.server_settings().websockets_origins), document_root_(node.server_settings().websockets_root) { } @@ -299,13 +404,54 @@ bool socket::start() return zmq::worker::start(); } +void socket::queue_response(uint32_t sequence, const data_chunk& data, + const std::string& command) +{ + auto task = std::make_shared( + sequence, data, command, handlers_, work_, correlations_); + + // Critical Section. + /////////////////////////////////////////////////////////////////////////// + unique_lock lock(query_response_task_mutex_); + query_response_tasks_.push_back(task); + /////////////////////////////////////////////////////////////////////////// +} + +bool socket::send_query_responses() +{ + query_response_task_list tasks; + + // Critical Section. + /////////////////////////////////////////////////////////////////////////// + query_response_task_mutex_.lock(); + query_response_tasks_.swap(tasks); + query_response_task_mutex_.unlock(); + /////////////////////////////////////////////////////////////////////////// + + for (const auto task: tasks) + if (!task->run()) + return false; + + return true; +} + void socket::handle_websockets() { bind_options options; + auto format_origins = [](const config::endpoint::list& endpoints) + { + manager::origin_list origins; + origins.reserve(endpoints.size()); + for (const auto& endpoint: endpoints) + origins.emplace_back(endpoint.to_string()); + + return origins; + }; + // This starts up the listener for the socket. manager_ = std::make_shared(secure_, &socket::handle_event, - document_root_); + document_root_, format_origins(origins_)); if (!manager_ || !manager_->initialize()) { @@ -330,8 +476,13 @@ void socket::handle_websockets() return; } + auto callback = [this]() + { + send_query_responses(); + }; + socket_started_.set_value(true); - manager_->start(); + manager_->start(static_cast(callback)); } // NOTE: query_socket is the only service that should implement this @@ -366,14 +517,12 @@ bool socket::stop_websocket_handler() size_t socket::connection_count() const { - // BUGBUG: use of work_ in this method is not thread safe. return work_.size(); } // Called by the websocket handling thread via handle_event. void socket::add_connection(connection_ptr connection) { - // BUGBUG: use of work_ in this method is not thread safe. BITCOIN_ASSERT(work_.find(connection) == work_.end()); // Initialize a new query_work_map for this connection. @@ -381,12 +530,8 @@ void socket::add_connection(connection_ptr connection) } // Called by the websocket handling thread via handle_event. -// Correlation lock usage is required because it protects the shared -// correlation map of ids, which can also used by the zmq service -// thread on response handling (i.e. query_socket::handle_query). void socket::remove_connection(connection_ptr connection) { - // BUGBUG: use of work_ in this method is not thread safe. if (work_.empty()) return; @@ -395,11 +540,6 @@ void socket::remove_connection(connection_ptr connection) { // Tearing down a connection is O(n) where n is the amount of // remaining outstanding queries. - - /////////////////////////////////////////////////////////////////////// - // Critical Section - correlation_lock_.lock_upgrade(); - auto& query_work_map = it->second; for (const auto& query_work: query_work_map) { @@ -407,18 +547,9 @@ void socket::remove_connection(connection_ptr connection) query_work.second.correlation_id); if (correlation != correlations_.end()) - { - correlation_lock_.unlock_upgrade_and_lock(); - // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ correlations_.erase(correlation); - // ------------------------------------------------------------ - correlation_lock_.unlock_and_lock_upgrade(); - } } - correlation_lock_.unlock_upgrade(); - /////////////////////////////////////////////////////////////////////// - // Clear the query_work_map for this connection before removal. query_work_map.clear(); work_.erase(it); @@ -426,9 +557,6 @@ void socket::remove_connection(connection_ptr connection) } // Called by the websocket handling thread via handle_event. -// Correlation lock usage is required because it protects the shared -// correlation map of ids, which is also used by the zmq service -// thread on query response handling. // // Errors write directly on the connection since this is called from // the event_handler, which is called on the websocket thread. @@ -460,8 +588,6 @@ void socket::notify_query_work(connection_ptr connection, return; } - // BUGBUG: use of work_ in this method is not thread safe. - // BUGBUG: this includes modification of query_work_map below. auto it = work_.find(connection); if (it == work_.end()) { @@ -486,10 +612,6 @@ void socket::notify_query_work(connection_ptr connection, handler->second.encode(request, handler->second.command, parameters, sequence_); - /////////////////////////////////////////////////////////////////////////// - // Critical Section - correlation_lock_.lock(); - // While each connection has its own id map (meaning correlation ids passed // from the web client are unique on a per connection basis, potentially // utilizing the full range available), we need an internal mapping that @@ -498,9 +620,6 @@ void socket::notify_query_work(connection_ptr connection, // sees this sequence_ value. correlations_[sequence_++] = { connection, id }; - correlation_lock_.unlock(); - /////////////////////////////////////////////////////////////////////////// - const auto ec = service()->send(request); if (ec) @@ -533,7 +652,6 @@ void socket::broadcast(const std::string& json) send(entry.first, json); }; - // BUGBUG: use of work_ in this method is not thread safe. std::for_each(work_.begin(), work_.end(), sender); } diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 1dec04b5..ce8daf81 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace libbitcoin { @@ -62,41 +63,67 @@ query_socket::query_socket(zmq::context& context, server_node& node, request.enqueue(to_chunk(hash)); }; + // A local clone of the task_sender send logic, for the decoders + // below that don't need to use that mechanism since they're + // already guaranteed to be run on the web thread. + auto decode_send = [](connection_ptr connection, const std::string& json) + { + if (!connection || connection->closed()) + return false; + + if (!connection->json_rpc()) + // BUGBUG: unguarded narrowing cast. + return connection->write(json) == static_cast(json.size()); + + http_reply reply; + const auto response = reply.generate(protocol_status::ok, {}, + json.size(), false) + json; + + LOG_VERBOSE(LOG_SERVER_HTTP) + << "Writing JSON-RPC response: " << response; + + // BUGBUG: unguarded narrowing cast. + return connection->write(response) == + static_cast(response.size()); + }; + // JSON to ZMQ response decoders. - //------------------------------------------------------------------------- - const auto decode_height = [this](const data_chunk& data, const uint32_t id, - connection_ptr connection) + // ------------------------------------------------------------------------- + // These all run on the websocket thread, so can write on the + // connection directly. + const auto decode_height = [this, decode_send](const data_chunk& data, + const uint32_t id, connection_ptr connection) { data_source istream(data); istream_reader source(istream); const auto height = source.read_4_bytes_little_endian(); - send(connection, to_json(height, id)); + decode_send(connection, to_json(height, id)); }; - const auto decode_transaction = [this, &node](const data_chunk& data, - const uint32_t id, connection_ptr connection) + const auto decode_transaction = [this, &node, decode_send]( + const data_chunk& data, const uint32_t id, connection_ptr connection) { const auto witness = chain::script::is_enabled( node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); const auto transaction = chain::transaction::factory(data, true, witness); - send(connection, to_json(transaction, id)); + decode_send(connection, to_json(transaction, id)); }; - const auto decode_block = [this, &node](const data_chunk& data, - const uint32_t id,connection_ptr connection) + const auto decode_block = [this, &node, decode_send]( + const data_chunk& data, const uint32_t id,connection_ptr connection) { const auto witness = chain::script::is_enabled( node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); const auto block = chain::block::factory(data, witness); - send(connection, to_json(block, id)); + decode_send(connection, to_json(block, id)); }; - const auto decode_block_header = [this](const data_chunk& data, + const auto decode_block_header = [this, decode_send](const data_chunk& data, const uint32_t id,connection_ptr connection) { const auto header = chain::header::factory(data, true); - send(connection, to_json(header, id)); + decode_send(connection, to_json(header, id)); }; handlers_["getblockcount"] = handlers @@ -207,10 +234,6 @@ void query_socket::work() // Called by this thread's zmq work() method. Returns true to // continue future notifications. -// -// Correlation lock usage is required because it protects the shared -// correlation map of query ids, which is also used by the websocket -// thread event handler (e.g. remove_connection, notify_query_work). bool query_socket::handle_query(zmq::socket& dealer) { if (stopped()) @@ -246,79 +269,7 @@ bool query_socket::handle_query(zmq::socket& dealer) return true; } - /////////////////////////////////////////////////////////////////////////// - // Critical Section - correlation_lock_.lock_upgrade(); - - // Use internal sequence number to find connection and work id. - auto correlation = correlations_.find(sequence); - if (correlation == correlations_.end()) - { - correlation_lock_.unlock_upgrade(); - //--------------------------------------------------------------------- - // This will happen anytime the client disconnects before this handler - // is called. We can safely discard the result here. - LOG_DEBUG(LOG_SERVER) - << "Unmatched websocket query work item sequence: " << sequence; - return true; - } - - auto connection = correlation->second.first; - const auto id = correlation->second.second; - // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - correlation_lock_.unlock_upgrade_and_lock(); - correlations_.erase(correlation); - correlation_lock_.unlock(); - /////////////////////////////////////////////////////////////////////////// - - // Use connection to locate connection state. - auto it = work_.find(connection); - if (it == work_.end()) - { - LOG_ERROR(LOG_SERVER) - << "Query work completed for unknown connection"; - return true; - } - - // Use work id to locate the query work item. - auto& query_work_map = it->second; - auto query_work = query_work_map.find(id); - if (query_work == query_work_map.end()) - { - // This can happen anytime the client disconnects before this - // code is reached. We can safely discard the result here. - LOG_DEBUG(LOG_SERVER) - << "Unmatched websocket query work id: " << id; - return true; - } - - const auto work = query_work->second; - query_work_map.erase(query_work); - - BITCOIN_ASSERT(work.id == id); - BITCOIN_ASSERT(work.correlation_id == sequence); - - data_source istream(data); - istream_reader source(istream); - ec = source.read_error_code(); - if (ec) - { - send(work.connection, to_json(ec, id)); - return true; - } - - const auto handler = handlers_.find(work.command); - if (handler == handlers_.end()) - { - static constexpr auto error = bc::error::not_implemented; - send(work.connection, to_json(error, id)); - return true; - } - - // Decode response and send query output to websocket client. The - // websocket write is performed on the websocket thread via task_sender. - const auto payload = source.read_bytes(); - handler->second.decode(payload, id, work.connection); + socket::queue_response(sequence, data, command); return true; } From 0bf3d4ebac12ddd3ed5dcbe2dfbdcccf56c1d9de Mon Sep 17 00:00:00 2001 From: Neill Miller Date: Tue, 30 Oct 2018 19:51:15 -0500 Subject: [PATCH 23/25] Move websocket/http code to libbitcoin-protocol. --- Makefile.am | 26 - builds/cmake/CMakeLists.txt | 4 + .../libbitcoin-server.vcxproj | 23 - .../libbitcoin-server.vcxproj.filters | 93 +- .../libbitcoin-server.vcxproj | 23 - .../libbitcoin-server.vcxproj.filters | 93 +- .../libbitcoin-server.vcxproj | 23 - .../libbitcoin-server.vcxproj.filters | 93 +- data/bs.cfg | 55 +- include/bitcoin/server.hpp | 18 - include/bitcoin/server/server_node.hpp | 8 +- include/bitcoin/server/settings.hpp | 6 - include/bitcoin/server/web/block_socket.hpp | 17 +- .../bitcoin/server/web/heartbeat_socket.hpp | 15 +- .../bitcoin/server/web/http/bind_options.hpp | 43 - .../bitcoin/server/web/http/connection.hpp | 142 -- .../server/web/http/connection_state.hpp | 42 - include/bitcoin/server/web/http/event.hpp | 45 - .../bitcoin/server/web/http/file_transfer.hpp | 42 - include/bitcoin/server/web/http/http.hpp | 102 -- .../bitcoin/server/web/http/http_reply.hpp | 131 -- .../bitcoin/server/web/http/http_request.hpp | 88 -- .../bitcoin/server/web/http/json_string.hpp | 49 - include/bitcoin/server/web/http/manager.hpp | 134 -- .../server/web/http/protocol_status.hpp | 53 - include/bitcoin/server/web/http/socket.hpp | 174 --- include/bitcoin/server/web/http/ssl.hpp | 47 - include/bitcoin/server/web/http/utilities.hpp | 65 - .../server/web/http/websocket_frame.hpp | 169 --- .../server/web/http/websocket_message.hpp | 45 - .../bitcoin/server/web/http/websocket_op.hpp | 42 - .../server/web/http/websocket_transfer.hpp | 45 - include/bitcoin/server/web/query_socket.hpp | 15 +- .../bitcoin/server/web/transaction_socket.hpp | 15 +- src/parser.cpp | 12 +- src/server_node.cpp | 16 +- src/settings.cpp | 6 - src/web/block_socket.cpp | 25 +- src/web/heartbeat_socket.cpp | 20 +- src/web/http/connection.cpp | 345 ----- src/web/http/json_string.cpp | 107 -- src/web/http/manager.cpp | 1288 ----------------- src/web/http/socket.cpp | 660 --------- src/web/http/utilities.cpp | 342 ----- src/web/query_socket.cpp | 61 +- src/web/transaction_socket.cpp | 26 +- 46 files changed, 159 insertions(+), 4734 deletions(-) delete mode 100644 include/bitcoin/server/web/http/bind_options.hpp delete mode 100644 include/bitcoin/server/web/http/connection.hpp delete mode 100644 include/bitcoin/server/web/http/connection_state.hpp delete mode 100644 include/bitcoin/server/web/http/event.hpp delete mode 100644 include/bitcoin/server/web/http/file_transfer.hpp delete mode 100644 include/bitcoin/server/web/http/http.hpp delete mode 100644 include/bitcoin/server/web/http/http_reply.hpp delete mode 100644 include/bitcoin/server/web/http/http_request.hpp delete mode 100644 include/bitcoin/server/web/http/json_string.hpp delete mode 100644 include/bitcoin/server/web/http/manager.hpp delete mode 100644 include/bitcoin/server/web/http/protocol_status.hpp delete mode 100644 include/bitcoin/server/web/http/socket.hpp delete mode 100644 include/bitcoin/server/web/http/ssl.hpp delete mode 100644 include/bitcoin/server/web/http/utilities.hpp delete mode 100644 include/bitcoin/server/web/http/websocket_frame.hpp delete mode 100644 include/bitcoin/server/web/http/websocket_message.hpp delete mode 100644 include/bitcoin/server/web/http/websocket_op.hpp delete mode 100644 include/bitcoin/server/web/http/websocket_transfer.hpp delete mode 100644 src/web/http/connection.cpp delete mode 100644 src/web/http/json_string.cpp delete mode 100644 src/web/http/manager.cpp delete mode 100644 src/web/http/socket.cpp delete mode 100644 src/web/http/utilities.cpp diff --git a/Makefile.am b/Makefile.am index 9be8d411..a9bdf03f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,11 +54,6 @@ src_libbitcoin_server_la_SOURCES = \ src/web/heartbeat_socket.cpp \ src/web/query_socket.cpp \ src/web/transaction_socket.cpp \ - src/web/http/connection.cpp \ - src/web/http/json_string.cpp \ - src/web/http/manager.cpp \ - src/web/http/socket.cpp \ - src/web/http/utilities.cpp \ src/workers/authenticator.cpp \ src/workers/notification_worker.cpp \ src/workers/query_worker.cpp @@ -135,27 +130,6 @@ include_bitcoin_server_web_HEADERS = \ include/bitcoin/server/web/query_socket.hpp \ include/bitcoin/server/web/transaction_socket.hpp -include_bitcoin_server_web_httpdir = ${includedir}/bitcoin/server/web/http -include_bitcoin_server_web_http_HEADERS = \ - include/bitcoin/server/web/http/bind_options.hpp \ - include/bitcoin/server/web/http/connection.hpp \ - include/bitcoin/server/web/http/connection_state.hpp \ - include/bitcoin/server/web/http/event.hpp \ - include/bitcoin/server/web/http/file_transfer.hpp \ - include/bitcoin/server/web/http/http.hpp \ - include/bitcoin/server/web/http/http_reply.hpp \ - include/bitcoin/server/web/http/http_request.hpp \ - include/bitcoin/server/web/http/json_string.hpp \ - include/bitcoin/server/web/http/manager.hpp \ - include/bitcoin/server/web/http/protocol_status.hpp \ - include/bitcoin/server/web/http/socket.hpp \ - include/bitcoin/server/web/http/ssl.hpp \ - include/bitcoin/server/web/http/utilities.hpp \ - include/bitcoin/server/web/http/websocket_frame.hpp \ - include/bitcoin/server/web/http/websocket_message.hpp \ - include/bitcoin/server/web/http/websocket_op.hpp \ - include/bitcoin/server/web/http/websocket_transfer.hpp - include_bitcoin_server_workersdir = ${includedir}/bitcoin/server/workers include_bitcoin_server_workers_HEADERS = \ include/bitcoin/server/workers/authenticator.hpp \ diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 85d38ab5..51c63aec 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -186,6 +186,10 @@ add_library( ${CANONICAL_LIB_NAME} "../../src/services/heartbeat_service.cpp" "../../src/services/query_service.cpp" "../../src/services/transaction_service.cpp" + "../../src/web/block_socket.cpp" + "../../src/web/heartbeat_socket.cpp" + "../../src/web/query_socket.cpp" + "../../src/web/transaction_socket.cpp" "../../src/workers/authenticator.cpp" "../../src/workers/notification_worker.cpp" "../../src/workers/query_worker.cpp" ) diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj index 5bb3abad..e7c73e7c 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj @@ -90,11 +90,6 @@ - - - - - @@ -122,24 +117,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters index bcf0a39a..7712a9de 100644 --- a/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2013/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -8,34 +8,31 @@ - {73CE0AC2-ECB2-4E8D-0000-000000000007} + {73CE0AC2-ECB2-4E8D-0000-000000000006} - {73CE0AC2-ECB2-4E8D-0000-000000000008} + {73CE0AC2-ECB2-4E8D-0000-000000000007} - {73CE0AC2-ECB2-4E8D-0000-000000000009} + {73CE0AC2-ECB2-4E8D-0000-000000000008} - {73CE0AC2-ECB2-4E8D-0000-00000000000A} + {73CE0AC2-ECB2-4E8D-0000-000000000009} - {73CE0AC2-ECB2-4E8D-0000-00000000000B} + {73CE0AC2-ECB2-4E8D-0000-00000000000A} - {73CE0AC2-ECB2-4E8D-0000-00000000000C} + {73CE0AC2-ECB2-4E8D-0000-00000000000B} - {73CE0AC2-ECB2-4E8D-0000-00000000000D} - - - {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-00000000000C} - {73CE0AC2-ECB2-4E8D-0000-00000000000E} + {73CE0AC2-ECB2-4E8D-0000-00000000000D} - {73CE0AC2-ECB2-4E8D-0000-000000000001} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -52,9 +49,6 @@ {73CE0AC2-ECB2-4E8D-0000-000000000004} - - {73CE0AC2-ECB2-4E8D-0000-000000000006} - {73CE0AC2-ECB2-4E8D-0000-000000000005} @@ -111,21 +105,6 @@ src\web - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - src\web @@ -203,60 +182,6 @@ include\bitcoin\server\web - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - include\bitcoin\server\web diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj index 23d91d59..5d692258 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj @@ -90,11 +90,6 @@ - - - - - @@ -122,24 +117,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters index 63519d6c..455c5690 100644 --- a/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2015/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -8,34 +8,31 @@ - {73CE0AC2-ECB2-4E8D-0000-000000000007} + {73CE0AC2-ECB2-4E8D-0000-000000000006} - {73CE0AC2-ECB2-4E8D-0000-000000000008} + {73CE0AC2-ECB2-4E8D-0000-000000000007} - {73CE0AC2-ECB2-4E8D-0000-000000000009} + {73CE0AC2-ECB2-4E8D-0000-000000000008} - {73CE0AC2-ECB2-4E8D-0000-00000000000A} + {73CE0AC2-ECB2-4E8D-0000-000000000009} - {73CE0AC2-ECB2-4E8D-0000-00000000000B} + {73CE0AC2-ECB2-4E8D-0000-00000000000A} - {73CE0AC2-ECB2-4E8D-0000-00000000000C} + {73CE0AC2-ECB2-4E8D-0000-00000000000B} - {73CE0AC2-ECB2-4E8D-0000-00000000000D} - - - {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-00000000000C} - {73CE0AC2-ECB2-4E8D-0000-00000000000E} + {73CE0AC2-ECB2-4E8D-0000-00000000000D} - {73CE0AC2-ECB2-4E8D-0000-000000000001} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -52,9 +49,6 @@ {73CE0AC2-ECB2-4E8D-0000-000000000004} - - {73CE0AC2-ECB2-4E8D-0000-000000000006} - {73CE0AC2-ECB2-4E8D-0000-000000000005} @@ -111,21 +105,6 @@ src\web - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - src\web @@ -203,60 +182,6 @@ include\bitcoin\server\web - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - include\bitcoin\server\web diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj index fc5453b0..de1c632c 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj @@ -90,11 +90,6 @@ - - - - - @@ -122,24 +117,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters index a6b78d91..8b28edba 100644 --- a/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters +++ b/builds/msvc/vs2017/libbitcoin-server/libbitcoin-server.vcxproj.filters @@ -8,34 +8,31 @@ - {73CE0AC2-ECB2-4E8D-0000-000000000007} + {73CE0AC2-ECB2-4E8D-0000-000000000006} - {73CE0AC2-ECB2-4E8D-0000-000000000008} + {73CE0AC2-ECB2-4E8D-0000-000000000007} - {73CE0AC2-ECB2-4E8D-0000-000000000009} + {73CE0AC2-ECB2-4E8D-0000-000000000008} - {73CE0AC2-ECB2-4E8D-0000-00000000000A} + {73CE0AC2-ECB2-4E8D-0000-000000000009} - {73CE0AC2-ECB2-4E8D-0000-00000000000B} + {73CE0AC2-ECB2-4E8D-0000-00000000000A} - {73CE0AC2-ECB2-4E8D-0000-00000000000C} + {73CE0AC2-ECB2-4E8D-0000-00000000000B} - {73CE0AC2-ECB2-4E8D-0000-00000000000D} - - - {73CE0AC2-ECB2-4E8D-0000-00000000000F} + {73CE0AC2-ECB2-4E8D-0000-00000000000C} - {73CE0AC2-ECB2-4E8D-0000-00000000000E} + {73CE0AC2-ECB2-4E8D-0000-00000000000D} - {73CE0AC2-ECB2-4E8D-0000-000000000001} + {73CE0AC2-ECB2-4E8D-0000-00000000000E} {73CE0AC2-ECB2-4E8D-0000-000000000000} @@ -52,9 +49,6 @@ {73CE0AC2-ECB2-4E8D-0000-000000000004} - - {73CE0AC2-ECB2-4E8D-0000-000000000006} - {73CE0AC2-ECB2-4E8D-0000-000000000005} @@ -111,21 +105,6 @@ src\web - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - - - src\web\http - src\web @@ -203,60 +182,6 @@ include\bitcoin\server\web - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - - - include\bitcoin\server\web\http - include\bitcoin\server\web diff --git a/data/bs.cfg b/data/bs.cfg index f246fe84..7c6a7203 100644 --- a/data/bs.cfg +++ b/data/bs.cfg @@ -233,9 +233,9 @@ send_high_water = 100 receive_high_water = 100 # The time limit to complete the connection handshake, defaults to 30. handshake_seconds = 30 -# Disable all public endpoints, defaults to false. +# Disable public endpoints, defaults to false. secure_only = false -# The number of query worker threads, defaults to 1 (0 disables service). +# The number of query worker threads per endpoint, defaults to 1 (0 disables service). query_workers = 1 # The maximum number of query subscriptions, defaults to 1000 (0 disables subscribe). subscription_limit = 1000 @@ -274,15 +274,22 @@ enabled = true # The optional directory for serving files via HTTP/S, defaults to 'web'. root = web # The SSL certificate authority file, defaults to 'ca.pem', enables secure endpoints. -ca_certificate = ca.pem +ca_certificate = secure/ca.pem # The SSL private key file, defaults to 'key.pem', enables secure endpoints. -server_private_key = key.pem +server_private_key = secure/key.pem # The SSL certificate file, defaults to 'server.pem', enables secure endpoints. -server_certificate = server.pem +server_certificate = secure/server.pem # The SSL client certificates directory, defaults to 'clients'. client_certificates = clients # An acceptable websocket origin, multiple entries allowed. +origin = http://localhost:9061 +origin = http://localhost:9062 +origin = http://localhost:9063 +origin = http://localhost:9064 origin = http://localhost:9071 +origin = http://localhost:9072 +origin = http://localhost:9073 +origin = http://localhost:9074 [zeromq] # The secure query zeromq endpoint, defaults to 'tcp://*:9081'. @@ -305,41 +312,3 @@ public_transaction_endpoint = tcp://*:9094 #server_private_key = # Allowed Z85-encoded public key of the client, multiple entries allowed. #client_public_key = - -[bitcoin] -# The difficulty retargeting factor, defaults to 4. -retargeting_factor = 4 -# The target block period in seconds, defaults to 600. -block_spacing_seconds = 600 -# The future timestamp allowance in seconds, defaults to 7200. -timestamp_limit_seconds = 7200 -# The difficulty retargeting period in seconds, defaults to 1209600. -retargeting_interval_seconds = 1209600 -# The proof of work limit, defaults to 486604799. -proof_of_work_limit = 486604799 -# The genesis block (defaults to mainnet). -genesis_block = 0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000 -# The number of new version blocks required for bip34 style soft fork activation, defaults to 750. -activation_threshold = 750 -# The number of new version blocks required for bip34 style soft fork enforcement, defaults to 950. -enforcement_threshold = 950 -# The number of blocks considered for bip34 style soft fork activation, defaults to 1000. -activation_sample = 1000 -# The block height to freeze the bip65 softfork as in bip90, defaults to 388381. -bip65_freeze = 388381 -# The block height to freeze the bip66 softfork as in bip90, defaults to 363725. -bip66_freeze = 363725 -# The block height to freeze the bip34 softfork as in bip90, defaults to 227931. -bip34_freeze = 227931 -# The activation time for bip16 in unix time, defaults to 1333238400. -bip16_activation_time = 1333238400 -# The hash:height checkpoint for bip34 activation, defaults to 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8:227931. -bip34_active_checkpoint = 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8:227931 -# The hash:height checkpoint for bip9 bit0 activation, defaults to 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5:419328. -bip9_bit0_active_checkpoint = 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5:419328 -# The hash:height checkpoint for bip9 bit1 activation, defaults to 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893:481824. -bip9_bit1_active_checkpoint = 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893:481824 -# The initial block subsidy in bitcoin, defaults to 50. -initial_block_subsidy_bitcoin = 50 -# The subsidy halving period in number of blocks, defaults to 210000. -subsidy_interval = 210000 diff --git a/include/bitcoin/server.hpp b/include/bitcoin/server.hpp index 52950cb8..bbb5dc7e 100644 --- a/include/bitcoin/server.hpp +++ b/include/bitcoin/server.hpp @@ -37,24 +37,6 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include diff --git a/include/bitcoin/server/server_node.hpp b/include/bitcoin/server/server_node.hpp index 1368608a..d900fec4 100644 --- a/include/bitcoin/server/server_node.hpp +++ b/include/bitcoin/server/server_node.hpp @@ -125,15 +125,13 @@ class BCS_API server_node notification_worker public_notification_worker_; // Websocket services -#ifdef WITH_MBEDTLS query_socket secure_query_websockets_; - heartbeat_socket secure_heartbeat_websockets_; - block_socket secure_block_websockets_; - transaction_socket secure_transaction_websockets_; -#endif query_socket public_query_websockets_; + heartbeat_socket secure_heartbeat_websockets_; heartbeat_socket public_heartbeat_websockets_; + block_socket secure_block_websockets_; block_socket public_block_websockets_; + transaction_socket secure_transaction_websockets_; transaction_socket public_transaction_websockets_; }; diff --git a/include/bitcoin/server/settings.hpp b/include/bitcoin/server/settings.hpp index 3bf592aa..3bb56f5a 100644 --- a/include/bitcoin/server/settings.hpp +++ b/include/bitcoin/server/settings.hpp @@ -73,12 +73,6 @@ class BCS_API settings system::config::endpoint websockets_public_transaction_endpoint; bool websockets_enabled; - boost::filesystem::path websockets_root; - boost::filesystem::path websockets_ca_certificate; - boost::filesystem::path websockets_server_private_key; - boost::filesystem::path websockets_server_certificate; - boost::filesystem::path websockets_client_certificates; - config::endpoint::list websockets_origins; /// [zeromq] system::config::endpoint zeromq_secure_query_endpoint; diff --git a/include/bitcoin/server/web/block_socket.hpp b/include/bitcoin/server/web/block_socket.hpp index 8ba95ca9..8c70c073 100644 --- a/include/bitcoin/server/web/block_socket.hpp +++ b/include/bitcoin/server/web/block_socket.hpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -21,7 +21,7 @@ #include #include -#include +#include namespace libbitcoin { namespace server { @@ -31,24 +31,27 @@ class server_node; // This class is thread safe. // Subscribe to block acceptances from a dedicated socket endpoint. class BCS_API block_socket - : public http::socket + : public bc::protocol::http::socket { public: typedef std::shared_ptr ptr; /// Construct a block socket service endpoint. - block_socket(bc::protocol::zmq::context& context, server_node& node - , bool secure); + block_socket(bc::protocol::zmq::context& context, server_node& node, + bool secure); protected: // Implement the service. virtual void work() override; - virtual const config::endpoint& zeromq_endpoint() const override; - virtual const config::endpoint& websocket_endpoint() const override; + virtual const system::config::endpoint& zeromq_endpoint() const override; + virtual const system::config::endpoint& websocket_endpoint() const override; private: bool handle_block(bc::protocol::zmq::socket& subscriber); + + const bc::server::settings& settings_; + const bc::protocol::settings& protocol_settings_; }; } // namespace server diff --git a/include/bitcoin/server/web/heartbeat_socket.hpp b/include/bitcoin/server/web/heartbeat_socket.hpp index bb6fd6d8..e0ec0519 100644 --- a/include/bitcoin/server/web/heartbeat_socket.hpp +++ b/include/bitcoin/server/web/heartbeat_socket.hpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -19,13 +19,9 @@ #ifndef LIBBITCOIN_SERVER_WEB_HEARTBEAT_SOCKET_HPP #define LIBBITCOIN_SERVER_WEB_HEARTBEAT_SOCKET_HPP -#include -#include -#include #include #include #include -#include namespace libbitcoin { namespace server { @@ -35,7 +31,7 @@ class server_node; // This class is thread safe. // Subscribe to a pulse from a dedicated socket endpoint. class BCS_API heartbeat_socket - : public http::socket + : public bc::protocol::http::socket { public: typedef std::shared_ptr ptr; @@ -49,11 +45,14 @@ class BCS_API heartbeat_socket // Implement the service. virtual void work() override; - virtual const config::endpoint& zeromq_endpoint() const override; - virtual const config::endpoint& websocket_endpoint() const override; + virtual const system::config::endpoint& zeromq_endpoint() const override; + virtual const system::config::endpoint& websocket_endpoint() const override; private: bool handle_heartbeat(bc::protocol::zmq::socket& subscriber); + + const bc::server::settings& settings_; + const bc::protocol::settings& protocol_settings_; }; } // namespace server diff --git a/include/bitcoin/server/web/http/bind_options.hpp b/include/bitcoin/server/web/http/bind_options.hpp deleted file mode 100644 index 7edade75..00000000 --- a/include/bitcoin/server/web/http/bind_options.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_BIND_OPTIONS_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_BIND_OPTIONS_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -struct BCS_API bind_options -{ - void* user_data; - uint32_t flags; - boost::filesystem::path ssl_key; - boost::filesystem::path ssl_certificate; - boost::filesystem::path ssl_ca_certificate; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/connection.hpp b/include/bitcoin/server/web/http/connection.hpp deleted file mode 100644 index 92b5aa15..00000000 --- a/include/bitcoin/server/web/http/connection.hpp +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_CONNECTION_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -class connection; - -// TODO: make internal connection typedefs. -typedef std::shared_ptr connection_ptr; -typedef std::set connection_set; -typedef std::vector connection_list; -typedef std::function event_handler; - -// This class is instantiated from accepted/incoming HTTP clients. -// Initiating outgoing HTTP connections are not currently supported. -class BCS_API connection -{ -public: - typedef std::function write_method; - - connection(); - connection(sock_t connection, const sockaddr_in& address); - ~connection(); - - // Properties. - // ------------------------------------------------------------------------ - - connection_state state() const; - void set_state(connection_state state); - - void* user_data(); - void set_user_data(void* user_data); - - bool websocket() const; - void set_websocket(bool websocket); - - bool json_rpc() const; - void set_json_rpc(bool json_rpc); - - // The connection endpoint is a request uri, such as '/'. - const std::string& uri() const; - void set_uri(const std::string& uri); - - // Readers and Writers. - // ------------------------------------------------------------------------ - // Signed integer results overload negative range for error code. - - http::read_buffer& read_buffer(); - data_chunk& write_buffer(); - - int32_t read(); - int32_t read_length(); - - int32_t write(const data_chunk& buffer); - int32_t write(const std::string& buffer); - int32_t write(const uint8_t* data, size_t length); - - int32_t unbuffered_write(const data_chunk& buffer); - int32_t unbuffered_write(const std::string& buffer); - int32_t unbuffered_write(const uint8_t* data, size_t length); - - // Other. - // ------------------------------------------------------------------------ - - void set_socket_non_blocking(); - sockaddr_in address() const; - bool reuse_address() const; - bool closed() const; - void close(); - sock_t& socket(); - http::ssl& ssl_context(); - bool ssl_enabled() const; - - http::file_transfer& file_transfer(); - http::websocket_transfer& websocket_transfer(); - - // Operator overloads. - // ------------------------------------------------------------------------ - - bool operator==(const connection& other); - -private: - void* user_data_; - connection_state state_; - sock_t socket_; - sockaddr_in address_; - asio::time_point last_active_; - ssl ssl_context_; - std::string uri_; - bool websocket_; - bool json_rpc_; - - // Transfer states used for read continuations, particularly for when the - // read_buffer_ size is too small to hold all of the incoming data. - http::file_transfer file_transfer_; - http::websocket_transfer websocket_transfer_; - - int32_t bytes_read_; - http::read_buffer read_buffer_; - data_chunk write_buffer_; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/connection_state.hpp b/include/bitcoin/server/web/http/connection_state.hpp deleted file mode 100644 index af5c4b7c..00000000 --- a/include/bitcoin/server/web/http/connection_state.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_CONNECTION_STATE_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_CONNECTION_STATE_HPP - -namespace libbitcoin { -namespace server { -namespace http { - -enum class connection_state -{ - error = -1, - connecting = 99, - connected = 100, - listening = 101, - ssl_handshake = 102, - closed = 103, - disconnect_immediately = 200, - unknown -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/event.hpp b/include/bitcoin/server/web/http/event.hpp deleted file mode 100644 index 1f7058dc..00000000 --- a/include/bitcoin/server/web/http/event.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_EVENT_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_EVENT_HPP - -#include - -namespace libbitcoin { -namespace server { -namespace http { - -enum class event : uint8_t -{ - read, - write, - listen, - accepted, - error, - closing, - websocket_frame, - websocket_control_frame, - json_rpc -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/file_transfer.hpp b/include/bitcoin/server/web/http/file_transfer.hpp deleted file mode 100644 index b93b300b..00000000 --- a/include/bitcoin/server/web/http/file_transfer.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_FILE_TRANSFER_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_FILE_TRANSFER_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -struct BCS_API file_transfer -{ - bool in_progress; - FILE* descriptor; - size_t offset; - size_t length; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/http.hpp b/include/bitcoin/server/web/http/http.hpp deleted file mode 100644 index b0f5e330..00000000 --- a/include/bitcoin/server/web/http/http.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_HTTP_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_HTTP_HPP - -#include -#include -#include -#include -#include -#include - -// Centrally including headers here. -#ifdef _MSC_VER - #include - #include - #include - #include - #include -#else - #include - #include - #include - #include - #include - #include - #include -#endif - -// Centrally including headers here. -#ifdef WITH_MBEDTLS - #include - #include - #include - #include - #include - #include - #include -#endif - -namespace libbitcoin { -namespace server { -namespace http { - -#ifdef _MSC_VER - typedef SOCKET sock_t; - typedef uint32_t in_addr_t; - #define CLOSE_SOCKET closesocket -#else - typedef uint32_t sock_t; - #define CLOSE_SOCKET ::close -#endif - -#ifdef WITH_MBEDTLS - static const int32_t default_ciphers[] = - { - MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, - MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, - MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - 0 - }; -#endif - -static const size_t default_buffer_length = 1 * 1024; -static const size_t transfer_buffer_length = 256 * 1024; - -typedef std::array read_buffer; -typedef std::unordered_map string_map; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/http_reply.hpp b/include/bitcoin/server/web/http/http_reply.hpp deleted file mode 100644 index d3047d62..00000000 --- a/include/bitcoin/server/web/http/http_reply.hpp +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_HTTP_REPLY_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_HTTP_REPLY_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -// TODO: move implementation to cpp. -class BCS_API http_reply -{ -public: - static std::string to_string(protocol_status status) - { - struct protocol_status_hasher - { - size_t operator()(const protocol_status& status) const - { - return std::hash{}(static_cast(status)); - } - }; - - typedef std::unordered_map status_map; - static const status_map status_strings - { - { protocol_status::switching, "HTTP/1.1 101 Switching Protocols\r\n" }, - { protocol_status::ok, "HTTP/1.0 200 OK\r\n" }, - { protocol_status::created, "HTTP/1.0 201 Created\r\n" }, - { protocol_status::accepted, "HTTP/1.0 202 Accepted\r\n" }, - { protocol_status::no_content, "HTTP/1.0 204 No Content\r\n" }, - { protocol_status::multiple_choices, "HTTP/1.0 300 Multiple Choices\r\n" }, - { protocol_status::moved_permanently, "HTTP/1.0 301 Moved Permanently\r\n" }, - { protocol_status::moved_temporarily, "HTTP/1.0 302 Moved Temporarily\r\n" }, - { protocol_status::not_modified, "HTTP/1.0 304 Not Modified\r\n" }, - { protocol_status::bad_request, "HTTP/1.0 400 Bad Request\r\n" }, - { protocol_status::unauthorized, "HTTP/1.0 401 Unauthorized\r\n" }, - { protocol_status::forbidden, "HTTP/1.0 403 Forbidden\r\n" }, - { protocol_status::not_found, "HTTP/1.0 404 Not Found\r\n" }, - { protocol_status::internal_server_error, "HTTP/1.0 500 Internal Server Error\r\n" }, - { protocol_status::not_implemented, "HTTP/1.0 501 Not Implemented\r\n" }, - { protocol_status::bad_gateway, "HTTP/1.0 502 Bad Gateway\r\n" }, - { protocol_status::service_unavailable, "HTTP/1.0 503 Service Unavailable\r\n" } - }; - - const auto it = status_strings.find(status); - return it == status_strings.end() ? std::string{} : it->second; - } - - static std::string generate(protocol_status status, std::string mime_type, - size_t content_length, bool keep_alive) - { - static const size_t max_date_time_length = 32; - std::array time_buffer; - const auto current_time = std::time(nullptr); - - // BUGBUG: std::gmtime may not be thread safe. - std::strftime(time_buffer.data(), time_buffer.size(), - "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(¤t_time)); - - std::stringstream response; - response - << to_string(status) - << "Date: " << time_buffer.data() << "\r\n" - << "Accept-Ranges: none\r\n" - << "Connection: " << (keep_alive ? "keep-alive" : "close") - << "\r\n"; - - if (!mime_type.empty()) - response << "Content-Type: " << mime_type << "\r\n"; - - if (content_length > 0) - response << "Content-Length: " << content_length << "\r\n"; - - response << "\r\n"; - return response.str(); - } - - static std::string generate_upgrade(const std::string& key_response, - const std::string& protocol) - { - std::stringstream response; - response - << to_string(protocol_status::switching) - << "Upgrade: websocket\r\n" - << "Connection: Upgrade" << "\r\n"; - - if (!protocol.empty()) - response << protocol << "\r\n"; - - response << "Sec-WebSocket-Accept: " << key_response << "\r\n\r\n"; - return response.str(); - } - - protocol_status status; - string_map headers; - std::string content; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/http_request.hpp b/include/bitcoin/server/web/http/http_request.hpp deleted file mode 100644 index 0596fa65..00000000 --- a/include/bitcoin/server/web/http/http_request.hpp +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_HTTP_REQUEST_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_HTTP_REQUEST_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -// TODO: move implementation to cpp. -class BCS_API http_request -{ -public: - http_request() - : method({}), - uri({}), - protocol({}), - protocol_version(0.0f), - message_length(0), - content_length(0), - headers({}), - parameters({}), - upgrade_request(false), - json_rpc(false) - { - } - - std::string find(const string_map& haystack, - const std::string& needle) const - { - const auto it = haystack.find(needle); - return it == haystack.end() ? std::string{} : it->second; - } - - std::string header(std::string header) const - { - boost::algorithm::to_lower(header); - return find(headers, header); - } - - std::string parameter(std::string parameter) const - { - boost::algorithm::to_lower(parameter); - return find(parameters, parameter); - } - - std::string method; - std::string uri; - std::string protocol; - double protocol_version; - size_t message_length; - size_t content_length; - string_map headers; - string_map parameters; - bool upgrade_request; - bool json_rpc; - boost::property_tree::ptree json_tree; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/json_string.hpp b/include/bitcoin/server/web/http/json_string.hpp deleted file mode 100644 index 56304f24..00000000 --- a/include/bitcoin/server/web/http/json_string.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_JSON_STRING_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_JSON_STRING_HPP - -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -// Object to JSON converters. -//----------------------------------------------------------------------------- - -BCS_API std::string to_json(const boost::property_tree::ptree& tree); -BCS_API std::string to_json(uint64_t height, uint32_t id); -BCS_API std::string to_json(const code& code, uint32_t id); -BCS_API std::string to_json(const chain::header& header, uint32_t id); -BCS_API std::string to_json(const chain::block& block, uint32_t id); -BCS_API std::string to_json(const chain::block& block, uint32_t height, - uint32_t id); -BCS_API std::string to_json(const chain::transaction& transaction, - uint32_t id); - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/manager.hpp b/include/bitcoin/server/web/http/manager.hpp deleted file mode 100644 index 41dde486..00000000 --- a/include/bitcoin/server/web/http/manager.hpp +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_MANAGER_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_MANAGER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -class BCS_API manager -{ -public: - class task - { - public: - virtual ~task() = default; - virtual bool run() = 0; - virtual connection_ptr connection() = 0; - }; - - typedef boost::filesystem::path path; - typedef std::shared_ptr task_ptr; - typedef std::vector task_list; - typedef std::shared_ptr ptr; - typedef std::function handler; - typedef std::vector origin_list; - - manager(bool ssl, event_handler handler, path document_root, - const origin_list origins); - ~manager(); - - bool initialize(); - bool bind(const config::endpoint& address, const bind_options& options); - - // Connections. - bool accept_connection(); - void add_connection(connection_ptr connection); - void remove_connection(connection_ptr connection); - size_t connection_count() const; - - bool ssl() const; - bool listening() const; - bool stopped() const; - - void start(); - // Same as start above, but allows a callback to be called after - // each iteration of run_once. - void start(handler callback); - void stop(); - void execute(task_ptr task); - void run_tasks(); - - void poll(size_t timeout_milliseconds); - bool handle_connection(connection_ptr connection, event current_event); - -private: -#ifdef WITH_MBEDTLS - // Passed to mbedtls for internal use only. - static int32_t ssl_send(void* data, const uint8_t* buffer, size_t length); - static int32_t ssl_receive(void* data, uint8_t* buffer, size_t length); -#endif - - void run_once(); - void select(size_t timeout_milliseconds, connection_list& sockets); - bool transfer_file_data(connection_ptr connection); - bool send_http_file(connection_ptr connection, const path& path, bool keep_alive); - bool handle_websocket(connection_ptr connection); - bool send_response(connection_ptr connection, const http_request& request); - bool send_generated_reply(connection_ptr connection, protocol_status status); - bool upgrade_connection(connection_ptr connection, const http_request& request); - bool validate_origin(const std::string& origin); - bool initialize_ssl(connection_ptr connection, bool listener); - - // These are thread safe. - const bool ssl_; - std::atomic running_; - std::atomic listening_; - - // Initialize is not thread safe. - bool initialized_; - - // Bind is not thread safe. - uint16_t port_; - - void* user_data_; - path key_; - path certificate_; - path ca_certificate_; - event_handler handler_; - path document_root_; - connection_list connections_; - connection_ptr listener_; - sockaddr_in listener_address_; - - // This is protected by mutex. - task_list tasks_; - shared_mutex task_mutex_; - - const origin_list origins_; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/protocol_status.hpp b/include/bitcoin/server/web/http/protocol_status.hpp deleted file mode 100644 index 89a2a14a..00000000 --- a/include/bitcoin/server/web/http/protocol_status.hpp +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_PROTOCOL_STATUS_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_PROTOCOL_STATUS_HPP - -#include - -namespace libbitcoin { -namespace server { -namespace http { - -enum class protocol_status : uint16_t -{ - switching = 101, - ok = 200, - created = 201, - accepted = 202, - no_content = 204, - multiple_choices = 300, - moved_permanently = 301, - moved_temporarily = 302, - not_modified = 304, - bad_request = 400, - unauthorized = 401, - forbidden = 403, - not_found = 404, - internal_server_error = 500, - not_implemented = 501, - bad_gateway = 502, - service_unavailable = 503 -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/socket.hpp b/include/bitcoin/server/web/http/socket.hpp deleted file mode 100644 index d97d107e..00000000 --- a/include/bitcoin/server/web/http/socket.hpp +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_SOCKET_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef WITH_MBEDTLS -extern "C" -{ -uint32_t https_random(void*, uint8_t* buffer, size_t length); -} -#endif - -namespace libbitcoin { -namespace server { - -// TODO: eliminate server_node dependency, for move to protocol. -class server_node; - -namespace http { - -class BCS_API socket - : public bc::protocol::zmq::worker -{ -public: - class query_response_task - { - public: - virtual ~query_response_task() = default; - virtual bool run() = 0; - }; - - typedef std::shared_ptr query_response_task_ptr; - typedef std::vector query_response_task_list; - - // Handles translation of incoming JSON to zmq protocol methods and - // converting the result back to JSON for web clients. - struct handlers - { - typedef std::function encode_handler; - typedef std::function decode_handler; - - std::string command; - encode_handler encode; - decode_handler decode; - }; - - typedef std::unordered_map handler_map; - - // Tracks websocket queries via the query_work_map. Used for matching - // websocket client requests to zmq query responses. - struct query_work_item - { - uint32_t id; - uint32_t correlation_id; - connection_ptr connection; - std::string command; - std::string arguments; - }; - - typedef std::unordered_map> - query_correlation_map; - - typedef std::unordered_map query_work_map; - typedef std::unordered_map - connection_work_map; - - /// Construct a socket class. - socket(bc::protocol::zmq::context& context, server_node& node, - bool secure); - - /// Start the service. - bool start() override; - - void queue_response(uint32_t sequence, const data_chunk& data, - const std::string& command); - bool send_query_responses(); - - size_t connection_count() const; - void add_connection(connection_ptr connection); - void remove_connection(connection_ptr connection); - void notify_query_work(connection_ptr connection, - const std::string& method, uint32_t id, const std::string& parameters); - -protected: - // Initialize the websocket event loop and start a thread to poll events. - virtual bool start_websocket_handler(); - - // Terminate the websocket event loop. - virtual bool stop_websocket_handler(); - - virtual void handle_websockets(); - - virtual const config::endpoint& zeromq_endpoint() const = 0; - virtual const config::endpoint& websocket_endpoint() const = 0; - virtual const std::shared_ptr service() const; - - // Send a message to the websocket client. - void send(connection_ptr connection, const std::string& json); - - // Send a message to every connected websocket client. - void broadcast(const std::string& json); - - // The zmq socket operates on only this one thread. - bc::protocol::zmq::context& context_; - const bool secure_; - const std::string security_; - const bc::server::settings& server_settings_; - const bc::protocol::settings& protocol_settings_; - - // handlers_ is effectively const. - handler_map handlers_; - - // For query socket, service() is used to retrieve the zmq socket - // connected to the query_socket service. This socket operates on - // only the below member thread_. - std::shared_ptr thread_; - std::promise socket_started_; - - // Used by the query_socket class. - uint32_t sequence_; - connection_work_map work_; - query_correlation_map correlations_; - -private: - static bool handle_event(connection_ptr connection, http::event event, - const void* data); - - manager::ptr manager_; - const config::endpoint::list origins_; - const boost::filesystem::path document_root_; - - // This is protected by mutex. - query_response_task_list query_response_tasks_; - shared_mutex query_response_task_mutex_; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/ssl.hpp b/include/bitcoin/server/web/http/ssl.hpp deleted file mode 100644 index 9b9704e3..00000000 --- a/include/bitcoin/server/web/http/ssl.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_SSL_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_SSL_HPP - -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -struct ssl -{ - bool enabled; - std::string hostname; -#ifdef WITH_MBEDTLS - mbedtls_ssl_context context; - mbedtls_ssl_config configuration; - mbedtls_pk_context key; - mbedtls_x509_crt certificate; - mbedtls_x509_crt ca_certificate; -#endif -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/utilities.hpp b/include/bitcoin/server/web/http/utilities.hpp deleted file mode 100644 index 43b46aef..00000000 --- a/include/bitcoin/server/web/http/utilities.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_UTILITIES_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_UTILITIES_HPP - -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -#ifdef _MSC_VER - #define last_error() GetLastError() -#else - #define last_error() errno -#endif - -#ifdef _MSC_VER - #define would_block(value) (value == WSAEWOULDBLOCK) - #define WOULD_BLOCK WSAEWOULDBLOCK -#else - #define would_block(value) (value == EAGAIN || value == EWOULDBLOCK) - #define WOULD_BLOCK EWOULDBLOCK -#endif - -#ifdef WITH_MBEDTLS - std::string mbedtls_error_string(int32_t error); - #define mbedtls_would_block(value) \ - (value == MBEDTLS_ERR_SSL_WANT_READ || \ - value == MBEDTLS_ERR_SSL_WANT_WRITE) -#endif - -BCS_API std::string error_string(); -BCS_API std::string op_to_string(websocket_op code); -BCS_API std::string websocket_key_response(const std::string& websocket_key); -BCS_API bool is_json_request(const std::string& header_value); -BCS_API bool parse_http(http_request& out, const std::string& request); -BCS_API std::string mime_type(const boost::filesystem::path& path); - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/websocket_frame.hpp b/include/bitcoin/server/web/http/websocket_frame.hpp deleted file mode 100644 index 57733295..00000000 --- a/include/bitcoin/server/web/http/websocket_frame.hpp +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_WEBSOCKET_FRAME_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_FRAME_HPP - -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -// TODO: move implementation to cpp. -class BCS_API websocket_frame -{ -public: - websocket_frame(const uint8_t* data, size_t size) - : valid_(false), flags_(0), header_(0), data_(0) - { - from_data(data, size); - } - - static data_chunk to_header(size_t length, websocket_op code) - { - if (length < 0x7e) - { - return build_chunk( - { - to_array(uint8_t(0x80) | static_cast(code)), - to_array(static_cast(length)) - }); - } - else if (length < max_uint16) - { - return build_chunk( - { - to_array(uint8_t(0x80) | static_cast(code)), - to_array(uint8_t(0x7e)), - to_big_endian(static_cast(length)) - }); - } - else - { - return build_chunk( - { - to_array(uint8_t(0x80) | static_cast(code)), - to_array(uint8_t(0x7f)), - to_big_endian(static_cast(length)) - }); - } - } - - operator bool() const - { - return valid_; - } - - bool final() const - { - return (flags_ & 0x80) != 0; - } - - bool fragment() const - { - return !final() || op_code() == websocket_op::continuation; - } - - event event_type() const - { - return (flags_ & 0x08) ? event::websocket_control_frame : - event::websocket_frame; - } - - websocket_op op_code() const - { - return static_cast(flags_ & 0x0f); - } - - uint8_t flags() const - { - return flags_; - } - - size_t header_length() const - { - return header_; - } - - size_t data_length() const - { - return data_; - } - - size_t mask_length() const - { - return valid_ ? mask_ : 0; - } - -private: - void from_data(const uint8_t* data, size_t read_length) - { - static constexpr size_t prefix = 2; - static constexpr size_t prefix16 = prefix + sizeof(uint16_t); - static constexpr size_t prefix64 = prefix + sizeof(uint64_t); - - // Invalid websocket frame (too small). - // Invalid websocket frame (unmasked). - if (read_length < 2 || (data[1] & 0x80) == 0) - return; - - flags_ = data[0]; - const size_t length = (data[1] & 0x7f); - - if (read_length >= mask_ && length < 0x7e) - { - header_ = prefix + mask_; - data_ = length; - } - else if (read_length >= prefix16 + mask_ && length == 0x7e) - { - header_ = prefix16 + mask_; - data_ = from_big_endian(&data[prefix], - &data[prefix16]); - } - else if (read_length >= prefix64 + mask_) - { - header_ = prefix64 + mask_; - data_ = from_big_endian(&data[prefix], - &data[prefix64]); - } - - valid_ = true; - return; - } - -private: - static const size_t mask_ = 4; - - bool valid_; - uint8_t flags_; - size_t header_; - size_t data_; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/websocket_message.hpp b/include/bitcoin/server/web/http/websocket_message.hpp deleted file mode 100644 index 9406d351..00000000 --- a/include/bitcoin/server/web/http/websocket_message.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_WEBSOCKET_MESSAGE_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_MESSAGE_HPP - -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -struct BCS_API websocket_message -{ - const std::string& endpoint; - const uint8_t* data; - size_t size; - uint8_t flags; - websocket_op code; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/websocket_op.hpp b/include/bitcoin/server/web/http/websocket_op.hpp deleted file mode 100644 index 270e0fd4..00000000 --- a/include/bitcoin/server/web/http/websocket_op.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_WEBSOCKET_OP_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_OP_HPP - -#include - -namespace libbitcoin { -namespace server { -namespace http { - -enum class websocket_op : uint8_t -{ - continuation = 0, - text = 1, - binary = 2, - close = 8, - ping = 9, - pong = 10, -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/http/websocket_transfer.hpp b/include/bitcoin/server/web/http/websocket_transfer.hpp deleted file mode 100644 index 51d7c4ec..00000000 --- a/include/bitcoin/server/web/http/websocket_transfer.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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_SERVER_WEB_HTTP_WEBSOCKET_TRANSFER_HPP -#define LIBBITCOIN_SERVER_WEB_HTTP_WEBSOCKET_TRANSFER_HPP - -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -struct BCS_API websocket_transfer -{ - bool in_progress; - size_t offset; - size_t length; - size_t header_length; - data_chunk mask; - data_chunk data; -}; - -} // namespace http -} // namespace server -} // namespace libbitcoin - -#endif diff --git a/include/bitcoin/server/web/query_socket.hpp b/include/bitcoin/server/web/query_socket.hpp index 22c32361..114577e3 100644 --- a/include/bitcoin/server/web/query_socket.hpp +++ b/include/bitcoin/server/web/query_socket.hpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -19,12 +19,9 @@ #ifndef LIBBITCOIN_SERVER_WEB_QUERY_SOCKET_HPP #define LIBBITCOIN_SERVER_WEB_QUERY_SOCKET_HPP -#include -#include #include #include #include -#include namespace libbitcoin { namespace server { @@ -35,7 +32,7 @@ class server_node; // Submit queries and address subscriptions and receive address // notifications on a dedicated socket endpoint. class BCS_API query_socket - : public http::socket + : public bc::protocol::http::socket { public: typedef std::shared_ptr ptr; @@ -53,16 +50,18 @@ class BCS_API query_socket // Initialize the query specific zmq socket. virtual void handle_websockets() override; - virtual const config::endpoint& zeromq_endpoint() const override; - virtual const config::endpoint& websocket_endpoint() const override; + virtual const system::config::endpoint& zeromq_endpoint() const override; + virtual const system::config::endpoint& websocket_endpoint() const override; virtual const std::shared_ptr service() const override; - const config::endpoint& query_endpoint() const; + const system::config::endpoint& query_endpoint() const; private: bool handle_query(bc::protocol::zmq::socket& dealer); + const bc::server::settings& settings_; + const bc::protocol::settings& protocol_settings_; std::shared_ptr service_; }; diff --git a/include/bitcoin/server/web/transaction_socket.hpp b/include/bitcoin/server/web/transaction_socket.hpp index 17753575..40117f61 100644 --- a/include/bitcoin/server/web/transaction_socket.hpp +++ b/include/bitcoin/server/web/transaction_socket.hpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -19,13 +19,9 @@ #ifndef LIBBITCOIN_SERVER_WEB_TRANSACTION_SOCKET_HPP #define LIBBITCOIN_SERVER_WEB_TRANSACTION_SOCKET_HPP -#include -#include -#include #include #include #include -#include namespace libbitcoin { namespace server { @@ -35,7 +31,7 @@ class server_node; // This class is thread safe. // Subscribe to tx acceptances into the pool from a dedicated socket endpoint. class BCS_API transaction_socket - : public http::socket + : public bc::protocol::http::socket { public: typedef std::shared_ptr ptr; @@ -49,11 +45,14 @@ class BCS_API transaction_socket // Implement the service. virtual void work() override; - virtual const config::endpoint& zeromq_endpoint() const override; - virtual const config::endpoint& websocket_endpoint() const override; + virtual const system::config::endpoint& zeromq_endpoint() const override; + virtual const system::config::endpoint& websocket_endpoint() const override; private: bool handle_transaction(bc::protocol::zmq::socket& subscriber); + + const bc::server::settings& settings_; + const bc::protocol::settings& protocol_settings_; }; } // namespace server diff --git a/src/parser.cpp b/src/parser.cpp index a5617211..04c6d5db 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -716,32 +716,32 @@ options_metadata parser::load_settings() ) ( "websockets.root", - value(&configured.server.websockets_root), + value(&configured.protocol.web_root), "The optional directory for serving files via HTTP/S, defaults to 'web'." ) ( "websockets.ca_certificate", - value(&configured.server.websockets_ca_certificate), + value(&configured.protocol.web_ca_certificate), "The SSL certificate authority file, defaults to '', enables secure endpoints." ) ( "websockets.server_private_key", - value(&configured.server.websockets_server_private_key), + value(&configured.protocol.web_server_private_key), "The SSL private key file, defaults to 'key.pem', enables secure endpoints." ) ( "websockets.server_certificate", - value(&configured.server.websockets_server_certificate), + value(&configured.protocol.web_server_certificate), "The SSL certificate file, defaults to 'server.pem', enables secure endpoints." ) ( "websockets.client_certificates", - value(&configured.server.websockets_client_certificates), + value(&configured.protocol.web_client_certificates), "The SSL client certificates directory, defaults to 'clients'." ) ( "websockets.origin", - value(&configured.server.websockets_origins), + value(&configured.protocol.web_origins), "An acceptable websocket origin, multiple entries allowed." ) diff --git a/src/server_node.cpp b/src/server_node.cpp index 5966a743..404195b2 100644 --- a/src/server_node.cpp +++ b/src/server_node.cpp @@ -50,15 +50,13 @@ server_node::server_node(const configuration& configuration) public_transaction_service_(authenticator_, *this, false), secure_notification_worker_(authenticator_, *this, true), public_notification_worker_(authenticator_, *this, false), -#ifdef WITH_MBEDTLS secure_query_websockets_(authenticator_, *this, true), - secure_heartbeat_websockets_(authenticator_, *this, true), - secure_block_websockets_(authenticator_, *this, true), - secure_transaction_websockets_(authenticator_, *this, true), -#endif public_query_websockets_(authenticator_, *this, false), + secure_heartbeat_websockets_(authenticator_, *this, true), public_heartbeat_websockets_(authenticator_, *this, false), + secure_block_websockets_(authenticator_, *this, true), public_block_websockets_(authenticator_, *this, false), + secure_transaction_websockets_(authenticator_, *this, true), public_transaction_websockets_(authenticator_, *this, false) { } @@ -218,12 +216,10 @@ bool server_node::start_query_services() if (settings.websockets_enabled) { -#ifdef WITH_MBEDTLS // Start secure service if enabled. if (settings.zeromq_server_private_key && !secure_query_websockets_.start()) return false; -#endif // Start public service if enabled. if (!settings.secure_only && !public_query_websockets_.start()) @@ -251,12 +247,10 @@ bool server_node::start_heartbeat_services() if (settings.websockets_enabled) { -#ifdef WITH_MBEDTLS // Start secure service if enabled. if (settings.zeromq_server_private_key && !secure_heartbeat_websockets_.start()) return false; -#endif // Start public service if enabled. if (!settings.secure_only && !public_heartbeat_websockets_.start()) @@ -283,12 +277,10 @@ bool server_node::start_block_services() if (settings.websockets_enabled) { -#ifdef WITH_MBEDTLS // Start secure service if enabled. if (settings.zeromq_server_private_key && !secure_block_websockets_.start()) return false; -#endif // Start public service if enabled. if (!settings.secure_only && !public_block_websockets_.start()) @@ -316,12 +308,10 @@ bool server_node::start_transaction_services() if (settings.websockets_enabled) { -#ifdef WITH_MBEDTLS // Start secure service if enabled. if (settings.zeromq_server_private_key && !secure_transaction_websockets_.start()) return false; -#endif // Start public service if enabled. if (!settings.secure_only && !public_transaction_websockets_.start()) diff --git a/src/settings.cpp b/src/settings.cpp index cc676e4c..bcc65d04 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -48,12 +48,6 @@ settings::settings() websockets_public_transaction_endpoint("tcp://*:9074"), websockets_enabled(true), - websockets_root("web"), - websockets_ca_certificate(""), - websockets_server_private_key("key.pem"), - websockets_server_certificate("server.pem"), - websockets_client_certificates("clients"), - websockets_origins{}, // [zeromq] zeromq_secure_query_endpoint("tcp://*:9081"), diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index f17050e9..99fb287f 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -26,22 +26,23 @@ #include #include #include -#include namespace libbitcoin { namespace server { -using namespace std::placeholders; -using namespace bc::chain; using namespace bc::protocol; -using namespace http; +using namespace bc::system; +using namespace bc::system::chain; +using namespace bc::system::config; using role = zmq::socket::role; static constexpr auto poll_interval_milliseconds = 100u; block_socket::block_socket(zmq::context& context, server_node& node, bool secure) - : http::socket(context, node, secure) + : http::socket(context, node.protocol_settings(), secure), + settings_(node.server_settings()), + protocol_settings_(node.protocol_settings()) { } @@ -128,8 +129,8 @@ bool block_socket::handle_block(zmq::socket& subscriber) response.dequeue(block_data); // Format and send transaction to websocket subscribers. - const auto block = block::factory(block_data, true); - broadcast(to_json(block, height, sequence)); + const auto block = system::chain::block::factory(block_data, true); + broadcast(http::to_json(block, height, sequence)); LOG_VERBOSE(LOG_SERVER) << "Broadcasted " << security_ << " socket block [" @@ -137,18 +138,18 @@ bool block_socket::handle_block(zmq::socket& subscriber) return true; } -const config::endpoint& block_socket::zeromq_endpoint() const +const endpoint& block_socket::zeromq_endpoint() const { // The Websocket to zeromq backend internally always uses the // local public zeromq endpoint since it does not affect the // external security of the websocket endpoint and impacts // configuration and performance for no additional gain. - return server_settings_.zeromq_block_endpoint(false /* secure_ */); + return settings_.zeromq_block_endpoint(false /* secure_ */); } -const config::endpoint& block_socket::websocket_endpoint() const +const endpoint& block_socket::websocket_endpoint() const { - return server_settings_.websockets_block_endpoint(secure_); + return settings_.websockets_block_endpoint(secure_); } } // namespace server diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index 8e6fa99d..e2433a98 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -23,21 +23,21 @@ #include #include #include -#include namespace libbitcoin { namespace server { static constexpr auto poll_interval_milliseconds = 100u; -using namespace bc::config; using namespace bc::protocol; -using namespace http; +using namespace bc::system::config; using role = zmq::socket::role; heartbeat_socket::heartbeat_socket(zmq::context& context, server_node& node, bool secure) - : http::socket(context, node, secure) + : http::socket(context, node.protocol_settings(), secure), + settings_(node.server_settings()), + protocol_settings_(node.protocol_settings()) { } @@ -123,7 +123,7 @@ bool heartbeat_socket::handle_heartbeat(zmq::socket& subscriber) response.dequeue(sequence); response.dequeue(height); - broadcast(to_json(height, sequence)); + broadcast(http::to_json(height, sequence)); LOG_VERBOSE(LOG_SERVER) << "Broadcasted " << security_ << " socket heartbeat [" << height @@ -131,18 +131,18 @@ bool heartbeat_socket::handle_heartbeat(zmq::socket& subscriber) return true; } -const config::endpoint& heartbeat_socket::zeromq_endpoint() const +const endpoint& heartbeat_socket::zeromq_endpoint() const { // The Websocket to zeromq backend internally always uses the // local public zeromq endpoint since it does not affect the // external security of the websocket endpoint and impacts // configuration and performance for no additional gain. - return server_settings_.zeromq_heartbeat_endpoint(false /* secure_ */); + return settings_.zeromq_heartbeat_endpoint(false /* secure_ */); } -const config::endpoint& heartbeat_socket::websocket_endpoint() const +const endpoint& heartbeat_socket::websocket_endpoint() const { - return server_settings_.websockets_heartbeat_endpoint(secure_); + return settings_.websockets_heartbeat_endpoint(secure_); } } // namespace server diff --git a/src/web/http/connection.cpp b/src/web/http/connection.cpp deleted file mode 100644 index bba3fc9f..00000000 --- a/src/web/http/connection.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 - -#ifdef _MSC_VER -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -static constexpr size_t maximum_read_length = 1024; -static constexpr size_t high_water_mark = 2 * 1024 * 1024; - -connection::connection() - : connection(0, sockaddr_in{}) -{ -} - -connection::connection(sock_t connection, const sockaddr_in& address) - : user_data_(nullptr), - state_(connection_state::unknown), - socket_(connection), - address_(address), - last_active_(asio::steady_clock::now()), - ssl_context_{}, - websocket_(false), - json_rpc_(false), - file_transfer_{}, - websocket_transfer_{}, - bytes_read_(0) -{ - write_buffer_.reserve(high_water_mark); -} - -connection::~connection() -{ - if (!closed()) - close(); -} - -connection_state connection::state() const -{ - return state_; -} - -void connection::set_state(connection_state state) -{ - state_ = state; -} - -void connection::set_socket_non_blocking() -{ -#ifdef _MSC_VER - ULONG non_blocking = 1; - ioctlsocket(socket_, FIONBIO, &non_blocking); -#else - fcntl(socket_, F_SETFL, fcntl(socket_, F_GETFD) | O_NONBLOCK); -#endif -} - -sockaddr_in connection::address() const -{ - return address_; -} - -bool connection::reuse_address() const -{ - static constexpr uint32_t opt = 1; - - // reinterpret_cast required for Win32, otherwise nop. - return setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, - reinterpret_cast(&opt), sizeof(opt)) != -1; -} - -bool connection::closed() const -{ - return state_ == connection_state::closed; -} - -int32_t connection::read() -{ -#ifdef WIN32 - // reinterpret_cast required for Win32, otherwise nop. - auto data = reinterpret_cast(read_buffer_.data()); -#else - auto data = read_buffer_.data(); -#endif - -#ifdef WITH_MBEDTLS - bytes_read_ = (ssl_context_.enabled ? - mbedtls_ssl_read(&ssl_context_.context, data, maximum_read_length) : - recv(socket_, data, maximum_read_length, 0)); -#else - bytes_read_ = recv(socket_, data, maximum_read_length, 0); -#endif - return bytes_read_; -} - -int32_t connection::read_length() -{ - return bytes_read_; -} - -http::read_buffer& connection::read_buffer() -{ - return read_buffer_; -} - -data_chunk& connection::write_buffer() -{ - return write_buffer_; -} - -int32_t connection::unbuffered_write(const data_chunk& buffer) -{ - return unbuffered_write(buffer.data(), buffer.size()); -} - -int32_t connection::unbuffered_write(const std::string& buffer) -{ - const auto data = reinterpret_cast(buffer.data()); - return unbuffered_write(data, buffer.size()); -} - -int32_t connection::unbuffered_write(const uint8_t* data, size_t length) -{ - const auto plaintext_write = [this](const uint8_t* data, size_t length) - { - // BUGBUG: must set errno for return error handling. - if (length > static_cast(max_int32)) - return -1; - -#ifdef _MSC_VER - return send(socket_, reinterpret_cast(data), - static_cast(length), 0); -#else - return static_cast(send(socket_, data, length, 0)); -#endif - }; - -#ifdef WITH_MBEDTLS - const auto ssl_write = [this](const uint8_t* data, size_t length) - { - int32_t value = mbedtls_ssl_write(&ssl_context_.context, data, length); - return mbedtls_would_block(value) ? WOULD_BLOCK : value; - }; - - auto writer = ssl_context_.enabled ? - static_cast(ssl_write) : - static_cast(plaintext_write); -#else - auto writer = static_cast(plaintext_write); -#endif - - auto remaining = length; - auto position = data; - - do - { - const auto written = writer(position, remaining); - - if (written < 0) - { - const auto error = last_error(); - if (!would_block(error)) - { - LOG_WARNING(LOG_SERVER_HTTP) - << "Unbuffered write failed. requested " << remaining - << " and wrote " << written << ": " << error_string(); - return written; - } - - // BUGBUG: non-terminating loop, or does would_block prevent? - continue; - } - - position += written; - remaining -= written; - - } while (remaining != 0); - - // TODO: isn't this always length? - return static_cast(position - data); -} - -int32_t connection::write(const data_chunk& buffer) -{ - return write(buffer.data(), buffer.size()); -} - -int32_t connection::write(const std::string& buffer) -{ - const auto data = reinterpret_cast(buffer.data()); - return write(data, buffer.size()); -} - -// If high water would be exceeded new messages are silently dropped. -int32_t connection::write(const uint8_t* data, size_t length) -{ - // BUGBUG: must set errno for return error handling. - if (length > static_cast(max_int32)) - return -1; - - const auto header = websocket_ ? websocket_frame::to_header(length, - websocket_op::text) : data_chunk{}; - const auto buffer_size = write_buffer_.size() + header.size() + length; - - if (buffer_size > high_water_mark) - { - LOG_VERBOSE(LOG_SERVER_HTTP) - << "High water exceeded, " << length << "byte message dropped."; - return static_cast(length); - } - - // TODO: this is very inefficient, use circular buffer. - // Buffer header and data for future writes (called from poll). - write_buffer_.insert(write_buffer_.end(), header.begin(), header.end()); - write_buffer_.insert(write_buffer_.end(), data, data + length); - return static_cast(length); -} - -void connection::close() -{ - if (state_ == connection_state::closed) - return; - -#ifdef WITH_MBEDTLS - if (ssl_context_.enabled) - { - if (state_ != connection_state::listening) - mbedtls_ssl_free(&ssl_context_.context); - - mbedtls_pk_free(&ssl_context_.key); - mbedtls_x509_crt_free(&ssl_context_.certificate); - mbedtls_x509_crt_free(&ssl_context_.ca_certificate); - mbedtls_ssl_config_free(&ssl_context_.configuration); - ssl_context_.enabled = false; - } -#endif - - CLOSE_SOCKET(socket_); - state_ = connection_state::closed; - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Closed socket " << this; -} - -sock_t& connection::socket() -{ - return socket_; -} - -http::ssl& connection::ssl_context() -{ - return ssl_context_; -} - -bool connection::ssl_enabled() const -{ - return ssl_context_.enabled; -} - -bool connection::websocket() const -{ - return websocket_; -} - -void connection::set_websocket(bool websocket) -{ - websocket_ = websocket; -} - -const std::string& connection::uri() const -{ - return uri_; -} - -void connection::set_uri(const std::string& uri) -{ - uri_ = uri; -} - -bool connection::json_rpc() const -{ - return json_rpc_; -} - -void connection::set_json_rpc(bool json_rpc) -{ - json_rpc_ = json_rpc; -} - -void* connection::user_data() -{ - return user_data_; -} - -void connection::set_user_data(void* user_data) -{ - user_data_ = user_data; -} - -http::file_transfer& connection::file_transfer() -{ - return file_transfer_; -} - -http::websocket_transfer& connection::websocket_transfer() -{ - return websocket_transfer_; -} - -bool connection::operator==(const connection& other) -{ - return user_data_ == other.user_data_ && socket_ == other.socket_; -} - -} // namespace http -} // namespace server -} // namespace libbitcoin diff --git a/src/web/http/json_string.cpp b/src/web/http/json_string.cpp deleted file mode 100644 index b390c921..00000000 --- a/src/web/http/json_string.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 - -// Explicitly use std::placeholders here for usage internally to the -// boost parsing helpers included from json_parser.hpp. -// See: https://svn.boost.org/trac10/ticket/12621 -using namespace std::placeholders; -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -using namespace boost::property_tree; - -// Object to JSON converters. -//----------------------------------------------------------------------------- - -std::string to_json(const ptree& tree) -{ - std::stringstream json_stream; - write_json(json_stream, tree); - return json_stream.str(); -} - -std::string to_json(const ptree& tree, uint32_t id) -{ - ptree result_tree; - result_tree.add_child("result", tree); - result_tree.put("id", id); - return to_json(result_tree); -} - -std::string to_json(uint64_t height, uint32_t id) -{ - ptree tree; - tree.put("result", height); - tree.put("id", id); - return to_json(tree); - - // TODO: The bc::property_tree call works fine, but the format is - // different than expected for json_rpc so eventually we need to - // separate out property_tree and json_rpc::property_tree, or - // something along the lines to make this a clear distinction. - //// return to_json(property_tree(height, id)); -} - -std::string to_json(const code& code, uint32_t id) -{ - ptree tree; - ptree error_tree; - error_tree.put("code", code.value()); - error_tree.put("message", code.message()); - tree.add_child("error", error_tree); - tree.put("id", id); - return to_json(tree); - //// return to_json(property_tree(code, id)); -} - -std::string to_json(const chain::header& header, uint32_t id) -{ - return to_json(property_tree(config::header(header)), id); -} - -std::string to_json(const chain::block& block, uint32_t id) -{ - return to_json(property_tree(block, true), id); -} - -std::string to_json(const chain::block& block, uint32_t, uint32_t id) -{ - return to_json(property_tree(config::header(block.header())), id); -} - -std::string to_json(const chain::transaction& transaction, uint32_t id) -{ - return to_json(property_tree(config::transaction(transaction), true), id); -} - -} // namespace http -} // namespace server -} // namespace libbitcoin diff --git a/src/web/http/manager.cpp b/src/web/http/manager.cpp deleted file mode 100644 index 91c2e4fb..00000000 --- a/src/web/http/manager.cpp +++ /dev/null @@ -1,1288 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef WITH_MBEDTLS -extern "C" -{ -// Random data generator used by mbedtls for SSL. -int https_random(void*, unsigned char* buffer, size_t length); -// The below signature does not build on gcc: -/* uint32_t https_random(void*, uint8_t* buffer, size_t length); */ -} -#endif - -namespace libbitcoin { -namespace server { -namespace http { - -// The protocol message_size_limit is on the order of 1M. The maximum -// websocket frame size is set to a much smaller fraction of this because our -// websocket protocol implementation does not contain large incoming messages, -// and also to help avoid DoS attacks via large incoming messages. -static constexpr size_t maximum_incoming_message_size = 4 * 1024; -static constexpr size_t timeout_milliseconds = 10; -static constexpr size_t maximum_backlog = 8; -static constexpr size_t maximum_connections = FD_SETSIZE; - -manager::manager(bool ssl, event_handler handler, path document_root, - const origin_list origins) - : ssl_(ssl), - running_(false), - listening_(false), - initialized_(false), - port_(0), - user_data_(nullptr), - key_{}, - certificate_{}, - ca_certificate_{}, - handler_(handler), - document_root_(document_root), - origins_(origins) -{ -#ifndef WITH_MBEDTLS - BITCOIN_ASSERT_MSG(!ssl, "Secure HTTP requires MBEDTLS library."); -#endif -} - -manager::~manager() -{ -#ifdef _MSC_VER - if (initialized_) - ::WSACleanup(); -#endif -} - -// Initialize is not thread safe. -bool manager::initialize() -{ -#ifdef _MSC_VER - WSADATA wsa_data; - static constexpr auto winsock_version = MAKEWORD(2, 2); - if (::WSAStartup(winsock_version, &wsa_data) != 0) - return false; - - initialized_ = true; - return LOBYTE(wsa_data.wVersion) == 2 && HIBYTE(wsa_data.wVersion) == 2; -#else - return true; -#endif -} - -// Bind is not thread safe. -bool manager::bind(const config::endpoint& address, const bind_options& options) -{ - if (address.host() != "*") - { - LOG_INFO(LOG_SERVER_HTTP) - << "Failed to bind to named host (unsupported): " << address; - return false; - } - - port_ = address.port(); - - LOG_VERBOSE(LOG_SERVER_HTTP) - << (ssl_ ? "Secure" : "Public") << " bind to port " << port_; - - std::memset(&listener_address_, 0, sizeof(listener_address_)); - listener_address_.sin_family = AF_INET; - listener_address_.sin_port = htons(port_); - listener_address_.sin_addr.s_addr = htonl(INADDR_ANY); - - listening_ = true; - user_data_ = options.user_data; - - listener_ = std::make_shared(); - - // asio::acceptor.open(endpoint.protocol()); - // ************************************************************************ - listener_->socket() = ::socket(listener_address_.sin_family, SOCK_STREAM, 0); - // ************************************************************************ - - if (listener_->socket() == 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Socket failed with error " << last_error() << ": " - << error_string(); - return false; - } - - if (ssl_) - { - if (!options.ssl_certificate.empty()) - { - key_ = options.ssl_key.generic_string(); - certificate_ = options.ssl_certificate.generic_string(); - } - - if (!options.ssl_ca_certificate.empty()) - { - // Specified and not found CA cert is a failure condition. - if (!exists(options.ssl_ca_certificate)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Specified CA certificate does not exist"; - return false; - } - - ca_certificate_ = options.ssl_ca_certificate.generic_string(); - } - - // The default context object for the listener socket is initialized. - if (!initialize_ssl(listener_, listening_)) - return false; - } - - //// asio::acceptor.set_option(reuse_address); - listener_->reuse_address(); - listener_->set_socket_non_blocking(); - - //// asio::acceptor.bind(address); - // ************************************************************************ - if (::bind(listener_->socket(), reinterpret_cast( - &listener_address_), sizeof(listener_address_)) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Bind failed with error " << last_error() << ": " - << error_string(); - listener_->close(); - return false; - } - // ************************************************************************ - - //// asio::acceptor.listen(asio::max_connections); - // ************************************************************************ - ::listen(listener_->socket(), maximum_backlog); - // ************************************************************************ - - listener_->set_state(connection_state::listening); - add_connection(listener_); - return true; -} - -bool manager::accept_connection() -{ - sockaddr_in remote_address{ 0, 0, 0, 0 }; - std::memset(&remote_address, 0, sizeof(remote_address)); - auto address_size = static_cast(sizeof(remote_address)); - auto socket = ::accept(listener_->socket(), reinterpret_cast( - &remote_address), &address_size); - - const auto error = last_error(); - - if ((static_cast(socket) == connection_state::error) && - !would_block(error)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Accept call failed with " << error_string(); - return false; - } - -#ifdef SO_NOSIGPIPE - int no_sig_pipe = 1; - if (setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, - sizeof(no_sig_pipe)) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to disable SIGPIPE"; - CLOSE_SOCKET(socket); - return false; - } -#endif - - auto connection = std::make_shared(socket, remote_address); - - if (!connection) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to create new connection object"; - CLOSE_SOCKET(socket); - return false; - } - -#ifdef WITH_MBEDTLS - if (ssl_) - { - if (!initialize_ssl(connection, false)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to initialize new SSL connection on port " << port_; - connection->close(); - return false; - } - - auto& context = connection->ssl_context().context; - mbedtls_ssl_set_bio(&context, reinterpret_cast(connection.get()), - ssl_send, ssl_receive, nullptr); - - int error = ~0; - while (error != 0) - { - error = mbedtls_ssl_handshake_step(&context); - if (mbedtls_would_block(error)) - continue; - - if (error < 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "SSL handshake failed: " << error_string() - << "-- dropping accepted connection " << connection; - - connection->close(); - return false; - } - } - - BITCOIN_ASSERT(connection->ssl_context().enabled); - } -#endif - - // Set all per-connection variables. - connection->set_state(connection_state::connected); - connection->set_user_data(user_data_); - connection->set_socket_non_blocking(); - - add_connection(connection); - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Accepted " << (ssl_ ? "SSL" : "Plaintext") << " connection: " - << connection << " on port " << port_; - return true; -} - -void manager::add_connection(connection_ptr connection) -{ - connections_.push_back(connection); - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Added Connection [" << connection << ", " - << connections_.size() << " total]"; -} - -void manager::remove_connection(connection_ptr connection) -{ - const auto it = std::find(connections_.begin(), connections_.end(), - connection); - - if (it != connections_.end()) - { - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Removing Connection [" << connection << ", " - << connections_.size() - 1 << " remaining]"; - - connections_.erase(it); - } - else - { - // TODO: this should never happend (hard failure). - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Cannot locate connection for removal"; - } -} - -size_t manager::connection_count() const -{ - return connections_.size(); -} - -bool manager::ssl() const -{ - return ssl_; -} - -bool manager::listening() const -{ - return listening_; -} - -void manager::start() -{ - running_ = true; - while (running_) - run_once(); -} - -void manager::start(handler callback) -{ - running_ = true; - while (running_) - { - run_once(); - callback(); - } -} - -void manager::run_once() -{ - if (stopped()) - return; - - // Run any user queued tasks that must be run inside this thread. - run_tasks(); - - // Monitor and process sockets. - poll(timeout_milliseconds); -} - -void manager::stop() -{ - running_.store(false); -} - -bool manager::stopped() const -{ - return !running_.load(); -} - -void manager::execute(task_ptr task) -{ - // Critical Section. - /////////////////////////////////////////////////////////////////////////// - unique_lock lock(task_mutex_); - tasks_.push_back(task); - /////////////////////////////////////////////////////////////////////////// -} - -void manager::run_tasks() -{ - task_list tasks; - - // Critical Section. - /////////////////////////////////////////////////////////////////////////// - task_mutex_.lock(); - tasks_.swap(tasks); - task_mutex_.unlock(); - /////////////////////////////////////////////////////////////////////////// - - for (const auto task: tasks) - if (!task->run()) - handle_connection(task->connection(), event::error); -} - -// Portable select based implementation. -// Break up number of connections into N lists of a specified maximum size and -// call select for each of them, given a timeout of (timeout_milliseconds / N). -// This is a hack to work around limitations of the select system call. Note -// that you may also need to adjust the descriptor limit for this process in -// order for this to work properly. -// With very large connection counts and small specified timeout values, this -// poll method may very well exceed the specified timeout. -void manager::poll(size_t timeout_milliseconds) -{ - if (connections_.size() > maximum_connections) - { - const auto number_of_lists = - (connections_.size() / maximum_connections) + 1u; - const auto adjusted_timeout = static_cast( - std::ceil(timeout_milliseconds / number_of_lists)); - std::vector connection_lists; - connection_lists.reserve(number_of_lists); - - for (size_t it = 0; it < number_of_lists; it++) - { - connection_list connection_list; - connection_list.reserve(maximum_connections); - connection_lists.push_back(connection_list); - } - - for (size_t it = 0, index = 0; it < connections_.size(); it++) - { - connection_lists[index].push_back(connections_[it]); - if ((it > 0) && ((it - 1) % maximum_connections) == 0) - index++; - } - - for (auto& connection_list: connection_lists) - select(adjusted_timeout, connection_list); - } - else - { - select(timeout_milliseconds, connections_); - } -} - -void manager::select(size_t timeout_milliseconds, connection_list& connections) -{ - // TODO: use std::array or std::vector. - connection_ptr socket_list[maximum_connections]; - - // This limit must be honored by the caller. - BITCOIN_ASSERT(connections.size() <= maximum_connections); - - timeval poll_interval; - poll_interval.tv_sec = static_cast(timeout_milliseconds / 1000); - poll_interval.tv_usec = static_cast((timeout_milliseconds * 1000) - - (poll_interval.tv_sec * 100000)); - - fd_set read_set; - fd_set write_set; - fd_set error_set; - - FD_ZERO(&read_set); - FD_ZERO(&write_set); - FD_ZERO(&error_set); - - size_t last_index = 0; - sock_t max_descriptor = 0; - - connection_list pending_removal; - for (const auto connection: connections) - { - if (!connection || connection->closed()) - continue; - - const auto descriptor = connection->socket(); - - // Check if the descriptor is too high to monitor in our fd_set. - // - // maximum_connections should be set to FD_SETSIZE. Since the - // select call tracks sockets in a 0-indexed bit field for - // file descriptors, any descriptor greater than FD_SETSIZE - // cannot be monitored in the bit field provided by the - // (select) mechanism. This means that if a single descriptor - // is greater than this value, it has to be closed since it - // can't be monitored, unless it can be dup'd (which asks the - // kernel to copy and return the descriptor mappings into the - // lowest-numbered unused descriptor). If that dup attempt - // fails to lower the value, close the connection. - if (descriptor > maximum_connections) - { -#ifdef _MSC_VER - CLOSE_SOCKET(descriptor); - LOG_ERROR(LOG_SERVER_HTTP) - << "Error: cannot monitor socket " << descriptor - << ", value is above" << maximum_connections; - pending_removal.push_back(connection); - continue; -#else - // If the dup system call is supported, attempt to resolve - // this by seeking a lower available descriptor. - sock_t new_descriptor = dup(descriptor); - if (new_descriptor < descriptor && - new_descriptor < maximum_connections) - { - connection->socket() = new_descriptor; - CLOSE_SOCKET(descriptor); - } - else - { - // Select cannot monitor this descriptor. - CLOSE_SOCKET(new_descriptor); - LOG_ERROR(LOG_SERVER_HTTP) - << "Error: cannot monitor socket " << descriptor - << ", value is above" << maximum_connections; - pending_removal.push_back(connection); - continue; - } -#endif - } - - if (connection->file_transfer().in_progress || - !connection->write_buffer().empty()) - FD_SET(descriptor, &write_set); - - FD_SET(descriptor, &read_set); - FD_SET(descriptor, &error_set); - - socket_list[last_index++] = connection; - if (descriptor > max_descriptor) - max_descriptor = descriptor; - } - - for (const auto connection: pending_removal) - handle_connection(connection, event::error); - - pending_removal.clear(); - - // Guard ::select(max_descriptor + 1, ...) - if (max_descriptor > static_cast(max_int32 - 1)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Error: select fd overflow: " << max_descriptor; - return; - } - - const auto fd_count = static_cast(max_descriptor + 1); - const auto num_events = ::select(fd_count, &read_set, &write_set, - &error_set, &poll_interval); - - if (num_events == 0) - return; - - if (num_events < 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Error: select failed: " << error_string() - << "max fd: " << max_descriptor; - return; - } - - for (size_t index = 0; index < last_index; index++) - { - const auto connection = socket_list[index]; - if (!connection || connection->closed()) - continue; - - const auto descriptor = connection->socket(); - if (FD_ISSET(descriptor, &error_set)) - { - pending_removal.push_back(connection); - continue; - } - - if (FD_ISSET(descriptor, &write_set)) - { - if (connection->file_transfer().in_progress && - !transfer_file_data(connection)) - { - pending_removal.push_back(connection); - continue; - } - - auto& write_buffer = connection->write_buffer(); - if (!write_buffer.empty()) - { - const auto segment_length = std::min(write_buffer.size(), - transfer_buffer_length); - - const auto written = connection->unbuffered_write( - write_buffer.data(), segment_length); - - if (written < 0) - { - pending_removal.push_back(connection); - continue; - } - - // TODO: this is very inefficient, use circular buffer. - write_buffer.erase(write_buffer.begin(), - write_buffer.begin() + written); - } - } - - if (FD_ISSET(descriptor, &read_set)) - { - if (connection->state() == connection_state::listening) - { - if (!handle_connection(connection, event::listen)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Terminating due to error on listening socket"; - stop(); - return; - } - - // After accepting a connection, break out of loop since we're - // iterating over a list that's just been updated. - break; - } - else - { - const auto read = connection->read(); - if ((read == 0) || ((read < 0) && !would_block(last_error()))) - pending_removal.push_back(connection); - else if (!handle_connection(connection, event::read)) - pending_removal.push_back(connection); - } - } - } - - for (const auto connection: pending_removal) - handle_connection(connection, event::error); -} - -bool manager::handle_connection(connection_ptr connection, event current_event) -{ - switch (current_event) - { - case event::listen: - { - if (!accept_connection()) - { - // Don't let this accept failure stop the service. - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to accept new connection"; - break; - } - - // We could allow the user to know that a connection was - // accepted here, but we instead opt to notify them only - // after the connection is upgraded to a websocket. - return true; - } - - case event::read: - { - const auto read_result = connection->read_length(); - if (read_result <= 0) - break; - - const auto read_length = static_cast(read_result); - auto& buffer = connection->read_buffer(); - if (connection->websocket()) - { - auto& transfer = connection->websocket_transfer(); - if (transfer.in_progress) - { - transfer.data.insert(transfer.data.end(), buffer.data(), - buffer.data() + read_length); - - transfer.offset += read_length; - BITCOIN_ASSERT(transfer.offset == transfer.data.size()); - - // Check for configuration violation (DoS protection). - if (transfer.offset > maximum_incoming_message_size) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Terminating connection due to excessive frame length."; - return false; - } - - if (transfer.offset != transfer.length) - return true; - } - - return handle_websocket(connection); - } - - const std::string request{ buffer.begin(), buffer.begin() + read_length }; - - http_request out; - if (parse_http(out, request)) - { - - // Check if we need to convert HTTP connection to websocket. - if (out.upgrade_request) - return upgrade_connection(connection, out); - - // Check if we need to mark HTTP connection as expecting a - // JSON-RPC reply. If so, we need to call the user handler - // to notify user that a new json_rpc connection was accepted - // so that they can track it. - connection->set_json_rpc(out.json_rpc); - const auto request = reinterpret_cast(&out); - - if (out.json_rpc) - { - return handler_(connection, event::accepted, nullptr) && - handler_(connection, event::json_rpc, request); - } - - // Call user's event handler with the parsed http request. - return handler_(connection, event::read, request) && - send_response(connection, out); - } - else - { - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Failed to parse HTTP request from " << request; - return handle_connection(connection, event::error); - } - - break; - } - - case event::write: - { - // Should never get here since writes are handled elsewhere. - BITCOIN_ASSERT(false); - return false; - } - - case event::error: - case event::closing: - { - if (!connection || connection->closed()) - return false; - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Connection closing: " << connection; - handler_(connection, event::closing, nullptr); - remove_connection(connection); - connection->close(); - return false; - } - - default: - return false; - } - - return true; -} - -#ifdef WITH_MBEDTLS -// static -int32_t manager::ssl_send(void* data, const uint8_t* buffer, size_t length) -{ - auto connection = reinterpret_cast(data); -#ifdef MSG_NOSIGNAL - int flags = MSG_NOSIGNAL; -#else - int flags = 0; -#endif - const auto sent = static_cast(send(connection->socket(), buffer, - length, flags)); - if (sent >= 0) - return sent; - - return ((would_block(sent) || sent == EINPROGRESS) ? - MBEDTLS_ERR_SSL_WANT_WRITE : -1); -} - -// static -int32_t manager::ssl_receive(void* data, uint8_t* buffer, size_t length) -{ - auto connection = reinterpret_cast(data); - auto read = static_cast(recv(connection->socket(), buffer, length, 0)); - if (read >= 0) - return read; - - return (would_block(read) || read == EINPROGRESS) ? - MBEDTLS_ERR_SSL_WANT_READ : -1; -} -#endif - -bool manager::transfer_file_data(connection_ptr connection) -{ - std::array buffer; - auto& file_transfer = connection->file_transfer(); - - if (!file_transfer.in_progress) - return false; - - auto amount_to_read = std::min(transfer_buffer_length, - file_transfer.length - file_transfer.offset); - - const auto read = std::fread(buffer.data(), sizeof(uint8_t), - amount_to_read, file_transfer.descriptor); - - auto success = (read == amount_to_read || - (read < amount_to_read && feof(file_transfer.descriptor))); - - if (!success) - { - if (file_transfer.in_progress) - { - fclose(file_transfer.descriptor); - file_transfer.in_progress = false; - file_transfer.offset = 0; - file_transfer.length = 0; - } - - return false; - } - - const auto written = connection->write(buffer.data(), read); - success = (written >= 0 && static_cast(written) == read); - - if (!success) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Write failed: requested " << read << " and wrote " << written; - return false; - } - - file_transfer.offset += written; - - if (file_transfer.offset == file_transfer.length) - { - if (file_transfer.in_progress) - { - fclose(file_transfer.descriptor); - file_transfer.in_progress = false; - file_transfer.offset = 0; - file_transfer.length = 0; - } - } - - return success; -} - -bool manager::send_http_file(connection_ptr connection, const path& path, - bool keep_alive) -{ - auto& file_transfer = connection->file_transfer(); - - if (!file_transfer.in_progress) - { - // BUGBUG: UTF8 string passed to Windows ANSI parameter. - const auto file = path.generic_string().c_str(); - - file_transfer.descriptor = fopen(file, "r"); - if (file_transfer.descriptor == nullptr) - return false; - - file_transfer.in_progress = true; - file_transfer.offset = 0; - file_transfer.length = boost::filesystem::file_size(path); - - http_reply reply; - const auto response = reply.generate(protocol_status::ok, - mime_type(path), file_transfer.length, keep_alive); - - if (!connection->write(response)) - return false; - } - - // On future iterations, this is called directly from poll while - // the file transfer is in progress. - return transfer_file_data(connection); -} - -bool manager::handle_websocket(connection_ptr connection) -{ - const auto read_length = static_cast(connection->read_length()); - auto& buffer = connection->read_buffer(); - auto& transfer = connection->websocket_transfer(); - auto data = transfer.in_progress ? transfer.data.data() : buffer.data(); - - websocket_frame frame(data, read_length); - - // Websocket fragments are not supported. - if (!frame || frame.fragment()) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Invalid websocket frame."; - return false; - } - - const auto flags = frame.flags(); - const auto final = frame.final(); - const auto op_code = frame.op_code(); - const auto event_type = frame.event_type(); - const auto mask_length = frame.mask_length(); - const auto data_length = frame.data_length(); - const auto header_length = frame.header_length(); - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Websocket data_frame flags: " << flags - << ", final_fragment: " << (final ? "true" : "false") - << ", read length: " << read_length; - - // If full frame payload isn't buffered, initiate state to track transfer. - if (data_length > read_length && !transfer.in_progress) - { - // Check if this transfer exceeds the maximum incoming frame length. - if (data_length > maximum_incoming_message_size) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Terminating connection due to excessive frame length."; - return false; - } - - transfer.in_progress = true; - transfer.header_length = header_length; - transfer.length = data_length + header_length; - - data_chunk data; - transfer.data.swap(data); - transfer.data.reserve(transfer.length); - transfer.data.insert(transfer.data.end(), buffer.data(), - buffer.data() + header_length); - - const auto non_mask_length = transfer.header_length - mask_length; - const auto mask_start = data.data() + non_mask_length; - transfer.mask = { mask_start, mask_start + mask_length }; - transfer.offset = transfer.data.size(); - - LOG_DEBUG(LOG_SERVER_HTTP) - << "Initiated frame transfer for a length of " << data_length; - return true; - } - else - { - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Data length: " << data_length << ", Header length: " - << header_length << ", Mask length: " << mask_length; - } - - const auto frame_length = header_length + data_length; - if (frame_length < header_length || frame_length < data_length) - return false; - - if (event_type == event::websocket_control_frame) - { - // XOR mask the payload using the client provided mask. - const auto mask_start = data + header_length - mask_length; - - for (size_t index = 0; index < data_length; ++index) - data[index + header_length] ^= mask_start[index % 4]; - - websocket_message message - { - connection->uri(), - data + header_length, - data_length, - flags, - op_code - }; - - // Possible TODO: If the opcode is a ping, send response here. - if (message.code != websocket_op::close) - { - LOG_DEBUG(LOG_SERVER_HTTP) - << "Unhandled websocket op: " << op_to_string(message.code); - } - - // Call user handler for control frames. - const auto status = handler_(connection, event_type, &message); - - // Returning false here causes the connection to be removed. - if (message.code == websocket_op::close) - return false; - - return status; - } - - if (final && transfer.in_progress && transfer.offset == transfer.length) - { - const auto mask_start = transfer.mask.data(); - const auto payload_length = transfer.length - transfer.header_length; - - for (size_t index = 0; index < payload_length; ++index) - data[index + transfer.header_length] ^= mask_start[index % 4]; - - websocket_message message - { - connection->uri(), - data + transfer.header_length, - transfer.data.size() - transfer.header_length, - flags, - op_code - }; - - // Call user handler on last fragment with the entire message. - const auto status = handler_(connection, event_type, &message); - - transfer.in_progress = false; - transfer.offset = 0; - transfer.length = 0; - transfer.header_length = 0; - transfer.mask.clear(); - transfer.data.clear(); - - return status; - } - else if (op_code == websocket_op::close) - { - LOG_DEBUG(LOG_SERVER_HTTP) - << "Closing websocket due to close op."; - return false; - } - else - { - // Check if we need to read again. - if (data_length > read_length) - return true; - - // Apply websocket mask (if required) before user handling. - if ((mask_length > 0) && (read_length > data_length)) - { - const auto mask_start = data + header_length - mask_length; - for (size_t index = 0; index < data_length; ++index) - data[index + header_length] ^= mask_start[index % 4]; - } - - websocket_message message - { - connection->uri(), - data + header_length, - data_length, - flags, - op_code - }; - - // Call user handler for non-fragmented frames. - return handler_(connection, event_type, &message); - } - - return false; -} - -bool manager::send_response(connection_ptr connection, - const http_request& request) -{ - auto path = document_root_; - - if (request.uri == "/") - { - static const std::vector index_files - { - { "index.html" }, - { "index.htm" }, - { "index.shtml" } - }; - - for (const auto& index: index_files) - { - const auto test_path = path / index; - if (boost::filesystem::exists(test_path)) - { - path = test_path; - break; - } - } - - if (path == document_root_) - return false; - } - else - { - // BUGBUG: sanitize path to guard against information leak. - path /= request.uri; - } - - if (!boost::filesystem::exists(path)) - { - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Requested Path: " << path << " does not exist"; - - // TODO: move time formatter to independent utility. - static const size_t max_date_time_length = 32; - std::array time_buffer; - const auto current_time = std::time(nullptr); - - // BUGBUG: std::gmtime may not be thread safe. - std::strftime(time_buffer.data(), time_buffer.size(), - "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(¤t_time)); - - const auto response = std::string( - "HTTP/1.1 404 Not Found\r\n" - "Date: ") + time_buffer.data() + std::string("\r\n" - "Content-Type: text/html\r\n" - "Content-Length: 90\r\n\r\n" - "Page not found" - "The page was not found.\r\n\r\n"); - - connection->write(response); - return true; - } - - const auto keep_alive = - ((request.protocol.find("HTTP/1.0") == std::string::npos) || - (request.header(std::string("Connection")) == "keep-alive")); - - return send_http_file(connection, path, keep_alive); -} - -bool manager::send_generated_reply(connection_ptr connection, - protocol_status status) -{ - http_reply reply; - return connection->write(reply.generate(status, {}, 0, false)); -} - -bool manager::upgrade_connection(connection_ptr connection, - const http_request& request) -{ - // Request MUST be GET and Protocol must be at least 1.1 - if ((request.method != "get") || (request.protocol_version >= 1.1f)) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Rejecting upgrade request for method " << request.method - << "/Protocol " << request.protocol; - send_generated_reply(connection, protocol_status::bad_request); - return false; - } - - // Verify if origin is acceptable (contains either localhost, hostname, - // or ip address of current server) - if (!validate_origin(request.header("origin"))) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Rejecting upgrade request for origin: " - << request.header("origin"); - send_generated_reply(connection, protocol_status::forbidden); - return false; - } - - const auto version = request.header("sec-websocket-version"); - if (!version.empty() && version != "13") - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Rejecting upgrade request for version: " << version; - send_generated_reply(connection, protocol_status::bad_request); - return false; - } - - const auto key = request.header("sec-websocket-key"); - if (key.empty()) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Rejecting upgrade request due to missing sec-websocket-key"; - send_generated_reply(connection, protocol_status::bad_request); - return false; - } - - http_reply reply; - const auto key_response = websocket_key_response(key); - const auto protocol = request.header("sec-websocket-protocol"); - const auto response = reply.generate_upgrade(key_response, protocol); - - // Unbuffered since not websocket yet and must complete before upgrade. - const auto result = connection->unbuffered_write(response); - - // TODO: why have both result conditions if both are a simple error? - // TODO: there must be a case where uneuqal response is not an error. - if (result < 0 || static_cast(result) != response.size()) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to upgrade connection due to a write failure"; - return false; - } - - connection->set_websocket(true); - connection->set_uri(request.uri); - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Upgraded connection " << connection << " for uri " << request.uri; - - // On upgrade, call the user handler so it can track this websocket. - return handler_(connection, event::accepted, nullptr); -} - -bool manager::validate_origin(const std::string& origin) -{ - return std::find(origins_.begin(), origins_.end(), origin) != - origins_.end(); -} - -bool manager::initialize_ssl(connection_ptr connection, bool listener) -{ -#ifdef WITH_MBEDTLS - auto& context = connection->ssl_context(); - auto& configuration = context.configuration; - - mbedtls_ssl_init(&context.context); - if (listener) - { - mbedtls_ssl_config_init(&configuration); - if (mbedtls_ssl_config_defaults(&configuration, MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) - return false; - - // TLS 1.2 and up - mbedtls_ssl_conf_min_version(&configuration, - MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); - mbedtls_ssl_conf_rng(&configuration, https_random, nullptr); - } - - if (!certificate_.empty()) - { - if (key_.empty()) - key_ = certificate_; - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Using cert " << certificate_ << " and key " << key_; - - mbedtls_pk_init(&context.key); - mbedtls_x509_crt_init(&context.certificate); - - if (mbedtls_x509_crt_parse_file(&context.certificate, - certificate_.c_str()) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to parse certificate: " << certificate_; - return false; - } - - if (mbedtls_pk_parse_keyfile(&context.key, key_.c_str(), nullptr) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to parse key: " << key_; - return false; - } - - if (mbedtls_ssl_conf_own_cert(&configuration, &context.certificate, - &context.key) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to set own certificate chain and private key"; - return false; - } - } - - if (!ca_certificate_.empty() && ca_certificate_ != "*") - { - mbedtls_x509_crt_init(&context.ca_certificate); - if (mbedtls_x509_crt_parse_file(&context.ca_certificate, - ca_certificate_.c_str()) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "Failed to parse CA certificate: " << ca_certificate_; - return false; - } - - mbedtls_ssl_conf_ca_chain(&configuration, &context.ca_certificate, - nullptr); - mbedtls_ssl_conf_authmode(&configuration, MBEDTLS_SSL_VERIFY_REQUIRED); - } - - if (!listener) - { - if (mbedtls_ssl_setup(&context.context, - &listener_->ssl_context().configuration) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "SSL setup failed for " << connection; - return false; - } - - if (!context.hostname.empty() && mbedtls_ssl_set_hostname( - &context.context, context.hostname.c_str()) != 0) - { - LOG_ERROR(LOG_SERVER_HTTP) - << "SSL set hostname failed for " << connection; - return false; - } - - mbedtls_ssl_session_reset(&context.context); - } - - // TODO: Allow ciphers to be caller specified - mbedtls_ssl_conf_ciphersuites(&configuration, default_ciphers); - - LOG_DEBUG(LOG_SERVER_HTTP) - << "SSL initialized for listener socket"; - - context.enabled = true; - return context.enabled; -#else - return false; -#endif -} - -} // namespace http -} // namespace server -} // namespace libbitcoin diff --git a/src/web/http/socket.cpp b/src/web/http/socket.cpp deleted file mode 100644 index 435bec0d..00000000 --- a/src/web/http/socket.cpp +++ /dev/null @@ -1,660 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef WITH_MBEDTLS -extern "C" -{ -uint32_t https_random(void*, uint8_t* buffer, size_t length) -{ - bc::data_chunk random(length); - bc::pseudo_random_fill(random); - std::memcpy(buffer, reinterpret_cast(random.data()), length); - return 0; -} -} -#endif - -namespace libbitcoin { -namespace server { -namespace http { - -using namespace asio; -using namespace bc::chain; -using namespace bc::protocol; -using namespace boost::filesystem; -using namespace boost::iostreams; -using namespace boost::property_tree; -using namespace http; -using http_event = http::event; -using role = zmq::socket::role; - -// Local class. -class task_sender - : public manager::task -{ -public: - task_sender(connection_ptr connection, const std::string& data) - : connection_(connection), data_(data) - { - } - - bool run() - { - if (!connection_ || connection_->closed()) - return false; - - if (!connection_->json_rpc()) - // BUGBUG: unguarded narrowing cast. - return connection_->write(data_) == static_cast(data_.size()); - - http_reply reply; - const auto response = reply.generate(protocol_status::ok, {}, - data_.size(), false) + data_; - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Writing JSON-RPC response: " << response; - - // BUGBUG: unguarded narrowing cast. - return connection_->write(response) == - static_cast(response.size()); - } - - connection_ptr connection() - { - return connection_; - } - -private: - connection_ptr connection_; - const std::string data_; -}; - -// Local class. -// -// The purpose of this class is to handle previously received zmq -// responses and write them to the requesting websocket. -// -// The run() method is only called from send_query_responses on the -// web thread. With this guarantee in mind, no locking of any state -// is required. -class query_response_task_sender - : public socket::query_response_task -{ -public: - query_response_task_sender(uint32_t sequence, const data_chunk& data, - const std::string& command, const socket::handler_map& handlers, - socket::connection_work_map& work, - socket::query_correlation_map& correlations) - : sequence_(sequence), - data_(data), - command_(command), - handlers_(handlers), - work_(work), - correlations_(correlations) - { - } - - bool run() - { - // Use internal sequence number to find connection and work id. - auto correlation = correlations_.find(sequence_); - if (correlation == correlations_.end()) - { - // This will happen anytime the client disconnects before this handler - // is called. We can safely discard the result here. - LOG_DEBUG(LOG_SERVER) - << "Unmatched websocket query work item sequence: " << sequence_; - return true; - } - - auto connection = correlation->second.first; - const auto id = correlation->second.second; - correlations_.erase(correlation); - - // Use connection to locate connection state. - auto it = work_.find(connection); - if (it == work_.end()) - { - LOG_ERROR(LOG_SERVER) - << "Query work completed for unknown connection"; - return true; - } - - // Use work id to locate the query work item. - auto& query_work_map = it->second; - auto query_work = query_work_map.find(id); - if (query_work == query_work_map.end()) - { - // This can happen anytime the client disconnects before this - // code is reached. We can safely discard the result here. - LOG_DEBUG(LOG_SERVER) - << "Unmatched websocket query work id: " << id; - return true; - } - - const auto work = query_work->second; - query_work_map.erase(query_work); - - BITCOIN_ASSERT(work.id == id); - BITCOIN_ASSERT(work.connection == connection); - BITCOIN_ASSERT(work.correlation_id == sequence); - - data_source istream(data_); - istream_reader source(istream); - const auto ec = source.read_error_code(); - if (ec) - { - work.connection->write(to_json(ec, id)); - return true; - } - - const auto handler = handlers_.find(work.command); - if (handler == handlers_.end()) - { - static constexpr auto error = bc::error::not_implemented; - work.connection->write(to_json(error, id)); - return true; - } - - // Decode response and send query output to websocket client. - // The websocket write is performed directly (running on the - // websocket thread). - const auto payload = source.read_bytes(); - handler->second.decode(payload, id, work.connection); - return true; - } - -private: - const uint32_t sequence_; - const data_chunk data_; - const std::string command_; - const socket::handler_map& handlers_; - socket::connection_work_map& work_; - socket::query_correlation_map& correlations_; -}; - -// TODO: eliminate the use of weak and untyped pointer to pass self here. -// static -// Callback made internally via socket::poll on the web socket thread. -bool socket::handle_event(connection_ptr connection, http_event event, - const void* data) -{ - switch (event) - { - case http_event::accepted: - { - // This connection is newly accepted and is either an HTTP - // JSON-RPC connection, or an already upgraded websocket. - // Returning false here will cause the service to stop - // accepting new connections. - auto instance = static_cast(connection->user_data()); - BITCOIN_ASSERT(instance != nullptr); - instance->add_connection(connection); - - const auto connection_type = connection->json_rpc() ? "JSON-RPC" : - "Websocket"; - LOG_DEBUG(LOG_SERVER) - << connection_type << " client connection established [" - << connection << "] (" << instance->connection_count() << ")"; - break; - } - - case http_event::json_rpc: - { - // Process new incoming user json_rpc request. Returning - // false here will cause this connection to be closed. - auto instance = static_cast(connection->user_data()); - BITCOIN_ASSERT(instance != nullptr); - BITCOIN_ASSERT(data != nullptr); - const auto& request = *reinterpret_cast(data); - BITCOIN_ASSERT(request.json_rpc); - - // Use default-value get to avoid exceptions on invalid input. - const auto id = request.json_tree.get("id", 0); - const auto method = request.json_tree.get("method", ""); - - if (request.json_tree.count("params") == 0) - { - http_reply reply; - connection->write(reply.generate( - protocol_status::bad_request, {}, 0, false)); - return false; - } - - std::vector parameter_list; - const auto child = request.json_tree.get_child("params"); - for (const auto& parameter: child) - parameter_list.push_back( - parameter.second.get_value()); - - // TODO: Support full parameter lists? - std::string parameters{}; - if (!parameter_list.empty()) - parameters = parameter_list[0]; - - LOG_VERBOSE(LOG_SERVER) - << "method " << method << ", parameters " << parameters - << ", id " << id; - - instance->notify_query_work(connection, method, id, parameters); - break; - } - - case http_event::websocket_frame: - { - // Process new incoming user websocket data. Returning false - // will cause this connection to be closed. - auto instance = static_cast(connection->user_data()); - if (instance == nullptr) - return false; - - BITCOIN_ASSERT(data != nullptr); - auto message = reinterpret_cast(data); - - ptree input_tree; - if (!bc::property_tree(input_tree, - { message->data, message->data + message->size })) - { - http_reply reply; - connection->write(reply.generate( - protocol_status::internal_server_error, {}, 0, false)); - return false; - } - - // Use default value get to avoid exceptions on invalid input. - const auto id = input_tree.get("id", 0); - const auto method = input_tree.get("method", ""); - std::string parameters; - - const auto child = input_tree.get_child("params"); - std::vector parameter_list; - for (const auto& parameter: child) - parameter_list.push_back( - parameter.second.get_value()); - - // TODO: Support full parameter lists? - if (!parameter_list.empty()) - parameters = parameter_list[0]; - - LOG_VERBOSE(LOG_SERVER) - << "method " << method << ", parameters " << parameters - << ", id " << id; - - instance->notify_query_work(connection, method, id, parameters); - break; - } - - case http_event::closing: - { - // This connection is going away after this handling. - auto instance = static_cast(connection->user_data()); - BITCOIN_ASSERT(instance != nullptr); - instance->remove_connection(connection); - - if (connection->websocket()) - { - const auto type = connection->json_rpc() ? "JSON-RPC" : "Websocket"; - LOG_DEBUG(LOG_SERVER) - << type << " client disconnected [" << connection << "] (" - << instance->connection_count() << ")"; - } - - break; - } - - // No specific handling required for other events. - case http_event::read: - case http_event::error: - case http_event::websocket_control_frame: - default: - break; - } - - return true; -} - -socket::socket(zmq::context& context, server_node& node, bool secure) - : worker(priority(node.server_settings().priority)), - context_(context), - secure_(secure), - security_(secure ? "secure" : "public"), - server_settings_(node.server_settings()), - protocol_settings_(node.protocol_settings()), - sequence_(0), - manager_(nullptr), - origins_(node.server_settings().websockets_origins), - document_root_(node.server_settings().websockets_root) -{ -} - -bool socket::start() -{ - if (!exists(document_root_)) - { - LOG_ERROR(LOG_SERVER) - << "Configured HTTP root path '" << document_root_ - << "' does not exist."; - return false; - } - - if (secure_) - { - if (!server_settings_.websockets_ca_certificate.generic_string().empty() && - !exists(server_settings_.websockets_server_certificate)) - { - LOG_ERROR(LOG_SERVER) - << "Requested CA certificate '" - << server_settings_.websockets_ca_certificate - << "' does not exist."; - return false; - } - - if (!exists(server_settings_.websockets_server_certificate)) - { - LOG_ERROR(LOG_SERVER) - << "Required server certificate '" - << server_settings_.websockets_server_certificate - << "' does not exist."; - return false; - } - - if (!exists(server_settings_.websockets_server_private_key)) - { - LOG_ERROR(LOG_SERVER) - << "Required server private key '" - << server_settings_.websockets_server_private_key - << "' does not exist."; - return false; - } - } - - return zmq::worker::start(); -} - -void socket::queue_response(uint32_t sequence, const data_chunk& data, - const std::string& command) -{ - auto task = std::make_shared( - sequence, data, command, handlers_, work_, correlations_); - - // Critical Section. - /////////////////////////////////////////////////////////////////////////// - unique_lock lock(query_response_task_mutex_); - query_response_tasks_.push_back(task); - /////////////////////////////////////////////////////////////////////////// -} - -bool socket::send_query_responses() -{ - query_response_task_list tasks; - - // Critical Section. - /////////////////////////////////////////////////////////////////////////// - query_response_task_mutex_.lock(); - query_response_tasks_.swap(tasks); - query_response_task_mutex_.unlock(); - /////////////////////////////////////////////////////////////////////////// - - for (const auto task: tasks) - if (!task->run()) - return false; - - return true; -} - -void socket::handle_websockets() -{ - bind_options options; - - auto format_origins = [](const config::endpoint::list& endpoints) - { - manager::origin_list origins; - origins.reserve(endpoints.size()); - for (const auto& endpoint: endpoints) - origins.emplace_back(endpoint.to_string()); - - return origins; - }; - - // This starts up the listener for the socket. - manager_ = std::make_shared(secure_, &socket::handle_event, - document_root_, format_origins(origins_)); - - if (!manager_ || !manager_->initialize()) - { - LOG_ERROR(LOG_SERVER) - << "Failed to initialize websocket manager"; - socket_started_.set_value(false); - return; - } - - if (secure_) - { - options.ssl_key = server_settings_.websockets_server_private_key; - options.ssl_certificate = - server_settings_.websockets_server_certificate; - options.ssl_ca_certificate = server_settings_.websockets_ca_certificate; - } - - options.user_data = static_cast(this); - if (!manager_->bind(websocket_endpoint(), options)) - { - socket_started_.set_value(false); - return; - } - - auto callback = [this]() - { - send_query_responses(); - }; - - socket_started_.set_value(true); - manager_->start(static_cast(callback)); -} - -// NOTE: query_socket is the only service that should implement this -// by returning something other than nullptr. -// -// The reason it's needed is so that socket::notify_query_work (which -// is called from handle_event in the web thread via -// handle_websockets) can retrieve the zmq socket within the query -// socket service (created on the same websocket thread) in order to -// send incoming requests to the internally connected zmq -// query_service. No other socket/service class requires this access. -const std::shared_ptr socket::service() const -{ - BITCOIN_ASSERT_MSG(false, "not implemented"); - return nullptr; -} - -bool socket::start_websocket_handler() -{ - auto status = socket_started_.get_future(); - thread_ = std::make_shared(&socket::handle_websockets, this); - return status.get(); -} - -bool socket::stop_websocket_handler() -{ - BITCOIN_ASSERT(manager_); - manager_->stop(); - thread_->join(); - return true; -} - -size_t socket::connection_count() const -{ - return work_.size(); -} - -// Called by the websocket handling thread via handle_event. -void socket::add_connection(connection_ptr connection) -{ - BITCOIN_ASSERT(work_.find(connection) == work_.end()); - - // Initialize a new query_work_map for this connection. - work_[connection].clear(); -} - -// Called by the websocket handling thread via handle_event. -void socket::remove_connection(connection_ptr connection) -{ - if (work_.empty()) - return; - - const auto it = work_.find(connection); - if (it != work_.end()) - { - // Tearing down a connection is O(n) where n is the amount of - // remaining outstanding queries. - auto& query_work_map = it->second; - for (const auto& query_work: query_work_map) - { - const auto correlation = correlations_.find( - query_work.second.correlation_id); - - if (correlation != correlations_.end()) - correlations_.erase(correlation); - } - - // Clear the query_work_map for this connection before removal. - query_work_map.clear(); - work_.erase(it); - } -} - -// Called by the websocket handling thread via handle_event. -// -// Errors write directly on the connection since this is called from -// the event_handler, which is called on the websocket thread. -void socket::notify_query_work(connection_ptr connection, - const std::string& method, uint32_t id, const std::string& parameters) -{ - const auto send_error_reply = [=](protocol_status status, - const bc::code& ec) - { - http_reply reply; - const auto error = to_json(ec, id); - const auto response = reply.generate(status, {}, error.size(), false); - LOG_VERBOSE(LOG_SERVER) << error + response; - connection->write(error + response); - }; - - if (handlers_.empty()) - { - send_error_reply(protocol_status::service_unavailable, - bc::error::http_invalid_request); - return; - } - - const auto handler = handlers_.find(method); - if (handler == handlers_.end()) - { - send_error_reply(protocol_status::not_found, - bc::error::http_method_not_found); - return; - } - - auto it = work_.find(connection); - if (it == work_.end()) - { - LOG_ERROR(LOG_SERVER) - << "Query work provided for unknown connection " << connection; - return; - } - - auto& query_work_map = it->second; - if (query_work_map.find(id) != query_work_map.end()) - { - send_error_reply(protocol_status::internal_server_error, - bc::error::http_internal_error); - return; - } - - query_work_map.emplace(id, - query_work_item{ id, sequence_, connection, method, parameters }); - - // Encode request based on query work and send to query_websocket. - zmq::message request; - handler->second.encode(request, handler->second.command, parameters, - sequence_); - - // While each connection has its own id map (meaning correlation ids passed - // from the web client are unique on a per connection basis, potentially - // utilizing the full range available), we need an internal mapping that - // will allow us to correlate each zmq request/response pair with the - // connection and original id number that originated it. The client never - // sees this sequence_ value. - correlations_[sequence_++] = { connection, id }; - - const auto ec = service()->send(request); - - if (ec) - { - send_error_reply(protocol_status::internal_server_error, - bc::error::http_internal_error); - return; - } -} - -// Sends json strings to the specified web or json_rpc socket (does nothing if -// neither). -void socket::send(connection_ptr connection, const std::string& json) -{ - if (!connection || connection->closed() || - (!connection->websocket() && !connection->json_rpc())) - return; - - // By using a task_sender via the manager's execute method, we guarantee - // that the write is performed on the manager's websocket thread (at the - // expense of copied json send and response payloads). - manager_->execute(std::make_shared(connection, json)); -} - -// Sends json strings to all connected web and json_rpc sockets. -void socket::broadcast(const std::string& json) -{ - auto sender = [this, &json](std::pair entry) - { - send(entry.first, json); - }; - - std::for_each(work_.begin(), work_.end(), sender); -} - -} // namespace http -} // namespace server -} // namespace libbitcoin diff --git a/src/web/http/utilities.cpp b/src/web/http/utilities.cpp deleted file mode 100644 index 083ac574..00000000 --- a/src/web/http/utilities.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 - -#ifdef _MSC_VER - #include -#endif - -#include -#include -#include -#include -#include -#include -#include - -namespace libbitcoin { -namespace server { -namespace http { - -// BUGBUG: std::strerror is not required to be thread safe. -std::string error_string() -{ -#ifdef _MSC_VER - WCHAR wide[MAX_PATH]; - const auto error = ::GetLastError(); - const auto flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; - const auto language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); - - if (::FormatMessageW(flags, nullptr, error, language, wide, MAX_PATH, - nullptr) == 0) - { - // TODO: incorporate `error`. - return "Format error message failed."; - } - - return to_utf8(wide); -#else - return { strerror(errno) }; -#endif -} - -#ifdef WITH_MBEDTLS -std::string mbedtls_error_string(int32_t error) -{ - static constexpr size_t error_buffer_length = 256; - std::array data; - mbedtls_strerror(error, data.data(), data.size()); - return { data.data() }; -} -#endif - -std::string op_to_string(websocket_op code) -{ - struct websocket_op_hasher - { - size_t operator()(const websocket_op& status) const - { - return std::hash{}(static_cast(status)); - } - }; - - static const std::string unknown = "unknown"; - static const std::unordered_map opcode_map - { - { websocket_op::continuation, "continue" }, - { websocket_op::text, "text" }, - { websocket_op::binary, "binary" }, - { websocket_op::close, "close" }, - { websocket_op::ping, "ping" }, - { websocket_op::pong, "pong" } - }; - - auto it = opcode_map.find(code); - return it == opcode_map.end() ? unknown : it->second; -} - -// Generates the RFC6455 handshake response described here: -// tools.ietf.org/html/rfc6455#section-1.3 -std::string websocket_key_response(const std::string& websocket_key) -{ - static const std::string rfc6455_guid = - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - const auto input = websocket_key + rfc6455_guid; - const data_chunk input_data(input.begin(), input.end()); - const data_slice slice(bc::sha1_hash(input_data)); - return encode_base64(slice); -} - -bool is_json_request(const std::string& header_value) -{ - return - header_value == "application/json-rpc" || - header_value == "application/json" || - header_value == "application/jsonrequest"; -} - -bool parse_http(http_request& out, const std::string& request) -{ - auto& headers = out.headers; - auto& parameters = out.parameters; - - out.message_length = request.size(); - - // Parse out the first line for: Method, URI, Protocol. - auto position = request.find("\r\n"); - if (position == std::string::npos) - return false; - - std::string method_line = request.substr(0, position); - string_list elements; - boost::split(elements, method_line, boost::is_any_of(" ")); - if (elements.size() != 3) - return false; - - for (auto& element: elements) - boost::algorithm::trim(element); - - // truncate the parameters from the uri (if any) - position = elements[1].find("?"); - if (position != std::string::npos) - elements[1] = elements[1].substr(0, position); - - boost::algorithm::to_lower(elements[0]); - boost::algorithm::to_lower(elements[2]); - - out.method = elements[0]; - out.uri = elements[1]; - out.protocol = elements[2]; - - position = out.protocol.find("/"); - if (position != std::string::npos) - { - const auto version = out.protocol.substr(position); - out.protocol_version = strtod(version.c_str(), nullptr); - } - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "Parsing HTTP request: Method: " << out.method << ", Uri: " - << out.uri << ", Protocol: " << out.protocol; - - // Parse out remaining lines to build both the header map and - // parameter map. - string_list header_lines; - boost::split(header_lines, request, boost::is_any_of("\r\n")); - - auto parse_line = [&headers](const std::string& line) - { - if (line.empty()) - return; - - string_list elements; - boost::split(elements, line, boost::is_any_of(":")); - if (elements.size() == 2) - { - boost::algorithm::trim(elements[0]); - boost::algorithm::trim(elements[1]); - boost::algorithm::to_lower(elements[0]); - - if (elements[0] != "sec-websocket-key") - boost::algorithm::to_lower(elements[1]); - - headers[elements[0]] = elements[1]; - } - else if (elements.size() > 2) - { - std::stringstream buffer; - for (size_t i = 0; i < elements.size(); i++) - { - boost::algorithm::trim(elements[i]); - - if (elements[0] != "sec-websocket-key") - boost::algorithm::to_lower(elements[i]); - - if (i > 0) - { - buffer << elements[i]; - if (i != elements.size() -1) - buffer << ":"; - } - } - - headers[elements[0]] = buffer.str(); - } - }; - - std::for_each(header_lines.begin(), header_lines.end(), parse_line); - - // Parse passed parameters (if any) - position = method_line.find("?"); - if (position != std::string::npos) - { - auto parameter_string = method_line.substr(position + 1); - - string_list parameter_elements; - boost::split(parameter_elements, parameter_string, - boost::is_any_of("&")); - - auto parse_parameter = [¶meters](const std::string& line) - { - if (line.empty()) - return; - - const auto line_end = line.find(" "); - const auto input_line = ((line_end == std::string::npos) ? line : - line.substr(0, line_end)); - - string_list elements; - boost::split(elements, input_line, boost::is_any_of("= ")); - if (elements.size() == 2) - { - boost::algorithm::trim(elements[0]); - boost::algorithm::trim(elements[1]); - boost::algorithm::to_lower(elements[0]); - boost::algorithm::to_lower(elements[1]); - parameters[elements[0]] = elements[1]; - } - }; - - std::for_each(parameter_elements.begin(), parameter_elements.end(), - parse_parameter); - } - - // Determine if this request contains the content length. - auto content_length = headers.find("content-length"); - out.content_length = (content_length != headers.end() ? - std::strtoul(content_length->second.c_str(), nullptr, 0) : 0); - - // Determine if this request is an upgrade request - auto connection = headers.find("connection"); - out.upgrade_request = ((connection != headers.end()) && - (connection->second.find("upgrade") != std::string::npos) && - (headers.find("sec-websocket-key") != headers.end())); - - // Determine if this request is a JSON-RPC request, or at least - // one that we support (i.e. non-standard; may not contain the - // required accept header nor the optional content-type header), - // via POST-only, etc. Instead we try to safely parse the data as - // JSON. - if (out.method == "post" && out.content_length > 0) - { - const auto json_request = request.substr(request.size() - - out.content_length, out.content_length); - - LOG_VERBOSE(LOG_SERVER_HTTP) - << "POST content: " << json_request; - - out.json_rpc = bc::property_tree(out.json_tree, json_request); - } - - return true; -} - -std::string mime_type(const boost::filesystem::path& path) -{ - static const std::unordered_map map - { - { ".html", "text/html" }, - { ".htm", "text/html" }, - { ".shtm", "text/html" }, - { ".shtml", "text/html" }, - { ".css", "text/css" }, - { ".js", "application/x-javascript" }, - { ".ico", "image/x-icon" }, - { ".gif", "image/gif" }, - { ".jpg", "image/jpeg" }, - { ".jpeg", "image/jpeg" }, - { ".png", "image/png" }, - { ".svg", "image/svg+xml" }, - { ".md", "text/plain" }, - { ".txt", "text/plain" }, - { ".torrent", "application/x-bittorrent" }, - { ".wav", "audio/x-wav" }, - { ".mp3", "audio/x-mp3" }, - { ".mid", "audio/mid" }, - { ".m3u", "audio/x-mpegurl" }, - { ".ogg", "application/ogg" }, - { ".ram", "audio/x-pn-realaudio" }, - { ".xml", "text/xml" }, - { ".ttf", "application/x-font-ttf" }, - { ".json", "application/json" }, - { ".xslt", "application/xml" }, - { ".xsl", "application/xml" }, - { ".ra", "audio/x-pn-realaudio" }, - { ".doc", "application/msword" }, - { ".exe", "application/octet-stream" }, - { ".zip", "application/x-zip-compressed" }, - { ".xls", "application/excel" }, - { ".tgz", "application/x-tar-gz" }, - { ".tar", "application/x-tar" }, - { ".gz", "application/x-gunzip" }, - { ".arj", "application/x-arj-compressed" }, - { ".rar", "application/x-rar-compressed" }, - { ".rtf", "application/rtf" }, - { ".pdf", "application/pdf" }, - { ".swf", "application/x-shockwave-flash" }, - { ".mpg", "video/mpeg" }, - { ".webm", "video/webm" }, - { ".mpeg", "video/mpeg" }, - { ".mov", "video/quicktime" }, - { ".mp4", "video/mp4" }, - { ".m4v", "video/x-m4v" }, - { ".asf", "video/x-ms-asf" }, - { ".avi", "video/x-msvideo" }, - { ".bmp", "image/bmp" } - }; - - if (path.has_extension()) - { - const auto it = map.find(path.extension().generic_string()); - if (it != map.end()) - return it->second; - } - - return { "text/plain" }; -} - -} // namespace http -} // namespace server -} // namespace libbitcoin diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index ce8daf81..766c271a 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -21,24 +21,24 @@ #include #include #include -#include -#include -#include namespace libbitcoin { namespace server { -using namespace bc::config; -using namespace bc::machine; using namespace bc::protocol; -using namespace http; +using namespace bc::system; +using namespace bc::system::config; +using namespace bc::system::machine; using role = zmq::socket::role; +using connection_ptr = http::connection_ptr; static constexpr auto poll_interval_milliseconds = 100u; query_socket::query_socket(zmq::context& context, server_node& node, bool secure) - : http::socket(context, node, secure) + : http::socket(context, node.protocol_settings(), secure), + settings_(node.server_settings()), + protocol_settings_(node.protocol_settings()) { // JSON to ZMQ request encoders. //------------------------------------------------------------------------- @@ -71,20 +71,26 @@ query_socket::query_socket(zmq::context& context, server_node& node, if (!connection || connection->closed()) return false; + if (json.size() > std::numeric_limits::max()) + { + LOG_ERROR(LOG_SERVER_HTTP) + << "Skipping JSON-RPC response of size " << json.size(); + return false; + } + + const int32_t json_size = static_cast(json.size()); + if (!connection->json_rpc()) - // BUGBUG: unguarded narrowing cast. - return connection->write(json) == static_cast(json.size()); + return connection->write(json) == json_size; - http_reply reply; - const auto response = reply.generate(protocol_status::ok, {}, - json.size(), false) + json; + http::http_reply reply; + const auto response = reply.generate(http::protocol_status::ok, {}, + json_size, false) + json; LOG_VERBOSE(LOG_SERVER_HTTP) << "Writing JSON-RPC response: " << response; - // BUGBUG: unguarded narrowing cast. - return connection->write(response) == - static_cast(response.size()); + return connection->write(response) == json_size; }; // JSON to ZMQ response decoders. @@ -97,7 +103,7 @@ query_socket::query_socket(zmq::context& context, server_node& node, data_source istream(data); istream_reader source(istream); const auto height = source.read_4_bytes_little_endian(); - decode_send(connection, to_json(height, id)); + decode_send(connection, http::to_json(height, id)); }; const auto decode_transaction = [this, &node, decode_send]( @@ -107,23 +113,23 @@ query_socket::query_socket(zmq::context& context, server_node& node, node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); const auto transaction = chain::transaction::factory(data, true, witness); - decode_send(connection, to_json(transaction, id)); + decode_send(connection, http::to_json(transaction, id)); }; const auto decode_block = [this, &node, decode_send]( - const data_chunk& data, const uint32_t id,connection_ptr connection) + const data_chunk& data, const uint32_t id, connection_ptr connection) { const auto witness = chain::script::is_enabled( node.blockchain_settings().enabled_forks(), rule_fork::bip141_rule); const auto block = chain::block::factory(data, witness); - decode_send(connection, to_json(block, id)); + decode_send(connection, http::to_json(block, id)); }; const auto decode_block_header = [this, decode_send](const data_chunk& data, - const uint32_t id,connection_ptr connection) + const uint32_t id, connection_ptr connection) { const auto header = chain::header::factory(data, true); - decode_send(connection, to_json(header, id)); + decode_send(connection, http::to_json(header, id)); }; handlers_["getblockcount"] = handlers @@ -153,6 +159,13 @@ query_socket::query_socket(zmq::context& context, server_node& node, encode_hash, decode_block_header }; + + handlers_["getblockheight"] = handlers + { + "blockchain.fetch_block_height", + encode_hash, + decode_height + }; } void query_socket::work() @@ -279,12 +292,12 @@ const endpoint& query_socket::zeromq_endpoint() const // local public zeromq endpoint since it does not affect the // external security of the websocket endpoint and impacts // configuration and performance for no additional gain. - return server_settings_.zeromq_query_endpoint(false /* secure_ */); + return settings_.zeromq_query_endpoint(false /* secure_ */); } const endpoint& query_socket::websocket_endpoint() const { - return server_settings_.websockets_query_endpoint(secure_); + return settings_.websockets_query_endpoint(secure_); } const endpoint& query_socket::query_endpoint() const diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index 452d73e4..a1b0be2f 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2018 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -18,29 +18,29 @@ */ #include -#include -#include #include #include #include #include -#include namespace libbitcoin { namespace server { using namespace std::placeholders; -using namespace bc::chain; -using namespace bc::message; using namespace bc::protocol; -using namespace http; +using namespace bc::system; +using namespace bc::system::chain; +using namespace bc::system::config; +using namespace bc::system::message; using role = zmq::socket::role; static constexpr auto poll_interval_milliseconds = 100u; transaction_socket::transaction_socket(zmq::context& context, server_node& node, bool secure) - : http::socket(context, node, secure) + : http::socket(context, node.protocol_settings(), secure), + settings_(node.server_settings()), + protocol_settings_(node.protocol_settings()) { } @@ -129,7 +129,7 @@ bool transaction_socket::handle_transaction(zmq::socket& subscriber) chain::transaction tx; tx.from_data(transaction_data, true, true); - broadcast(to_json(tx, sequence)); + broadcast(http::to_json(tx, sequence)); LOG_VERBOSE(LOG_SERVER) << "Broadcasted " << security_ << " socket tx [" @@ -137,18 +137,18 @@ bool transaction_socket::handle_transaction(zmq::socket& subscriber) return true; } -const config::endpoint& transaction_socket::zeromq_endpoint() const +const endpoint& transaction_socket::zeromq_endpoint() const { // The Websocket to zeromq backend internally always uses the // local public zeromq endpoint since it does not affect the // external security of the websocket endpoint and impacts // configuration and performance for no additional gain. - return server_settings_.zeromq_transaction_endpoint(false /* secure_ */); + return settings_.zeromq_transaction_endpoint(false /* secure_ */); } -const config::endpoint& transaction_socket::websocket_endpoint() const +const endpoint& transaction_socket::websocket_endpoint() const { - return server_settings_.websockets_transaction_endpoint(secure_); + return settings_.websockets_transaction_endpoint(secure_); } } // namespace server From b7071fcd6f23a759b766ffbad91a399b034620dd Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 22 Dec 2018 16:32:57 -0800 Subject: [PATCH 24/25] Style: balance braces. --- src/workers/notification_worker.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/workers/notification_worker.cpp b/src/workers/notification_worker.cpp index 0dc657a7..2a7d62ab 100644 --- a/src/workers/notification_worker.cpp +++ b/src/workers/notification_worker.cpp @@ -541,9 +541,10 @@ code notification_worker::subscribe_address(const message& request, address_mutex_.unlock_upgrade_and_lock(); //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - address_subscriptions_.insert({ + address_subscriptions_.insert( + { std::move(address_hash), - subscription{ request.route(), request.id(), current_time() } + { request.route(), request.id(), current_time() } }); address_mutex_.unlock(); @@ -593,9 +594,10 @@ code notification_worker::subscribe_stealth(const message& request, stealth_mutex_.unlock_upgrade_and_lock(); //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - stealth_subscriptions_.insert({ + stealth_subscriptions_.insert( + { std::move(prefix_filter), - subscription{ request.route(), request.id(), current_time() } + { request.route(), request.id(), current_time() } }); stealth_mutex_.unlock(); From 3b9daf5d6f21735c47d023cdc8f5bbf11967deb3 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Sat, 22 Dec 2018 16:37:46 -0800 Subject: [PATCH 25/25] Comments on hiding worker thread reference. --- src/web/block_socket.cpp | 1 + src/web/heartbeat_socket.cpp | 1 + src/web/query_socket.cpp | 1 + src/web/transaction_socket.cpp | 1 + 4 files changed, 4 insertions(+) diff --git a/src/web/block_socket.cpp b/src/web/block_socket.cpp index 99fb287f..ffc1ee4e 100644 --- a/src/web/block_socket.cpp +++ b/src/web/block_socket.cpp @@ -72,6 +72,7 @@ void block_socket::work() << "Bound " << security_ << " websocket block service to " << websocket_endpoint(); + // TODO: this should be hidden in socket base. // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; diff --git a/src/web/heartbeat_socket.cpp b/src/web/heartbeat_socket.cpp index e2433a98..a1a5a1b5 100644 --- a/src/web/heartbeat_socket.cpp +++ b/src/web/heartbeat_socket.cpp @@ -68,6 +68,7 @@ void heartbeat_socket::work() << "Bound " << security_ << " websocket heartbeat service to " << websocket_endpoint(); + // TODO: this should be hidden in socket base. // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; diff --git a/src/web/query_socket.cpp b/src/web/query_socket.cpp index 766c271a..fc327221 100644 --- a/src/web/query_socket.cpp +++ b/src/web/query_socket.cpp @@ -206,6 +206,7 @@ void query_socket::work() << "Bound " << security_ << " websocket query service to " << websocket_endpoint(); + // TODO: this should be hidden in socket base. // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_; diff --git a/src/web/transaction_socket.cpp b/src/web/transaction_socket.cpp index a1b0be2f..4254ed78 100644 --- a/src/web/transaction_socket.cpp +++ b/src/web/transaction_socket.cpp @@ -71,6 +71,7 @@ void transaction_socket::work() << "Bound " << security_ << " websocket transaction service to " << websocket_endpoint(); + // TODO: this should be hidden in socket base. // Hold a shared reference to the websocket thread_ so that we can // properly call stop_websocket_handler on cleanup. const auto thread_ref = thread_;