rippled
Loading...
Searching...
No Matches
ServerHandler.cpp
1#include <xrpld/app/main/Application.h>
2#include <xrpld/core/ConfigSections.h>
3#include <xrpld/overlay/Overlay.h>
4#include <xrpld/rpc/RPCHandler.h>
5#include <xrpld/rpc/Role.h>
6#include <xrpld/rpc/ServerHandler.h>
7#include <xrpld/rpc/detail/Tuning.h>
8#include <xrpld/rpc/detail/WSInfoSub.h>
9#include <xrpld/rpc/json_body.h>
10
11#include <xrpl/basics/Log.h>
12#include <xrpl/basics/base64.h>
13#include <xrpl/basics/contract.h>
14#include <xrpl/basics/make_SSLContext.h>
15#include <xrpl/beast/net/IPAddressConversion.h>
16#include <xrpl/beast/rfc2616.h>
17#include <xrpl/core/JobQueue.h>
18#include <xrpl/json/json_reader.h>
19#include <xrpl/json/to_string.h>
20#include <xrpl/protocol/ApiVersion.h>
21#include <xrpl/protocol/ErrorCodes.h>
22#include <xrpl/protocol/RPCErr.h>
23#include <xrpl/resource/Fees.h>
24#include <xrpl/resource/ResourceManager.h>
25#include <xrpl/server/NetworkOPs.h>
26#include <xrpl/server/Server.h>
27#include <xrpl/server/SimpleWriter.h>
28#include <xrpl/server/detail/JSONRPCUtil.h>
29
30#include <boost/algorithm/string.hpp>
31#include <boost/beast/http/fields.hpp>
32#include <boost/beast/http/string_body.hpp>
33
34#include <algorithm>
35#include <memory>
36#include <stdexcept>
37
38namespace xrpl {
39
40class Peer;
41class LedgerMaster;
42class Transaction;
43class ValidatorKeys;
44class CanonicalTXSet;
45
46static bool
48{
49 return request.version() >= 11 && request.target() == "/" && request.body().size() == 0 &&
50 request.method() == boost::beast::http::verb::get;
51}
52
53static Handoff
54statusRequestResponse(http_request_type const& request, boost::beast::http::status status)
55{
56 using namespace boost::beast::http;
57 Handoff handoff;
58 response<string_body> msg;
59 msg.version(request.version());
60 msg.result(status);
61 msg.insert("Server", BuildInfo::getFullVersionString());
62 msg.insert("Content-Type", "text/html");
63 msg.insert("Connection", "close");
64 msg.body() = "Invalid protocol.";
65 msg.prepare_payload();
67 return handoff;
68}
69
70// VFALCO TODO Rewrite to use boost::beast::http::fields
71static bool
73{
74 if (port.user.empty() || port.password.empty())
75 return true;
76
77 auto const it = h.find("authorization");
78 if ((it == h.end()) || (it->second.substr(0, 6) != "Basic "))
79 return false;
80 std::string strUserPass64 = it->second.substr(6);
81 boost::trim(strUserPass64);
82 std::string strUserPass = base64_decode(strUserPass64);
83 std::string::size_type nColon = strUserPass.find(":");
84 if (nColon == std::string::npos)
85 return false;
86 std::string strUser = strUserPass.substr(0, nColon);
87 std::string strPassword = strUserPass.substr(nColon + 1);
88 return strUser == port.user && strPassword == port.password;
89}
90
93 Application& app,
94 boost::asio::io_context& io_context,
95 JobQueue& jobQueue,
96 NetworkOPs& networkOPs,
97 Resource::Manager& resourceManager,
99 : app_(app)
100 , m_resourceManager(resourceManager)
101 , m_journal(app_.journal("Server"))
102 , m_networkOPs(networkOPs)
103 , m_server(make_Server(*this, io_context, app_.journal("Server")))
104 , m_jobQueue(jobQueue)
105{
106 auto const& group(cm.group("rpc"));
107 rpc_requests_ = group->make_counter("requests");
108 rpc_size_ = group->make_event("size");
109 rpc_time_ = group->make_event("time");
110}
111
113{
114 m_server = nullptr;
115}
116
117void
119{
120 setup_ = setup;
121 endpoints_ = m_server->ports(setup.ports);
122
123 // fix auto ports
124 for (auto& port : setup_.ports)
125 {
126 if (auto it = endpoints_.find(port.name); it != endpoints_.end())
127 {
128 auto const endpointPort = it->second.port();
129 if (!port.port)
130 port.port = endpointPort;
131
132 if (!setup_.client.port &&
133 (port.protocol.count("http") > 0 || port.protocol.count("https") > 0))
134 setup_.client.port = endpointPort;
135
136 if (!setup_.overlay.port() && (port.protocol.count("peer") > 0))
137 setup_.overlay.port(endpointPort);
138 }
139 }
140}
141
142//------------------------------------------------------------------------------
143
144void
146{
147 m_server->close();
148 {
150 condition_.wait(lock, [this] { return stopped_; });
151 }
152}
153
154//------------------------------------------------------------------------------
155
156bool
157ServerHandler::onAccept(Session& session, boost::asio::ip::tcp::endpoint endpoint)
158{
159 auto const& port = session.port();
160
161 auto const c = [this, &port]() {
163 return ++count_[port];
164 }();
165
166 if (port.limit && c >= port.limit)
167 {
168 JLOG(m_journal.trace()) << port.name << " is full; dropping " << endpoint;
169 return false;
170 }
171
172 return true;
173}
174
177 Session& session,
179 http_request_type&& request,
180 boost::asio::ip::tcp::endpoint const& remote_address)
181{
182 using namespace boost::beast;
183 auto const& p{session.port().protocol};
184 bool const is_ws{
185 p.count("ws") > 0 || p.count("ws2") > 0 || p.count("wss") > 0 || p.count("wss2") > 0};
186
187 if (websocket::is_upgrade(request))
188 {
189 if (!is_ws)
190 return statusRequestResponse(request, http::status::unauthorized);
191
193 try
194 {
195 ws = session.websocketUpgrade();
196 }
197 catch (std::exception const& e)
198 {
199 JLOG(m_journal.error()) << "Exception upgrading websocket: " << e.what() << "\n";
200 return statusRequestResponse(request, http::status::internal_server_error);
201 }
202
204 auto const beast_remote_address = beast::IPAddressConversion::from_asio(remote_address);
205 is->getConsumer() = requestInboundEndpoint(
207 beast_remote_address,
209 Role::GUEST, session.port(), Json::Value(), beast_remote_address, is->user()),
210 is->user(),
211 is->forwarded_for());
212 ws->appDefined = std::move(is);
213 ws->run();
214
215 Handoff handoff;
216 handoff.moved = true;
217 return handoff;
218 }
219
220 if (bundle && p.count("peer") > 0)
221 return app_.overlay().onHandoff(std::move(bundle), std::move(request), remote_address);
222
223 if (is_ws && isStatusRequest(request))
224 return statusResponse(request);
225
226 // Otherwise pass to legacy onRequest or websocket
227 return {};
228}
229
230static inline Json::Output
232{
233 return [&](boost::beast::string_view const& b) { session.write(b.data(), b.size()); };
234}
235
237build_map(boost::beast::http::fields const& h)
238{
240 for (auto const& e : h)
241 {
242 // key cannot be a std::string_view because it needs to be used in
243 // map and along with iterators
244 std::string key(e.name_string());
245 std::transform(key.begin(), key.end(), key.begin(), [](auto kc) {
246 return std::tolower(static_cast<unsigned char>(kc));
247 });
248 c[key] = e.value();
249 }
250 return c;
251}
252
253template <class ConstBufferSequence>
254static std::string
255buffers_to_string(ConstBufferSequence const& bs)
256{
257 using boost::asio::buffer_size;
258 std::string s;
259 s.reserve(buffer_size(bs));
260 // Use auto&& so the right thing happens whether bs returns a copy or
261 // a reference
262 for (auto&& b : bs)
263 s.append(static_cast<char const*>(b.data()), buffer_size(b));
264 return s;
265}
266
267void
269{
270 // Make sure RPC is enabled on the port
271 if (session.port().protocol.count("http") == 0 && session.port().protocol.count("https") == 0)
272 {
273 HTTPReply(403, "Forbidden", makeOutput(session), app_.journal("RPC"));
274 session.close(true);
275 return;
276 }
277
278 // Check user/password authorization
279 if (!authorized(session.port(), build_map(session.request())))
280 {
281 HTTPReply(403, "Forbidden", makeOutput(session), app_.journal("RPC"));
282 session.close(true);
283 return;
284 }
285
286 std::shared_ptr<Session> detachedSession = session.detach();
287 auto const postResult = m_jobQueue.postCoro(
288 jtCLIENT_RPC, "RPC-Client", [this, detachedSession](std::shared_ptr<JobQueue::Coro> coro) {
289 processSession(detachedSession, coro);
290 });
291 if (postResult == nullptr)
292 {
293 // The coroutine was rejected, probably because we're shutting down.
294 HTTPReply(503, "Service Unavailable", makeOutput(*detachedSession), app_.journal("RPC"));
295 detachedSession->close(true);
296 return;
297 }
298}
299
300void
304{
305 Json::Value jv;
306 auto const size = boost::asio::buffer_size(buffers);
307 if (size > RPC::Tuning::maxRequestSize || !Json::Reader{}.parse(jv, buffers) || !jv.isObject())
308 {
310 jvResult[jss::type] = jss::error;
311 jvResult[jss::error] = "jsonInvalid";
312 jvResult[jss::value] = buffers_to_string(buffers);
313 boost::beast::multi_buffer sb;
314 Json::stream(jvResult, [&sb](auto const p, auto const n) {
315 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(p, n)));
316 });
317 JLOG(m_journal.trace()) << "Websocket sending '" << jvResult << "'";
318 session->send(std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
319 session->complete();
320 return;
321 }
322
323 JLOG(m_journal.trace()) << "Websocket received '" << jv << "'";
324
325 auto const postResult = m_jobQueue.postCoro(
327 "WS-Client",
328 [this, session, jv = std::move(jv)](std::shared_ptr<JobQueue::Coro> const& coro) {
329 auto const jr = this->processSession(session, coro, jv);
330 auto const s = to_string(jr);
331 auto const n = s.length();
332 boost::beast::multi_buffer sb(n);
333 sb.commit(boost::asio::buffer_copy(sb.prepare(n), boost::asio::buffer(s.c_str(), n)));
334 session->send(std::make_shared<StreambufWSMsg<decltype(sb)>>(std::move(sb)));
335 session->complete();
336 });
337 if (postResult == nullptr)
338 {
339 // The coroutine was rejected, probably because we're shutting down.
340 session->close({boost::beast::websocket::going_away, "Shutting Down"});
341 }
342}
343
344void
345ServerHandler::onClose(Session& session, boost::system::error_code const&)
346{
348 --count_[session.port()];
349}
350
351void
358
359//------------------------------------------------------------------------------
360
361template <class T>
362void
363logDuration(Json::Value const& request, T const& duration, beast::Journal& journal)
364{
365 using namespace std::chrono_literals;
366 auto const level = (duration >= 10s) ? journal.error()
367 : (duration >= 1s) ? journal.warn()
368 : journal.debug();
369
370 JLOG(level) << "RPC request processing duration = "
371 << std::chrono::duration_cast<std::chrono::microseconds>(duration).count()
372 << " microseconds. request = " << request;
373}
374
377 std::shared_ptr<WSSession> const& session,
379 Json::Value const& jv)
380{
381 auto is = std::static_pointer_cast<WSInfoSub>(session->appDefined);
382 if (is->getConsumer().disconnect(m_journal))
383 {
384 session->close({boost::beast::websocket::policy_error, "threshold exceeded"});
385 // FIX: This rpcError is not delivered since the session
386 // was just closed.
387 return rpcError(rpcSLOW_DOWN);
388 }
389
390 // Requests without "command" are invalid.
393 try
394 {
395 auto apiVersion = RPC::getAPIVersionNumber(jv, app_.config().BETA_RPC_API);
396 if (apiVersion == RPC::apiInvalidVersion ||
397 (!jv.isMember(jss::command) && !jv.isMember(jss::method)) ||
398 (jv.isMember(jss::command) && !jv[jss::command].isString()) ||
399 (jv.isMember(jss::method) && !jv[jss::method].isString()) ||
400 (jv.isMember(jss::command) && jv.isMember(jss::method) &&
401 jv[jss::command].asString() != jv[jss::method].asString()))
402 {
403 jr[jss::type] = jss::response;
404 jr[jss::status] = jss::error;
405 jr[jss::error] = apiVersion == RPC::apiInvalidVersion ? jss::invalid_API_version
406 : jss::missingCommand;
407 jr[jss::request] = jv;
408 if (jv.isMember(jss::id))
409 jr[jss::id] = jv[jss::id];
410 if (jv.isMember(jss::jsonrpc))
411 jr[jss::jsonrpc] = jv[jss::jsonrpc];
412 if (jv.isMember(jss::ripplerpc))
413 jr[jss::ripplerpc] = jv[jss::ripplerpc];
414 if (jv.isMember(jss::api_version))
415 jr[jss::api_version] = jv[jss::api_version];
416
417 is->getConsumer().charge(Resource::feeMalformedRPC);
418 return jr;
419 }
420
421 auto required = RPC::roleRequired(
422 apiVersion,
424 jv.isMember(jss::command) ? jv[jss::command].asString() : jv[jss::method].asString());
425 auto role = requestRole(
426 required,
427 session->port(),
428 jv,
429 beast::IP::from_asio(session->remote_endpoint().address()),
430 is->user());
431 if (Role::FORBID == role)
432 {
433 loadType = Resource::feeMalformedRPC;
434 jr[jss::result] = rpcError(rpcFORBIDDEN);
435 }
436 else
437 {
438 RPC::JsonContext context{
439 {app_.journal("RPCHandler"),
440 app_,
441 loadType,
442 app_.getOPs(),
444 is->getConsumer(),
445 role,
446 coro,
447 is,
448 apiVersion},
449 jv,
450 {is->user(), is->forwarded_for()}};
451
452 auto start = std::chrono::system_clock::now();
453 RPC::doCommand(context, jr[jss::result]);
455 logDuration(jv, end - start, m_journal);
456 }
457 }
458 catch (std::exception const& ex)
459 {
460 // LCOV_EXCL_START
461 jr[jss::result] = RPC::make_error(rpcINTERNAL);
462 JLOG(m_journal.error()) << "Exception while processing WS: " << ex.what() << "\n"
463 << "Input JSON: " << Json::Compact{Json::Value{jv}};
464 // LCOV_EXCL_STOP
465 }
466
467 is->getConsumer().charge(loadType);
468 if (is->getConsumer().warn())
469 jr[jss::warning] = jss::load;
470
471 // Currently we will simply unwrap errors returned by the RPC
472 // API, in the future maybe we can make the responses
473 // consistent.
474 //
475 // Regularize result. This is duplicate code.
476 if (jr[jss::result].isMember(jss::error))
477 {
478 jr = jr[jss::result];
479 jr[jss::status] = jss::error;
480
481 auto rq = jv;
482
483 if (rq.isObject())
484 {
485 if (rq.isMember(jss::passphrase.c_str()))
486 rq[jss::passphrase.c_str()] = "<masked>";
487 if (rq.isMember(jss::secret.c_str()))
488 rq[jss::secret.c_str()] = "<masked>";
489 if (rq.isMember(jss::seed.c_str()))
490 rq[jss::seed.c_str()] = "<masked>";
491 if (rq.isMember(jss::seed_hex.c_str()))
492 rq[jss::seed_hex.c_str()] = "<masked>";
493 }
494
495 jr[jss::request] = rq;
496 }
497 else
498 {
499 if (jr[jss::result].isMember("forwarded") && jr[jss::result]["forwarded"])
500 jr = jr[jss::result];
501 jr[jss::status] = jss::success;
502 }
503
504 if (jv.isMember(jss::id))
505 jr[jss::id] = jv[jss::id];
506 if (jv.isMember(jss::jsonrpc))
507 jr[jss::jsonrpc] = jv[jss::jsonrpc];
508 if (jv.isMember(jss::ripplerpc))
509 jr[jss::ripplerpc] = jv[jss::ripplerpc];
510 if (jv.isMember(jss::api_version))
511 jr[jss::api_version] = jv[jss::api_version];
512
513 jr[jss::type] = jss::response;
514 return jr;
515}
516
517// Run as a coroutine.
518void
520 std::shared_ptr<Session> const& session,
522{
524 session->port(),
525 buffers_to_string(session->request().body().data()),
526 session->remoteAddress().at_port(0),
527 makeOutput(*session),
528 coro,
529 forwardedFor(session->request()),
530 [&] {
531 auto const iter = session->request().find("X-User");
532 if (iter != session->request().end())
533 return iter->value();
534 return boost::beast::string_view{};
535 }());
536
537 if (beast::rfc2616::is_keep_alive(session->request()))
538 session->complete();
539 else
540 session->close(true);
541}
542
543static Json::Value
545{
547 sub["code"] = code;
548 sub["message"] = std::move(message);
550 r["error"] = sub;
551 return r;
552}
553
554Json::Int constexpr method_not_found = -32601;
555Json::Int constexpr server_overloaded = -32604;
556Json::Int constexpr forbidden = -32605;
557Json::Int constexpr wrong_version = -32606;
558
559void
560ServerHandler::processRequest(
561 Port const& port,
562 std::string const& request,
563 beast::IP::Endpoint const& remoteIPAddress,
564 Output&& output,
566 std::string_view forwardedFor,
567 std::string_view user)
568{
569 auto rpcJ = app_.journal("RPC");
570
571 Json::Value jsonOrig;
572 {
573 Json::Reader reader;
574 if ((request.size() > RPC::Tuning::maxRequestSize) || !reader.parse(request, jsonOrig) ||
575 !jsonOrig || !jsonOrig.isObject())
576 {
577 HTTPReply(
578 400,
579 "Unable to parse request: " + reader.getFormattedErrorMessages(),
580 output,
581 rpcJ);
582 return;
583 }
584 }
585
586 bool batch = false;
587 unsigned size = 1;
588 if (jsonOrig.isMember(jss::method) && jsonOrig[jss::method] == "batch")
589 {
590 batch = true;
591 if (!jsonOrig.isMember(jss::params) || !jsonOrig[jss::params].isArray())
592 {
593 HTTPReply(400, "Malformed batch request", output, rpcJ);
594 return;
595 }
596 size = jsonOrig[jss::params].size();
597 }
598
600 auto const start(std::chrono::high_resolution_clock::now());
601 for (unsigned i = 0; i < size; ++i)
602 {
603 Json::Value const& jsonRPC = batch ? jsonOrig[jss::params][i] : jsonOrig;
604
605 if (!jsonRPC.isObject())
606 {
608 r[jss::request] = jsonRPC;
609 r[jss::error] = make_json_error(method_not_found, "Method not found");
610 reply.append(r);
611 continue;
612 }
613
614 unsigned apiVersion = RPC::apiVersionIfUnspecified;
615 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
616 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][0u].isObject())
617 {
618 apiVersion = RPC::getAPIVersionNumber(
619 jsonRPC[jss::params][Json::UInt(0)], app_.config().BETA_RPC_API);
620 }
621
622 if (apiVersion == RPC::apiVersionIfUnspecified && batch)
623 {
624 // for batch request, api_version may be at a different level
625 apiVersion = RPC::getAPIVersionNumber(jsonRPC, app_.config().BETA_RPC_API);
626 }
627
628 if (apiVersion == RPC::apiInvalidVersion)
629 {
630 if (!batch)
631 {
632 HTTPReply(400, jss::invalid_API_version.c_str(), output, rpcJ);
633 return;
634 }
636 r[jss::request] = jsonRPC;
637 r[jss::error] = make_json_error(wrong_version, jss::invalid_API_version.c_str());
638 reply.append(r);
639 continue;
640 }
641
642 /* ------------------------------------------------------------------ */
643 auto role = Role::FORBID;
644 auto required = Role::FORBID;
645 if (jsonRPC.isMember(jss::method) && jsonRPC[jss::method].isString())
646 required = RPC::roleRequired(
647 apiVersion, app_.config().BETA_RPC_API, jsonRPC[jss::method].asString());
648
649 if (jsonRPC.isMember(jss::params) && jsonRPC[jss::params].isArray() &&
650 jsonRPC[jss::params].size() > 0 && jsonRPC[jss::params][Json::UInt(0)].isObjectOrNull())
651 {
652 role = requestRole(
653 required, port, jsonRPC[jss::params][Json::UInt(0)], remoteIPAddress, user);
654 }
655 else
656 {
657 role = requestRole(required, port, Json::objectValue, remoteIPAddress, user);
658 }
659
660 Resource::Consumer usage;
661 if (isUnlimited(role))
662 {
663 usage = m_resourceManager.newUnlimitedEndpoint(remoteIPAddress);
664 }
665 else
666 {
667 usage = m_resourceManager.newInboundEndpoint(
668 remoteIPAddress, role == Role::PROXY, forwardedFor);
669 if (usage.disconnect(m_journal))
670 {
671 if (!batch)
672 {
673 HTTPReply(503, "Server is overloaded", output, rpcJ);
674 return;
675 }
676 Json::Value r = jsonRPC;
677 r[jss::error] = make_json_error(server_overloaded, "Server is overloaded");
678 reply.append(r);
679 continue;
680 }
681 }
682
683 if (role == Role::FORBID)
684 {
685 usage.charge(Resource::feeMalformedRPC);
686 if (!batch)
687 {
688 HTTPReply(403, "Forbidden", output, rpcJ);
689 return;
690 }
691 Json::Value r = jsonRPC;
692 r[jss::error] = make_json_error(forbidden, "Forbidden");
693 reply.append(r);
694 continue;
695 }
696
697 if (!jsonRPC.isMember(jss::method) || jsonRPC[jss::method].isNull())
698 {
699 usage.charge(Resource::feeMalformedRPC);
700 if (!batch)
701 {
702 HTTPReply(400, "Null method", output, rpcJ);
703 return;
704 }
705 Json::Value r = jsonRPC;
706 r[jss::error] = make_json_error(method_not_found, "Null method");
707 reply.append(r);
708 continue;
709 }
710
711 Json::Value const& method = jsonRPC[jss::method];
712 if (!method.isString())
713 {
714 usage.charge(Resource::feeMalformedRPC);
715 if (!batch)
716 {
717 HTTPReply(400, "method is not string", output, rpcJ);
718 return;
719 }
720 Json::Value r = jsonRPC;
721 r[jss::error] = make_json_error(method_not_found, "method is not string");
722 reply.append(r);
723 continue;
724 }
725
726 std::string strMethod = method.asString();
727 if (strMethod.empty())
728 {
729 usage.charge(Resource::feeMalformedRPC);
730 if (!batch)
731 {
732 HTTPReply(400, "method is empty", output, rpcJ);
733 return;
734 }
735 Json::Value r = jsonRPC;
736 r[jss::error] = make_json_error(method_not_found, "method is empty");
737 reply.append(r);
738 continue;
739 }
740
741 // Extract request parameters from the request Json as `params`.
742 //
743 // If the field "params" is empty, `params` is an empty object.
744 //
745 // Otherwise, that field must be an array of length 1 (why?)
746 // and we take that first entry and validate that it's an object.
747 Json::Value params;
748 if (!batch)
749 {
750 params = jsonRPC[jss::params];
751 if (!params)
753
754 else if (!params.isArray() || params.size() != 1)
755 {
756 usage.charge(Resource::feeMalformedRPC);
757 HTTPReply(400, "params unparsable", output, rpcJ);
758 return;
759 }
760 else
761 {
762 params = std::move(params[0u]);
763 if (!params.isObjectOrNull())
764 {
765 usage.charge(Resource::feeMalformedRPC);
766 HTTPReply(400, "params unparsable", output, rpcJ);
767 return;
768 }
769 }
770 }
771 else // batch
772 {
773 params = jsonRPC;
774 }
775
776 std::string ripplerpc = "1.0";
777 if (params.isMember(jss::ripplerpc))
778 {
779 if (!params[jss::ripplerpc].isString())
780 {
781 usage.charge(Resource::feeMalformedRPC);
782 if (!batch)
783 {
784 HTTPReply(400, "ripplerpc is not a string", output, rpcJ);
785 return;
786 }
787
788 Json::Value r = jsonRPC;
789 r[jss::error] = make_json_error(method_not_found, "ripplerpc is not a string");
790 reply.append(r);
791 continue;
792 }
793 ripplerpc = params[jss::ripplerpc].asString();
794 }
795
800 if (role != Role::IDENTIFIED && role != Role::PROXY)
801 {
803 user.remove_suffix(user.size());
804 }
805
806 JLOG(m_journal.debug()) << "Query: " << strMethod << params;
807
808 // Provide the JSON-RPC method as the field "command" in the request.
809 params[jss::command] = strMethod;
810 JLOG(m_journal.trace()) << "doRpcCommand:" << strMethod << ":" << params;
811
812 Resource::Charge loadType = Resource::feeReferenceRPC;
813
814 RPC::JsonContext context{
815 {m_journal,
816 app_,
817 loadType,
818 m_networkOPs,
819 app_.getLedgerMaster(),
820 usage,
821 role,
822 coro,
824 apiVersion},
825 params,
826 {user, forwardedFor}};
827 Json::Value result;
828
829 auto start = std::chrono::system_clock::now();
830
831 try
832 {
833 RPC::doCommand(context, result);
834 }
835 catch (std::exception const& ex)
836 {
837 // LCOV_EXCL_START
838 result = RPC::make_error(rpcINTERNAL);
839 JLOG(m_journal.error())
840 << "Internal error : " << ex.what()
841 << " when processing request: " << Json::Compact{Json::Value{params}};
842 // LCOV_EXCL_STOP
843 }
844
846
847 logDuration(params, end - start, m_journal);
848
849 usage.charge(loadType);
850 if (usage.warn())
851 result[jss::warning] = jss::load;
852
854 if (ripplerpc >= "2.0")
855 {
856 if (result.isMember(jss::error))
857 {
858 result[jss::status] = jss::error;
859 result["code"] = result[jss::error_code];
860 result["message"] = result[jss::error_message];
861 result.removeMember(jss::error_message);
862 JLOG(m_journal.debug())
863 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
864 r[jss::error] = std::move(result);
865 }
866 else
867 {
868 result[jss::status] = jss::success;
869 r[jss::result] = std::move(result);
870 }
871 }
872 else
873 {
874 // Always report "status". On an error report the request as
875 // received.
876 if (result.isMember(jss::error))
877 {
878 auto rq = params;
879
880 if (rq.isObject())
881 { // But mask potentially sensitive information.
882 if (rq.isMember(jss::passphrase.c_str()))
883 rq[jss::passphrase.c_str()] = "<masked>";
884 if (rq.isMember(jss::secret.c_str()))
885 rq[jss::secret.c_str()] = "<masked>";
886 if (rq.isMember(jss::seed.c_str()))
887 rq[jss::seed.c_str()] = "<masked>";
888 if (rq.isMember(jss::seed_hex.c_str()))
889 rq[jss::seed_hex.c_str()] = "<masked>";
890 }
891
892 result[jss::status] = jss::error;
893 result[jss::request] = rq;
894
895 JLOG(m_journal.debug())
896 << "rpcError: " << result[jss::error] << ": " << result[jss::error_message];
897 }
898 else
899 {
900 result[jss::status] = jss::success;
901 }
902 r[jss::result] = std::move(result);
903 }
904
905 if (params.isMember(jss::jsonrpc))
906 r[jss::jsonrpc] = params[jss::jsonrpc];
907 if (params.isMember(jss::ripplerpc))
908 r[jss::ripplerpc] = params[jss::ripplerpc];
909 if (params.isMember(jss::id))
910 r[jss::id] = params[jss::id];
911 if (batch)
912 reply.append(std::move(r));
913 else
914 reply = std::move(r);
915
916 if (reply.isMember(jss::result) && reply[jss::result].isMember(jss::result))
917 {
918 reply = reply[jss::result];
919 if (reply.isMember(jss::status))
920 {
921 reply[jss::result][jss::status] = reply[jss::status];
922 reply.removeMember(jss::status);
923 }
924 }
925 }
926
927 // If we're returning an error_code, use that to determine the HTTP status.
928 int const httpStatus = [&reply]() {
929 // This feature is enabled with ripplerpc version 3.0 and above.
930 // Before ripplerpc version 3.0 always return 200.
931 if (reply.isMember(jss::ripplerpc) && reply[jss::ripplerpc].isString() &&
932 reply[jss::ripplerpc].asString() >= "3.0")
933 {
934 // If there's an error_code, use that to determine the HTTP Status.
935 if (reply.isMember(jss::error) && reply[jss::error].isMember(jss::error_code) &&
936 reply[jss::error][jss::error_code].isInt())
937 {
938 int const errCode = reply[jss::error][jss::error_code].asInt();
939 return RPC::error_code_http_status(static_cast<error_code_i>(errCode));
940 }
941 }
942 // Return OK.
943 return 200;
944 }();
945
946 auto response = to_string(reply);
947
948 rpc_time_.notify(
949 std::chrono::duration_cast<std::chrono::milliseconds>(
951 ++rpc_requests_;
952 rpc_size_.notify(beast::insight::Event::value_type{response.size()});
953
954 response += '\n';
955
956 if (auto stream = m_journal.debug())
957 {
958 static int const maxSize = 10000;
959 if (response.size() <= maxSize)
960 stream << "Reply: " << response;
961 else
962 stream << "Reply: " << response.substr(0, maxSize);
963 }
964
965 HTTPReply(httpStatus, response, output, rpcJ);
966}
967
968//------------------------------------------------------------------------------
969
970/* This response is used with load balancing.
971 If the server is overloaded, status 500 is reported. Otherwise status 200
972 is reported, meaning the server can accept more connections.
973*/
975ServerHandler::statusResponse(http_request_type const& request) const
976{
977 using namespace boost::beast::http;
978 Handoff handoff;
979 response<string_body> msg;
980 std::string reason;
981 if (app_.serverOkay(reason))
982 {
983 msg.result(boost::beast::http::status::ok);
984 msg.body() = "<!DOCTYPE html><html><head><title>" + systemName() +
985 " Test page for rippled</title></head><body><h1>" + systemName() +
986 " Test</h1><p>This page shows rippled http(s) "
987 "connectivity is working.</p></body></html>";
988 }
989 else
990 {
991 msg.result(boost::beast::http::status::internal_server_error);
992 msg.body() = "<HTML><BODY>Server cannot accept clients: " + reason + "</BODY></HTML>";
993 }
994 msg.version(request.version());
995 msg.insert("Server", BuildInfo::getFullVersionString());
996 msg.insert("Content-Type", "text/html");
997 msg.insert("Connection", "close");
998 msg.prepare_payload();
1000 return handoff;
1001}
1002
1003//------------------------------------------------------------------------------
1004
1005void
1006ServerHandler::Setup::makeContexts()
1007{
1008 for (auto& p : ports)
1009 {
1010 if (p.secure())
1011 {
1012 if (p.ssl_key.empty() && p.ssl_cert.empty() && p.ssl_chain.empty())
1013 p.context = make_SSLContext(p.ssl_ciphers);
1014 else
1015 p.context =
1016 make_SSLContextAuthed(p.ssl_key, p.ssl_cert, p.ssl_chain, p.ssl_ciphers);
1017 }
1018 else
1019 {
1020 p.context =
1021 std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
1022 }
1023 }
1024}
1025
1026static Port
1027to_Port(ParsedPort const& parsed, std::ostream& log)
1028{
1029 Port p;
1030 p.name = parsed.name;
1031
1032 if (!parsed.ip)
1033 {
1034 log << "Missing 'ip' in [" << p.name << "]";
1035 Throw<std::exception>();
1036 }
1037 p.ip = *parsed.ip;
1038
1039 if (!parsed.port)
1040 {
1041 log << "Missing 'port' in [" << p.name << "]";
1042 Throw<std::exception>();
1043 }
1044 p.port = *parsed.port;
1045
1046 if (parsed.protocol.empty())
1047 {
1048 log << "Missing 'protocol' in [" << p.name << "]";
1049 Throw<std::exception>();
1050 }
1051 p.protocol = parsed.protocol;
1052
1053 p.user = parsed.user;
1054 p.password = parsed.password;
1055 p.admin_user = parsed.admin_user;
1056 p.admin_password = parsed.admin_password;
1057 p.ssl_key = parsed.ssl_key;
1058 p.ssl_cert = parsed.ssl_cert;
1059 p.ssl_chain = parsed.ssl_chain;
1060 p.ssl_ciphers = parsed.ssl_ciphers;
1061 p.pmd_options = parsed.pmd_options;
1062 p.ws_queue_limit = parsed.ws_queue_limit;
1063 p.limit = parsed.limit;
1064 p.admin_nets_v4 = parsed.admin_nets_v4;
1065 p.admin_nets_v6 = parsed.admin_nets_v6;
1068
1069 return p;
1070}
1071
1072static std::vector<Port>
1073parse_Ports(Config const& config, std::ostream& log)
1074{
1075 std::vector<Port> result;
1076
1077 if (!config.exists("server"))
1078 {
1079 log << "Required section [server] is missing";
1080 Throw<std::exception>();
1081 }
1082
1083 ParsedPort common;
1084 parse_Port(common, config["server"], log);
1085
1086 auto const& names = config.section("server").values();
1087 result.reserve(names.size());
1088 for (auto const& name : names)
1089 {
1090 if (!config.exists(name))
1091 {
1092 log << "Missing section: [" << name << "]";
1093 Throw<std::exception>();
1094 }
1095
1096 // grpc ports are parsed by GRPCServer class. Do not validate
1097 // grpc port information in this file.
1098 if (name == SECTION_PORT_GRPC)
1099 continue;
1100
1101 ParsedPort parsed = common;
1102 parse_Port(parsed, config[name], log);
1103 result.push_back(to_Port(parsed, log));
1104 }
1105
1106 if (config.standalone())
1107 {
1108 auto it = result.begin();
1109
1110 while (it != result.end())
1111 {
1112 auto& p = it->protocol;
1113
1114 // Remove the peer protocol, and if that would
1115 // leave the port empty, remove the port as well
1116 if (p.erase("peer") && p.empty())
1117 it = result.erase(it);
1118 else
1119 ++it;
1120 }
1121 }
1122 else
1123 {
1124 auto const count = std::count_if(result.cbegin(), result.cend(), [](Port const& p) {
1125 return p.protocol.count("peer") != 0;
1126 });
1127
1128 if (count > 1)
1129 {
1130 log << "Error: More than one peer protocol configured in [server]";
1131 Throw<std::exception>();
1132 }
1133
1134 if (count == 0)
1135 log << "Warning: No peer protocol configured";
1136 }
1137
1138 return result;
1139}
1140
1141// Fill out the client portion of the Setup
1142static void
1144{
1145 decltype(setup.ports)::const_iterator iter;
1146 for (iter = setup.ports.cbegin(); iter != setup.ports.cend(); ++iter)
1147 if (iter->protocol.count("http") > 0 || iter->protocol.count("https") > 0)
1148 break;
1149 if (iter == setup.ports.cend())
1150 return;
1151 setup.client.secure = iter->protocol.count("https") > 0;
1152 setup.client.ip = beast::IP::is_unspecified(iter->ip) ?
1153 // VFALCO HACK! to make localhost work
1154 (iter->ip.is_v6() ? "::1" : "127.0.0.1")
1155 : iter->ip.to_string();
1156 setup.client.port = iter->port;
1157 setup.client.user = iter->user;
1158 setup.client.password = iter->password;
1159 setup.client.admin_user = iter->admin_user;
1160 setup.client.admin_password = iter->admin_password;
1161}
1162
1163// Fill out the overlay portion of the Setup
1164static void
1166{
1167 auto const iter = std::find_if(setup.ports.cbegin(), setup.ports.cend(), [](Port const& port) {
1168 return port.protocol.count("peer") != 0;
1169 });
1170 if (iter == setup.ports.cend())
1171 {
1172 setup.overlay = {};
1173 return;
1174 }
1175 setup.overlay = {iter->ip, iter->port};
1176}
1177
1178ServerHandler::Setup
1180{
1182 setup.ports = parse_Ports(config, log);
1183
1184 setup_Client(setup);
1185 setup_Overlay(setup);
1186
1187 return setup;
1188}
1189
1192 Application& app,
1193 boost::asio::io_context& io_context,
1194 JobQueue& jobQueue,
1195 NetworkOPs& networkOPs,
1196 Resource::Manager& resourceManager,
1197 CollectorManager& cm)
1198{
1201 app,
1202 io_context,
1203 jobQueue,
1204 networkOPs,
1205 resourceManager,
1206 cm);
1207}
1208
1209} // namespace xrpl
T append(T... args)
T begin(T... args)
Decorator for streaming out compact json.
Unserialize a JSON document into a Value.
Definition json_reader.h:17
std::string getFormattedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
bool isArray() const
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
bool isObjectOrNull() const
Int asInt() const
bool isString() const
bool isObject() const
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
bool isNull() const
isNull() tests to see if this field is null.
bool isMember(char const *key) const
Return true if the object has a member named key.
bool isInt() const
A version-independent IP address and port combination.
Definition IPEndpoint.h:18
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
virtual Config & config()=0
bool exists(std::string const &name) const
Returns true if a section with the given name exists.
Section & section(std::string const &name)
Returns the section with the given name.
Provides the beast::insight::Collector service.
virtual beast::insight::Group::ptr const & group(std::string const &name)=0
bool BETA_RPC_API
Definition Config.h:268
bool standalone() const
Definition Config.h:312
A pool of threads to perform work.
Definition JobQueue.h:37
std::shared_ptr< Coro > postCoro(JobType t, std::string const &name, F &&f)
Creates a coroutine and adds a job to the queue which will run it.
Definition JobQueue.h:387
Provides server functionality for clients.
Definition NetworkOPs.h:71
virtual Handoff onHandoff(std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint remote_address)=0
Conditionally accept an incoming HTTP request.
A consumption charge.
Definition Charge.h:10
An endpoint that consumes resources.
Definition Consumer.h:16
bool warn()
Returns true if the consumer should be warned.
Definition Consumer.cpp:96
bool disconnect(beast::Journal const &j)
Returns true if the consumer should be disconnected.
Definition Consumer.cpp:103
Disposition charge(Charge const &fee, std::string const &context={})
Apply a load charge to the consumer.
Definition Consumer.cpp:85
Tracks load and resource consumption.
std::vector< std::string > const & values() const
Returns all the values in the section.
Definition BasicConfig.h:58
Resource::Manager & m_resourceManager
std::condition_variable condition_
ServerHandler(ServerHandlerCreator const &, Application &app, boost::asio::io_context &io_context, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
Handoff onHandoff(Session &session, std::unique_ptr< stream_type > &&bundle, http_request_type &&request, boost::asio::ip::tcp::endpoint const &remote_address)
Application & app_
void onClose(Session &session, boost::system::error_code const &)
Setup const & setup() const
bool onAccept(Session &session, boost::asio::ip::tcp::endpoint endpoint)
beast::insight::Event rpc_size_
void onStopped(Server &)
Handoff statusResponse(http_request_type const &request) const
void processRequest(Port const &port, std::string const &request, beast::IP::Endpoint const &remoteIPAddress, Output &&, std::shared_ptr< JobQueue::Coro > coro, std::string_view forwardedFor, std::string_view user)
std::map< std::reference_wrapper< Port const >, int > count_
void onRequest(Session &session)
beast::insight::Event rpc_time_
void onWSMessage(std::shared_ptr< WSSession > session, std::vector< boost::asio::const_buffer > const &buffers)
NetworkOPs & m_networkOPs
beast::Journal m_journal
std::unique_ptr< Server > m_server
beast::insight::Counter rpc_requests_
Json::Value processSession(std::shared_ptr< WSSession > const &session, std::shared_ptr< JobQueue::Coro > const &coro, Json::Value const &jv)
A multi-protocol server.
Definition ServerImpl.h:29
virtual NetworkOPs & getOPs()=0
virtual Overlay & overlay()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual beast::Journal journal(std::string const &name)=0
Persistent state information for a connection session.
Definition Session.h:23
virtual Port const & port()=0
Returns the Port settings for this connection.
virtual void close(bool graceful)=0
Close the session.
virtual std::shared_ptr< Session > detach()=0
Detach the session.
virtual http_request_type & request()=0
Returns the current HTTP request.
virtual std::shared_ptr< WSSession > websocketUpgrade()=0
Convert the connection to WebSocket.
void write(std::string const &s)
Send a copy of data asynchronously.
Definition Session.h:56
T count(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T insert(T... args)
T is_same_v
T make_shared(T... args)
void stream(Json::Value const &jv, Write const &write)
Stream compact JSON to the specified function.
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
int Int
unsigned int UInt
Endpoint from_asio(boost::asio::ip::address const &address)
Convert to Endpoint.
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
Definition IPAddress.h:37
bool is_keep_alive(boost::beast::http::message< isRequest, Body, Fields > const &m)
Definition rfc2616.h:359
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:64
static int constexpr maxRequestSize
static constexpr auto apiInvalidVersion
Definition ApiVersion.h:40
Role roleRequired(unsigned int version, bool betaEnabled, std::string const &method)
Status doCommand(RPC::JsonContext &context, Json::Value &result)
Execute an RPC command and store the results in a Json::Value.
unsigned int getAPIVersionNumber(Json::Value const &jv, bool betaEnabled)
Retrieve the api version number from the json value.
Definition ApiVersion.h:99
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Charge const feeReferenceRPC
Charge const feeMalformedRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static std::map< std::string, std::string > build_map(boost::beast::http::fields const &h)
void parse_Port(ParsedPort &port, Section const &section, std::ostream &log)
Definition Port.cpp:194
void HTTPReply(int nStatus, std::string const &strMsg, Json::Output const &, beast::Journal j)
static std::string buffers_to_string(ConstBufferSequence const &bs)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:600
static void setup_Client(ServerHandler::Setup &setup)
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
std::string base64_decode(std::string_view data)
static std::vector< Port > parse_Ports(Config const &config, std::ostream &log)
Json::Int constexpr wrong_version
std::shared_ptr< boost::asio::ssl::context > make_SSLContext(std::string const &cipherList)
Create a self-signed SSL context that allows anonymous Diffie Hellman.
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
Role requestRole(Role const &required, Port const &port, Json::Value const &params, beast::IP::Endpoint const &remoteIp, std::string_view user)
Return the allowed privilege role.
Definition Role.cpp:71
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, std::string_view user, std::string_view forwardedFor)
Definition Role.cpp:115
static Json::Value make_json_error(Json::Int code, Json::Value &&message)
std::string_view forwardedFor(http_request_type const &request)
Definition Role.cpp:230
ServerHandler::Setup setup_ServerHandler(Config const &config, std::ostream &&log)
static Port to_Port(ParsedPort const &parsed, std::ostream &log)
std::unique_ptr< ServerHandler > make_ServerHandler(Application &app, boost::asio::io_context &io_context, JobQueue &jobQueue, NetworkOPs &networkOPs, Resource::Manager &resourceManager, CollectorManager &cm)
@ jtCLIENT_RPC
Definition Job.h:29
@ jtCLIENT_WEBSOCKET
Definition Job.h:30
Json::Int constexpr forbidden
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
Json::Int constexpr method_not_found
std::unique_ptr< Server > make_Server(Handler &handler, boost::asio::io_context &io_context, beast::Journal journal)
Create the HTTP server using the specified handler.
Definition Server.h:15
Json::Int constexpr server_overloaded
void logDuration(Json::Value const &request, T const &duration, beast::Journal &journal)
Overlay::Setup setup_Overlay(BasicConfig const &config)
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:98
std::shared_ptr< boost::asio::ssl::context > make_SSLContextAuthed(std::string const &keyFile, std::string const &certFile, std::string const &chainFile, std::string const &cipherList)
Create an authenticated SSL context using the specified files.
static Json::Output makeOutput(Session &session)
static bool isStatusRequest(http_request_type const &request)
error_code_i
Definition ErrorCodes.h:20
@ rpcSLOW_DOWN
Definition ErrorCodes.h:37
@ rpcINTERNAL
Definition ErrorCodes.h:110
@ rpcFORBIDDEN
Definition ErrorCodes.h:28
static Handoff statusRequestResponse(http_request_type const &request, boost::beast::http::status status)
T push_back(T... args)
T remove_suffix(T... args)
T reserve(T... args)
T size(T... args)
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
Used to indicate the result of a server connection handoff.
Definition Handoff.h:18
std::shared_ptr< Writer > response
Definition Handoff.h:27
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:82
boost::beast::websocket::permessage_deflate pmd_options
Definition Port.h:91
std::optional< boost::asio::ip::address > ip
Definition Port.h:95
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition Port.h:100
std::string admin_password
Definition Port.h:86
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition Port.h:99
std::string ssl_chain
Definition Port.h:89
std::string ssl_key
Definition Port.h:87
std::string name
Definition Port.h:81
std::uint16_t ws_queue_limit
Definition Port.h:93
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition Port.h:97
std::string user
Definition Port.h:83
std::optional< std::uint16_t > port
Definition Port.h:96
std::string ssl_ciphers
Definition Port.h:90
std::string password
Definition Port.h:84
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition Port.h:98
std::string admin_user
Definition Port.h:85
std::string ssl_cert
Definition Port.h:88
Configuration information for a Server listening port.
Definition Port.h:30
int limit
Definition Port.h:54
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition Port.h:37
std::string admin_password
Definition Port.h:44
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition Port.h:40
std::string ssl_key
Definition Port.h:45
std::string password
Definition Port.h:42
std::set< std::string, boost::beast::iless > protocol
Definition Port.h:36
std::string ssl_ciphers
Definition Port.h:48
std::string ssl_cert
Definition Port.h:46
std::string ssl_chain
Definition Port.h:47
boost::beast::websocket::permessage_deflate pmd_options
Definition Port.h:49
std::string admin_user
Definition Port.h:43
std::uint16_t ws_queue_limit
Definition Port.h:57
boost::asio::ip::address ip
Definition Port.h:34
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition Port.h:39
std::string name
Definition Port.h:33
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition Port.h:38
std::string user
Definition Port.h:41
std::uint16_t port
Definition Port.h:35
boost::asio::ip::tcp::endpoint overlay
std::vector< Port > ports
T substr(T... args)
T transform(T... args)
T what(T... args)