rippled
Loading...
Searching...
No Matches
ServerStatus_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/JSONRPCClient.h>
22#include <test/jtx/WSClient.h>
23#include <test/jtx/envconfig.h>
24
25#include <xrpld/app/ledger/LedgerMaster.h>
26#include <xrpld/app/misc/LoadFeeTrack.h>
27#include <xrpld/app/misc/NetworkOPs.h>
28#include <xrpld/rpc/ServerHandler.h>
29
30#include <xrpl/basics/base64.h>
31#include <xrpl/beast/test/yield_to.h>
32#include <xrpl/json/json_reader.h>
33
34#include <boost/algorithm/string/predicate.hpp>
35#include <boost/asio.hpp>
36#include <boost/asio/ssl.hpp>
37#include <boost/beast/core/multi_buffer.hpp>
38#include <boost/beast/http.hpp>
39
40#include <algorithm>
41#include <array>
42#include <random>
43#include <regex>
44
45namespace ripple {
46namespace test {
47
50{
51 class myFields : public boost::beast::http::fields
52 {
53 };
54
55 auto
57 std::string const& proto,
58 bool admin = true,
59 bool credentials = false)
60 {
61 auto const section_name =
62 boost::starts_with(proto, "h") ? "port_rpc" : "port_ws";
63 auto p = jtx::envconfig();
64
65 p->overwrite(section_name, "protocol", proto);
66 if (!admin)
67 p->overwrite(section_name, "admin", "");
68
69 if (credentials)
70 {
71 (*p)[section_name].set("admin_password", "p");
72 (*p)[section_name].set("admin_user", "u");
73 }
74
75 p->overwrite(
76 boost::starts_with(proto, "h") ? "port_ws" : "port_rpc",
77 "protocol",
78 boost::starts_with(proto, "h") ? "ws" : "http");
79
80 if (proto == "https")
81 {
82 // this port is here to allow the env to create its internal client,
83 // which requires an http endpoint to talk to. In the connection
84 // failure test, this endpoint should never be used
85 (*p)["server"].append("port_alt");
86 (*p)["port_alt"].set("ip", getEnvLocalhostAddr());
87 (*p)["port_alt"].set("port", "7099");
88 (*p)["port_alt"].set("protocol", "http");
89 (*p)["port_alt"].set("admin", getEnvLocalhostAddr());
90 }
91
92 return p;
93 }
94
95 auto
96 makeWSUpgrade(std::string const& host, uint16_t port)
97 {
98 using namespace boost::asio;
99 using namespace boost::beast::http;
100 request<string_body> req;
101
102 req.target("/");
103 req.version(11);
104 req.insert("Host", host + ":" + std::to_string(port));
105 req.insert("User-Agent", "test");
106 req.method(boost::beast::http::verb::get);
107 req.insert("Upgrade", "websocket");
108 {
109 // not secure, but OK for a testing
111 std::mt19937 e{rd()};
114 for (auto& v : key)
115 v = d(e);
116 req.insert(
117 "Sec-WebSocket-Key", base64_encode(key.data(), key.size()));
118 };
119 req.insert("Sec-WebSocket-Version", "13");
120 req.insert(boost::beast::http::field::connection, "upgrade");
121 return req;
122 }
123
124 auto
126 std::string const& host,
127 uint16_t port,
128 std::string const& body,
129 myFields const& fields)
130 {
131 using namespace boost::asio;
132 using namespace boost::beast::http;
133 request<string_body> req;
134
135 req.target("/");
136 req.version(11);
137 for (auto const& f : fields)
138 req.insert(f.name(), f.value());
139 req.insert("Host", host + ":" + std::to_string(port));
140 req.insert("User-Agent", "test");
141 if (body.empty())
142 {
143 req.method(boost::beast::http::verb::get);
144 }
145 else
146 {
147 req.method(boost::beast::http::verb::post);
148 req.insert("Content-Type", "application/json; charset=UTF-8");
149 req.body() = body;
150 }
151 req.prepare_payload();
152
153 return req;
154 }
155
156 void
158 boost::asio::yield_context& yield,
159 boost::beast::http::request<boost::beast::http::string_body>&& req,
160 std::string const& host,
161 uint16_t port,
162 bool secure,
163 boost::beast::http::response<boost::beast::http::string_body>& resp,
164 boost::system::error_code& ec)
165 {
166 using namespace boost::asio;
167 using namespace boost::beast::http;
168 io_service& ios = get_io_service();
169 ip::tcp::resolver r{ios};
170 boost::beast::multi_buffer sb;
171
172 auto it = r.async_resolve(
173 ip::tcp::resolver::query{host, std::to_string(port)}, yield[ec]);
174 if (ec)
175 return;
176
177 resp.body().clear();
178 if (secure)
179 {
180 ssl::context ctx{ssl::context::sslv23};
181 ctx.set_verify_mode(ssl::verify_none);
182 ssl::stream<ip::tcp::socket> ss{ios, ctx};
183 async_connect(ss.next_layer(), it, yield[ec]);
184 if (ec)
185 return;
186 ss.async_handshake(ssl::stream_base::client, yield[ec]);
187 if (ec)
188 return;
189 boost::beast::http::async_write(ss, req, yield[ec]);
190 if (ec)
191 return;
192 async_read(ss, sb, resp, yield[ec]);
193 if (ec)
194 return;
195 }
196 else
197 {
198 ip::tcp::socket sock{ios};
199 async_connect(sock, it, yield[ec]);
200 if (ec)
201 return;
202 boost::beast::http::async_write(sock, req, yield[ec]);
203 if (ec)
204 return;
205 async_read(sock, sb, resp, yield[ec]);
206 if (ec)
207 return;
208 }
209
210 return;
211 }
212
213 void
215 test::jtx::Env& env,
216 boost::asio::yield_context& yield,
217 bool secure,
218 boost::beast::http::response<boost::beast::http::string_body>& resp,
219 boost::system::error_code& ec)
220 {
221 auto const port =
222 env.app().config()["port_ws"].get<std::uint16_t>("port");
223 auto ip = env.app().config()["port_ws"].get<std::string>("ip");
224 doRequest(
225 yield, makeWSUpgrade(*ip, *port), *ip, *port, secure, resp, ec);
226 return;
227 }
228
229 void
231 test::jtx::Env& env,
232 boost::asio::yield_context& yield,
233 bool secure,
234 boost::beast::http::response<boost::beast::http::string_body>& resp,
235 boost::system::error_code& ec,
236 std::string const& body = "",
237 myFields const& fields = {})
238 {
239 auto const port =
240 env.app().config()["port_rpc"].get<std::uint16_t>("port");
241 auto const ip = env.app().config()["port_rpc"].get<std::string>("ip");
242 doRequest(
243 yield,
244 makeHTTPRequest(*ip, *port, body, fields),
245 *ip,
246 *port,
247 secure,
248 resp,
249 ec);
250 return;
251 }
252
253 auto
255 jtx::Env& env,
256 std::string const& proto,
257 std::string const& user,
258 std::string const& password,
259 bool subobject = false)
260 {
261 Json::Value jrr;
262
264 if (!user.empty())
265 {
266 jp["admin_user"] = user;
267 if (subobject)
268 {
269 // special case of bad password..passed as object
271 jpi["admin_password"] = password;
272 jp["admin_password"] = jpi;
273 }
274 else
275 {
276 jp["admin_password"] = password;
277 }
278 }
279
280 if (boost::starts_with(proto, "h"))
281 {
282 auto jrc = makeJSONRPCClient(env.app().config());
283 jrr = jrc->invoke("ledger_accept", jp);
284 }
285 else
286 {
287 auto wsc = makeWSClient(env.app().config(), proto == "ws2");
288 jrr = wsc->invoke("ledger_accept", jp);
289 }
290
291 return jrr;
292 }
293
294 // ------------
295 // Test Cases
296 // ------------
297
298 void
299 testAdminRequest(std::string const& proto, bool admin, bool credentials)
300 {
301 testcase << "Admin request over " << proto << ", config "
302 << (admin ? "enabled" : "disabled") << ", credentials "
303 << (credentials ? "" : "not ") << "set";
304 using namespace jtx;
305 Env env{*this, makeConfig(proto, admin, credentials)};
306
307 Json::Value jrr;
308 auto const proto_ws = boost::starts_with(proto, "w");
309
310 // the set of checks we do are different depending
311 // on how the admin config options are set
312
313 if (admin && credentials)
314 {
315 auto const user = env.app()
316 .config()[proto_ws ? "port_ws" : "port_rpc"]
317 .get<std::string>("admin_user");
318
319 auto const password =
320 env.app()
321 .config()[proto_ws ? "port_ws" : "port_rpc"]
322 .get<std::string>("admin_password");
323
324 // 1 - FAILS with wrong pass
325 jrr = makeAdminRequest(
326 env, proto, *user, *password + "_")[jss::result];
327 BEAST_EXPECT(
328 jrr["error"] == proto_ws ? "forbidden" : "noPermission");
329 BEAST_EXPECT(
330 jrr["error_message"] == proto_ws
331 ? "Bad credentials."
332 : "You don't have permission for this command.");
333
334 // 2 - FAILS with password in an object
335 jrr = makeAdminRequest(
336 env, proto, *user, *password, true)[jss::result];
337 BEAST_EXPECT(
338 jrr["error"] == proto_ws ? "forbidden" : "noPermission");
339 BEAST_EXPECT(
340 jrr["error_message"] == proto_ws
341 ? "Bad credentials."
342 : "You don't have permission for this command.");
343
344 // 3 - FAILS with wrong user
345 jrr = makeAdminRequest(
346 env, proto, *user + "_", *password)[jss::result];
347 BEAST_EXPECT(
348 jrr["error"] == proto_ws ? "forbidden" : "noPermission");
349 BEAST_EXPECT(
350 jrr["error_message"] == proto_ws
351 ? "Bad credentials."
352 : "You don't have permission for this command.");
353
354 // 4 - FAILS no credentials
355 jrr = makeAdminRequest(env, proto, "", "")[jss::result];
356 BEAST_EXPECT(
357 jrr["error"] == proto_ws ? "forbidden" : "noPermission");
358 BEAST_EXPECT(
359 jrr["error_message"] == proto_ws
360 ? "Bad credentials."
361 : "You don't have permission for this command.");
362
363 // 5 - SUCCEEDS with proper credentials
364 jrr = makeAdminRequest(env, proto, *user, *password)[jss::result];
365 BEAST_EXPECT(jrr["status"] == "success");
366 }
367 else if (admin)
368 {
369 // 1 - SUCCEEDS with proper credentials
370 jrr = makeAdminRequest(env, proto, "u", "p")[jss::result];
371 BEAST_EXPECT(jrr["status"] == "success");
372
373 // 2 - SUCCEEDS without proper credentials
374 jrr = makeAdminRequest(env, proto, "", "")[jss::result];
375 BEAST_EXPECT(jrr["status"] == "success");
376 }
377 else
378 {
379 // 1 - FAILS - admin disabled
380 jrr = makeAdminRequest(env, proto, "", "")[jss::result];
381 BEAST_EXPECT(
382 jrr["error"] == proto_ws ? "forbidden" : "noPermission");
383 BEAST_EXPECT(
384 jrr["error_message"] == proto_ws
385 ? "Bad credentials."
386 : "You don't have permission for this command.");
387 }
388 }
389
390 void
391 testWSClientToHttpServer(boost::asio::yield_context& yield)
392 {
393 testcase("WS client to http server fails");
394 using namespace jtx;
395 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
396 cfg->section("port_ws").set("protocol", "http,https");
397 return cfg;
398 })};
399
400 // non-secure request
401 {
402 boost::system::error_code ec;
403 boost::beast::http::response<boost::beast::http::string_body> resp;
404 doWSRequest(env, yield, false, resp, ec);
405 if (!BEAST_EXPECTS(!ec, ec.message()))
406 return;
407 BEAST_EXPECT(
408 resp.result() == boost::beast::http::status::unauthorized);
409 }
410
411 // secure request
412 {
413 boost::system::error_code ec;
414 boost::beast::http::response<boost::beast::http::string_body> resp;
415 doWSRequest(env, yield, true, resp, ec);
416 if (!BEAST_EXPECTS(!ec, ec.message()))
417 return;
418 BEAST_EXPECT(
419 resp.result() == boost::beast::http::status::unauthorized);
420 }
421 }
422
423 void
424 testStatusRequest(boost::asio::yield_context& yield)
425 {
426 testcase("Status request");
427 using namespace jtx;
428 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
429 cfg->section("port_rpc").set("protocol", "ws2,wss2");
430 cfg->section("port_ws").set("protocol", "http");
431 return cfg;
432 })};
433
434 // non-secure request
435 {
436 boost::system::error_code ec;
437 boost::beast::http::response<boost::beast::http::string_body> resp;
438 doHTTPRequest(env, yield, false, resp, ec);
439 if (!BEAST_EXPECTS(!ec, ec.message()))
440 return;
441 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
442 }
443
444 // secure request
445 {
446 boost::system::error_code ec;
447 boost::beast::http::response<boost::beast::http::string_body> resp;
448 doHTTPRequest(env, yield, true, resp, ec);
449 if (!BEAST_EXPECTS(!ec, ec.message()))
450 return;
451 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
452 }
453 }
454
455 void
456 testTruncatedWSUpgrade(boost::asio::yield_context& yield)
457 {
458 testcase("Partial WS upgrade request");
459 using namespace jtx;
460 using namespace boost::asio;
461 using namespace boost::beast::http;
462 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
463 cfg->section("port_ws").set("protocol", "ws2");
464 return cfg;
465 })};
466
467 auto const port =
468 env.app().config()["port_ws"].get<std::uint16_t>("port");
469 auto const ip = env.app().config()["port_ws"].get<std::string>("ip");
470
471 boost::system::error_code ec;
472 response<string_body> resp;
473 auto req = makeWSUpgrade(*ip, *port);
474
475 // truncate the request message to near the value of the version header
476 auto req_string = boost::lexical_cast<std::string>(req);
477 req_string.erase(req_string.find_last_of("13"), std::string::npos);
478
479 io_service& ios = get_io_service();
480 ip::tcp::resolver r{ios};
481 boost::beast::multi_buffer sb;
482
483 auto it = r.async_resolve(
484 ip::tcp::resolver::query{*ip, std::to_string(*port)}, yield[ec]);
485 if (!BEAST_EXPECTS(!ec, ec.message()))
486 return;
487
488 ip::tcp::socket sock{ios};
489 async_connect(sock, it, yield[ec]);
490 if (!BEAST_EXPECTS(!ec, ec.message()))
491 return;
492 async_write(sock, boost::asio::buffer(req_string), yield[ec]);
493 if (!BEAST_EXPECTS(!ec, ec.message()))
494 return;
495 // since we've sent an incomplete request, the server will
496 // keep trying to read until it gives up (by timeout)
497 async_read(sock, sb, resp, yield[ec]);
498 BEAST_EXPECT(ec);
499 }
500
501 void
503 std::string const& client_protocol,
504 std::string const& server_protocol,
505 boost::asio::yield_context& yield)
506 {
507 // The essence of this test is to have a client and server configured
508 // out-of-phase with respect to ssl (secure client and insecure server
509 // or vice-versa)
510 testcase << "Connect fails: " << client_protocol << " client to "
511 << server_protocol << " server";
512 using namespace jtx;
513 Env env{*this, makeConfig(server_protocol)};
514
515 boost::beast::http::response<boost::beast::http::string_body> resp;
516 boost::system::error_code ec;
517 if (boost::starts_with(client_protocol, "h"))
518 {
519 doHTTPRequest(env, yield, client_protocol == "https", resp, ec);
520 BEAST_EXPECT(ec);
521 }
522 else
523 {
525 env,
526 yield,
527 client_protocol == "wss" || client_protocol == "wss2",
528 resp,
529 ec);
530 BEAST_EXPECT(ec);
531 }
532 }
533
534 void
535 testAuth(bool secure, boost::asio::yield_context& yield)
536 {
537 testcase << "Server with authorization, "
538 << (secure ? "secure" : "non-secure");
539
540 using namespace test::jtx;
541 Env env{*this, envconfig([secure](std::unique_ptr<Config> cfg) {
542 (*cfg)["port_rpc"].set("user", "me");
543 (*cfg)["port_rpc"].set("password", "secret");
544 (*cfg)["port_rpc"].set(
545 "protocol", secure ? "https" : "http");
546 if (secure)
547 (*cfg)["port_ws"].set("protocol", "http,ws");
548 return cfg;
549 })};
550
551 Json::Value jr;
552 jr[jss::method] = "server_info";
553 boost::beast::http::response<boost::beast::http::string_body> resp;
554 boost::system::error_code ec;
555 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr));
556 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
557
559 auth.insert("Authorization", "");
560 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
561 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
562
563 auth.set("Authorization", "Basic NOT-VALID");
564 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
565 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
566
567 auth.set("Authorization", "Basic " + base64_encode("me:badpass"));
568 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
569 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
570
571 auto const user = env.app()
572 .config()
573 .section("port_rpc")
574 .get<std::string>("user")
575 .value();
576 auto const pass = env.app()
577 .config()
578 .section("port_rpc")
579 .get<std::string>("password")
580 .value();
581
582 // try with the correct user/pass, but not encoded
583 auth.set("Authorization", "Basic " + user + ":" + pass);
584 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
585 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
586
587 // finally if we use the correct user/pass encoded, we should get a 200
588 auth.set("Authorization", "Basic " + base64_encode(user + ":" + pass));
589 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
590 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
591 BEAST_EXPECT(!resp.body().empty());
592 }
593
594 void
595 testLimit(boost::asio::yield_context& yield, int limit)
596 {
597 testcase << "Server with connection limit of " << limit;
598
599 using namespace test::jtx;
600 using namespace boost::asio;
601 using namespace boost::beast::http;
602 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
603 (*cfg)["port_rpc"].set("limit", std::to_string(limit));
604 return cfg;
605 })};
606
607 auto const port =
608 env.app().config()["port_rpc"].get<std::uint16_t>("port").value();
609 auto const ip =
610 env.app().config()["port_rpc"].get<std::string>("ip").value();
611
612 boost::system::error_code ec;
613 io_service& ios = get_io_service();
614 ip::tcp::resolver r{ios};
615
616 Json::Value jr;
617 jr[jss::method] = "server_info";
618
619 auto it = r.async_resolve(
620 ip::tcp::resolver::query{ip, std::to_string(port)}, yield[ec]);
621 BEAST_EXPECT(!ec);
622
624 clients;
625 int connectionCount{1}; // starts at 1 because the Env already has one
626 // for JSONRPCCLient
627
628 // for nonzero limits, go one past the limit, although failures happen
629 // at the limit, so this really leads to the last two clients failing.
630 // for zero limit, pick an arbitrary nonzero number of clients - all
631 // should connect fine.
632
633 int testTo = (limit == 0) ? 50 : limit + 1;
634 while (connectionCount < testTo)
635 {
637 ip::tcp::socket{ios}, boost::beast::multi_buffer{}));
638 async_connect(clients.back().first, it, yield[ec]);
639 BEAST_EXPECT(!ec);
640 auto req = makeHTTPRequest(ip, port, to_string(jr), {});
641 async_write(clients.back().first, req, yield[ec]);
642 BEAST_EXPECT(!ec);
643 ++connectionCount;
644 }
645
646 int readCount = 0;
647 for (auto& [soc, buf] : clients)
648 {
649 boost::beast::http::response<boost::beast::http::string_body> resp;
650 async_read(soc, buf, resp, yield[ec]);
651 ++readCount;
652 // expect the reads to fail for the clients that connected at or
653 // above the limit. If limit is 0, all reads should succeed
654 BEAST_EXPECT(
655 (limit == 0 || readCount < limit - 1) ? (!ec) : bool(ec));
656 }
657 }
658
659 void
660 testWSHandoff(boost::asio::yield_context& yield)
661 {
662 testcase("Connection with WS handoff");
663
664 using namespace test::jtx;
665 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
666 (*cfg)["port_ws"].set("protocol", "wss");
667 return cfg;
668 })};
669
670 auto const port =
671 env.app().config()["port_ws"].get<std::uint16_t>("port").value();
672 auto const ip =
673 env.app().config()["port_ws"].get<std::string>("ip").value();
674 boost::beast::http::response<boost::beast::http::string_body> resp;
675 boost::system::error_code ec;
676 doRequest(yield, makeWSUpgrade(ip, port), ip, port, true, resp, ec);
677 BEAST_EXPECT(
678 resp.result() == boost::beast::http::status::switching_protocols);
679 BEAST_EXPECT(
680 resp.find("Upgrade") != resp.end() &&
681 resp["Upgrade"] == "websocket");
682 BEAST_EXPECT(
683 resp.find("Connection") != resp.end() &&
684 resp["Connection"] == "upgrade");
685 }
686
687 void
688 testNoRPC(boost::asio::yield_context& yield)
689 {
690 testcase("Connection to port with no RPC enabled");
691
692 using namespace test::jtx;
693 Env env{*this};
694
695 auto const port =
696 env.app().config()["port_ws"].get<std::uint16_t>("port").value();
697 auto const ip =
698 env.app().config()["port_ws"].get<std::string>("ip").value();
699 boost::beast::http::response<boost::beast::http::string_body> resp;
700 boost::system::error_code ec;
701 // body content is required here to avoid being
702 // detected as a status request
703 doRequest(
704 yield,
705 makeHTTPRequest(ip, port, "foo", {}),
706 ip,
707 port,
708 false,
709 resp,
710 ec);
711 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
712 BEAST_EXPECT(resp.body() == "Forbidden\r\n");
713 }
714
715 void
716 testWSRequests(boost::asio::yield_context& yield)
717 {
718 testcase("WS client sends assorted input");
719
720 using namespace test::jtx;
721 using namespace boost::asio;
722 using namespace boost::beast::http;
723 Env env{*this};
724
725 auto const port =
726 env.app().config()["port_ws"].get<std::uint16_t>("port").value();
727 auto const ip =
728 env.app().config()["port_ws"].get<std::string>("ip").value();
729 boost::system::error_code ec;
730
731 io_service& ios = get_io_service();
732 ip::tcp::resolver r{ios};
733
734 auto it = r.async_resolve(
735 ip::tcp::resolver::query{ip, std::to_string(port)}, yield[ec]);
736 if (!BEAST_EXPECT(!ec))
737 return;
738
739 ip::tcp::socket sock{ios};
740 async_connect(sock, it, yield[ec]);
741 if (!BEAST_EXPECT(!ec))
742 return;
743
744 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
745 ws.handshake(ip + ":" + std::to_string(port), "/");
746
747 // helper lambda, used below
748 auto sendAndParse = [&](std::string const& req) -> Json::Value {
749 ws.async_write_some(true, buffer(req), yield[ec]);
750 if (!BEAST_EXPECT(!ec))
751 return Json::objectValue;
752
753 boost::beast::multi_buffer sb;
754 ws.async_read(sb, yield[ec]);
755 if (!BEAST_EXPECT(!ec))
756 return Json::objectValue;
757
758 Json::Value resp;
759 Json::Reader jr;
760 if (!BEAST_EXPECT(jr.parse(
761 boost::lexical_cast<std::string>(
762 boost::beast::make_printable(sb.data())),
763 resp)))
764 return Json::objectValue;
765 sb.consume(sb.size());
766 return resp;
767 };
768
769 { // send invalid json
770 auto resp = sendAndParse("NOT JSON");
771 BEAST_EXPECT(
772 resp.isMember(jss::error) && resp[jss::error] == "jsonInvalid");
773 BEAST_EXPECT(!resp.isMember(jss::status));
774 }
775
776 { // send incorrect json (method and command fields differ)
777 Json::Value jv;
778 jv[jss::command] = "foo";
779 jv[jss::method] = "bar";
780 auto resp = sendAndParse(to_string(jv));
781 BEAST_EXPECT(
782 resp.isMember(jss::error) &&
783 resp[jss::error] == "missingCommand");
784 BEAST_EXPECT(
785 resp.isMember(jss::status) && resp[jss::status] == "error");
786 }
787
788 { // send a ping (not an error)
789 Json::Value jv;
790 jv[jss::command] = "ping";
791 auto resp = sendAndParse(to_string(jv));
792 BEAST_EXPECT(
793 resp.isMember(jss::status) && resp[jss::status] == "success");
794 BEAST_EXPECT(
795 resp.isMember(jss::result) &&
796 resp[jss::result].isMember(jss::role) &&
797 resp[jss::result][jss::role] == "admin");
798 }
799 }
800
801 void
802 testAmendmentWarning(boost::asio::yield_context& yield)
803 {
804 testcase(
805 "Status request over WS and RPC with/without Amendment Warning");
806 using namespace jtx;
807 using namespace boost::asio;
808 using namespace boost::beast::http;
809 Env env{
810 *this,
811 validator(
813 cfg->section("port_rpc").set("protocol", "http");
814 return cfg;
815 }),
816 "")};
817
818 env.close();
819
820 // advance the ledger so that server status
821 // sees a published ledger -- without this, we get a status
822 // failure message about no published ledgers
824
825 // make an RPC server info request and look for
826 // amendment warning status
827 auto si = env.rpc("server_info")[jss::result];
828 BEAST_EXPECT(si.isMember(jss::info));
829 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
830 BEAST_EXPECT(
831 env.app().getOPs().getConsensusInfo()["validating"] == true);
832 BEAST_EXPECT(!si.isMember(jss::warnings));
833
834 // make an RPC server state request and look for
835 // amendment warning status
836 si = env.rpc("server_state")[jss::result];
837 BEAST_EXPECT(si.isMember(jss::state));
838 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
839 BEAST_EXPECT(
840 env.app().getOPs().getConsensusInfo()["validating"] == true);
841 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
842
843 auto const port_ws =
844 env.app().config()["port_ws"].get<std::uint16_t>("port");
845 auto const ip_ws = env.app().config()["port_ws"].get<std::string>("ip");
846
847 boost::system::error_code ec;
848 response<string_body> resp;
849
850 doRequest(
851 yield,
852 makeHTTPRequest(*ip_ws, *port_ws, "", {}),
853 *ip_ws,
854 *port_ws,
855 false,
856 resp,
857 ec);
858
859 if (!BEAST_EXPECTS(!ec, ec.message()))
860 return;
861 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
862 BEAST_EXPECT(
863 resp.body().find("connectivity is working.") != std::string::npos);
864
865 // mark the Network as having an Amendment Warning, but won't fail
866 env.app().getOPs().setAmendmentWarned();
867 env.app().getOPs().beginConsensus(env.closed()->info().hash, {});
868
869 // consensus doesn't change
870 BEAST_EXPECT(
871 env.app().getOPs().getConsensusInfo()["validating"] == true);
872
873 // RPC request server_info again, now unsupported majority should be
874 // returned
875 si = env.rpc("server_info")[jss::result];
876 BEAST_EXPECT(si.isMember(jss::info));
877 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
878 BEAST_EXPECT(
879 si[jss::info].isMember(jss::warnings) &&
880 si[jss::info][jss::warnings].isArray() &&
881 si[jss::info][jss::warnings].size() == 1 &&
882 si[jss::info][jss::warnings][0u][jss::id].asInt() ==
884
885 // RPC request server_state again, now unsupported majority should be
886 // returned
887 si = env.rpc("server_state")[jss::result];
888 BEAST_EXPECT(si.isMember(jss::state));
889 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
890 BEAST_EXPECT(
891 si[jss::state].isMember(jss::warnings) &&
892 si[jss::state][jss::warnings].isArray() &&
893 si[jss::state][jss::warnings].size() == 1 &&
894 si[jss::state][jss::warnings][0u][jss::id].asInt() ==
896
897 // but status does not indicate a problem
898 doRequest(
899 yield,
900 makeHTTPRequest(*ip_ws, *port_ws, "", {}),
901 *ip_ws,
902 *port_ws,
903 false,
904 resp,
905 ec);
906
907 if (!BEAST_EXPECTS(!ec, ec.message()))
908 return;
909 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
910 BEAST_EXPECT(
911 resp.body().find("connectivity is working.") != std::string::npos);
912
913 // with ELB_SUPPORT, status still does not indicate a problem
914 env.app().config().ELB_SUPPORT = true;
915
916 doRequest(
917 yield,
918 makeHTTPRequest(*ip_ws, *port_ws, "", {}),
919 *ip_ws,
920 *port_ws,
921 false,
922 resp,
923 ec);
924
925 if (!BEAST_EXPECTS(!ec, ec.message()))
926 return;
927 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
928 BEAST_EXPECT(
929 resp.body().find("connectivity is working.") != std::string::npos);
930 }
931
932 void
933 testAmendmentBlock(boost::asio::yield_context& yield)
934 {
935 testcase("Status request over WS and RPC with/without Amendment Block");
936 using namespace jtx;
937 using namespace boost::asio;
938 using namespace boost::beast::http;
939 Env env{
940 *this,
941 validator(
943 cfg->section("port_rpc").set("protocol", "http");
944 return cfg;
945 }),
946 "")};
947
948 env.close();
949
950 // advance the ledger so that server status
951 // sees a published ledger -- without this, we get a status
952 // failure message about no published ledgers
954
955 // make an RPC server info request and look for
956 // amendment_blocked status
957 auto si = env.rpc("server_info")[jss::result];
958 BEAST_EXPECT(si.isMember(jss::info));
959 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
960 BEAST_EXPECT(
961 env.app().getOPs().getConsensusInfo()["validating"] == true);
962 BEAST_EXPECT(!si.isMember(jss::warnings));
963
964 // make an RPC server state request and look for
965 // amendment_blocked status
966 si = env.rpc("server_state")[jss::result];
967 BEAST_EXPECT(si.isMember(jss::state));
968 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
969 BEAST_EXPECT(
970 env.app().getOPs().getConsensusInfo()["validating"] == true);
971 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
972
973 auto const port_ws =
974 env.app().config()["port_ws"].get<std::uint16_t>("port");
975 auto const ip_ws = env.app().config()["port_ws"].get<std::string>("ip");
976
977 boost::system::error_code ec;
978 response<string_body> resp;
979
980 doRequest(
981 yield,
982 makeHTTPRequest(*ip_ws, *port_ws, "", {}),
983 *ip_ws,
984 *port_ws,
985 false,
986 resp,
987 ec);
988
989 if (!BEAST_EXPECTS(!ec, ec.message()))
990 return;
991 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
992 BEAST_EXPECT(
993 resp.body().find("connectivity is working.") != std::string::npos);
994
995 // mark the Network as Amendment Blocked, but still won't fail until
996 // ELB is enabled (next step)
998 env.app().getOPs().beginConsensus(env.closed()->info().hash, {});
999
1000 // consensus now sees validation disabled
1001 BEAST_EXPECT(
1002 env.app().getOPs().getConsensusInfo()["validating"] == false);
1003
1004 // RPC request server_info again, now AB should be returned
1005 si = env.rpc("server_info")[jss::result];
1006 BEAST_EXPECT(si.isMember(jss::info));
1007 BEAST_EXPECT(
1008 si[jss::info].isMember(jss::amendment_blocked) &&
1009 si[jss::info][jss::amendment_blocked] == true);
1010 BEAST_EXPECT(
1011 si[jss::info].isMember(jss::warnings) &&
1012 si[jss::info][jss::warnings].isArray() &&
1013 si[jss::info][jss::warnings].size() == 1 &&
1014 si[jss::info][jss::warnings][0u][jss::id].asInt() ==
1016
1017 // RPC request server_state again, now AB should be returned
1018 si = env.rpc("server_state")[jss::result];
1019 BEAST_EXPECT(
1020 si[jss::state].isMember(jss::amendment_blocked) &&
1021 si[jss::state][jss::amendment_blocked] == true);
1022 BEAST_EXPECT(
1023 si[jss::state].isMember(jss::warnings) &&
1024 si[jss::state][jss::warnings].isArray() &&
1025 si[jss::state][jss::warnings].size() == 1 &&
1026 si[jss::state][jss::warnings][0u][jss::id].asInt() ==
1028
1029 // but status does not indicate because it still relies on ELB
1030 // being enabled
1031 doRequest(
1032 yield,
1033 makeHTTPRequest(*ip_ws, *port_ws, "", {}),
1034 *ip_ws,
1035 *port_ws,
1036 false,
1037 resp,
1038 ec);
1039
1040 if (!BEAST_EXPECTS(!ec, ec.message()))
1041 return;
1042 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
1043 BEAST_EXPECT(
1044 resp.body().find("connectivity is working.") != std::string::npos);
1045
1046 env.app().config().ELB_SUPPORT = true;
1047
1048 doRequest(
1049 yield,
1050 makeHTTPRequest(*ip_ws, *port_ws, "", {}),
1051 *ip_ws,
1052 *port_ws,
1053 false,
1054 resp,
1055 ec);
1056
1057 if (!BEAST_EXPECTS(!ec, ec.message()))
1058 return;
1059 BEAST_EXPECT(
1060 resp.result() == boost::beast::http::status::internal_server_error);
1061 BEAST_EXPECT(
1062 resp.body().find("cannot accept clients:") != std::string::npos);
1063 BEAST_EXPECT(
1064 resp.body().find("Server version too old") != std::string::npos);
1065 }
1066
1067 void
1068 testRPCRequests(boost::asio::yield_context& yield)
1069 {
1070 testcase("RPC client sends assorted input");
1071
1072 using namespace test::jtx;
1073 Env env{*this};
1074
1075 boost::system::error_code ec;
1076 {
1077 boost::beast::http::response<boost::beast::http::string_body> resp;
1078 doHTTPRequest(env, yield, false, resp, ec, "{}");
1079 BEAST_EXPECT(
1080 resp.result() == boost::beast::http::status::bad_request);
1081 BEAST_EXPECT(resp.body() == "Unable to parse request: \r\n");
1082 }
1083
1084 {
1085 boost::beast::http::response<boost::beast::http::string_body> resp;
1086 Json::Value jv;
1087 jv["invalid"] = 1;
1088 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1089 BEAST_EXPECT(
1090 resp.result() == boost::beast::http::status::bad_request);
1091 BEAST_EXPECT(resp.body() == "Null method\r\n");
1092 }
1093
1094 {
1095 boost::beast::http::response<boost::beast::http::string_body> resp;
1097 jv.append("invalid");
1098 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1099 BEAST_EXPECT(
1100 resp.result() == boost::beast::http::status::bad_request);
1101 BEAST_EXPECT(resp.body() == "Unable to parse request: \r\n");
1102 }
1103
1104 {
1105 boost::beast::http::response<boost::beast::http::string_body> resp;
1107 Json::Value j;
1108 j["invalid"] = 1;
1109 jv.append(j);
1110 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1111 BEAST_EXPECT(
1112 resp.result() == boost::beast::http::status::bad_request);
1113 BEAST_EXPECT(resp.body() == "Unable to parse request: \r\n");
1114 }
1115
1116 {
1117 boost::beast::http::response<boost::beast::http::string_body> resp;
1118 Json::Value jv;
1119 jv[jss::method] = "batch";
1120 jv[jss::params] = 2;
1121 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1122 BEAST_EXPECT(
1123 resp.result() == boost::beast::http::status::bad_request);
1124 BEAST_EXPECT(resp.body() == "Malformed batch request\r\n");
1125 }
1126
1127 {
1128 boost::beast::http::response<boost::beast::http::string_body> resp;
1129 Json::Value jv;
1130 jv[jss::method] = "batch";
1131 jv[jss::params] = Json::objectValue;
1132 jv[jss::params]["invalid"] = 3;
1133 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1134 BEAST_EXPECT(
1135 resp.result() == boost::beast::http::status::bad_request);
1136 BEAST_EXPECT(resp.body() == "Malformed batch request\r\n");
1137 }
1138
1139 Json::Value jv;
1140 {
1141 boost::beast::http::response<boost::beast::http::string_body> resp;
1142 jv[jss::method] = Json::nullValue;
1143 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1144 BEAST_EXPECT(
1145 resp.result() == boost::beast::http::status::bad_request);
1146 BEAST_EXPECT(resp.body() == "Null method\r\n");
1147 }
1148
1149 {
1150 boost::beast::http::response<boost::beast::http::string_body> resp;
1151 jv[jss::method] = 1;
1152 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1153 BEAST_EXPECT(
1154 resp.result() == boost::beast::http::status::bad_request);
1155 BEAST_EXPECT(resp.body() == "method is not string\r\n");
1156 }
1157
1158 {
1159 boost::beast::http::response<boost::beast::http::string_body> resp;
1160 jv[jss::method] = "";
1161 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1162 BEAST_EXPECT(
1163 resp.result() == boost::beast::http::status::bad_request);
1164 BEAST_EXPECT(resp.body() == "method is empty\r\n");
1165 }
1166
1167 {
1168 boost::beast::http::response<boost::beast::http::string_body> resp;
1169 jv[jss::method] = "some_method";
1170 jv[jss::params] = "params";
1171 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1172 BEAST_EXPECT(
1173 resp.result() == boost::beast::http::status::bad_request);
1174 BEAST_EXPECT(resp.body() == "params unparseable\r\n");
1175 }
1176
1177 {
1178 boost::beast::http::response<boost::beast::http::string_body> resp;
1179 jv[jss::params] = Json::arrayValue;
1180 jv[jss::params][0u] = "not an object";
1181 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1182 BEAST_EXPECT(
1183 resp.result() == boost::beast::http::status::bad_request);
1184 BEAST_EXPECT(resp.body() == "params unparseable\r\n");
1185 }
1186 }
1187
1188 void
1189 testStatusNotOkay(boost::asio::yield_context& yield)
1190 {
1191 testcase("Server status not okay");
1192
1193 using namespace test::jtx;
1194 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
1195 cfg->ELB_SUPPORT = true;
1196 return cfg;
1197 })};
1198
1199 // raise the fee so that the server is considered overloaded
1200 env.app().getFeeTrack().raiseLocalFee();
1201
1202 boost::beast::http::response<boost::beast::http::string_body> resp;
1203 boost::system::error_code ec;
1204 doHTTPRequest(env, yield, false, resp, ec);
1205 BEAST_EXPECT(
1206 resp.result() == boost::beast::http::status::internal_server_error);
1207 std::regex body{"Server cannot accept clients"};
1208 BEAST_EXPECT(std::regex_search(resp.body(), body));
1209 }
1210
1211public:
1212 void
1213 run() override
1214 {
1215 for (auto it : {"http", "ws", "ws2"})
1216 {
1217 testAdminRequest(it, true, true);
1218 testAdminRequest(it, true, false);
1219 testAdminRequest(it, false, false);
1220 }
1221
1222 yield_to([&](boost::asio::yield_context& yield) {
1224 testStatusRequest(yield);
1226
1227 // these are secure/insecure protocol pairs, i.e. for
1228 // each item, the second value is the secure or insecure equivalent
1229 testCantConnect("ws", "wss", yield);
1230 testCantConnect("ws2", "wss2", yield);
1231 testCantConnect("http", "https", yield);
1232 testCantConnect("wss", "ws", yield);
1233 testCantConnect("wss2", "ws2", yield);
1234 testCantConnect("https", "http", yield);
1235
1236 testAmendmentWarning(yield);
1237 testAmendmentBlock(yield);
1238 testAuth(false, yield);
1239 testAuth(true, yield);
1240 testLimit(yield, 5);
1241 testLimit(yield, 0);
1242 testWSHandoff(yield);
1243 testNoRPC(yield);
1244 testWSRequests(yield);
1245 testRPCRequests(yield);
1246 testStatusNotOkay(yield);
1247 });
1248 }
1249};
1250
1251BEAST_DEFINE_TESTSUITE(ServerStatus, server, ripple);
1252
1253} // namespace test
1254} // namespace ripple
T back(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:39
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:78
Represents a JSON value.
Definition: json_value.h:150
Value & append(Value const &value)
Append value to array at the end.
Definition: json_value.cpp:910
Mix-in to support tests using asio coroutines.
Definition: yield_to.h:31
void yield_to(F0 &&f0, FN &&... fn)
Run one or more functions, each in a coroutine.
Definition: yield_to.h:101
boost::asio::io_service & get_io_service()
Return the io_service associated with the object.
Definition: yield_to.h:62
A testsuite class.
Definition: suite.h:55
void pass()
Record a successful test condition.
Definition: suite.h:511
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
virtual Config & config()=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual NetworkOPs & getOPs()=0
virtual LedgerMaster & getLedgerMaster()=0
Section & section(std::string const &name)
Returns the section with the given name.
bool ELB_SUPPORT
Definition: Config.h:138
virtual void setAmendmentWarned()=0
virtual Json::Value getConsensusInfo()=0
virtual void setAmendmentBlocked()=0
virtual bool beginConsensus(uint256 const &netLCL, std::unique_ptr< std::stringstream > const &clog)=0
std::optional< T > get(std::string const &name) const
Definition: BasicConfig.h:140
void testAmendmentBlock(boost::asio::yield_context &yield)
void testCantConnect(std::string const &client_protocol, std::string const &server_protocol, boost::asio::yield_context &yield)
void testAuth(bool secure, boost::asio::yield_context &yield)
auto makeHTTPRequest(std::string const &host, uint16_t port, std::string const &body, myFields const &fields)
void testRPCRequests(boost::asio::yield_context &yield)
void testStatusRequest(boost::asio::yield_context &yield)
void testAmendmentWarning(boost::asio::yield_context &yield)
void testTruncatedWSUpgrade(boost::asio::yield_context &yield)
auto makeConfig(std::string const &proto, bool admin=true, bool credentials=false)
void doRequest(boost::asio::yield_context &yield, boost::beast::http::request< boost::beast::http::string_body > &&req, std::string const &host, uint16_t port, bool secure, boost::beast::http::response< boost::beast::http::string_body > &resp, boost::system::error_code &ec)
void doWSRequest(test::jtx::Env &env, boost::asio::yield_context &yield, bool secure, boost::beast::http::response< boost::beast::http::string_body > &resp, boost::system::error_code &ec)
void testWSRequests(boost::asio::yield_context &yield)
void testLimit(boost::asio::yield_context &yield, int limit)
void testWSHandoff(boost::asio::yield_context &yield)
void testNoRPC(boost::asio::yield_context &yield)
void testStatusNotOkay(boost::asio::yield_context &yield)
void testWSClientToHttpServer(boost::asio::yield_context &yield)
void run() override
Runs the suite.
auto makeAdminRequest(jtx::Env &env, std::string const &proto, std::string const &user, std::string const &password, bool subobject=false)
auto makeWSUpgrade(std::string const &host, uint16_t port)
void doHTTPRequest(test::jtx::Env &env, boost::asio::yield_context &yield, bool secure, boost::beast::http::response< boost::beast::http::string_body > &resp, boost::system::error_code &ec, std::string const &body="", myFields const &fields={})
void testAdminRequest(std::string const &proto, bool admin, bool credentials)
A transaction testing environment.
Definition: Env.h:121
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:111
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
Application & app()
Definition: Env.h:261
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
T data(T... args)
T emplace_back(T... args)
T empty(T... args)
T make_pair(T... args)
@ nullValue
'null' value
Definition: json_value.h:39
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
std::unique_ptr< Config > validator(std::unique_ptr< Config >, std::string const &)
adjust configuration with params needed to be a validator
Definition: envconfig.cpp:113
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:302
char const * getEnvLocalhostAddr()
Definition: envconfig.h:36
std::unique_ptr< AbstractClient > makeJSONRPCClient(Config const &cfg, unsigned rpc_version)
Returns a client using JSON-RPC over HTTP/S.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ warnRPC_UNSUPPORTED_MAJORITY
Definition: ErrorCodes.h:168
@ warnRPC_AMENDMENT_BLOCKED
Definition: ErrorCodes.h:169
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Definition: BasicConfig.h:315
std::string base64_encode(std::uint8_t const *data, std::size_t len)
Definition: base64.cpp:239
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
T regex_search(T... args)
T size(T... args)
T to_string(T... args)