mgmt: refactoring and cleanup in StatusDatasetContext

 * remove {get,set}Expiry(): it's not the responsibility
   of StatusDatasetContext to deal with in-memory storage
 * increase the max payload size of each segment to 8k bytes
 * minimize the number of memory allocations
 * really check that the prefix does not end with a segment
   component, as stated in the docs
 * throw std::logic_error instead of std::domain_error
 * improve exception messages
 * cleanup doxygen

Change-Id: Id6990f3dd064cc90a45eab4ec1c52141f5212be5
diff --git a/ndn-cxx/mgmt/dispatcher.cpp b/ndn-cxx/mgmt/dispatcher.cpp
index 306c7f3..ed0e00c 100644
--- a/ndn-cxx/mgmt/dispatcher.cpp
+++ b/ndn-cxx/mgmt/dispatcher.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2021 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -28,8 +28,6 @@
 namespace ndn {
 namespace mgmt {
 
-const time::milliseconds DEFAULT_FRESHNESS_PERIOD = 1_s;
-
 Authorization
 makeAcceptAllAuthorization()
 {
@@ -133,10 +131,10 @@
 
 void
 Dispatcher::sendData(const Name& dataName, const Block& content, const MetaInfo& metaInfo,
-                     SendDestination option, time::milliseconds imsFresh)
+                     SendDestination option)
 {
   auto data = make_shared<Data>(dataName);
-  data->setContent(content).setMetaInfo(metaInfo).setFreshnessPeriod(DEFAULT_FRESHNESS_PERIOD);
+  data->setContent(content).setMetaInfo(metaInfo).setFreshnessPeriod(1_s);
 
   m_keyChain.sign(*data, m_signingInfo);
 
@@ -144,7 +142,7 @@
     lp::CachePolicy policy;
     policy.setPolicy(lp::CachePolicyType::NO_CACHE);
     data->setTag(make_shared<lp::CachePolicyTag>(policy));
-    m_storage.insert(*data, imsFresh);
+    m_storage.insert(*data, 1_s);
   }
 
   if (option == SendDestination::FACE || option == SendDestination::FACE_AND_IMS) {
@@ -159,7 +157,7 @@
     m_face.put(data);
   }
   catch (const Face::Error& e) {
-    NDN_LOG_ERROR("sendOnFace: " << e.what());
+    NDN_LOG_ERROR("sendOnFace(" << data.getName() << "): " << e.what());
   }
 }
 
@@ -215,8 +213,7 @@
   }
 
   // control response is always sent out through the face
-  sendData(interest.getName(), resp.wireEncode(), metaInfo,
-           SendDestination::FACE, DEFAULT_FRESHNESS_PERIOD);
+  sendData(interest.getName(), resp.wireEncode(), metaInfo, SendDestination::FACE);
 }
 
 void
@@ -272,14 +269,13 @@
                                                    const StatusDatasetHandler& handler)
 {
   StatusDatasetContext context(interest,
-                               bind(&Dispatcher::sendStatusDatasetSegment, this, _1, _2, _3, _4),
+                               bind(&Dispatcher::sendStatusDatasetSegment, this, _1, _2, _3),
                                bind(&Dispatcher::sendControlResponse, this, _1, interest, true));
   handler(prefix, interest, context);
 }
 
 void
-Dispatcher::sendStatusDatasetSegment(const Name& dataName, const Block& content,
-                                     time::milliseconds imsFresh, bool isFinalBlock)
+Dispatcher::sendStatusDatasetSegment(const Name& dataName, const Block& content, bool isFinalBlock)
 {
   // the first segment will be sent to both places (the face and the in-memory storage)
   // other segments will be inserted to the in-memory storage only
@@ -293,7 +289,7 @@
     metaInfo.setFinalBlock(dataName[-1]);
   }
 
-  sendData(dataName, content, metaInfo, destination, imsFresh);
+  sendData(dataName, content, metaInfo, destination);
 }
 
 PostNotification
@@ -331,7 +327,7 @@
 
   // notification is sent out via the face after inserting into the in-memory storage,
   // because a request may be pending in the PIT
-  sendData(streamName, notification, {}, SendDestination::FACE_AND_IMS, DEFAULT_FRESHNESS_PERIOD);
+  sendData(streamName, notification, {}, SendDestination::FACE_AND_IMS);
 }
 
 } // namespace mgmt
