mgmt refactoring: ManagerBase

Change-Id: I0710297f352723874d05092d091128b02b3747a2
Refs: #2107
diff --git a/common.hpp b/common.hpp
index bd8f10b..9e44569 100644
--- a/common.hpp
+++ b/common.hpp
@@ -97,6 +97,7 @@
 using ndn::Interest;
 using ndn::Data;
 using ndn::Name;
+using ndn::PartialName;
 using ndn::Exclude;
 using ndn::Link;
 using ndn::Block;
diff --git a/core/config-file.cpp b/core/config-file.cpp
index 9724168..d989455 100644
--- a/core/config-file.cpp
+++ b/core/config-file.cpp
@@ -52,6 +52,25 @@
   // do nothing
 }
 
+bool
+ConfigFile::parseYesNo(const ConfigSection::const_iterator& i,
+                        const std::string& optionName,
+                        const std::string& sectionName)
+{
+  const std::string value = i->second.get_value<std::string>();
+  if (value == "yes") {
+    return true;
+  }
+
+  if (value == "no") {
+    return false;
+  }
+
+  BOOST_THROW_EXCEPTION(ConfigFile::Error("Invalid value for option \"" +
+                                          optionName + "\" in \"" +
+                                          sectionName + "\" section"));
+}
+
 ConfigFile::ConfigFile(UnknownConfigSectionHandler unknownSectionCallback)
   : m_unknownSectionCallback(unknownSectionCallback)
 {
diff --git a/core/config-file.hpp b/core/config-file.hpp
index 9991484..ae9102e 100644
--- a/core/config-file.hpp
+++ b/core/config-file.hpp
@@ -73,6 +73,16 @@
                        const ConfigSection& section,
                        bool isDryRun);
 
+  /** @brief parse a config option that can be either "yes" or "no"
+   *
+   *  @throw ConfigFile::Error value is neither "yes" nor "no"
+   *  @return true if "yes", false if "no"
+   */
+  static bool
+  parseYesNo(const ConfigSection::const_iterator& i,
+             const std::string& optionName,
+             const std::string& sectionName);
+
   /// \brief setup notification of configuration file sections
   void
   addSectionHandler(const std::string& sectionName,
diff --git a/daemon/mgmt/manager-base.cpp b/daemon/mgmt/manager-base.cpp
new file mode 100644
index 0000000..418c3d6
--- /dev/null
+++ b/daemon/mgmt/manager-base.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "manager-base.hpp"
+
+namespace nfd {
+
+using ndn::mgmt::ValidateParameters;
+using ndn::mgmt::Authorization;
+
+ManagerBase::ManagerBase(Dispatcher& dispatcher,
+                         CommandValidator& validator,
+                         const std::string& module)
+  : m_dispatcher(dispatcher)
+  , m_validator(validator)
+  , m_mgmtModuleName(module)
+{
+  m_validator.addSupportedPrivilege(module);
+}
+
+void
+ManagerBase::registerStatusDatasetHandler(const std::string& verb,
+                                          const ndn::mgmt::StatusDatasetHandler& handler)
+{
+  m_dispatcher.addStatusDataset(makeRelPrefix(verb),
+                                ndn::mgmt::makeAcceptAllAuthorization(),
+                                handler);
+}
+
+ndn::mgmt::PostNotification
+ManagerBase::registerNotificationStream(const std::string& verb)
+{
+  return m_dispatcher.addNotificationStream(makeRelPrefix(verb));
+}
+
+void
+ManagerBase::authorize(const Name& prefix, const Interest& interest,
+                       const ndn::mgmt::ControlParameters* params,
+                       ndn::mgmt::AcceptContinuation accept,
+                       ndn::mgmt::RejectContinuation reject)
+{
+  BOOST_ASSERT(params != nullptr);
+  BOOST_ASSERT(typeid(*params) == typeid(ndn::nfd::ControlParameters));
+
+  m_validator.validate(interest,
+                       bind(&ManagerBase::extractRequester, this, interest, accept),
+                       bind([&] { reject(ndn::mgmt::RejectReply::STATUS403); }));
+}
+
+void
+ManagerBase::extractRequester(const Interest& interest,
+                              ndn::mgmt::AcceptContinuation accept)
+{
+  const Name& interestName = interest.getName();
+
+  try {
+    ndn::SignatureInfo sigInfo(interestName.at(ndn::signed_interest::POS_SIG_INFO).blockFromValue());
+    if (!sigInfo.hasKeyLocator() ||
+        sigInfo.getKeyLocator().getType() != ndn::KeyLocator::KeyLocator_Name) {
+      return accept("");
+    }
+
+    accept(sigInfo.getKeyLocator().getName().toUri());
+  }
+  catch (const tlv::Error&) {
+    accept("");
+  }
+}
+
+bool
+ManagerBase::validateParameters(const nfd::ControlCommand& command, const ndn::mgmt::ControlParameters& parameters)
+{
+  BOOST_ASSERT(dynamic_cast<const ControlParameters*>(&parameters) != nullptr);
+
+  try {
+    command.validateRequest(static_cast<const ControlParameters&>(parameters));
+  }
+  catch (const ControlCommand::ArgumentError&) {
+    return false;
+  }
+  return true;
+}
+
+void
+ManagerBase::handleCommand(shared_ptr<nfd::ControlCommand> command,
+                           const ControlCommandHandler& handler,
+                           const Name& prefix, const Interest& interest,
+                           const ndn::mgmt::ControlParameters& params,
+                           ndn::mgmt::CommandContinuation done)
+{
+  BOOST_ASSERT(dynamic_cast<const ControlParameters*>(&params) != nullptr);
+  ControlParameters parameters = static_cast<const ControlParameters&>(params);
+  command->applyDefaultsToRequest(parameters);
+  handler(*command, prefix, interest, parameters, done);
+}
+
+
+} // namespace nfd
diff --git a/daemon/mgmt/manager-base.hpp b/daemon/mgmt/manager-base.hpp
new file mode 100644
index 0000000..988fff1
--- /dev/null
+++ b/daemon/mgmt/manager-base.hpp
@@ -0,0 +1,180 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_MGMT_MANAGER_BASE_HPP
+#define NFD_DAEMON_MGMT_MANAGER_BASE_HPP
+
+#include "common.hpp"
+#include "mgmt/command-validator.hpp"
+
+#include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/management/nfd-control-command.hpp>
+#include <ndn-cxx/management/nfd-control-response.hpp>
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
+
+namespace nfd {
+
+using ndn::mgmt::Dispatcher;
+
+using ndn::nfd::ControlCommand;
+using ndn::nfd::ControlResponse;
+using ndn::nfd::ControlParameters;
+
+/**
+ * @brief a collection of common functions shared by all NFD managers,
+ *        such as communicating with the dispatcher and command validator.
+ */
+class ManagerBase : public noncopyable
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
+  ManagerBase(Dispatcher& dispatcher,
+              CommandValidator& validator,
+              const std::string& module);
+
+PUBLIC_WITH_TESTS_ELSE_PROTECTED: // registrations to the dispatcher
+
+  // difference from mgmt::ControlCommand: accepts nfd::ControlParameters
+  typedef function<void(const ControlCommand& command,
+                        const Name& prefix, const Interest& interest,
+                        const ControlParameters& parameters,
+                        const ndn::mgmt::CommandContinuation done)> ControlCommandHandler;
+
+  template<typename Command>
+  void
+  registerCommandHandler(const std::string& verb,
+                         const ControlCommandHandler& handler);
+
+  void
+  registerStatusDatasetHandler(const std::string& verb,
+                               const ndn::mgmt::StatusDatasetHandler& handler);
+
+  ndn::mgmt::PostNotification
+  registerNotificationStream(const std::string& verb);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // command validation
+  /**
+   * @brief validate a request for ControlCommand.
+   *
+   * This is called by the dispatcher.
+   *
+   * @pre params != null
+   * @pre typeid(*params) == typeid(ndn::nfd::ControlParameters)
+   *
+   * @param prefix the top prefix
+   * @param interest a request for ControlCommand
+   * @param params the parameters for ControlCommand
+   * @param accept callback of successful validation, take the requester string as a argument
+   * @param reject callback of failure in validation, take the action code as a argument
+   */
+  void
+  authorize(const Name& prefix, const Interest& interest,
+            const ndn::mgmt::ControlParameters* params,
+            ndn::mgmt::AcceptContinuation accept,
+            ndn::mgmt::RejectContinuation reject);
+
+  /**
+   * @brief extract a requester from a ControlCommand request
+   *
+   * This is called after the signature is validated.
+   *
+   * @param interest a request for ControlCommand
+   * @param accept callback of successful validation, take the requester string as a argument
+   */
+  void
+  extractRequester(const Interest& interest,
+                   ndn::mgmt::AcceptContinuation accept);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE: // helpers
+  /**
+   * @brief validate the @p parameters for a given @p command
+   *
+   * @param parameters the original ControlParameters
+   *
+   * @return whether the original ControlParameters can be validated
+   */
+  static bool
+  validateParameters(const nfd::ControlCommand& command,
+                     const ndn::mgmt::ControlParameters& parameters);
+
+  /** @brief Handle control command
+   */
+  static void
+  handleCommand(shared_ptr<nfd::ControlCommand> command,
+                const ControlCommandHandler& handler,
+                const Name& prefix, const Interest& interest,
+                const ndn::mgmt::ControlParameters& params,
+                ndn::mgmt::CommandContinuation done);
+
+  /**
+   * @brief generate the relative prefix for a handler,
+   *        by appending the verb name to the module name.
+   *
+   * @param verb the verb name
+   *
+   * @return the generated relative prefix
+   */
+  PartialName
+  makeRelPrefix(const std::string& verb);
+
+private:
+  Dispatcher&       m_dispatcher;
+  CommandValidator& m_validator;
+  std::string       m_mgmtModuleName;
+};
+
+inline PartialName
+ManagerBase::makeRelPrefix(const std::string& verb)
+{
+  return PartialName(m_mgmtModuleName).append(verb);
+}
+
+template<typename Command>
+inline void
+ManagerBase::registerCommandHandler(const std::string& verb,
+                                    const ControlCommandHandler& handler)
+{
+  auto command = make_shared<Command>();
+
+  m_dispatcher.addControlCommand<ControlParameters>(
+    makeRelPrefix(verb),
+    bind(&ManagerBase::authorize, this, _1, _2, _3, _4, _5),
+    bind(&ManagerBase::validateParameters, cref(*command), _1),
+    bind(&ManagerBase::handleCommand, command, handler, _1, _2, _3, _4));
+}
+
+} // namespace nfd
+
+#endif // NFD_DAEMON_MGMT_MANAGER_BASE_HPP
diff --git a/daemon/nfd.cpp b/daemon/nfd.cpp
index 66ab514..741e495 100644
--- a/daemon/nfd.cpp
+++ b/daemon/nfd.cpp
@@ -39,6 +39,9 @@
 // #include "mgmt/status-server.hpp"
 #include "mgmt/general-config-section.hpp"
 #include "mgmt/tables-config-section.hpp"
