tools: erase all requested entries from CS in nfdc

This change modifies the behavior of nfdc cs
erase to erase up to the requested count of CS
entries, instead of requiring the user to
continually rerun the command to reach the
desired count of erases.

refs #4744

Change-Id: Ia9b6fa2b4f97769fe6fa911a7b0b5c641ce215dc
diff --git a/tests/tools/nfdc/cs-module.t.cpp b/tests/tools/nfdc/cs-module.t.cpp
index d96f5f6..9b1ba0a 100644
--- a/tests/tools/nfdc/cs-module.t.cpp
+++ b/tests/tools/nfdc/cs-module.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -95,9 +95,10 @@
 
 BOOST_FIXTURE_TEST_SUITE(EraseCommand, ExecuteCommandFixture)
 
-BOOST_AUTO_TEST_CASE(NoCount)
+BOOST_AUTO_TEST_CASE(WithoutCount)
 {
-  this->processInterest = [this] (const Interest& interest) {
+  size_t countRequests = 0;
+  this->processInterest = [this, &countRequests] (const Interest& interest) {
     ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/cs/erase");
     BOOST_REQUIRE(req.hasName());
     BOOST_CHECK_EQUAL(req.getName(), "/S2NrUoNJcQ");
@@ -105,60 +106,127 @@
 
     ControlParameters resp;
     resp.setName("/S2NrUoNJcQ");
-    resp.setCount(152);
+
+    switch (countRequests) {
+    case 0:
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      break;
+    case 1:
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      break;
+    case 2:
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      break;
+    case 3:
+      resp.setCount(500);
+      break;
+    default:
+      BOOST_FAIL("Exceeded allowed number of erase requests");
+      break;
+    }
+    countRequests++;
     this->succeedCommand(interest, resp);
   };
 
   this->execute("cs erase /S2NrUoNJcQ");
   BOOST_CHECK_EQUAL(exitCode, 0);
-  BOOST_CHECK(out.is_equal("cs-erased prefix=/S2NrUoNJcQ count=152 has-more=no\n"));
+  BOOST_CHECK_EQUAL(countRequests, 4);
+  BOOST_CHECK(out.is_equal("cs-erased prefix=/S2NrUoNJcQ count=3500\n"));
   BOOST_CHECK(err.is_empty());
 }
 
 BOOST_AUTO_TEST_CASE(WithCount)
 {
-  this->processInterest = [this] (const Interest& interest) {
+  size_t countRequests = 0;
+  this->processInterest = [this, &countRequests] (const Interest& interest) {
     ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/cs/erase");
     BOOST_REQUIRE(req.hasName());
     BOOST_CHECK_EQUAL(req.getName(), "/gr7ADmIq");
     BOOST_REQUIRE(req.hasCount());
-    BOOST_CHECK_EQUAL(req.getCount(), 7568);
 
     ControlParameters resp;
     resp.setName("/gr7ADmIq");
-    resp.setCount(141);
+
+    switch (countRequests) {
+    case 0:
+      BOOST_CHECK_EQUAL(req.getCount(), 3568);
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      break;
+    case 1:
+      BOOST_CHECK_EQUAL(req.getCount(), 2568);
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      break;
+    case 2:
+      BOOST_CHECK_EQUAL(req.getCount(), 1568);
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      break;
+    case 3:
+      BOOST_CHECK_EQUAL(req.getCount(), 568);
+      resp.setCount(568);
+      break;
+    default:
+      BOOST_FAIL("Exceeded allowed number of erase requests");
+      break;
+    }
+
+    countRequests++;
     this->succeedCommand(interest, resp);
   };
 
-  this->execute("cs erase /gr7ADmIq count 7568");
+  this->execute("cs erase /gr7ADmIq count 3568");
   BOOST_CHECK_EQUAL(exitCode, 0);
-  BOOST_CHECK(out.is_equal("cs-erased prefix=/gr7ADmIq count=141 has-more=no\n"));
+  BOOST_CHECK_EQUAL(countRequests, 4);
+  BOOST_CHECK(out.is_equal("cs-erased prefix=/gr7ADmIq count=3568\n"));
   BOOST_CHECK(err.is_empty());
 }
 
-BOOST_AUTO_TEST_CASE(HasMore)
+BOOST_AUTO_TEST_CASE(WithCountError)
 {
-  this->processInterest = [this] (const Interest& interest) {
+  size_t countRequests = 0;
+  this->processInterest = [this, &countRequests] (const Interest& interest) {
     ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/cs/erase");
     BOOST_REQUIRE(req.hasName());
-    BOOST_CHECK_EQUAL(req.getName(), "/8Rq1Merv");
+    BOOST_CHECK_EQUAL(req.getName(), "/gr7ADmIq");
     BOOST_REQUIRE(req.hasCount());
-    BOOST_CHECK_EQUAL(req.getCount(), 16519);
 
     ControlParameters resp;
-    resp.setName("/8Rq1Merv");
-    resp.setCount(256);
-    resp.setCapacity(256);
-    this->succeedCommand(interest, resp);
+    resp.setName("/gr7ADmIq");
+
+    switch (countRequests) {
+    case 0:
+      BOOST_CHECK_EQUAL(req.getCount(), 3568);
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      this->succeedCommand(interest, resp);
+      break;
+    case 1:
+      BOOST_CHECK_EQUAL(req.getCount(), 2568);
+      resp.setCount(1000);
+      resp.setCapacity(1000);
+      this->failCommand(interest, 500, "internal error");
+      break;
+    default:
+      BOOST_FAIL("Exceeded allowed number of erase requests");
+      break;
+    }
+
+    countRequests++;
   };
 
-  this->execute("cs erase /8Rq1Merv count 16519");
-  BOOST_CHECK_EQUAL(exitCode, 0);
-  BOOST_CHECK(out.is_equal("cs-erased prefix=/8Rq1Merv count=256 has-more=yes\n"));
-  BOOST_CHECK(err.is_empty());
+  this->execute("cs erase /gr7ADmIq count 3568");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK_EQUAL(countRequests, 2);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 500 when erasing cached Data: internal error\n"));
 }
 
-BOOST_AUTO_TEST_CASE(ErrorCommand)
+BOOST_AUTO_TEST_CASE(Timeout)
 {
   this->processInterest = nullptr; // no response to command
 
diff --git a/tools/nfdc/cs-module.cpp b/tools/nfdc/cs-module.cpp
index 1b9915e..cdaad5a 100644
--- a/tools/nfdc/cs-module.cpp
+++ b/tools/nfdc/cs-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -92,26 +92,42 @@
   auto prefix = ctx.args.get<Name>("prefix");
   auto count = ctx.args.getOptional<uint64_t>("count");
 
+  uint64_t numErased = 0;
+  bool wasLimited = false;
+  bool wasSuccessful = true;
+
   ControlParameters params;
   params.setName(prefix);
-  if (count) {
-    params.setCount(*count);
+
+  // The cs/erase command can have a limit on the number of CS entries erased in a single operation.
+  // Therefore, we may need to run cs/erase multiple times to achieve the desired number of erases.
+  do {
+    if (count) {
+      params.setCount(*count - numErased);
+    }
+
+    wasSuccessful = false;
+
+    ctx.controller.start<ndn::nfd::CsEraseCommand>(
+      params,
+      [&] (const ControlParameters& resp) {
+        wasSuccessful = true;
+        numErased += resp.getCount();
+        wasLimited = resp.hasCapacity();
+      },
+      ctx.makeCommandFailureHandler("erasing cached Data"),
+      ctx.makeCommandOptions());
+
+    ctx.face.processEvents();
+  } while (wasSuccessful && wasLimited);
+
+  if (wasSuccessful) {
+    text::ItemAttributes ia;
+    ctx.out << "cs-erased "
+            << ia("prefix") << prefix
+            << ia("count") << numErased
+            << '\n';
   }
-
-  ctx.controller.start<ndn::nfd::CsEraseCommand>(
-    params,
-    [&] (const ControlParameters& resp) {
-      text::ItemAttributes ia;
-      ctx.out << "cs-erased "
-              << ia("prefix") << resp.getName()
-              << ia("count") << resp.getCount()
-              << ia("has-more") << text::YesNo{resp.hasCapacity()}
-              << '\n';
-    },
-    ctx.makeCommandFailureHandler("erasing cached Data"),
-    ctx.makeCommandOptions());
-
-  ctx.face.processEvents();
 }
 
 void