diff --git a/ndn-cxx/mgmt/dispatcher.hpp b/ndn-cxx/mgmt/dispatcher.hpp
index de0a69b..43ae1d2 100644
--- a/ndn-cxx/mgmt/dispatcher.hpp
+++ b/ndn-cxx/mgmt/dispatcher.hpp
@@ -323,21 +323,20 @@
    * @brief send data to the face and/or in-memory storage
    *
    * Create a Data packet with the given @p dataName, @p content, and @p metaInfo,
-   * set its FreshnessPeriod to DEFAULT_FRESHNESS_PERIOD, and then send it out through
-   * the face and/or insert it into the in-memory storage as specified in @p destination.
+   * set its FreshnessPeriod to 1 second, and then send it out through the face
+   * and/or insert it into the in-memory storage as specified by @p destination.
    *
    * If it's toward the in-memory storage, set its CachePolicy to NO_CACHE and limit
-   * its FreshnessPeriod in the storage to @p imsFresh.
+   * its FreshnessPeriod in the storage to 1 second.
    *
    * @param dataName the name of this piece of data
    * @param content the content of this piece of data
    * @param metaInfo some meta information of this piece of data
    * @param destination where to send this piece of data
-   * @param imsFresh freshness period of this piece of data in in-memory storage
    */
   void
   sendData(const Name& dataName, const Block& content, const MetaInfo& metaInfo,
-           SendDestination destination, time::milliseconds imsFresh);
+           SendDestination destination);
 
   /**
    * @brief send out a data packt through the face
@@ -423,12 +422,10 @@
    *
    * @param dataName the name of this piece of data
    * @param content the content of this piece of data
-   * @param imsFresh the freshness period of this piece of data in the in-memory storage
    * @param isFinalBlock indicates whether this piece of data is the final block
    */
   void
-  sendStatusDatasetSegment(const Name& dataName, const Block& content,
-                           time::milliseconds imsFresh, bool isFinalBlock);
+  sendStatusDatasetSegment(const Name& dataName, const Block& content, bool isFinalBlock);
 
   void
   postNotification(const Block& notification, const PartialName& relPrefix);
