tools: refactor nfd-status

refs #3658

Change-Id: Ia347074bea802eba5f539208e276e849a60db8a4
diff --git a/.jenkins.d/20-tests.sh b/.jenkins.d/20-tests.sh
index f12ee59..89ff5da 100755
--- a/.jenkins.d/20-tests.sh
+++ b/.jenkins.d/20-tests.sh
@@ -18,6 +18,7 @@
   sudo setcap cap_net_raw,cap_net_admin=eip `pwd`/build/unit-tests-core || true
   sudo setcap cap_net_raw,cap_net_admin=eip `pwd`/build/unit-tests-daemon || true
   sudo setcap cap_net_raw,cap_net_admin=eip `pwd`/build/unit-tests-rib || true
+  # unit-tests-tools does not need capabilities
 fi
 
 ndnsec-keygen "/tmp/jenkins/$NODE_NAME" | ndnsec-install-cert -
@@ -31,6 +32,8 @@
   ./build/unit-tests-daemon -l all -- --log_format2=XML --log_sink2=build/xunit-daemon-report.xml
 
   ./build/unit-tests-rib -l all -- --log_format2=XML --log_sink2=build/xunit-rib-report.xml
+
+  ./build/unit-tests-tools -l all -- --log_format2=XML --log_sink2=build/xunit-tools-report.xml
 else
   ./build/unit-tests-core -l test_suite
   sudo ./build/unit-tests-core -t TestPrivilegeHelper -l test_suite
@@ -38,4 +41,6 @@
   ./build/unit-tests-daemon -l test_suite
 
   ./build/unit-tests-rib -l test_suite
+
+  ./build/unit-tests-tools -l test_suite
 fi
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
index 9774363..0f7d3c4 100644
--- a/tests/identity-management-fixture.hpp
+++ b/tests/identity-management-fixture.hpp
@@ -1,22 +1,26 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2014 Regents of the University of California.
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
  *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
  *
- * 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.
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
  *
- * 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.
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
  *
- * You should have received 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.
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifndef NFD_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
@@ -55,6 +59,11 @@
   std::vector<ndn::Name> m_identities;
 };
 
+class IdentityManagementTimeFixture : public UnitTestTimeFixture
+                                    , public IdentityManagementFixture
+{
+};
+
 } // namespace tests
 } // namespace nfd
 
diff --git a/tests/tools/nfd-status/channel-module.t.cpp b/tests/tools/nfd-status/channel-module.t.cpp
new file mode 100644
index 0000000..f524907
--- /dev/null
+++ b/tests/tools/nfd-status/channel-module.t.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/channel-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestChannelModule, ModuleFixture<ChannelModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <channels>
+    <channel>
+      <localUri>tcp4://192.0.2.1:6363</localUri>
+    </channel>
+    <channel>
+      <localUri>ws://[::]:9696/NFD</localUri>
+    </channel>
+  </channels>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+Channels:
+  tcp4://192.0.2.1:6363
+  ws://[::]:9696/NFD
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  ChannelStatus payload1;
+  payload1.setLocalUri("tcp4://192.0.2.1:6363");
+  ChannelStatus payload2;
+  payload2.setLocalUri("ws://[::]:9696/NFD");
+  this->sendDataset("/localhost/nfd/faces/channels", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestChannelModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/face-module.t.cpp b/tests/tools/nfd-status/face-module.t.cpp
new file mode 100644
index 0000000..292ccd8
--- /dev/null
+++ b/tests/tools/nfd-status/face-module.t.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/face-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestFaceModule, ModuleFixture<FaceModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <faces>
+    <face>
+      <faceId>134</faceId>
+      <remoteUri>udp4://233.252.0.4:6363</remoteUri>
+      <localUri>udp4://192.0.2.1:6363</localUri>
+      <faceScope>non-local</faceScope>
+      <facePersistency>permanent</facePersistency>
+      <linkType>multi-access</linkType>
+      <packetCounters>
+        <incomingPackets>
+          <nInterests>22562</nInterests>
+          <nDatas>22031</nDatas>
+          <nNacks>63</nNacks>
+        </incomingPackets>
+        <outgoingPackets>
+          <nInterests>30121</nInterests>
+          <nDatas>20940</nDatas>
+          <nNacks>1218</nNacks>
+        </outgoingPackets>
+      </packetCounters>
+      <byteCounters>
+        <incomingBytes>2522915</incomingBytes>
+        <outgoingBytes>1353592</outgoingBytes>
+      </byteCounters>
+    </face>
+    <face>
+      <faceId>745</faceId>
+      <remoteUri>fd://75</remoteUri>
+      <localUri>unix:///var/run/nfd.sock</localUri>
+      <faceScope>local</faceScope>
+      <facePersistency>on-demand</facePersistency>
+      <linkType>point-to-point</linkType>
+      <packetCounters>
+        <incomingPackets>
+          <nInterests>18998</nInterests>
+          <nDatas>26701</nDatas>
+          <nNacks>147</nNacks>
+        </incomingPackets>
+        <outgoingPackets>
+          <nInterests>34779</nInterests>
+          <nDatas>17028</nDatas>
+          <nNacks>1176</nNacks>
+        </outgoingPackets>
+      </packetCounters>
+      <byteCounters>
+        <incomingBytes>4672308</incomingBytes>
+        <outgoingBytes>8957187</outgoingBytes>
+      </byteCounters>
+    </face>
+  </faces>
+)XML");
+
+const std::string STATUS_TEXT =
+  "Faces:\n"
+  "  faceid=134 remote=udp4://233.252.0.4:6363 local=udp4://192.0.2.1:6363"
+    " counters={in={22562i 22031d 63n 2522915B} out={30121i 20940d 1218n 1353592B}}"
+    " non-local permanent multi-access\n"
+  "  faceid=745 remote=fd://75 local=unix:///var/run/nfd.sock"
+    " counters={in={18998i 26701d 147n 4672308B} out={34779i 17028d 1176n 8957187B}}"
+    " local on-demand point-to-point\n";
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  FaceStatus payload1;
+  payload1.setFaceId(134)
+          .setRemoteUri("udp4://233.252.0.4:6363")
+          .setLocalUri("udp4://192.0.2.1:6363")
+          .setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL)
+          .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT)
+          .setLinkType(ndn::nfd::LINK_TYPE_MULTI_ACCESS)
+          .setNInInterests(22562)
+          .setNInDatas(22031)
+          .setNInNacks(63)
+          .setNOutInterests(30121)
+          .setNOutDatas(20940)
+          .setNOutNacks(1218)
+          .setNInBytes(2522915)
+          .setNOutBytes(1353592);
+  FaceStatus payload2;
+  payload2.setFaceId(745)
+          .setRemoteUri("fd://75")
+          .setLocalUri("unix:///var/run/nfd.sock")
+          .setFaceScope(ndn::nfd::FACE_SCOPE_LOCAL)
+          .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND)
+          .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
+          .setNInInterests(18998)
+          .setNInDatas(26701)
+          .setNInNacks(147)
+          .setNOutInterests(34779)
+          .setNOutDatas(17028)
+          .setNOutNacks(1176)
+          .setNInBytes(4672308)
+          .setNOutBytes(8957187);
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/fib-module.t.cpp b/tests/tools/nfd-status/fib-module.t.cpp
new file mode 100644
index 0000000..f509461
--- /dev/null
+++ b/tests/tools/nfd-status/fib-module.t.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/fib-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestFibModule, ModuleFixture<FibModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <fib>
+    <fibEntry>
+      <prefix>/</prefix>
+      <nextHops>
+        <nextHop>
+          <faceId>262</faceId>
+          <cost>9</cost>
+        </nextHop>
+        <nextHop>
+          <faceId>272</faceId>
+          <cost>50</cost>
+        </nextHop>
+        <nextHop>
+          <faceId>274</faceId>
+          <cost>78</cost>
+        </nextHop>
+      </nextHops>
+    </fibEntry>
+    <fibEntry>
+      <prefix>/localhost/nfd</prefix>
+      <nextHops>
+        <nextHop>
+          <faceId>1</faceId>
+          <cost>0</cost>
+        </nextHop>
+        <nextHop>
+          <faceId>274</faceId>
+          <cost>0</cost>
+        </nextHop>
+      </nextHops>
+    </fibEntry>
+  </fib>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+FIB:
+  / nexthops={faceid=262 (cost=9), faceid=272 (cost=50), faceid=274 (cost=78)}
+  /localhost/nfd nexthops={faceid=1 (cost=0), faceid=274 (cost=0)}
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  FibEntry payload1;
+  payload1.setPrefix("/")
+          .addNextHopRecord(NextHopRecord().setFaceId(262).setCost(9))
+          .addNextHopRecord(NextHopRecord().setFaceId(272).setCost(50))
+          .addNextHopRecord(NextHopRecord().setFaceId(274).setCost(78));
+  FibEntry payload2;
+  payload2.setPrefix("/localhost/nfd")
+          .addNextHopRecord(NextHopRecord().setFaceId(1).setCost(0))
+          .addNextHopRecord(NextHopRecord().setFaceId(274).setCost(0));
+  this->sendDataset("/localhost/nfd/fib/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/format-helpers.t.cpp b/tests/tools/nfd-status/format-helpers.t.cpp
new file mode 100644
index 0000000..01d7f47
--- /dev/null
+++ b/tests/tools/nfd-status/format-helpers.t.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/format-helpers.hpp"
+
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_AUTO_TEST_SUITE(TestFormatHelpers)
+
+BOOST_AUTO_TEST_SUITE(Xml)
+
+BOOST_AUTO_TEST_CASE(TextEscaping)
+{
+  output_test_stream os;
+  os << xml::Text{"\"less than\" & 'greater than' surround XML <element> tag name"};
+
+  BOOST_CHECK(os.is_equal("&quot;less than&quot; &amp; &apos;greater than&apos;"
+                          " surround XML &lt;element&gt; tag name"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Xml
+
+BOOST_AUTO_TEST_SUITE(Text)
+
+BOOST_AUTO_TEST_CASE(Sep)
+{
+  output_test_stream os;
+  text::Separator sep(",");
+  for (int i = 1; i <= 3; ++i) {
+    os << sep << i;
+  }
+
+  BOOST_CHECK(os.is_equal("1,2,3"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Text
+
+BOOST_AUTO_TEST_SUITE_END() // TestFormatHelpers
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/forwarder-general-module.t.cpp b/tests/tools/nfd-status/forwarder-general-module.t.cpp
new file mode 100644
index 0000000..e9c881b
--- /dev/null
+++ b/tests/tools/nfd-status/forwarder-general-module.t.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/forwarder-general-module.hpp"
+#include <ndn-cxx/security/signing-helpers.hpp>
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestForwarderGeneralModule, ModuleFixture<ForwarderGeneralModule>)
+
+class MakeNfdIdCollector
+{
+public:
+  unique_ptr<NfdIdCollector>
+  operator()(Face&, KeyChain&) const
+  {
+    return make_unique<NfdIdCollector>(make_unique<ValidatorNull>());
+  };
+};
+
+class ForwarderGeneralStatusFixture : public ModuleFixture<ForwarderGeneralModule,
+                                                           MakeNfdIdCollector>
+{
+protected:
+  ForwarderGeneralStatusFixture()
+  {
+    module.setNfdIdCollector(*validator);
+
+    this->systemClock->setNow(time::seconds(1468784936));
+    BOOST_REQUIRE(this->addIdentity("/nfd-status/test-nfdid"));
+  }
+
+private:
+  virtual void
+  signDatasetReply(Data& data) override
+  {
+    m_keyChain.sign(data, ndn::security::signingByIdentity("/nfd-status/test-nfdid"));
+  }
+};
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <generalStatus>
+    <nfdId>/nfd-status/test-nfdid/KEY/ksk-1468784936000/ID-CERT</nfdId>
+    <version>0.4.1-1-g704430c</version>
+    <startTime>2016-06-24T15:13:46.856000</startTime>
+    <currentTime>2016-07-17T17:55:54.109000</currentTime>
+    <uptime>PT1996927S</uptime>
+    <nNameTreeEntries>668</nNameTreeEntries>
+    <nFibEntries>70</nFibEntries>
+    <nPitEntries>7</nPitEntries>
+    <nMeasurementsEntries>1</nMeasurementsEntries>
+    <nCsEntries>65536</nCsEntries>
+    <packetCounters>
+      <incomingPackets>
+        <nInterests>20699052</nInterests>
+        <nDatas>5598070</nDatas>
+        <nNacks>7230</nNacks>
+      </incomingPackets>
+      <outgoingPackets>
+        <nInterests>36501092</nInterests>
+        <nDatas>5671942</nDatas>
+        <nNacks>26762</nNacks>
+      </outgoingPackets>
+    </packetCounters>
+  </generalStatus>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+General NFD status:
+                 nfdId=/nfd-status/test-nfdid/KEY/ksk-1468784936000/ID-CERT
+               version=0.4.1-1-g704430c
+             startTime=20160624T151346.856000
+           currentTime=20160717T175554.109000
+                uptime=1996927 seconds
+      nNameTreeEntries=668
+           nFibEntries=70
+           nPitEntries=7
+  nMeasurementsEntries=1
+            nCsEntries=65536
+          nInInterests=20699052
+         nOutInterests=36501092
+              nInDatas=5598070
+             nOutDatas=5671942
+              nInNacks=7230
+             nOutNacks=26762
+)TEXT").substr(1);
+
+BOOST_FIXTURE_TEST_CASE(Status, ForwarderGeneralStatusFixture)
+{
+  this->fetchStatus();
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.1-1-g704430c")
+         .setStartTimestamp(time::fromUnixTimestamp(time::milliseconds(1466781226856)))
+         .setCurrentTimestamp(time::fromUnixTimestamp(time::milliseconds(1468778154109)))
+         .setNNameTreeEntries(668)
+         .setNFibEntries(70)
+         .setNPitEntries(7)
+         .setNMeasurementsEntries(1)
+         .setNCsEntries(65536)
+         .setNInInterests(20699052)
+         .setNInDatas(5598070)
+         .setNInNacks(7230)
+         .setNOutInterests(36501092)
+         .setNOutDatas(5671942)
+         .setNOutNacks(26762);
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_CASE(StatusNoNfdId)
+{
+  this->fetchStatus();
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.1-1-g704430c");
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  BOOST_CHECK_NO_THROW(this->prepareStatusOutput());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestForwarderGeneralModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/module-fixture.hpp b/tests/tools/nfd-status/module-fixture.hpp
new file mode 100644
index 0000000..38d0d64
--- /dev/null
+++ b/tests/tools/nfd-status/module-fixture.hpp
@@ -0,0 +1,236 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TESTS_TOOLS_NFD_STATUS_MODULE_FIXTURE_HPP
+#define NFD_TESTS_TOOLS_NFD_STATUS_MODULE_FIXTURE_HPP
+
+#include "nfd-status/module.hpp"
+#include <ndn-cxx/security/validator-null.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#include "tests/test-common.hpp"
+#include "tests/identity-management-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+using namespace nfd::tests;
+using ndn::Face;
+using ndn::KeyChain;
+using ndn::Validator;
+using ndn::ValidatorNull;
+using ndn::util::DummyClientFace;
+using boost::test_tools::output_test_stream;
+
+class MakeValidatorNull
+{
+public:
+  unique_ptr<ValidatorNull>
+  operator()(Face&, KeyChain&) const
+  {
+    return make_unique<ValidatorNull>();
+  };
+};
+
+/** \brief fixture to test a \p Module
+ *  \tparam MODULE a subclass of \p Module
+ *  \tparam MakeValidator a callable to make a Validator for use in \p controller;
+ *                        MakeValidator()(Face&, KeyChain&) should return a unique_ptr
+ *                        to Validator or its subclass
+ */
+template<typename MODULE, typename MakeValidator = MakeValidatorNull>
+class ModuleFixture : public UnitTestTimeFixture
+                    , public IdentityManagementFixture
+{
+protected:
+  typedef typename std::result_of<MakeValidator(Face&, KeyChain&)>::type ValidatorUniquePtr;
+
+  ModuleFixture()
+    : face(g_io, m_keyChain)
+    , validator(MakeValidator()(face, m_keyChain))
+    , controller(face, m_keyChain, *validator)
+    , nFetchStatusSuccess(0)
+  {
+  }
+
+protected: // status fetching
+  /** \brief start fetching status
+   *
+   *  A test case should call \p fetchStatus, \p sendDataset, and \p prepareStatusOutput
+   *  in this order, and then check \p statusXml and \p statusText contain the correct outputs.
+   *  No advanceClocks is needed in between, as they are handled by the fixture.
+   */
+  void
+  fetchStatus()
+  {
+    nFetchStatusSuccess = 0;
+    module.fetchStatus(controller, [this] { ++nFetchStatusSuccess; },
+                       [this] (uint32_t code, const std::string& reason) {
+                         BOOST_FAIL("fetchStatus failure " << code << " " << reason);
+                       },
+                       CommandOptions());
+    this->advanceClocks(time::milliseconds(1));
+  }
+
+  /** \brief send one WireEncodable in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload payload block
+   *  \note payload must fit in one Data
+   *  \pre fetchStatus has been invoked, sendDataset has not been invoked
+   */
+  template<typename T>
+  void
+  sendDataset(const Name& prefix, const T& payload)
+  {
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T>));
+
+    this->sendDatasetReply(prefix, payload.wireEncode());
+  }
+
+  /** \brief send two WireEncodables in reply to StatusDataset request
+   *  \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
+   *  \pre fetchStatus has been invoked, sendDataset has not been invoked
+   */
+  template<typename T1, typename T2>
+  void
+  sendDataset(const Name& prefix, const T1& payload1, const T2& payload2)
+  {
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T1>));
+    BOOST_CONCEPT_ASSERT((ndn::WireEncodable<T2>));
+
+    ndn::encoding::EncodingBuffer buffer;
+    payload2.wireEncode(buffer);
+    payload1.wireEncode(buffer);
+
+    this->sendDatasetReply(prefix, buffer.buf(), buffer.size());
+  }
+
+  /** \brief prepare status output as XML and text
+   *  \pre sendDataset has been invoked
+   */
+  void
+  prepareStatusOutput()
+  {
+    this->advanceClocks(time::milliseconds(1));
+    BOOST_REQUIRE_EQUAL(nFetchStatusSuccess, 1);
+
+    statusXml.str("");
+    module.formatStatusXml(statusXml);
+    statusText.str("");
+    module.formatStatusText(statusText);
+  }
+
+private:
+  /** \brief send a payload in reply to StatusDataset request
+   *  \param prefix dataset prefix without version and segment
+   *  \param contentArgs passed to Data::setContent
+   */
+  template<typename ...ContentArgs>
+  void
+  sendDatasetReply(const Name& prefix, ContentArgs&&...contentArgs)
+  {
+    Name name = prefix;
+    name.appendVersion().appendSegment(0);
+
+    // These warnings assist in debugging a `nFetchStatusSuccess != 1` 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(name[-1]);
+    data->setContent(std::forward<ContentArgs>(contentArgs)...);
+    this->signDatasetReply(*data);
+    face.receive(*data);
+  }
+
+  virtual void
+  signDatasetReply(Data& data)
+  {
+    signData(data);
+  }
+
+protected:
+  DummyClientFace face;
+  ValidatorUniquePtr validator;
+  Controller controller;
+
+  MODULE module;
+
+  int nFetchStatusSuccess;
+  output_test_stream statusXml;
+  output_test_stream statusText;
+};
+
+/** \brief strips leading spaces on every line in expected XML
+ *
+ *  This allows expected XML to be written as:
+ *  \code
+ *  const std::string STATUS_XML = stripXmlSpaces(R"XML(
+ *    <rootElement>
+ *      <element>value</element>
+ *    </rootElement>
+ *  )XML");
+ *  \endcode
+ *  And \p STATUS_XML would be assigned:
+ *  \code
+ *  "<rootElement><element>value</element></rootElement>"
+ *  \endcode
+ */
+inline std::string
+stripXmlSpaces(const std::string& xml)
+{
+  std::string s;
+  bool isSkipping = true;
+  std::copy_if(xml.begin(), xml.end(), std::back_inserter(s),
+               [&isSkipping] (char ch) {
+                 if (ch == '\n') {
+                   isSkipping = true;
+                 }
+                 else if (ch != ' ') {
+                   isSkipping = false;
+                 }
+                 return !isSkipping;
+               });
+  return s;
+}
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TESTS_TOOLS_NFD_STATUS_MODULE_FIXTURE_HPP
diff --git a/tests/tools/nfd-status/rib-module.t.cpp b/tests/tools/nfd-status/rib-module.t.cpp
new file mode 100644
index 0000000..bfd663c
--- /dev/null
+++ b/tests/tools/nfd-status/rib-module.t.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/rib-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestRibModule, ModuleFixture<RibModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <rib>
+    <ribEntry>
+      <prefix>/</prefix>
+      <routes>
+        <route>
+          <faceId>262</faceId>
+          <origin>255</origin>
+          <cost>9</cost>
+          <flags>
+            <ribCapture/>
+          </flags>
+        </route>
+        <route>
+          <faceId>272</faceId>
+          <origin>255</origin>
+          <cost>50</cost>
+          <flags/>
+        </route>
+        <route>
+          <faceId>274</faceId>
+          <origin>255</origin>
+          <cost>78</cost>
+          <flags>
+            <childInherit/>
+            <ribCapture/>
+          </flags>
+        </route>
+        <route>
+          <faceId>276</faceId>
+          <origin>255</origin>
+          <cost>79</cost>
+          <flags>
+            <childInherit/>
+          </flags>
+          <expirationPeriod>PT47S</expirationPeriod>
+        </route>
+      </routes>
+    </ribEntry>
+    <ribEntry>
+      <prefix>/localhost/nfd</prefix>
+      <routes>
+        <route>
+          <faceId>258</faceId>
+          <origin>0</origin>
+          <cost>0</cost>
+          <flags>
+            <childInherit/>
+          </flags>
+        </route>
+      </routes>
+    </ribEntry>
+  </rib>
+)XML");
+
+const std::string STATUS_TEXT =
+  "RIB:\n"
+  "  / route={faceid=262 (origin=255 cost=9 RibCapture), faceid=272 (origin=255 cost=50), "
+    "faceid=274 (origin=255 cost=78 ChildInherit RibCapture), "
+    "faceid=276 (origin=255 cost=79 expires=47s ChildInherit)}\n"
+  "  /localhost/nfd route={faceid=258 (origin=0 cost=0 ChildInherit)}\n";
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  RibEntry payload1;
+  payload1.setName("/")
+          .addRoute(Route().setFaceId(262)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(9)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE))
+          .addRoute(Route().setFaceId(272)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(50)
+                           .setFlags(ndn::nfd::ROUTE_FLAGS_NONE))
+          .addRoute(Route().setFaceId(274)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(78)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT | ndn::nfd::ROUTE_FLAG_CAPTURE))
+          .addRoute(Route().setFaceId(276)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+                           .setCost(79)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+                           .setExpirationPeriod(time::milliseconds(47292)));
+  RibEntry payload2;
+  payload2.setName("/localhost/nfd")
+          .addRoute(Route().setFaceId(258)
+                           .setOrigin(ndn::nfd::ROUTE_ORIGIN_APP)
+                           .setCost(0)
+                           .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+  this->sendDataset("/localhost/nfd/rib/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/status-report.t.cpp b/tests/tools/nfd-status/status-report.t.cpp
new file mode 100644
index 0000000..309c83a
--- /dev/null
+++ b/tests/tools/nfd-status/status-report.t.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/status-report.hpp"
+#include "core/scheduler.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <?xml version="1.0"?>
+  <nfdStatus xmlns="ndn:/localhost/nfd/status/1">
+    <module1/>
+    <module2/>
+  </nfdStatus>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+module1
+module2
+)TEXT").substr(1);
+
+class DummyModule : public Module
+{
+public:
+  explicit
+  DummyModule(const std::string& moduleName)
+    : m_moduleName(moduleName)
+    , m_res(0)
+    , m_delay(time::milliseconds(1))
+  {
+  }
+
+  /** \brief cause fetchStatus to succeed or fail
+   *  \param res zero to succeed, non-zero to fail with specific code
+   *  \param delay duration from fetchStatus invocation to succeed or fail; must be positive
+   */
+  void
+  setResult(uint32_t res, time::nanoseconds delay)
+  {
+    BOOST_ASSERT(delay > time::nanoseconds::zero());
+    m_res = res;
+    m_delay = delay;
+  }
+
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override
+  {
+    ++nFetchStatusCalls;
+    scheduler::schedule(m_delay, [=] {
+      if (m_res == 0) {
+        onSuccess();
+      }
+      else {
+        onFailure(m_res, m_moduleName + " fails with code " + to_string(m_res));
+      }
+    });
+  }
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override
+  {
+    os << '<' << m_moduleName << "/>";
+  }
+
+  virtual void
+  formatStatusText(std::ostream& os) const override
+  {
+    os << m_moduleName << '\n';
+  }
+
+public:
+  int nFetchStatusCalls = 0;
+
+private:
+  std::string m_moduleName;
+  uint32_t m_res;
+  time::nanoseconds m_delay;
+};
+
+class StatusReportTester : public StatusReport
+{
+private:
+  virtual void
+  processEvents(Face&) override
+  {
+    processEventsFunc();
+  }
+
+public:
+  std::function<void()> processEventsFunc;
+};
+
+class StatusReportModulesFixture : public IdentityManagementTimeFixture
+{
+protected:
+  StatusReportModulesFixture()
+    : face(g_io, m_keyChain)
+    , controller(face, m_keyChain, validator)
+    , res(0)
+  {
+  }
+
+  DummyModule&
+  addModule(const std::string& moduleName)
+  {
+    report.sections.push_back(make_unique<DummyModule>(moduleName));
+    return static_cast<DummyModule&>(*report.sections.back());
+  }
+
+  void
+  collect(time::nanoseconds tick, size_t nTicks)
+  {
+    report.processEventsFunc = [=] {
+      this->advanceClocks(tick, nTicks);
+    };
+    res = report.collect(face, m_keyChain, validator, CommandOptions());
+
+    if (res == 0) {
+      statusXml.str("");
+      report.formatXml(statusXml);
+      statusText.str("");
+      report.formatText(statusText);
+    }
+  }
+
+protected:
+  DummyClientFace face;
+  ValidatorNull validator;
+  Controller controller;
+  StatusReportTester report;
+
+  uint32_t res;
+  output_test_stream statusXml;
+  output_test_stream statusText;
+};
+
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestStatusReport, StatusReportModulesFixture)
+
+BOOST_AUTO_TEST_CASE(Normal)
+{
+  DummyModule& m1 = addModule("module1");
+  m1.setResult(0, time::milliseconds(10));
+  DummyModule& m2 = addModule("module2");
+  m2.setResult(0, time::milliseconds(20));
+
+  this->collect(time::milliseconds(5), 6);
+
+  BOOST_CHECK_EQUAL(m1.nFetchStatusCalls, 1);
+  BOOST_CHECK_EQUAL(m2.nFetchStatusCalls, 1);
+
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_CASE(Reorder)
+{
+  DummyModule& m1 = addModule("module1");
+  m1.setResult(0, time::milliseconds(20));
+  DummyModule& m2 = addModule("module2");
+  m2.setResult(0, time::milliseconds(10)); // module2 completes earlier than module1
+
+  this->collect(time::milliseconds(5), 6);
+
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML)); // output is still in order
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_CASE(Error)
+{
+  DummyModule& m1 = addModule("module1");
+  m1.setResult(0, time::milliseconds(20));
+  DummyModule& m2 = addModule("module2");
+  m2.setResult(500, time::milliseconds(10));
+
+  this->collect(time::milliseconds(5), 6);
+
+  BOOST_CHECK_EQUAL(res, 1000500);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/tools/nfd-status/strategy-choice-module.t.cpp b/tests/tools/nfd-status/strategy-choice-module.t.cpp
new file mode 100644
index 0000000..a41ba1f
--- /dev/null
+++ b/tests/tools/nfd-status/strategy-choice-module.t.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-status/strategy-choice-module.hpp"
+
+#include "module-fixture.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(NfdStatus)
+BOOST_FIXTURE_TEST_SUITE(TestStrategyChoiceModule, ModuleFixture<StrategyChoiceModule>)
+
+const std::string STATUS_XML = stripXmlSpaces(R"XML(
+  <strategyChoices>
+    <strategyChoice>
+      <namespace>/</namespace>
+      <strategy>
+        <name>/localhost/nfd/strategy/best-route/%FD%04</name>
+      </strategy>
+    </strategyChoice>
+    <strategyChoice>
+      <namespace>/localhost</namespace>
+      <strategy>
+        <name>/localhost/nfd/strategy/multicast/%FD%01</name>
+      </strategy>
+    </strategyChoice>
+  </strategyChoices>
+)XML");
+
+const std::string STATUS_TEXT = std::string(R"TEXT(
+Strategy choices:
+  / strategy=/localhost/nfd/strategy/best-route/%FD%04
+  /localhost strategy=/localhost/nfd/strategy/multicast/%FD%01
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(Status)
+{
+  this->fetchStatus();
+  StrategyChoice payload1;
+  payload1.setName("/")
+          .setStrategy("/localhost/nfd/strategy/best-route/%FD%04");
+  StrategyChoice payload2;
+  payload2.setName("/localhost")
+          .setStrategy("/localhost/nfd/strategy/multicast/%FD%01");
+  this->sendDataset("/localhost/nfd/strategy-choice/list", payload1, payload2);
+  this->prepareStatusOutput();
+
+  BOOST_CHECK(statusXml.is_equal(STATUS_XML));
+  BOOST_CHECK(statusText.is_equal(STATUS_TEXT));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStrategyChoiceModule
+BOOST_AUTO_TEST_SUITE_END() // NfdStatus
+
+} // namespace tests
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tests/wscript b/tests/wscript
index 3d080aa..dc82fe6 100644
--- a/tests/wscript
+++ b/tests/wscript
@@ -40,9 +40,10 @@
             defines='UNIT_TEST_CONFIG_PATH=\"%s/tmp-files/\"' % bld.bldnode,
           )
 
-        for module, name in {"core": "NFD Core Tests",
-                             "daemon": "NFD Daemon Tests",
-                             "rib": "NFD RIB Tests"}.items():
+        for module, name in {"core": "Core Tests",
+                             "daemon": "Daemon Tests",
+                             "rib": "RIB Tests",
+                             "tools": "Tools Tests"}.items():
             # main()
             bld(target='unit-tests-%s-main' % module,
                 name='unit-tests-%s-main' % module,
diff --git a/tools/nfd-status.cpp b/tools/nfd-status.cpp
deleted file mode 100644
index 21c62a1..0000000
--- a/tools/nfd-status.cpp
+++ /dev/null
@@ -1,926 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2016,  Regents of the University of California,
- *                           Arizona Board of Regents,
- *                           Colorado State University,
- *                           University Pierre & Marie Curie, Sorbonne University,
- *                           Washington University in St. Louis,
- *                           Beijing Institute of Technology,
- *                           The University of Memphis.
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @author Jerald Paul Abraham <jeraldabraham@email.arizona.edu>
- */
-
-#include "version.hpp"
-#include "common.hpp"
-
-#include <ndn-cxx/face.hpp>
-#include <ndn-cxx/name.hpp>
-#include <ndn-cxx/interest.hpp>
-#include <ndn-cxx/encoding/buffer-stream.hpp>
-
-#include <ndn-cxx/management/nfd-forwarder-status.hpp>
-#include <ndn-cxx/management/nfd-channel-status.hpp>
-#include <ndn-cxx/management/nfd-face-status.hpp>
-#include <ndn-cxx/management/nfd-fib-entry.hpp>
-#include <ndn-cxx/management/nfd-rib-entry.hpp>
-#include <ndn-cxx/management/nfd-strategy-choice.hpp>
-#include <ndn-cxx/util/segment-fetcher.hpp>
-#include <ndn-cxx/security/validator-null.hpp>
-
-#include <boost/algorithm/string/replace.hpp>
-#include <list>
-
-namespace ndn {
-
-using util::SegmentFetcher;
-
-/** \brief custom validator for the purpose of obtaining nfdId.
- */
-class ForwarderStatusValidator : public Validator
-{
-protected:
-  virtual void
-  checkPolicy(const Data& data,
-              int nSteps,
-              const OnDataValidated& onValidated,
-              const OnDataValidationFailed& onValidationFailed,
-              std::vector<shared_ptr<ValidationRequest>>& nextSteps) override
-  {
-    const ndn::KeyLocator& locator = data.getSignature().getKeyLocator();
-    if (nfdId.empty() && locator.getType() == KeyLocator::KeyLocator_Name)
-      nfdId = locator.getName();
-    onValidated(data.shared_from_this());
-  }
-
-  virtual void
-  checkPolicy(const Interest& interest,
-              int nSteps,
-              const OnInterestValidated& onValidated,
-              const OnInterestValidationFailed& onValidationFailed,
-              std::vector<shared_ptr<ValidationRequest>>& nextSteps) override
-  {
-    BOOST_ASSERT(false);
-  }
-
-public:
-  Name nfdId;
-};
-
-
-class NfdStatus
-{
-public:
-  explicit
-  NfdStatus(char* toolName)
-    : m_toolName(toolName)
-    , m_needVersionRetrieval(false)
-    , m_needChannelStatusRetrieval(false)
-    , m_needFaceStatusRetrieval(false)
-    , m_needFibEnumerationRetrieval(false)
-    , m_needRibStatusRetrieval(false)
-    , m_needStrategyChoiceRetrieval(false)
-    , m_isOutputXml(false)
-  {
-  }
-
-  void
-  usage()
-  {
-    std::cout << "Usage: \n  " << m_toolName << " [options]\n\n"
-      "Show NFD version and status information\n\n"
-      "Options:\n"
-      "  [-h] - print this help message\n"
-      "  [-v] - retrieve version information\n"
-      "  [-c] - retrieve channel status information\n"
-      "  [-f] - retrieve face status information\n"
-      "  [-b] - retrieve FIB information\n"
-      "  [-r] - retrieve RIB information\n"
-      "  [-s] - retrieve configured strategy choice for NDN namespaces\n"
-      "  [-x] - output NFD status information in XML format\n"
-      "\n"
-      "  [-V] - show version information of nfd-status and exit\n"
-      "\n"
-      "If no options are provided, all information is retrieved.\n"
-      "If -x is provided, other options(-v, -c, etc.) are ignored, and all information is printed in XML format.\n"
-      ;
-  }
-
-  void
-  enableVersionRetrieval()
-  {
-    m_needVersionRetrieval = true;
-  }
-
-  void
-  enableChannelStatusRetrieval()
-  {
-    m_needChannelStatusRetrieval = true;
-  }
-
-  void
-  enableFaceStatusRetrieval()
-  {
-    m_needFaceStatusRetrieval = true;
-  }
-
-  void
-  enableFibEnumerationRetrieval()
-  {
-    m_needFibEnumerationRetrieval = true;
-  }
-
-  void
-  enableStrategyChoiceRetrieval()
-  {
-    m_needStrategyChoiceRetrieval = true;
-  }
-
-  void
-  enableRibStatusRetrieval()
-  {
-    m_needRibStatusRetrieval = true;
-  }
-
-  void
-  enableXmlOutput()
-  {
-    m_isOutputXml = true;
-  }
-
-  void
-  onTimeout()
-  {
-    std::cerr << "Request timed out" << std::endl;
-
-    runNextStep();
-  }
-
-
-  void
-  onFetchError(uint32_t errorCode, const std::string& errorMsg)
-  {
-    std::cerr << "Error code:" << errorCode << ", message:" << errorMsg << std::endl;
-
-    runNextStep();
-  }
-
-  void
-  escapeSpecialCharacters(std::string *data)
-  {
-    using boost::algorithm::replace_all;
-    replace_all(*data, "&",  "&amp;");
-    replace_all(*data, "\"", "&quot;");
-    replace_all(*data, "\'", "&apos;");
-    replace_all(*data, "<",  "&lt;");
-    replace_all(*data, ">",  "&gt;");
-  }
-
-  //////////////////////////////////////////////////////////////////////////////////
-  //////////////////////////////////////////////////////////////////////////////////
-
-  void
-  fetchVersionInformation()
-  {
-    Interest interest("/localhost/nfd/status/general");
-    interest.setChildSelector(1);
-    interest.setMustBeFresh(true);
-    SegmentFetcher::fetch(m_face, interest,
-                          m_forwarderStatusValidator,
-                          bind(&NfdStatus::afterFetchedVersionInformation, this, _1),
-                          bind(&NfdStatus::onFetchError, this, _1, _2));
-  }
-
-  void
-  afterFetchedVersionInformation(const ConstBufferPtr& dataset)
-  {
-    Block block(tlv::Content, dataset);
-    nfd::ForwarderStatus status(block);
-
-    if (m_isOutputXml) {
-      std::cout << "<generalStatus>";
-      std::cout << "<nfdId>"
-                << m_forwarderStatusValidator.nfdId << "</nfdId>";
-      std::cout << "<version>"
-                << status.getNfdVersion() << "</version>";
-      std::cout << "<startTime>"
-                << time::toString(status.getStartTimestamp(), "%Y-%m-%dT%H:%M:%S%F")
-                << "</startTime>";
-      std::cout << "<currentTime>"
-                << time::toString(status.getCurrentTimestamp(), "%Y-%m-%dT%H:%M:%S%F")
-                << "</currentTime>";
-      std::cout << "<uptime>PT"
-                << time::duration_cast<time::seconds>(status.getCurrentTimestamp() -
-                                                      status.getStartTimestamp()).count()
-                << "S</uptime>";
-      std::cout << "<nNameTreeEntries>"     << status.getNNameTreeEntries()
-                << "</nNameTreeEntries>";
-      std::cout << "<nFibEntries>"          << status.getNFibEntries()
-                << "</nFibEntries>";
-      std::cout << "<nPitEntries>"          << status.getNPitEntries()
-                << "</nPitEntries>";
-      std::cout << "<nMeasurementsEntries>" << status.getNMeasurementsEntries()
-                << "</nMeasurementsEntries>";
-      std::cout << "<nCsEntries>"           << status.getNCsEntries()
-                << "</nCsEntries>";
-      std::cout << "<packetCounters>";
-      std::cout << "<incomingPackets>";
-      std::cout << "<nInterests>"           << status.getNInInterests()
-                << "</nInterests>";
-      std::cout << "<nDatas>"               << status.getNInDatas()
-                << "</nDatas>";
-      std::cout << "<nNacks>"               << status.getNInNacks()
-                << "</nNacks>";
-      std::cout << "</incomingPackets>";
-      std::cout << "<outgoingPackets>";
-      std::cout << "<nInterests>"           << status.getNOutInterests()
-                << "</nInterests>";
-      std::cout << "<nDatas>"               << status.getNOutDatas()
-                << "</nDatas>";
-      std::cout << "<nNacks>"               << status.getNOutNacks()
-                << "</nNacks>";
-      std::cout << "</outgoingPackets>";
-      std::cout << "</packetCounters>";
-      std::cout << "</generalStatus>";
-    }
-    else {
-      std::cout << "General NFD status:" << std::endl;
-      std::cout << "                 nfdId="
-                << m_forwarderStatusValidator.nfdId << std::endl;
-      std::cout << "               version="
-                << status.getNfdVersion() << std::endl;
-      std::cout << "             startTime="
-                << time::toIsoString(status.getStartTimestamp()) << std::endl;
-      std::cout << "           currentTime="
-                << time::toIsoString(status.getCurrentTimestamp()) << std::endl;
-      std::cout << "                uptime="
-                << time::duration_cast<time::seconds>(status.getCurrentTimestamp() -
-                                                      status.getStartTimestamp()) << std::endl;
-
-      std::cout << "      nNameTreeEntries=" << status.getNNameTreeEntries()     << std::endl;
-      std::cout << "           nFibEntries=" << status.getNFibEntries()          << std::endl;
-      std::cout << "           nPitEntries=" << status.getNPitEntries()          << std::endl;
-      std::cout << "  nMeasurementsEntries=" << status.getNMeasurementsEntries() << std::endl;
-      std::cout << "            nCsEntries=" << status.getNCsEntries()           << std::endl;
-      std::cout << "          nInInterests=" << status.getNInInterests()         << std::endl;
-      std::cout << "         nOutInterests=" << status.getNOutInterests()        << std::endl;
-      std::cout << "              nInDatas=" << status.getNInDatas()             << std::endl;
-      std::cout << "             nOutDatas=" << status.getNOutDatas()            << std::endl;
-      std::cout << "              nInNacks=" << status.getNInNacks()             << std::endl;
-      std::cout << "             nOutNacks=" << status.getNOutNacks()            << std::endl;
-    }
-
-    runNextStep();
-  }
-
-  //////////////////////////////////////////////////////////////////////////////////
-  //////////////////////////////////////////////////////////////////////////////////
-
-  void
-  fetchChannelStatusInformation()
-  {
-    m_buffer = make_shared<OBufferStream>();
-
-    Interest interest("/localhost/nfd/faces/channels");
-    interest.setChildSelector(1);
-    interest.setMustBeFresh(true);
-
-    SegmentFetcher::fetch(m_face, interest,
-                          m_validator,
-                          bind(&NfdStatus::afterFetchedChannelStatusInformation, this, _1),
-                          bind(&NfdStatus::onFetchError, this, _1, _2));
-  }
-
-  void
-  afterFetchedChannelStatusInformation(const ConstBufferPtr& dataset)
-  {
-    if (m_isOutputXml) {
-      std::cout << "<channels>";
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode ChannelStatus TLV" << std::endl;
-          break;
-        }
-
-        offset += block.size();
-
-        nfd::ChannelStatus channelStatus(block);
-
-        std::cout << "<channel>";
-        std::string localUri(channelStatus.getLocalUri());
-        escapeSpecialCharacters(&localUri);
-        std::cout << "<localUri>" << localUri << "</localUri>";
-        std::cout << "</channel>";
-      }
-
-      std::cout << "</channels>";
-    }
-    else {
-      std::cout << "Channels:" << std::endl;
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode ChannelStatus TLV" << std::endl;
-          break;
-        }
-
-        offset += block.size();
-
-        nfd::ChannelStatus channelStatus(block);
-        std::cout << "  " << channelStatus.getLocalUri() << std::endl;
-      }
-    }
-
-    runNextStep();
-  }
-
-  //////////////////////////////////////////////////////////////////////////////////
-  //////////////////////////////////////////////////////////////////////////////////
-
-  void
-  fetchFaceStatusInformation()
-  {
-    m_buffer = make_shared<OBufferStream>();
-
-    Interest interest("/localhost/nfd/faces/list");
-    interest.setChildSelector(1);
-    interest.setMustBeFresh(true);
-
-    SegmentFetcher::fetch(m_face, interest,
-                          m_validator,
-                          bind(&NfdStatus::afterFetchedFaceStatusInformation, this, _1),
-                          bind(&NfdStatus::onFetchError, this, _1, _2));
-  }
-
-  void
-  afterFetchedFaceStatusInformation(const ConstBufferPtr& dataset)
-  {
-    if (m_isOutputXml) {
-      std::cout << "<faces>";
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode FaceStatus TLV" << std::endl;
-          break;
-        }
-
-        offset += block.size();
-
-        nfd::FaceStatus faceStatus(block);
-
-        std::cout << "<face>";
-        std::cout << "<faceId>" << faceStatus.getFaceId() << "</faceId>";
-
-        std::string remoteUri(faceStatus.getRemoteUri());
-        escapeSpecialCharacters(&remoteUri);
-        std::cout << "<remoteUri>" << remoteUri << "</remoteUri>";
-
-        std::string localUri(faceStatus.getLocalUri());
-        escapeSpecialCharacters(&localUri);
-        std::cout << "<localUri>" << localUri << "</localUri>";
-
-        if (faceStatus.hasExpirationPeriod()) {
-          std::cout << "<expirationPeriod>PT"
-                    << time::duration_cast<time::seconds>(faceStatus.getExpirationPeriod())
-                       .count() << "S"
-                    << "</expirationPeriod>";
-        }
-
-        std::cout << "<faceScope>" << faceStatus.getFaceScope()
-                  << "</faceScope>";
-        std::cout << "<facePersistency>" << faceStatus.getFacePersistency()
-                  << "</facePersistency>";
-        std::cout << "<linkType>" << faceStatus.getLinkType()
-                  << "</linkType>";
-
-        std::cout << "<packetCounters>";
-        std::cout << "<incomingPackets>";
-        std::cout << "<nInterests>"       << faceStatus.getNInInterests()
-                  << "</nInterests>";
-        std::cout << "<nDatas>"           << faceStatus.getNInDatas()
-                  << "</nDatas>";
-        std::cout << "<nNacks>"           << faceStatus.getNInNacks()
-                  << "</nNacks>";
-        std::cout << "</incomingPackets>";
-        std::cout << "<outgoingPackets>";
-        std::cout << "<nInterests>"       << faceStatus.getNOutInterests()
-                  << "</nInterests>";
-        std::cout << "<nDatas>"           << faceStatus.getNOutDatas()
-                  << "</nDatas>";
-        std::cout << "<nNacks>"           << faceStatus.getNOutNacks()
-                  << "</nNacks>";
-        std::cout << "</outgoingPackets>";
-        std::cout << "</packetCounters>";
-
-        std::cout << "<byteCounters>";
-        std::cout << "<incomingBytes>"    << faceStatus.getNInBytes()
-                  << "</incomingBytes>";
-        std::cout << "<outgoingBytes>"    << faceStatus.getNOutBytes()
-                  << "</outgoingBytes>";
-        std::cout << "</byteCounters>";
-
-        std::cout << "</face>";
-      }
-      std::cout << "</faces>";
-    }
-    else {
-      std::cout << "Faces:" << std::endl;
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode FaceStatus TLV" << std::endl;
-          break;
-        }
-
-        offset += block.size();
-
-        nfd::FaceStatus faceStatus(block);
-
-        std::cout << "  faceid=" << faceStatus.getFaceId()
-                  << " remote=" << faceStatus.getRemoteUri()
-                  << " local=" << faceStatus.getLocalUri();
-        if (faceStatus.hasExpirationPeriod()) {
-          std::cout  << " expires="
-                     << time::duration_cast<time::seconds>(faceStatus.getExpirationPeriod())
-                        .count() << "s";
-        }
-        std::cout << " counters={"
-                  << "in={" << faceStatus.getNInInterests() << "i "
-                  << faceStatus.getNInDatas() << "d "
-                  << faceStatus.getNInNacks() << "n "
-                  << faceStatus.getNInBytes() << "B}"
-                  << " out={" << faceStatus.getNOutInterests() << "i "
-                  << faceStatus.getNOutDatas() << "d "
-                  << faceStatus.getNOutNacks() << "n "
-                  << faceStatus.getNOutBytes() << "B}"
-                  << "}";
-        std::cout << " " << faceStatus.getFaceScope()
-                  << " " << faceStatus.getFacePersistency()
-                  << " " << faceStatus.getLinkType();
-        std::cout << std::endl;
-      }
-    }
-
-    runNextStep();
-  }
-
-  //////////////////////////////////////////////////////////////////////////////////
-  //////////////////////////////////////////////////////////////////////////////////
-
-  void
-  fetchFibEnumerationInformation()
-  {
-    m_buffer = make_shared<OBufferStream>();
-
-    Interest interest("/localhost/nfd/fib/list");
-    interest.setChildSelector(1);
-    interest.setMustBeFresh(true);
-
-    SegmentFetcher::fetch(m_face, interest,
-                          m_validator,
-                          bind(&NfdStatus::afterFetchedFibEnumerationInformation, this, _1),
-                          bind(&NfdStatus::onFetchError, this, _1, _2));
-  }
-
-  void
-  afterFetchedFibEnumerationInformation(const ConstBufferPtr& dataset)
-  {
-    if (m_isOutputXml) {
-      std::cout << "<fib>";
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode FibEntry TLV";
-          break;
-        }
-        offset += block.size();
-
-        nfd::FibEntry fibEntry(block);
-
-        std::cout << "<fibEntry>";
-        std::string prefix(fibEntry.getPrefix().toUri());
-        escapeSpecialCharacters(&prefix);
-        std::cout << "<prefix>" << prefix << "</prefix>";
-        std::cout << "<nextHops>";
-        for (const nfd::NextHopRecord& nextHop : fibEntry.getNextHopRecords()) {
-          std::cout << "<nextHop>" ;
-          std::cout << "<faceId>"  << nextHop.getFaceId() << "</faceId>";
-          std::cout << "<cost>"    << nextHop.getCost()   << "</cost>";
-          std::cout << "</nextHop>";
-        }
-        std::cout << "</nextHops>";
-        std::cout << "</fibEntry>";
-      }
-
-      std::cout << "</fib>";
-    }
-    else {
-      std::cout << "FIB:" << std::endl;
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode FibEntry TLV" << std::endl;
-          break;
-        }
-        offset += block.size();
-
-        nfd::FibEntry fibEntry(block);
-
-        std::cout << "  " << fibEntry.getPrefix() << " nexthops={";
-        bool isFirst = true;
-        for (const nfd::NextHopRecord& nextHop : fibEntry.getNextHopRecords()) {
-          if (isFirst)
-            isFirst = false;
-          else
-            std::cout << ", ";
-
-          std::cout << "faceid=" << nextHop.getFaceId()
-                    << " (cost=" << nextHop.getCost() << ")";
-        }
-        std::cout << "}" << std::endl;
-      }
-    }
-
-    runNextStep();
-  }
-
-  //////////////////////////////////////////////////////////////////////////////////
-  //////////////////////////////////////////////////////////////////////////////////
-
-  void
-  fetchStrategyChoiceInformation()
-  {
-    m_buffer = make_shared<OBufferStream>();
-
-    Interest interest("/localhost/nfd/strategy-choice/list");
-    interest.setChildSelector(1);
-    interest.setMustBeFresh(true);
-
-    SegmentFetcher::fetch(m_face, interest,
-                          m_validator,
-                          bind(&NfdStatus::afterFetchedStrategyChoiceInformationInformation, this, _1),
-                          bind(&NfdStatus::onFetchError, this, _1, _2));
-  }
-
-  void
-  afterFetchedStrategyChoiceInformationInformation(const ConstBufferPtr& dataset)
-  {
-    if (m_isOutputXml) {
-      std::cout << "<strategyChoices>";
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode StrategyChoice TLV";
-          break;
-        }
-        offset += block.size();
-
-        nfd::StrategyChoice strategyChoice(block);
-
-        std::cout << "<strategyChoice>";
-
-        std::string name(strategyChoice.getName().toUri());
-        escapeSpecialCharacters(&name);
-        std::cout << "<namespace>" << name << "</namespace>";
-        std::cout << "<strategy>";
-
-        std::string strategy(strategyChoice.getStrategy().toUri());
-        escapeSpecialCharacters(&strategy);
-
-        std::cout << "<name>" << strategy << "</name>";
-        std::cout << "</strategy>";
-        std::cout << "</strategyChoice>";
-      }
-
-      std::cout << "</strategyChoices>";
-    }
-    else {
-      std::cout << "Strategy choices:" << std::endl;
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode StrategyChoice TLV" << std::endl;
-          break;
-        }
-        offset += block.size();
-
-        nfd::StrategyChoice strategyChoice(block);
-
-        std::cout << "  " << strategyChoice.getName()
-                  << " strategy=" << strategyChoice.getStrategy() << std::endl;
-      }
-    }
-
-    runNextStep();
-  }
-
-  void
-  fetchRibStatusInformation()
-  {
-    m_buffer = make_shared<OBufferStream>();
-
-    Interest interest("/localhost/nfd/rib/list");
-    interest.setChildSelector(1);
-    interest.setMustBeFresh(true);
-
-    SegmentFetcher::fetch(m_face, interest,
-                          m_validator,
-                          bind(&NfdStatus::afterFetchedRibStatusInformation, this, _1),
-                          bind(&NfdStatus::onFetchError, this, _1, _2));
-  }
-
-  void
-  afterFetchedRibStatusInformation(const ConstBufferPtr& dataset)
-  {
-    if (m_isOutputXml) {
-      std::cout << "<rib>";
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode RibEntry TLV";
-          break;
-        }
-        offset += block.size();
-
-        nfd::RibEntry ribEntry(block);
-
-        std::cout << "<ribEntry>";
-        std::string prefix(ribEntry.getName().toUri());
-        escapeSpecialCharacters(&prefix);
-        std::cout << "<prefix>" << prefix << "</prefix>";
-        std::cout << "<routes>";
-        for (const nfd::Route& nextRoute : ribEntry) {
-          std::cout << "<route>" ;
-          std::cout << "<faceId>"  << nextRoute.getFaceId() << "</faceId>";
-          std::cout << "<origin>"  << nextRoute.getOrigin() << "</origin>";
-          std::cout << "<cost>"    << nextRoute.getCost()   << "</cost>";
-
-          std::cout << "<flags>";
-          if (nextRoute.isChildInherit())
-            std::cout << "<childInherit/>";
-          if (nextRoute.isRibCapture())
-            std::cout << "<ribCapture/>";
-          std::cout << "</flags>";
-
-          if (!nextRoute.hasInfiniteExpirationPeriod()) {
-            std::cout << "<expirationPeriod>PT"
-                      << time::duration_cast<time::seconds>(nextRoute.getExpirationPeriod())
-                         .count() << "S"
-                      << "</expirationPeriod>";
-          }
-          std::cout << "</route>";
-        }
-        std::cout << "</routes>";
-        std::cout << "</ribEntry>";
-      }
-
-      std::cout << "</rib>";
-    }
-    else {
-      std::cout << "RIB:" << std::endl;
-
-      size_t offset = 0;
-      while (offset < dataset->size()) {
-        bool isOk = false;
-        Block block;
-        std::tie(isOk, block) = Block::fromBuffer(dataset, offset);
-        if (!isOk) {
-          std::cerr << "ERROR: cannot decode RibEntry TLV" << std::endl;
-          break;
-        }
-
-        offset += block.size();
-
-        nfd::RibEntry ribEntry(block);
-
-        std::cout << "  " << ribEntry.getName().toUri() << " route={";
-        bool isFirst = true;
-        for (const nfd::Route& nextRoute : ribEntry) {
-          if (isFirst)
-            isFirst = false;
-          else
-            std::cout << ", ";
-
-          std::cout << "faceid="   << nextRoute.getFaceId()
-                    << " (origin="  << nextRoute.getOrigin()
-                    << " cost="    << nextRoute.getCost();
-          if (!nextRoute.hasInfiniteExpirationPeriod()) {
-            std::cout << " expires="
-                      << time::duration_cast<time::seconds>(nextRoute.getExpirationPeriod())
-                         .count() << "s";
-          }
-
-          if (nextRoute.isChildInherit())
-            std::cout << " ChildInherit";
-          if (nextRoute.isRibCapture())
-            std::cout << " RibCapture";
-
-          std::cout << ")";
-        }
-        std::cout << "}" << std::endl;
-      }
-    }
-
-    runNextStep();
-  }
-
-
-  void
-  fetchInformation()
-  {
-    if (m_isOutputXml ||
-        (!m_needVersionRetrieval &&
-         !m_needChannelStatusRetrieval &&
-         !m_needFaceStatusRetrieval &&
-         !m_needFibEnumerationRetrieval &&
-         !m_needRibStatusRetrieval &&
-         !m_needStrategyChoiceRetrieval))
-      {
-        enableVersionRetrieval();
-        enableChannelStatusRetrieval();
-        enableFaceStatusRetrieval();
-        enableFibEnumerationRetrieval();
-        enableRibStatusRetrieval();
-        enableStrategyChoiceRetrieval();
-      }
-
-    if (m_isOutputXml)
-      m_fetchSteps.push_back(bind(&NfdStatus::printXmlHeader, this));
-
-    if (m_needVersionRetrieval)
-      m_fetchSteps.push_back(bind(&NfdStatus::fetchVersionInformation, this));
-
-    if (m_needChannelStatusRetrieval)
-      m_fetchSteps.push_back(bind(&NfdStatus::fetchChannelStatusInformation, this));
-
-    if (m_needFaceStatusRetrieval)
-      m_fetchSteps.push_back(bind(&NfdStatus::fetchFaceStatusInformation, this));
-
-    if (m_needFibEnumerationRetrieval)
-      m_fetchSteps.push_back(bind(&NfdStatus::fetchFibEnumerationInformation, this));
-
-    if (m_needRibStatusRetrieval)
-      m_fetchSteps.push_back(bind(&NfdStatus::fetchRibStatusInformation, this));
-
-    if (m_needStrategyChoiceRetrieval)
-      m_fetchSteps.push_back(bind(&NfdStatus::fetchStrategyChoiceInformation, this));
-
-    if (m_isOutputXml)
-      m_fetchSteps.push_back(bind(&NfdStatus::printXmlFooter, this));
-
-    runNextStep();
-    m_face.processEvents();
-  }
-
-private:
-  void
-  printXmlHeader()
-  {
-    std::cout << "<?xml version=\"1.0\"?>";
-    std::cout << "<nfdStatus xmlns=\"ndn:/localhost/nfd/status/1\">";
-
-    runNextStep();
-  }
-
-  void
-  printXmlFooter()
-  {
-    std::cout << "</nfdStatus>";
-
-    runNextStep();
-  }
-
-  void
-  runNextStep()
-  {
-    if (m_fetchSteps.empty())
-      return;
-
-    function<void()> nextStep = m_fetchSteps.front();
-    m_fetchSteps.pop_front();
-    nextStep();
-  }
-
-private:
-  std::string m_toolName;
-  bool m_needVersionRetrieval;
-  bool m_needChannelStatusRetrieval;
-  bool m_needFaceStatusRetrieval;
-  bool m_needFibEnumerationRetrieval;
-  bool m_needRibStatusRetrieval;
-  bool m_needStrategyChoiceRetrieval;
-  bool m_isOutputXml;
-  Face m_face;
-
-  shared_ptr<OBufferStream> m_buffer;
-
-  std::deque<function<void()>> m_fetchSteps;
-
-  ndn::ValidatorNull m_validator;
-
-  ForwarderStatusValidator m_forwarderStatusValidator;
-};
-
-} // namespace ndn
-
-int main(int argc, char* argv[])
-{
-  int option;
-  ndn::NfdStatus nfdStatus(argv[0]);
-
-  while ((option = getopt(argc, argv, "hvcfbrsxV")) != -1) {
-    switch (option) {
-    case 'h':
-      nfdStatus.usage();
-      return 0;
-    case 'v':
-      nfdStatus.enableVersionRetrieval();
-      break;
-    case 'c':
-      nfdStatus.enableChannelStatusRetrieval();
-      break;
-    case 'f':
-      nfdStatus.enableFaceStatusRetrieval();
-      break;
-    case 'b':
-      nfdStatus.enableFibEnumerationRetrieval();
-      break;
-    case 'r':
-      nfdStatus.enableRibStatusRetrieval();
-      break;
-    case 's':
-      nfdStatus.enableStrategyChoiceRetrieval();
-      break;
-    case 'x':
-      nfdStatus.enableXmlOutput();
-      break;
-    case 'V':
-      std::cout << NFD_VERSION_BUILD_STRING << std::endl;
-      return 0;
-    default:
-      nfdStatus.usage();
-      return 1;
-    }
-  }
-
-  try {
-    nfdStatus.fetchInformation();
-  }
-  catch (std::exception& e) {
-    std::cerr << "ERROR: " << e.what() << std::endl;
-    return 2;
-  }
-
-  return 0;
-}
diff --git a/tools/nfd-status/channel-module.cpp b/tools/nfd-status/channel-module.cpp
new file mode 100644
index 0000000..4cd5484
--- /dev/null
+++ b/tools/nfd-status/channel-module.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "channel-module.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+void
+ChannelModule::fetchStatus(Controller& controller,
+                           const function<void()>& onSuccess,
+                           const Controller::CommandFailCallback& onFailure,
+                           const CommandOptions& options)
+{
+  controller.fetch<ndn::nfd::ChannelDataset>(
+    [this, onSuccess] (const std::vector<ChannelStatus>& result) {
+      m_status = result;
+      onSuccess();
+    },
+    onFailure, options);
+}
+
+void
+ChannelModule::formatStatusXml(std::ostream& os) const
+{
+  os << "<channels>";
+  for (const ChannelStatus& item : m_status) {
+    this->formatItemXml(os, item);
+  }
+  os << "</channels>";
+}
+
+void
+ChannelModule::formatItemXml(std::ostream& os, const ChannelStatus& item) const
+{
+  os << "<channel>";
+  os << "<localUri>" << xml::Text{item.getLocalUri()} << "</localUri>";
+  os << "</channel>";
+}
+
+void
+ChannelModule::formatStatusText(std::ostream& os) const
+{
+  os << "Channels:\n";
+  for (const ChannelStatus& item : m_status) {
+    this->formatItemText(os, item);
+  }
+}
+
+void
+ChannelModule::formatItemText(std::ostream& os, const ChannelStatus& item) const
+{
+  os << "  " << item.getLocalUri();
+  os << "\n";
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/channel-module.hpp b/tools/nfd-status/channel-module.hpp
new file mode 100644
index 0000000..1230554
--- /dev/null
+++ b/tools/nfd-status/channel-module.hpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_CHANNEL_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_CHANNEL_MODULE_HPP
+
+#include "module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::ChannelStatus;
+
+/** \brief provides access to NFD channel dataset
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#Channel-Dataset
+ */
+class ChannelModule : public Module, noncopyable
+{
+public:
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override;
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override;
+
+  /** \brief format a single status item as XML
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemXml(std::ostream& os, const ChannelStatus& item) const;
+
+  virtual void
+  formatStatusText(std::ostream& os) const override;
+
+  /** \brief format a single status item as text
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemText(std::ostream& os, const ChannelStatus& item) const;
+
+private:
+  std::vector<ChannelStatus> m_status;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_CHANNEL_MODULE_HPP
diff --git a/tools/nfd-status/face-module.cpp b/tools/nfd-status/face-module.cpp
new file mode 100644
index 0000000..3ec0467
--- /dev/null
+++ b/tools/nfd-status/face-module.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \todo
+ *  #2542 plans to merge nfd-status with nfdc.
+ *  FaceModule class should be changed as follows:
+ *  (1) move into ndn::nfd::nfdc namespace
+ *  (2) assuming command syntax is similar to Windows NT \p netsh ,
+ *      this class can handle command argument parsing as soon as
+ *      'face' sub-command is detected
+ *  (3) introduce methods to create and destroy faces, and update face attributes
+ *
+ *  \todo
+ *  #3444 aims at improving output texts of nfdc.
+ *  Assuming it's worked with or after #2542:
+ *  (1) introduce an \p style parameter on formatItemText method to specify desired text style,
+ *      such as human friendly style vs. porcelain style for script consumption
+ *  (2) upon successful command execute, convert the command result into FaceStatus type,
+ *      and use formatItemText to render the result, so that output from status retrieval
+ *      and command execution have consistent styles
+ *  }
+ */
+
+#include "face-module.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+void
+FaceModule::fetchStatus(Controller& controller,
+                        const function<void()>& onSuccess,
+                        const Controller::CommandFailCallback& onFailure,
+                        const CommandOptions& options)
+{
+  controller.fetch<ndn::nfd::FaceDataset>(
+    [this, onSuccess] (const std::vector<FaceStatus>& result) {
+      m_status = result;
+      onSuccess();
+    },
+    onFailure, options);
+}
+
+void
+FaceModule::formatStatusXml(std::ostream& os) const
+{
+  os << "<faces>";
+  for (const FaceStatus& item : m_status) {
+    this->formatItemXml(os, item);
+  }
+  os << "</faces>";
+}
+
+void
+FaceModule::formatItemXml(std::ostream& os, const FaceStatus& item) const
+{
+  os << "<face>";
+
+  os << "<faceId>" << item.getFaceId() << "</faceId>";
+  os << "<remoteUri>" << xml::Text{item.getRemoteUri()} << "</remoteUri>";
+  os << "<localUri>" << xml::Text{item.getLocalUri()} << "</localUri>";
+
+  if (item.hasExpirationPeriod()) {
+    os << "<expirationPeriod>" << xml::formatDuration(item.getExpirationPeriod())
+       << "</expirationPeriod>";
+  }
+  os << "<faceScope>" << item.getFaceScope() << "</faceScope>";
+  os << "<facePersistency>" << item.getFacePersistency() << "</facePersistency>";
+  os << "<linkType>" << item.getLinkType() << "</linkType>";
+
+  os << "<packetCounters>";
+  os << "<incomingPackets>"
+     << "<nInterests>" << item.getNInInterests() << "</nInterests>"
+     << "<nDatas>" << item.getNInDatas() << "</nDatas>"
+     << "<nNacks>" << item.getNInNacks() << "</nNacks>"
+     << "</incomingPackets>";
+  os << "<outgoingPackets>"
+     << "<nInterests>" << item.getNOutInterests() << "</nInterests>"
+     << "<nDatas>" << item.getNOutDatas() << "</nDatas>"
+     << "<nNacks>" << item.getNOutNacks() << "</nNacks>"
+     << "</outgoingPackets>";
+  os << "</packetCounters>";
+
+  os << "<byteCounters>";
+  os << "<incomingBytes>" << item.getNInBytes() << "</incomingBytes>";
+  os << "<outgoingBytes>" << item.getNOutBytes() << "</outgoingBytes>";
+  os << "</byteCounters>";
+
+  os << "</face>";
+}
+
+void
+FaceModule::formatStatusText(std::ostream& os) const
+{
+  os << "Faces:\n";
+  for (const FaceStatus& item : m_status) {
+    this->formatItemText(os, item);
+  }
+}
+
+void
+FaceModule::formatItemText(std::ostream& os, const FaceStatus& item) const
+{
+  os << "  faceid=" << item.getFaceId();
+  os << " remote=" << item.getRemoteUri();
+  os << " local=" << item.getLocalUri();
+
+  if (item.hasExpirationPeriod()) {
+    os << " expires=" << text::formatDuration(item.getExpirationPeriod());
+  }
+
+  os << " counters={in={"
+     << item.getNInInterests() << "i "
+     << item.getNInDatas() << "d "
+     << item.getNInNacks() << "n "
+     << item.getNInBytes() << "B} ";
+  os << "out={"
+     << item.getNOutInterests() << "i "
+     << item.getNOutDatas() << "d "
+     << item.getNOutNacks() << "n "
+     << item.getNOutBytes() << "B}}";
+
+  os << " " << item.getFaceScope();
+  os << " " << item.getFacePersistency();
+  os << " " << item.getLinkType();
+  os << "\n";
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/face-module.hpp b/tools/nfd-status/face-module.hpp
new file mode 100644
index 0000000..58bb5ee
--- /dev/null
+++ b/tools/nfd-status/face-module.hpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_FACE_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_FACE_MODULE_HPP
+
+#include "module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::FaceStatus;
+
+/** \brief provides access to NFD face management
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/FaceMgmt
+ */
+class FaceModule : public Module, noncopyable
+{
+public:
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override;
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override;
+
+  /** \brief format a single status item as XML
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemXml(std::ostream& os, const FaceStatus& item) const;
+
+  virtual void
+  formatStatusText(std::ostream& os) const override;
+
+  /** \brief format a single status item as text
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemText(std::ostream& os, const FaceStatus& item) const;
+
+private:
+  std::vector<FaceStatus> m_status;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_FACE_MODULE_HPP
diff --git a/tools/nfd-status/fib-module.cpp b/tools/nfd-status/fib-module.cpp
new file mode 100644
index 0000000..a1c7f94
--- /dev/null
+++ b/tools/nfd-status/fib-module.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fib-module.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+void
+FibModule::fetchStatus(Controller& controller,
+                       const function<void()>& onSuccess,
+                       const Controller::CommandFailCallback& onFailure,
+                       const CommandOptions& options)
+{
+  controller.fetch<ndn::nfd::FibDataset>(
+    [this, onSuccess] (const std::vector<FibEntry>& result) {
+      m_status = result;
+      onSuccess();
+    },
+    onFailure, options);
+}
+
+void
+FibModule::formatStatusXml(std::ostream& os) const
+{
+  os << "<fib>";
+  for (const FibEntry& item : m_status) {
+    this->formatItemXml(os, item);
+  }
+  os << "</fib>";
+}
+
+void
+FibModule::formatItemXml(std::ostream& os, const FibEntry& item) const
+{
+  os << "<fibEntry>";
+
+  os << "<prefix>" << xml::Text{item.getPrefix().toUri()} << "</prefix>";
+
+  os << "<nextHops>";
+  for (const NextHopRecord& nh : item.getNextHopRecords()) {
+    os << "<nextHop>"
+       << "<faceId>" << nh.getFaceId() << "</faceId>"
+       << "<cost>" << nh.getCost() << "</cost>"
+       << "</nextHop>";
+  }
+  os << "</nextHops>";
+
+  os << "</fibEntry>";
+}
+
+void
+FibModule::formatStatusText(std::ostream& os) const
+{
+  os << "FIB:\n";
+  for (const FibEntry& item : m_status) {
+    this->formatItemText(os, item);
+  }
+}
+
+void
+FibModule::formatItemText(std::ostream& os, const FibEntry& item) const
+{
+  os << "  " << item.getPrefix() << " nexthops={";
+
+  text::Separator sep(", ");
+  for (const NextHopRecord& nh : item.getNextHopRecords()) {
+    os << sep
+       << "faceid=" << nh.getFaceId()
+       << " (cost=" << nh.getCost() << ")";
+  }
+
+  os << "}";
+  os << "\n";
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/fib-module.hpp b/tools/nfd-status/fib-module.hpp
new file mode 100644
index 0000000..353e594
--- /dev/null
+++ b/tools/nfd-status/fib-module.hpp
@@ -0,0 +1,78 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_FIB_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_FIB_MODULE_HPP
+
+#include "module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::FibEntry;
+using ndn::nfd::NextHopRecord;
+
+/** \brief provides access to NFD FIB management
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/FibMgmt
+ */
+class FibModule : public Module, noncopyable
+{
+public:
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override;
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override;
+
+  /** \brief format a single status item as XML
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemXml(std::ostream& os, const FibEntry& item) const;
+
+  virtual void
+  formatStatusText(std::ostream& os) const override;
+
+  /** \brief format a single status item as text
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemText(std::ostream& os, const FibEntry& item) const;
+
+private:
+  std::vector<FibEntry> m_status;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_FIB_MODULE_HPP
diff --git a/tools/nfd-status/format-helpers.cpp b/tools/nfd-status/format-helpers.cpp
new file mode 100644
index 0000000..c7e595b
--- /dev/null
+++ b/tools/nfd-status/format-helpers.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+namespace xml {
+
+void
+printHeader(std::ostream& os)
+{
+  os << "<?xml version=\"1.0\"?>"
+     << "<nfdStatus xmlns=\"ndn:/localhost/nfd/status/1\">";
+}
+
+void
+printFooter(std::ostream& os)
+{
+  os << "</nfdStatus>";
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Text& text)
+{
+  for (char ch : text.s) {
+    switch (ch) {
+      case '"':
+        os << "&quot;";
+        break;
+      case '&':
+        os << "&amp;";
+        break;
+      case '\'':
+        os << "&apos;";
+        break;
+      case '<':
+        os << "&lt;";
+        break;
+      case '>':
+        os << "&gt;";
+        break;
+      default:
+        os << ch;
+        break;
+    }
+  }
+  return os;
+}
+
+std::string
+formatSeconds(time::seconds d)
+{
+  return "PT" + to_string(d.count()) + "S";
+}
+
+std::string
+formatTimestamp(time::system_clock::TimePoint t)
+{
+  return time::toString(t, "%Y-%m-%dT%H:%M:%S%F");
+}
+
+} // namespace xml
+
+namespace text {
+
+Separator::Separator(const std::string& first, const std::string& subsequent)
+  : m_first(first)
+  , m_subsequent(subsequent)
+  , m_count(0)
+{
+}
+
+Separator::Separator(const std::string& subsequent)
+  : Separator("", subsequent)
+{
+}
+
+std::ostream&
+operator<<(std::ostream& os, Separator& sep)
+{
+  if (++sep.m_count == 1) {
+    return os << sep.m_first;
+  }
+  return os << sep.m_subsequent;
+}
+
+std::string
+formatSeconds(time::seconds d, bool isLong)
+{
+  return to_string(d.count()) + (isLong ? " seconds" : "s");
+}
+
+std::string
+formatTimestamp(time::system_clock::TimePoint t)
+{
+  return time::toIsoString(t);
+}
+
+} // namespace text
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/format-helpers.hpp b/tools/nfd-status/format-helpers.hpp
new file mode 100644
index 0000000..11d3d5a
--- /dev/null
+++ b/tools/nfd-status/format-helpers.hpp
@@ -0,0 +1,118 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_FORMAT_HELPERS_HPP
+#define NFD_TOOLS_NFD_STATUS_FORMAT_HELPERS_HPP
+
+#include "common.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+namespace xml {
+
+void
+printHeader(std::ostream& os);
+
+void
+printFooter(std::ostream& os);
+
+struct Text
+{
+  const std::string& s;
+};
+
+/** \brief print XML text with special character represented as predefined entities
+ */
+std::ostream&
+operator<<(std::ostream& os, const Text& text);
+
+std::string
+formatSeconds(time::seconds d);
+
+template<typename DURATION>
+std::string
+formatDuration(DURATION d)
+{
+  return formatSeconds(time::duration_cast<time::seconds>(d));
+}
+
+std::string
+formatTimestamp(time::system_clock::TimePoint t);
+
+} // namespace xml
+
+namespace text {
+
+/** \brief print different string on first and subsequent usage
+ *
+ *  \code
+ *  Separator sep(",");
+ *  for (int i = 1; i <= 3; ++i) {
+ *    os << sep << i;
+ *  }
+ *  // prints: 1,2,3
+ *  \endcode
+ */
+class Separator
+{
+public:
+  Separator(const std::string& first, const std::string& subsequent);
+
+  explicit
+  Separator(const std::string& subsequent);
+
+private:
+  std::string m_first;
+  std::string m_subsequent;
+  int m_count;
+
+  friend std::ostream& operator<<(std::ostream& os, Separator& sep);
+};
+
+std::ostream&
+operator<<(std::ostream& os, Separator& sep);
+
+std::string
+formatSeconds(time::seconds d, bool isLong = false);
+
+template<typename DURATION>
+std::string
+formatDuration(DURATION d, bool isLong = false)
+{
+  return formatSeconds(time::duration_cast<time::seconds>(d), isLong);
+}
+
+std::string
+formatTimestamp(time::system_clock::TimePoint t);
+
+} // namespace text
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_FORMAT_HELPERS_HPP
diff --git a/tools/nfd-status/forwarder-general-module.cpp b/tools/nfd-status/forwarder-general-module.cpp
new file mode 100644
index 0000000..41b715a
--- /dev/null
+++ b/tools/nfd-status/forwarder-general-module.cpp
@@ -0,0 +1,190 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "forwarder-general-module.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+ForwarderGeneralModule::ForwarderGeneralModule()
+  : m_nfdIdCollector(nullptr)
+{
+}
+
+void
+ForwarderGeneralModule::fetchStatus(Controller& controller,
+                                    const function<void()>& onSuccess,
+                                    const Controller::CommandFailCallback& onFailure,
+                                    const CommandOptions& options)
+{
+  controller.fetch<ndn::nfd::ForwarderGeneralStatusDataset>(
+    [this, onSuccess] (const ForwarderStatus& result) {
+      m_status = result;
+      onSuccess();
+    },
+    onFailure, options);
+}
+
+static time::system_clock::Duration
+calculateUptime(const ForwarderStatus& status)
+{
+  return status.getCurrentTimestamp() - status.getStartTimestamp();
+}
+
+const Name&
+ForwarderGeneralModule::getNfdId() const
+{
+  if (m_nfdIdCollector != nullptr && m_nfdIdCollector->hasNfdId()) {
+    return m_nfdIdCollector->getNfdId();
+  }
+
+  static Name unavailable("/nfdId-unavailable");
+  return unavailable;
+}
+
+void
+ForwarderGeneralModule::formatStatusXml(std::ostream& os) const
+{
+  this->formatItemXml(os, m_status, this->getNfdId());
+}
+
+void
+ForwarderGeneralModule::formatItemXml(std::ostream& os, const ForwarderStatus& item,
+                                      const Name& nfdId) const
+{
+  os << "<generalStatus>";
+
+  os << "<nfdId>" << nfdId << "</nfdId>";
+  os << "<version>" << xml::Text{item.getNfdVersion()} << "</version>";
+  os << "<startTime>" << xml::formatTimestamp(item.getStartTimestamp()) << "</startTime>";
+  os << "<currentTime>" << xml::formatTimestamp(item.getCurrentTimestamp()) << "</currentTime>";
+  os << "<uptime>" << xml::formatDuration(calculateUptime(item)) << "</uptime>";
+
+  os << "<nNameTreeEntries>" << item.getNNameTreeEntries() << "</nNameTreeEntries>";
+  os << "<nFibEntries>" << item.getNFibEntries() << "</nFibEntries>";
+  os << "<nPitEntries>" << item.getNPitEntries() << "</nPitEntries>";
+  os << "<nMeasurementsEntries>" << item.getNMeasurementsEntries() << "</nMeasurementsEntries>";
+  os << "<nCsEntries>" << item.getNCsEntries() << "</nCsEntries>";
+
+  os << "<packetCounters>";
+  os << "<incomingPackets>"
+     << "<nInterests>" << item.getNInInterests() << "</nInterests>"
+     << "<nDatas>" << item.getNInDatas() << "</nDatas>"
+     << "<nNacks>" << item.getNInNacks() << "</nNacks>"
+     << "</incomingPackets>";
+  os << "<outgoingPackets>"
+     << "<nInterests>" << item.getNOutInterests() << "</nInterests>"
+     << "<nDatas>" << item.getNOutDatas() << "</nDatas>"
+     << "<nNacks>" << item.getNOutNacks() << "</nNacks>"
+     << "</outgoingPackets>";
+  os << "</packetCounters>";
+
+  os << "</generalStatus>";
+}
+
+void
+ForwarderGeneralModule::formatStatusText(std::ostream& os) const
+{
+  os << "General NFD status:\n";
+  this->formatItemText(os, m_status, this->getNfdId());
+}
+
+void
+ForwarderGeneralModule::formatItemText(std::ostream& os, const ForwarderStatus& item,
+                                       const Name& nfdId) const
+{
+  os << "                 nfdId=" << nfdId << "\n";
+  os << "               version=" << item.getNfdVersion() << "\n";
+  os << "             startTime=" << text::formatTimestamp(item.getStartTimestamp()) << "\n";
+  os << "           currentTime=" << text::formatTimestamp(item.getCurrentTimestamp()) << "\n";
+  os << "                uptime=" << text::formatDuration(calculateUptime(item), true) << "\n";
+
+  os << "      nNameTreeEntries=" << item.getNNameTreeEntries() << "\n";
+  os << "           nFibEntries=" << item.getNFibEntries() << "\n";
+  os << "           nPitEntries=" << item.getNPitEntries() << "\n";
+  os << "  nMeasurementsEntries=" << item.getNMeasurementsEntries() << "\n";
+  os << "            nCsEntries=" << item.getNCsEntries() << "\n";
+
+  os << "          nInInterests=" << item.getNInInterests() << "\n";
+  os << "         nOutInterests=" << item.getNOutInterests() << "\n";
+  os << "              nInDatas=" << item.getNInDatas() << "\n";
+  os << "             nOutDatas=" << item.getNOutDatas() << "\n";
+  os << "              nInNacks=" << item.getNInNacks() << "\n";
+  os << "             nOutNacks=" << item.getNOutNacks() << "\n";
+}
+
+NfdIdCollector::NfdIdCollector(unique_ptr<ndn::Validator> inner)
+  : m_inner(std::move(inner))
+  , m_hasNfdId(false)
+{
+  BOOST_ASSERT(m_inner != nullptr);
+}
+
+const Name&
+NfdIdCollector::getNfdId() const
+{
+  if (!m_hasNfdId) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("NfdId is unavailable"));
+  }
+
+  return m_nfdId;
+}
+
+void
+NfdIdCollector::checkPolicy(const Data& data, int nSteps,
+                            const ndn::OnDataValidated& accept,
+                            const ndn::OnDataValidationFailed& reject,
+                            std::vector<shared_ptr<ndn::ValidationRequest>>& nextSteps)
+{
+  ndn::OnDataValidated accepted = [this, accept] (const shared_ptr<const Data>& data) {
+    accept(data); // pass the Data to Validator user's validated callback
+
+    if (m_hasNfdId) {
+      return;
+    }
+
+    const ndn::Signature& sig = data->getSignature();
+    if (!sig.hasKeyLocator()) {
+      return;
+    }
+
+    const ndn::KeyLocator& kl = sig.getKeyLocator();
+    if (kl.getType() != ndn::KeyLocator::KeyLocator_Name) {
+      return;
+    }
+
+    m_nfdId = kl.getName();
+    m_hasNfdId = true;
+  };
+
+  BOOST_ASSERT(nSteps == 0);
+  m_inner->validate(data, accepted, reject);
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/forwarder-general-module.hpp b/tools/nfd-status/forwarder-general-module.hpp
new file mode 100644
index 0000000..a55bc21
--- /dev/null
+++ b/tools/nfd-status/forwarder-general-module.hpp
@@ -0,0 +1,140 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_FORWARDER_GENERAL_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_FORWARDER_GENERAL_MODULE_HPP
+
+#include "module.hpp"
+#include <ndn-cxx/security/validator.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::ForwarderStatus;
+
+class NfdIdCollector;
+
+/** \brief provides access to NFD forwarder general status
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/ForwarderStatus
+ */
+class ForwarderGeneralModule : public Module, noncopyable
+{
+public:
+  ForwarderGeneralModule();
+
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override;
+
+  void
+  setNfdIdCollector(const NfdIdCollector& nfdIdCollector)
+  {
+    m_nfdIdCollector = &nfdIdCollector;
+  }
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override;
+
+  /** \brief format a single status item as XML
+   *  \param os output stream
+   *  \param item status item
+   *  \param nfdId NFD's signing certificate name
+   */
+  void
+  formatItemXml(std::ostream& os, const ForwarderStatus& item, const Name& nfdId) const;
+
+  virtual void
+  formatStatusText(std::ostream& os) const override;
+
+  /** \brief format a single status item as text
+   *  \param os output stream
+   *  \param item status item
+   *  \param nfdId NFD's signing certificate name
+   */
+  void
+  formatItemText(std::ostream& os, const ForwarderStatus& item, const Name& nfdId) const;
+
+private:
+  const Name&
+  getNfdId() const;
+
+private:
+  const NfdIdCollector* m_nfdIdCollector;
+  ForwarderStatus m_status;
+};
+
+
+/** \brief a validator that can collect NFD's signing certificate name
+ *
+ *  This validator redirects all validation requests to an inner validator.
+ *  For the first Data packet accepted by the inner validator that has a Name in KeyLocator,
+ *  this Name is collected as NFD's signing certificate name.
+ */
+class NfdIdCollector : public ndn::Validator
+{
+public:
+  explicit
+  NfdIdCollector(unique_ptr<ndn::Validator> inner);
+
+  bool
+  hasNfdId() const
+  {
+    return m_hasNfdId;
+  }
+
+  const Name&
+  getNfdId() const;
+
+protected:
+  virtual void
+  checkPolicy(const Interest& interest, int nSteps,
+              const ndn::OnInterestValidated& accept,
+              const ndn::OnInterestValidationFailed& reject,
+              std::vector<shared_ptr<ndn::ValidationRequest>>& nextSteps) override
+  {
+    BOOST_ASSERT(nSteps == 0);
+    m_inner->validate(interest, accept, reject);
+  }
+
+  virtual void
+  checkPolicy(const Data& data, int nSteps,
+              const ndn::OnDataValidated& accept,
+              const ndn::OnDataValidationFailed& reject,
+              std::vector<shared_ptr<ndn::ValidationRequest>>& nextSteps) override;
+
+private:
+  unique_ptr<ndn::Validator> m_inner;
+  bool m_hasNfdId;
+  Name m_nfdId;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_FORWARDER_GENERAL_MODULE_HPP
diff --git a/tools/nfd-status/main.cpp b/tools/nfd-status/main.cpp
new file mode 100644
index 0000000..a3033a6
--- /dev/null
+++ b/tools/nfd-status/main.cpp
@@ -0,0 +1,199 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "version.hpp"
+#include <ndn-cxx/security/validator-null.hpp>
+
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options/parsers.hpp>
+
+#include "status-report.hpp"
+#include "forwarder-general-module.hpp"
+#include "channel-module.hpp"
+#include "face-module.hpp"
+#include "fib-module.hpp"
+#include "rib-module.hpp"
+#include "strategy-choice-module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+enum class OutputFormat
+{
+  XML = 1,
+  TEXT = 2
+};
+
+struct Options
+{
+  OutputFormat output = OutputFormat::TEXT;
+  bool wantForwarderGeneral = false;
+  bool wantChannels = false;
+  bool wantFaces = false;
+  bool wantFib = false;
+  bool wantRib = false;
+  bool wantStrategyChoice = false;
+};
+
+static void
+showUsage(std::ostream& os, const boost::program_options::options_description& cmdOptions)
+{
+  os << "Usage: nfd-status [options]\n\n"
+     << "Show NFD version and status information.\n\n"
+     << cmdOptions;
+}
+
+/** \brief parse command line, and show usage if necessary
+ *  \return if first item is -1, caller should retrieve and display StatusReport;
+ *          otherwise, caller should immediately exit with the specified exit code
+ */
+static std::tuple<int, Options>
+parseCommandLine(int argc, const char* const* argv)
+{
+  Options options;
+
+  namespace po = boost::program_options;
+  po::options_description cmdOptions("Options");
+  cmdOptions.add_options()
+    ("help,h", "print this help message")
+    ("version,V", "show program version")
+    ("general,v", po::bool_switch(&options.wantForwarderGeneral), "show general status")
+    ("channels,c", po::bool_switch(&options.wantChannels), "show channels")
+    ("faces,f", po::bool_switch(&options.wantFaces), "show faces")
+    ("fib,b", po::bool_switch(&options.wantFib), "show FIB entries")
+    ("rib,r", po::bool_switch(&options.wantRib), "show RIB routes")
+    ("sc,s", po::bool_switch(&options.wantStrategyChoice), "show strategy choice entries")
+    ("xml,x", "output as XML instead of text (implies -vcfbrs)");
+  po::variables_map vm;
+  try {
+    po::store(po::parse_command_line(argc, argv, cmdOptions), vm);
+  }
+  catch (const po::error& e) {
+    std::cerr << e.what() << "\n";
+    showUsage(std::cerr, cmdOptions);
+    return std::make_tuple(2, options);
+  }
+  po::notify(vm);
+
+  if (vm.count("help") > 0) {
+    showUsage(std::cout, cmdOptions);
+    return std::make_tuple(0, options);
+  }
+  if (vm.count("version") > 0) {
+    std::cout << "nfd-status " << NFD_VERSION_BUILD_STRING << "\n";
+    return std::make_tuple(0, options);
+  }
+
+  if (vm.count("xml") > 0) {
+    options.output = OutputFormat::XML;
+  }
+  if (options.output == OutputFormat::XML ||
+      (!options.wantForwarderGeneral && !options.wantChannels && !options.wantFaces &&
+       !options.wantFib && !options.wantRib && !options.wantStrategyChoice)) {
+    options.wantForwarderGeneral = options.wantChannels = options.wantFaces =
+      options.wantFib = options.wantRib = options.wantStrategyChoice = true;
+  }
+
+  return std::make_tuple(-1, options);
+}
+
+static int
+main(int argc, char** argv)
+{
+  int exitCode = -1;
+  Options options;
+  std::tie(exitCode, options) = parseCommandLine(argc, argv);
+  if (exitCode >= 0) {
+    return exitCode;
+  }
+
+  Face face;
+  KeyChain keyChain;
+  unique_ptr<Validator> validator = make_unique<ndn::ValidatorNull>();
+  CommandOptions ctrlOptions;
+
+  StatusReport report;
+
+  if (options.wantForwarderGeneral) {
+    auto nfdIdCollector = make_unique<NfdIdCollector>(std::move(validator));
+    auto forwarderGeneralModule = make_unique<ForwarderGeneralModule>();
+    forwarderGeneralModule->setNfdIdCollector(*nfdIdCollector);
+    report.sections.push_back(std::move(forwarderGeneralModule));
+    validator = std::move(nfdIdCollector);
+  }
+
+  if (options.wantChannels) {
+    report.sections.push_back(make_unique<ChannelModule>());
+  }
+
+  if (options.wantFaces) {
+    report.sections.push_back(make_unique<FaceModule>());
+  }
+
+  if (options.wantFib) {
+    report.sections.push_back(make_unique<FibModule>());
+  }
+
+  if (options.wantRib) {
+    report.sections.push_back(make_unique<RibModule>());
+  }
+
+  if (options.wantStrategyChoice) {
+    report.sections.push_back(make_unique<StrategyChoiceModule>());
+  }
+
+  uint32_t code = report.collect(face, keyChain, *validator, ctrlOptions);
+  if (code != 0) {
+    // Give a simple error code for end user.
+    // Technical support personnel:
+    // 1. get the exact command from end user
+    // 2. code div 1000000 is zero-based section index
+    // 3. code mod 1000000 is a Controller.fetch error code
+    std::cerr << "Error while collecting status report (" << code << ").\n";
+    return 1;
+  }
+
+  switch (options.output) {
+    case OutputFormat::XML:
+      report.formatXml(std::cout);
+      break;
+    case OutputFormat::TEXT:
+      report.formatText(std::cout);
+      break;
+  }
+  return 0;
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+int
+main(int argc, char** argv)
+{
+  return nfd::tools::nfd_status::main(argc, argv);
+}
diff --git a/tools/nfd-status/module.hpp b/tools/nfd-status/module.hpp
new file mode 100644
index 0000000..07d5647
--- /dev/null
+++ b/tools/nfd-status/module.hpp
@@ -0,0 +1,80 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_MODULE_HPP
+
+#include "common.hpp"
+#include <ndn-cxx/management/nfd-controller.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::Controller;
+using ndn::nfd::CommandOptions;
+
+/** \brief provides access to an NFD management module
+ *  \note This type is an interface. It should not have member fields.
+ */
+class Module
+{
+public:
+  virtual
+  ~Module() = default;
+
+  /** \brief collect status from NFD
+   *  \pre no other fetchStatus is in progress
+   *  \param controller a controller through which StatusDataset can be requested
+   *  \param onSuccess invoked when status has been collected into this instance
+   *  \param onFailure passed to controller.fetch
+   *  \param options passed to controller.fetch
+   */
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) = 0;
+
+  /** \brief format collected status as XML
+   *  \pre fetchStatus has been successful
+   *  \param os output stream
+   */
+  virtual void
+  formatStatusXml(std::ostream& os) const = 0;
+
+  /** \brief format collected status as text
+   *  \pre fetchStatus has been successful
+   *  \param os output stream
+   */
+  virtual void
+  formatStatusText(std::ostream& os) const = 0;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_MODULE_HPP
diff --git a/tools/nfd-status/rib-module.cpp b/tools/nfd-status/rib-module.cpp
new file mode 100644
index 0000000..fe254cc
--- /dev/null
+++ b/tools/nfd-status/rib-module.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-module.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+void
+RibModule::fetchStatus(Controller& controller,
+                       const function<void()>& onSuccess,
+                       const Controller::CommandFailCallback& onFailure,
+                       const CommandOptions& options)
+{
+  controller.fetch<ndn::nfd::RibDataset>(
+    [this, onSuccess] (const std::vector<RibEntry>& result) {
+      m_status = result;
+      onSuccess();
+    },
+    onFailure, options);
+}
+
+void
+RibModule::formatStatusXml(std::ostream& os) const
+{
+  os << "<rib>";
+  for (const RibEntry& item : m_status) {
+    this->formatItemXml(os, item);
+  }
+  os << "</rib>";
+}
+
+void
+RibModule::formatItemXml(std::ostream& os, const RibEntry& item) const
+{
+  os << "<ribEntry>";
+
+  os << "<prefix>" << xml::Text{item.getName().toUri()} << "</prefix>";
+
+  os << "<routes>";
+  for (const Route& route : item.getRoutes()) {
+    os << "<route>"
+       << "<faceId>" << route.getFaceId() << "</faceId>"
+       << "<origin>" << route.getOrigin() << "</origin>"
+       << "<cost>" << route.getCost() << "</cost>";
+    if (route.getFlags() == ndn::nfd::ROUTE_FLAGS_NONE) {
+       os << "<flags/>";
+    }
+    else {
+       os << "<flags>";
+      if (route.isChildInherit()) {
+        os << "<childInherit/>";
+      }
+      if (route.isRibCapture()) {
+        os << "<ribCapture/>";
+      }
+      os << "</flags>";
+    }
+    if (!route.hasInfiniteExpirationPeriod()) {
+      os << "<expirationPeriod>"
+         << xml::formatDuration(route.getExpirationPeriod())
+         << "</expirationPeriod>";
+    }
+    os << "</route>";
+  }
+  os << "</routes>";
+
+  os << "</ribEntry>";
+}
+
+void
+RibModule::formatStatusText(std::ostream& os) const
+{
+  os << "RIB:\n";
+  for (const RibEntry& item : m_status) {
+    this->formatItemText(os, item);
+  }
+}
+
+void
+RibModule::formatItemText(std::ostream& os, const RibEntry& item) const
+{
+  os << "  " << item.getName() << " route={";
+
+  text::Separator sep(", ");
+  for (const Route& route : item.getRoutes()) {
+    os << sep
+       << "faceid=" << route.getFaceId()
+       << " (origin=" << route.getOrigin()
+       << " cost=" << route.getCost();
+    if (!route.hasInfiniteExpirationPeriod()) {
+      os << " expires=" << text::formatDuration(route.getExpirationPeriod());
+    }
+    if (route.isChildInherit()) {
+      os << " ChildInherit";
+    }
+    if (route.isRibCapture()) {
+      os << " RibCapture";
+    }
+    os << ")";
+  }
+
+  os << "}";
+  os << "\n";
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/rib-module.hpp b/tools/nfd-status/rib-module.hpp
new file mode 100644
index 0000000..6a4ddd1
--- /dev/null
+++ b/tools/nfd-status/rib-module.hpp
@@ -0,0 +1,78 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_RIB_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_RIB_MODULE_HPP
+
+#include "module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::RibEntry;
+using ndn::nfd::Route;
+
+/** \brief provides access to NFD RIB management
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/RibMgmt
+ */
+class RibModule : public Module, noncopyable
+{
+public:
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override;
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override;
+
+  /** \brief format a single status item as XML
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemXml(std::ostream& os, const RibEntry& item) const;
+
+  virtual void
+  formatStatusText(std::ostream& os) const override;
+
+  /** \brief format a single status item as text
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemText(std::ostream& os, const RibEntry& item) const;
+
+private:
+  std::vector<RibEntry> m_status;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_RIB_MODULE_HPP
diff --git a/tools/nfd-status/status-report.cpp b/tools/nfd-status/status-report.cpp
new file mode 100644
index 0000000..ff04e1f
--- /dev/null
+++ b/tools/nfd-status/status-report.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "status-report.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+uint32_t
+StatusReport::collect(Face& face, KeyChain& keyChain, Validator& validator, const CommandOptions& options)
+{
+  Controller controller(face, keyChain, validator);
+  uint32_t errorCode = 0;
+
+  for (size_t i = 0; i < sections.size(); ++i) {
+    Module& module = *sections[i];
+    module.fetchStatus(
+      controller,
+      [] {},
+      [i, &errorCode] (uint32_t code, const std::string& reason) {
+        errorCode = i * 1000000 + code;
+      },
+      options);
+  }
+
+  this->processEvents(face);
+  return errorCode;
+}
+
+void
+StatusReport::processEvents(Face& face)
+{
+  face.processEvents();
+}
+
+void
+StatusReport::formatXml(std::ostream& os) const
+{
+  xml::printHeader(os);
+  for (const unique_ptr<Module>& module : sections) {
+    module->formatStatusXml(os);
+  }
+  xml::printFooter(os);
+}
+
+void
+StatusReport::formatText(std::ostream& os) const
+{
+  for (const unique_ptr<Module>& module : sections) {
+    module->formatStatusText(os);
+  }
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/status-report.hpp b/tools/nfd-status/status-report.hpp
new file mode 100644
index 0000000..80aaee7
--- /dev/null
+++ b/tools/nfd-status/status-report.hpp
@@ -0,0 +1,85 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_STATUS_REPORT_HPP
+#define NFD_TOOLS_NFD_STATUS_STATUS_REPORT_HPP
+
+#include "module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::Face;
+using ndn::security::KeyChain;
+using ndn::Validator;
+
+/** \brief collects and prints NFD status report
+ */
+class StatusReport : noncopyable
+{
+public:
+#ifdef WITH_TESTS
+  virtual
+  ~StatusReport() = default;
+#endif
+
+  /** \brief collect status via chosen \p sections
+   *
+   *  This function is blocking. It has exclusive use of \p face.
+   *
+   *  \return if status has been fetched successfully, 0;
+   *          otherwise, error code from any failed section, plus 1000000 * section index
+   */
+  uint32_t
+  collect(Face& face, KeyChain& keyChain, Validator& validator, const CommandOptions& options);
+
+  /** \brief print an XML report
+   *  \param os output stream
+   */
+  void
+  formatXml(std::ostream& os) const;
+
+  /** \brief print a text report
+   *  \param os output stream
+   */
+  void
+  formatText(std::ostream& os) const;
+
+private:
+  VIRTUAL_WITH_TESTS void
+  processEvents(Face& face);
+
+public:
+  /** \brief modules through which status is collected
+   */
+  std::vector<unique_ptr<Module>> sections;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_STATUS_REPORT_HPP
diff --git a/tools/nfd-status/strategy-choice-module.cpp b/tools/nfd-status/strategy-choice-module.cpp
new file mode 100644
index 0000000..efdff50
--- /dev/null
+++ b/tools/nfd-status/strategy-choice-module.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "strategy-choice-module.hpp"
+#include "format-helpers.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+void
+StrategyChoiceModule::fetchStatus(Controller& controller,
+                                  const function<void()>& onSuccess,
+                                  const Controller::CommandFailCallback& onFailure,
+                                  const CommandOptions& options)
+{
+  controller.fetch<ndn::nfd::StrategyChoiceDataset>(
+    [this, onSuccess] (const std::vector<StrategyChoice>& result) {
+      m_status = result;
+      onSuccess();
+    },
+    onFailure, options);
+}
+
+void
+StrategyChoiceModule::formatStatusXml(std::ostream& os) const
+{
+  os << "<strategyChoices>";
+  for (const StrategyChoice& item : m_status) {
+    this->formatItemXml(os, item);
+  }
+  os << "</strategyChoices>";
+}
+
+void
+StrategyChoiceModule::formatItemXml(std::ostream& os, const StrategyChoice& item) const
+{
+  os << "<strategyChoice>";
+  os << "<namespace>" << xml::Text{item.getName().toUri()} << "</namespace>";
+  os << "<strategy><name>" << xml::Text{item.getStrategy().toUri()} << "</name></strategy>";
+  os << "</strategyChoice>";
+}
+
+void
+StrategyChoiceModule::formatStatusText(std::ostream& os) const
+{
+  os << "Strategy choices:\n";
+  for (const StrategyChoice& item : m_status) {
+    this->formatItemText(os, item);
+  }
+}
+
+void
+StrategyChoiceModule::formatItemText(std::ostream& os, const StrategyChoice& item) const
+{
+  os << "  " << item.getName()
+     << " strategy=" << item.getStrategy()
+     << "\n";
+}
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfd-status/strategy-choice-module.hpp b/tools/nfd-status/strategy-choice-module.hpp
new file mode 100644
index 0000000..be84374
--- /dev/null
+++ b/tools/nfd-status/strategy-choice-module.hpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NFD_STATUS_STARTEGY_CHOICE_MODULE_HPP
+#define NFD_TOOLS_NFD_STATUS_STARTEGY_CHOICE_MODULE_HPP
+
+#include "module.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfd_status {
+
+using ndn::nfd::StrategyChoice;
+
+/** \brief provides access to NFD Strategy Choice management
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/StrategyChoice
+ */
+class StrategyChoiceModule : public Module, noncopyable
+{
+public:
+  virtual void
+  fetchStatus(Controller& controller,
+              const function<void()>& onSuccess,
+              const Controller::CommandFailCallback& onFailure,
+              const CommandOptions& options) override;
+
+  virtual void
+  formatStatusXml(std::ostream& os) const override;
+
+  /** \brief format a single status item as XML
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemXml(std::ostream& os, const StrategyChoice& item) const;
+
+  virtual void
+  formatStatusText(std::ostream& os) const override;
+
+  /** \brief format a single status item as text
+   *  \param os output stream
+   *  \param item status item
+   */
+  void
+  formatItemText(std::ostream& os, const StrategyChoice& item) const;
+
+private:
+  std::vector<StrategyChoice> m_status;
+};
+
+} // namespace nfd_status
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFD_STATUS_STARTEGY_CHOICE_MODULE_HPP
diff --git a/tools/wscript b/tools/wscript
index ee1909e..f3dd5c9 100644
--- a/tools/wscript
+++ b/tools/wscript
@@ -4,26 +4,59 @@
 
 top = '..'
 
+TOOLS_DEPENDENCY = 'core-objects NDN_CXX BOOST LIBRESOLV'
+
 def build(bld):
-    # List all .cpp files (whole tool should be in one .cpp)
+    # single object tools
+    # tools/example-tool.cpp should a self-contained tool with a main function
+    # and it's built into build/bin/example-tool.
+    # These tools cannot be unit-tested.
     for i in bld.path.ant_glob(['*.cpp']):
         name = str(i)[:-len(".cpp")]
-        bld(features=['cxx', 'cxxprogram'],
+        bld(features='cxx cxxprogram',
             target="../bin/%s" % name,
-            source=[i] + bld.path.ant_glob(['%s/**/*.cpp' % name]),
-            use='core-objects NDN_CXX BOOST LIBRESOLV'
+            source=[i],
+            use=TOOLS_DEPENDENCY
             )
 
-    # List all directories files (tool can has multiple .cpp in the directory)
+
+    # sub-directory tools
+    # tools/example-tool/**/*.cpp is compiled and linked into build/bin/example-tool
+    # tools/example-tool/main.cpp must exist and it should contain the main function.
+    # All other objects are collected into 'tools-objects' and can be unit-tested.
+    testableObjects = []
     for name in bld.path.ant_glob(['*'], dir=True, src=False):
-        srcFiles = bld.path.ant_glob(['%s/**/*.cpp' % name])
+        mainFile = bld.path.find_node(['%s/main.cpp' % name])
+        if mainFile is None:
+          continue # not a C++ tool
+        srcFiles = bld.path.ant_glob(['%s/**/*.cpp' % name], excl=['%s/main.cpp' % name])
         if len(srcFiles) > 0:
-            bld(features=['cxx', 'cxxprogram'],
-                target="../bin/%s" % name,
+            srcObjects = 'tools-%s-objects' % name
+            bld(features='cxx',
+                name=srcObjects,
                 source=srcFiles,
-                use='core-objects NDN_CXX BOOST LIBRESOLV',
+                use=TOOLS_DEPENDENCY,
                 includes='%s' % name,
                 )
+            testableObjects.append(srcObjects)
+
+            bld(features='cxx cxxprogram',
+                target="../bin/%s" % name,
+                source=[mainFile],
+                use=TOOLS_DEPENDENCY + ' ' + srcObjects,
+                includes='%s' % name,
+                )
+        else:
+            bld(features='cxx cxxprogram',
+                target="../bin/%s" % name,
+                source=[mainFile],
+                use=TOOLS_DEPENDENCY,
+                includes='%s' % name,
+                )
+
+    bld(name='tools-objects',
+        export_includes='.',
+        use=testableObjects)
 
     bld(features="subst",
         source=bld.path.ant_glob(['*.sh', '*.py']),