Skip to content

Conversation

@thecodefactory
Copy link
Member

Implement a HTTP(s), JSON-RPC, 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.

Resolves #153

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.
@thecodefactory
Copy link
Member Author

This is an improved version of #449 in that it improves performance considerably, has the addition of JSON-RPC support, and does not have the licensing issues due to the third-party dependency previously used.

@coveralls
Copy link

coveralls commented Oct 6, 2018

Coverage Status

Coverage remained the same at ?% when pulling b05f7c0 on thecodefactory:websocket-http into 7f8beaa on libbitcoin:master.

@evoskuil
Copy link
Member

MSVC++ 2017 warnings and errors:

1>utilities.cpp
1>..\..\..\..\src\web\http\connection.cpp(89): warning C4267: 'return': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\connection.cpp(100): warning C4267: 'return': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\connection.cpp(148): error C2664: 'int recv(SOCKET,char *,int,int)': cannot convert argument 2 from 'unsigned char *' to 'char *'
1>..\..\..\..\src\web\http\connection.cpp(148): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
1>..\..\..\..\src\web\http\connection.cpp(176): error C2664: 'int send(SOCKET,const char *,int,int)': cannot convert argument 2 from 'const unsigned char *' to 'const char *'
1>..\..\..\..\src\web\http\connection.cpp(176): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
1>..\..\..\..\src\web\http\connection.cpp(177): error C2440: 'initializing': cannot convert from 'libbitcoin::server::http::connection::do_write::<lambda_8dbd610e2ec718e3325475b48bb7a95f>' to 'std::function<int32_t (const unsigned char *,::size_t)>'
1>..\..\..\..\src\web\http\connection.cpp(177): note: No constructor could take the source type, or constructor overload resolution was ambiguous
1>..\..\..\..\src\web\http\connection.cpp(228): warning C4267: 'return': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\connection.cpp(282): warning C4267: 'return': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\connection.cpp(389): warning C4267: '=': conversion from 'size_t' to 'uint8_t', possible loss of data
1>..\..\..\..\src\web\http\utilities.cpp(40): error C2440: 'static_cast': cannot convert from 'LPVOID *' to 'LPTSTR'
1>..\..\..\..\src\web\http\utilities.cpp(40): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
1>..\..\..\..\src\web\http\utilities.cpp(36): error C2660: 'FormatMessageW': function does not take 6 arguments
1>..\..\..\..\src\web\http\utilities.cpp(44): error C2065: 'lpszFunction': undeclared identifier
1>..\..\..\..\src\web\http\utilities.cpp(42): error C2660: 'LocalAlloc': function does not take 1 arguments
1>..\..\..\..\src\web\http\utilities.cpp(49): error C2065: 'lpszFunction': undeclared identifier
1>..\..\..\..\src\web\http\utilities.cpp(51): error C2440: 'initializing': cannot convert from 'initializer list' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
1>..\..\..\..\src\web\http\utilities.cpp(51): note: No constructor could take the source type, or constructor overload resolution was ambiguous
1>..\..\..\..\src\web\http\manager.cpp(170): warning C4267: 'argument': conversion from 'size_t' to 'int', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(415): warning C4244: 'argument': conversion from 'const double' to '::size_t', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(433): warning C4267: '=': conversion from 'size_t' to 'long', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(435): warning C4267: '=': conversion from 'size_t' to 'long', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(461): error C3861: 'dup': identifier not found
1>..\..\..\..\src\web\http\manager.cpp(488): warning C4244: '=': conversion from 'libbitcoin::server::http::socket_connection' to 'int', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(882): warning C4244: '=': conversion from 'uint64_t' to 'int32_t', possible loss of data

@thecodefactory
Copy link
Member Author

Updated, but untested on MSVC++ ...

@evoskuil
Copy link
Member

I'll work through these:

1>..\..\..\..\src\web\http\connection.cpp(183): error C2440: 'reinterpret_cast': cannot convert from 'const unsigned char *' to 'char *'
1>..\..\..\..\src\web\http\connection.cpp(183): note: Conversion loses qualifiers
1>..\..\..\..\src\web\http\connection.cpp(181): error C2660: 'send': function does not take 3 arguments
1>..\..\..\..\src\web\http\connection.cpp(188): error C2440: 'initializing': cannot convert from 'libbitcoin::server::http::connection::do_write::<lambda_bc5c4fe4fb9a53653fe98e46dc1a9137>' to 'std::function<int32_t (const unsigned char *,int32_t)>'
1>..\..\..\..\src\web\http\connection.cpp(188): note: No constructor could take the source type, or constructor overload resolution was ambiguous
1>..\..\..\..\src\web\http\connection.cpp(246): warning C4267: 'argument': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\connection.cpp(257): warning C4267: 'initializing': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\connection.cpp(257): warning C4267: 'initializing': conversion from 'size_t' to 'const int32_t', possible loss of data
1>..\..\..\..\src\web\http\utilities.cpp(41): error C2440: 'initializing': cannot convert from 'initializer list' to 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>'
1>..\..\..\..\src\web\http\utilities.cpp(41): note: No constructor could take the source type, or constructor overload resolution was ambiguous
1>..\..\..\..\src\web\http\manager.cpp(547): warning C4267: 'argument': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(757): warning C4267: 'initializing': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(810): warning C4244: '=': conversion from 'uintmax_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(890): warning C4244: '=': conversion from 'uint64_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(938): warning C4267: '=': conversion from 'size_t' to 'int32_t', possible loss of data
1>..\..\..\..\src\web\http\manager.cpp(1178): warning C4267: 'argument': conversion from 'size_t' to 'int32_t', possible loss of data

Copy link
Member

@evoskuil evoskuil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working through some of these issues myself now, as well as eliminating a good amount of unnecessary casting. I'll post this as a partial update to the PR later today.


#include <ostream>
#include <bitcoin/bitcoin.hpp>
#include <bitcoin/server/define.hpp>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong reason, they didn't appear to be required. I did not consider external include uses, so it's probably safer to retain them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bc namespace is used (requires bitcoin.hpp) and the BCS_API symbol is used (requires define.hpp).

secure_notification_worker_(authenticator_, *this, true),
public_notification_worker_(authenticator_, *this, false)
public_notification_worker_(authenticator_, *this, false),
#ifdef WITH_MBEDTLS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to isolate the WITH_MBEDTLS conditions to lower level code (e.g. not in server_node and to simply prevent intialization if it is not configured.

{
auto data_length = static_cast<uint16_t>(length);
*reinterpret_cast<uint16_t*>(start) =
boost::endian::endian_reverse(data_length);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an endian bug, since it assumes a platform endianness (LE) in order to achieve a desired wire endianness (BE), but on a BE platform the wire serialization will be LE.


// Clear the query_work_map for this connection before removal.
query_work_map.clear();
connections_.erase(it);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a thread safety bug, since connections_ is unguarded but used concurrently throughout the class.

LocalFree(buffer);
return error_string;
#else
return { strerror(last_error()) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thread safety: std::strerror is not required to be thread safe.

FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&buffer), 0, nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unicode bug: the FormatMessage() and T-String (TSTR) macros compile as unicode (UCS-2) but the buffer is UTF-8.

// boost parsing helpers included from json_parser.hpp.
// See: https://svn.boost.org/trac10/ticket/12621
#include <functional>
using namespace std::placeholders;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid declaring using namespace in headers.

namespace server {
namespace http {

using namespace boost::property_tree;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid declaring using namespace in headers.

@thecodefactory
Copy link
Member Author

@evoskuil Thanks for taking a look at this, and good finds! Will review and re-test the incoming update.

@evoskuil
Copy link
Member

It's a huge amount of work, there's bound to be stuff like this, but I'm excited about it.

struct in_addr address;
std::memcpy(&address, &ip, sizeof(address));

const auto ip_address = std::string(inet_ntoa(address));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The address computation is dead code, as only the hostname string is used. What is the intent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, the ip_address comparison is missing, but should be used similarly to the hostname comparison.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me that this is an important security check. What exactly does it prevent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not claiming that it actually secures anything, because it's a field that can easily be spoofed. It is supposed to be checked/validated however:

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Origin

https://tools.ietf.org/html/rfc6455

See section 1.6:
"The WebSocket Protocol uses the origin model used by web browsers to
restrict which web pages can contact a WebSocket server when the
WebSocket Protocol is used from a web page. Naturally, when the
WebSocket Protocol is used by a dedicated client directly (i.e., not
from a web page through a web browser), the origin model is not
useful, as the client can provide any arbitrary origin string."

And:

https://tools.ietf.org/html/rfc6455#section-10.2

I chose to do the validation because the libbitcoin use-cases envisioned are both from browsers and standalone applications. In the standalone application case, it's trivial to fake with something like this (nodejs), as stated above:

var query_socket = new WebSocket(query_url,
{
    origin: 'https://localhost:9000',
    rejectUnauthorized: false
});


const auto ip = resolve_hostname({ hostname.data() });
struct in_addr address;
std::memcpy(&address, &ip, sizeof(address));
Copy link
Member

@evoskuil evoskuil Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an endianness bug as it copies from the address of an integral value into a multi-segment structure.

if (origin.empty())
return false;

if (gethostname(hostname.data(), max_hostname_length) != 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a unicode bug as it will only obtain hostname using the ANSI code page on Win32, but UTF-8 unicode is expected in the return.

}

int32_t written = connection->write(buffer.data(), read);
if (written < 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the subsequent log message it appears that this condition should be:

if (written != read)...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. I believe that check as originally written is ok (and the log could be adjusted since it is really showing the write failure, not the amount of bytes written), but then the bug is that file_transfer.offset should be incremented by written, not read. It's not technically required that all of that write complete (or be buffered) at once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, it may be better to adjust that check as you specified (although the log message could still be adjusted) because otherwise we'd have to account for seeking backwards by the amount difference (read - written) to prevent data corruption in the stream.


return ((origin.find("127.0.0.1") != std::string::npos) ||
(origin.find(hostname_string) != std::string::npos) ||
(origin.find("localhost") != std::string::npos));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests would match values such as 127.0.0.12 and foo_localhost42.

if (buffered_length >= high_water_mark_)
{
// Drain the buffered data.
while (!write_buffer_.empty())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throttles the server in order to satisfy a slow client. When high water is hit for a client new to-be-buffered messages should be dropped.

@thecodefactory
Copy link
Member Author

After many rounds of feedback and re-factoring, this PR is being replaced by libbitcoin/libbitcoin-protocol#193 and #497

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants