mgmt: move management/nfd-* to mgmt/nfd/

refs #3760

Change-Id: Ib4bde3412b5c39b9f4f46113199cebe78704505e
diff --git a/tests/unit-tests/mgmt/dispatcher.t.cpp b/tests/unit-tests/mgmt/dispatcher.t.cpp
index d7e4efc..877ec02 100644
--- a/tests/unit-tests/mgmt/dispatcher.t.cpp
+++ b/tests/unit-tests/mgmt/dispatcher.t.cpp
@@ -20,7 +20,7 @@
  */
 
 #include "mgmt/dispatcher.hpp"
-#include "management/nfd-control-parameters.hpp"
+#include "mgmt/nfd/control-parameters.hpp"
 #include "util/dummy-client-face.hpp"
 
 #include "boost-test.hpp"
diff --git a/tests/unit-tests/mgmt/nfd/channel-status.t.cpp b/tests/unit-tests/mgmt/nfd/channel-status.t.cpp
new file mode 100644
index 0000000..c682f59
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/channel-status.t.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/channel-status.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestChannelStatus)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ChannelStatus status1;
+  status1
+    .setLocalUri("udp4://192.168.2.1")
+    ;
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = status1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x82, 0x14, 0x81, 0x12, 0x75, 0x64, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31
+  };
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                  wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(ChannelStatus(wire));
+  ChannelStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1.getLocalUri(), status2.getLocalUri());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestChannelStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/command-options.t.cpp b/tests/unit-tests/mgmt/nfd/command-options.t.cpp
new file mode 100644
index 0000000..470cac9
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/command-options.t.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/command-options.hpp"
+#include "security/signing-helpers.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestCommandOptions)
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getTimeout(), CommandOptions::DEFAULT_TIMEOUT);
+
+  co.setTimeout(time::milliseconds(7414));
+  BOOST_CHECK_EQUAL(co.getTimeout(), time::milliseconds(7414));
+
+  BOOST_CHECK_THROW(co.setTimeout(time::milliseconds::zero()), std::out_of_range);
+  BOOST_CHECK_THROW(co.setTimeout(time::milliseconds(-1)), std::out_of_range);
+  BOOST_CHECK_EQUAL(co.getTimeout(), time::milliseconds(7414)); // unchanged after throw
+
+  co.setTimeout(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(co.getTimeout(), time::milliseconds(1));
+}
+
+BOOST_AUTO_TEST_CASE(Prefix)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getPrefix(), CommandOptions::DEFAULT_PREFIX);
+
+  co.setPrefix(Name()); // empty Name is okay
+  BOOST_CHECK_EQUAL(co.getPrefix(), Name());
+
+  co.setPrefix("ndn:/localhop/net/example/nfd");
+  BOOST_CHECK_EQUAL(co.getPrefix(), Name("ndn:/localhop/net/example/nfd"));
+}
+
+BOOST_AUTO_TEST_CASE(SigningInfo)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerType(), security::SigningInfo::SIGNER_TYPE_NULL);
+
+  co.setSigningInfo(signingByIdentity("ndn:/tmp/identity"));
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerType(), security::SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerName(), "ndn:/tmp/identity");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandOptions
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/control-command.t.cpp b/tests/unit-tests/mgmt/nfd/control-command.t.cpp
new file mode 100644
index 0000000..d282acf
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/control-command.t.cpp
@@ -0,0 +1,381 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-command.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlCommand)
+
+BOOST_AUTO_TEST_CASE(FaceCreate)
+{
+  FaceCreateCommand command;
+
+  ControlParameters p1;
+  p1.setUri("tcp4://192.0.2.1")
+    .setFaceId(4);
+  BOOST_CHECK_THROW(command.validateRequest(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  ControlParameters p2;
+  p2.setName("ndn:/example");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setUri("tcp4://192.0.2.1")
+    .setFaceId(0);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+
+  ControlParameters p4;
+  p4.setUri("tcp4://192.0.2.1")
+    .setFacePersistency(FACE_PERSISTENCY_PERSISTENT)
+    .setFlags(0x3)
+    .setMask(0x1);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p4));
+
+  ControlParameters p5;
+  p5.setUri("tcp4://192.0.2.1")
+    .setFacePersistency(FACE_PERSISTENCY_PERSISTENT)
+    .setFlags(0x1);
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+
+  p4.unsetFacePersistency();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p4));
+  command.applyDefaultsToRequest(p4);
+  BOOST_REQUIRE(p4.hasFacePersistency());
+  BOOST_CHECK_EQUAL(p4.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+
+  ControlParameters p6;
+  p6.setFaceId(4)
+    .setUri("tcp4://192.0.2.1")
+    .setFacePersistency(FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p6));
+
+  ControlParameters p7;
+  p7.setUri("tcp4://192.0.2.1:6363");
+  Name n7;
+  BOOST_CHECK_NO_THROW(n7 = command.getRequestName("/PREFIX", p7));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/create").isPrefixOf(n7));
+
+  ControlParameters p8;
+  p8.setFaceId(5)
+    .setFacePersistency(FACE_PERSISTENCY_PERMANENT)
+    .setFlags(0x2);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p8));
+}
+
+BOOST_AUTO_TEST_CASE(FaceUpdate)
+{
+  FaceUpdateCommand command;
+
+  ControlParameters p1;
+  p1.setFaceId(0);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  p1.setFaceId(1);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  command.applyDefaultsToRequest(p1);
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 1);
+
+  ControlParameters p2;
+  p2.setFaceId(1)
+    .setFacePersistency(FACE_PERSISTENCY_PERSISTENT)
+    .setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError); // Mask forbidden but present
+
+  // Flags without Mask
+  p2.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+
+  p2.setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+
+  ControlParameters p3;
+  p3.setFaceId(1)
+    .setName("/ndn/name");
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+
+  ControlParameters p4;
+  p4.setFaceId(1)
+    .setUri("tcp4://192.0.2.1");
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  ControlParameters p5;
+  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK(!p5.hasFaceId());
+  command.applyDefaultsToRequest(p5);
+  BOOST_REQUIRE(p5.hasFaceId());
+  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_EQUAL(p5.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDestroy)
+{
+  FaceDestroyCommand command;
+
+  ControlParameters p1;
+  p1.setUri("tcp4://192.0.2.1")
+    .setFaceId(4);
+  BOOST_CHECK_THROW(command.validateRequest(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  ControlParameters p2;
+  p2.setFaceId(0);
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setFaceId(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p3));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p3));
+  Name n3;
+  BOOST_CHECK_NO_THROW(n3 = command.getRequestName("/PREFIX", p3));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/destroy").isPrefixOf(n3));
+}
+
+BOOST_AUTO_TEST_CASE(FaceEnableLocalControl)
+{
+  FaceEnableLocalControlCommand command;
+
+  ControlParameters p1;
+  p1.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/enable-local-control").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID)
+    .setFaceId(9);
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setLocalControlFeature(static_cast<LocalControlFeature>(666));
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDisableLocalControl)
+{
+  FaceDisableLocalControlCommand command;
+
+  ControlParameters p1;
+  p1.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/disable-local-control").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID)
+    .setFaceId(9);
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setLocalControlFeature(static_cast<LocalControlFeature>(666));
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FibAddNextHop)
+{
+  FibAddNextHopCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/fib/add-nexthop").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0)
+    .setCost(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasCost());
+  BOOST_CHECK_EQUAL(p1.getCost(), 0);
+
+  p1.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasFaceId());
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FibRemoveNextHop)
+{
+  FibRemoveNextHopCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/fib/remove-nexthop").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  p1.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasFaceId());
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceSet)
+{
+  StrategyChoiceSetCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setStrategy("ndn:/strategy/P");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/set").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceUnset)
+{
+  StrategyChoiceUnsetCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/example");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/unset").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setStrategy("ndn:/strategy/P");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setName("ndn:/");
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(RibRegister)
+{
+  RibRegisterCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/register").isPrefixOf(n1));
+
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasOrigin());
+  BOOST_CHECK_EQUAL(p1.getOrigin(), static_cast<uint64_t>(ROUTE_ORIGIN_APP));
+  BOOST_REQUIRE(p1.hasCost());
+  BOOST_CHECK_EQUAL(p1.getCost(), 0);
+  BOOST_REQUIRE(p1.hasFlags());
+  BOOST_CHECK_EQUAL(p1.getFlags(), static_cast<uint64_t>(ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(p1.hasExpirationPeriod(), false);
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(2)
+    .setCost(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  command.applyDefaultsToRequest(p2);
+  BOOST_CHECK_EQUAL(p2.hasExpirationPeriod(), false);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+}
+
+BOOST_AUTO_TEST_CASE(RibUnregister)
+{
+  RibUnregisterCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22)
+    .setOrigin(ROUTE_ORIGIN_STATIC);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/unregister").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0)
+    .setOrigin(ROUTE_ORIGIN_APP);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlCommand
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp b/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp
new file mode 100644
index 0000000..7ef364e
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/control-parameters.t.cpp
@@ -0,0 +1,276 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-parameters.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlParameters)
+
+BOOST_AUTO_TEST_CASE(FaceOptions)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+  parameters.setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  parameters.setFlags(1);
+  parameters.setMask(1);
+  Block wire = parameters.wireEncode();
+
+  ControlParameters decoded(wire);
+  BOOST_CHECK_EQUAL(decoded.getUri(), "tcp4://192.0.2.1:6363");
+  BOOST_CHECK_EQUAL(decoded.getFacePersistency(), ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(decoded.getFlags(), 1);
+  BOOST_CHECK_EQUAL(decoded.getMask(), 1);
+
+  BOOST_CHECK_EQUAL(decoded.hasName(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FaceLocalControlOptions)
+{
+  ControlParameters parameters;
+  parameters.setLocalControlFeature(LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+
+  Block wire = parameters.wireEncode();
+
+  ControlParameters decoded(wire);
+  BOOST_CHECK_EQUAL(decoded.getLocalControlFeature(), LOCAL_CONTROL_FEATURE_INCOMING_FACE_ID);
+
+  BOOST_CHECK_EQUAL(decoded.hasName(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FibOptions)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/example")
+            .setFaceId(4)
+            .setCost(555);
+
+  Block wire = parameters.wireEncode();
+
+  ControlParameters decoded(wire);
+  BOOST_CHECK_EQUAL(decoded.getName(), Name("ndn:/example"));
+  BOOST_CHECK_EQUAL(decoded.getFaceId(), 4);
+  BOOST_CHECK_EQUAL(decoded.getCost(), 555);
+
+  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceOptions)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/")
+            .setStrategy("ndn:/strategy/A");
+
+  Block wire = parameters.wireEncode();
+
+  ControlParameters decoded(wire);
+  BOOST_CHECK_EQUAL(decoded.getName(), Name("ndn:/"));
+  BOOST_CHECK_EQUAL(decoded.getStrategy(), Name("ndn:/strategy/A"));
+
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+}
+
+BOOST_AUTO_TEST_CASE(RibOptions)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/example")
+            .setFaceId(4)
+            .setOrigin(128)
+            .setCost(6)
+            .setFlags(0x01)
+            .setExpirationPeriod(time::milliseconds(1800000));
+
+  Block wire = parameters.wireEncode();
+
+  ControlParameters decoded(wire);
+  BOOST_CHECK_EQUAL(decoded.getName(), Name("ndn:/example"));
+  BOOST_CHECK_EQUAL(decoded.getFaceId(), 4);
+  BOOST_CHECK_EQUAL(decoded.getOrigin(), 128);
+  BOOST_CHECK_EQUAL(decoded.getCost(), 6);
+  BOOST_CHECK_EQUAL(decoded.getFlags(), 0x01);
+  BOOST_CHECK_EQUAL(decoded.getExpirationPeriod(), time::milliseconds(1800000));
+
+  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasLocalControlFeature(), false);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FlagsAndMask)
+{
+  ControlParameters p;
+
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  // Set bit 2 to true (change Mask)
+  p.setFlagBit(2, true);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getFlags(), 4);
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 3 to true (no change to Mask)
+  p.setFlagBit(3, true, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+
+  // Set bit 1 to false (change Mask)
+  p.setFlagBit(1, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 2 to false (change Mask)
+  p.setFlagBit(2, false);
+  // 2^3 = 8
+  BOOST_CHECK_EQUAL(p.getFlags(), 8);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 0 to true (change Mask)
+  p.setFlagBit(0, true);
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 + 2^0 = 7
+  BOOST_CHECK_EQUAL(p.getMask(), 7);
+  BOOST_CHECK(p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 0
+  p.unsetFlagBit(0);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 3 (already unset in Mask, so no change)
+  p.unsetFlagBit(3);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+
+  // Unset bit 2
+  p.unsetFlagBit(2);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^1 = 2
+  BOOST_CHECK_EQUAL(p.getMask(), 2);
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+
+  // Unset bit 1
+  // Flags and Mask fields will be deleted as Mask is now 0
+  p.unsetFlagBit(1);
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(!p.getFlagBit(3));
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  BOOST_CHECK_THROW(p.setFlagBit(64, true), std::out_of_range);
+  BOOST_CHECK_THROW(p.unsetFlagBit(64), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlParameters
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/control-response.t.cpp b/tests/unit-tests/mgmt/nfd/control-response.t.cpp
new file mode 100644
index 0000000..58fc3bc
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/control-response.t.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-response.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlResponse)
+
+const uint8_t TestControlResponse[] = {0x65, 0x17,
+                                       0x66, 0x02, 0x01, 0x94, 0x67, 0x11, 0x4e, 0x6f, 0x74,
+                                       0x68, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x6f, 0x74, 0x20,
+                                       0x66, 0x6f, 0x75, 0x6e, 0x64};
+
+// ControlResponse
+
+BOOST_AUTO_TEST_CASE(ControlResponseEncode)
+{
+  ControlResponse controlResponse(404, "Nothing not found");
+  const Block &wire = controlResponse.wireEncode();
+
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(TestControlResponse,
+                                  TestControlResponse+sizeof(TestControlResponse),
+                                  wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(ControlResponseDecode)
+{
+  ControlResponse controlResponse;
+
+  BOOST_REQUIRE_NO_THROW(controlResponse.wireDecode(Block(TestControlResponse,
+                                                          sizeof(TestControlResponse))));
+
+  BOOST_REQUIRE_EQUAL(controlResponse.getCode(), 404);
+  BOOST_REQUIRE_EQUAL(controlResponse.getText(), "Nothing not found");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlResponse
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/controller-fixture.hpp b/tests/unit-tests/mgmt/nfd/controller-fixture.hpp
new file mode 100644
index 0000000..76e7bde
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/controller-fixture.hpp
@@ -0,0 +1,95 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
+#define NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
+
+#include "mgmt/nfd/controller.hpp"
+#include "../../../dummy-validator.hpp"
+
+#include "boost-test.hpp"
+#include "util/dummy-client-face.hpp"
+#include "../../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+class ControllerFixture : public IdentityManagementTimeFixture
+{
+protected:
+  ControllerFixture()
+    : face(io, m_keyChain)
+    , controller(face, m_keyChain, m_validator)
+    , commandFailCallback(bind(&ControllerFixture::recordCommandFail, this, _1))
+    , datasetFailCallback(bind(&ControllerFixture::recordDatasetFail, this, _1, _2))
+  {
+    Name identityName("/localhost/ControllerFixture");
+    if (this->addIdentity(identityName)) {
+      m_keyChain.setDefaultIdentity(identityName);
+    }
+    else {
+      BOOST_FAIL("cannot create identity");
+    }
+  }
+
+  /** \brief controls whether Controller's validator should accept or reject validation requests
+   *
+   *  Initially, the validator accepts all requests.
+   *  Setting \p false causes validator to reject all requests.
+   */
+  void
+  setValidationResult(bool shouldAccept)
+  {
+    m_validator.setResult(shouldAccept);
+  }
+
+private:
+  void
+  recordCommandFail(const ControlResponse& response)
+  {
+    failCodes.push_back(response.getCode());
+  }
+
+  void
+  recordDatasetFail(uint32_t code, const std::string& reason)
+  {
+    failCodes.push_back(code);
+  }
+
+protected:
+  ndn::util::DummyClientFace face;
+  Controller controller;
+  Controller::CommandFailCallback commandFailCallback;
+  Controller::DatasetFailCallback datasetFailCallback;
+  std::vector<uint32_t> failCodes;
+
+private:
+  DummyValidator m_validator;
+};
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
+
+#endif // NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
diff --git a/tests/unit-tests/mgmt/nfd/controller.t.cpp b/tests/unit-tests/mgmt/nfd/controller.t.cpp
new file mode 100644
index 0000000..4d6b8a2
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/controller.t.cpp
@@ -0,0 +1,305 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/controller.hpp"
+#include "mgmt/nfd/control-response.hpp"
+
+#include <boost/tuple/tuple.hpp>
+
+#include "controller-fixture.hpp"
+#include "../../make-interest-data.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+
+class CommandFixture : public ControllerFixture
+{
+protected:
+  CommandFixture()
+    : succeedCallback(bind(&CommandFixture::succeed, this, _1))
+  {
+  }
+
+private:
+  void
+  succeed(const ControlParameters& parameters)
+  {
+    succeeds.push_back(parameters);
+  }
+
+protected:
+  Controller::CommandSucceedCallback succeedCallback;
+  std::vector<ControlParameters> succeeds;
+};
+
+// This test suite focuses on ControlCommand functionality of Controller.
+// Individual commands are tested in nfd-control-command.t.cpp
+// StatusDataset functionality is tested in nfd-status-dataset.t.cpp
+BOOST_FIXTURE_TEST_SUITE(TestController, CommandFixture)
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters, succeedCallback, commandFailCallback));
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(Name("/localhost/nfd/faces/create").isPrefixOf(requestInterest.getName()));
+  // 9 components: ndn:/localhost/nfd/faces/create/<parameters>/<signed Interest x4>
+  BOOST_REQUIRE_EQUAL(requestInterest.getName().size(), 9);
+  ControlParameters request;
+  // 4th component: <parameters>
+  BOOST_REQUIRE_NO_THROW(request.wireDecode(requestInterest.getName().at(4).blockFromValue()));
+  BOOST_CHECK_NO_THROW(command.validateRequest(request));
+  BOOST_CHECK_EQUAL(request.getUri(), parameters.getUri());
+  BOOST_CHECK_EQUAL(requestInterest.getInterestLifetime(), CommandOptions::DEFAULT_TIMEOUT);
+
+  ControlParameters responseBody;
+  responseBody.setUri("tcp4://192.0.2.1:6363")
+              .setFaceId(22)
+              .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+
+  auto responseData = makeData(requestInterest.getName());
+  responseData->setContent(responsePayload.wireEncode());
+  face.receive(*responseData);
+
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+  BOOST_REQUIRE_EQUAL(succeeds.size(), 1);
+  BOOST_CHECK_EQUAL(succeeds.back().getUri(), responseBody.getUri());
+  BOOST_CHECK_EQUAL(succeeds.back().getFaceId(), responseBody.getFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(SuccessNoCallback)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, nullptr, commandFailCallback);
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  ControlParameters responseBody;
+  responseBody.setUri("tcp4://192.0.2.1:6363")
+              .setFaceId(22)
+              .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+
+  auto responseData = makeData(requestInterest.getName());
+  responseData->setContent(responsePayload.wireEncode());
+  face.receive(*responseData);
+
+  BOOST_CHECK_NO_THROW(this->advanceClocks(time::milliseconds(1)));
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(OptionsPrefix)
+{
+  ControlParameters parameters;
+  parameters.setName("/ndn/com/example");
+  parameters.setFaceId(400);
+
+  CommandOptions options;
+  options.setPrefix("/localhop/net/example/router1/nfd");
+
+  BOOST_CHECK_NO_THROW(controller.start<RibRegisterCommand>(
+                         parameters, succeedCallback, commandFailCallback, options));
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(Name("/localhop/net/example/router1/nfd/rib/register").isPrefixOf(
+              requestInterest.getName()));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidRequest)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/should-not-have-this-field");
+  // Uri is missing
+
+  BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(
+                      parameters, succeedCallback, commandFailCallback),
+                    ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  this->setValidationResult(false);
+
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters, succeedCallback, commandFailCallback));
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  ControlParameters responseBody;
+  responseBody.setUri("tcp4://192.0.2.1:6363")
+              .setFaceId(22)
+              .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+
+  auto responseData = makeData(requestInterest.getName());
+  responseData->setContent(responsePayload.wireEncode());
+  face.receive(*responseData);
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_VALIDATION);
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCode)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters, succeedCallback, commandFailCallback));
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  ControlResponse responsePayload(401, "Not Authenticated");
+
+  auto responseData = makeData(requestInterest.getName());
+  responseData->setContent(responsePayload.wireEncode());
+  face.receive(*responseData);
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), 401);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidResponse)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters, succeedCallback, commandFailCallback));
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.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());
+
+  auto responseData = makeData(requestInterest.getName());
+  responseData->setContent(responsePayload.wireEncode());
+  face.receive(*responseData);
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters, succeedCallback, commandFailCallback));
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  auto responseNack = makeNack(requestInterest, lp::NackReason::NO_ROUTE);
+  face.receive(responseNack);
+  this->advanceClocks(time::milliseconds(1));
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_NACK);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  CommandOptions options;
+  options.setTimeout(time::milliseconds(50));
+
+  BOOST_CHECK_NO_THROW(controller.start<FaceCreateCommand>(
+                         parameters, succeedCallback, commandFailCallback, options));
+  this->advanceClocks(time::milliseconds(1), 101); // Face's PIT granularity is 100ms
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_TIMEOUT);
+}
+
+BOOST_AUTO_TEST_CASE(FailureNoCallback)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  CommandOptions options;
+  options.setTimeout(time::milliseconds(50));
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, nullptr, options);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(time::milliseconds(100), 10));
+  // timeout
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestController
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/face-event-notification.t.cpp b/tests/unit-tests/mgmt/nfd/face-event-notification.t.cpp
new file mode 100644
index 0000000..7def221
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/face-event-notification.t.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-event-notification.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceEventNotification)
+
+BOOST_AUTO_TEST_CASE(Traits)
+{
+  FaceEventNotification notification;
+
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFaceScope(FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFacePersistency(FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFacePersistency(FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_MULTI_ACCESS);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeCreated)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_CREATED)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS);
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = notification1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x3e, 0xc1, 0x01, 0x01, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01,
+  };
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                  wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(FaceEventNotification(wire));
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1.getKind(), notification2.getKind());
+  BOOST_CHECK_EQUAL(notification1.getFaceId(), notification2.getFaceId());
+  BOOST_CHECK_EQUAL(notification1.getRemoteUri(), notification2.getRemoteUri());
+  BOOST_CHECK_EQUAL(notification1.getLocalUri(), notification2.getLocalUri());
+  BOOST_CHECK_EQUAL(notification1.getFaceScope(), notification2.getFaceScope());
+  BOOST_CHECK_EQUAL(notification1.getFacePersistency(), notification2.getFacePersistency());
+  BOOST_CHECK_EQUAL(notification1.getLinkType(), notification2.getLinkType());
+
+  std::ostringstream os;
+  os << notification2;
+  BOOST_CHECK_EQUAL(os.str(), "FaceEventNotification("
+                              "Kind: created, "
+                              "FaceID: 20, "
+                              "RemoteUri: tcp4://192.0.2.1:55555, "
+                              "LocalUri: tcp4://192.0.2.2:6363, "
+                              "FaceScope: local, "
+                              "FacePersistency: on-demand, "
+                              "LinkType: multi-access)");
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDestroyed)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_DESTROYED)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS);
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = notification1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x3e, 0xc1, 0x01, 0x02, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01,
+  };
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                  wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(FaceEventNotification(wire));
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1.getKind(), notification2.getKind());
+  BOOST_CHECK_EQUAL(notification1.getFaceId(), notification2.getFaceId());
+  BOOST_CHECK_EQUAL(notification1.getRemoteUri(), notification2.getRemoteUri());
+  BOOST_CHECK_EQUAL(notification1.getLocalUri(), notification2.getLocalUri());
+  BOOST_CHECK_EQUAL(notification1.getFaceScope(), notification2.getFaceScope());
+  BOOST_CHECK_EQUAL(notification1.getFacePersistency(), notification2.getFacePersistency());
+  BOOST_CHECK_EQUAL(notification1.getLinkType(), notification2.getLinkType());
+
+  std::ostringstream os;
+  os << notification2;
+  BOOST_CHECK_EQUAL(os.str(), "FaceEventNotification("
+                              "Kind: destroyed, "
+                              "FaceID: 20, "
+                              "RemoteUri: tcp4://192.0.2.1:55555, "
+                              "LocalUri: tcp4://192.0.2.2:6363, "
+                              "FaceScope: local, "
+                              "FacePersistency: on-demand, "
+                              "LinkType: multi-access)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceEventNotification
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/face-query-filter.t.cpp b/tests/unit-tests/mgmt/nfd/face-query-filter.t.cpp
new file mode 100644
index 0000000..3871ce0
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/face-query-filter.t.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-query-filter.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceQueryFilter)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  FaceQueryFilter filter1;
+  BOOST_CHECK_EQUAL(filter1.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(filter1.hasUriScheme(), false);
+  BOOST_CHECK_EQUAL(filter1.hasRemoteUri(), false);
+  BOOST_CHECK_EQUAL(filter1.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(filter1.hasFaceScope(), false);
+  BOOST_CHECK_EQUAL(filter1.hasFacePersistency(), false);
+  BOOST_CHECK_EQUAL(filter1.hasLinkType(), false);
+
+  filter1.setFaceId(100)
+         .setUriScheme("tcp4")
+         .setRemoteUri("tcp4://192.0.2.1:6363")
+         .setLocalUri("tcp4://192.0.2.2:55555")
+         .setFaceScope(FACE_SCOPE_LOCAL)
+         .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+         .setLinkType(LINK_TYPE_MULTI_ACCESS);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = filter1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x96, 0x41, 0x69, 0x01, 0x64, 0x83, 0x04, 0x74, 0x63, 0x70,
+    0x34, 0x72, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f,
+    0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a,
+    0x36, 0x33, 0x36, 0x33, 0x81, 0x16, 0x74, 0x63, 0x70, 0x34,
+    0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32,
+    0x2e, 0x32, 0x3a, 0x35, 0x35, 0x35, 0x35, 0x35, 0x84, 0x01,
+    0x01, 0x85, 0x01, 0x01, 0x86, 0x01, 0x01,
+  };
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(FaceQueryFilter(wire));
+
+  FaceQueryFilter filter2(wire);
+  BOOST_CHECK_EQUAL(filter1.getFaceId(), filter2.getFaceId());
+  BOOST_CHECK_EQUAL(filter1.getUriScheme(), filter2.getUriScheme());
+  BOOST_CHECK_EQUAL(filter1.getRemoteUri(), filter2.getRemoteUri());
+  BOOST_CHECK_EQUAL(filter1.getLocalUri(), filter2.getLocalUri());
+  BOOST_CHECK_EQUAL(filter1.getFaceScope(), filter2.getFaceScope());
+  BOOST_CHECK_EQUAL(filter1.getFacePersistency(), filter2.getFacePersistency());
+  BOOST_CHECK_EQUAL(filter1.getLinkType(), filter2.getLinkType());
+
+  std::ostringstream os;
+  os << filter2;
+  BOOST_CHECK_EQUAL(os.str(), "FaceQueryFilter(FaceID: 100,\n"
+                              "UriScheme: tcp4,\n"
+                              "RemoteUri: tcp4://192.0.2.1:6363,\n"
+                              "LocalUri: tcp4://192.0.2.2:55555,\n"
+                              "FaceScope: local,\n"
+                              "FacePersistency: on-demand,\n"
+                              "LinkType: multi-access,\n"
+                              ")");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceQueryFilter
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/face-status.t.cpp b/tests/unit-tests/mgmt/nfd/face-status.t.cpp
new file mode 100644
index 0000000..c8a97f3
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/face-status.t.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-status.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceStatus)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  FaceStatus status1;
+  status1.setFaceId(100)
+         .setRemoteUri("tcp4://192.0.2.1:6363")
+         .setLocalUri("tcp4://192.0.2.2:55555")
+         .setFaceScope(FACE_SCOPE_LOCAL)
+         .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+         .setLinkType(LINK_TYPE_MULTI_ACCESS)
+         .setExpirationPeriod(time::seconds(10))
+         .setNInInterests(10)
+         .setNInDatas(200)
+         .setNInNacks(1)
+         .setNOutInterests(3000)
+         .setNOutDatas(4)
+         .setNOutNacks(2)
+         .setNInBytes(1329719163)
+         .setNOutBytes(999110448);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = status1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x80, 0x5e, 0x69, 0x01, 0x64, 0x72, 0x15, 0x74, 0x63, 0x70,
+    0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e,
+    0x32, 0x2e, 0x31, 0x3a, 0x36, 0x33, 0x36, 0x33, 0x81, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x6d, 0x02, 0x27, 0x10, 0x84, 0x01, 0x01, 0x85,
+    0x01, 0x01, 0x86, 0x01, 0x01, 0x90, 0x01, 0x0a, 0x91, 0x01,
+    0xc8, 0x97, 0x01, 0x01, 0x92, 0x02, 0x0b, 0xb8, 0x93, 0x01,
+    0x04, 0x98, 0x01, 0x02, 0x94, 0x04, 0x4f, 0x41, 0xe7, 0x7b,
+    0x95, 0x04, 0x3b, 0x8d, 0x37, 0x30,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(FaceStatus(wire));
+  FaceStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1.getFaceId(), status2.getFaceId());
+  BOOST_CHECK_EQUAL(status1.getRemoteUri(), status2.getRemoteUri());
+  BOOST_CHECK_EQUAL(status1.getLocalUri(), status2.getLocalUri());
+  BOOST_CHECK_EQUAL(status1.getFaceScope(), status2.getFaceScope());
+  BOOST_CHECK_EQUAL(status1.getFacePersistency(), status2.getFacePersistency());
+  BOOST_CHECK_EQUAL(status1.getLinkType(), status2.getLinkType());
+  BOOST_CHECK_EQUAL(status1.getNInInterests(), status2.getNInInterests());
+  BOOST_CHECK_EQUAL(status1.getNInDatas(), status2.getNInDatas());
+  BOOST_CHECK_EQUAL(status1.getNInNacks(), status2.getNInNacks());
+  BOOST_CHECK_EQUAL(status1.getNOutInterests(), status2.getNOutInterests());
+  BOOST_CHECK_EQUAL(status1.getNOutDatas(), status2.getNOutDatas());
+  BOOST_CHECK_EQUAL(status1.getNOutNacks(), status2.getNOutNacks());
+  BOOST_CHECK_EQUAL(status1.getNInBytes(), status2.getNInBytes());
+  BOOST_CHECK_EQUAL(status1.getNOutBytes(), status2.getNOutBytes());
+
+  std::ostringstream os;
+  os << status2;
+  BOOST_CHECK_EQUAL(os.str(), "FaceStatus(FaceID: 100,\n"
+                              "RemoteUri: tcp4://192.0.2.1:6363,\n"
+                              "LocalUri: tcp4://192.0.2.2:55555,\n"
+                              "ExpirationPeriod: 10000 milliseconds,\n"
+                              "FaceScope: local,\n"
+                              "FacePersistency: on-demand,\n"
+                              "LinkType: multi-access,\n"
+                              "Counters: { Interests: {in: 10, out: 3000},\n"
+                              "            Data: {in: 200, out: 4},\n"
+                              "            Nack: {in: 1, out: 2},\n"
+                              "            bytes: {in: 1329719163, out: 999110448} }\n"
+                              ")");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/fib-entry.t.cpp b/tests/unit-tests/mgmt/nfd/fib-entry.t.cpp
new file mode 100644
index 0000000..7723974
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/fib-entry.t.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/fib-entry.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFibEntry)
+
+const uint8_t TestNextHopRecord[] =
+{
+  0x81, 0x06, 0x69, 0x01, 0x0a, 0x6a, 0x01, 0xc8
+};
+
+const uint8_t TestFibEntryNoNextHops[] =
+{
+  0x80, 0x15, 0x07, 0x13, 0x08, 0x04, 0x74, 0x68, 0x69, 0x73,
+  0x08, 0x02, 0x69, 0x73, 0x08, 0x01, 0x61, 0x08, 0x04, 0x74,
+  0x65, 0x73, 0x74
+};
+
+const uint8_t TestFibEntry[] =
+{
+  0x80, 0x38, 0x07, 0x13, 0x08, 0x04, 0x74, 0x68, 0x69, 0x73, 0x08, 0x02, 0x69, 0x73, 0x08, 0x01,
+  0x61, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x81, 0x06, 0x69, 0x01, 0x0a, 0x6a, 0x01, 0xc8, 0x81,
+  0x07, 0x69, 0x01, 0x14, 0x6a, 0x02, 0x01, 0x2c, 0x81, 0x07, 0x69, 0x01, 0x1e, 0x6a, 0x02, 0x01,
+  0x90, 0x81, 0x07, 0x69, 0x01, 0x28, 0x6a, 0x02, 0x01, 0xf4
+};
+
+BOOST_AUTO_TEST_CASE(TestNextHopRecordEncode)
+{
+  NextHopRecord record;
+  record.setFaceId(10);
+  record.setCost(200);
+
+  const Block& wire = record.wireEncode();
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(TestNextHopRecord,
+                                  TestNextHopRecord + sizeof(TestNextHopRecord),
+                                  wire.begin(), wire.end());
+
+}
+
+BOOST_AUTO_TEST_CASE(TestNextHopRecordDecode)
+{
+  NextHopRecord record;
+
+  BOOST_REQUIRE_NO_THROW(record.wireDecode(Block(TestNextHopRecord,
+                                                sizeof(TestNextHopRecord))));
+  BOOST_REQUIRE_EQUAL(record.getFaceId(), 10);
+  BOOST_REQUIRE_EQUAL(record.getCost(), 200);
+}
+
+BOOST_AUTO_TEST_CASE(TestFibEntryNoNextHopEncode)
+{
+  FibEntry entry;
+  entry.setPrefix("/this/is/a/test");
+
+  const Block& wire = entry.wireEncode();
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(TestFibEntryNoNextHops,
+                                  TestFibEntryNoNextHops + sizeof(TestFibEntryNoNextHops),
+                                  wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(TestFibEntryNoNextHopsDecode)
+{
+  FibEntry entry;
+  BOOST_REQUIRE_NO_THROW(entry.wireDecode(Block(TestFibEntryNoNextHops,
+                                                sizeof(TestFibEntryNoNextHops))));
+
+  BOOST_REQUIRE_EQUAL(entry.getPrefix(), "/this/is/a/test");
+  BOOST_REQUIRE(entry.getNextHopRecords().empty());
+}
+
+BOOST_AUTO_TEST_CASE(TestFibEntryEncode)
+{
+  FibEntry entry;
+  entry.setPrefix("/this/is/a/test");
+
+  std::list<NextHopRecord> records;
+
+  for (int i = 1; i < 4; i++)
+    {
+      NextHopRecord record;
+      record.setFaceId(i * 10);
+      record.setCost((i * 100) + 100);
+      records.push_back(record);
+    }
+
+  entry.setNextHopRecords(records.begin(), records.end());
+
+  NextHopRecord oneMore;
+  oneMore.setFaceId(40);
+  oneMore.setCost(500);
+
+  entry.addNextHopRecord(oneMore);
+
+  const Block& wire = entry.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(TestFibEntry,
+                                TestFibEntry + sizeof(TestFibEntry),
+                                wire.begin(), wire.end());
+
+  // std::ofstream of("out.tmp");
+  // of.write((const char*)entry.wireEncode().wire(),
+  //          entry.wireEncode().size());
+}
+
+BOOST_AUTO_TEST_CASE(TestFibEntryDecode)
+{
+  FibEntry entry;
+  BOOST_REQUIRE_NO_THROW(entry.wireDecode(Block(TestFibEntry,
+                                                sizeof(TestFibEntry))));
+
+  std::list<NextHopRecord> records = entry.getNextHopRecords();
+
+  BOOST_CHECK_EQUAL(entry.getPrefix(), "/this/is/a/test");
+  BOOST_CHECK_EQUAL(entry.getNextHopRecords().size(), 4);
+
+  size_t value = 1;
+
+  for (std::list<NextHopRecord>::const_iterator i = records.begin();
+       i != records.end();
+       ++i)
+    {
+      BOOST_CHECK_EQUAL(i->getFaceId(), value * 10);
+      BOOST_CHECK_EQUAL(i->getCost(), (value * 100) + 100);
+      ++value;
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibEntry
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/forwarder-status.t.cpp b/tests/unit-tests/mgmt/nfd/forwarder-status.t.cpp
new file mode 100644
index 0000000..b7aefa0f
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/forwarder-status.t.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/forwarder-status.hpp"
+#include "data.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestForwarderStatus)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ForwarderStatus status1;
+  status1.setNfdVersion("0.2.0-65-g75ab6b7");
+  status1.setStartTimestamp(time::fromUnixTimestamp(time::milliseconds(375193249325LL)));
+  status1.setCurrentTimestamp(time::fromUnixTimestamp(time::milliseconds(886109034272LL)));
+  status1.setNNameTreeEntries(1849943160);
+  status1.setNFibEntries(621739748);
+  status1.setNPitEntries(482129741);
+  status1.setNMeasurementsEntries(1771725298);
+  status1.setNCsEntries(1264968688);
+  status1.setNInInterests(612811615);
+  status1.setNInDatas(1843576050);
+  status1.setNInNacks(1234);
+  status1.setNOutInterests(952144445);
+  status1.setNOutDatas(138198826);
+  status1.setNOutNacks(4321);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = status1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  //for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  //}
+  static const uint8_t expected[] = {
+    0x15, 0x65, 0x80, 0x11, 0x30, 0x2e, 0x32, 0x2e, 0x30, 0x2d, 0x36, 0x35,
+    0x2d, 0x67, 0x37, 0x35, 0x61, 0x62, 0x36, 0x62, 0x37, 0x81, 0x08, 0x00,
+    0x00, 0x00, 0x57, 0x5b, 0x42, 0xa6, 0x2d, 0x82, 0x08, 0x00, 0x00, 0x00,
+    0xce, 0x50, 0x36, 0xd7, 0x20, 0x83, 0x04, 0x6e, 0x43, 0xe4, 0x78, 0x84,
+    0x04, 0x25, 0x0e, 0xfe, 0xe4, 0x85, 0x04, 0x1c, 0xbc, 0xb7, 0x4d, 0x86,
+    0x04, 0x69, 0x9a, 0x61, 0xf2, 0x87, 0x04, 0x4b, 0x65, 0xe3, 0xf0, 0x90,
+    0x04, 0x24, 0x86, 0xc3, 0x5f, 0x91, 0x04, 0x6d, 0xe2, 0xbc, 0xf2, 0x97,
+    0x02, 0x04, 0xd2, 0x92, 0x04, 0x38, 0xc0, 0x92, 0x3d, 0x93, 0x04, 0x08,
+    0x3c, 0xbf, 0x2a, 0x98, 0x02, 0x10, 0xe1,
+  };
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                  wire.begin(), wire.end());
+
+  Data data;
+  data.setContent(wire);
+
+  BOOST_REQUIRE_NO_THROW(ForwarderStatus(data.getContent()));
+  ForwarderStatus status2(data.getContent());
+  BOOST_CHECK_EQUAL(status1.getNfdVersion(), status2.getNfdVersion());
+  BOOST_CHECK_EQUAL(status1.getStartTimestamp(), status2.getStartTimestamp());
+  BOOST_CHECK_EQUAL(status1.getCurrentTimestamp(), status2.getCurrentTimestamp());
+  BOOST_CHECK_EQUAL(status1.getNNameTreeEntries(), status2.getNNameTreeEntries());
+  BOOST_CHECK_EQUAL(status1.getNFibEntries(), status2.getNFibEntries());
+  BOOST_CHECK_EQUAL(status1.getNPitEntries(), status2.getNPitEntries());
+  BOOST_CHECK_EQUAL(status1.getNMeasurementsEntries(), status2.getNMeasurementsEntries());
+  BOOST_CHECK_EQUAL(status1.getNCsEntries(), status2.getNCsEntries());
+  BOOST_CHECK_EQUAL(status1.getNInInterests(), status2.getNInInterests());
+  BOOST_CHECK_EQUAL(status1.getNInDatas(), status2.getNInDatas());
+  BOOST_CHECK_EQUAL(status1.getNInNacks(), status2.getNInNacks());
+  BOOST_CHECK_EQUAL(status1.getNOutInterests(), status2.getNOutInterests());
+  BOOST_CHECK_EQUAL(status1.getNOutDatas(), status2.getNOutDatas());
+  BOOST_CHECK_EQUAL(status1.getNOutNacks(), status2.getNOutNacks());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestForwarderStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp b/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp
new file mode 100644
index 0000000..415be29
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/rib-entry.t.cpp
@@ -0,0 +1,347 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/rib-entry.hpp"
+#include "mgmt/nfd/control-command.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestRibEntry)
+
+const uint8_t RouteData[] =
+{
+  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+  0x6d, 0x02, 0x27, 0x10
+};
+
+const uint8_t RouteInfiniteExpirationPeriod[] =
+{
+  0x81, 0x0C, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02
+};
+
+const uint8_t RibEntryData[] =
+{
+  // Header + Name
+  0x80, 0x34, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+  0x08, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64,
+  // Route
+  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+  0x6d, 0x02, 0x27, 0x10,
+  // Route
+  0x81, 0x10, 0x69, 0x01, 0x02, 0x6f, 0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01,
+  0x6d, 0x02, 0x13, 0x88
+};
+
+const uint8_t RibEntryInfiniteExpirationPeriod[] =
+{
+  // Header + Name
+  0x80, 0x30, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+  0x08, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64,
+  // Route
+  0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+  0x6d, 0x02, 0x27, 0x10,
+  // Route with no ExpirationPeriod
+  0x81, 0x0C, 0x69, 0x01, 0x02, 0x6f, 0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01,
+};
+
+BOOST_AUTO_TEST_CASE(RouteEncode)
+{
+  Route route;
+  route.setFaceId(1);
+  route.setOrigin(128);
+  route.setCost(100);
+  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route.setExpirationPeriod(time::milliseconds(10000));
+
+  const Block& wire = route.wireEncode();
+
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(RouteData,
+                                  RouteData + sizeof(RouteData),
+                                  wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(RouteDecode)
+{
+  Route route;
+
+  BOOST_REQUIRE_NO_THROW(route.wireDecode(Block(RouteData, sizeof(RouteData))));
+
+  BOOST_REQUIRE_EQUAL(route.getFaceId(), 1);
+  BOOST_REQUIRE_EQUAL(route.getOrigin(), 128);
+  BOOST_REQUIRE_EQUAL(route.getCost(), 100);
+  BOOST_REQUIRE_EQUAL(route.getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
+  BOOST_REQUIRE_EQUAL(route.getExpirationPeriod(), time::milliseconds(10000));
+  BOOST_REQUIRE_EQUAL(route.hasInfiniteExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(RouteInfiniteExpirationPeriodEncode)
+{
+  Route route;
+  route.setFaceId(1);
+  route.setOrigin(128);
+  route.setCost(100);
+  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
+
+  const Block& wire = route.wireEncode();
+
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(RouteInfiniteExpirationPeriod,
+                                  RouteInfiniteExpirationPeriod + sizeof(RouteInfiniteExpirationPeriod),
+                                  wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(RouteInfiniteExpirationPeriodDecode)
+{
+  Route route;
+
+  BOOST_REQUIRE_NO_THROW(route.wireDecode(Block(RouteInfiniteExpirationPeriod,
+                                                sizeof(RouteInfiniteExpirationPeriod))));
+
+  BOOST_REQUIRE_EQUAL(route.getFaceId(), 1);
+  BOOST_REQUIRE_EQUAL(route.getOrigin(), 128);
+  BOOST_REQUIRE_EQUAL(route.getCost(), 100);
+  BOOST_REQUIRE_EQUAL(route.getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
+  BOOST_REQUIRE_EQUAL(route.getExpirationPeriod(), Route::INFINITE_EXPIRATION_PERIOD);
+  BOOST_REQUIRE_EQUAL(route.hasInfiniteExpirationPeriod(), true);
+}
+
+BOOST_AUTO_TEST_CASE(RouteOutputStream)
+{
+  Route route;
+  route.setFaceId(1);
+  route.setOrigin(128);
+  route.setCost(100);
+  route.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route.setExpirationPeriod(time::milliseconds(10000));
+
+  std::ostringstream os;
+  os << route;
+
+  BOOST_CHECK_EQUAL(os.str(), "Route(FaceId: 1, Origin: 128, Cost: 100, "
+                              "Flags: 2, ExpirationPeriod: 10000 milliseconds)");
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryEncode)
+{
+  RibEntry entry;
+  entry.setName("/hello/world");
+
+  Route route1;
+  route1.setFaceId(1);
+  route1.setOrigin(128);
+  route1.setCost(100);
+  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route1.setExpirationPeriod(time::milliseconds(10000));
+  entry.addRoute(route1);
+
+  Route route2;
+  route2.setFaceId(2);
+  route2.setOrigin(0);
+  route2.setCost(32);
+  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
+  route2.setExpirationPeriod(time::milliseconds(5000));
+  entry.addRoute(route2);
+
+  const Block& wire = entry.wireEncode();
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(RibEntryData,
+                                RibEntryData + sizeof(RibEntryData),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryDecode)
+{
+  RibEntry entry;
+  BOOST_REQUIRE_NO_THROW(entry.wireDecode(Block(RibEntryData,
+                                          sizeof(RibEntryData))));
+
+  BOOST_CHECK_EQUAL(entry.getName(), "/hello/world");
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 2);
+
+  std::list<Route> routes = entry.getRoutes();
+
+  std::list<Route>::const_iterator it = routes.begin();
+  BOOST_CHECK_EQUAL(it->getFaceId(), 1);
+  BOOST_CHECK_EQUAL(it->getOrigin(), 128);
+  BOOST_CHECK_EQUAL(it->getCost(), 100);
+  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
+  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(10000));
+  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
+
+  ++it;
+  BOOST_CHECK_EQUAL(it->getFaceId(), 2);
+  BOOST_CHECK_EQUAL(it->getOrigin(), 0);
+  BOOST_CHECK_EQUAL(it->getCost(), 32);
+  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(5000));
+  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryInfiniteExpirationPeriodEncode)
+{
+  RibEntry entry;
+  entry.setName("/hello/world");
+
+  Route route1;
+  route1.setFaceId(1);
+  route1.setOrigin(128);
+  route1.setCost(100);
+  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route1.setExpirationPeriod(time::milliseconds(10000));
+  entry.addRoute(route1);
+
+  Route route2;
+  route2.setFaceId(2);
+  route2.setOrigin(0);
+  route2.setCost(32);
+  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
+  route2.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
+  entry.addRoute(route2);
+
+  const Block& wire = entry.wireEncode();
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(RibEntryInfiniteExpirationPeriod,
+                                RibEntryInfiniteExpirationPeriod +
+                                sizeof(RibEntryInfiniteExpirationPeriod),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryInfiniteExpirationPeriodDecode)
+{
+  RibEntry entry;
+  BOOST_REQUIRE_NO_THROW(entry.wireDecode(Block(RibEntryInfiniteExpirationPeriod,
+                                          sizeof(RibEntryInfiniteExpirationPeriod))));
+
+  BOOST_CHECK_EQUAL(entry.getName(), "/hello/world");
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 2);
+
+  std::list<Route> routes = entry.getRoutes();
+
+  std::list<Route>::const_iterator it = routes.begin();
+  BOOST_CHECK_EQUAL(it->getFaceId(), 1);
+  BOOST_CHECK_EQUAL(it->getOrigin(), 128);
+  BOOST_CHECK_EQUAL(it->getCost(), 100);
+  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
+  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(10000));
+  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
+
+  ++it;
+  BOOST_CHECK_EQUAL(it->getFaceId(), 2);
+  BOOST_CHECK_EQUAL(it->getOrigin(), 0);
+  BOOST_CHECK_EQUAL(it->getCost(), 32);
+  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), Route::INFINITE_EXPIRATION_PERIOD);
+  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), true);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryClear)
+{
+  RibEntry entry;
+  entry.setName("/hello/world");
+
+  Route route1;
+  route1.setFaceId(1);
+  route1.setOrigin(128);
+  route1.setCost(100);
+  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route1.setExpirationPeriod(time::milliseconds(10000));
+  entry.addRoute(route1);
+  BOOST_REQUIRE_EQUAL(entry.getRoutes().size(), 1);
+
+  std::list<Route> routes = entry.getRoutes();
+
+  std::list<Route>::const_iterator it = routes.begin();
+  BOOST_CHECK_EQUAL(it->getFaceId(), 1);
+  BOOST_CHECK_EQUAL(it->getOrigin(), 128);
+  BOOST_CHECK_EQUAL(it->getCost(), 100);
+  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CAPTURE));
+  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), time::milliseconds(10000));
+  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), false);
+
+  entry.clearRoutes();
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
+
+  Route route2;
+  route2.setFaceId(2);
+  route2.setOrigin(0);
+  route2.setCost(32);
+  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
+  route2.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
+  entry.addRoute(route2);
+  BOOST_REQUIRE_EQUAL(entry.getRoutes().size(), 1);
+
+  routes = entry.getRoutes();
+
+  it = routes.begin();
+  BOOST_CHECK_EQUAL(it->getFaceId(), 2);
+  BOOST_CHECK_EQUAL(it->getOrigin(), 0);
+  BOOST_CHECK_EQUAL(it->getCost(), 32);
+  BOOST_CHECK_EQUAL(it->getFlags(), static_cast<uint64_t>(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(it->getExpirationPeriod(), Route::INFINITE_EXPIRATION_PERIOD);
+  BOOST_CHECK_EQUAL(it->hasInfiniteExpirationPeriod(), true);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryOutputStream)
+{
+  RibEntry entry;
+  entry.setName("/hello/world");
+
+  Route route1;
+  route1.setFaceId(1);
+  route1.setOrigin(128);
+  route1.setCost(100);
+  route1.setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE);
+  route1.setExpirationPeriod(time::milliseconds(10000));
+  entry.addRoute(route1);
+
+  Route route2;
+  route2.setFaceId(2);
+  route2.setOrigin(0);
+  route2.setCost(32);
+  route2.setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT);
+  route2.setExpirationPeriod(Route::INFINITE_EXPIRATION_PERIOD);
+  entry.addRoute(route2);
+
+  std::ostringstream os;
+  os << entry;
+
+  BOOST_CHECK_EQUAL(os.str(), "RibEntry{\n"
+                              "  Name: /hello/world\n"
+                              "  Route(FaceId: 1, Origin: 128, Cost: 100, "
+                              "Flags: 2, ExpirationPeriod: 10000 milliseconds)\n"
+                              "  Route(FaceId: 2, Origin: 0, Cost: 32, "
+                              "Flags: 1, ExpirationPeriod: Infinity)\n"
+                              "}");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibEntry
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/status-dataset.t.cpp b/tests/unit-tests/mgmt/nfd/status-dataset.t.cpp
new file mode 100644
index 0000000..aadb2c4
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/status-dataset.t.cpp
@@ -0,0 +1,459 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/status-dataset.hpp"
+#include "mgmt/nfd/controller.hpp"
+
+#include "controller-fixture.hpp"
+#include "../../make-interest-data.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+
+class ControllerStatusDatasetFixture : public ControllerFixture
+{
+protected:
+  /** \brief send one WireEncodable as Data reply
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload payload block
+   *  \note payload must fit in one Data
+   */
+  template<typename T>
+  void
+  sendDataset(const Name& prefix, const T& payload)
+  {
+    BOOST_CONCEPT_ASSERT((WireEncodable<T>));
+
+    auto data = this->prepareDatasetReply(prefix);
+    data->setContent(payload.wireEncode());
+    face.receive(*signData(data));
+  }
+
+  /** \brief send two WireEncodables as Data reply
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload1 first vector item
+   *  \param payload2 second vector item
+   *  \note all payloads must fit in one Data
+   */
+  template<typename T1, typename T2>
+  void
+  sendDataset(const Name& prefix, const T1& payload1, const T2& payload2)
+  {
+    // The test suite allows up to two items, and put them in the same Data packet,
+    // because this test suite focuses on Controller::fetch<StatusDataset>,
+    // and is not intended to cover SegmentFetcher behavior.
+
+    BOOST_CONCEPT_ASSERT((WireEncodable<T1>));
+    BOOST_CONCEPT_ASSERT((WireEncodable<T2>));
+
+    ndn::encoding::EncodingBuffer buffer;
+    payload2.wireEncode(buffer);
+    payload1.wireEncode(buffer);
+
+    auto data = this->prepareDatasetReply(prefix);
+    data->setContent(buffer.buf(), buffer.size());
+    face.receive(*signData(data));
+  }
+
+private:
+  shared_ptr<Data>
+  prepareDatasetReply(const Name& prefix)
+  {
+    Name name = prefix;
+    name.appendVersion().appendSegment(0);
+
+    // These warnings assist in debugging a `hasResult` check failure.
+    // They usually indicate a misspelled prefix or incorrect timing in the test case.
+    if (face.sentInterests.size() < 1) {
+      BOOST_WARN_MESSAGE(false, "no Interest expressed");
+    }
+    else {
+      BOOST_WARN_MESSAGE(face.sentInterests.back().getName().isPrefixOf(name),
+                         "last Interest " << face.sentInterests.back().getName() <<
+                         " cannot be satisfied by this Data " << name);
+    }
+
+    auto data = make_shared<Data>(name);
+    data->setFinalBlockId(data->getName()[-1]);
+    return data;
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestStatusDataset, ControllerStatusDatasetFixture)
+
+BOOST_AUTO_TEST_SUITE(Failures)
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  CommandOptions options;
+  options.setTimeout(time::milliseconds(3000));
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(time::milliseconds(500), 7);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_TIMEOUT);
+}
+
+BOOST_AUTO_TEST_CASE(DataHasNoSegment)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  face.receive(*makeData("/localhost/nfd/faces/list/%FD%00"));
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  this->setValidationResult(false);
+
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  FaceStatus payload;
+  payload.setFaceId(5744);
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_VALIDATION);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  face.receive(lp::Nack(face.sentInterests.back()));
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_NACK);
+}
+
+BOOST_AUTO_TEST_CASE(ParseError1)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  Name payload; // Name is not valid FaceStatus
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_CASE(ParseError2)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  FaceStatus payload1;
+  payload1.setFaceId(10930);
+  Name payload2; // Name is not valid FaceStatus
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Failures
+
+BOOST_AUTO_TEST_SUITE(NoCallback)
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+  controller.fetch<FaceDataset>(
+    nullptr,
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  FaceStatus payload;
+  payload.setFaceId(2577);
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(time::milliseconds(500)));
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Failure)
+{
+  CommandOptions options;
+  options.setTimeout(time::milliseconds(3000));
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    nullptr,
+    options);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(time::milliseconds(500), 7));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // NoCallback
+
+BOOST_AUTO_TEST_SUITE(Datasets)
+
+BOOST_AUTO_TEST_CASE(StatusGeneral)
+{
+  bool hasResult = false;
+  controller.fetch<ForwarderGeneralStatusDataset>(
+    [&hasResult] (const ForwarderStatus& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.getNfdVersion(), "0.4.2");
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.2");
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceList)
+{
+  bool hasResult = false;
+  controller.fetch<FaceDataset>(
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 24485);
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  FaceStatus payload1;
+  payload1.setFaceId(24485);
+  FaceStatus payload2;
+  payload2.setFaceId(12987);
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceQuery)
+{
+  FaceQueryFilter filter;
+  filter.setUriScheme("udp4");
+  bool hasResult = false;
+  controller.fetch<FaceQueryDataset>(
+    filter,
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 8795);
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  Name prefix("/localhost/nfd/faces/query");
+  prefix.append(filter.wireEncode());
+  FaceStatus payload;
+  payload.setFaceId(8795);
+  this->sendDataset(prefix, payload);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceQueryWithOptions)
+{
+  FaceQueryFilter filter;
+  filter.setUriScheme("udp4");
+  CommandOptions options;
+  options.setTimeout(time::milliseconds(3000));
+  bool hasResult = false;
+  controller.fetch<FaceQueryDataset>(
+    filter,
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 14022);
+    },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(time::milliseconds(500));
+
+  Name prefix("/localhost/nfd/faces/query");
+  prefix.append(filter.wireEncode());
+  FaceStatus payload;
+  payload.setFaceId(14022);
+  this->sendDataset(prefix, payload);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceChannels)
+{
+  bool hasResult = false;
+  controller.fetch<ChannelDataset>(
+    [&hasResult] (const std::vector<ChannelStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getLocalUri(), "tcp4://192.0.2.1:6363");
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  ChannelStatus payload1;
+  payload1.setLocalUri("tcp4://192.0.2.1:6363");
+  ChannelStatus payload2;
+  payload2.setLocalUri("udp4://192.0.2.1:6363");
+  this->sendDataset("/localhost/nfd/faces/channels", payload1, payload2);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FibList)
+{
+  bool hasResult = false;
+  controller.fetch<FibDataset>(
+    [&hasResult] (const std::vector<FibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getPrefix(), "/wYs7fzYcfG");
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  FibEntry payload1;
+  payload1.setPrefix("/wYs7fzYcfG");
+  FibEntry payload2;
+  payload2.setPrefix("/LKvmnzY5S");
+  this->sendDataset("/localhost/nfd/fib/list", payload1, payload2);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceList)
+{
+  bool hasResult = false;
+  controller.fetch<StrategyChoiceDataset>(
+    [&hasResult] (const std::vector<StrategyChoice>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/8MLz6N3B");
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  StrategyChoice payload1;
+  payload1.setName("/8MLz6N3B");
+  StrategyChoice payload2;
+  payload2.setName("/svqcBu0YwU");
+  this->sendDataset("/localhost/nfd/strategy-choice/list", payload1, payload2);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibList)
+{
+  bool hasResult = false;
+  controller.fetch<RibDataset>(
+    [&hasResult] (const std::vector<RibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/zXxBth97ee");
+    },
+    datasetFailCallback);
+  this->advanceClocks(time::milliseconds(500));
+
+  RibEntry payload1;
+  payload1.setName("/zXxBth97ee");
+  RibEntry payload2;
+  payload2.setName("/rJ8CvUpr4G");
+  this->sendDataset("/localhost/nfd/rib/list", payload1, payload2);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibListWithOptions)
+{
+  CommandOptions options;
+  options.setPrefix("/localhop/nfd");
+  bool hasResult = false;
+  controller.fetch<RibDataset>(
+    [&hasResult] (const std::vector<RibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/e6L5K4ascd");
+    },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(time::milliseconds(500));
+
+  RibEntry payload;
+  payload.setName("/e6L5K4ascd");
+  this->sendDataset("/localhop/nfd/rib/list", payload);
+  this->advanceClocks(time::milliseconds(500));
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Datasets
+
+BOOST_AUTO_TEST_SUITE_END() // TestStatusDataset
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit-tests/mgmt/nfd/strategy-choice.t.cpp b/tests/unit-tests/mgmt/nfd/strategy-choice.t.cpp
new file mode 100644
index 0000000..4fca737
--- /dev/null
+++ b/tests/unit-tests/mgmt/nfd/strategy-choice.t.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/strategy-choice.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestStrategyChoice)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  StrategyChoice strategyChoice1;
+  strategyChoice1
+    .setName("/hello/world")
+    .setStrategy("/some/non/existing/strategy/name")
+    ;
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = strategyChoice1.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x80, 0x39, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x08, 0x05, 0x77,
+    0x6f, 0x72, 0x6c, 0x64, 0x6b, 0x27, 0x07, 0x25, 0x08, 0x04, 0x73, 0x6f, 0x6d, 0x65,
+    0x08, 0x03, 0x6e, 0x6f, 0x6e, 0x08, 0x08, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e,
+    0x67, 0x08, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x08, 0x04, 0x6e,
+    0x61, 0x6d, 0x65
+  };
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                  wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(StrategyChoice(wire));
+  StrategyChoice strategyChoice2(wire);
+  BOOST_CHECK_EQUAL(strategyChoice1.getName(), strategyChoice2.getName());
+  BOOST_CHECK_EQUAL(strategyChoice1.getStrategy(), strategyChoice2.getStrategy());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStrategyChoice
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn