diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj
index 4e0cef95b..33456f4e4 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj
+++ b/Builds/VisualStudio2015/RippleD.vcxproj
@@ -3399,6 +3399,10 @@
True
True
+
+ True
+ True
+
True
True
diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters
index 429e3f8e0..14daa4a93 100644
--- a/Builds/VisualStudio2015/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters
@@ -3852,6 +3852,9 @@
ripple\rpc\tests
+
+ ripple\rpc\tests
+
ripple\rpc\tests
diff --git a/src/ripple/rpc/tests/LedgerData.test.cpp b/src/ripple/rpc/tests/LedgerData.test.cpp
new file mode 100644
index 000000000..68f8c2ea6
--- /dev/null
+++ b/src/ripple/rpc/tests/LedgerData.test.cpp
@@ -0,0 +1,237 @@
+//------------------------------------------------------------------------------
+/*
+ This file is part of rippled: https://github.com/ripple/rippled
+ Copyright (c) 2016 Ripple Labs Inc.
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+//==============================================================================
+
+#include
+#include
+
+namespace ripple {
+
+class LedgerData_test : public beast::unit_test::suite
+{
+public:
+
+ static
+ std::unique_ptr
+ makeConfig(bool setup_admin)
+ {
+ auto p = std::make_unique();
+ test::setupConfigForUnitTests(*p);
+ // the default config has admin active
+ // ...we remove them if setup_admin is false
+ if (! setup_admin)
+ {
+ (*p)["port_rpc"].set("admin","");
+ (*p)["port_ws"].set("admin","");
+ }
+ return p;
+ }
+
+ // test helper
+ static bool checkArraySize(Json::Value const& val, unsigned int size)
+ {
+ return val.isArray() &&
+ val.size() == size;
+ }
+
+ // test helper
+ static bool checkMarker(Json::Value const& val)
+ {
+ return val.isMember(jss::marker) &&
+ val[jss::marker].isString() &&
+ val[jss::marker].asString().size() > 0;
+ }
+
+ void testCurrentLedgerToLimits(bool as_admin)
+ {
+ using namespace test::jtx;
+ Env env {*this, makeConfig(as_admin)};
+ Account const gw {"gateway"};
+ auto const USD = gw["USD"];
+ env.fund(XRP(100000), gw);
+
+ int const max_limit = 256; //would be 2048 for binary requests, no need to test that here
+
+ for (auto i = 0; i < max_limit + 10; i++)
+ {
+ Account const bob {std::string("bob") + std::to_string(i)};
+ env.fund(XRP(1000), bob);
+ }
+ env.close();
+
+ // no limit specified
+ // with no limit specified, we get the max_limit if the total number of
+ // accounts is greater than max, which it is here
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = "current";
+ jvParams[jss::binary] = false;
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(
+ jrr[jss::ledger_current_index].isIntegral() &&
+ jrr[jss::ledger_current_index].asInt() > 0 );
+ BEAST_EXPECT( checkMarker(jrr) );
+ BEAST_EXPECT( checkArraySize(jrr[jss::state], max_limit) );
+
+ // check limits values around the max_limit (+/- 1)
+ for (auto delta = -1; delta <= 1; delta++)
+ {
+ jvParams[jss::limit] = max_limit + delta;
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(
+ checkArraySize( jrr[jss::state],
+ (delta > 0 && !as_admin) ? max_limit : max_limit + delta ));
+ }
+ }
+
+ void testCurrentLedgerBinary()
+ {
+ using namespace test::jtx;
+ Env env { *this, makeConfig(false) };
+ Account const gw { "gateway" };
+ auto const USD = gw["USD"];
+ env.fund(XRP(100000), gw);
+
+ int const num_accounts = 10;
+
+ for (auto i = 0; i < num_accounts; i++)
+ {
+ Account const bob { std::string("bob") + std::to_string(i) };
+ env.fund(XRP(1000), bob);
+ }
+ env.close();
+
+ // no limit specified
+ // with no limit specified, we should get all of our fund entries
+ // plus three more related to the gateway setup
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = "current";
+ jvParams[jss::binary] = true;
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(
+ jrr[jss::ledger_current_index].isIntegral() &&
+ jrr[jss::ledger_current_index].asInt() > 0);
+ BEAST_EXPECT( ! jrr.isMember(jss::marker) );
+ BEAST_EXPECT( checkArraySize(jrr[jss::state], num_accounts + 3) );
+ }
+
+ void testBadInput()
+ {
+ using namespace test::jtx;
+ Env env { *this };
+ Account const gw { "gateway" };
+ auto const USD = gw["USD"];
+ Account const bob { "bob" };
+
+ env.fund(XRP(10000), gw, bob);
+ env.trust(USD(1000), bob);
+
+ {
+ // bad limit
+ Json::Value jvParams;
+ jvParams[jss::limit] = "0"; // NOT an integer
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+ BEAST_EXPECT(jrr[jss::status] == "error");
+ BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not integer.");
+ }
+
+ {
+ // invalid marker
+ Json::Value jvParams;
+ jvParams[jss::marker] = "NOT_A_MARKER";
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+ BEAST_EXPECT(jrr[jss::status] == "error");
+ BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not valid.");
+ }
+
+ {
+ // invalid marker - not a string
+ Json::Value jvParams;
+ jvParams[jss::marker] = 1;
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+ BEAST_EXPECT(jrr[jss::status] == "error");
+ BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not valid.");
+ }
+
+ {
+ // ask for a bad ledger index
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = 10u;
+ auto const jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
+ BEAST_EXPECT(jrr[jss::status] == "error");
+ BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
+ }
+ }
+
+ void testMarkerFollow()
+ {
+ using namespace test::jtx;
+ Env env { *this, makeConfig(false) };
+ Account const gw { "gateway" };
+ auto const USD = gw["USD"];
+ env.fund(XRP(100000), gw);
+
+ int const num_accounts = 20;
+
+ for (auto i = 0; i < num_accounts; i++)
+ {
+ Account const bob { std::string("bob") + std::to_string(i) };
+ env.fund(XRP(1000), bob);
+ }
+ env.close();
+
+ // no limit specified
+ // with no limit specified, we should get all of our fund entries
+ // plus three more related to the gateway setup
+ Json::Value jvParams;
+ jvParams[jss::ledger_index] = "current";
+ jvParams[jss::binary] = false;
+ auto jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ auto const total_count = jrr[jss::state].size();
+
+ // now make request with a limit and loop until we get all
+ jvParams[jss::limit] = 5;
+ jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ BEAST_EXPECT( checkMarker(jrr) );
+ auto running_total = jrr[jss::state].size();
+ while ( jrr.isMember(jss::marker) )
+ {
+ jvParams[jss::marker] = jrr[jss::marker];
+ jrr = env.rpc ( "json", "ledger_data", jvParams.toStyledString() ) [jss::result];
+ running_total += jrr[jss::state].size();
+ }
+ BEAST_EXPECT( running_total == total_count );
+ }
+
+ void run()
+ {
+ testCurrentLedgerToLimits(true);
+ testCurrentLedgerToLimits(false);
+ testCurrentLedgerBinary();
+ testBadInput();
+ testMarkerFollow();
+ }
+
+};
+
+BEAST_DEFINE_TESTSUITE(LedgerData,app,ripple);
+
+}
diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp
index 2c922acb6..146461fac 100644
--- a/src/ripple/unity/rpcx.cpp
+++ b/src/ripple/unity/rpcx.cpp
@@ -105,6 +105,7 @@
#include
#include
#include
+#include
#include
#include
#include