mgmt: implement cs/erase command

refs #4318

Change-Id: I2c8180cde940cc378a219f9aaecae71cd3b0b28c
diff --git a/daemon/mgmt/cs-manager.cpp b/daemon/mgmt/cs-manager.cpp
index 20ee279..60099d2 100644
--- a/daemon/mgmt/cs-manager.cpp
+++ b/daemon/mgmt/cs-manager.cpp
@@ -28,6 +28,8 @@
 
 namespace nfd {
 
+constexpr size_t CsManager::ERASE_LIMIT;
+
 CsManager::CsManager(Cs& cs, const ForwarderCounters& fwCnt,
                      Dispatcher& dispatcher, CommandAuthenticator& authenticator)
   : NfdManagerBase(dispatcher, authenticator, "cs")
@@ -36,6 +38,8 @@
 {
   registerCommandHandler<ndn::nfd::CsConfigCommand>("config",
     bind(&CsManager::changeConfig, this, _4, _5));
+  registerCommandHandler<ndn::nfd::CsEraseCommand>("erase",
+    bind(&CsManager::erase, this, _4, _5));
 
   registerStatusDatasetHandler("info", bind(&CsManager::serveInfo, this, _1, _2, _3));
 }
@@ -66,6 +70,34 @@
 }
 
 void
+CsManager::erase(const ControlParameters& parameters,
+                 const ndn::mgmt::CommandContinuation& done)
+{
+  size_t count = parameters.hasCount() ?
+                 parameters.getCount() :
+                 std::numeric_limits<size_t>::max();
+  m_cs.erase(parameters.getName(), std::min(count, ERASE_LIMIT),
+    [=] (size_t nErased) {
+      ControlParameters body;
+      body.setName(parameters.getName());
+      body.setCount(nErased);
+      if (nErased == ERASE_LIMIT && count > ERASE_LIMIT) {
+        m_cs.find(Interest(parameters.getName()).setCanBePrefix(true),
+          [=] (const Interest&, const Data&) mutable {
+            body.setCapacity(ERASE_LIMIT);
+            done(ControlResponse(200, "OK").setBody(body.wireEncode()));
+          },
+          [=] (const Interest&) {
+            done(ControlResponse(200, "OK").setBody(body.wireEncode()));
+          });
+      }
+      else {
+        done(ControlResponse(200, "OK").setBody(body.wireEncode()));
+      }
+    });
+}
+
+void
 CsManager::serveInfo(const Name& topPrefix, const Interest& interest,
                      ndn::mgmt::StatusDatasetContext& context) const
 {
diff --git a/daemon/mgmt/cs-manager.hpp b/daemon/mgmt/cs-manager.hpp
index 6a862f1..e1d2bbd 100644
--- a/daemon/mgmt/cs-manager.hpp
+++ b/daemon/mgmt/cs-manager.hpp
@@ -32,7 +32,7 @@
 
 namespace nfd {
 
-/** \brief implement the CS Management of NFD Management Protocol.
+/** \brief Implement the CS Management of NFD Management Protocol.
  *  \sa https://redmine.named-data.net/projects/nfd/wiki/CsMgmt
  */
 class CsManager : public NfdManagerBase
@@ -42,16 +42,27 @@
             Dispatcher& dispatcher, CommandAuthenticator& authenticator);
 
 private:
+  /** \brief Process cs/config command.
+   */
   void
   changeConfig(const ControlParameters& parameters,
                const ndn::mgmt::CommandContinuation& done);
 
-  /** \brief serve CS information dataset
+  /** \brief Process cs/erase command.
+   */
+  void
+  erase(const ControlParameters& parameters,
+        const ndn::mgmt::CommandContinuation& done);
+
+  /** \brief Serve CS information dataset.
    */
   void
   serveInfo(const Name& topPrefix, const Interest& interest,
             ndn::mgmt::StatusDatasetContext& context) const;
 
+public:
+  static constexpr size_t ERASE_LIMIT = 256;
+
 private:
   Cs& m_cs;
   const ForwarderCounters& m_fwCnt;
diff --git a/tests/daemon/mgmt/cs-manager.t.cpp b/tests/daemon/mgmt/cs-manager.t.cpp
index c5d22f4..18e2cdc 100644
--- a/tests/daemon/mgmt/cs-manager.t.cpp
+++ b/tests/daemon/mgmt/cs-manager.t.cpp
@@ -96,6 +96,106 @@
   BOOST_CHECK_EQUAL(m_cs.shouldServe(), false);
 }
 
