management: convert nfd::Controller to use ControlCommand

refs #1397

Change-Id: I4106c167e15b7cf4951687b3d18c4807c334d502
diff --git a/src/management/nfd-control-command.hpp b/src/management/nfd-control-command.hpp
index 69407c0..168daec 100644
--- a/src/management/nfd-control-command.hpp
+++ b/src/management/nfd-control-command.hpp
@@ -16,7 +16,7 @@
 /** \brief base class of NFD ControlCommand
  *  \sa http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
  */
-class ControlCommand
+class ControlCommand : noncopyable
 {
 public:
   /** \brief represents an error in ControlParameters
diff --git a/src/management/nfd-control-parameters.hpp b/src/management/nfd-control-parameters.hpp
index 836369b..c92a23f 100644
--- a/src/management/nfd-control-parameters.hpp
+++ b/src/management/nfd-control-parameters.hpp
@@ -14,8 +14,14 @@
 namespace nfd {
 
 class ControlParameters;
+/** \deprecated use ControlParameters instead
+ */
 typedef ControlParameters FaceManagementOptions;
+/** \deprecated use ControlParameters instead
+ */
 typedef ControlParameters FibManagementOptions;
+/** \deprecated use ControlParameters instead
+ */
 typedef ControlParameters StrategyChoiceOptions;
 
 enum ControlParameterField {
@@ -42,7 +48,11 @@
   LOCAL_CONTROL_FEATURE_NEXT_HOP_FACE_ID = 2
 };
 
-class ControlParameters {
+/** \brief represents parameters in a ControlCommand request or response
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/ControlCommand
+ */
+class ControlParameters
+{
 public:
   class Error : public Tlv::Error
   {
diff --git a/src/management/nfd-controller.cpp b/src/management/nfd-controller.cpp
index 19e98f8..5a7e397 100644
--- a/src/management/nfd-controller.cpp
+++ b/src/management/nfd-controller.cpp
@@ -4,13 +4,7 @@
  * See COPYING for copyright and distribution information.
  */
 
-#include "common.hpp"
-#include "../face.hpp"
-
 #include "nfd-controller.hpp"
-#include "nfd-fib-management-options.hpp"
-#include "nfd-face-management-options.hpp"
-#include "nfd-strategy-choice-options.hpp"
 #include "nfd-control-response.hpp"
 
 namespace ndn {
@@ -22,11 +16,70 @@
 }
 
 void
+Controller::processCommandResponse(const Data& data,
+                                   const shared_ptr<ControlCommand>& command,
+                                   const CommandSucceedCallback& onSuccess,
+                                   const CommandFailCallback& onFailure)
+{
+  /// \todo verify Data signature
+
+  const uint32_t serverErrorCode = 500;
+
+  ControlResponse response;
+  try {
+    response.wireDecode(data.getContent().blockFromValue());
+  }
+  catch (ndn::Tlv::Error& e) {
+    if (static_cast<bool>(onFailure))
+      onFailure(serverErrorCode, e.what());
+    return;
+  }
+
+  uint32_t code = response.getCode();
+  const uint32_t errorCodeLowerBound = 400;
+  if (code >= errorCodeLowerBound) {
+    if (static_cast<bool>(onFailure))
+      onFailure(code, response.getText());
+    return;
+  }
+
+  ControlParameters parameters;
+  try {
+    parameters.wireDecode(response.getBody());
+  }
+  catch (ndn::Tlv::Error& e) {
+    if (static_cast<bool>(onFailure))
+      onFailure(serverErrorCode, e.what());
+    return;
+  }
+
+  try {
+    command->validateResponse(parameters);
+  }
+  catch (ControlCommand::ArgumentError& e) {
+    if (static_cast<bool>(onFailure))
+      onFailure(serverErrorCode, e.what());
+    return;
+  }
+
+  onSuccess(parameters);
+}
+
+
+void
 Controller::selfRegisterPrefix(const Name& prefixToRegister,
                                const SuccessCallback& onSuccess,
                                const FailCallback&    onFail)
 {
-  fibAddNextHop(prefixToRegister, 0, 0, bind(onSuccess), onFail);
+  const uint32_t selfFaceId = 0;
+
+  ControlParameters parameters;
+  parameters.setName(prefixToRegister)
+            .setFaceId(selfFaceId);
+
+  this->start<FibAddNextHopCommand>(parameters,
+                                    bind(onSuccess),
+                                    bind(onFail, _2));
 }
 
 void
@@ -34,7 +87,15 @@
                                  const SuccessCallback& onSuccess,
                                  const FailCallback&    onFail)
 {
-  fibRemoveNextHop(prefixToDeRegister, 0, bind(onSuccess), onFail);
+  const uint32_t selfFaceId = 0;
+
+  ControlParameters parameters;
+  parameters.setName(prefixToDeRegister)
+            .setFaceId(selfFaceId);
+
+  this->start<FibRemoveNextHopCommand>(parameters,
+                                       bind(onSuccess),
+                                       bind(onFail, _2));
 }
 
 void
@@ -42,12 +103,16 @@
                           const FibCommandSucceedCallback& onSuccess,
                           const FailCallback& onFail)
 {
-  startFibCommand("add-nexthop",
-                  FibManagementOptions()
-                    .setName(prefix)
-                    .setFaceId(faceId)
-                    .setCost(cost),
-                  onSuccess, onFail);
+  BOOST_ASSERT(cost >= 0);
+
+  ControlParameters parameters;
+  parameters.setName(prefix)
+            .setFaceId(faceId)
+            .setCost(static_cast<uint64_t>(cost));
+
+  this->start<FibAddNextHopCommand>(parameters,
+                                    onSuccess,
+                                    bind(onFail, _2));
 }
 
 void
@@ -55,11 +120,13 @@
                              const FibCommandSucceedCallback& onSuccess,
                              const FailCallback& onFail)
 {
-  startFibCommand("remove-nexthop",
-                  FibManagementOptions()
-                    .setName(prefix)
-                    .setFaceId(faceId),
-                  onSuccess, onFail);
+  ControlParameters parameters;
+  parameters.setName(prefix)
+            .setFaceId(faceId);
+
+  this->start<FibRemoveNextHopCommand>(parameters,
+                                       onSuccess,
+                                       bind(onFail, _2));
 }
 
 void
@@ -68,41 +135,19 @@
                             const FibCommandSucceedCallback& onSuccess,
                             const FailCallback& onFail)
 {
-  Name fibCommandInterestName("/localhost/nfd/fib");
-  fibCommandInterestName
-    .append(command)
-    .append(options.wireEncode());
-
-  Interest fibCommandInterest(fibCommandInterestName);
-  m_commandInterestGenerator.generate(fibCommandInterest);
-
-  m_face.expressInterest(fibCommandInterest,
-                         bind(&Controller::processFibCommandResponse, this, _2,
-                              onSuccess, onFail),
-                         bind(onFail, "Command Interest timed out"));
-}
-
-void
-Controller::processFibCommandResponse(Data& data,
-                                      const FibCommandSucceedCallback& onSuccess,
-                                      const FailCallback& onFail)
-{
-  /// \todo Add validation of incoming Data
-
-  try
-    {
-      ControlResponse response(data.getContent().blockFromValue());
-      if (response.getCode() != 200)
-        return onFail(response.getText());
-
-      FibManagementOptions options(response.getBody());
-      return onSuccess(options);
-    }
-  catch(ndn::Tlv::Error& e)
-    {
-      if (static_cast<bool>(onFail))
-        return onFail(e.what());
-    }
+  if (command == "add-nexthop") {
+    this->start<FibAddNextHopCommand>(options,
+                                      onSuccess,
+                                      bind(onFail, _2));
+  }
+  else if (command == "remove-nexthop") {
+    this->start<FibRemoveNextHopCommand>(options,
+                                         onSuccess,
+                                         bind(onFail, _2));
+  }
+  else {
+    onFail("unknown command");
+  }
 }
 
 void