+#include "mgmt/command-validator.hpp"
+
+#include <ndn-cxx/mgmt/dispatcher.hpp>
 
 namespace nfd {
 
@@ -131,6 +134,9 @@
   m_internalFace = make_shared<InternalFace>();
   m_forwarder->getFaceTable().addReserved(m_internalFace, FACEID_INTERNAL_FACE);
   m_internalClientFace = makeInternalClientFace(m_internalFace, m_keyChain);
+  m_dispatcher.reset(new ndn::mgmt::Dispatcher(*m_internalClientFace, m_keyChain));
+
+  m_validator.reset(new CommandValidator());
 
   // m_fibManager.reset(new FibManager(m_forwarder->getFib(),
   //                                   bind(&Forwarder::getFace, m_forwarder.get(), _1),
@@ -153,9 +159,7 @@
                                    m_forwarder->getMeasurements());
   tablesConfig.setConfigFile(config);
 
-  // m_internalFace->getValidator().setConfigFile(config);
-
-  m_forwarder->getFaceTable().addReserved(m_internalFace, FACEID_INTERNAL_FACE);
+  m_validator->setConfigFile(config);
 
   // m_faceManager->setConfigFile(config);
 
@@ -172,8 +176,10 @@
   tablesConfig.ensureTablesAreConfigured();
 
   // add FIB entry for NFD Management Protocol
-  shared_ptr<fib::Entry> entry = m_forwarder->getFib().insert("/localhost/nfd").first;
+  Name topPrefix("/localhost/nfd");
+  auto entry = m_forwarder->getFib().insert(topPrefix).first;
   entry->addNextHop(m_internalFace, 0);
+  m_dispatcher->addTopPrefix(topPrefix, false);
 }
 
 void
@@ -196,8 +202,8 @@
 
   tablesConfig.setConfigFile(config);
 
-  // m_internalFace->getValidator().setConfigFile(config);
   // m_faceManager->setConfigFile(config);
+  m_validator->setConfigFile(config);
 
   if (!m_configFile.empty()) {
     config.parse(m_configFile, false);
diff --git a/daemon/nfd.hpp b/daemon/nfd.hpp
index 1240835..7dc7148 100644
--- a/daemon/nfd.hpp
+++ b/daemon/nfd.hpp
@@ -33,6 +33,14 @@
 #include <ndn-cxx/security/key-chain.hpp>
 #include <ndn-cxx/util/network-monitor.hpp>
 
+namespace ndn {
+namespace mgmt {
+
+class Dispatcher;
+
+}
+}
+
 namespace nfd {
 
 class Forwarder;
@@ -42,6 +50,7 @@
 class StrategyChoiceManager;
 class StatusServer;
 class InternalClientFace;
+class CommandValidator;
 
 /**
  * \brief Class representing NFD instance
@@ -101,6 +110,9 @@
   ndn::KeyChain&                    m_keyChain;
   shared_ptr<InternalFace>          m_internalFace;
   shared_ptr<InternalClientFace>    m_internalClientFace;
+  unique_ptr<CommandValidator>      m_validator;
+
+  unique_ptr<ndn::mgmt::Dispatcher> m_dispatcher;
   // unique_ptr<FibManager>            m_fibManager;
   // unique_ptr<FaceManager>           m_faceManager;
   // unique_ptr<StrategyChoiceManager> m_strategyChoiceManager;
diff --git a/tests/daemon/mgmt/manager-base.t.cpp b/tests/daemon/mgmt/manager-base.t.cpp
new file mode 100644
index 0000000..f3ea152
--- /dev/null
+++ b/tests/daemon/mgmt/manager-base.t.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mgmt/manager-base.hpp"
+#include "manager-common-fixture.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/management/nfd-control-command.hpp>
+
+namespace nfd {
+namespace tests {
+
+NFD_LOG_INIT("ManagerBaseTest");
+
+class TestCommandVoidParameters : public ndn::nfd::ControlCommand
+{
+public:
+  TestCommandVoidParameters()
+    : ndn::nfd::ControlCommand("test-module", "test-void-parameters")
+  {
+  }
+};
+
+class TestCommandRequireName : public ndn::nfd::ControlCommand
+{
+public:
+  TestCommandRequireName()
+    : ndn::nfd::ControlCommand("test-module", "test-require-name")
+  {
+    m_requestValidator.required(ndn::nfd::CONTROL_PARAMETER_NAME);
+  }
+};
+
+class ManagerBaseFixture : public ManagerCommonFixture
+{
+public:
+  ManagerBaseFixture()
+    : m_manager(m_dispatcher, m_validator, "test-module")
+  {
+  }
+
+protected:
+  ManagerBase m_manager;
+};
+
+BOOST_FIXTURE_TEST_SUITE(MgmtManagerBase, ManagerBaseFixture)
+
+BOOST_AUTO_TEST_CASE(AddSupportedPrivilegeInConstructor)
+{
+  BOOST_CHECK_NO_THROW(m_validator.addSupportedPrivilege("other-module"));
+  // test-module has already been added by the constructor of ManagerBase
+  BOOST_CHECK_THROW(m_validator.addSupportedPrivilege("test-module"), CommandValidator::Error);
+}
+
+BOOST_AUTO_TEST_CASE(RegisterCommandHandler)
+{
+  bool wasCommandHandlerCalled = false;
+  auto handler = bind([&] { wasCommandHandlerCalled = true; });
+
+  m_manager.registerCommandHandler<TestCommandVoidParameters>("test-void", handler);
+  m_manager.registerCommandHandler<TestCommandRequireName>("test-require-name", handler);
+  setTopPrefixAndPrivilege("/localhost/nfd", "test-module");
+
+  auto testRegisterCommandHandler = [&wasCommandHandlerCalled, this] (const Name& commandName) {
+    wasCommandHandlerCalled = false;
+    receiveInterest(makeControlCommandRequest(commandName, ControlParameters()));
+  };
+
+  testRegisterCommandHandler("/localhost/nfd/test-module/test-void");
+  BOOST_CHECK(wasCommandHandlerCalled);
+
+  testRegisterCommandHandler("/localhost/nfd/test-module/test-require-name");
+  BOOST_CHECK(!wasCommandHandlerCalled);
+}
+
+BOOST_AUTO_TEST_CASE(RegisterStatusDataset)
+{
+  bool isStatusDatasetCalled = false;
+  auto handler = bind([&] { isStatusDatasetCalled = true; });
+
+  m_manager.registerStatusDatasetHandler("test-status", handler);
+  m_dispatcher.addTopPrefix("/localhost/nfd");
+  advanceClocks(time::milliseconds(1));
+
+  receiveInterest(makeInterest("/localhost/nfd/test-module/test-status"));
+  BOOST_CHECK(isStatusDatasetCalled);
+}
+
+BOOST_AUTO_TEST_CASE(RegisterNotificationStream)
+{
+  auto post = m_manager.registerNotificationStream("test-notification");
+  m_dispatcher.addTopPrefix("/localhost/nfd");
+  advanceClocks(time::milliseconds(1));
+
+  post(Block("\x82\x01\x02", 3));
+  advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(m_responses.size(), 1);
+  BOOST_CHECK_EQUAL(m_responses[0].getName(),
+                    Name("/localhost/nfd/test-module/test-notification/%FE%00"));
+}
+
+BOOST_AUTO_TEST_CASE(CommandAuthorization)
+{
+  bool didAcceptCallbackFire = false;
+  bool didRejectCallbackFire = false;
+  auto testAuthorization = [&] {
+    didAcceptCallbackFire = false;
+    didRejectCallbackFire = false;
+
+    auto command = makeControlCommandRequest("/localhost/nfd/test-module/test-verb",
+                                             ControlParameters());
+    ndn::nfd::ControlParameters params;
+    m_manager.authorize("/top/prefix", *command, &params,
+                        bind([&] { didAcceptCallbackFire = true; }),
+                        bind([&] { didRejectCallbackFire = true; }));
+  };
+
+  testAuthorization();
+  BOOST_CHECK(!didAcceptCallbackFire);
+  BOOST_CHECK(didRejectCallbackFire);
+
+  m_validator.addInterestRule("^<localhost><nfd><test-module>", *m_certificate);
+  testAuthorization();
+  BOOST_CHECK(didAcceptCallbackFire);
+  BOOST_CHECK(!didRejectCallbackFire);
+}
+
+BOOST_AUTO_TEST_CASE(ExtractRequester)
+{
+  std::string requesterName;
+  auto testAccept = [&] (const std::string& requester) { requesterName = requester; };
+
+  auto unsignedCommand = makeInterest("/test/interest/unsigned");
+  auto signedCommand = makeControlCommandRequest("/test/interest/signed", ControlParameters());
+
+  m_manager.extractRequester(*unsignedCommand, testAccept);
+  BOOST_CHECK(requesterName.empty());
+
+  requesterName = "";
+  m_manager.extractRequester(*signedCommand, testAccept);
+  auto keyLocator = m_keyChain.getDefaultCertificateNameForIdentity(m_identityName).getPrefix(-1);
+  BOOST_CHECK_EQUAL(requesterName, keyLocator.toUri());
+}
+
+BOOST_AUTO_TEST_CASE(ValidateParameters)
+{
+  ControlParameters params;
+  TestCommandVoidParameters commandVoidParams;
+  TestCommandRequireName commandRequireName;
+
+  BOOST_CHECK_EQUAL(ManagerBase::validateParameters(commandVoidParams, params), true); // succeeds
+  BOOST_CHECK_EQUAL(ManagerBase::validateParameters(commandRequireName, params), false); // fails
+
+  params.setName("test-name");
+  BOOST_CHECK_EQUAL(ManagerBase::validateParameters(commandRequireName, params), true); // succeeds
+}
+
+BOOST_AUTO_TEST_CASE(MakeRelPrefix)
+{
+  auto generatedRelPrefix = m_manager.makeRelPrefix("test-verb");
+  BOOST_CHECK_EQUAL(generatedRelPrefix, PartialName("/test-module/test-verb"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/manager-common-fixture.cpp b/tests/daemon/mgmt/manager-common-fixture.cpp
new file mode 100644
index 0000000..b03449e
--- /dev/null
+++ b/tests/daemon/mgmt/manager-common-fixture.cpp
@@ -0,0 +1,193 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "manager-common-fixture.hpp"
+
+namespace nfd {
+namespace tests {
+
+ManagerCommonFixture::ManagerCommonFixture()
+  : m_face(ndn::util::makeDummyClientFace(UnitTestTimeFixture::g_io, {true, true}))
+  , m_dispatcher(*m_face, m_keyChain, ndn::security::SigningInfo())
+  , m_responses(m_face->sentDatas)
+  , m_identityName("/unit-test/ManagerCommonFixture/identity")
+  , m_certificate(m_keyChain.getCertificate(m_keyChain.createIdentity(m_identityName)))
+{
+}
+
+void
+ManagerCommonFixture::setTopPrefixAndPrivilege(const Name& topPrefix,
+                                               const std::string& privilege)
+{
+  m_dispatcher.addTopPrefix(topPrefix); // such that all filters are added
+  advanceClocks(time::milliseconds(1));
+
+  std::string regex("^");
+  for (auto component : topPrefix) {
+    regex += "<" + component.toUri() + ">";
+  }
+  m_validator.addInterestRule(regex + "<" + privilege + ">", *m_certificate);
+}
+
+shared_ptr<Interest>
+ManagerCommonFixture::makeControlCommandRequest(Name commandName,
+                                                const ControlParameters& parameters,
+                                                const InterestHandler& beforeSigning)
+{
+  shared_ptr<Interest> command = makeInterest(commandName.append(parameters.wireEncode()));
+
+  if (beforeSigning != nullptr) {
+    beforeSigning(command);
+  }
+
+  m_keyChain.sign(*command, ndn::security::SigningInfo(ndn::security::SigningInfo::SIGNER_TYPE_ID,
+                                                       m_identityName));
+  return command;
+}
+
+void
+ManagerCommonFixture::receiveInterest(shared_ptr<Interest> interest)
+{
+  m_face->receive<Interest>(*interest);
+  advanceClocks(time::milliseconds(1));
+}
+
+ControlResponse
+ManagerCommonFixture::makeResponse(uint32_t code, const std::string& text,
+                                   const ControlParameters& parameters)
+{
+  return ControlResponse(code, text).setBody(parameters.wireEncode());
+}
+
+ManagerCommonFixture::CheckResponseResult
+ManagerCommonFixture::checkResponse(size_t idx,
+                                    const Name& expectedName,
+                                    const ControlResponse& expectedResponse,
+                                    int expectedContentType /*= -1*/)
+{
+  Data data;
+  try {
+    data = m_responses.at(idx);
+  }
+  catch (const std::out_of_range&) {
+    return CheckResponseResult::OUT_OF_BOUNDARY;
+  }
+
+  if (data.getName() != expectedName) {
+    return CheckResponseResult::WRONG_NAME;
+  }
+
+  if (expectedContentType != -1 &&
+      data.getContentType() != static_cast<uint32_t>(expectedContentType)) {
+    return CheckResponseResult::WRONG_CONTENT_TYPE;
+  }
+
+  ControlResponse response;
+  try {
+    response.wireDecode(data.getContent().blockFromValue());
+  }
+  catch (const tlv::Error&) {
+    return CheckResponseResult::INVALID_RESPONSE;
+  }
+
+  if (response.getCode() != expectedResponse.getCode()) {
+    return CheckResponseResult::WRONG_CODE;
+  }
+
+  if (response.getText() != expectedResponse.getText()) {
+    return CheckResponseResult::WRONG_TEXT;
+  }
+
+  const Block& body = response.getBody();
+  const Block& expectedBody = expectedResponse.getBody();
+  if (body.value_size() != expectedBody.value_size()) {
+    return CheckResponseResult::WRONG_BODY_SIZE;
+  }
+  if (body.value_size() > 0 && memcmp(body.value(), expectedBody.value(), body.value_size()) != 0) {
+    return CheckResponseResult::WRONG_BODY_VALUE;
+  }
+
+  return CheckResponseResult::OK;
+}
+
+Block
+ManagerCommonFixture::concatenateResponses(size_t startIndex, size_t nResponses)
+{
+  size_t endIndex = startIndex + nResponses; // not included
+  if (nResponses == startIndex || endIndex > m_responses.size()) {
+    endIndex = m_responses.size();
+  }
+
+  ndn::EncodingBuffer encoder;
+  size_t valueLength = 0;
+  for (size_t i = startIndex; i < endIndex ; i ++) {
+    valueLength += encoder.appendByteArray(m_responses[i].getContent().value(),
+                                           m_responses[i].getContent().value_size());
+  }
+  encoder.prependVarNumber(valueLength);
+  encoder.prependVarNumber(tlv::Content);
+  return encoder.block();
+}
+
+std::ostream&
+operator<<(std::ostream &os, const ManagerCommonFixture::CheckResponseResult& result)
+{
+  switch (result) {
+  case ManagerCommonFixture::CheckResponseResult::OK:
+    os << "OK";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::OUT_OF_BOUNDARY:
+    os << "OUT_OF_BOUNDARY";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::WRONG_NAME:
+    os << "WRONG_NAME";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::WRONG_CONTENT_TYPE:
+    os << "WRONG_CONTENT_TYPE";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::INVALID_RESPONSE:
+    os << "INVALID_RESPONSE";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::WRONG_CODE:
+    os << "WRONG_CODE";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::WRONG_TEXT:
+    os << "WRONG_TEXT";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::WRONG_BODY_SIZE:
+    os << "WRONG_BODY_SIZE";
+    break;
+  case ManagerCommonFixture::CheckResponseResult::WRONG_BODY_VALUE:
+    os << "WRONG_BODY_VALUE";
+    break;
+  default:
+    break;
+  };
+
+  return os;
+}
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/manager-common-fixture.hpp b/tests/daemon/mgmt/manager-common-fixture.hpp
new file mode 100644
index 0000000..ffdb579
--- /dev/null
+++ b/tests/daemon/mgmt/manager-common-fixture.hpp
@@ -0,0 +1,163 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TESTS_NFD_MGMT_MANAGER_COMMON_HPP
+#define NFD_TESTS_NFD_MGMT_MANAGER_COMMON_HPP
+
+#include "tests/test-common.hpp"
+#include "tests/identity-management-fixture.hpp"
+#include "mgmt/manager-base.hpp"
+#include "fw/forwarder.hpp"
+
+#include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+namespace nfd {
+namespace tests {
+
+/**
+ * @brief a collection of common functions shared by all manager's testing fixtures.
+ */
+class ManagerCommonFixture : public UnitTestTimeFixture
+                           , public IdentityManagementFixture
+{
+public: // initialize
+  ManagerCommonFixture();
+
+  /**
+   * @brief set topPrefix to the dispatcher and configure an interest rule for the module.
+   *
+   * after setting @param topPrefix, call advanceClocks to ensure all added filters take effects.
+   *
+   * @param topPrefix top prefix for the dispatcher
+   * @param privilege the module name
+   */
+  void
+  setTopPrefixAndPrivilege(const Name& topPrefix, const std::string& privilege);
+
+public: // test
+  typedef std::function<void(shared_ptr<Interest> interest)> InterestHandler;
+
+  /**
+   * @brief create a ControlCommand request
+   *
+   * step1: append the @param parameters to the @param commandName and make an Interest.
+   * step2: call @param beforeSinging to do something with the Interest.
+   * step3: sign the generated command
+   *
+   * @param commandName the command name
+   * @param parameters the ControlParameters
+   * @param beforeSigning a callback that can modify the Interest before it's signed
+   *
+   * @return the signed ControlCommand request
+   */
+  shared_ptr<Interest>
+  makeControlCommandRequest(Name commandName,
+                            const ControlParameters& parameters,
+                            const InterestHandler& beforeSigning = nullptr);
+
+  /**
+   * @brief cause management to receive an Interest
+   *
+   * call DummyClientFace::receive to receive Interest and then call advanceClocks to ensure
+   * the Interest dispatched
+   *
+   * @param interest the Interest to receive
+   */
+  void
+  receiveInterest(shared_ptr<Interest> interest);
+
+public: // verify
+  ControlResponse
+  makeResponse(uint32_t code, const std::string& text,
+               const ControlParameters& parameters);
+
+  enum class CheckResponseResult
+  {
+    OK,
+    OUT_OF_BOUNDARY,
+    WRONG_NAME,
+    WRONG_CONTENT_TYPE,
+    INVALID_RESPONSE,
+    WRONG_CODE,
+    WRONG_TEXT,
+    WRONG_BODY_SIZE,
+    WRONG_BODY_VALUE
+   };
+
+  /**
+   * @brief check a specified response data with the expected ControlResponse
+   *
+   * @param idx the index of the specified Data in m_responses
+   * @param expectedDataName the expected name of this Data
+   * @param expectedResponse the expected ControlResponse
+   * @param expectedContentType the expected content type of this Data, use -1 to skip this check
+   *
+   * @retval OK the response at the specified index can be decoded from the response data,
+   *            and its code, text and response body are all matched with the expected response
+   * @retval OUT_OF_BOUNDARY the specified index out of boundary
+   * @retval WRONG_NAME the name of the specified response data does not match
+   * @retval WRONG_CONTENT_TYPE the content type of the specified response data does not match
+   * @retval INVALID_RESPONSE the data name matches but it fails in decoding a ControlResponse from
+   *                          the content of the specified response data
+   * @retval WRONG_CODE a valid ControlResponse can be decoded but has a wrong code
+   * @retval WRONG_TEXT a valid ControlResponse can be decoded but has a wrong text
+   * @retval WRONG_BODY_SIZE the body size of decoded ControlResponse does not match
+   * @retval WRONT_BODY_VALUE the body value of decoded ControlResponse does not match
+   */
+  CheckResponseResult
+  checkResponse(size_t idx,
+                const Name& expectedName,
+                const ControlResponse& expectedResponse,
+                int expectedContentType = -1);
+
+  /**
+   * @brief concatenate specified response Data into a single block
+   *
+   * @param startIndex the start index in m_responses
+   * @param nResponses the number of response to concatenate
+   *
+   * @return the generated block
+   */
+  Block
+  concatenateResponses(size_t startIndex = 0, size_t nResponses = 0);
+
+protected:
+  shared_ptr<ndn::util::DummyClientFace> m_face;
+  ndn::mgmt::Dispatcher                  m_dispatcher;
+  CommandValidator                       m_validator;
+  Forwarder                              m_forwarder;
+  std::vector<Data>&                     m_responses; // a reference of m_face->sentDatas
+  Name                                   m_identityName; // the identity used to sign request
+  shared_ptr<ndn::IdentityCertificate>   m_certificate; // the certificate used to sign request
+};
+
+std::ostream&
+operator<<(std::ostream &os, const ManagerCommonFixture::CheckResponseResult& result);
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NFD_MGMT_MANAGER_COMMON_HPP
diff --git a/tests/daemon/mgmt/validation-common.cpp b/tests/daemon/mgmt/validation-common.cpp
deleted file mode 100644
index 6065c27..0000000
--- a/tests/daemon/mgmt/validation-common.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014  Regents of the University of California,
- *                     Arizona Board of Regents,
- *                     Colorado State University,
- *                     University Pierre & Marie Curie, Sorbonne University,
- *                     Washington University in St. Louis,
- *                     Beijing Institute of Technology
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#include "validation-common.hpp"
-
-#include <boost/test/unit_test.hpp>
-
-namespace nfd {
-namespace tests {
-
-const Name CommandIdentityGlobalFixture::s_identityName("/unit-test/CommandFixture/id");
-shared_ptr<ndn::IdentityCertificate> CommandIdentityGlobalFixture::s_certificate;
-
-CommandIdentityGlobalFixture::CommandIdentityGlobalFixture()
-{
-  BOOST_ASSERT(!static_cast<bool>(s_certificate));
-  s_certificate = m_keys.getCertificate(m_keys.createIdentity(s_identityName));
-}
-
-CommandIdentityGlobalFixture::~CommandIdentityGlobalFixture()
-{
-  s_certificate.reset();
-  m_keys.deleteIdentity(s_identityName);
-}
-
-BOOST_GLOBAL_FIXTURE(CommandIdentityGlobalFixture)
-
-} // namespace tests
-} // namespace nfd
diff --git a/tests/daemon/mgmt/validation-common.hpp b/tests/daemon/mgmt/validation-common.hpp
deleted file mode 100644
index f8b1367..0000000
--- a/tests/daemon/mgmt/validation-common.hpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014  Regents of the University of California,
- *                     Arizona Board of Regents,
- *                     Colorado State University,
- *                     University Pierre & Marie Curie, Sorbonne University,
- *                     Washington University in St. Louis,
- *                     Beijing Institute of Technology
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- **/
-
-#ifndef NFD_TESTS_NFD_MGMT_VALIDATION_COMMON_HPP
-#define NFD_TESTS_NFD_MGMT_VALIDATION_COMMON_HPP
-
-#include "common.hpp"
-#include <ndn-cxx/util/command-interest-generator.hpp>
-
-namespace nfd {
-namespace tests {
-
-// class ValidatedManagementFixture
-// {
-// public:
-//   ValidatedManagementFixture()
-//     : m_validator(make_shared<ndn::CommandInterestValidator>())
-//   {
-//   }
-
-//   virtual
-//   ~ValidatedManagementFixture()
-//   {
-//   }
-
-// protected:
-//   shared_ptr<ndn::CommandInterestValidator> m_validator;
-// };
-
-/// a global fixture that holds the identity for CommandFixture
-class CommandIdentityGlobalFixture
-{
-public:
-  CommandIdentityGlobalFixture();
-
-  ~CommandIdentityGlobalFixture();
-
-  static const Name& getIdentityName()
-  {
-    return s_identityName;
-  }
-
-  static shared_ptr<ndn::IdentityCertificate> getCertificate()
-  {
-    BOOST_ASSERT(static_cast<bool>(s_certificate));
-    return s_certificate;
-  }
-
-private:
-  ndn::KeyChain m_keys;
-  static const Name s_identityName;
-  static shared_ptr<ndn::IdentityCertificate> s_certificate;
-};
-
-template<typename T>
-class CommandFixture : public T
-{
-public:
-  virtual
-  ~CommandFixture()
-  {
-  }
-
-  void
-  generateCommand(Interest& interest)
-  {
-    m_generator.generateWithIdentity(interest, getIdentityName());
-  }
-
-  const Name&
-  getIdentityName() const
-  {
-    return CommandIdentityGlobalFixture::getIdentityName();
-  }
-
-protected:
-  CommandFixture()
-    : m_certificate(CommandIdentityGlobalFixture::getCertificate())
-  {
-  }
-
-protected:
-  shared_ptr<ndn::IdentityCertificate> m_certificate;
-  ndn::CommandInterestGenerator m_generator;
-};
-
-template <typename T>
-class UnauthorizedCommandFixture : public CommandFixture<T>
-{
-public:
-  UnauthorizedCommandFixture()
-  {
-  }
-
-  virtual
-  ~UnauthorizedCommandFixture()
-  {
-  }
-};
-
-} // namespace tests
-} // namespace nfd
-
-#endif // NFD_TESTS_NFD_MGMT_VALIDATION_COMMON_HPP