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