diff --git a/src/rpc/RPC.cpp b/src/rpc/RPC.cpp index bd4e1720..ff6a7354 100644 --- a/src/rpc/RPC.cpp +++ b/src/rpc/RPC.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include namespace RPC { @@ -126,33 +127,81 @@ make_error(Status const& status) json["type"] = "response"; return json; } -static std::unordered_map> - handlerTable{ - {"account_channels", &doAccountChannels}, - {"account_currencies", &doAccountCurrencies}, - {"account_info", &doAccountInfo}, - {"account_lines", &doAccountLines}, - {"account_nfts", &doAccountNFTs}, - {"account_objects", &doAccountObjects}, - {"account_offers", &doAccountOffers}, - {"account_tx", &doAccountTx}, - {"gateway_balances", &doGatewayBalances}, - {"noripple_check", &doNoRippleCheck}, - {"book_offers", &doBookOffers}, - {"channel_authorize", &doChannelAuthorize}, - {"channel_verify", &doChannelVerify}, - {"ledger", &doLedger}, - {"ledger_data", &doLedgerData}, - {"ledger_entry", &doLedgerEntry}, - {"ledger_range", &doLedgerRange}, - {"nft_buy_offers", &doNFTBuyOffers}, - {"nft_sell_offers", &doNFTSellOffers}, - {"subscribe", &doSubscribe}, - {"server_info", &doServerInfo}, - {"unsubscribe", &doUnsubscribe}, - {"tx", &doTx}, - {"transaction_entry", &doTransactionEntry}, - {"random", &doRandom}}; + +using LimitRange = std::tuple; +using HandlerFunction = std::function; + +struct Handler +{ + std::string method; + std::function handler; + std::optional limit; +}; + +class HandlerTable +{ + std::unordered_map handlerMap_; + +public: + HandlerTable(std::initializer_list handlers) + { + for (auto const& handler : handlers) + { + handlerMap_[handler.method] = std::move(handler); + } + } + + bool + contains(std::string const& method) + { + return handlerMap_.contains(method); + } + + std::optional + getLimitRange(std::string const& command) + { + if (!handlerMap_.contains(command)) + return {}; + + return handlerMap_[command].limit; + } + + std::optional + getHandler(std::string const& command) + { + if (!handlerMap_.contains(command)) + return {}; + + return handlerMap_[command].handler; + } +}; + +static HandlerTable handlerTable{ + {"account_channels", &doAccountChannels, LimitRange{10, 50, 256}}, + {"account_currencies", &doAccountCurrencies, {}}, + {"account_info", &doAccountInfo, {}}, + {"account_lines", &doAccountLines, LimitRange{10, 50, 256}}, + {"account_nfts", &doAccountNFTs, LimitRange{1, 5, 10}}, + {"account_objects", &doAccountObjects, LimitRange{10, 50, 256}}, + {"account_offers", &doAccountOffers, LimitRange{10, 50, 256}}, + {"account_tx", &doAccountTx, LimitRange{1, 50, 100}}, + {"gateway_balances", &doGatewayBalances, {}}, + {"noripple_check", &doNoRippleCheck, {}}, + {"book_offers", &doBookOffers, LimitRange{1, 50, 100}}, + {"channel_authorize", &doChannelAuthorize, {}}, + {"channel_verify", &doChannelVerify, {}}, + {"ledger", &doLedger, {}}, + {"ledger_data", &doLedgerData, LimitRange{1, 100, 2048}}, + {"nft_buy_offers", &doNFTBuyOffers, LimitRange{1, 50, 100}}, + {"nft_sell_offers", &doNFTSellOffers, LimitRange{1, 50, 100}}, + {"ledger_entry", &doLedgerEntry, {}}, + {"ledger_range", &doLedgerRange, {}}, + {"subscribe", &doSubscribe, {}}, + {"server_info", &doServerInfo, {}}, + {"unsubscribe", &doUnsubscribe, {}}, + {"tx", &doTx, {}}, + {"transaction_entry", &doTransactionEntry, {}}, + {"random", &doRandom, {}}}; static std::unordered_set forwardCommands{ "submit", @@ -169,6 +218,36 @@ validHandler(std::string const& method) return handlerTable.contains(method) || forwardCommands.contains(method); } +Status +getLimit(RPC::Context const& context, std::uint32_t& limit) +{ + if (!handlerTable.getHandler(context.method)) + return Status{Error::rpcUNKNOWN_COMMAND}; + + if (!handlerTable.getLimitRange(context.method)) + return Status{Error::rpcINVALID_PARAMS, "rpcDoesNotRequireLimit"}; + + auto [lo, def, hi] = *handlerTable.getLimitRange(context.method); + + if (context.params.contains(JS(limit))) + { + if (!context.params.at(JS(limit)).is_int64()) + return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; + + limit = context.params.at(JS(limit)).as_int64(); + if (limit <= 0) + return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; + + limit = std::clamp(limit, lo, hi); + } + else + { + limit = def; + } + + return {}; +} + bool shouldForwardToRippled(Context const& ctx) { @@ -219,14 +298,14 @@ buildResponse(Context const& ctx) if (ctx.method == "ping") return boost::json::object{}; - if (handlerTable.find(ctx.method) == handlerTable.end()) - return Status{Error::rpcUNKNOWN_COMMAND}; + auto method = handlerTable.getHandler(ctx.method); - auto method = handlerTable[ctx.method]; + if (!method) + return Status{Error::rpcUNKNOWN_COMMAND}; try { - auto v = method(ctx); + auto v = (*method)(ctx); if (auto object = std::get_if(&v)) (*object)["validated"] = true; diff --git a/src/rpc/RPC.h b/src/rpc/RPC.h index 3266f219..f9d38755 100644 --- a/src/rpc/RPC.h +++ b/src/rpc/RPC.h @@ -199,6 +199,9 @@ buildResponse(Context const& ctx); bool validHandler(std::string const& method); +Status +getLimit(RPC::Context const& context, std::uint32_t& limit); + template void logDuration(Context const& ctx, T const& dur) diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 2f123a06..75bfb41d 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -191,22 +191,6 @@ getHexMarker(boost::json::object const& request, ripple::uint256& marker) return {}; } -Status -getLimit(boost::json::object const& request, std::uint32_t& limit) -{ - if (request.contains(JS(limit))) - { - if (!request.at(JS(limit)).is_int64()) - return Status{Error::rpcINVALID_PARAMS, "limitNotInt"}; - - limit = request.at(JS(limit)).as_int64(); - if (limit <= 0) - return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"}; - } - - return {}; -} - Status getAccount( boost::json::object const& request, diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index a21ef548..1188e6ab 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -229,9 +229,6 @@ getString( Status getHexMarker(boost::json::object const& request, ripple::uint256& marker); -Status -getLimit(boost::json::object const& request, std::uint32_t& limit); - Status getAccount(boost::json::object const& request, ripple::AccountID& accountId); diff --git a/src/rpc/handlers/AccountChannels.cpp b/src/rpc/handlers/AccountChannels.cpp index cc95bf7f..e4359d51 100644 --- a/src/rpc/handlers/AccountChannels.cpp +++ b/src/rpc/handlers/AccountChannels.cpp @@ -70,8 +70,8 @@ doAccountChannels(Context const& context) status) return status; - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; std::optional marker = {}; @@ -93,11 +93,6 @@ doAccountChannels(Context const& context) (!destAccount || destAccount == sle.getAccountID(ripple::sfDestination))) { - if (limit-- == 0) - { - return false; - } - addChannel(jsonChannels, sle); } diff --git a/src/rpc/handlers/AccountLines.cpp b/src/rpc/handlers/AccountLines.cpp index 29af1f4e..8921f9eb 100644 --- a/src/rpc/handlers/AccountLines.cpp +++ b/src/rpc/handlers/AccountLines.cpp @@ -113,8 +113,8 @@ doAccountLines(Context const& context) if (auto const status = getAccount(request, peerAccount, JS(peer)); status) return status; - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; std::optional marker = {}; diff --git a/src/rpc/handlers/AccountObjects.cpp b/src/rpc/handlers/AccountObjects.cpp index 47150cc9..95f18cb6 100644 --- a/src/rpc/handlers/AccountObjects.cpp +++ b/src/rpc/handlers/AccountObjects.cpp @@ -53,10 +53,8 @@ doAccountNFTs(Context const& context) if (!rawAcct) return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; - // limit controls the number of pages being returned. each page could have - // 32 nfts - std::uint32_t limit = 10; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; ripple::uint256 marker; @@ -150,8 +148,8 @@ doAccountObjects(Context const& context) if (auto const status = getAccount(request, accountID); status) return status; - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; std::optional marker = {}; diff --git a/src/rpc/handlers/AccountOffers.cpp b/src/rpc/handlers/AccountOffers.cpp index e056c8d7..183e6a9a 100644 --- a/src/rpc/handlers/AccountOffers.cpp +++ b/src/rpc/handlers/AccountOffers.cpp @@ -86,8 +86,8 @@ doAccountOffers(Context const& context) if (!rawAcct) return Status{Error::rpcACT_NOT_FOUND, "accountNotFound"}; - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; std::optional marker = {}; @@ -108,11 +108,6 @@ doAccountOffers(Context const& context) auto const addToResponse = [&](ripple::SLE const& sle) { if (sle.getType() == ripple::ltOFFER) { - if (limit-- == 0) - { - return false; - } - addOffer(jsonLines, sle); } diff --git a/src/rpc/handlers/AccountTx.cpp b/src/rpc/handlers/AccountTx.cpp index d201a5e6..072df070 100644 --- a/src/rpc/handlers/AccountTx.cpp +++ b/src/rpc/handlers/AccountTx.cpp @@ -121,8 +121,8 @@ doAccountTx(Context const& context) cursor = {maxIndex, INT32_MAX}; } - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; if (request.contains(JS(limit))) diff --git a/src/rpc/handlers/BookOffers.cpp b/src/rpc/handlers/BookOffers.cpp index 6efa33f4..3e433ae9 100644 --- a/src/rpc/handlers/BookOffers.cpp +++ b/src/rpc/handlers/BookOffers.cpp @@ -48,8 +48,8 @@ doBookOffers(Context const& context) } } - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; ripple::AccountID takerID = beast::zero; diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 21a80829..6dc15bb3 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -30,10 +30,13 @@ doLedgerData(Context const& context) bool const binary = getBool(request, "binary", false); - std::uint32_t limit = binary ? 2048 : 256; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; + if (!binary) + limit = std::clamp(limit, {1}, {256}); + bool outOfOrder = false; if (request.contains("out_of_order")) { diff --git a/src/rpc/handlers/NFTOffers.cpp b/src/rpc/handlers/NFTOffers.cpp index f7bdbb02..adf513ff 100644 --- a/src/rpc/handlers/NFTOffers.cpp +++ b/src/rpc/handlers/NFTOffers.cpp @@ -50,8 +50,8 @@ enumerateNFTOffers( directory.key, lgrInfo.seq, context.yield)) return Status{Error::rpcOBJECT_NOT_FOUND, "notFound"}; - std::uint32_t limit = 200; - if (auto const status = getLimit(request, limit); status) + std::uint32_t limit; + if (auto const status = getLimit(context, limit); status) return status; boost::json::object response = {}; diff --git a/src/rpc/handlers/NoRippleCheck.cpp b/src/rpc/handlers/NoRippleCheck.cpp index 894e0bce..3ab61ef4 100644 --- a/src/rpc/handlers/NoRippleCheck.cpp +++ b/src/rpc/handlers/NoRippleCheck.cpp @@ -35,7 +35,7 @@ doNoRippleCheck(Context const& context) } std::uint32_t limit = 300; - if (auto const status = getLimit(request, limit); status) + if (auto const status = getLimit(context, limit); status) return status; bool includeTxs = getBool(request, "transactions", false);