tools: add 'nfdc cs config' command

refs #4050

Change-Id: Ifc49b78a286b1947452d3d7917b5937b95d1bfe5
diff --git a/docs/manpages/nfdc-cs.rst b/docs/manpages/nfdc-cs.rst
index 4ff387c..99b46d8 100644
--- a/docs/manpages/nfdc-cs.rst
+++ b/docs/manpages/nfdc-cs.rst
@@ -1,14 +1,30 @@
 nfdc-cs
-===========
+=======
 
 SYNOPSIS
 --------
 | nfdc cs [info]
+| nfdc cs config [capacity <CAPACITY>] [admit on|off] [serve on|off]
 
 DESCRIPTION
 -----------
 The **nfdc cs info** command shows CS statistics information.
 
+The **nfdc cs config** command updates CS configuration.
+
+OPTIONS
+-------
+<CAPACITY>
+    Maximum number of Data packets the CS can store.
+    Lowering the capacity causes the CS to evict excess Data packets.
+
+admit on|off
+    Whether the CS can admit new Data.
+
+serve on|off
+    Whether the CS can satisfy incoming Interests using cached Data.
+    Turning this off causes all CS lookups to miss.
+
 SEE ALSO
 --------
 nfd(1), nfdc(1)
diff --git a/tests/tools/nfdc/cs-module.t.cpp b/tests/tools/nfdc/cs-module.t.cpp
index 29a444b..7191f7d 100644
--- a/tests/tools/nfdc/cs-module.t.cpp
+++ b/tests/tools/nfdc/cs-module.t.cpp
@@ -26,8 +26,7 @@
 #include "nfdc/cs-module.hpp"
 
 #include "status-fixture.hpp"
-
-#include <ndn-cxx/security/signing-helpers.hpp>
+#include "execute-command-fixture.hpp"
 
 namespace nfd {
 namespace tools {
@@ -35,7 +34,64 @@
 namespace tests {
 
 BOOST_AUTO_TEST_SUITE(Nfdc)
-BOOST_FIXTURE_TEST_SUITE(TestCsModule, StatusFixture<CsModule>)
+BOOST_AUTO_TEST_SUITE(TestCsModule)
+
+BOOST_FIXTURE_TEST_SUITE(ConfigCommand, ExecuteCommandFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/cs/config");
+    BOOST_REQUIRE(req.hasCapacity());
+    BOOST_CHECK_EQUAL(req.getCapacity(), 29850);
+    BOOST_CHECK(req.hasFlagBit(ndn::nfd::BIT_CS_ENABLE_ADMIT));
+    BOOST_CHECK_EQUAL(req.getFlagBit(ndn::nfd::BIT_CS_ENABLE_ADMIT), false);
+    BOOST_CHECK(req.hasFlagBit(ndn::nfd::BIT_CS_ENABLE_SERVE));
+    BOOST_CHECK_EQUAL(req.getFlagBit(ndn::nfd::BIT_CS_ENABLE_SERVE), true);
+
+    ControlParameters resp(req);
+    resp.unsetMask();
+    this->succeedCommand(interest, resp);
+  };
+
+  this->execute("cs config admit off serve on capacity 29850");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("cs-config-updated capacity=29850 admit=off serve=on\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(NoUpdate)
+{
+  this->processInterest = [this] (const Interest& interest) {
+    ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/cs/config");
+    BOOST_CHECK(!req.hasCapacity());
+    BOOST_CHECK(!req.hasFlagBit(ndn::nfd::BIT_CS_ENABLE_ADMIT));
+    BOOST_CHECK(!req.hasFlagBit(ndn::nfd::BIT_CS_ENABLE_SERVE));
+
+    ControlParameters resp;
+    resp.setCapacity(573599);
+    resp.setFlagBit(ndn::nfd::BIT_CS_ENABLE_ADMIT, true, false);
+    resp.setFlagBit(ndn::nfd::BIT_CS_ENABLE_SERVE, false, false);
+    this->succeedCommand(interest, resp);
+  };
+
+  this->execute("cs config");
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("cs-config-updated capacity=573599 admit=on serve=off\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCommand)
+{
+  this->processInterest = nullptr; // no response to command
+
+  this->execute("cs config capacity 19956");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 10060 when updating CS config: request timed out\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ConfigCommand
 
 const std::string STATUS_XML = stripXmlSpaces(R"XML(
   <cs>
@@ -49,7 +105,7 @@
   nHits=14363 nMisses=27462
 )TEXT").substr(1);
 
-BOOST_AUTO_TEST_CASE(Status)
+BOOST_FIXTURE_TEST_CASE(Status, StatusFixture<CsModule>)
 {
   this->fetchStatus();
   CsInfo payload;
diff --git a/tools/nfdc/available-commands.cpp b/tools/nfdc/available-commands.cpp
index fca0745..5be4854 100644
--- a/tools/nfdc/available-commands.cpp
+++ b/tools/nfdc/available-commands.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "available-commands.hpp"
+#include "cs-module.hpp"
 #include "face-module.hpp"
 #include "rib-module.hpp"
 #include "status.hpp"
@@ -39,6 +40,7 @@
   registerStatusCommands(parser);
   FaceModule::registerCommands(parser);
   RibModule::registerCommands(parser);
+  CsModule::registerCommands(parser);
   StrategyChoiceModule::registerCommands(parser);
 }
 
diff --git a/tools/nfdc/cs-module.cpp b/tools/nfdc/cs-module.cpp
index 975fd99..9d28855 100644
--- a/tools/nfdc/cs-module.cpp
+++ b/tools/nfdc/cs-module.cpp
@@ -31,6 +31,53 @@
 namespace nfdc {
 
 void
+CsModule::registerCommands(CommandParser& parser)
+{
+  CommandDefinition defCsConfig("cs", "config");
+  defCsConfig
+    .setTitle("change CS configuration")
+    .addArg("capacity", ArgValueType::UNSIGNED, Required::NO, Positional::NO)
+    .addArg("admit", ArgValueType::BOOLEAN, Required::NO, Positional::NO)
+    .addArg("serve", ArgValueType::BOOLEAN, Required::NO, Positional::NO);
+  parser.addCommand(defCsConfig, &CsModule::config);
+}
+
+void
+CsModule::config(ExecuteContext& ctx)
+{
+  using boost::logic::indeterminate;
+
+  auto capacity = ctx.args.getOptional<uint64_t>("capacity");
+  auto enableAdmit = ctx.args.getTribool("admit");
+  auto enableServe = ctx.args.getTribool("serve");
+
+  ControlParameters p;
+  if (capacity) {
+    p.setCapacity(*capacity);
+  }
+  if (!indeterminate(enableAdmit)) {
+    p.setFlagBit(ndn::nfd::BIT_CS_ENABLE_ADMIT, enableAdmit);
+  }
+  if (!indeterminate(enableServe)) {
+    p.setFlagBit(ndn::nfd::BIT_CS_ENABLE_SERVE, enableServe);
+  }
+
+  ctx.controller.start<ndn::nfd::CsConfigCommand>(p,
+    [&] (const ControlParameters& resp) {
+      text::ItemAttributes ia;
+      ctx.out << "cs-config-updated "
+              << ia("capacity") << resp.getCapacity()
+              << ia("admit") << text::OnOff{resp.getFlagBit(ndn::nfd::BIT_CS_ENABLE_ADMIT)}
+              << ia("serve") << text::OnOff{resp.getFlagBit(ndn::nfd::BIT_CS_ENABLE_SERVE)}
+              << '\n';
+    },
+    ctx.makeCommandFailureHandler("updating CS config"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
 CsModule::fetchStatus(Controller& controller,
                       const function<void()>& onSuccess,
                       const Controller::DatasetFailCallback& onFailure,
diff --git a/tools/nfdc/cs-module.hpp b/tools/nfdc/cs-module.hpp
index 143b3e4..7634d3d 100644
--- a/tools/nfdc/cs-module.hpp
+++ b/tools/nfdc/cs-module.hpp
@@ -26,6 +26,7 @@
 #ifndef NFD_TOOLS_NFDC_CS_MODULE_HPP
 #define NFD_TOOLS_NFDC_CS_MODULE_HPP
 
+#include "command-parser.hpp"
 #include "module.hpp"
 
 namespace nfd {
@@ -40,6 +41,16 @@
 class CsModule : public Module, noncopyable
 {
 public:
+  /** \brief register 'cs config' command
+   */
+  static void
+  registerCommands(CommandParser& parser);
+
+  /** \brief the 'cs config' command
+   */
+  static void
+  config(ExecuteContext& ctx);
+
   void
   fetchStatus(Controller& controller,
               const function<void()>& onSuccess,