@@ -111,40 +156,19 @@
                              const FaceCommandSucceedCallback& onSuccess,
                              const FailCallback& onFail)
 {
-  Name faceCommandInterestName("/localhost/nfd/faces");
-  faceCommandInterestName
-    .append(command)
-    .append(options.wireEncode());
-
-  Interest faceCommandInterest(faceCommandInterestName);
-  m_commandInterestGenerator.generate(faceCommandInterest);
-
-  m_face.expressInterest(faceCommandInterest,
-                         bind(&Controller::processFaceCommandResponse, this, _2,
-                              onSuccess, onFail),
-                         bind(onFail, "Command Interest timed out"));
-}
-  
-void
-Controller::processFaceCommandResponse(Data& data,
-                                       const FaceCommandSucceedCallback& onSuccess,
-                                       const FailCallback& onFail)
-{
-  /// \todo Add validation of incoming Data
-  
-  try
-  {
-    ControlResponse response(data.getContent().blockFromValue());
-    if (response.getCode() != 200)
-      return onFail(response.getText());
-    
-    FaceManagementOptions options(response.getBody());
-    return onSuccess(options);
+  if (command == "create") {
+    this->start<FaceCreateCommand>(options,
+                                   onSuccess,
+                                   bind(onFail, _2));
   }
-  catch(ndn::Tlv::Error& e)
-  {
-    if (static_cast<bool>(onFail))
-      return onFail(e.what());
+  else if (command == "destroy") {
+    this->start<FaceDestroyCommand>(options,
+                                    onSuccess,
+                                    bind(onFail, _2));
+  }
+  // enable-local-control and disable-local-control are not in legacy API.
+  else {
+    onFail("unknown command");
   }
 }
 
@@ -154,43 +178,21 @@
                                        const StrategyChoiceCommandSucceedCallback& onSuccess,
                                        const FailCallback& onFail)
 {
-  Name strategyChoiceCommandInterestName("/localhost/nfd/strategy-choice");
-  strategyChoiceCommandInterestName
-    .append(command)
-    .append(options.wireEncode());
-  
-  Interest strategyChoiceCommandInterest(strategyChoiceCommandInterestName);
-  m_commandInterestGenerator.generate(strategyChoiceCommandInterest);
-  
-  m_face.expressInterest(strategyChoiceCommandInterest,
-                         bind(&Controller::processStrategyChoiceCommandResponse, this, _2,
-                              onSuccess, onFail),
-                         bind(onFail, "Command Interest timed out"));
-}
-void
-Controller::processStrategyChoiceCommandResponse(
-    Data& data,
-    const StrategyChoiceCommandSucceedCallback& onSuccess,
-    const FailCallback& onFail)
-{
-  /// \todo Add validation of incoming Data
-  
-  try
-  {
-    ControlResponse response(data.getContent().blockFromValue());
-    if (response.getCode() != 200)
-      return onFail(response.getText());
-    
-    StrategyChoiceOptions options(response.getBody());
-    return onSuccess(options);
+  if (command == "set") {
+    this->start<StrategyChoiceSetCommand>(options,
+                                          onSuccess,
+                                          bind(onFail, _2));
   }
-  catch (ndn::Tlv::Error& error)
-  {
-    if (static_cast<bool>(onFail))
-      return onFail(error.what());
+  else if (command == "unset") {
+    this->start<StrategyChoiceUnsetCommand>(options,
+                                            onSuccess,
+                                            bind(onFail, _2));
+  }
+  else {
+    onFail("unknown command");
   }
 }
 
-  
+
 } // namespace nfd
 } // namespace ndn
diff --git a/src/management/nfd-controller.hpp b/src/management/nfd-controller.hpp
index f3ab116..536994c 100644
--- a/src/management/nfd-controller.hpp
+++ b/src/management/nfd-controller.hpp
@@ -8,25 +8,37 @@
 #define NDN_MANAGEMENT_NFD_CONTROLLER_HPP
 
 #include "controller.hpp"
-#include "nfd-control-parameters.hpp"
-#include "../util/command-interest-generator.hpp"
+#include "nfd-control-command.hpp"
+#include "../face.hpp"
 
 namespace ndn {
-
 namespace nfd {
 
+/** \brief NFD Management protocol - ControlCommand client
+ */
 class Controller : public ndn::Controller
 {
 public:
-  typedef function<void(const FibManagementOptions&)> FibCommandSucceedCallback;
-  typedef function<void(const FaceManagementOptions&)> FaceCommandSucceedCallback;
-  typedef function<void(const StrategyChoiceOptions&)> StrategyChoiceCommandSucceedCallback;
-
-  /**
-   * @brief Construct ndnd::Control object
+  /** \brief a callback on command success
    */
+  typedef function<void(const ControlParameters&)> CommandSucceedCallback;
+
+  /** \brief a callback on command failure
+   */
+  typedef function<void(uint32_t/*code*/,const std::string&/*reason*/)> CommandFailCallback;
+
+  explicit
   Controller(Face& face);
 
+  /** \brief start command execution
+   */
+  template<typename Command>
+  void
+  start(const ControlParameters& parameters,
+               const CommandSucceedCallback& onSuccess,
+               const CommandFailCallback&    onFailure);
+
+public: // selfreg
   virtual void
   selfRegisterPrefix(const Name& prefixToRegister,
                      const SuccessCallback& onSuccess,
@@ -37,8 +49,20 @@
                        const SuccessCallback& onSuccess,
                        const FailCallback&    onFail);
 
+public:
+  /** \deprecated use CommandSucceedCallback instead
+   */
+  typedef function<void(const FibManagementOptions&)> FibCommandSucceedCallback;
+  /** \deprecated use CommandSucceedCallback instead
+   */
+  typedef function<void(const FaceManagementOptions&)> FaceCommandSucceedCallback;
+  /** \deprecated use CommandSucceedCallback instead
+   */
+  typedef function<void(const StrategyChoiceOptions&)> StrategyChoiceCommandSucceedCallback;
+
   /**
    * \brief Adds a nexthop to an existing or new FIB entry
+   * \deprecated use startCommand instead
    *
    * If FIB entry for the specified prefix does not exist, it will be automatically created.
    *
@@ -58,6 +82,7 @@
 
   /**
    * \brief Remove a nexthop from FIB entry
+   * \deprecated use startCommand instead
    *
    * If after removal of the nexthop FIB entry has zero next hops, this FIB entry will
    * be automatically deleted.
@@ -75,18 +100,24 @@
                    const FailCallback& onFail);
 
 protected:
+  /** \deprecated use startCommand instead
+   */
   void
   startFibCommand(const std::string& command,
                   const FibManagementOptions& options,
                   const FibCommandSucceedCallback& onSuccess,
                   const FailCallback& onFailure);
 
+  /** \deprecated use startCommand instead
+   */
   void
   startFaceCommand(const std::string& command,
                    const FaceManagementOptions& options,
                    const FaceCommandSucceedCallback& onSuccess,
                    const FailCallback& onFailure);
 
+  /** \deprecated use startCommand instead
+   */
   void
   startStrategyChoiceCommand(const std::string& command,
                              const StrategyChoiceOptions& options,
@@ -95,25 +126,35 @@
 
 private:
   void
-  processFibCommandResponse(Data& data,
-                            const FibCommandSucceedCallback& onSuccess,
-                            const FailCallback& onFail);
-
-  void
-  processFaceCommandResponse(Data& data,
-                             const FaceCommandSucceedCallback& onSuccess,
-                             const FailCallback& onFail);
-
-  void
-  processStrategyChoiceCommandResponse(Data& data,
-                                       const StrategyChoiceCommandSucceedCallback& onSuccess,
-                                       const FailCallback& onFail);
+  processCommandResponse(const Data& data,
+                         const shared_ptr<ControlCommand>& command,
+                         const CommandSucceedCallback& onSuccess,
+                         const CommandFailCallback& onFailure);
 
 protected:
   Face& m_face;
   CommandInterestGenerator m_commandInterestGenerator;
 };
 
+
+template<typename Command>
+void
+Controller::start(const ControlParameters& parameters,
+                  const CommandSucceedCallback& onSuccess,
+                  const CommandFailCallback&    onFailure)
+{
+  shared_ptr<ControlCommand> command = make_shared<Command>();
+
+  Interest commandInterest = command->makeCommandInterest(parameters, m_commandInterestGenerator);
+
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668.aspx
+  const uint32_t timeoutCode = 10060;
+  m_face.expressInterest(commandInterest,
+                         bind(&Controller::processCommandResponse, this, _2,
+                              command, onSuccess, onFailure),
+                         bind(onFailure, timeoutCode, "Command Interest timed out"));
+}
+
 } // namespace nfd
 } // namespace ndn
 
diff --git a/tests/management/nfd-controller.cpp b/tests/management/nfd-controller.cpp
new file mode 100644
index 0000000..9176690
--- /dev/null
+++ b/tests/management/nfd-controller.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "management/nfd-controller.hpp"
+// Having a separate compilation unit is necessary to ensure .hpp can compile on its own.
+#include "management/nfd-control-response.hpp"
+
+#include "../transport/dummy-face.hpp"
+#include <boost/test/unit_test.hpp>
+
+namespace ndn {
+namespace nfd {
+
+BOOST_AUTO_TEST_SUITE(NfdController)
+
+class CommandFixture
+{
+protected:
+  CommandFixture()
+    : face(makeDummyFace())
+    , controller(*face)
+    , commandSucceedCallback(bind(&CommandFixture::onCommandSucceed, this, _1))
+    , commandFailCallback(bind(&CommandFixture::onCommandFail, this, _1, _2))
+  {
+  }
+
+private:
+  void
+  onCommandSucceed(const ControlParameters& parameters)
+  {
+    commandSucceedHistory.push_back(boost::make_tuple(parameters));
+  }
+
+  void
+  onCommandFail(uint32_t code, const std::string& reason)
+  {
+    commandFailHistory.push_back(boost::make_tuple(code, reason));
+  }
+
+protected:
+  shared_ptr<DummyFace> face;
+  Controller controller;
+  KeyChain keyChain;
+
+  Controller::CommandSucceedCallback commandSucceedCallback;
+  typedef boost::tuple<ControlParameters> CommandSucceedArgs;
+  std::vector<CommandSucceedArgs> commandSucceedHistory;
+
+  Controller::CommandFailCallback commandFailCallback;
+  typedef boost::tuple<uint32_t,std::string> CommandFailArgs;
+  std::vector<CommandFailArgs> commandFailHistory;
+};
+
+BOOST_FIXTURE_TEST_CASE(CommandSuccess, CommandFixture)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://example.com");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters,
+                         commandSucceedCallback,
+                         commandFailCallback));
+  face->processEvents(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face->m_sentInterests.size(), 1);
+  const Interest& commandInterest = face->m_sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(command.getPrefix().isPrefixOf(commandInterest.getName()));
+  // 9 components: ndn:/localhost/nfd/face/create/<parameters>/<command Interest signature x4>
+  BOOST_REQUIRE_EQUAL(commandInterest.getName().size(), 9);
+  ControlParameters request;
+  // 4th component: <parameters>
+  BOOST_REQUIRE_NO_THROW(request.wireDecode(commandInterest.getName().at(4).blockFromValue()));
+  BOOST_CHECK_NO_THROW(command.validateRequest(request));
+  BOOST_CHECK_EQUAL(request.getUri(), parameters.getUri());
+
+  ControlParameters responseBody;
+  responseBody.setUri("tcp4://192.0.2.1:6363")
+              .setFaceId(22);
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+
+  Data responseData(commandInterest.getName());
+  responseData.setContent(responsePayload.wireEncode());
+  keyChain.sign(responseData);
+  face->receive(responseData);
+  face->processEvents(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(commandFailHistory.size(), 0);
+  BOOST_REQUIRE_EQUAL(commandSucceedHistory.size(), 1);
+  const ControlParameters& response = commandSucceedHistory[0].get<0>();
+  BOOST_CHECK_EQUAL(response.getUri(), responseBody.getUri());
+  BOOST_CHECK_EQUAL(response.getFaceId(), responseBody.getFaceId());
+}
+
+BOOST_FIXTURE_TEST_CASE(CommandInvalidRequest, CommandFixture)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/should-not-have-this-field");
+  // Uri is missing
+
+  BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(
+                      parameters,
+                      commandSucceedCallback,
+                      commandFailCallback),
+                    ControlCommand::ArgumentError);
+}
+
+BOOST_FIXTURE_TEST_CASE(CommandErrorCode, CommandFixture)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://example.com");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters,
+                         commandSucceedCallback,
+                         commandFailCallback));
+  face->processEvents(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face->m_sentInterests.size(), 1);
+  const Interest& commandInterest = face->m_sentInterests[0];
+
+  ControlResponse responsePayload(401, "Not Authenticated");
+
+  Data responseData(commandInterest.getName());
+  responseData.setContent(responsePayload.wireEncode());
+  keyChain.sign(responseData);
+  face->receive(responseData);
+  face->processEvents(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(commandSucceedHistory.size(), 0);
+  BOOST_REQUIRE_EQUAL(commandFailHistory.size(), 1);
+  BOOST_CHECK_EQUAL(commandFailHistory[0].get<0>(), 401);
+}
+
+BOOST_FIXTURE_TEST_CASE(CommandInvalidResponse, CommandFixture)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp://example.com");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters,
+                         commandSucceedCallback,
+                         commandFailCallback));
+  face->processEvents(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face->m_sentInterests.size(), 1);
+  const Interest& commandInterest = face->m_sentInterests[0];
+
+  ControlParameters responseBody;
+  responseBody.setUri("tcp4://192.0.2.1:6363")
+              .setName("ndn:/should-not-have-this-field");
+  // FaceId is missing
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+
+  Data responseData(commandInterest.getName());
+  responseData.setContent(responsePayload.wireEncode());
+  keyChain.sign(responseData);
+  face->receive(responseData);
+  face->processEvents(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(commandSucceedHistory.size(), 0);
+  BOOST_REQUIRE_EQUAL(commandFailHistory.size(), 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/security/identity-fixture.cpp b/tests/security/identity-fixture.cpp
new file mode 100644
index 0000000..2cc7e4f
--- /dev/null
+++ b/tests/security/identity-fixture.cpp
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * @author: Yingdi Yu <yingdi0@cs.ucla.edu>
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "security/key-chain.hpp"
+#include <boost/test/unit_test.hpp>
+
+namespace ndn {
+
+// OSX KeyChain, when used on a headless server,
+// forbids usage of a private key if that key isn't created by the calling process.
+// Therefore, unit testing must create its own key pair.
+
+class IdentityFixture
+{
+public:
+  IdentityFixture()
+  {
+    // save the old default identity
+    m_oldDefaultIdentity = m_keyChain.getDefaultIdentity();
+
+    m_newIdentity.set("/ndn-cpp-dev-test-identity");
+    m_newIdentity.appendVersion();
+
+    // create the new identity and self-signed certificate
+    m_keyChain.createIdentity(m_newIdentity);
+
+    // set the new identity as default identity,
+    // and the corresponding certificate becomes the default certificate
+    m_keyChain.setDefaultIdentity(m_newIdentity);
+  }
+
+  ~IdentityFixture()
+  {
+    // recover the old default setting
+    m_keyChain.setDefaultIdentity(m_oldDefaultIdentity);
+
+    // remove the temporarily created identity and certificates
+    m_keyChain.deleteIdentity(m_newIdentity);
+  }
+
+private:
+  KeyChain m_keyChain;
+  Name m_oldDefaultIdentity;
+  Name m_newIdentity;
+};
+
+BOOST_GLOBAL_FIXTURE(IdentityFixture);
+
+} // namespace ndn
diff --git a/tests/transport/dummy-face.hpp b/tests/transport/dummy-face.hpp
new file mode 100644
index 0000000..1ac3cc9
--- /dev/null
+++ b/tests/transport/dummy-face.hpp
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NDN_TESTS_DUMMY_FACE_HPP
+#define NDN_TESTS_DUMMY_FACE_HPP
+
+#include "face.hpp"
+
+namespace ndn {
+
+class DummyTransport : public Transport
+{
+public:
+  void
+  receive(const Block& block)
+  {
+    m_receiveCallback(block);
+  }
+
+  virtual void
+  close()
+  {
+  }
+
+  virtual void
+  pause()
+  {
+  }
+
+  virtual void
+  resume()
+  {
+  }
+
+  virtual void
+  send(const Block& wire)
+  {
+    if (wire.type() == Tlv::Interest) {
+      m_sentInterests->push_back(Interest(wire));
+    }
+    else if (wire.type() == Tlv::Data) {
+      m_sentDatas->push_back(Data(wire));
+    }
+  }
+
+  virtual void
+  send(const Block& header, const Block& payload)
+  {
+    this->send(payload);
+  }
+
+public:
+  std::vector<Interest>* m_sentInterests;
+  std::vector<Data>*     m_sentDatas;
+};
+
+
+/** \brief a Face for unit testing
+ */
+class DummyFace : public Face
+{
+public:
+  explicit
+  DummyFace(shared_ptr<DummyTransport> transport)
+    : Face(transport)
+    , m_transport(transport)
+  {
+    m_transport->m_sentInterests = &m_sentInterests;
+    m_transport->m_sentDatas     = &m_sentDatas;
+  }
+
+  /** \brief cause the Face to receive a packet
+   */
+  template<typename Packet>
+  void
+  receive(const Packet& packet)
+  {
+    m_transport->receive(packet.wireEncode());
+  }
+
+public:
+  std::vector<Interest> m_sentInterests;
+  std::vector<Data>     m_sentDatas;
+
+private:
+  shared_ptr<DummyTransport> m_transport;
+};
+
+inline shared_ptr<DummyFace>
+makeDummyFace()
+{
+  return make_shared<DummyFace>(make_shared<DummyTransport>());
+}
+
+} // namespace ndn
+#endif // NDN_TESTS_DUMMY_FACE_HPP