mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-08 11:16:51 +00:00
667 lines
25 KiB
C++
667 lines
25 KiB
C++
#pragma once
|
|
|
|
#include <test/jtx/envconfig.h>
|
|
|
|
#include <xrpl/basics/base64.h>
|
|
#include <xrpl/basics/random.h>
|
|
#include <xrpl/basics/strHex.h>
|
|
#include <xrpl/protocol/PublicKey.h>
|
|
#include <xrpl/protocol/SecretKey.h>
|
|
#include <xrpl/protocol/Sign.h>
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <boost/asio.hpp>
|
|
#include <boost/asio/ip/tcp.hpp>
|
|
#include <boost/asio/ssl/stream.hpp>
|
|
#include <boost/beast/core/flat_buffer.hpp>
|
|
#include <boost/beast/http.hpp>
|
|
#include <boost/beast/ssl.hpp>
|
|
#include <boost/beast/version.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include <memory>
|
|
#include <thread>
|
|
|
|
namespace xrpl {
|
|
namespace test {
|
|
|
|
class TrustedPublisherServer : public std::enable_shared_from_this<TrustedPublisherServer>
|
|
{
|
|
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
|
using address_type = boost::asio::ip::address;
|
|
using socket_type = boost::asio::ip::tcp::socket;
|
|
|
|
using req_type = boost::beast::http::request<boost::beast::http::string_body>;
|
|
using resp_type = boost::beast::http::response<boost::beast::http::string_body>;
|
|
using error_code = boost::system::error_code;
|
|
|
|
socket_type sock_;
|
|
endpoint_type ep_;
|
|
boost::asio::ip::tcp::acceptor acceptor_;
|
|
// Generates a version 1 validator list, using the int parameter as the
|
|
// actual version.
|
|
std::function<std::string(int)> getList_;
|
|
// Generates a version 2 validator list, using the int parameter as the
|
|
// actual version.
|
|
std::function<std::string(int)> getList2_;
|
|
|
|
// The SSL context is required, and holds certificates
|
|
bool useSSL_;
|
|
boost::asio::ssl::context sslCtx_{boost::asio::ssl::context::tlsv12};
|
|
|
|
SecretKey publisherSecret_;
|
|
PublicKey publisherPublic_;
|
|
|
|
// Load a signed certificate into the ssl context, and configure
|
|
// the context for use with a server.
|
|
inline void
|
|
load_server_certificate()
|
|
{
|
|
sslCtx_.set_password_callback(
|
|
[](std::size_t, boost::asio::ssl::context_base::password_purpose) { return "test"; });
|
|
|
|
sslCtx_.set_options(
|
|
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 |
|
|
boost::asio::ssl::context::single_dh_use);
|
|
|
|
sslCtx_.use_certificate_chain(boost::asio::buffer(cert().data(), cert().size()));
|
|
|
|
sslCtx_.use_private_key(
|
|
boost::asio::buffer(key().data(), key().size()),
|
|
boost::asio::ssl::context::file_format::pem);
|
|
|
|
sslCtx_.use_tmp_dh(boost::asio::buffer(dh().data(), dh().size()));
|
|
}
|
|
|
|
struct BlobInfo
|
|
{
|
|
BlobInfo(std::string b, std::string s) : blob(b), signature(s)
|
|
{
|
|
}
|
|
|
|
// base-64 encoded JSON containing the validator list.
|
|
std::string blob;
|
|
// hex-encoded signature of the blob using the publisher's signing key
|
|
std::string signature;
|
|
};
|
|
|
|
public:
|
|
struct Validator
|
|
{
|
|
PublicKey masterPublic;
|
|
PublicKey signingPublic;
|
|
std::string manifest;
|
|
};
|
|
|
|
static std::string
|
|
makeManifestString(
|
|
PublicKey const& pk,
|
|
SecretKey const& sk,
|
|
PublicKey const& spk,
|
|
SecretKey const& ssk,
|
|
int seq)
|
|
{
|
|
STObject st(sfGeneric);
|
|
st[sfSequence] = seq;
|
|
st[sfPublicKey] = pk;
|
|
st[sfSigningPubKey] = spk;
|
|
|
|
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
|
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
|
|
|
|
Serializer s;
|
|
st.add(s);
|
|
|
|
return base64_encode(std::string(static_cast<char const*>(s.data()), s.size()));
|
|
}
|
|
|
|
static Validator
|
|
randomValidator()
|
|
{
|
|
auto const secret = randomSecretKey();
|
|
auto const masterPublic = derivePublicKey(KeyType::ed25519, secret);
|
|
auto const signingKeys = randomKeyPair(KeyType::secp256k1);
|
|
return {
|
|
masterPublic,
|
|
signingKeys.first,
|
|
makeManifestString(masterPublic, secret, signingKeys.first, signingKeys.second, 1)};
|
|
}
|
|
|
|
// TrustedPublisherServer must be accessed through a shared_ptr.
|
|
// This constructor is only public so std::make_shared has access.
|
|
// The function `make_TrustedPublisherServer` should be used to create
|
|
// instances.
|
|
// The `futures` member is expected to be structured as
|
|
// effective / expiration time point pairs for use in version 2 UNLs
|
|
TrustedPublisherServer(
|
|
boost::asio::io_context& ioc,
|
|
std::vector<Validator> const& validators,
|
|
NetClock::time_point validUntil,
|
|
std::vector<std::pair<NetClock::time_point, NetClock::time_point>> const& futures,
|
|
bool useSSL = false,
|
|
int version = 1,
|
|
bool immediateStart = true,
|
|
int sequence = 1)
|
|
: sock_{ioc}
|
|
, ep_{boost::asio::ip::make_address(xrpl::test::getEnvLocalhostAddr()),
|
|
// 0 means let OS pick the port based on what's available
|
|
0}
|
|
, acceptor_{ioc}
|
|
, useSSL_{useSSL}
|
|
, publisherSecret_{randomSecretKey()}
|
|
, publisherPublic_{derivePublicKey(KeyType::ed25519, publisherSecret_)}
|
|
{
|
|
auto const keys = randomKeyPair(KeyType::secp256k1);
|
|
auto const manifest =
|
|
makeManifestString(publisherPublic_, publisherSecret_, keys.first, keys.second, 1);
|
|
|
|
std::vector<BlobInfo> blobInfo;
|
|
blobInfo.reserve(futures.size() + 1);
|
|
auto const [data, blob] = [&]() -> std::pair<std::string, std::string> {
|
|
// Builds the validator list, then encodes it into a blob.
|
|
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
|
",\"expiration\":" + std::to_string(validUntil.time_since_epoch().count()) +
|
|
",\"validators\":[";
|
|
|
|
for (auto const& val : validators)
|
|
{
|
|
data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) +
|
|
"\",\"manifest\":\"" + val.manifest + "\"},";
|
|
}
|
|
data.pop_back();
|
|
data += "]}";
|
|
std::string blob = base64_encode(data);
|
|
return std::make_pair(data, blob);
|
|
}();
|
|
auto const sig = strHex(sign(keys.first, keys.second, makeSlice(data)));
|
|
blobInfo.emplace_back(blob, sig);
|
|
getList_ = [blob = blob, sig, manifest, version](int interval) {
|
|
// Build the contents of a version 1 format UNL file
|
|
std::stringstream l;
|
|
l << "{\"blob\":\"" << blob << "\"" << ",\"signature\":\"" << sig << "\""
|
|
<< ",\"manifest\":\"" << manifest << "\""
|
|
<< ",\"refresh_interval\": " << interval << ",\"version\":" << version << '}';
|
|
return l.str();
|
|
};
|
|
for (auto const& future : futures)
|
|
{
|
|
std::string data = "{\"sequence\":" + std::to_string(++sequence) +
|
|
",\"effective\":" + std::to_string(future.first.time_since_epoch().count()) +
|
|
",\"expiration\":" + std::to_string(future.second.time_since_epoch().count()) +
|
|
",\"validators\":[";
|
|
|
|
// Use the same set of validators for simplicity
|
|
for (auto const& val : validators)
|
|
{
|
|
data += "{\"validation_public_key\":\"" + strHex(val.masterPublic) +
|
|
"\",\"manifest\":\"" + val.manifest + "\"},";
|
|
}
|
|
data.pop_back();
|
|
data += "]}";
|
|
std::string blob = base64_encode(data);
|
|
auto const sig = strHex(sign(keys.first, keys.second, makeSlice(data)));
|
|
blobInfo.emplace_back(blob, sig);
|
|
}
|
|
getList2_ = [blobInfo, manifest, version](int interval) {
|
|
// Build the contents of a version 2 format UNL file
|
|
// Use `version + 1` to get 2 for most tests, but have
|
|
// a "bad" version number for tests that provide an override.
|
|
std::stringstream l;
|
|
for (auto const& info : blobInfo)
|
|
{
|
|
l << "{\"blob\":\"" << info.blob << "\"" << ",\"signature\":\"" << info.signature
|
|
<< "\"},";
|
|
}
|
|
std::string blobs = l.str();
|
|
blobs.pop_back();
|
|
l.str(std::string());
|
|
l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest << "\""
|
|
<< ",\"refresh_interval\": " << interval << ",\"version\":" << (version + 1) << '}';
|
|
return l.str();
|
|
};
|
|
|
|
if (useSSL_)
|
|
{
|
|
// This holds the self-signed certificate used by the server
|
|
load_server_certificate();
|
|
}
|
|
}
|
|
|
|
void
|
|
start()
|
|
{
|
|
error_code ec;
|
|
acceptor_.open(ep_.protocol());
|
|
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
|
|
acceptor_.bind(ep_);
|
|
acceptor_.listen(boost::asio::socket_base::max_listen_connections);
|
|
acceptor_.async_accept(
|
|
sock_, [wp = std::weak_ptr<TrustedPublisherServer>{shared_from_this()}](error_code ec) {
|
|
if (auto p = wp.lock())
|
|
{
|
|
p->on_accept(ec);
|
|
}
|
|
});
|
|
}
|
|
|
|
void
|
|
stop()
|
|
{
|
|
error_code ec;
|
|
acceptor_.close(ec);
|
|
// TODO: consider making this join
|
|
// any running do_peer threads
|
|
}
|
|
|
|
~TrustedPublisherServer()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
endpoint_type
|
|
local_endpoint() const
|
|
{
|
|
return acceptor_.local_endpoint();
|
|
}
|
|
|
|
PublicKey const&
|
|
publisherPublic() const
|
|
{
|
|
return publisherPublic_;
|
|
}
|
|
|
|
/* CA/self-signed certs :
|
|
*
|
|
* The following three methods return certs/keys used by
|
|
* server and/or client to do the SSL handshake. These strings
|
|
* were generated using the script below. The server key and cert
|
|
* are used to configure the server (see load_server_certificate
|
|
* above). The ca.crt should be used to configure the client
|
|
* when ssl verification is enabled.
|
|
*
|
|
* note:
|
|
* cert() ==> server.crt
|
|
* key() ==> server.key
|
|
* ca_cert() ==> ca.crt
|
|
* dh() ==> dh.pem
|
|
```
|
|
#!/usr/bin/env bash
|
|
|
|
mkdir -p /tmp/__certs__
|
|
pushd /tmp/__certs__
|
|
rm *.crt *.key *.pem
|
|
|
|
# generate CA
|
|
openssl genrsa -out ca.key 2048
|
|
openssl req -new -x509 -nodes -days 10000 -key ca.key -out ca.crt \
|
|
-subj "/C=US/ST=CA/L=Los
|
|
Angeles/O=rippled-unit-tests/CN=example.com" # generate private cert
|
|
openssl genrsa -out server.key 2048
|
|
# Generate certificate signing request
|
|
# since our unit tests can run in either ipv4 or ipv6 mode,
|
|
# we need to use extensions (subjectAltName) so that we can
|
|
# associate both ipv4 and ipv6 localhost addresses with this cert
|
|
cat >"extras.cnf" <<EOF
|
|
[req]
|
|
req_extensions = v3_req
|
|
distinguished_name = req_distinguished_name
|
|
|
|
[req_distinguished_name]
|
|
|
|
[v3_req]
|
|
subjectAltName = @alt_names
|
|
|
|
[alt_names]
|
|
DNS.1 = localhost
|
|
IP.1 = ::1
|
|
EOF
|
|
openssl req -new -key server.key -out server.csr \
|
|
-config extras.cnf \
|
|
-subj "/C=US/ST=California/L=San
|
|
Francisco/O=rippled-unit-tests/CN=127.0.0.1" \
|
|
|
|
# Create public certificate by signing with our CA
|
|
openssl x509 -req -days 10000 -in server.csr -CA ca.crt -CAkey ca.key
|
|
-out server.crt \ -extfile extras.cnf -set_serial 01 -extensions v3_req
|
|
|
|
# generate DH params for server
|
|
openssl dhparam -out dh.pem 2048
|
|
# verify certs
|
|
openssl verify -CAfile ca.crt server.crt
|
|
openssl x509 -in server.crt -text -noout
|
|
popd
|
|
```
|
|
*/
|
|
static std::string const&
|
|
cert()
|
|
{
|
|
static std::string const cert{R"cert(
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDczCCAlugAwIBAgIBATANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEL
|
|
MAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMRswGQYDVQQKDBJyaXBw
|
|
bGVkLXVuaXQtdGVzdHMxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIyMDIwNTIz
|
|
NDk0M1oXDTQ5MDYyMzIzNDk0M1owazELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
|
|
bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoMEnJpcHBs
|
|
ZWQtdW5pdC10ZXN0czESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B
|
|
AQEFAAOCAQ8AMIIBCgKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJ
|
|
qTuBUcVcTRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs
|
|
+T/qkwmc/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9f
|
|
n9vrxnWX9LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3
|
|
zVDe7+sxR//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0q
|
|
S1Uj77mhwxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABoyowKDAmBgNV
|
|
HREEHzAdgglsb2NhbGhvc3SHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
|
|
BQADggEBAJkUFNS0CeEAKvo0ttzooXnCDH3esj2fwmLJQYLUGsAF8DFrFHTqZEcx
|
|
hFRdr0ftEb/VKpV9dVF6xtSoMU56kHOnhbHEWADyqdKUkCDjrGBet5QdWmEwNV2L
|
|
nYrwGQBAybMt/+1XMUV8HeLFJNHnyxfQYcW0fUsrmNGk8W0kzWuuq88qbhfXZAIx
|
|
KiXrzYpLlM0RlpWXRfYQ6mTdSrRrLnEo5MklizVgNB8HYX78lxa06zP08oReQcfT
|
|
GSGO8NEEq8BTVmp69zD1JyfvQcXzsi7WtkAX+/EOFZ7LesnZ6VsyjZ74wECCaQuD
|
|
X1yu/XxHqchM+DOzzVw6wRKaM7Zsk80=
|
|
-----END CERTIFICATE-----
|
|
)cert"};
|
|
return cert;
|
|
}
|
|
|
|
static std::string const&
|
|
key()
|
|
{
|
|
static std::string const key{R"pkey(
|
|
-----BEGIN RSA PRIVATE KEY-----
|
|
MIIEpAIBAAKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJqTuBUcVc
|
|
TRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs+T/qkwmc
|
|
/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9fn9vrxnWX
|
|
9LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3zVDe7+sx
|
|
R//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0qS1Uj77mh
|
|
wxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABAoIBAEC9MDpOu+quvg8+
|
|
kt4MKSFdIhQuM7WguNaTe5AkSspDrcJzT7SK275mp259QIYCzMxxuA8TSZTb8A1C
|
|
t6dgKbi7k6FaGMCYMRHzzK6NZfMbPi6cj245q9LYlZpdQswuM/FdPpPH1zUxrNYK
|
|
CIaooZ6ZHzlSD/eaRMgkBQEkONHrZZtEinLIvKedwssPCaXkIISmt7MFQTDOlxkf
|
|
K0Mt1mnRREPYbYSfPEEfIyy/KDIiB5AzgGt+uPOn8Oeb1pSqy69jpYcfhSj+bo4S
|
|
UV6qTuTfBd4qkkNI6d/Z7DcDJFFlfloG/vVgGk/beWNnL2e39vzxiebB3w+MQn4F
|
|
Wyx5mCECgYEA22z1/ihqt9LIAWtP42oSS3S/RxlFzpp5d7QfNqFnEoVgeRhQzleP
|
|
pRJIzVXpMYBxexZYqZA/q8xBSggz+2gmRoYnW20VIzl14DsSH378ye3FRwJB0tLy
|
|
dWU8DC7ZB5XQCTvI9UY3voJNToknODw7RCNO1h3V3T1y6JRLdcLskk8CgYEA2OLy
|
|
aE5bvsUaLBSv7W9NFhSuZ0p9Y0pFmRgHI7g8i/AgRZ0BgiE8u8OZSHmPJPMaNs/h
|
|
YIEIrlsgDci1PzwrUYseRp/aiVE1kyev09/ihqRXTPpLQu6h/d63KRe/06W3t5X3
|
|
Dmfj49hH5zGPBI/0y1ECV/n0fwnRhxSv7fNr3RECgYBEuFpOUAAkNApZj29ErNqv
|
|
8Q9ayAp5yx1RpQLFjEUIoub05e2gwgGF1DUiwc43p59iyjvYVwnp1x13fxwwl4yt
|
|
N6Sp2H7vOja1lCp33MB0yVeohodw7InsxFjLA/0KiBvQWH32exhIPOzTNNcooIx7
|
|
KYeuPUfWc0FCn/cGGZcXtwKBgQC1hp1k99CKBuY05suoanOWe5DNGud/ZvaBgD7Z
|
|
gqYKadxY52QPyknOzZNJuZQ5VM8n+S2lW9osNFDLuKUaW/3Vrh6U9c4vCC1TEPB0
|
|
4PnzvzDiWMsNJjWnCfU7C4meVyFBIt84y3NNjAQCWNRe+S3lzdOsVqRwf4NDD+l/
|
|
uzEYQQKBgQCJczIlwobm1Y6O41hbGZhZL/CGMNS6Z0INi2yasV0WDqYlh7XayHMD
|
|
cK55dMILcbHqeIBq/wR6sIhw6IJcaDBfFfrJiKKDilfij2lHxR2FQrEngtTCCRV+
|
|
ZzARzaWhQPvbDqEtLJDWuXZNXfL8/PTIs5NmuKuQ8F4+gQJpkQgwaw==
|
|
-----END RSA PRIVATE KEY-----
|
|
)pkey"};
|
|
return key;
|
|
}
|
|
|
|
static std::string const&
|
|
ca_cert()
|
|
{
|
|
static std::string const cert{R"cert(
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIDpzCCAo+gAwIBAgIUWc45WqaaNuaSLoFYTMC/Mjfqw/gwDQYJKoZIhvcNAQEL
|
|
BQAwYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtMb3MgQW5n
|
|
ZWxlczEbMBkGA1UECgwScmlwcGxlZC11bml0LXRlc3RzMRQwEgYDVQQDDAtleGFt
|
|
cGxlLmNvbTAeFw0yMjAyMDUyMzQ5MDFaFw00OTA2MjMyMzQ5MDFaMGMxCzAJBgNV
|
|
BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLTG9zIEFuZ2VsZXMxGzAZBgNV
|
|
BAoMEnJpcHBsZWQtdW5pdC10ZXN0czEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEi
|
|
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0f2JBW2XNW2wT5/ajX2qxmUY+
|
|
aNJGfpV6gZ5CmwdQpbHrPPvJoskxwsCyr3GifzT/GtCpmb1fiu59uUAPxQEYCxiq
|
|
V+HchX4g4Vl27xKJ0P+usxuEED9v7TCteKum9u9eMZ8UDF0fspXcnWGs9fXlyoTj
|
|
uTRP1SBQllk44DPc/KzlrtH+QNXmr9XQnP8XvwWCgJXMx87voxEGiFFOVhkSSAOv
|
|
v+OUGgEuq0NPgwv2LHBlYHSdkoU9F5Z/TmkCAFMShbyoUjldIz2gcWXjN2tespGo
|
|
D6qYvasvPIpmcholBBkc0z8QDt+RNq+Wzrults7epJXy/u+txGK9cHCNlLCpAgMB
|
|
AAGjUzBRMB0GA1UdDgQWBBS1oydh+YyqDNOFKYOvOtVMWKqV4zAfBgNVHSMEGDAW
|
|
gBS1oydh+YyqDNOFKYOvOtVMWKqV4zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
|
|
DQEBCwUAA4IBAQCDPyGKQwQ8Lz0yEgvIl/Uo9BtwAzlvjrLM/39qhStLQqDGSs2Q
|
|
xFIbtjzjuLf5vR3q6OJ62CCvzqXgHkJ+hzVN/tAvyliGTdjJrK+xv1M5a+XipO2f
|
|
c9lb4gRbFL/DyoeoWgb1Rkv3gFf0FlCYH+ZUcYb9ZYCRlGtFgOcxJI2g+T7jSLFp
|
|
8+hSzQ6W5Sp9L6b5iJyCww1vjBvBqzNyZMNeB4gXGtd6z9vMDSvKboTdGD7wcFB+
|
|
mRMyNekaRw+Npy4Hjou5sx272cXHHmPCSF5TjwdaibSaGjx1k0Q50mOf7S9KG5b5
|
|
7X1e3FekJlaD02EBEhtkXURIxogOQALdFncj
|
|
-----END CERTIFICATE-----
|
|
)cert"};
|
|
return cert;
|
|
}
|
|
|
|
static std::string const&
|
|
dh()
|
|
{
|
|
static std::string const dh{R"dh(
|
|
-----BEGIN DH PARAMETERS-----
|
|
MIIBCAKCAQEAp2I2fWEUZ3sCNfitSRC/MdAhJE/bS+NO0O2tWdIdlvmIFE6B5qhC
|
|
sGW9ojrQT8DTxBvGAcbjr/jagmlE3BV4oSnxyhP37G2mDvMOJ29J3NvFD/ZFAW0d
|
|
BvZJ1RNvMu29NmVCyt6/jgzcqrqnami9uD93aK+zaVrlPsPEYM8xB19HXwqsEYCL
|
|
ux2B7sqXm9Ts74HPg/EV+pcVon9phxNWxxgHlOvFc2QjZ3hXH++kzmJ4vs7N/XDB
|
|
xbEQ+TUZ5jbJGSeBqNFKFeuOUQGJ46Io0jBSYd4rSmKUXkvElQwR+n7KF3jy1uAt
|
|
/8hzd8tHn9TyW7Q2/CPkOA6dCXzltpOSowIBAg==
|
|
-----END DH PARAMETERS-----
|
|
)dh"};
|
|
return dh;
|
|
}
|
|
|
|
private:
|
|
struct lambda
|
|
{
|
|
int id;
|
|
TrustedPublisherServer& self;
|
|
socket_type sock;
|
|
boost::asio::executor_work_guard<boost::asio::executor> work;
|
|
bool ssl;
|
|
|
|
lambda(int id_, TrustedPublisherServer& self_, socket_type&& sock_, bool ssl_)
|
|
: id(id_), self(self_), sock(std::move(sock_)), work(sock_.get_executor()), ssl(ssl_)
|
|
{
|
|
}
|
|
|
|
void
|
|
operator()()
|
|
{
|
|
self.do_peer(id, std::move(sock), ssl);
|
|
}
|
|
};
|
|
|
|
void
|
|
on_accept(error_code ec)
|
|
{
|
|
if (ec || !acceptor_.is_open())
|
|
return;
|
|
|
|
static int id_ = 0;
|
|
std::thread{lambda{++id_, *this, std::move(sock_), useSSL_}}.detach();
|
|
acceptor_.async_accept(
|
|
sock_, [wp = std::weak_ptr<TrustedPublisherServer>{shared_from_this()}](error_code ec) {
|
|
if (auto p = wp.lock())
|
|
{
|
|
p->on_accept(ec);
|
|
}
|
|
});
|
|
}
|
|
|
|
void
|
|
do_peer(int id, socket_type&& s, bool ssl)
|
|
{
|
|
using namespace boost::beast;
|
|
using namespace boost::asio;
|
|
socket_type sock(std::move(s));
|
|
flat_buffer sb;
|
|
error_code ec;
|
|
std::optional<ssl_stream<ip::tcp::socket&>> ssl_stream;
|
|
|
|
if (ssl)
|
|
{
|
|
// Construct the stream around the socket
|
|
ssl_stream.emplace(sock, sslCtx_);
|
|
// Perform the SSL handshake
|
|
ssl_stream->handshake(ssl::stream_base::server, ec);
|
|
if (ec)
|
|
return;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
resp_type res;
|
|
req_type req;
|
|
try
|
|
{
|
|
if (ssl)
|
|
http::read(*ssl_stream, sb, req, ec);
|
|
else
|
|
http::read(sock, sb, req, ec);
|
|
|
|
if (ec)
|
|
break;
|
|
|
|
std::string_view const path = req.target();
|
|
res.insert("Server", "TrustedPublisherServer");
|
|
res.version(req.version());
|
|
res.keep_alive(req.keep_alive());
|
|
bool prepare = true;
|
|
|
|
if (boost::starts_with(path, "/validators2"))
|
|
{
|
|
res.result(http::status::ok);
|
|
res.insert("Content-Type", "application/json");
|
|
if (path == "/validators2/bad")
|
|
res.body() = "{ 'bad': \"2']";
|
|
else if (path == "/validators2/missing")
|
|
res.body() = "{\"version\": 2}";
|
|
else
|
|
{
|
|
int refresh = 5;
|
|
constexpr char const* refreshPrefix = "/validators2/refresh/";
|
|
if (boost::starts_with(path, refreshPrefix))
|
|
refresh = boost::lexical_cast<unsigned int>(
|
|
path.substr(strlen(refreshPrefix)));
|
|
res.body() = getList2_(refresh);
|
|
}
|
|
}
|
|
else if (boost::starts_with(path, "/validators"))
|
|
{
|
|
res.result(http::status::ok);
|
|
res.insert("Content-Type", "application/json");
|
|
if (path == "/validators/bad")
|
|
res.body() = "{ 'bad': \"1']";
|
|
else if (path == "/validators/missing")
|
|
res.body() = "{\"version\": 1}";
|
|
else
|
|
{
|
|
int refresh = 5;
|
|
constexpr char const* refreshPrefix = "/validators/refresh/";
|
|
if (boost::starts_with(path, refreshPrefix))
|
|
refresh = boost::lexical_cast<unsigned int>(
|
|
path.substr(strlen(refreshPrefix)));
|
|
res.body() = getList_(refresh);
|
|
}
|
|
}
|
|
else if (boost::starts_with(path, "/textfile"))
|
|
{
|
|
prepare = false;
|
|
res.result(http::status::ok);
|
|
res.insert("Content-Type", "text/example");
|
|
// if huge was requested, lie about content length
|
|
std::uint64_t cl = boost::starts_with(path, "/textfile/huge")
|
|
? std::numeric_limits<uint64_t>::max()
|
|
: 1024;
|
|
res.content_length(cl);
|
|
if (req.method() == http::verb::get)
|
|
{
|
|
std::stringstream body;
|
|
for (auto i = 0; i < 1024; ++i)
|
|
body << static_cast<char>(rand_int<short>(32, 126)),
|
|
res.body() = body.str();
|
|
}
|
|
}
|
|
else if (boost::starts_with(path, "/sleep/"))
|
|
{
|
|
auto const sleep_sec = boost::lexical_cast<unsigned int>(path.substr(7));
|
|
std::this_thread::sleep_for(std::chrono::seconds(sleep_sec));
|
|
}
|
|
else if (boost::starts_with(path, "/redirect"))
|
|
{
|
|
if (boost::ends_with(path, "/301"))
|
|
res.result(http::status::moved_permanently);
|
|
else if (boost::ends_with(path, "/302"))
|
|
res.result(http::status::found);
|
|
else if (boost::ends_with(path, "/307"))
|
|
res.result(http::status::temporary_redirect);
|
|
else if (boost::ends_with(path, "/308"))
|
|
res.result(http::status::permanent_redirect);
|
|
|
|
std::stringstream location;
|
|
if (boost::starts_with(path, "/redirect_to/"))
|
|
{
|
|
location << path.substr(13);
|
|
}
|
|
else if (!boost::starts_with(path, "/redirect_nolo"))
|
|
{
|
|
location << (ssl ? "https://" : "http://") << local_endpoint()
|
|
<< (boost::starts_with(path, "/redirect_forever/")
|
|
? path
|
|
: "/validators");
|
|
}
|
|
if (!location.str().empty())
|
|
res.insert("Location", location.str());
|
|
}
|
|
else
|
|
{
|
|
// unknown request
|
|
res.result(boost::beast::http::status::not_found);
|
|
res.insert("Content-Type", "text/html");
|
|
res.body() = "The file '" + std::string(path) +
|
|
"' was not "
|
|
"found";
|
|
}
|
|
|
|
if (prepare)
|
|
res.prepare_payload();
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
res = {};
|
|
res.result(boost::beast::http::status::internal_server_error);
|
|
res.version(req.version());
|
|
res.insert("Server", "TrustedPublisherServer");
|
|
res.insert("Content-Type", "text/html");
|
|
res.body() = std::string{"An internal error occurred"} + e.what();
|
|
res.prepare_payload();
|
|
}
|
|
|
|
if (ssl)
|
|
write(*ssl_stream, res, ec);
|
|
else
|
|
write(sock, res, ec);
|
|
|
|
if (ec || req.need_eof())
|
|
break;
|
|
}
|
|
|
|
// Perform the SSL shutdown
|
|
if (ssl)
|
|
ssl_stream->shutdown(ec);
|
|
}
|
|
};
|
|
|
|
inline std::shared_ptr<TrustedPublisherServer>
|
|
make_TrustedPublisherServer(
|
|
boost::asio::io_context& ioc,
|
|
std::vector<TrustedPublisherServer::Validator> const& validators,
|
|
NetClock::time_point validUntil,
|
|
std::vector<std::pair<NetClock::time_point, NetClock::time_point>> const& futures,
|
|
bool useSSL = false,
|
|
int version = 1,
|
|
bool immediateStart = true,
|
|
int sequence = 1)
|
|
{
|
|
auto const r = std::make_shared<TrustedPublisherServer>(
|
|
ioc, validators, validUntil, futures, useSSL, version, sequence);
|
|
if (immediateStart)
|
|
r->start();
|
|
return r;
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace xrpl
|