+BOOST_AUTO_TEST_CASE(Erase)
+{
+  m_cs.setLimit(CsManager::ERASE_LIMIT * 5);
+  m_cs.insert(*makeData("/B/C/1"));
+  m_cs.insert(*makeData("/B/C/2"));
+  m_cs.insert(*makeData("/B/D/3"));
+  m_cs.insert(*makeData("/B/D/4"));
+  for (size_t i = 0; i < CsManager::ERASE_LIMIT - 1; ++i) {
+    m_cs.insert(*makeData(Name("/E").appendSequenceNumber(i)));
+  }
+  for (size_t i = 0; i < CsManager::ERASE_LIMIT; ++i) {
+    m_cs.insert(*makeData(Name("/F").appendSequenceNumber(i)));
+  }
+  for (size_t i = 0; i < CsManager::ERASE_LIMIT + 1; ++i) {
+    m_cs.insert(*makeData(Name("/G").appendSequenceNumber(i)));
+  }
+  for (size_t i = 0; i < CsManager::ERASE_LIMIT + 1; ++i) {
+    m_cs.insert(*makeData(Name("/H").appendSequenceNumber(i)));
+  }
+  const Name cmdPrefix("/localhost/nfd/cs/erase");
+
+  // requested Name matches no Data
+  auto req = makeControlCommandRequest(cmdPrefix,
+    ControlParameters().setName("/A").setCount(1));
+  receiveInterest(req);
+
+  // response should include zero as actual Count
+  ControlParameters body;
+  body.setName("/A");
+  body.setCount(0);
+  BOOST_CHECK_EQUAL(checkResponse(0, req.getName(),
+                                  ControlResponse(200, "OK").setBody(body.wireEncode())),
+                    CheckResponseResult::OK);
+
+  // requested Count is less than erase limit
+  req = makeControlCommandRequest(cmdPrefix,
+    ControlParameters().setName("/B").setCount(3));
+  receiveInterest(req);
+
+  // response should include actual Count and omit Capacity
+  body.setName("/B");
+  body.setCount(3);
+  BOOST_CHECK_EQUAL(checkResponse(1, req.getName(),
+                                  ControlResponse(200, "OK").setBody(body.wireEncode())),
+                    CheckResponseResult::OK);
+
+  // requested Count equals erase limit
+  req = makeControlCommandRequest(cmdPrefix,
+    ControlParameters().setName("/E").setCount(CsManager::ERASE_LIMIT));
+  receiveInterest(req);
+
+  // response should include actual Count and omit Capacity
+  body.setName("/E");
+  body.setCount(CsManager::ERASE_LIMIT - 1);
+  BOOST_CHECK_EQUAL(checkResponse(2, req.getName(),
+                                  ControlResponse(200, "OK").setBody(body.wireEncode())),
+                    CheckResponseResult::OK);
+
+  // requested Count exceeds erase limit, but there are no more Data
+  req = makeControlCommandRequest(cmdPrefix,
+    ControlParameters().setName("/F").setCount(CsManager::ERASE_LIMIT + 1));
+  receiveInterest(req);
+
+  // response should include actual Count and omit Capacity
+  body.setName("/F");
+  body.setCount(CsManager::ERASE_LIMIT);
+  BOOST_CHECK_EQUAL(checkResponse(3, req.getName(),
+                                  ControlResponse(200, "OK").setBody(body.wireEncode())),
+                    CheckResponseResult::OK);
+
+  // requested Count exceeds erase limit, and there are more Data
+  req = makeControlCommandRequest(cmdPrefix,
+    ControlParameters().setName("/G").setCount(CsManager::ERASE_LIMIT + 1));
+  receiveInterest(req);
+
+  // response should include both actual Count and Capacity
+  body.setName("/G");
+  body.setCount(CsManager::ERASE_LIMIT);
+  body.setCapacity(CsManager::ERASE_LIMIT);
+  BOOST_CHECK_EQUAL(checkResponse(4, req.getName(),
+                                  ControlResponse(200, "OK").setBody(body.wireEncode())),
+                    CheckResponseResult::OK);
+
+  // request omit Count, which implies "no limit" aka exceeds erase limit
+  req = makeControlCommandRequest(cmdPrefix,
+    ControlParameters().setName("/H"));
+  receiveInterest(req);
+
+  // response should include both actual Count and Capacity since there are more Data
+  body.setName("/H");
+  body.setCount(CsManager::ERASE_LIMIT);
+  body.setCapacity(CsManager::ERASE_LIMIT);
+  BOOST_CHECK_EQUAL(checkResponse(5, req.getName(),
+                                  ControlResponse(200, "OK").setBody(body.wireEncode())),
+                    CheckResponseResult::OK);
+
+  // one Data each under /A, /G, /H remain, all other Data are erased
+  BOOST_CHECK_EQUAL(m_cs.size(), 3);
+}
+
 BOOST_AUTO_TEST_CASE(Info)
 {
   m_cs.setLimit(2681);