xrpld
Loading...
Searching...
No Matches
TrustedPublisherServer.h
1#pragma once
2
3#include <test/jtx/envconfig.h>
4
5#include <xrpl/basics/Slice.h>
6#include <xrpl/basics/base64.h>
7#include <xrpl/basics/chrono.h>
8#include <xrpl/basics/random.h>
9#include <xrpl/basics/strHex.h>
10#include <xrpl/protocol/HashPrefix.h>
11#include <xrpl/protocol/KeyType.h>
12#include <xrpl/protocol/PublicKey.h>
13#include <xrpl/protocol/SField.h>
14#include <xrpl/protocol/STObject.h>
15#include <xrpl/protocol/SecretKey.h>
16#include <xrpl/protocol/Serializer.h>
17#include <xrpl/protocol/Sign.h>
18
19#include <boost/asio.hpp>
20#include <boost/asio/ip/tcp.hpp>
21#include <boost/asio/ssl/stream.hpp>
22#include <boost/beast/core/flat_buffer.hpp>
23#include <boost/beast/http.hpp>
24#include <boost/beast/ssl.hpp>
25#include <boost/beast/version.hpp>
26#include <boost/lexical_cast.hpp>
27
28#include <chrono>
29#include <cstddef>
30#include <cstdint>
31#include <cstring>
32#include <exception>
33#include <functional>
34#include <limits>
35#include <memory>
36#include <optional>
37#include <sstream>
38#include <string>
39#include <string_view>
40#include <thread>
41#include <utility>
42#include <vector>
43
44namespace xrpl::test {
45
46class TrustedPublisherServer : public std::enable_shared_from_this<TrustedPublisherServer>
47{
48 using endpoint_type = boost::asio::ip::tcp::endpoint;
49 using address_type = boost::asio::ip::address;
50 using socket_type = boost::asio::ip::tcp::socket;
51
52 using req_type = boost::beast::http::request<boost::beast::http::string_body>;
53 using resp_type = boost::beast::http::response<boost::beast::http::string_body>;
54 using error_code = boost::system::error_code;
55
58 boost::asio::ip::tcp::acceptor acceptor_;
59 // Generates a version 1 validator list, using the int parameter as the
60 // actual version.
62 // Generates a version 2 validator list, using the int parameter as the
63 // actual version.
65
66 // The SSL context is required, and holds certificates
67 bool useSSL_;
68 boost::asio::ssl::context sslCtx_{boost::asio::ssl::context::tlsv12};
69
72
73 // Load a signed certificate into the ssl context, and configure
74 // the context for use with a server.
75 void
77 {
78 sslCtx_.set_password_callback(
79 [](std::size_t, boost::asio::ssl::context_base::password_purpose) { return "test"; });
80
81 sslCtx_.set_options(
82 boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 |
83 boost::asio::ssl::context::single_dh_use);
84
85 sslCtx_.use_certificate_chain(boost::asio::buffer(cert().data(), cert().size()));
86
87 sslCtx_.use_private_key(
88 boost::asio::buffer(key().data(), key().size()),
89 boost::asio::ssl::context::file_format::pem);
90
91 sslCtx_.use_tmp_dh(boost::asio::buffer(dh().data(), dh().size()));
92 }
93
94 struct BlobInfo
95 {
96 BlobInfo(std::string b, std::string s) : blob(std::move(b)), signature(std::move(s))
97 {
98 }
99
100 // base-64 encoded JSON containing the validator list.
102 // hex-encoded signature of the blob using the publisher's signing key
104 };
105
106public:
113
114 static std::string
116 PublicKey const& pk,
117 SecretKey const& sk,
118 PublicKey const& spk,
119 SecretKey const& ssk,
120 int seq)
121 {
123 st[sfSequence] = seq;
124 st[sfPublicKey] = pk;
125 st[sfSigningPubKey] = spk;
126
127 // NOLINTBEGIN(bugprone-unchecked-optional-access) publicKeyType returns value for valid
128 // keys
129 sign(st, HashPrefix::Manifest, *publicKeyType(spk), ssk);
130 sign(st, HashPrefix::Manifest, *publicKeyType(pk), sk, sfMasterSignature);
131 // NOLINTEND(bugprone-unchecked-optional-access)
132
133 Serializer s;
134 st.add(s);
135
136 return base64Encode(std::string(static_cast<char const*>(s.data()), s.size()));
137 }
138
139 static Validator
141 {
142 auto const secret = randomSecretKey();
143 auto const masterPublic = derivePublicKey(KeyType::Ed25519, secret);
144 auto const signingKeys = randomKeyPair(KeyType::Secp256k1);
145 return {
146 .masterPublic = masterPublic,
147 .signingPublic = signingKeys.first,
148 .manifest =
149 makeManifestString(masterPublic, secret, signingKeys.first, signingKeys.second, 1)};
150 }
151
152 // TrustedPublisherServer must be accessed through a shared_ptr.
153 // This constructor is only public so std::make_shared has access.
154 // The function `makeTrustedPublisherServer` should be used to create
155 // instances.
156 // The `futures` member is expected to be structured as
157 // effective / expiration time point pairs for use in version 2 UNLs
159 boost::asio::io_context& ioc,
160 std::vector<Validator> const& validators,
161 NetClock::time_point validUntil,
163 bool useSSL = false,
164 int version = 1,
165 bool immediateStart = true,
166 int sequence = 1)
167 : sock_{ioc}
168 , ep_{boost::asio::ip::make_address(xrpl::test::getEnvLocalhostAddr()),
169 // 0 means let OS pick the port based on what's available
170 0}
171 , acceptor_{ioc}
172 , useSSL_{useSSL}
175 {
176 auto const keys = randomKeyPair(KeyType::Secp256k1);
177 auto const manifest =
178 makeManifestString(publisherPublic_, publisherSecret_, keys.first, keys.second, 1);
179
180 std::vector<BlobInfo> blobInfo;
181 blobInfo.reserve(futures.size() + 1);
182 auto const [data, blob] = [&]() -> std::pair<std::string, std::string> {
183 // Builds the validator list, then encodes it into a blob.
184 std::string data = "{\"sequence\":" + std::to_string(sequence) +
185 ",\"expiration\":" + std::to_string(validUntil.time_since_epoch().count()) +
186 ",\"validators\":[";
187
188 for (auto const& val : validators)
189 {
190 data += R"({"validation_public_key":")" + strHex(val.masterPublic) +
191 R"(","manifest":")" + val.manifest + "\"},";
192 }
193 data.pop_back();
194 data += "]}";
195 std::string const blob = base64Encode(data);
196 return std::make_pair(data, blob);
197 }();
198 auto const sig = strHex(sign(keys.first, keys.second, makeSlice(data)));
199 blobInfo.emplace_back(blob, sig);
200 getList_ = [blob = blob, sig, manifest, version](int interval) {
201 // Build the contents of a version 1 format UNL file
203 l << R"({"blob":")" << blob << "\"" << R"(,"signature":")" << sig << "\""
204 << R"(,"manifest":")" << manifest << "\""
205 << ",\"refresh_interval\": " << interval << ",\"version\":" << version << '}';
206 return l.str();
207 };
208 for (auto const& future : futures)
209 {
210 std::string data = "{\"sequence\":" + std::to_string(++sequence) +
211 ",\"effective\":" + std::to_string(future.first.time_since_epoch().count()) +
212 ",\"expiration\":" + std::to_string(future.second.time_since_epoch().count()) +
213 ",\"validators\":[";
214
215 // Use the same set of validators for simplicity
216 for (auto const& val : validators)
217 {
218 data += R"({"validation_public_key":")" + strHex(val.masterPublic) +
219 R"(","manifest":")" + val.manifest + "\"},";
220 }
221 data.pop_back();
222 data += "]}";
223 std::string const blob = base64Encode(data);
224 auto const sig = strHex(sign(keys.first, keys.second, makeSlice(data)));
225 blobInfo.emplace_back(blob, sig);
226 }
227 getList2_ = [blobInfo, manifest, version](int interval) {
228 // Build the contents of a version 2 format UNL file
229 // Use `version + 1` to get 2 for most tests, but have
230 // a "bad" version number for tests that provide an override.
232 for (auto const& info : blobInfo)
233 {
234 l << R"({"blob":")" << info.blob << "\"" << R"(,"signature":")" << info.signature
235 << "\"},";
236 }
237 std::string blobs = l.str();
238 blobs.pop_back();
239 l.str(std::string());
240 l << "{\"blobs_v2\": [ " << blobs << R"(],"manifest":")" << manifest << "\""
241 << ",\"refresh_interval\": " << interval << ",\"version\":" << (version + 1) << '}';
242 return l.str();
243 };
244
245 if (useSSL_)
246 {
247 // This holds the self-signed certificate used by the server
249 }
250 }
251
252 void
254 {
255 error_code ec;
256 acceptor_.open(ep_.protocol());
257 acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
258 acceptor_.bind(ep_);
259 acceptor_.listen(boost::asio::socket_base::max_listen_connections);
260 acceptor_.async_accept(
262 if (auto p = wp.lock())
263 {
264 p->onAccept(ec);
265 }
266 });
267 }
268
269 void
271 {
272 error_code ec;
273 acceptor_.close(ec);
274 // TODO: consider making this join
275 // any running do_peer threads
276 }
277
279 {
280 stop();
281 }
282
285 {
286 return acceptor_.local_endpoint();
287 }
288
289 PublicKey const&
291 {
292 return publisherPublic_;
293 }
294
295 /* CA/self-signed certs :
296 *
297 * The following three methods return certs/keys used by
298 * server and/or client to do the SSL handshake. These strings
299 * were generated using the script below. The server key and cert
300 * are used to configure the server (see loadServerCertificate
301 * above). The ca.crt should be used to configure the client
302 * when ssl verification is enabled.
303 *
304 * note:
305 * cert() ==> server.crt
306 * key() ==> server.key
307 * caCert() ==> ca.crt
308 * dh() ==> dh.pem
309 ```
310 #!/usr/bin/env bash
311
312 mkdir -p /tmp/__certs__
313 pushd /tmp/__certs__
314 rm *.crt *.key *.pem
315
316 # generate CA
317 openssl genrsa -out ca.key 2048
318 openssl req -new -x509 -nodes -days 10000 -key ca.key -out ca.crt \
319 -subj "/C=US/ST=CA/L=Los
320 Angeles/O=xrpld-unit-tests/CN=example.com" # generate private cert
321 openssl genrsa -out server.key 2048
322 # Generate certificate signing request
323 # since our unit tests can run in either ipv4 or ipv6 mode,
324 # we need to use extensions (subjectAltName) so that we can
325 # associate both ipv4 and ipv6 localhost addresses with this cert
326 cat >"extras.cnf" <<EOF
327 [req]
328 req_extensions = v3_req
329 distinguished_name = req_distinguished_name
330
331 [req_distinguished_name]
332
333 [v3_req]
334 subjectAltName = @alt_names
335
336 [alt_names]
337 DNS.1 = localhost
338 IP.1 = ::1
339 EOF
340 openssl req -new -key server.key -out server.csr \
341 -config extras.cnf \
342 -subj "/C=US/ST=California/L=San
343 Francisco/O=xrpld-unit-tests/CN=127.0.0.1" \
344
345 # Create public certificate by signing with our CA
346 openssl x509 -req -days 10000 -in server.csr -CA ca.crt -CAkey ca.key
347 -out server.crt \ -extfile extras.cnf -set_serial 01 -extensions v3_req
348
349 # generate DH params for server
350 openssl dhparam -out dh.pem 2048
351 # verify certs
352 openssl verify -CAfile ca.crt server.crt
353 openssl x509 -in server.crt -text -noout
354 popd
355 ```
356 */
357 static std::string const&
359 {
360 static std::string const kCert{R"cert(
361-----BEGIN CERTIFICATE-----
362MIIDczCCAlugAwIBAgIBATANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEL
363MAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMRswGQYDVQQKDBJyaXBw
364bGVkLXVuaXQtdGVzdHMxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIyMDIwNTIz
365NDk0M1oXDTQ5MDYyMzIzNDk0M1owazELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
366bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoMEnJpcHBs
367ZWQtdW5pdC10ZXN0czESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B
368AQEFAAOCAQ8AMIIBCgKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJ
369qTuBUcVcTRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs
370+T/qkwmc/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9f
371n9vrxnWX9LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3
372zVDe7+sxR//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0q
373S1Uj77mhwxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABoyowKDAmBgNV
374HREEHzAdgglsb2NhbGhvc3SHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
375BQADggEBAJkUFNS0CeEAKvo0ttzooXnCDH3esj2fwmLJQYLUGsAF8DFrFHTqZEcx
376hFRdr0ftEb/VKpV9dVF6xtSoMU56kHOnhbHEWADyqdKUkCDjrGBet5QdWmEwNV2L
377nYrwGQBAybMt/+1XMUV8HeLFJNHnyxfQYcW0fUsrmNGk8W0kzWuuq88qbhfXZAIx
378KiXrzYpLlM0RlpWXRfYQ6mTdSrRrLnEo5MklizVgNB8HYX78lxa06zP08oReQcfT
379GSGO8NEEq8BTVmp69zD1JyfvQcXzsi7WtkAX+/EOFZ7LesnZ6VsyjZ74wECCaQuD
380X1yu/XxHqchM+DOzzVw6wRKaM7Zsk80=
381-----END CERTIFICATE-----
382)cert"};
383 return kCert;
384 }
385
386 static std::string const&
387 key()
389 static std::string const kKey{R"pkey(
390-----BEGIN RSA PRIVATE KEY-----
391MIIEpAIBAAKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJqTuBUcVc
392TRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs+T/qkwmc
393/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9fn9vrxnWX
3949LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3zVDe7+sx
395R//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0qS1Uj77mh
396wxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABAoIBAEC9MDpOu+quvg8+
397kt4MKSFdIhQuM7WguNaTe5AkSspDrcJzT7SK275mp259QIYCzMxxuA8TSZTb8A1C
398t6dgKbi7k6FaGMCYMRHzzK6NZfMbPi6cj245q9LYlZpdQswuM/FdPpPH1zUxrNYK
399CIaooZ6ZHzlSD/eaRMgkBQEkONHrZZtEinLIvKedwssPCaXkIISmt7MFQTDOlxkf
400K0Mt1mnRREPYbYSfPEEfIyy/KDIiB5AzgGt+uPOn8Oeb1pSqy69jpYcfhSj+bo4S
401UV6qTuTfBd4qkkNI6d/Z7DcDJFFlfloG/vVgGk/beWNnL2e39vzxiebB3w+MQn4F
402Wyx5mCECgYEA22z1/ihqt9LIAWtP42oSS3S/RxlFzpp5d7QfNqFnEoVgeRhQzleP
403pRJIzVXpMYBxexZYqZA/q8xBSggz+2gmRoYnW20VIzl14DsSH378ye3FRwJB0tLy
404dWU8DC7ZB5XQCTvI9UY3voJNToknODw7RCNO1h3V3T1y6JRLdcLskk8CgYEA2OLy
405aE5bvsUaLBSv7W9NFhSuZ0p9Y0pFmRgHI7g8i/AgRZ0BgiE8u8OZSHmPJPMaNs/h
406YIEIrlsgDci1PzwrUYseRp/aiVE1kyev09/ihqRXTPpLQu6h/d63KRe/06W3t5X3
407Dmfj49hH5zGPBI/0y1ECV/n0fwnRhxSv7fNr3RECgYBEuFpOUAAkNApZj29ErNqv
4088Q9ayAp5yx1RpQLFjEUIoub05e2gwgGF1DUiwc43p59iyjvYVwnp1x13fxwwl4yt
409N6Sp2H7vOja1lCp33MB0yVeohodw7InsxFjLA/0KiBvQWH32exhIPOzTNNcooIx7
410KYeuPUfWc0FCn/cGGZcXtwKBgQC1hp1k99CKBuY05suoanOWe5DNGud/ZvaBgD7Z
411gqYKadxY52QPyknOzZNJuZQ5VM8n+S2lW9osNFDLuKUaW/3Vrh6U9c4vCC1TEPB0
4124PnzvzDiWMsNJjWnCfU7C4meVyFBIt84y3NNjAQCWNRe+S3lzdOsVqRwf4NDD+l/
413uzEYQQKBgQCJczIlwobm1Y6O41hbGZhZL/CGMNS6Z0INi2yasV0WDqYlh7XayHMD
414cK55dMILcbHqeIBq/wR6sIhw6IJcaDBfFfrJiKKDilfij2lHxR2FQrEngtTCCRV+
415ZzARzaWhQPvbDqEtLJDWuXZNXfL8/PTIs5NmuKuQ8F4+gQJpkQgwaw==
416-----END RSA PRIVATE KEY-----
417)pkey"};
418 return kKey;
419 }
420
421 static std::string const&
422 caCert()
423 {
424 static std::string const kCert{R"cert(
425-----BEGIN CERTIFICATE-----
426MIIDpzCCAo+gAwIBAgIUWc45WqaaNuaSLoFYTMC/Mjfqw/gwDQYJKoZIhvcNAQEL
427BQAwYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtMb3MgQW5n
428ZWxlczEbMBkGA1UECgwScmlwcGxlZC11bml0LXRlc3RzMRQwEgYDVQQDDAtleGFt
429cGxlLmNvbTAeFw0yMjAyMDUyMzQ5MDFaFw00OTA2MjMyMzQ5MDFaMGMxCzAJBgNV
430BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLTG9zIEFuZ2VsZXMxGzAZBgNV
431BAoMEnJpcHBsZWQtdW5pdC10ZXN0czEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEi
432MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0f2JBW2XNW2wT5/ajX2qxmUY+
433aNJGfpV6gZ5CmwdQpbHrPPvJoskxwsCyr3GifzT/GtCpmb1fiu59uUAPxQEYCxiq
434V+HchX4g4Vl27xKJ0P+usxuEED9v7TCteKum9u9eMZ8UDF0fspXcnWGs9fXlyoTj
435uTRP1SBQllk44DPc/KzlrtH+QNXmr9XQnP8XvwWCgJXMx87voxEGiFFOVhkSSAOv
436v+OUGgEuq0NPgwv2LHBlYHSdkoU9F5Z/TmkCAFMShbyoUjldIz2gcWXjN2tespGo
437D6qYvasvPIpmcholBBkc0z8QDt+RNq+Wzrults7epJXy/u+txGK9cHCNlLCpAgMB
438AAGjUzBRMB0GA1UdDgQWBBS1oydh+YyqDNOFKYOvOtVMWKqV4zAfBgNVHSMEGDAW
439gBS1oydh+YyqDNOFKYOvOtVMWKqV4zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
440DQEBCwUAA4IBAQCDPyGKQwQ8Lz0yEgvIl/Uo9BtwAzlvjrLM/39qhStLQqDGSs2Q
441xFIbtjzjuLf5vR3q6OJ62CCvzqXgHkJ+hzVN/tAvyliGTdjJrK+xv1M5a+XipO2f
442c9lb4gRbFL/DyoeoWgb1Rkv3gFf0FlCYH+ZUcYb9ZYCRlGtFgOcxJI2g+T7jSLFp
4438+hSzQ6W5Sp9L6b5iJyCww1vjBvBqzNyZMNeB4gXGtd6z9vMDSvKboTdGD7wcFB+
444mRMyNekaRw+Npy4Hjou5sx272cXHHmPCSF5TjwdaibSaGjx1k0Q50mOf7S9KG5b5
4457X1e3FekJlaD02EBEhtkXURIxogOQALdFncj
446-----END CERTIFICATE-----
447)cert"};
448 return kCert;
449 }
450
451 static std::string const&
452 dh()
453 {
454 static std::string const kDH{R"dh(
455-----BEGIN DH PARAMETERS-----
456MIIBCAKCAQEAp2I2fWEUZ3sCNfitSRC/MdAhJE/bS+NO0O2tWdIdlvmIFE6B5qhC
457sGW9ojrQT8DTxBvGAcbjr/jagmlE3BV4oSnxyhP37G2mDvMOJ29J3NvFD/ZFAW0d
458BvZJ1RNvMu29NmVCyt6/jgzcqrqnami9uD93aK+zaVrlPsPEYM8xB19HXwqsEYCL
459ux2B7sqXm9Ts74HPg/EV+pcVon9phxNWxxgHlOvFc2QjZ3hXH++kzmJ4vs7N/XDB
460xbEQ+TUZ5jbJGSeBqNFKFeuOUQGJ46Io0jBSYd4rSmKUXkvElQwR+n7KF3jy1uAt
461/8hzd8tHn9TyW7Q2/CPkOA6dCXzltpOSowIBAg==
462-----END DH PARAMETERS-----
463)dh"};
464 return kDH;
465 }
466
467private:
468 struct Lambda
469 {
470 int id;
473 boost::asio::executor_work_guard<boost::asio::executor> work;
474 bool ssl;
475
477 : id(id), self(self), sock(std::move(sock)), work(this->sock.get_executor()), ssl(ssl)
478 {
479 }
480
481 void
482 operator()()
483 {
484 self.doPeer(id, std::move(sock), ssl);
485 }
486 };
487
488 void
490 {
491 if (ec || !acceptor_.is_open())
492 return;
493
494 static int nextId = 0; // NOLINT(readability-identifier-naming)
495 std::thread{Lambda{++nextId, *this, std::move(sock_), useSSL_}}.detach();
496 acceptor_.async_accept(
497 sock_, [wp = std::weak_ptr<TrustedPublisherServer>{shared_from_this()}](error_code ec) {
498 if (auto p = wp.lock())
499 {
500 p->onAccept(ec);
501 }
502 });
503 }
504
505 void
506 doPeer(int id, socket_type&& s, bool ssl)
507 {
508 using namespace boost::beast;
509 using namespace boost::asio;
510 socket_type sock(std::move(s));
511 flat_buffer sb;
512 error_code ec;
513 std::optional<ssl_stream<ip::tcp::socket&>> sslStream;
514
515 if (ssl)
516 {
517 // Construct the stream around the socket
518 sslStream.emplace(sock, sslCtx_);
519 // Perform the SSL handshake
520 sslStream->handshake(ssl::stream_base::server, ec);
521 if (ec)
522 return;
523 }
524
525 for (;;)
526 {
527 resp_type res;
528 req_type req;
529 try
530 {
531 if (ssl)
532 {
533 http::read(
534 *sslStream, sb, req, ec); // NOLINT(bugprone-unchecked-optional-access)
535 // ssl_stream emplaced when ssl==true
536 }
537 else
538 {
539 http::read(sock, sb, req, ec);
540 }
541
542 if (ec)
543 break;
544
545 std::string_view const path = req.target();
546 res.insert("Server", "TrustedPublisherServer");
547 res.version(req.version());
548 res.keep_alive(req.keep_alive());
549 bool prepare = true;
550
551 if (path.starts_with("/validators2"))
552 {
553 res.result(http::status::ok);
554 res.insert("Content-Type", "application/json");
555 if (path == "/validators2/bad")
556 {
557 res.body() = "{ 'bad': \"2']";
558 }
559 else if (path == "/validators2/missing")
560 {
561 res.body() = "{\"version\": 2}";
562 }
563 else
564 {
565 int refresh = 5;
566 static constexpr char const* kRefreshPrefix = "/validators2/refresh/";
567 if (path.starts_with(kRefreshPrefix))
568 {
569 refresh = boost::lexical_cast<unsigned int>(
570 path.substr(strlen(kRefreshPrefix)));
571 }
572 res.body() = getList2_(refresh);
573 }
574 }
575 else if (path.starts_with("/validators"))
576 {
577 res.result(http::status::ok);
578 res.insert("Content-Type", "application/json");
579 if (path == "/validators/bad")
580 {
581 res.body() = "{ 'bad': \"1']";
582 }
583 else if (path == "/validators/missing")
584 {
585 res.body() = "{\"version\": 1}";
586 }
587 else
588 {
589 int refresh = 5;
590 static constexpr char const* kRefreshPrefix = "/validators/refresh/";
591 if (path.starts_with(kRefreshPrefix))
592 {
593 refresh = boost::lexical_cast<unsigned int>(
594 path.substr(strlen(kRefreshPrefix)));
595 }
596 res.body() = getList_(refresh);
597 }
598 }
599 else if (path.starts_with("/textfile"))
600 {
601 prepare = false;
602 res.result(http::status::ok);
603 res.insert("Content-Type", "text/example");
604 // if huge was requested, lie about content length
605 std::uint64_t const cl = path.starts_with("/textfile/huge")
607 : 1024;
608 res.content_length(cl);
609 if (req.method() == http::verb::get)
610 {
611 std::stringstream body;
612 for (auto i = 0; i < 1024; ++i)
613 {
614 body << static_cast<char>(randInt<short>(32, 126)),
615 res.body() = body.str();
616 }
617 }
618 }
619 else if (path.starts_with("/sleep/"))
620 {
621 auto const sleepSec = boost::lexical_cast<unsigned int>(path.substr(7));
622 std::this_thread::sleep_for(std::chrono::seconds(sleepSec));
623 }
624 else if (path.starts_with("/redirect"))
625 {
626 if (path.ends_with("/301"))
627 {
628 res.result(http::status::moved_permanently);
629 }
630 else if (path.ends_with("/302"))
631 {
632 res.result(http::status::found);
633 }
634 else if (path.ends_with("/307"))
635 {
636 res.result(http::status::temporary_redirect);
637 }
638 else if (path.ends_with("/308"))
639 {
640 res.result(http::status::permanent_redirect);
641 }
642
643 std::stringstream location;
644 if (path.starts_with("/redirect_to/"))
645 {
646 location << path.substr(13);
647 }
648 else if (!path.starts_with("/redirect_nolo"))
649 {
650 location << (ssl ? "https://" : "http://") << localEndpoint()
651 << (path.starts_with("/redirect_forever/") ? path : "/validators");
652 }
653 if (!location.str().empty())
654 res.insert("Location", location.str());
655 }
656 else
657 {
658 // unknown request
659 res.result(boost::beast::http::status::not_found);
660 res.insert("Content-Type", "text/html");
661 res.body() = "The file '" + std::string(path) +
662 "' was not "
663 "found";
664 }
665
666 if (prepare)
667 res.prepare_payload();
668 }
669 catch (std::exception const& e)
670 {
671 res = {};
672 res.result(boost::beast::http::status::internal_server_error);
673 res.version(req.version());
674 res.insert("Server", "TrustedPublisherServer");
675 res.insert("Content-Type", "text/html");
676 res.body() = std::string{"An internal error occurred"} + e.what();
677 res.prepare_payload();
678 }
679
680 if (ssl)
681 {
682 write(*sslStream, res, ec); // NOLINT(bugprone-unchecked-optional-access)
683 // ssl_stream emplaced when ssl==true
684 }
685 else
686 {
687 write(sock, res, ec);
688 }
689
690 if (ec || req.need_eof())
691 break;
692 }
693
694 // Perform the SSL shutdown
695 if (ssl)
696 sslStream->shutdown(ec); // NOLINT(bugprone-unchecked-optional-access) ssl_stream
697 // emplaced when ssl==true
698 }
699};
700
701inline std::shared_ptr<TrustedPublisherServer>
703 boost::asio::io_context& ioc,
705 NetClock::time_point validUntil,
707 bool useSSL = false,
708 int version = 1,
709 bool immediateStart = true,
710 int sequence = 1)
711{
713 ioc, validators, validUntil, futures, useSSL, version, sequence);
714 if (immediateStart)
715 r->start();
716 return r;
717}
718
719} // namespace xrpl::test
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
A public key.
Definition PublicKey.h:53
void add(Serializer &s) const override
Definition STObject.cpp:123
A secret key.
Definition SecretKey.h:24
std::size_t size() const noexcept
Definition Serializer.h:51
void const * data() const noexcept
Definition Serializer.h:57
static std::string makeManifestString(PublicKey const &pk, SecretKey const &sk, PublicKey const &spk, SecretKey const &ssk, int seq)
boost::beast::http::request< boost::beast::http::string_body > req_type
boost::asio::ip::tcp::acceptor acceptor_
std::function< std::string(int)> getList_
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)
boost::asio::ip::tcp::endpoint endpoint_type
void doPeer(int id, socket_type &&s, bool ssl)
boost::asio::ip::tcp::socket socket_type
boost::beast::http::response< boost::beast::http::string_body > resp_type
std::function< std::string(int)> getList2_
T emplace_back(T... args)
T emplace(T... args)
T ends_with(T... args)
T make_pair(T... args)
T make_shared(T... args)
T max(T... args)
T move(T... args)
STL namespace.
void write(nudb::detail::ostream &os, std::size_t t)
Definition Varint.h:120
void sign(json::Value &jv, Account const &account, json::Value &sigObject)
Sign automatically into a specific Json field of the jv object.
Definition utility.cpp:40
std::shared_ptr< TrustedPublisherServer > makeTrustedPublisherServer(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)
char const * getEnvLocalhostAddr()
Definition envconfig.h:15
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
KeyType
Definition KeyType.h:8
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
SField const sfGeneric
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
SecretKey randomSecretKey()
Create a secret key using secure random numbers.
std::string base64Encode(std::uint8_t const *data, std::size_t len)
@ Manifest
Manifest.
Definition HashPrefix.h:84
T pop_back(T... args)
T reserve(T... args)
T sleep_for(T... args)
T starts_with(T... args)
T str(T... args)
T strlen(T... args)
Lambda(int id, TrustedPublisherServer &self, socket_type &&sock, bool ssl)
boost::asio::executor_work_guard< boost::asio::executor > work
T substr(T... args)
T time_since_epoch(T... args)
T to_string(T... args)
T what(T... args)