diff --git a/ndn-cxx/mgmt/status-dataset-context.cpp b/ndn-cxx/mgmt/status-dataset-context.cpp
index 0487061..81e0b96 100644
--- a/ndn-cxx/mgmt/status-dataset-context.cpp
+++ b/ndn-cxx/mgmt/status-dataset-context.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2021 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -24,69 +24,64 @@
 namespace ndn {
 namespace mgmt {
 
-const time::milliseconds DEFAULT_STATUS_DATASET_FRESHNESS_PERIOD = 1_s;
+const size_t MAX_PAYLOAD_LENGTH = MAX_NDN_PACKET_SIZE - 800;
 
-const Name&
-StatusDatasetContext::getPrefix() const
+StatusDatasetContext::StatusDatasetContext(const Interest& interest,
+                                           DataSender dataSender, NackSender nackSender)
+  : m_interest(interest)
+  , m_dataSender(std::move(dataSender))
+  , m_nackSender(std::move(nackSender))
 {
-  return m_prefix;
+  setPrefix(interest.getName());
+  m_buffer.reserve(MAX_PAYLOAD_LENGTH);
 }
 
 StatusDatasetContext&
 StatusDatasetContext::setPrefix(const Name& prefix)
 {
-  if (!m_interest.getName().isPrefixOf(prefix)) {
-    NDN_THROW(std::invalid_argument("prefix does not start with Interest Name"));
+  if (m_state != State::INITIAL) {
+    NDN_THROW(std::logic_error("cannot call setPrefix() after append/end/reject"));
   }
 
-  if (m_state != State::INITIAL) {
-    NDN_THROW(std::domain_error("state is not in INITIAL"));
+  if (!m_interest.getName().isPrefixOf(prefix)) {
+    NDN_THROW(std::invalid_argument("prefix must start with the Interest's name"));
+  }
+
+  if (prefix.at(-1).isSegment()) {
+    NDN_THROW(std::invalid_argument("prefix must not contain a segment component"));
   }
 
   m_prefix = prefix;
-
-  if (!m_prefix[-1].isVersion()) {
+  if (!m_prefix.at(-1).isVersion()) {
     m_prefix.appendVersion();
   }
 
   return *this;
 }
 
-const time::milliseconds&
-StatusDatasetContext::getExpiry() const
-{
-  return m_expiry;
-}
-
-StatusDatasetContext&
-StatusDatasetContext::setExpiry(const time::milliseconds& expiry)
-{
-  m_expiry = expiry;
-  return *this;
-}
-
 void
 StatusDatasetContext::append(const Block& block)
 {
   if (m_state == State::FINALIZED) {
-    NDN_THROW(std::domain_error("state is in FINALIZED"));
+    NDN_THROW(std::logic_error("cannot call append() on a finalized context"));
   }
 
   m_state = State::RESPONDED;
 
-  size_t nBytesLeft = block.size();
-  while (nBytesLeft > 0) {
-    size_t nBytesAppend = std::min(nBytesLeft,
-                                   (ndn::MAX_NDN_PACKET_SIZE >> 1) - m_buffer->size());
-    m_buffer->appendByteArray(block.wire() + (block.size() - nBytesLeft), nBytesAppend);
-    nBytesLeft -= nBytesAppend;
+  auto cur = block.begin();
+  while (cur != block.end()) {
+    auto nBytesToAppend = std::min<std::ptrdiff_t>(std::distance(cur, block.end()),
+                                                   MAX_PAYLOAD_LENGTH - m_buffer.size());
+    auto next = std::next(cur, nBytesToAppend);
+    m_buffer.insert(m_buffer.end(), cur, next);
+    cur = next;
 
-    if (nBytesLeft > 0) {
+    if (cur != block.end()) {
+      BOOST_ASSERT(m_buffer.size() == MAX_PAYLOAD_LENGTH);
       m_dataSender(Name(m_prefix).appendSegment(m_segmentNo++),
-                   makeBinaryBlock(tlv::Content, m_buffer->buf(), m_buffer->size()),
-                   m_expiry, false);
-
-      m_buffer = make_shared<EncodingBuffer>();
+                   makeBinaryBlock(tlv::Content, m_buffer.begin(), m_buffer.end()),
+                   false);
+      m_buffer.clear();
     }
   }
 }
@@ -95,39 +90,25 @@
 StatusDatasetContext::end()
 {
   if (m_state == State::FINALIZED) {
-    NDN_THROW(std::domain_error("state is in FINALIZED"));
+    NDN_THROW(std::logic_error("cannot call end() on a finalized context"));
   }
 
   m_state = State::FINALIZED;
   m_dataSender(Name(m_prefix).appendSegment(m_segmentNo),
-               makeBinaryBlock(tlv::Content, m_buffer->buf(), m_buffer->size()),
-               m_expiry, true);
+               makeBinaryBlock(tlv::Content, m_buffer.begin(), m_buffer.end()),
+               true);
 }
 
 void
-StatusDatasetContext::reject(const ControlResponse& resp /*= a ControlResponse with 400*/)
+StatusDatasetContext::reject(const ControlResponse& resp)
 {
   if (m_state != State::INITIAL) {
-    NDN_THROW(std::domain_error("state is in RESPONDED or FINALIZED"));
+    NDN_THROW(std::logic_error("cannot call reject() after append/end"));
   }
 
   m_state = State::FINALIZED;
   m_nackSender(resp);
 }
 
-StatusDatasetContext::StatusDatasetContext(const Interest& interest,
-                                           const DataSender& dataSender,
-                                           const NackSender& nackSender)
-  : m_interest(interest)
-  , m_dataSender(dataSender)
-  , m_nackSender(nackSender)
-  , m_expiry(DEFAULT_STATUS_DATASET_FRESHNESS_PERIOD)
-  , m_buffer(make_shared<EncodingBuffer>())
-  , m_segmentNo(0)
-  , m_state(State::INITIAL)
-{
-  setPrefix(interest.getName());
-}
-
 } // namespace mgmt
 } // namespace ndn
diff --git a/ndn-cxx/mgmt/status-dataset-context.hpp b/ndn-cxx/mgmt/status-dataset-context.hpp
index db86397..5f911df 100644
--- a/ndn-cxx/mgmt/status-dataset-context.hpp
+++ b/ndn-cxx/mgmt/status-dataset-context.hpp
@@ -22,83 +22,74 @@
 #ifndef NDN_CXX_MGMT_STATUS_DATASET_CONTEXT_HPP
 #define NDN_CXX_MGMT_STATUS_DATASET_CONTEXT_HPP
 
-#include "ndn-cxx/data.hpp"
 #include "ndn-cxx/interest.hpp"
-#include "ndn-cxx/encoding/encoding-buffer.hpp"
 #include "ndn-cxx/mgmt/control-response.hpp"
-#include "ndn-cxx/util/time.hpp"
 
 namespace ndn {
 namespace mgmt {
 
-/** \brief provides a context for generating response to a StatusDataset request
+/**
+ * \brief Provides a context for generating the response to a StatusDataset request.
  */
 class StatusDatasetContext : noncopyable
 {
 public:
-  /** \return prefix of Data packets, with version component but without segment component
+  /**
+   * \brief Returns the prefix of Data packets, with version component but without segment component.
    */
   const Name&
-  getPrefix() const;
+  getPrefix() const
+  {
+    return m_prefix;
+  }
 
-  /** \brief change prefix of Data packets
-   *  \param prefix the prefix; it must start with Interest Name, may contain version component,
-   *         but must not contain segment component
-   *  \throw std::invalid_argument prefix does not start with Interest Name
-   *  \throw std::domain_error append, end, or reject has been invoked
+  /**
+   * \brief Changes the prefix of the response Data packets.
+   * \param prefix the prefix; it must start with the Interest Name, may contain a version
+   *               component, but must not contain a segment component
    *
-   *  StatusDatasetHandler may change the prefix of Data packets with this method,
-   *  before sending any response.
-   *  The version component is optional, and will be generated from current timestamp when omitted.
+   * StatusDatasetHandler may change the prefix of Data packets with this method,
+   * before sending any response.
+   *
+   * The version component is optional, and will be generated from the current timestamp if omitted.
+   *
+   * \throw std::invalid_argument \p prefix does not satisfy the requirements
+   * \throw std::logic_error append(), end(), or reject() has already been invoked
    */
   StatusDatasetContext&
   setPrefix(const Name& prefix);
 
-  /** \return expiration duration for this dataset response
-   */
-  const time::milliseconds&
-  getExpiry() const;
-
-  /** \brief set expiration duration
-   *
-   *  The response will be cached for the specified duration.
-   *  Incoming Interest that matches a cached response will be satisfied with that response,
-   *  without invoking StatusDatasetHandler again.
-   */
-  StatusDatasetContext&
-  setExpiry(const time::milliseconds& expiry);
-
-  /** \brief append a Block to the response
-   *  \throw std::domain_error end or reject has been invoked
+  /**
+   * \brief Appends a Block to the response.
+   * \throw std::logic_error end() or reject() has already been invoked
    */
   void
   append(const Block& block);
 
-  /** \brief end the response successfully after appending zero or more blocks
-   *  \throw std::domain_error reject has been invoked
+  /**
+   * \brief Finalizes the response successfully after appending zero or more blocks.
+   * \throw std::logic_error reject() has already been invoked
    */
   void
   end();
 
-  /** \brief declare the non-existence of a response
-   *  \throw std::domain_error append or end has been invoked
+  /**
+   * \brief Rejects the request.
+   * \param resp Content of producer-generated NACK
    *
-   *  This should be invoked when the incoming Interest is malformed.
-   *  A producer-generated NACK will be returned to requester.
+   * This should be invoked when the incoming Interest is malformed.
+   * A producer-generated NACK will be returned to the requester.
    *
-   *  \param resp Content of producer-generated NACK
+   * \throw std::logic_error append() or end() has already been invoked
    */
   void
   reject(const ControlResponse& resp = ControlResponse().setCode(400));
 
 NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  typedef std::function<void(const Name& dataName, const Block& content, time::milliseconds imsFresh,
-                             bool isFinalBlock)> DataSender;
-  typedef std::function<void(const ControlResponse& resp)> NackSender;
+  using DataSender = std::function<void(const Name& dataName, const Block& content, bool isFinalBlock)>;
+  using NackSender = std::function<void(const ControlResponse&)>;
 
-  StatusDatasetContext(const Interest& interest,
-                       const DataSender& dataSender,
-                       const NackSender& nackSender);
+  StatusDatasetContext(const Interest& interest, DataSender dataSender, NackSender nackSender);
 
 private:
   friend class Dispatcher;
@@ -107,18 +98,15 @@
   DataSender m_dataSender;
   NackSender m_nackSender;
   Name m_prefix;
-  time::milliseconds m_expiry;
-
-NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  shared_ptr<EncodingBuffer> m_buffer;
-  uint64_t m_segmentNo;
+  std::vector<uint8_t> m_buffer;
+  uint64_t m_segmentNo = 0;
 
   enum class State {
-    INITIAL, ///< none of .append, .end, .reject has been invoked
-    RESPONDED, ///< .append has been invoked
-    FINALIZED ///< .end or .reject has been invoked
+    INITIAL,   ///< none of append(), end(), reject() has been invoked
+    RESPONDED, ///< append() has been invoked
+    FINALIZED, ///< end() or reject() has been invoked
   };
-  State m_state;
+  State m_state = State::INITIAL;
 };
 
 } // namespace mgmt
diff --git a/tests/unit/mgmt/dispatcher.t.cpp b/tests/unit/mgmt/dispatcher.t.cpp
index 79d3dd3..d89da1b 100644
--- a/tests/unit/mgmt/dispatcher.t.cpp
+++ b/tests/unit/mgmt/dispatcher.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2020 Regents of the University of California.
+ * Copyright (c) 2013-2021 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -74,11 +74,8 @@
 static Authorization
 makeTestAuthorization()
 {
-  return [] (const Name& prefix,
-             const Interest& interest,
-             const ControlParameters* params,
-             AcceptContinuation accept,
-             RejectContinuation reject) {
+  return [] (const Name&, const Interest& interest, const ControlParameters*,
+             AcceptContinuation accept, RejectContinuation reject) {
     if (interest.getName()[-1] == name::Component("valid")) {
       accept("");
     }
@@ -251,7 +248,7 @@
   }
 
   void
-  wireDecode(const Block& wire) final
+  wireDecode(const Block&) final
   {
     m_state = EXPECTED_STATE;
   }
@@ -271,9 +268,9 @@
 {
   AcceptContinuation authorizationAccept;
   auto authorization =
-    [&authorizationAccept] (const Name& prefix, const Interest& interest, const ControlParameters* params,
-        AcceptContinuation accept, RejectContinuation reject) {
-      authorizationAccept = accept;
+    [&authorizationAccept] (const Name&, const Interest&, const ControlParameters*,
+                            AcceptContinuation accept, RejectContinuation) {
+      authorizationAccept = std::move(accept);
     };
 
   auto validateParameters =
@@ -282,11 +279,8 @@
     };
 
   size_t nCallbackCalled = 0;
-  dispatcher
-    .addControlCommand<StatefulParameters>("test",
-                                           authorization,
-                                           validateParameters,
-                                           bind([&nCallbackCalled] { ++nCallbackCalled; }));
+  dispatcher.addControlCommand<StatefulParameters>("test", authorization, validateParameters,
+                                                   bind([&nCallbackCalled] { ++nCallbackCalled; }));
 
   dispatcher.addTopPrefix("/root");
   advanceClocks(1_ms);
@@ -305,20 +299,15 @@
 {
   const uint8_t smallBuf[] = {0x81, 0x01, 0x01};
   const Block smallBlock(smallBuf, sizeof(smallBuf));
-  Block largeBlock;
-  {
-    EncodingBuffer encoder;
-    for (size_t i = 0; i < 2500; ++i) {
-      encoder.prependByte(1);
-    }
-    encoder.prependVarNumber(2500);
-    encoder.prependVarNumber(129);
-    largeBlock = encoder.block();
-  }
+  const Block largeBlock = [] {
+    Block b(129, std::make_shared<const Buffer>(3000));
+    b.encode();
+    return b;
+  }();
 
   dispatcher.addStatusDataset("test/small",
                               makeTestAuthorization(),
-                              [&smallBlock] (const Name& prefix, const Interest& interest,
+                              [&smallBlock] (const Name&, const Interest&,
                                              StatusDatasetContext& context) {
                                 context.append(smallBlock);
                                 context.append(smallBlock);
@@ -328,7 +317,7 @@
 
   dispatcher.addStatusDataset("test/large",
                               makeTestAuthorization(),
-                              [&largeBlock] (const Name& prefix, const Interest& interest,
+                              [&largeBlock] (const Name&, const Interest&,
                                              StatusDatasetContext& context) {
                                 context.append(largeBlock);
                                 context.append(largeBlock);
@@ -338,8 +327,7 @@
 
   dispatcher.addStatusDataset("test/reject",
                               makeTestAuthorization(),
-                              [] (const Name& prefix, const Interest& interest,
-                                  StatusDatasetContext& context) {
+                              [] (const Name&, const Interest&, StatusDatasetContext& context) {
                                 context.reject();
                               });
 
@@ -351,8 +339,8 @@
   face.receive(*makeInterest("/root/test/small/%80%00/invalid")); // returns 403
   face.receive(*makeInterest("/root/test/small/%80%00/silent")); // silently ignored
   advanceClocks(1_ms, 20);
-  BOOST_CHECK_EQUAL(face.sentData.size(), 2);
 
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 2);
   BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Blob);
   BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 403);
   BOOST_CHECK_EQUAL(face.sentData[1].getContentType(), tlv::ContentType_Blob);
@@ -365,7 +353,7 @@
   advanceClocks(1_ms, 10);
 
   // one data packet is generated and sent to both places
-  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
   BOOST_CHECK_EQUAL(storage.size(), 1);
 
   auto fetchedData = storage.find(interestSmall);
@@ -375,7 +363,7 @@
   face.receive(*makeInterest(Name("/root/test/small/valid").appendVersion(10))); // should be ignored
   face.receive(*makeInterest(Name("/root/test/small/valid").appendSegment(20))); // should be ignored
   advanceClocks(1_ms, 10);
-  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
   BOOST_CHECK_EQUAL(storage.size(), 1);
 
   Block content = face.sentData[0].getContent();
@@ -393,8 +381,8 @@
 
   // two data packets are generated, the first one will be sent to both places
   // while the second one will only be inserted into the in-memory storage
-  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
-  BOOST_CHECK_EQUAL(storage.size(), 2);
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+  BOOST_REQUIRE_EQUAL(storage.size(), 2);
 
   // segment0 should be sent through the face
   const auto& component = face.sentData[0].getName().at(-1);
@@ -429,7 +417,8 @@
   face.sentData.clear();
   face.receive(*makeInterest("/root/test/reject/%80%00/valid")); // returns nack
   advanceClocks(1_ms);
-  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
   BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Nack);
   BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 400);
   BOOST_CHECK_EQUAL(storage.size(), 0); // the nack packet will not be inserted into the in-memory storage
diff --git a/tests/unit/mgmt/status-dataset-context.t.cpp b/tests/unit/mgmt/status-dataset-context.t.cpp
index c46292c..58c8382 100644
--- a/tests/unit/mgmt/status-dataset-context.t.cpp
+++ b/tests/unit/mgmt/status-dataset-context.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2020 Regents of the University of California.
+ * Copyright (c) 2013-2021 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -36,7 +36,6 @@
   {
     Name dataName;
     Block content;
-    time::milliseconds imsFresh;
     bool isFinalBlock;
   };
 
@@ -45,15 +44,12 @@
     : interest(makeInterest("/test/context/interest"))
     , contentBlock(makeStringBlock(tlv::Content, "/test/data/content"))
     , context(*interest,
-              [this] (const Name& dataName, const Block& content,
-                      time::milliseconds imsFresh, bool isFinalBlock) {
-                SendDataArgs args{dataName, content, imsFresh, isFinalBlock};
-                sendDataHistory.push_back(args);
+              [this] (auto&&... args) {
+                sendDataHistory.push_back({std::forward<decltype(args)>(args)...});
               },
-              [this] (const ControlResponse& resp) {
+              [this] (const auto& resp) {
                 sendNackHistory.push_back(resp);
               })
-    , defaultImsFresh(1000_ms)
   {
   }
 
@@ -79,12 +75,11 @@
   }
 
 protected:
-  std::vector<SendDataArgs> sendDataHistory;
-  std::vector<ControlResponse> sendNackHistory;
   shared_ptr<Interest> interest;
   Block contentBlock;
-  mgmt::StatusDatasetContext context;
-  time::milliseconds defaultImsFresh;
+  StatusDatasetContext context;
+  std::vector<SendDataArgs> sendDataHistory;
+  std::vector<ControlResponse> sendNackHistory;
 };
 
 BOOST_AUTO_TEST_SUITE(Mgmt)
@@ -103,95 +98,95 @@
 {
   Name validPrefix = Name(interest->getName()).append("/valid");
   BOOST_CHECK_NO_THROW(context.setPrefix(validPrefix));
-  BOOST_CHECK(context.getPrefix()[-1].isVersion());
   BOOST_CHECK_EQUAL(context.getPrefix().getPrefix(-1), validPrefix);
+  BOOST_CHECK(context.getPrefix()[-1].isVersion());
+
+  // trailing version component is preserved
+  validPrefix.appendVersion(42);
+  BOOST_CHECK_NO_THROW(context.setPrefix(validPrefix));
+  BOOST_CHECK_EQUAL(context.getPrefix(), validPrefix);
 }
 
 BOOST_AUTO_TEST_CASE(SetInvalid)
 {
+  // Interest name is not a prefix of invalidPrefix
   Name invalidPrefix = Name(interest->getName()).getPrefix(-1).append("/invalid");
-  BOOST_CHECK_THROW(context.setPrefix(invalidPrefix), std::invalid_argument);
+  BOOST_CHECK_EXCEPTION(context.setPrefix(invalidPrefix), std::invalid_argument, [] (const auto& e) {
+    return e.what() == "prefix must start with the Interest's name"s;
+  });
+
+  // invalidPrefix contains a segment component
+  invalidPrefix = Name(interest->getName()).appendSegment(1);
+  BOOST_CHECK_EXCEPTION(context.setPrefix(invalidPrefix), std::invalid_argument, [] (const auto& e) {
+    return e.what() == "prefix must not contain a segment component"s;
+  });
 }
 
-BOOST_AUTO_TEST_CASE(SetValidWithAppendCalled)
+BOOST_AUTO_TEST_CASE(SetValidAfterAppend)
 {
   Name validPrefix = Name(interest->getName()).append("/valid");
   context.append(contentBlock);
-  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.setPrefix(validPrefix), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call setPrefix() after append/end/reject"s;
+  });
 }
 
-BOOST_AUTO_TEST_CASE(SetValidWithEndCalled)
+BOOST_AUTO_TEST_CASE(SetValidAfterEnd)
 {
   Name validPrefix = Name(interest->getName()).append("/valid");
   context.end();
-  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.setPrefix(validPrefix), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call setPrefix() after append/end/reject"s;
+  });
 }
 
-BOOST_AUTO_TEST_CASE(SetValidWithRejectCalled)
+BOOST_AUTO_TEST_CASE(SetValidAfterReject)
 {
   Name validPrefix = Name(interest->getName()).append("/valid");
   context.reject();
-  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.setPrefix(validPrefix), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call setPrefix() after append/end/reject"s;
+  });
 }
 
 BOOST_AUTO_TEST_SUITE_END() // Prefix
 
-BOOST_AUTO_TEST_SUITE(Expiry)
-
-BOOST_AUTO_TEST_CASE(GetAndSet)
-{
-  auto period = 9527_ms;
-  BOOST_CHECK_EQUAL(context.getExpiry(), 1000_ms);
-  BOOST_CHECK_EQUAL(context.setExpiry(period).getExpiry(), period);
-}
-
-BOOST_AUTO_TEST_SUITE_END() // Expiry
-
 BOOST_AUTO_TEST_SUITE(Respond)
 
 BOOST_AUTO_TEST_CASE(Basic)
 {
-  BOOST_CHECK_NO_THROW(context.append(contentBlock));
-  BOOST_CHECK(sendDataHistory.empty()); // does not call end yet
+  context.append(contentBlock);
+  BOOST_CHECK(sendDataHistory.empty()); // end() not called yet
 
-  BOOST_CHECK_NO_THROW(context.end());
-
+  context.end();
   BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 1);
-  const auto& args = sendDataHistory[0];
 
+  const auto& args = sendDataHistory[0];
   BOOST_CHECK_EQUAL(args.dataName, makeSegmentName(0));
   BOOST_CHECK_EQUAL(args.content.blockFromValue(), contentBlock);
-  BOOST_CHECK_EQUAL(args.imsFresh, defaultImsFresh);
   BOOST_CHECK_EQUAL(args.isFinalBlock, true);
 }
 
 BOOST_AUTO_TEST_CASE(Large)
 {
-  static Block largeBlock = [] () -> Block {
-    EncodingBuffer encoder;
-    size_t maxBlockSize = MAX_NDN_PACKET_SIZE >> 1;
-    for (size_t i = 0; i < maxBlockSize; ++i) {
-      encoder.prependByte(1);
-    }
-    encoder.prependVarNumber(maxBlockSize);
-    encoder.prependVarNumber(tlv::Content);
-    return encoder.block();
+  const Block largeBlock = [] {
+    Block b(tlv::Content, std::make_shared<const Buffer>(10000));
+    b.encode();
+    return b;
   }();
 
-  BOOST_CHECK_NO_THROW(context.append(largeBlock));
-  BOOST_CHECK_NO_THROW(context.end());
+  context.append(largeBlock);
+  BOOST_CHECK_EQUAL(sendDataHistory.size(), 1);
 
-  // two segments are generated
+  context.end();
   BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 2);
 
-  // check segment0
+  // check segment 0
   BOOST_CHECK_EQUAL(sendDataHistory[0].dataName, makeSegmentName(0));
-  BOOST_CHECK_EQUAL(sendDataHistory[0].imsFresh, defaultImsFresh);
   BOOST_CHECK_EQUAL(sendDataHistory[0].isFinalBlock, false);
 
-  // check segment1
+  // check segment 1
   BOOST_CHECK_EQUAL(sendDataHistory[1].dataName, makeSegmentName(1));
-  BOOST_CHECK_EQUAL(sendDataHistory[1].imsFresh, defaultImsFresh);
   BOOST_CHECK_EQUAL(sendDataHistory[1].isFinalBlock, true);
 
   // check data content
@@ -203,39 +198,34 @@
 
 BOOST_AUTO_TEST_CASE(MultipleSmall)
 {
-  size_t nBlocks = 100;
+  const size_t nBlocks = 100;
   for (size_t i = 0 ; i < nBlocks ; i ++) {
-    BOOST_CHECK_NO_THROW(context.append(contentBlock));
+    context.append(contentBlock);
   }
-  BOOST_CHECK_NO_THROW(context.end());
+  context.end();
 
   // check data to in-memory storage
   BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 1);
   BOOST_CHECK_EQUAL(sendDataHistory[0].dataName, makeSegmentName(0));
-  BOOST_CHECK_EQUAL(sendDataHistory[0].imsFresh, defaultImsFresh);
   BOOST_CHECK_EQUAL(sendDataHistory[0].isFinalBlock, true);
 
   auto contentMultiBlocks = concatenateDataContent();
-  BOOST_CHECK_NO_THROW(contentMultiBlocks.parse());
+  contentMultiBlocks.parse();
   BOOST_CHECK_EQUAL(contentMultiBlocks.elements().size(), nBlocks);
-  for (auto&& element : contentMultiBlocks.elements()) {
+  for (const auto& element : contentMultiBlocks.elements()) {
     BOOST_CHECK_EQUAL(element, contentBlock);
   }
 }
 
 BOOST_AUTO_TEST_SUITE_END() // Respond
 
-BOOST_AUTO_TEST_SUITE(Reject)
-
-BOOST_AUTO_TEST_CASE(Basic)
+BOOST_AUTO_TEST_CASE(Reject)
 {
   BOOST_CHECK_NO_THROW(context.reject());
   BOOST_REQUIRE_EQUAL(sendNackHistory.size(), 1);
   BOOST_CHECK_EQUAL(sendNackHistory[0].getCode(), 400);
 }
 
-BOOST_AUTO_TEST_SUITE_END() // Reject
-
 class AbnormalStateTestFixture
 {
 protected:
@@ -245,7 +235,7 @@
   }
 
 protected:
-  mgmt::StatusDatasetContext context;
+  StatusDatasetContext context;
 };
 
 BOOST_FIXTURE_TEST_SUITE(AbnormalState, AbnormalStateTestFixture)
@@ -254,7 +244,9 @@
 {
   const uint8_t buf[] = {0x82, 0x01, 0x02};
   BOOST_CHECK_NO_THROW(context.append(Block(buf, sizeof(buf))));
-  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.reject(), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call reject() after append/end"s;
+  });
 }
 
 BOOST_AUTO_TEST_CASE(AppendEndReject)
@@ -262,40 +254,51 @@
   const uint8_t buf[] = {0x82, 0x01, 0x02};
   BOOST_CHECK_NO_THROW(context.append(Block(buf, sizeof(buf))));
   BOOST_CHECK_NO_THROW(context.end());
-  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.reject(), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call reject() after append/end"s;
+  });
 }
 
 BOOST_AUTO_TEST_CASE(EndAppend)
 {
   BOOST_CHECK_NO_THROW(context.end());
-  // end, append -> error
   const uint8_t buf[] = {0x82, 0x01, 0x02};
-  BOOST_CHECK_THROW(context.append(Block(buf, sizeof(buf))), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.append(Block(buf, sizeof(buf))), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call append() on a finalized context"s;
+  });
 }
 
 BOOST_AUTO_TEST_CASE(EndEnd)
 {
   BOOST_CHECK_NO_THROW(context.end());
-  BOOST_CHECK_THROW(context.end(), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.end(), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call end() on a finalized context"s;
+  });
 }
 
 BOOST_AUTO_TEST_CASE(EndReject)
 {
   BOOST_CHECK_NO_THROW(context.end());
-  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.reject(), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call reject() after append/end"s;
+  });
 }
 
 BOOST_AUTO_TEST_CASE(RejectAppend)
 {
   BOOST_CHECK_NO_THROW(context.reject());
   const uint8_t buf[] = {0x82, 0x01, 0x02};
-  BOOST_CHECK_THROW(context.append(Block(buf, sizeof(buf))), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.append(Block(buf, sizeof(buf))), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call append() on a finalized context"s;
+  });
 }
 
 BOOST_AUTO_TEST_CASE(RejectEnd)
 {
   BOOST_CHECK_NO_THROW(context.reject());
-  BOOST_CHECK_THROW(context.end(), std::domain_error);
+  BOOST_CHECK_EXCEPTION(context.end(), std::logic_error, [] (const auto& e) {
+    return e.what() == "cannot call end() on a finalized context"s;
+  });
 }
 
 BOOST_AUTO_TEST_SUITE_END() // AbnormalState