Rename 'tests/unit-tests' directory to 'tests/unit'

Change-Id: I78ea29938259fac288781bed12fb2399ac7eba26
diff --git a/tests/unit/data.t.cpp b/tests/unit/data.t.cpp
new file mode 100644
index 0000000..0d91005
--- /dev/null
+++ b/tests/unit/data.t.cpp
@@ -0,0 +1,442 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "data.hpp"
+#include "encoding/buffer-stream.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+#include "security/transform/private-key.hpp"
+#include "security/transform/public-key.hpp"
+#include "security/transform/signer-filter.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "security/verification-helpers.hpp"
+#include "util/sha256.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestData)
+
+const uint8_t CONTENT1[] = {0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x21};
+
+const uint8_t DATA1[] = {
+0x06, 0xc5, // Data
+    0x07, 0x14, // Name
+        0x08, 0x05,
+            0x6c, 0x6f, 0x63, 0x61, 0x6c,
+        0x08, 0x03,
+            0x6e, 0x64, 0x6e,
+        0x08, 0x06,
+            0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
+    0x14, 0x04, // MetaInfo
+        0x19, 0x02, // FreshnessPeriod
+            0x27, 0x10,
+    0x15, 0x08, // Content
+        0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x21,
+    0x16, 0x1b, // SignatureInfo
+        0x1b, 0x01, // SignatureType
+            0x01,
+        0x1c, 0x16, // KeyLocator
+            0x07, 0x14, // Name
+                0x08, 0x04,
+                    0x74, 0x65, 0x73, 0x74,
+                0x08, 0x03,
+                    0x6b, 0x65, 0x79,
+                0x08, 0x07,
+                    0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+    0x17, 0x80, // SignatureValue
+        0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+        0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+        0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+        0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+        0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+        0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+        0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+        0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+        0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+        0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1
+};
+
+// ---- constructor, encode, decode ----
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+  Data d;
+  BOOST_CHECK_EQUAL(d.hasWire(), false);
+  BOOST_CHECK_EQUAL(d.getName(), "/");
+  BOOST_CHECK_EQUAL(d.getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(d.getFreshnessPeriod(), DEFAULT_FRESHNESS_PERIOD);
+  BOOST_CHECK(!d.getFinalBlock());
+  BOOST_CHECK_EQUAL(d.getContent().type(), tlv::Content);
+  BOOST_CHECK_EQUAL(d.getContent().value_size(), 0);
+  BOOST_CHECK(!d.getSignature());
+}
+
+class DataSigningKeyFixture
+{
+protected:
+  DataSigningKeyFixture()
+  {
+    m_privKey.loadPkcs1(PRIVATE_KEY_DER, sizeof(PRIVATE_KEY_DER));
+    auto buf = m_privKey.derivePublicKey();
+    m_pubKey.loadPkcs8(buf->data(), buf->size());
+  }
+
+protected:
+  security::transform::PrivateKey m_privKey;
+  security::transform::PublicKey m_pubKey;
+
+private:
+  static const uint8_t PRIVATE_KEY_DER[632];
+};
+
+const uint8_t DataSigningKeyFixture::PRIVATE_KEY_DER[] = {
+  0x30, 0x82, 0x02, 0x74, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x02, 0x5e, 0x30, 0x82, 0x02, 0x5a, 0x02, 0x01,
+  0x00, 0x02, 0x81, 0x81, 0x00, 0x9e, 0x06, 0x3e, 0x47, 0x85, 0xb2, 0x34, 0x37, 0xaa, 0x85, 0x47,
+  0xac, 0x03, 0x24, 0x83, 0xb5, 0x9c, 0xa8, 0x05, 0x3a, 0x24, 0x1e, 0xeb, 0x89, 0x01, 0xbb, 0xe9,
+  0x9b, 0xb2, 0xc3, 0x22, 0xac, 0x68, 0xe3, 0xf0, 0x6c, 0x02, 0xce, 0x68, 0xa6, 0xc4, 0xd0, 0xa7,
+  0x06, 0x90, 0x9c, 0xaa, 0x1b, 0x08, 0x1d, 0x8b, 0x43, 0x9a, 0x33, 0x67, 0x44, 0x6d, 0x21, 0xa3,
+  0x1b, 0x88, 0x9a, 0x97, 0x5e, 0x59, 0xc4, 0x15, 0x0b, 0xd9, 0x2c, 0xbd, 0x51, 0x07, 0x61, 0x82,
+  0xad, 0xc1, 0xb8, 0xd7, 0xbf, 0x9b, 0xcf, 0x7d, 0x24, 0xc2, 0x63, 0xf3, 0x97, 0x17, 0xeb, 0xfe,
+  0x62, 0x25, 0xba, 0x5b, 0x4d, 0x8a, 0xc2, 0x7a, 0xbd, 0x43, 0x8a, 0x8f, 0xb8, 0xf2, 0xf1, 0xc5,
+  0x6a, 0x30, 0xd3, 0x50, 0x8c, 0xc8, 0x9a, 0xdf, 0xef, 0xed, 0x35, 0xe7, 0x7a, 0x62, 0xea, 0x76,
+  0x7c, 0xbb, 0x08, 0x26, 0xc7, 0x02, 0x01, 0x11, 0x02, 0x81, 0x80, 0x04, 0xa5, 0xd4, 0xa7, 0xc0,
+  0x2a, 0xe3, 0x6b, 0x0c, 0x8b, 0x73, 0x0c, 0x96, 0xae, 0x40, 0x1b, 0xee, 0x04, 0xf1, 0x18, 0x4c,
+  0x5b, 0x43, 0x29, 0xad, 0x3a, 0x3b, 0x93, 0xa3, 0x60, 0x17, 0x9b, 0xa8, 0xbb, 0x68, 0xf4, 0x1e,
+  0x33, 0x3f, 0x50, 0x32, 0xf7, 0x13, 0xf8, 0xa9, 0xe6, 0x7d, 0x79, 0x44, 0x00, 0xde, 0x72, 0xed,
+  0xf2, 0x73, 0xfa, 0x7b, 0xae, 0x2a, 0x71, 0xc0, 0x40, 0xc8, 0x37, 0x6f, 0x38, 0xb2, 0x69, 0x1f,
+  0xa8, 0x83, 0x7b, 0x42, 0x00, 0x73, 0x46, 0xe6, 0x4c, 0x91, 0x7f, 0x13, 0x06, 0x69, 0x06, 0xd8,
+  0x3f, 0x22, 0x15, 0x75, 0xf6, 0xde, 0xcd, 0xb0, 0xbc, 0x66, 0x61, 0x91, 0x08, 0x9b, 0x2b, 0xb2,
+  0x00, 0xa9, 0x67, 0x05, 0x39, 0x40, 0xb9, 0x37, 0x85, 0x88, 0x4f, 0x76, 0x79, 0x63, 0xc0, 0x88,
+  0x3c, 0x86, 0xa8, 0x12, 0x94, 0x5f, 0xe4, 0x36, 0x3d, 0xea, 0xb9, 0x02, 0x41, 0x00, 0xb6, 0x2e,
+  0xbb, 0xcd, 0x2f, 0x3a, 0x99, 0xe0, 0xa1, 0xa5, 0x44, 0x77, 0xea, 0x0b, 0xbe, 0x16, 0x95, 0x0e,
+  0x64, 0xa7, 0x68, 0xd7, 0x4b, 0x15, 0x15, 0x23, 0xe2, 0x1e, 0x4e, 0x00, 0x2c, 0x22, 0x97, 0xae,
+  0xb0, 0x74, 0xa6, 0x99, 0xd0, 0x5d, 0xb7, 0x1b, 0x10, 0x34, 0x13, 0xd2, 0x5f, 0x6e, 0x56, 0xad,
+  0x85, 0x4a, 0xdb, 0xf0, 0x78, 0xbd, 0xf4, 0x8c, 0xb7, 0x9a, 0x3e, 0x99, 0xef, 0xb9, 0x02, 0x41,
+  0x00, 0xde, 0x0d, 0xa7, 0x48, 0x75, 0x90, 0xad, 0x11, 0xa1, 0xac, 0xee, 0xcb, 0x41, 0x81, 0xc6,
+  0xc8, 0x7f, 0xe7, 0x25, 0x94, 0xa1, 0x2a, 0x21, 0xa8, 0x57, 0xfe, 0x84, 0xf2, 0x5e, 0xb4, 0x96,
+  0x35, 0xaf, 0xef, 0x2e, 0x7a, 0xf8, 0xda, 0x3f, 0xac, 0x8a, 0x3c, 0x1c, 0x9c, 0xbd, 0x44, 0xd6,
+  0x90, 0xb5, 0xce, 0x1b, 0x12, 0xf9, 0x3b, 0x8c, 0x69, 0xf6, 0xa9, 0x02, 0x93, 0x48, 0x35, 0x0a,
+  0x7f, 0x02, 0x40, 0x6b, 0x2a, 0x8c, 0x96, 0xd0, 0x7c, 0xd2, 0xfc, 0x9b, 0x52, 0x28, 0x46, 0x89,
+  0xac, 0x8d, 0xef, 0x2a, 0x80, 0xef, 0xea, 0x01, 0x6f, 0x95, 0x93, 0xee, 0x51, 0x57, 0xd5, 0x97,
+  0x4b, 0x65, 0x41, 0x86, 0x66, 0xc2, 0x26, 0x80, 0x1e, 0x3e, 0x55, 0x3e, 0x88, 0x63, 0xe2, 0x66,
+  0x03, 0x47, 0x31, 0xd8, 0xa2, 0x4e, 0x68, 0x45, 0x24, 0x0a, 0xca, 0x17, 0x61, 0xd5, 0x69, 0xca,
+  0x78, 0xab, 0x21, 0x02, 0x41, 0x00, 0x8f, 0xae, 0x7b, 0x4d, 0x00, 0xc7, 0x06, 0x92, 0xf0, 0x24,
+  0x9a, 0x83, 0x84, 0xbd, 0x62, 0x81, 0xbc, 0x2c, 0x27, 0x60, 0x2c, 0x0c, 0x33, 0xe5, 0x66, 0x1d,
+  0x28, 0xd9, 0x10, 0x1a, 0x7f, 0x4f, 0xea, 0x4f, 0x78, 0x6d, 0xb0, 0x14, 0xbf, 0xc9, 0xff, 0x17,
+  0xd6, 0x47, 0x4d, 0x4a, 0xa8, 0xf4, 0x39, 0x67, 0x3e, 0xb1, 0xec, 0x8f, 0xf1, 0x71, 0xbd, 0xb8,
+  0xa7, 0x50, 0x3d, 0xc7, 0xf7, 0xbb, 0x02, 0x40, 0x0d, 0x85, 0x32, 0x73, 0x9f, 0x0a, 0x33, 0x2f,
+  0x4b, 0xa2, 0xbd, 0xd1, 0xb1, 0x42, 0xf0, 0x72, 0xa8, 0x7a, 0xc8, 0x15, 0x37, 0x1b, 0xde, 0x76,
+  0x70, 0xce, 0xfd, 0x69, 0x20, 0x00, 0x4d, 0xc9, 0x4f, 0x35, 0x6f, 0xd1, 0x35, 0xa1, 0x04, 0x95,
+  0x30, 0xe8, 0x3b, 0xd5, 0x03, 0x5a, 0x50, 0x21, 0x6d, 0xa0, 0x84, 0x39, 0xe9, 0x2e, 0x1e, 0xfc,
+  0xe4, 0x82, 0x43, 0x20, 0x46, 0x7d, 0x0a, 0xb6
+};
+
+BOOST_FIXTURE_TEST_CASE(Encode, DataSigningKeyFixture)
+{
+  // manual data packet creation for now
+
+  Data d(Name("/local/ndn/prefix"));
+  d.setContentType(tlv::ContentType_Blob);
+  d.setFreshnessPeriod(10_s);
+  d.setContent(CONTENT1, sizeof(CONTENT1));
+
+  Block signatureInfo(tlv::SignatureInfo);
+  // SignatureType
+  signatureInfo.push_back(makeNonNegativeIntegerBlock(tlv::SignatureType, tlv::SignatureSha256WithRsa));
+  // KeyLocator
+  {
+    KeyLocator keyLocator;
+    keyLocator.setName("/test/key/locator");
+    signatureInfo.push_back(keyLocator.wireEncode());
+  }
+  signatureInfo.encode();
+
+  // SignatureValue
+  OBufferStream os;
+  tlv::writeVarNumber(os, tlv::SignatureValue);
+
+  OBufferStream sig;
+  {
+    namespace tr = security::transform;
+
+    tr::StepSource input;
+    input >> tr::signerFilter(DigestAlgorithm::SHA256, m_privKey) >> tr::streamSink(sig);
+
+    input.write(d.getName().    wireEncode().wire(), d.getName().    wireEncode().size());
+    input.write(d.getMetaInfo().wireEncode().wire(), d.getMetaInfo().wireEncode().size());
+    input.write(d.getContent().              wire(), d.getContent().              size());
+    input.write(signatureInfo.               wire(), signatureInfo.               size());
+    input.end();
+  }
+  auto buf = sig.buf();
+  tlv::writeVarNumber(os, buf->size());
+  os.write(buf->get<char>(), buf->size());
+
+  Block signatureValue(os.buf());
+  Signature signature(signatureInfo, signatureValue);
+  d.setSignature(signature);
+
+  Block dataBlock(d.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(DATA1, DATA1 + sizeof(DATA1),
+                                dataBlock.begin(), dataBlock.end());
+}
+
+BOOST_FIXTURE_TEST_CASE(Decode02, DataSigningKeyFixture)
+{
+  Block dataBlock(DATA1, sizeof(DATA1));
+  Data d(dataBlock);
+
+  BOOST_CHECK_EQUAL(d.getName().toUri(), "/local/ndn/prefix");
+  BOOST_CHECK_EQUAL(d.getContentType(), static_cast<uint32_t>(tlv::ContentType_Blob));
+  BOOST_CHECK_EQUAL(d.getFreshnessPeriod(), 10_s);
+  BOOST_CHECK_EQUAL(std::string(reinterpret_cast<const char*>(d.getContent().value()),
+                                d.getContent().value_size()), "SUCCESS!");
+  BOOST_CHECK_EQUAL(d.getSignature().getType(), tlv::SignatureSha256WithRsa);
+
+  Block block = d.getSignature().getInfo();
+  block.parse();
+  KeyLocator keyLocator(block.get(tlv::KeyLocator));
+  BOOST_CHECK_EQUAL(keyLocator.getName().toUri(), "/test/key/locator");
+
+  BOOST_CHECK(security::verifySignature(d, m_pubKey));
+}
+
+class Decode03Fixture
+{
+protected:
+  Decode03Fixture()
+  {
+    // initialize all elements to non-empty, to verify wireDecode clears them
+    d.setName("/A");
+    d.setContentType(tlv::ContentType_Key);
+    d.setContent("1504C0C1C2C3"_block);
+    d.setSignature(Signature("160A 1B0101 1C050703080142"_block,
+                   "1780 B48F1707A3BCA3CFC5F32DE51D9B46C32D7D262A21544EBDA88C3B415D637503"
+                        "FC9BEF20F88202A56AF9831E0D30205FD4154B08502BCDEE860267A5C3E03D8E"
+                        "A6CB74BE391C01E0A57B991B4404FC11B7D777F1B700A4B65F201118CF1840A8"
+                        "30A2A7C17DB4B7A8777E58515121AF9E2498627F8475414CDFD9801B8152AD5B"_block));
+  }
+
+protected:
+  Data d;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Decode03, Decode03Fixture)
+
+BOOST_AUTO_TEST_CASE(MinimalNoSigValue)
+{
+  d.wireDecode("0607 0700 16031B0100"_block);
+  BOOST_CHECK_EQUAL(d.getName(), "/"); // empty Name is allowed in Data
+  BOOST_CHECK_EQUAL(d.getMetaInfo(), MetaInfo());
+  BOOST_CHECK_EQUAL(d.getContent().value_size(), 0);
+  BOOST_CHECK_EQUAL(d.getSignature().getType(), tlv::DigestSha256);
+  BOOST_CHECK_EQUAL(d.getSignature().getValue().value_size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Minimal)
+{
+  d.wireDecode("062C 0703080144 16031B0100 "
+               "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block);
+  BOOST_CHECK_EQUAL(d.getName(), "/D");
+  BOOST_CHECK_EQUAL(d.getMetaInfo(), MetaInfo());
+  BOOST_CHECK_EQUAL(d.getContent().value_size(), 0);
+  BOOST_CHECK_EQUAL(d.getSignature().getType(), tlv::DigestSha256);
+  BOOST_CHECK_EQUAL(d.getSignature().getValue().value_size(), 32);
+
+  // encode without modification: retain original wire encoding
+  BOOST_CHECK_EQUAL(d.wireEncode().value_size(), 44);
+
+  // modify then re-encode as v0.2 format
+  d.setName("/E");
+  BOOST_CHECK_EQUAL(d.wireEncode(),
+    "0630 0703080145 1400 1500 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block);
+}
+
+BOOST_AUTO_TEST_CASE(Full)
+{
+  d.wireDecode("063A 0703080144 FC00 1400 FC00 1500 FC00 16031B0100 FC00 "
+               "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76 FC00"_block);
+  BOOST_CHECK_EQUAL(d.getName(), "/D");
+  BOOST_CHECK_EQUAL(d.getMetaInfo(), MetaInfo());
+  BOOST_CHECK_EQUAL(d.getContent().value_size(), 0);
+  BOOST_CHECK_EQUAL(d.getSignature().getType(), tlv::DigestSha256);
+  BOOST_CHECK_EQUAL(d.getSignature().getValue().value_size(), 32);
+
+  // encode without modification: retain original wire encoding
+  BOOST_CHECK_EQUAL(d.wireEncode().value_size(), 58);
+
+  // modify then re-encode as v0.2 format
+  d.setName("/E");
+  BOOST_CHECK_EQUAL(d.wireEncode(),
+    "0630 0703080145 1400 1500 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block);
+}
+
+BOOST_AUTO_TEST_CASE(CriticalElementOutOfOrder)
+{
+  BOOST_CHECK_THROW(d.wireDecode(
+    "0630 1400 0703080145 1500 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(d.wireDecode(
+    "0630 0703080145 1500 1400 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(d.wireDecode(
+    "0630 0703080145 1400 16031B0100 1500 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(d.wireDecode(
+    "0630 0703080145 1400 1500 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76 16031B0100"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(d.wireDecode(
+    "0652 0703080145 1400 1500 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block),
+    tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NameMissing)
+{
+  BOOST_CHECK_THROW(d.wireDecode("0605 16031B0100"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SigInfoMissing)
+{
+  BOOST_CHECK_THROW(d.wireDecode("0605 0703080144"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnrecognizedNonCriticalElementBeforeName)
+{
+  BOOST_CHECK_THROW(d.wireDecode(
+    "062F FC00 0703080144 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block),
+    tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnrecognizedCriticalElement)
+{
+  BOOST_CHECK_THROW(d.wireDecode(
+    "0632 0703080145 FB00 1400 1500 16031B0100 "
+    "1720612A79399E60304A9F701C1ECAC7956BF2F1B046E6C6F0D6C29B3FE3A29BAD76"_block),
+    tlv::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Decode03
+
+BOOST_FIXTURE_TEST_CASE(FullName, IdentityManagementFixture)
+{
+  Data d(Name("/local/ndn/prefix"));
+  d.setContentType(tlv::ContentType_Blob);
+  d.setFreshnessPeriod(10_s);
+  d.setContent(CONTENT1, sizeof(CONTENT1));
+  BOOST_CHECK_THROW(d.getFullName(), Data::Error); // FullName is unavailable without signing
+
+  m_keyChain.sign(d);
+  BOOST_CHECK_EQUAL(d.hasWire(), true);
+  Name fullName = d.getFullName(); // FullName is available after signing
+
+  BOOST_CHECK_EQUAL(d.getName().size() + 1, fullName.size());
+  BOOST_CHECK_EQUAL_COLLECTIONS(d.getName().begin(), d.getName().end(),
+                                fullName.begin(), fullName.end() - 1);
+  BOOST_CHECK_EQUAL(fullName.get(-1).value_size(), util::Sha256::DIGEST_SIZE);
+
+  // FullName should be cached, so value() pointer points to same memory location
+  BOOST_CHECK_EQUAL(fullName.get(-1).value(), d.getFullName().get(-1).value());
+
+  d.setFreshnessPeriod(100_s); // invalidates FullName
+  BOOST_CHECK_THROW(d.getFullName(), Data::Error);
+
+  Data d1(Block(DATA1, sizeof(DATA1)));
+  BOOST_CHECK_EQUAL(d1.getFullName(),
+    "/local/ndn/prefix/"
+    "sha256digest=28bad4b5275bd392dbb670c75cf0b66f13f7942b21e80f55c0e86b374753a548");
+}
+
+// ---- operators ----
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  Data a;
+  Data b;
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  a.setName("ndn:/A");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setName("ndn:/B");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setName("ndn:/A");
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  a.setFreshnessPeriod(10_s);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setFreshnessPeriod(10_s);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  static const uint8_t someData[] = "someData";
+  a.setContent(someData, sizeof(someData));
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setContent(someData, sizeof(someData));
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  a.setSignature(SignatureSha256WithRsa());
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setSignature(SignatureSha256WithRsa());
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  Data d(Block(DATA1, sizeof(DATA1)));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(d),
+                    "Name: /local/ndn/prefix\n"
+                    "MetaInfo: ContentType: 0, FreshnessPeriod: 10000 milliseconds\n"
+                    "Content: (size: 8)\n"
+                    "Signature: (type: SignatureSha256WithRsa, value_length: 128)\n");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestData
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/delegation-list.t.cpp b/tests/unit/delegation-list.t.cpp
new file mode 100644
index 0000000..e2aada8
--- /dev/null
+++ b/tests/unit/delegation-list.t.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "delegation-list.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestDelegationList)
+
+const uint8_t DEL1A[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x01, // Preference=1
+        0x07, 0x03, 0x08, 0x01, 0x41 // Name=/A
+};
+const uint8_t DEL1B[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x01, // Preference=1
+        0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+};
+const uint8_t DEL2A[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x02, // Preference=2
+        0x07, 0x03, 0x08, 0x01, 0x41 // Name=/A
+};
+const uint8_t DEL2B[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x02, // Preference=2
+        0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+};
+
+Block
+makeDelegationListBlock(uint32_t type, std::initializer_list<const uint8_t*> dels)
+{
+  Block block(type);
+  for (const uint8_t* del : dels) {
+    block.push_back(Block(del, 2 + del[1]));
+  }
+  block.encode();
+  return block;
+}
+
+BOOST_AUTO_TEST_SUITE(Decode)
+
+BOOST_AUTO_TEST_CASE(DecodeUnsorted)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL2B, DEL1A}), false);
+  BOOST_CHECK_EQUAL(dl.size(), 3);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/B");
+  BOOST_CHECK_EQUAL(dl.at(2).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(2).name, "/A");
+}
+
+BOOST_AUTO_TEST_CASE(DecodeSorted)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::Content, {DEL2A, DEL2B, DEL1A}));
+  BOOST_CHECK_EQUAL(dl.size(), 3);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(2).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(2).name, "/B");
+}
+
+BOOST_AUTO_TEST_CASE(DecodeEmpty)
+{
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeBadType)
+{
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::Selectors, {DEL1A, DEL2B});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeNotDelegation)
+{
+  const uint8_t BAD_DEL[] = {
+    0x09, 0x00 // Selectors
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeMissingPreference)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x05, // Delegation
+          0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeMissingName)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x03, // Delegation
+          0x1e, 0x01, 0x02, // Preference=2
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeUnknownField)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x0a, // Delegation
+          0x1e, 0x01, 0x02, // Preference=2
+          0x09, 0x00, // Selectors
+          0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeWrongOrder)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x08, // Delegation
+          0x07, 0x03, 0x08, 0x01, 0x42, // Name=/B
+          0x1e, 0x01, 0x02 // Preference=2
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Decode
+
+BOOST_AUTO_TEST_SUITE(InsertEncode)
+
+BOOST_AUTO_TEST_CASE(InsertSimple)
+{
+  DelegationList dl;
+  BOOST_CHECK_EQUAL(dl.empty(), true);
+  dl.insert(2, "/A");
+  BOOST_CHECK_EQUAL(dl.empty(), false);
+  dl.insert(1, "/B");
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK_EQUAL(encoder.block(), makeDelegationListBlock(tlv::ForwardingHint, {DEL1B, DEL2A}));
+}
+
+BOOST_AUTO_TEST_CASE(InsertReplace)
+{
+  DelegationList dl({{2, "/A"}});
+  dl.insert(Delegation{1, "/A"}, DelegationList::INS_REPLACE);
+  BOOST_CHECK_EQUAL(dl.size(), 1);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl[0].name, "/A");
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK_EQUAL(encoder.block(), makeDelegationListBlock(tlv::ForwardingHint, {DEL1A}));
+}
+
+BOOST_AUTO_TEST_CASE(InsertAppend)
+{
+  DelegationList dl({{2, "/A"}});
+  dl.insert(Delegation{1, "/A"}, DelegationList::INS_APPEND);
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK_EQUAL(encoder.block(), makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, DEL2A}));
+}
+
+BOOST_AUTO_TEST_CASE(InsertSkip)
+{
+  DelegationList dl({{2, "/A"}});
+  dl.insert(Delegation{1, "/A"}, DelegationList::INS_SKIP);
+  BOOST_CHECK_EQUAL(dl.size(), 1);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 2);
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK_EQUAL(encoder.block(), makeDelegationListBlock(tlv::ForwardingHint, {DEL2A}));
+}
+
+BOOST_AUTO_TEST_CASE(Unsorted)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A}), false);
+  dl.insert(1, "/B");
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/B");
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder, tlv::Content);
+  BOOST_CHECK_EQUAL(encoder.block(), makeDelegationListBlock(tlv::Content, {DEL2A, DEL1B}));
+}
+
+BOOST_AUTO_TEST_CASE(EncodeBadType)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A}));
+  EncodingBuffer encoder;
+  BOOST_CHECK_THROW(dl.wireEncode(encoder, tlv::Selectors), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeEmpty)
+{
+  DelegationList dl;
+  EncodingBuffer encoder;
+  BOOST_CHECK_THROW(dl.wireEncode(encoder), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // InsertEncode
+
+BOOST_AUTO_TEST_SUITE(Erase)
+
+BOOST_AUTO_TEST_CASE(EraseNoop)
+{
+  DelegationList dl;
+  dl.insert(1, "/A");
+  BOOST_CHECK_EQUAL(dl.erase(2, "/A"), 0);
+  BOOST_CHECK_EQUAL(dl.erase(Delegation{1, "/B"}), 0);
+  BOOST_CHECK_EQUAL(dl.size(), 1);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+}
+
+BOOST_AUTO_TEST_CASE(EraseOne)
+{
+  DelegationList dl;
+  dl.insert(1, "/A");
+  BOOST_CHECK_EQUAL(dl.erase(1, "/A"), 1);
+  BOOST_CHECK_EQUAL(dl.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(EraseByName)
+{
+  DelegationList dl;
+  dl.insert(1, "/A");
+  dl.insert(2, "/A", DelegationList::INS_APPEND);
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+  BOOST_CHECK_EQUAL(dl.erase("/A"), 2);
+  BOOST_CHECK_EQUAL(dl.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Erase
+
+BOOST_AUTO_TEST_SUITE(Sort)
+
+BOOST_AUTO_TEST_CASE(Noop)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL1A}));
+  BOOST_CHECK_EQUAL(dl.isSorted(), true);
+  dl.sort();
+  BOOST_CHECK_EQUAL(dl.isSorted(), true);
+}
+
+BOOST_AUTO_TEST_CASE(Sort)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL2B, DEL1A}), false);
+  BOOST_CHECK_EQUAL(dl.isSorted(), false);
+  dl.sort();
+  BOOST_CHECK_EQUAL(dl.isSorted(), true);
+  BOOST_CHECK_EQUAL(dl.size(), 3);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(2).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(2).name, "/B");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Sort
+
+BOOST_AUTO_TEST_SUITE(Compare)
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+  DelegationList dl1, dl2;
+  BOOST_CHECK_EQUAL(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(SortedEqual)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B})),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL1B, DEL2A}));
+  BOOST_CHECK_EQUAL(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(SortedUnequal)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B})),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL1A, DEL2B}));
+  BOOST_CHECK_NE(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(UnsortedSameOrder)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B}), false),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL2A, DEL1B}), false);
+  BOOST_CHECK_EQUAL(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(UnsortedDifferentOrder)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B}), false),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL1B, DEL2A}), false);
+  BOOST_CHECK_NE(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Compare
+
+BOOST_AUTO_TEST_SUITE(Print)
+
+BOOST_AUTO_TEST_CASE(PrintEmpty)
+{
+  DelegationList dl;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(dl), "[]");
+}
+
+BOOST_AUTO_TEST_CASE(PrintNormal)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B}));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(dl), "[/B(1),/A(2)]");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Print
+
+BOOST_AUTO_TEST_SUITE_END() // TestDelegationList
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/delegation.t.cpp b/tests/unit/delegation.t.cpp
new file mode 100644
index 0000000..6cf03dd
--- /dev/null
+++ b/tests/unit/delegation.t.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "delegation.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestDelegation)
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  BOOST_CHECK_EQUAL((Delegation{1, "/A"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_LE((Delegation{1, "/A"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GE((Delegation{1, "/A"}), (Delegation{1, "/A"}));
+
+  BOOST_CHECK_NE((Delegation{1, "/A"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_NE((Delegation{1, "/A"}), (Delegation{1, "/B"}));
+
+  BOOST_CHECK_LT((Delegation{1, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_LE((Delegation{1, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_LT((Delegation{1, "/B"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_LE((Delegation{1, "/B"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_LT((Delegation{1, "/A"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_LE((Delegation{1, "/A"}), (Delegation{2, "/A"}));
+
+  BOOST_CHECK_GT((Delegation{1, "/B"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GE((Delegation{1, "/B"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GT((Delegation{2, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_GE((Delegation{2, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_GT((Delegation{2, "/A"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GE((Delegation{2, "/A"}), (Delegation{1, "/A"}));
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(Delegation{1, "/B"}), "/B(1)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDelegation
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/encoding/block-helpers.t.cpp b/tests/unit/encoding/block-helpers.t.cpp
new file mode 100644
index 0000000..5b9711d
--- /dev/null
+++ b/tests/unit/encoding/block-helpers.t.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/block-helpers.hpp"
+#include "name.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace encoding {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestBlockHelpers)
+
+enum E8 : uint8_t
+{
+  E8_NONE
+};
+
+enum class EC8 : uint8_t
+{
+  NONE
+};
+
+enum E16 : uint16_t
+{
+  E16_NONE
+};
+
+enum class EC16 : uint16_t
+{
+  NONE
+};
+
+BOOST_AUTO_TEST_CASE(NonNegativeInteger)
+{
+  Block b = makeNonNegativeIntegerBlock(100, 1000);
+  BOOST_CHECK_EQUAL(b.type(), 100);
+  BOOST_CHECK_GT(b.value_size(), 0);
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(b), 1000);
+
+  BOOST_CHECK_THROW(readNonNegativeInteger(Block()), tlv::Error);
+
+  BOOST_CHECK_THROW(readNonNegativeIntegerAs<uint8_t>(b), tlv::Error);
+  BOOST_CHECK_EQUAL(readNonNegativeIntegerAs<uint16_t>(b), 1000);
+  BOOST_CHECK_EQUAL(readNonNegativeIntegerAs<size_t>(b), 1000);
+  BOOST_CHECK_THROW(readNonNegativeIntegerAs<E8>(b), tlv::Error);
+  BOOST_CHECK_EQUAL(static_cast<uint16_t>(readNonNegativeIntegerAs<E16>(b)), 1000);
+  BOOST_CHECK_THROW(readNonNegativeIntegerAs<EC8>(b), tlv::Error);
+  BOOST_CHECK_EQUAL(static_cast<uint16_t>(readNonNegativeIntegerAs<EC16>(b)), 1000);
+}
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+  Block b = makeEmptyBlock(200);
+  BOOST_CHECK_EQUAL(b.type(), 200);
+  BOOST_CHECK_EQUAL(b.value_size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(String)
+{
+  Block b = makeStringBlock(100, "Hello, world!");
+  BOOST_CHECK_EQUAL(b.type(), 100);
+  BOOST_CHECK_GT(b.value_size(), 0);
+  BOOST_CHECK_EQUAL(readString(b), "Hello, world!");
+}
+
+BOOST_AUTO_TEST_CASE(Double)
+{
+  const double f = 0.25;
+  Block b = makeDoubleBlock(100, f);
+  BOOST_CHECK_EQUAL(b, "64083FD0000000000000"_block);
+
+  EncodingEstimator estimator;
+  size_t totalLength = prependDoubleBlock(estimator, 100, f);
+  EncodingBuffer encoder(totalLength, 0);
+  prependDoubleBlock(encoder, 100, f);
+  BOOST_CHECK_EQUAL(encoder.block(), b);
+
+  BOOST_CHECK_EQUAL(readDouble(b), f);
+  BOOST_CHECK_THROW(readDouble("4200"_block), tlv::Error);
+  BOOST_CHECK_THROW(readDouble("64043E800000"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(Data)
+{
+  std::string buf1{1, 1, 1, 1};
+  const uint8_t buf2[]{1, 1, 1, 1};
+  std::list<uint8_t> buf3{1, 1, 1, 1};
+
+  Block b1 = makeBinaryBlock(100, buf1.data(), buf1.size());
+  Block b2 = makeBinaryBlock(100, buf2, sizeof(buf2));
+  Block b3 = makeBinaryBlock(100, buf1.begin(), buf1.end()); // fast encoding (random access iterator)
+  Block b4 = makeBinaryBlock(100, buf3.begin(), buf3.end()); // slow encoding (general iterator)
+
+  BOOST_CHECK_EQUAL(b1, b2);
+  BOOST_CHECK_EQUAL(b1, b3);
+  BOOST_CHECK_EQUAL(b1.type(), 100);
+  BOOST_CHECK_EQUAL(b1.value_size(), buf1.size());
+  BOOST_CHECK_EQUAL_COLLECTIONS(b1.value_begin(), b1.value_end(), buf2, buf2 + sizeof(buf2));
+}
+
+BOOST_AUTO_TEST_CASE(Nested)
+{
+  Name name("ndn:/Hello/World!");
+  Block b1 = makeNestedBlock(100, name);
+
+  BOOST_CHECK_EQUAL(b1.type(), 100);
+  b1.parse();
+  BOOST_CHECK_EQUAL(b1.elements().size(), 1);
+  BOOST_CHECK_EQUAL(b1.elements().begin()->type(), name.wireEncode().type());
+  BOOST_CHECK_EQUAL(*b1.elements().begin(), name.wireEncode());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBlockHelpers
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace encoding
+} // namespace ndn
diff --git a/tests/unit/encoding/block.t.cpp b/tests/unit/encoding/block.t.cpp
new file mode 100644
index 0000000..a3459e7
--- /dev/null
+++ b/tests/unit/encoding/block.t.cpp
@@ -0,0 +1,571 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/block.hpp"
+#include "encoding/block-helpers.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+#include <cstring>
+#include <sstream>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestBlock)
+
+BOOST_AUTO_TEST_SUITE(Construction)
+
+static const uint8_t TEST_BUFFER[] = {
+  0x00, 0x01, 0xfa, // ok
+  0x01, 0x01, 0xfb, // ok
+  0x03, 0x02, 0xff // bad: TLV-LENGTH is 2 but there's only 1-octet TLV-VALUE
+};
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+  Block b;
+  BOOST_CHECK_EQUAL(b.empty(), true);
+}
+
+BOOST_AUTO_TEST_CASE(FromEncodingBuffer)
+{
+  const uint8_t VALUE[4] = {0x11, 0x12, 0x13, 0x14};
+
+  EncodingBuffer encoder;
+  size_t length = encoder.prependByteArray(VALUE, sizeof(VALUE));
+  encoder.prependVarNumber(length);
+  encoder.prependVarNumber(0xe0);
+
+  Block b = encoder.block();
+  BOOST_CHECK_EQUAL(b.type(), 0xe0);
+  BOOST_CHECK_EQUAL(b.value_size(), sizeof(VALUE));
+  BOOST_CHECK_EQUAL_COLLECTIONS(b.value_begin(), b.value_end(),
+                                VALUE, VALUE + sizeof(VALUE));
+
+  b = Block(encoder);
+  BOOST_CHECK_EQUAL(b.type(), 0xe0);
+  BOOST_CHECK_EQUAL(b.value_size(), sizeof(VALUE));
+  BOOST_CHECK_EQUAL_COLLECTIONS(b.value_begin(), b.value_end(),
+                                VALUE, VALUE + sizeof(VALUE));
+}
+
+BOOST_AUTO_TEST_CASE(FromEmptyEncodingBuffer)
+{
+  EncodingBuffer encoder;
+  Block b;
+  BOOST_CHECK_THROW(b = Block(encoder), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(FromBlock)
+{
+  static uint8_t buffer[] = {0x80, 0x06, 0x81, 0x01, 0x01, 0x82, 0x01, 0x01};
+  Block block(buffer, sizeof(buffer));
+
+  Block derivedBlock(block, block.begin(), block.end());
+  BOOST_CHECK_EQUAL(derivedBlock.wire(), block.wire()); // pointers should match
+  BOOST_CHECK(derivedBlock == block); // blocks should match
+
+  derivedBlock = Block(block, block.begin() + 2, block.begin() + 5);
+  BOOST_CHECK(derivedBlock.begin() == block.begin() + 2);
+  BOOST_CHECK(derivedBlock == Block(buffer + 2, 3));
+
+  Buffer otherBuffer(buffer, sizeof(buffer));
+  BOOST_CHECK_THROW(Block(block, otherBuffer.begin(), block.end()), std::invalid_argument);
+  BOOST_CHECK_THROW(Block(block, block.begin(), otherBuffer.end()), std::invalid_argument);
+  BOOST_CHECK_THROW(Block(block, otherBuffer.begin(), otherBuffer.end()), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(FromBlockCopyOnWriteModifyOriginal)
+{
+  const uint8_t BUFFER[] = {
+    0x05, 0x0b, 0x07, 0x03, 0x01, 0x02, 0x03, 0x0a, 0x04, 0x04, 0x05, 0x06, 0x07,
+  };
+
+  Block b1(BUFFER, sizeof(BUFFER));
+
+  Block b2(b1, b1.begin(), b1.end());
+  auto buf2 = b2.getBuffer();
+
+  b1.parse();
+  b1.remove(tlv::Name);
+  b1.encode();
+
+  b2.parse();
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(b2.begin(), b2.end(), BUFFER, BUFFER + sizeof(BUFFER));
+  BOOST_CHECK_EQUAL(buf2, b2.getBuffer());
+}
+
+BOOST_AUTO_TEST_CASE(FromBlockCopyOnWriteModifyCopy)
+{
+  const uint8_t BUFFER[] = {
+    0x05, 0x0b, 0x07, 0x03, 0x01, 0x02, 0x03, 0x0a, 0x04, 0x04, 0x05, 0x06, 0x07,
+  };
+
+  Block b1(BUFFER, sizeof(BUFFER));
+  auto buf1 = b1.getBuffer();
+
+  Block b2(b1, b1.begin(), b1.end());
+
+  b2.parse();
+  b2.remove(tlv::Name);
+  b2.encode();
+
+  b1.parse();
+  BOOST_CHECK_EQUAL_COLLECTIONS(b1.begin(), b1.end(), BUFFER, BUFFER + sizeof(BUFFER));
+  BOOST_CHECK_EQUAL(buf1, b1.getBuffer());
+}
+
+BOOST_AUTO_TEST_CASE(FromType)
+{
+  Block b1(4);
+  BOOST_CHECK_EQUAL(b1.empty(), false);
+  BOOST_CHECK_EQUAL(b1.type(), 4);
+  BOOST_CHECK_EQUAL(b1.size(), 2); // 1-octet TLV-TYPE and 1-octet TLV-LENGTH
+  BOOST_CHECK_EQUAL(b1.value_size(), 0);
+
+  Block b2(258);
+  BOOST_CHECK_EQUAL(b2.type(), 258);
+  BOOST_CHECK_EQUAL(b2.size(), 4); // 3-octet TLV-TYPE and 1-octet TLV-LENGTH
+  BOOST_CHECK_EQUAL(b2.value_size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FromStream)
+{
+  std::stringstream stream;
+  stream.write(reinterpret_cast<const char*>(TEST_BUFFER), sizeof(TEST_BUFFER));
+  stream.seekg(0);
+
+  Block b = Block::fromStream(stream);
+  BOOST_CHECK_EQUAL(b.type(), 0);
+  BOOST_CHECK_EQUAL(b.size(), 3);
+  BOOST_CHECK_EQUAL(b.value_size(), 1);
+  BOOST_CHECK_EQUAL(*b.wire(),  0x00);
+  BOOST_CHECK_EQUAL(*b.value(), 0xfa);
+
+  b = Block::fromStream(stream);
+  BOOST_CHECK_EQUAL(b.type(), 1);
+  BOOST_CHECK_EQUAL(b.size(), 3);
+  BOOST_CHECK_EQUAL(b.value_size(), 1);
+  BOOST_CHECK_EQUAL(*b.wire(),  0x01);
+  BOOST_CHECK_EQUAL(*b.value(), 0xfb);
+
+  BOOST_CHECK_THROW(Block::fromStream(stream), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(FromStreamWhitespace) // Bug 2728
+{
+  const uint8_t PACKET[] = {
+    0x06, 0x20, // Data
+          0x07, 0x11, // Name
+                0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, // GenericNameComponent 'hello'
+                0x08, 0x01, 0x31, // GenericNameComponent '1'
+                0x08, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64, // GenericNameComponent 'world'
+          0x14, 0x00, // MetaInfo empty
+          0x15, 0x00, // Content empty
+          0x16, 0x05, // SignatureInfo
+                0x1b, 0x01, 0x01, // SignatureType RSA
+                0x1c, 0x00, // KeyLocator empty
+          0x17, 0x00 // SignatureValue empty
+  };
+  // TLV-LENGTH of <Data> is 0x20 which happens to be ASCII whitespace
+
+  std::stringstream stream;
+  stream.write(reinterpret_cast<const char*>(PACKET), sizeof(PACKET));
+  stream.seekg(0);
+
+  Block b = Block::fromStream(stream);
+  BOOST_CHECK_EQUAL(b.type(), 6);
+  BOOST_CHECK_EQUAL(b.value_size(), 32);
+  b.parse();
+}
+
+BOOST_AUTO_TEST_CASE(FromStreamZeroLength)
+{
+  const uint8_t BUFFER[] = {0x70, 0x00,
+                            0x71, 0x03, 0x86, 0x11, 0x24,
+                            0x72, 0x00};
+
+  std::stringstream stream;
+  stream.write(reinterpret_cast<const char*>(BUFFER), sizeof(BUFFER));
+  stream.seekg(0);
+
+  Block b1 = Block::fromStream(stream);
+  BOOST_CHECK_EQUAL(b1.type(), 0x70);
+  BOOST_CHECK_EQUAL(b1.value_size(), 0);
+
+  Block b2 = Block::fromStream(stream);
+  BOOST_CHECK_EQUAL(b2.type(), 0x71);
+  BOOST_CHECK_EQUAL(b2.value_size(), 3);
+  const uint8_t EXPECTED_VALUE2[] = {0x86, 0x11, 0x24};
+  BOOST_CHECK_EQUAL_COLLECTIONS(b2.value_begin(), b2.value_end(),
+                                EXPECTED_VALUE2, EXPECTED_VALUE2 + sizeof(EXPECTED_VALUE2));
+
+  Block b3 = Block::fromStream(stream);
+  BOOST_CHECK_EQUAL(b3.type(), 0x72);
+  BOOST_CHECK_EQUAL(b3.value_size(), 0);
+
+  BOOST_CHECK_THROW(Block::fromStream(stream), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(FromStreamPacketTooLarge)
+{
+  const uint8_t BUFFER[] = {0x07, 0xfe, 0x00, 0x01, 0x00, 0x00};
+
+  std::stringstream stream;
+  stream.write(reinterpret_cast<const char*>(BUFFER), sizeof(BUFFER));
+  for (int i = 0; i < 0x10000; ++i) {
+    stream.put('\0');
+  }
+  stream.seekg(0);
+
+  BOOST_CHECK_THROW(Block::fromStream(stream), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(FromWireBuffer)
+{
+  ConstBufferPtr buffer = make_shared<Buffer>(TEST_BUFFER, sizeof(TEST_BUFFER));
+
+  size_t offset = 0;
+  bool isOk = false;
+  Block b;
+  std::tie(isOk, b) = Block::fromBuffer(buffer, offset);
+  BOOST_CHECK(isOk);
+  BOOST_CHECK_EQUAL(b.type(), 0);
+  BOOST_CHECK_EQUAL(b.size(), 3);
+  BOOST_CHECK_EQUAL(b.value_size(), 1);
+  BOOST_CHECK_EQUAL(*b.wire(),  0x00);
+  BOOST_CHECK_EQUAL(*b.value(), 0xfa);
+  offset += b.size();
+
+  std::tie(isOk, b) = Block::fromBuffer(buffer, offset);
+  BOOST_CHECK(isOk);
+  BOOST_CHECK_EQUAL(b.type(), 1);
+  BOOST_CHECK_EQUAL(b.size(), 3);
+  BOOST_CHECK_EQUAL(b.value_size(), 1);
+  BOOST_CHECK_EQUAL(*b.wire(),  0x01);
+  BOOST_CHECK_EQUAL(*b.value(), 0xfb);
+  offset += b.size();
+
+  std::tie(isOk, b) = Block::fromBuffer(buffer, offset);
+  BOOST_CHECK(!isOk);
+}
+
+BOOST_AUTO_TEST_CASE(FromRawBuffer)
+{
+  size_t offset = 0;
+  bool isOk = false;
+  Block b;
+  std::tie(isOk, b) = Block::fromBuffer(TEST_BUFFER + offset, sizeof(TEST_BUFFER) - offset);
+  BOOST_CHECK(isOk);
+  BOOST_CHECK_EQUAL(b.type(), 0);
+  BOOST_CHECK_EQUAL(b.size(), 3);
+  BOOST_CHECK_EQUAL(b.value_size(), 1);
+  BOOST_CHECK_EQUAL(*b.wire(),  0x00);
+  BOOST_CHECK_EQUAL(*b.value(), 0xfa);
+  offset += b.size();
+
+  std::tie(isOk, b) = Block::fromBuffer(TEST_BUFFER + offset, sizeof(TEST_BUFFER) - offset);
+  BOOST_CHECK(isOk);
+  BOOST_CHECK_EQUAL(b.type(), 1);
+  BOOST_CHECK_EQUAL(b.size(), 3);
+  BOOST_CHECK_EQUAL(b.value_size(), 1);
+  BOOST_CHECK_EQUAL(*b.wire(),  0x01);
+  BOOST_CHECK_EQUAL(*b.value(), 0xfb);
+  offset += b.size();
+
+  std::tie(isOk, b) = Block::fromBuffer(TEST_BUFFER + offset, sizeof(TEST_BUFFER) - offset);
+  BOOST_CHECK(!isOk);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Construction
+
+BOOST_AUTO_TEST_SUITE(SubElements)
+
+BOOST_AUTO_TEST_CASE(Parse)
+{
+  const uint8_t PACKET[] = {
+    0x06, 0x20, // Data
+          0x07, 0x11, // Name
+                0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, // GenericNameComponent 'hello'
+                0x08, 0x01, 0x31, // GenericNameComponent '1'
+                0x08, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64, // GenericNameComponent 'world'
+          0x14, 0x00, // MetaInfo empty
+          0x15, 0x00, // Content empty
+          0x16, 0x05, // SignatureInfo
+                0x1b, 0x01, 0x01, // SignatureType RSA
+                0x1c, 0x00, // KeyLocator empty
+          0x17, 0x00 // SignatureValue empty
+  };
+
+  Block data(PACKET, sizeof(PACKET));
+  data.parse();
+
+  BOOST_CHECK_EQUAL(data.elements_size(), 5);
+  BOOST_CHECK_EQUAL(data.elements().at(0).type(), 0x07);
+  BOOST_CHECK_EQUAL(data.elements().at(0).elements().size(), 0); // parse is not recursive
+
+  BOOST_CHECK(data.get(0x15) == data.elements().at(2));
+  BOOST_CHECK_THROW(data.get(0x01), Block::Error);
+
+  BOOST_CHECK(data.find(0x15) == data.elements_begin() + 2);
+  BOOST_CHECK(data.find(0x01) == data.elements_end());
+}
+
+BOOST_AUTO_TEST_CASE(InsertBeginning)
+{
+  Block masterBlock(tlv::Name);
+  Block firstBlock = makeStringBlock(tlv::GenericNameComponent, "firstName");
+  Block secondBlock = makeStringBlock(tlv::GenericNameComponent, "secondName");
+  Block thirdBlock = makeStringBlock(tlv::GenericNameComponent, "thirdName");
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 0);
+  masterBlock.push_back(secondBlock);
+  masterBlock.push_back(thirdBlock);
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 2);
+  Block::element_const_iterator it = masterBlock.find(tlv::GenericNameComponent);
+  BOOST_CHECK_EQUAL(*it == secondBlock, true);
+
+  it = masterBlock.insert(it, firstBlock);
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 3);
+  BOOST_CHECK_EQUAL(*(it + 1) == secondBlock, true);
+  BOOST_CHECK_EQUAL(*(masterBlock.elements_begin()) == firstBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(InsertEnd)
+{
+  Block masterBlock(tlv::Name);
+  Block firstBlock = makeStringBlock(tlv::GenericNameComponent, "firstName");
+  Block secondBlock = makeStringBlock(tlv::GenericNameComponent, "secondName");
+  Block thirdBlock = makeStringBlock(tlv::GenericNameComponent, "thirdName");
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 0);
+  masterBlock.push_back(firstBlock);
+  masterBlock.push_back(secondBlock);
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 2);
+  Block::element_const_iterator it = masterBlock.elements_end();
+  BOOST_CHECK_EQUAL(*(it - 1) == secondBlock, true);
+
+  it = masterBlock.insert(it, thirdBlock);
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 3);
+  BOOST_CHECK_EQUAL(*(it - 1) == secondBlock, true);
+  BOOST_CHECK_EQUAL(*(masterBlock.elements_end() - 1) == thirdBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(InsertMiddle)
+{
+  Block masterBlock(tlv::Name);
+  Block firstBlock = makeStringBlock(tlv::GenericNameComponent, "firstName");
+  Block secondBlock = makeStringBlock(tlv::GenericNameComponent, "secondName");
+  Block thirdBlock = makeStringBlock(tlv::GenericNameComponent, "thirdName");
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 0);
+  masterBlock.push_back(firstBlock);
+  masterBlock.push_back(thirdBlock);
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 2);
+  Block::element_const_iterator it = masterBlock.find(tlv::GenericNameComponent);
+  BOOST_CHECK_EQUAL(*it == firstBlock, true);
+
+  it = masterBlock.insert(it + 1, secondBlock);
+
+  BOOST_CHECK_EQUAL(*it == secondBlock, true);
+  BOOST_CHECK_EQUAL(*(it + 1) == thirdBlock, true);
+  BOOST_CHECK_EQUAL(*(it - 1) == firstBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(EraseSingleElement)
+{
+  Block masterBlock(tlv::Name);
+  Block firstBlock = makeStringBlock(tlv::GenericNameComponent, "firstName");
+  Block secondBlock = makeStringBlock(tlv::GenericNameComponent, "secondName");
+  Block thirdBlock = makeStringBlock(tlv::GenericNameComponent, "thirdName");
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 0);
+  masterBlock.push_back(firstBlock);
+  masterBlock.push_back(secondBlock);
+  masterBlock.push_back(thirdBlock);
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 3);
+  Block::element_const_iterator it = masterBlock.find(tlv::GenericNameComponent);
+  it++;
+  BOOST_CHECK_EQUAL(*it == secondBlock, true);
+
+  it = masterBlock.erase(it);
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 2);
+  BOOST_CHECK_EQUAL(*(it) == thirdBlock, true);
+  BOOST_CHECK_EQUAL(*(it - 1) == firstBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(EraseRange)
+{
+  Block masterBlock(tlv::Name);
+  Block firstBlock = makeStringBlock(tlv::GenericNameComponent, "firstName");
+  Block secondBlock = makeStringBlock(tlv::GenericNameComponent, "secondName");
+  Block thirdBlock = makeStringBlock(tlv::GenericNameComponent, "thirdName");
+  Block fourthBlock = makeStringBlock(tlv::GenericNameComponent, "fourthName");
+  Block fifthBlock = makeStringBlock(tlv::GenericNameComponent, "fifthName");
+  Block sixthBlock = makeStringBlock(tlv::GenericNameComponent, "sixthName");
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 0);
+  masterBlock.push_back(firstBlock);
+  masterBlock.push_back(secondBlock);
+  masterBlock.push_back(thirdBlock);
+  masterBlock.push_back(fourthBlock);
+  masterBlock.push_back(fifthBlock);
+  masterBlock.push_back(sixthBlock);
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 6);
+  Block::element_const_iterator itStart = masterBlock.find(tlv::GenericNameComponent);
+  itStart++;
+  Block::element_const_iterator itEnd = itStart + 3;
+  BOOST_CHECK_EQUAL(*itStart == secondBlock, true);
+  BOOST_CHECK_EQUAL(*itEnd == fifthBlock, true);
+
+  Block::element_const_iterator newIt = masterBlock.erase(itStart, itEnd);
+
+  BOOST_CHECK_EQUAL(masterBlock.elements_size(), 3);
+  BOOST_CHECK_EQUAL(*(newIt) == fifthBlock, true);
+  BOOST_CHECK_EQUAL(*(newIt - 1) == firstBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(Remove)
+{
+  Block block(tlv::Data);
+  block.push_back(makeNonNegativeIntegerBlock(tlv::ContentType, 0));
+  block.push_back(makeNonNegativeIntegerBlock(tlv::FreshnessPeriod, 123));
+  block.push_back(makeStringBlock(tlv::Name, "ndn:/test-prefix"));
+  block.push_back(makeNonNegativeIntegerBlock(tlv::ContentType, 2));
+  block.push_back(makeNonNegativeIntegerBlock(tlv::ContentType, 1));
+
+  BOOST_CHECK_EQUAL(5, block.elements_size());
+  BOOST_REQUIRE_NO_THROW(block.remove(tlv::ContentType));
+  BOOST_CHECK_EQUAL(2, block.elements_size());
+
+  Block::element_container elements = block.elements();
+
+  BOOST_CHECK_EQUAL(tlv::FreshnessPeriod, elements[0].type());
+  BOOST_CHECK_EQUAL(123, readNonNegativeInteger(elements[0]));
+  BOOST_CHECK_EQUAL(tlv::Name, elements[1].type());
+  BOOST_CHECK(readString(elements[1]).compare("ndn:/test-prefix") == 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SubElements
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  const uint8_t one[] = {0x08, 0x00};
+  Block a(one, sizeof(one));
+  Block b(one, sizeof(one));
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  const uint8_t two[] = {0x06, 0x00};
+  Block c(two, sizeof(two));
+  Block d(one, sizeof(one));
+  BOOST_CHECK_EQUAL(c == d, false);
+  BOOST_CHECK_EQUAL(c != d, true);
+
+  const uint8_t three[] = {0x06, 0x01, 0xcc};
+  Block e(two, sizeof(two));
+  Block f(three, sizeof(three));
+  BOOST_CHECK_EQUAL(e == f, false);
+  BOOST_CHECK_EQUAL(e != f, true);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  // default constructed
+  Block b;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "[invalid]");
+
+  // zero length
+  b = "0700"_block;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "7[empty]");
+
+  // unparsed
+  b = "0E10FF7E4E6B3B21C902660F16ED589FCCCC"_block;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b),
+                    "14[16]=FF7E4E6B3B21C902660F16ED589FCCCC");
+  // set and restore format flags
+  {
+    std::ostringstream oss;
+    oss << std::showbase << std::hex << 0xd23c4 << b << 0x4981e;
+    BOOST_CHECK_EQUAL(oss.str(), "0xd23c414[16]=FF7E4E6B3B21C902660F16ED589FCCCC0x4981e");
+  }
+
+  // parsed
+  b = "FD010808 0502CADD 59024E42"_block;
+  b.parse();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b),
+                    "264[8]={5[2]=CADD,89[2]=4E42}");
+
+  // parsed then modified: print modified sub-elements
+  b = "FD010808 0502CADD 59024E42"_block;
+  b.parse();
+  b.erase(b.elements_begin());
+  b.push_back("10022386"_block);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b),
+                    "264[8]={89[2]=4E42,16[2]=2386}");
+}
+
+BOOST_AUTO_TEST_SUITE(BlockLiteral)
+
+BOOST_AUTO_TEST_CASE(Simple)
+{
+  Block b0 = "0000"_block;
+  BOOST_CHECK_EQUAL(b0.type(), 0x00);
+  BOOST_CHECK_EQUAL(b0.value_size(), 0);
+
+  Block b1 = "0101A0"_block;
+  BOOST_CHECK_EQUAL(b1.type(), 0x01);
+  BOOST_REQUIRE_EQUAL(b1.value_size(), 1);
+  BOOST_CHECK_EQUAL(b1.value()[0], 0xA0);
+}
+
+BOOST_AUTO_TEST_CASE(Comment)
+{
+  Block b0 = "a2b0c0d2eBf0G.B 1+"_block;
+  BOOST_CHECK_EQUAL(b0.type(), 0x20);
+  BOOST_REQUIRE_EQUAL(b0.value_size(), 2);
+  BOOST_CHECK_EQUAL(b0.value()[0], 0xB0);
+  BOOST_CHECK_EQUAL(b0.value()[1], 0xB1);
+}
+
+BOOST_AUTO_TEST_CASE(BadInput)
+{
+  BOOST_CHECK_THROW(""_block, std::invalid_argument);
+  BOOST_CHECK_THROW("1"_block, std::invalid_argument);
+  BOOST_CHECK_THROW("333"_block, std::invalid_argument);
+
+  BOOST_CHECK_THROW("0202C0"_block, tlv::Error);
+  BOOST_CHECK_THROW("0201C0C1"_block, tlv::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // BlockLiteral
+
+BOOST_AUTO_TEST_SUITE_END() // TestBlock
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/encoding/buffer-stream.t.cpp b/tests/unit/encoding/buffer-stream.t.cpp
new file mode 100644
index 0000000..9c95b2c
--- /dev/null
+++ b/tests/unit/encoding/buffer-stream.t.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/buffer-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestBufferStream)
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+  OBufferStream os;
+
+  shared_ptr<Buffer> buf = os.buf();
+  BOOST_CHECK_EQUAL(buf->size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Put)
+{
+  OBufferStream os;
+  os.put(0x33);
+  os.put(0x44);
+
+  shared_ptr<Buffer> buf = os.buf();
+  BOOST_REQUIRE_EQUAL(buf->size(), 2);
+  BOOST_CHECK_EQUAL(buf->at(0), 0x33);
+  BOOST_CHECK_EQUAL(buf->at(1), 0x44);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+  OBufferStream os;
+  os.write("\x11\x22", 2);
+
+  shared_ptr<Buffer> buf = os.buf();
+  BOOST_REQUIRE_EQUAL(buf->size(), 2);
+  BOOST_CHECK_EQUAL(buf->at(0), 0x11);
+  BOOST_CHECK_EQUAL(buf->at(1), 0x22);
+}
+
+BOOST_AUTO_TEST_CASE(Destructor) // Bug 3727
+{
+  auto os = make_unique<OBufferStream>();
+  *os << 'x';
+  os.reset(); // should not cause use-after-free
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBufferStream
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/encoding/encoder.t.cpp b/tests/unit/encoding/encoder.t.cpp
new file mode 100644
index 0000000..47cf8e4
--- /dev/null
+++ b/tests/unit/encoding/encoder.t.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/encoder.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace encoding {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestEncoder)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  Encoder e;
+  BOOST_CHECK_GT(e.capacity(), 100);
+
+  Encoder e1(100);
+  BOOST_CHECK_EQUAL(e1.capacity(), 100);
+
+  Encoder e2(100, 100);
+  BOOST_CHECK_EQUAL(e2.capacity(), 100);
+
+  BOOST_CHECK_EQUAL(e.prependByte(1), 1);
+  BOOST_CHECK_EQUAL(e.appendByte(1), 1);
+
+  uint8_t buf1[] = {'t', 'e', 's', 't', '1'};
+  BOOST_CHECK_EQUAL(e1.prependByteArray(buf1, sizeof(buf1)), 5);
+  BOOST_CHECK_EQUAL(e1.appendByteArray(buf1, sizeof(buf1)), 5);
+
+  std::vector<uint8_t> buf2 = {'t', 'e', 's', 't', '2'};
+  BOOST_CHECK_EQUAL(e1.prependRange(buf2.begin(), buf2.end()), 5);
+  BOOST_CHECK_EQUAL(e1.appendRange(buf2.begin(), buf2.end()), 5);
+
+  std::list<uint8_t> buf3 = {'t', 'e', 's', 't', '2'};
+  BOOST_CHECK_EQUAL(e2.prependRange(buf3.begin(), buf3.end()), 5);
+  BOOST_CHECK_EQUAL(e2.appendRange(buf3.begin(), buf3.end()), 5);
+
+  uint8_t expected1[] = {1, 1};
+  BOOST_CHECK_EQUAL_COLLECTIONS(e.buf(), e.buf() + e.size(),
+                                expected1, expected1 + sizeof(expected1));
+
+  const Encoder& constE = e;
+  BOOST_CHECK_EQUAL_COLLECTIONS(constE.buf(), constE.buf() + constE.size(),
+                                expected1, expected1 + sizeof(expected1));
+
+  uint8_t expected2[] = {'t', 'e', 's', 't', '2',
+                           't', 'e', 's', 't', '1', 't', 'e', 's', 't', '1',
+                         't', 'e', 's', 't', '2'};
+  BOOST_CHECK_EQUAL_COLLECTIONS(e1.begin(), e1.end(),
+                                expected2, expected2 + sizeof(expected2));
+  const Encoder& constE1 = e1;
+  BOOST_CHECK_EQUAL_COLLECTIONS(constE1.begin(), constE1.end(),
+                                expected2, expected2 + sizeof(expected2));
+
+  BOOST_CHECK_THROW(e1.block(), tlv::Error);
+  BOOST_CHECK_NO_THROW(e1.block(false));
+
+  e1.prependVarNumber(20);
+  e1.prependVarNumber(100);
+
+  BOOST_CHECK_NO_THROW(e1.block());
+}
+
+BOOST_AUTO_TEST_CASE(Tlv)
+{
+  Encoder e;
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(1), 1);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(1), 1);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(252), 1);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(252), 1);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(253), 3);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(253), 3);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(65536), 5);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(65536), 5);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(4294967296LL), 9);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(4294967296LL), 9);
+
+  //
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(1), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(1), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(252), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(252), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(253), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(253), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(255), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(255), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(256), 2);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(256), 2);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(65535), 2);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(65535), 2);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(65536), 4);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(65536), 4);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(4294967296LL), 8);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(4294967296LL), 8);
+
+  //
+
+  uint8_t buf[] = {0x01, 0x03, 0x00, 0x00, 0x00};
+  Block block1(buf, sizeof(buf));
+
+  BOOST_CHECK_EQUAL(e.prependByteArrayBlock(100, buf, sizeof(buf)), 7);
+  BOOST_CHECK_EQUAL(e.appendByteArrayBlock(100, buf, sizeof(buf)), 7);
+
+  BOOST_CHECK_EQUAL(e.prependBlock(block1), 5);
+  BOOST_CHECK_EQUAL(e.appendBlock(block1), 5);
+
+  Block block2(100, block1);
+
+  BOOST_CHECK_EQUAL(e.prependBlock(block2), 7);
+  BOOST_CHECK_EQUAL(e.appendBlock(block2), 7);
+}
+
+BOOST_AUTO_TEST_CASE(Reserve)
+{
+  Encoder e(100, 0);
+  BOOST_CHECK_EQUAL(e.capacity(), 100);
+
+  e.reserve(100, true);
+  BOOST_CHECK_EQUAL(e.capacity(), 100);
+
+  e.reserve(200, true);
+  BOOST_CHECK_EQUAL(e.capacity(), 200);
+
+  e.reserve(100, false);
+  BOOST_CHECK_EQUAL(e.capacity(), 200);
+
+  e.reserveFront(1000);
+  BOOST_CHECK_GT(e.capacity(), 1000);
+
+  e.reserveBack(1000);
+  BOOST_CHECK_GT(e.capacity(), 2000);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestEncoder
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace encoding
+} // namespace ndn
diff --git a/tests/unit/encoding/encoding-buffer.t.cpp b/tests/unit/encoding/encoding-buffer.t.cpp
new file mode 100644
index 0000000..646024b
--- /dev/null
+++ b/tests/unit/encoding/encoding-buffer.t.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/encoding-buffer.hpp"
+#include "encoding/block.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+class BufferEstimatorFixture
+{
+public:
+  EncodingBuffer buffer;
+  EncodingEstimator estimator;
+};
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestEncodingBuffer)
+
+BOOST_AUTO_TEST_CASE(ConstructFromBlock)
+{
+  auto buf = make_shared<Buffer>(10);
+  Block block(0xab, buf);
+  block.encode();
+
+  {
+    EncodingBuffer buffer(block);
+    BOOST_CHECK_EQUAL(buffer.size(), 12);
+    BOOST_CHECK_EQUAL(buffer.capacity(), 12);
+  }
+
+  (*buf)[1] = 0xe0;
+  (*buf)[2] = 2;
+  block = Block(buf, buf->begin() + 1, buf->begin() + 5);
+  BOOST_CHECK_EQUAL(block.type(), 0xe0);
+
+  {
+    EncodingBuffer buffer(block);
+    BOOST_CHECK_EQUAL(buffer.size(), 4);
+    BOOST_CHECK_EQUAL(buffer.capacity(), 10);
+  }
+}
+
+BOOST_FIXTURE_TEST_SUITE(PrependVarNumber, BufferEstimatorFixture)
+
+BOOST_AUTO_TEST_CASE(OneByte1)
+{
+  size_t s1 = buffer.prependVarNumber(252);
+  size_t s2 = estimator.prependVarNumber(252);
+  BOOST_CHECK_EQUAL(buffer.size(), 1);
+  BOOST_CHECK_EQUAL(s1, 1);
+  BOOST_CHECK_EQUAL(s2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(ThreeBytes1)
+{
+  size_t s1 = buffer.prependVarNumber(253);
+  size_t s2 = estimator.prependVarNumber(253);
+  BOOST_CHECK_EQUAL(buffer.size(), 3);
+  BOOST_CHECK_EQUAL(s1, 3);
+  BOOST_CHECK_EQUAL(s2, 3);
+}
+
+BOOST_AUTO_TEST_CASE(ThreeBytes2)
+{
+  size_t s1 = buffer.prependVarNumber(255);
+  size_t s2 = estimator.prependVarNumber(255);
+  BOOST_CHECK_EQUAL(buffer.size(), 3);
+  BOOST_CHECK_EQUAL(s1, 3);
+  BOOST_CHECK_EQUAL(s2, 3);
+}
+
+BOOST_AUTO_TEST_CASE(ThreeBytes3)
+{
+  size_t s1 = buffer.prependVarNumber(65535);
+  size_t s2 = estimator.prependVarNumber(65535);
+  BOOST_CHECK_EQUAL(buffer.size(), 3);
+  BOOST_CHECK_EQUAL(s1, 3);
+  BOOST_CHECK_EQUAL(s2, 3);
+}
+
+BOOST_AUTO_TEST_CASE(FiveBytes1)
+{
+  size_t s1 = buffer.prependVarNumber(65536);
+  size_t s2 = estimator.prependVarNumber(65536);
+  BOOST_CHECK_EQUAL(buffer.size(), 5);
+  BOOST_CHECK_EQUAL(s1, 5);
+  BOOST_CHECK_EQUAL(s2, 5);
+}
+
+BOOST_AUTO_TEST_CASE(FiveBytes2)
+{
+  size_t s1 = buffer.prependVarNumber(4294967295LL);
+  size_t s2 = estimator.prependVarNumber(4294967295LL);
+  BOOST_CHECK_EQUAL(buffer.size(), 5);
+  BOOST_CHECK_EQUAL(s1, 5);
+  BOOST_CHECK_EQUAL(s2, 5);
+}
+
+BOOST_AUTO_TEST_CASE(NineBytes)
+{
+  size_t s1 = buffer.prependVarNumber(4294967296LL);
+  size_t s2 = estimator.prependVarNumber(4294967296LL);
+  BOOST_CHECK_EQUAL(buffer.size(), 9);
+  BOOST_CHECK_EQUAL(s1, 9);
+  BOOST_CHECK_EQUAL(s2, 9);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // PrependVarNumber
+
+BOOST_FIXTURE_TEST_SUITE(PrependNonNegativeNumber, BufferEstimatorFixture)
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberOneByte1)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(252);
+  size_t s2 = estimator.prependNonNegativeInteger(252);
+  BOOST_CHECK_EQUAL(buffer.size(), 1);
+  BOOST_CHECK_EQUAL(s1, 1);
+  BOOST_CHECK_EQUAL(s2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberOneByte2)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(255);
+  size_t s2 = estimator.prependNonNegativeInteger(255);
+  BOOST_CHECK_EQUAL(buffer.size(), 1);
+  BOOST_CHECK_EQUAL(s1, 1);
+  BOOST_CHECK_EQUAL(s2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberTwoBytes1)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(256);
+  size_t s2 = estimator.prependNonNegativeInteger(256);
+  BOOST_CHECK_EQUAL(buffer.size(), 2);
+  BOOST_CHECK_EQUAL(s1, 2);
+  BOOST_CHECK_EQUAL(s2, 2);
+}
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberTwoBytes2)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(65535);
+  size_t s2 = estimator.prependNonNegativeInteger(65535);
+  BOOST_CHECK_EQUAL(buffer.size(), 2);
+  BOOST_CHECK_EQUAL(s1, 2);
+  BOOST_CHECK_EQUAL(s2, 2);
+}
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberFourBytes1)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(65536);
+  size_t s2 = estimator.prependNonNegativeInteger(65536);
+  BOOST_CHECK_EQUAL(buffer.size(), 4);
+  BOOST_CHECK_EQUAL(s1, 4);
+  BOOST_CHECK_EQUAL(s2, 4);
+}
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberFourBytes2)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(4294967295LL);
+  size_t s2 = estimator.prependNonNegativeInteger(4294967295LL);
+  BOOST_CHECK_EQUAL(buffer.size(), 4);
+  BOOST_CHECK_EQUAL(s1, 4);
+  BOOST_CHECK_EQUAL(s2, 4);
+}
+
+BOOST_AUTO_TEST_CASE(NonNegativeNumberEightBytes)
+{
+  size_t s1 = buffer.prependNonNegativeInteger(4294967296LL);
+  size_t s2 = estimator.prependNonNegativeInteger(4294967296LL);
+  BOOST_CHECK_EQUAL(buffer.size(), 8);
+  BOOST_CHECK_EQUAL(s1, 8);
+  BOOST_CHECK_EQUAL(s2, 8);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // PrependNonNegativeNumber
+
+BOOST_AUTO_TEST_SUITE_END() // TestEncodingBuffer
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/encoding/estimator.t.cpp b/tests/unit/encoding/estimator.t.cpp
new file mode 100644
index 0000000..8ab4399
--- /dev/null
+++ b/tests/unit/encoding/estimator.t.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/estimator.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace encoding {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestEstimator)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  Estimator e;
+
+  BOOST_CHECK_EQUAL(e.prependByte(1), 1);
+  BOOST_CHECK_EQUAL(e.appendByte(1), 1);
+
+  uint8_t buf1[] = {'t', 'e', 's', 't', '1'};
+  BOOST_CHECK_EQUAL(e.prependByteArray(buf1, sizeof(buf1)), 5);
+  BOOST_CHECK_EQUAL(e.appendByteArray(buf1, sizeof(buf1)), 5);
+
+  std::vector<uint8_t> buf2 = {'t', 'e', 's', 't', '2'};
+  BOOST_CHECK_EQUAL(e.prependRange(buf2.begin(), buf2.end()), 5);
+  BOOST_CHECK_EQUAL(e.appendRange(buf2.begin(), buf2.end()), 5);
+
+  std::list<uint8_t> buf3 = {'t', 'e', 's', 't', '2'};
+  BOOST_CHECK_EQUAL(e.prependRange(buf3.begin(), buf3.end()), 5);
+  BOOST_CHECK_EQUAL(e.appendRange(buf3.begin(), buf3.end()), 5);
+}
+
+BOOST_AUTO_TEST_CASE(Tlv)
+{
+  Estimator e;
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(1), 1);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(1), 1);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(252), 1);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(252), 1);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(253), 3);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(253), 3);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(65536), 5);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(65536), 5);
+
+  BOOST_CHECK_EQUAL(e.prependVarNumber(4294967296LL), 9);
+  BOOST_CHECK_EQUAL(e.appendVarNumber(4294967296LL), 9);
+
+  //
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(1), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(1), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(252), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(252), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(253), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(253), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(255), 1);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(255), 1);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(256), 2);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(256), 2);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(65535), 2);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(65535), 2);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(65536), 4);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(65536), 4);
+
+  BOOST_CHECK_EQUAL(e.prependNonNegativeInteger(4294967296LL), 8);
+  BOOST_CHECK_EQUAL(e.appendNonNegativeInteger(4294967296LL), 8);
+
+  //
+
+  uint8_t buf[] = {0x01, 0x03, 0x00, 0x00, 0x00};
+  Block block1(buf, sizeof(buf));
+
+  BOOST_CHECK_EQUAL(e.prependByteArrayBlock(100, buf, sizeof(buf)), 7);
+  BOOST_CHECK_EQUAL(e.appendByteArrayBlock(100, buf, sizeof(buf)), 7);
+
+  BOOST_CHECK_EQUAL(e.prependBlock(block1), 5);
+  BOOST_CHECK_EQUAL(e.appendBlock(block1), 5);
+
+  Block block2(100, block1);
+
+  BOOST_CHECK_EQUAL(e.prependBlock(block2), 7);
+  BOOST_CHECK_EQUAL(e.appendBlock(block2), 7);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestEstimator
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace encoding
+} // namespace ndn
diff --git a/tests/unit/encoding/nfd-constants.t.cpp b/tests/unit/encoding/nfd-constants.t.cpp
new file mode 100644
index 0000000..af9997b
--- /dev/null
+++ b/tests/unit/encoding/nfd-constants.t.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/nfd-constants.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestNfdConstants)
+
+BOOST_AUTO_TEST_CASE(PrintFaceScope)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_SCOPE_NONE), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_SCOPE_NON_LOCAL), "non-local");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_SCOPE_LOCAL), "local");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<FaceScope>(126)), "126");
+}
+
+BOOST_AUTO_TEST_CASE(PrintFacePersistency)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_PERSISTENCY_NONE), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_PERSISTENCY_ON_DEMAND), "on-demand");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_PERSISTENCY_PERSISTENT), "persistent");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_PERSISTENCY_PERMANENT), "permanent");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<FacePersistency>(110)), "110");
+}
+
+BOOST_AUTO_TEST_CASE(PrintLinkType)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(LINK_TYPE_NONE), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(LINK_TYPE_POINT_TO_POINT), "point-to-point");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(LINK_TYPE_MULTI_ACCESS), "multi-access");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(LINK_TYPE_AD_HOC), "adhoc");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<LinkType>(104)), "104");
+}
+
+BOOST_AUTO_TEST_CASE(PrintFaceEventKind)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_EVENT_NONE), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_EVENT_CREATED), "created");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_EVENT_DESTROYED), "destroyed");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_EVENT_UP), "up");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(FACE_EVENT_DOWN), "down");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<FaceEventKind>(175)), "175");
+}
+
+BOOST_AUTO_TEST_CASE(ParseRouteOrigin)
+{
+  auto expectSuccess = [] (const std::string& input, RouteOrigin expected) {
+    std::istringstream is(input);
+    RouteOrigin routeOrigin;
+    is >> routeOrigin;
+
+    BOOST_TEST_MESSAGE("parsing " << input);
+    BOOST_CHECK_EQUAL(routeOrigin, expected);
+  };
+
+  auto expectFail = [] (const std::string& input) {
+    std::istringstream is(input);
+    RouteOrigin routeOrigin;
+    is >> routeOrigin;
+
+    BOOST_TEST_MESSAGE("parsing " << input);
+    BOOST_CHECK(is.fail());
+    BOOST_CHECK_EQUAL(routeOrigin, ROUTE_ORIGIN_NONE);
+  };
+
+  expectSuccess("none", ROUTE_ORIGIN_NONE);
+  expectSuccess("App", ROUTE_ORIGIN_APP);
+  expectSuccess("AutoReg", ROUTE_ORIGIN_AUTOREG);
+  expectSuccess("Client", ROUTE_ORIGIN_CLIENT);
+  expectSuccess("AutoConf", ROUTE_ORIGIN_AUTOCONF);
+  expectSuccess("NLSR", ROUTE_ORIGIN_NLSR);
+  expectSuccess("PrefixAnn", ROUTE_ORIGIN_PREFIXANN);
+  expectSuccess("static", ROUTE_ORIGIN_STATIC);
+  expectSuccess("27", static_cast<RouteOrigin>(27));
+
+  expectSuccess(" app", ROUTE_ORIGIN_APP);
+  expectSuccess("app ", ROUTE_ORIGIN_APP);
+  expectSuccess(" app ", ROUTE_ORIGIN_APP);
+
+  expectFail("unrecognized");
+  expectFail("-1");
+  expectFail("0.1");
+  expectFail("65537");
+  expectFail("");
+}
+
+BOOST_AUTO_TEST_CASE(PrintRouteOrigin)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_NONE), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_APP), "app");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_AUTOREG), "autoreg");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_CLIENT), "client");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_AUTOCONF), "autoconf");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_NLSR), "nlsr");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_PREFIXANN), "prefixann");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_STATIC), "static");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<RouteOrigin>(27)), "27");
+}
+
+BOOST_AUTO_TEST_CASE(PrintRouteFlags)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_FLAGS_NONE), "none");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_FLAG_CHILD_INHERIT), "child-inherit");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_FLAG_CAPTURE), "capture");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<RouteFlags>(
+                    ROUTE_FLAG_CHILD_INHERIT | ROUTE_FLAG_CAPTURE)), "child-inherit|capture");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<RouteFlags>(
+                    ROUTE_FLAG_CAPTURE | 0x9c)), "capture|0x9c");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNfdConstants
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/encoding/tlv.t.cpp b/tests/unit/encoding/tlv.t.cpp
new file mode 100644
index 0000000..a82ffbe
--- /dev/null
+++ b/tests/unit/encoding/tlv.t.cpp
@@ -0,0 +1,522 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "encoding/tlv.hpp"
+#include "encoding/buffer.hpp"
+
+#include "boost-test.hpp"
+
+#include <array>
+#include <deque>
+#include <list>
+#include <sstream>
+#include <boost/concept_archetype.hpp>
+#include <boost/iostreams/stream.hpp>
+#include <boost/iostreams/device/array.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tlv {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Encoding)
+BOOST_AUTO_TEST_SUITE(TestTlv)
+
+BOOST_AUTO_TEST_CASE(CriticalType)
+{
+  BOOST_CHECK_EQUAL(isCriticalType(0), true);
+  BOOST_CHECK_EQUAL(isCriticalType(1), true);
+  BOOST_CHECK_EQUAL(isCriticalType(2), true);
+  BOOST_CHECK_EQUAL(isCriticalType(30), true);
+  BOOST_CHECK_EQUAL(isCriticalType(31), true);
+  BOOST_CHECK_EQUAL(isCriticalType(32), false);
+  BOOST_CHECK_EQUAL(isCriticalType(33), true);
+  BOOST_CHECK_EQUAL(isCriticalType(34), false);
+  BOOST_CHECK_EQUAL(isCriticalType(10000), false);
+  BOOST_CHECK_EQUAL(isCriticalType(10001), true);
+}
+
+using ArrayStream = boost::iostreams::stream<boost::iostreams::array_source>;
+using StreamIterator = std::istream_iterator<uint8_t>;
+
+#define ASSERT_READ_NUMBER_IS_FAST(T) \
+  static_assert(std::is_base_of<detail::ReadNumberFast<T>, detail::ReadNumber<T>>::value, \
+                # T " should use ReadNumberFast")
+#define ASSERT_READ_NUMBER_IS_SLOW(T) \
+  static_assert(std::is_base_of<detail::ReadNumberSlow<T>, detail::ReadNumber<T>>::value, \
+                # T " should use ReadNumberSlow")
+
+ASSERT_READ_NUMBER_IS_FAST(const uint8_t*);
+ASSERT_READ_NUMBER_IS_FAST(uint8_t*);
+ASSERT_READ_NUMBER_IS_FAST(int8_t*);
+ASSERT_READ_NUMBER_IS_FAST(char*);
+ASSERT_READ_NUMBER_IS_FAST(unsigned char*);
+ASSERT_READ_NUMBER_IS_FAST(signed char*);
+ASSERT_READ_NUMBER_IS_FAST(const uint8_t[]);
+ASSERT_READ_NUMBER_IS_FAST(uint8_t[]);
+ASSERT_READ_NUMBER_IS_FAST(const uint8_t[12]);
+ASSERT_READ_NUMBER_IS_FAST(uint8_t[12]);
+using Uint8Array = std::array<uint8_t, 87>;
+ASSERT_READ_NUMBER_IS_FAST(Uint8Array::const_iterator);
+ASSERT_READ_NUMBER_IS_FAST(Uint8Array::iterator);
+using CharArray = std::array<char, 87>;
+ASSERT_READ_NUMBER_IS_FAST(CharArray::iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::string::const_iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::string::iterator);
+ASSERT_READ_NUMBER_IS_FAST(Buffer::const_iterator);
+ASSERT_READ_NUMBER_IS_FAST(Buffer::iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::vector<uint8_t>::const_iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::vector<uint8_t>::iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::vector<int8_t>::iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::vector<char>::iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::vector<unsigned char>::iterator);
+ASSERT_READ_NUMBER_IS_FAST(std::vector<signed char>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::vector<bool>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::vector<uint16_t>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::vector<uint32_t>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::vector<uint64_t>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::deque<uint8_t>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(std::list<uint8_t>::iterator);
+ASSERT_READ_NUMBER_IS_SLOW(StreamIterator);
+
+BOOST_AUTO_TEST_SUITE(VarNumber)
+
+// This check ensures readVarNumber and readType only require InputIterator concept and nothing
+// more. This function should compile, but should never be executed.
+void
+checkArchetype()
+{
+  boost::input_iterator_archetype<uint8_t> begin, end;
+  uint64_t number = readVarNumber(begin, end);
+  uint32_t type = readType(begin, end);;
+  readVarNumber(begin, end, number);
+  readType(begin, end, type);
+}
+
+static const uint8_t BUFFER[] = {
+  0x01, // == 1
+  0xfc, // == 252
+  0xfd, 0x00, 0xfd, // == 253
+  0xfe, 0x00, 0x01, 0x00, 0x00, // == 65536
+  0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 // == 4294967296
+};
+
+BOOST_AUTO_TEST_CASE(SizeOf)
+{
+  BOOST_CHECK_EQUAL(sizeOfVarNumber(1), 1);
+  BOOST_CHECK_EQUAL(sizeOfVarNumber(252), 1);
+  BOOST_CHECK_EQUAL(sizeOfVarNumber(253), 3);
+  BOOST_CHECK_EQUAL(sizeOfVarNumber(65536), 5);
+  BOOST_CHECK_EQUAL(sizeOfVarNumber(4294967296), 9);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+  std::ostringstream os;
+
+  writeVarNumber(os, 1);
+  writeVarNumber(os, 252);
+  writeVarNumber(os, 253);
+  writeVarNumber(os, 65536);
+  writeVarNumber(os, 4294967296);
+
+  std::string buffer = os.str();
+  const uint8_t* actual = reinterpret_cast<const uint8_t*>(buffer.c_str());
+
+  BOOST_CHECK_EQUAL(buffer.size(), sizeof(BUFFER));
+  BOOST_CHECK_EQUAL_COLLECTIONS(BUFFER, BUFFER + sizeof(BUFFER),
+                                actual, actual + sizeof(BUFFER));
+}
+
+BOOST_AUTO_TEST_CASE(ReadFromBuffer)
+{
+  const uint8_t* begin;
+  uint64_t value;
+
+  begin = BUFFER;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 1, value), true);
+  begin = BUFFER;
+  BOOST_CHECK_NO_THROW(readVarNumber(begin, begin + 1));
+  BOOST_CHECK_EQUAL(value, 1);
+
+  begin = BUFFER + 1;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 1, value), true);
+  begin = BUFFER + 1;
+  BOOST_CHECK_NO_THROW(readVarNumber(begin, begin + 1));
+  BOOST_CHECK_EQUAL(value, 252);
+
+  begin = BUFFER + 2;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 1, value), false);
+  begin = BUFFER + 2;
+  BOOST_CHECK_THROW(readVarNumber(begin, begin + 1), Error);
+
+  begin = BUFFER + 2;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 2, value), false);
+  begin = BUFFER + 2;
+  BOOST_CHECK_THROW(readVarNumber(begin, begin + 2), Error);
+
+  begin = BUFFER + 2;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 3, value), true);
+  begin = BUFFER + 2;
+  BOOST_CHECK_NO_THROW(readVarNumber(begin, begin + 3));
+  BOOST_CHECK_EQUAL(value, 253);
+
+
+  begin = BUFFER + 5;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 1, value), false);
+  begin = BUFFER + 5;
+  BOOST_CHECK_THROW(readVarNumber(begin, begin + 1), Error);
+
+  begin = BUFFER + 5;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 4, value), false);
+  begin = BUFFER + 5;
+  BOOST_CHECK_THROW(readVarNumber(begin, begin + 4), Error);
+
+  begin = BUFFER + 5;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 5, value), true);
+  begin = BUFFER + 5;
+  BOOST_CHECK_NO_THROW(readVarNumber(begin, begin + 5));
+  BOOST_CHECK_EQUAL(value, 65536);
+
+  begin = BUFFER + 10;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 1, value), false);
+  begin = BUFFER + 10;
+  BOOST_CHECK_THROW(readVarNumber(begin, begin + 1), Error);
+
+  begin = BUFFER + 10;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 8, value), false);
+  begin = BUFFER + 10;
+  BOOST_CHECK_THROW(readVarNumber(begin, begin + 8), Error);
+
+  begin = BUFFER + 10;
+  BOOST_CHECK_EQUAL(readVarNumber(begin, begin + 9, value), true);
+  begin = BUFFER + 10;
+  BOOST_CHECK_NO_THROW(readVarNumber(begin, begin + 9));
+  BOOST_CHECK_EQUAL(value, 4294967296);
+}
+
+BOOST_AUTO_TEST_CASE(ReadFromStream)
+{
+  StreamIterator end; // end of stream
+  uint64_t value;
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), true);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_NO_THROW(readVarNumber(begin, end));
+    BOOST_CHECK_EQUAL(value, 1);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 1, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), true);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 1, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_NO_THROW(readVarNumber(begin, end));
+    BOOST_CHECK_EQUAL(value, 252);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 2, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), false);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 2, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readVarNumber(begin, end), Error);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 2, 2);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), false);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 2, 2);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readVarNumber(begin, end), Error);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 2, 3);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), true);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 2, 3);
+    StreamIterator begin(stream);
+    BOOST_CHECK_NO_THROW(readVarNumber(begin, end));
+    BOOST_CHECK_EQUAL(value, 253);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 5, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), false);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 5, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readVarNumber(begin, end), Error);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 5, 4);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), false);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 5, 4);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readVarNumber(begin, end), Error);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 5, 5);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), true);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 5, 5);
+    StreamIterator begin(stream);
+    BOOST_CHECK_NO_THROW(readVarNumber(begin, end));
+    BOOST_CHECK_EQUAL(value, 65536);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 10, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), false);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 10, 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readVarNumber(begin, end), Error);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 10, 8);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), false);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 10, 8);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readVarNumber(begin, end), Error);
+  }
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 10, 9);
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readVarNumber(begin, end, value), true);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER) + 10, 9);
+    StreamIterator begin(stream);
+    BOOST_CHECK_NO_THROW(readVarNumber(begin, end));
+    BOOST_CHECK_EQUAL(value, 4294967296);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // VarNumber
+
+BOOST_AUTO_TEST_SUITE(NonNegativeInteger)
+
+// This check ensures readNonNegativeInteger only requires InputIterator concept and nothing more.
+// This function should compile, but should never be executed.
+void
+checkArchetype()
+{
+  boost::input_iterator_archetype<uint8_t> begin, end;
+  readNonNegativeInteger(0, begin, end);
+}
+
+static const uint8_t BUFFER[] = {
+  0x01, // 1
+  0xff, // 255
+  0x01, 0x02, // 258
+  0x01, 0x01, 0x01, 0x02, // 16843010
+  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02 // 72340172838076674
+};
+
+BOOST_AUTO_TEST_CASE(SizeOf)
+{
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(1), 1);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(253), 1);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(255), 1);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(256), 2);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(65536), 4);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(16843009), 4);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(4294967296), 8);
+  BOOST_CHECK_EQUAL(sizeOfNonNegativeInteger(72340172838076673), 8);
+}
+
+BOOST_AUTO_TEST_CASE(Write)
+{
+  std::ostringstream os;
+
+  writeNonNegativeInteger(os, 1);
+  writeNonNegativeInteger(os, 255);
+  writeNonNegativeInteger(os, 258);
+  writeNonNegativeInteger(os, 16843010);
+  writeNonNegativeInteger(os, 72340172838076674);
+
+  std::string buffer = os.str();
+  const uint8_t* actual = reinterpret_cast<const uint8_t*>(buffer.c_str());
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(BUFFER, BUFFER + sizeof(BUFFER),
+                                actual, actual + sizeof(BUFFER));
+}
+
+BOOST_AUTO_TEST_CASE(ReadFromBuffer)
+{
+  const uint8_t* begin = nullptr;
+
+  begin = BUFFER;
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(1, begin, begin + 1), 1);
+  BOOST_CHECK_EQUAL(begin, BUFFER + 1);
+
+  begin = BUFFER + 1;
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(1, begin, begin + 1), 255);
+  BOOST_CHECK_EQUAL(begin, BUFFER + 2);
+
+  begin = BUFFER + 2;
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(2, begin, begin + 2), 258);
+  BOOST_CHECK_EQUAL(begin, BUFFER + 4);
+
+  begin = BUFFER + 4;
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(4, begin, begin + 4), 16843010);
+  BOOST_CHECK_EQUAL(begin, BUFFER + 8);
+
+  begin = BUFFER + 8;
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(8, begin, begin + 8), 72340172838076674);
+  BOOST_CHECK_EQUAL(begin, BUFFER + 16);
+
+  // invalid size
+  begin = BUFFER;
+  BOOST_CHECK_THROW(readNonNegativeInteger(3, begin, begin + 3), Error);
+
+  // available buffer smaller than size
+  begin = BUFFER;
+  BOOST_CHECK_THROW(readNonNegativeInteger(1, begin, begin + 0), Error);
+  begin = BUFFER;
+  BOOST_CHECK_THROW(readNonNegativeInteger(2, begin, begin + 1), Error);
+  begin = BUFFER;
+  BOOST_CHECK_THROW(readNonNegativeInteger(4, begin, begin + 3), Error);
+  begin = BUFFER;
+  BOOST_CHECK_THROW(readNonNegativeInteger(8, begin, begin + 7), Error);
+}
+
+BOOST_AUTO_TEST_CASE(ReadFromStream)
+{
+  StreamIterator end; // end of stream
+
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), sizeof(BUFFER));
+    StreamIterator begin(stream);
+    BOOST_CHECK_EQUAL(readNonNegativeInteger(1, begin, end), 1);
+    BOOST_CHECK_EQUAL(readNonNegativeInteger(1, begin, end), 255);
+    BOOST_CHECK_EQUAL(readNonNegativeInteger(2, begin, end), 258);
+    BOOST_CHECK_EQUAL(readNonNegativeInteger(4, begin, end), 16843010);
+    BOOST_CHECK_EQUAL(readNonNegativeInteger(8, begin, end), 72340172838076674);
+    BOOST_CHECK(begin == end);
+  }
+
+  // invalid size
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 3);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readNonNegativeInteger(3, begin, end), Error);
+  }
+
+  // available buffer smaller than size
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 0);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readNonNegativeInteger(1, begin, end), Error);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 1);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readNonNegativeInteger(2, begin, end), Error);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 3);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readNonNegativeInteger(4, begin, end), Error);
+  }
+  {
+    ArrayStream stream(reinterpret_cast<const char*>(BUFFER), 7);
+    StreamIterator begin(stream);
+    BOOST_CHECK_THROW(readNonNegativeInteger(8, begin, end), Error);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // NonNegativeInteger
+
+BOOST_AUTO_TEST_SUITE(PrintHelpers)
+
+BOOST_AUTO_TEST_CASE(PrintSignatureTypeValue)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(DigestSha256), "DigestSha256");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(SignatureSha256WithRsa), "SignatureSha256WithRsa");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<SignatureTypeValue>(2)), "Unknown(2)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(SignatureSha256WithEcdsa), "SignatureSha256WithEcdsa");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(SignatureHmacWithSha256), "SignatureHmacWithSha256");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<SignatureTypeValue>(5)), "Unknown(5)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<SignatureTypeValue>(200)), "Unknown(200)");
+}
+
+BOOST_AUTO_TEST_CASE(PrintContentTypeValue)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_Blob), "Blob");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_Link), "Link");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_Key), "Key");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_Nack), "Nack");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_Manifest), "Manifest");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_PrefixAnn), "PrefixAnn");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(6)), "Reserved(6)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(1023)), "Reserved(1023)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ContentType_Flic), "FLIC");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(1025)), "Unknown(1025)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(8999)), "Unknown(8999)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(9000)), "Experimental(9000)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(9999)), "Experimental(9999)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(10000)), "Unknown(10000)");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<ContentTypeValue>(19910118)), "Unknown(19910118)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // PrintHelpers
+
+BOOST_AUTO_TEST_SUITE_END() // TestTlv
+BOOST_AUTO_TEST_SUITE_END() // Encoding
+
+} // namespace tests
+} // namespace tlv
+} // namespace ndn
diff --git a/tests/unit/exclude.t.cpp b/tests/unit/exclude.t.cpp
new file mode 100644
index 0000000..da93a85
--- /dev/null
+++ b/tests/unit/exclude.t.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "exclude.hpp"
+#include "util/sha256.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestExclude)
+
+BOOST_AUTO_TEST_SUITE(GenericComponent) // exclude GenericNameComponent
+
+BOOST_AUTO_TEST_CASE(One)
+{
+  Exclude e;
+  std::vector<Exclude::Range> enumerated;
+
+  e.excludeOne(name::Component("b"));
+  BOOST_CHECK_EQUAL(e.toUri(), "b");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[0].from, name::Component("b"));
+
+  e.excludeOne(name::Component("d"));
+  BOOST_CHECK_EQUAL(e.toUri(), "b,d");
+  BOOST_CHECK_EQUAL(e.size(), 2);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 2);
+  BOOST_CHECK_EQUAL(enumerated[0].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[0].from, name::Component("b"));
+  BOOST_CHECK_EQUAL(enumerated[1].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[1].from, name::Component("d"));
+
+  e.excludeOne(name::Component("a"));
+  BOOST_CHECK_EQUAL(e.toUri(), "a,b,d");
+  BOOST_CHECK_EQUAL(e.size(), 3);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 3);
+  BOOST_CHECK_EQUAL(enumerated[0].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[0].from, name::Component("a"));
+  BOOST_CHECK_EQUAL(enumerated[1].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[1].from, name::Component("b"));
+  BOOST_CHECK_EQUAL(enumerated[2].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[2].from, name::Component("d"));
+
+  e.excludeOne(name::Component("aa"));
+  BOOST_CHECK_EQUAL(e.toUri(), "a,b,d,aa");
+  BOOST_CHECK_EQUAL(e.size(), 4);
+
+  e.excludeOne(name::Component("cc"));
+  BOOST_CHECK_EQUAL(e.toUri(), "a,b,d,aa,cc");
+  BOOST_CHECK_EQUAL(e.size(), 5);
+
+  e.excludeOne(name::Component("c"));
+  BOOST_CHECK_EQUAL(e.toUri(), "a,b,c,d,aa,cc");
+  BOOST_CHECK_EQUAL(e.size(), 6);
+}
+
+BOOST_AUTO_TEST_CASE(Before)
+{
+  // based on https://redmine.named-data.net/issues/1158
+  ndn::Exclude e;
+  BOOST_REQUIRE_NO_THROW(e.excludeBefore(name::Component("PuQxMaf91")));
+
+  BOOST_CHECK_EQUAL(e.toUri(), "*,PuQxMaf91");
+}
+
+BOOST_AUTO_TEST_CASE(Ranges)
+{
+  // example: ANY /b /d ANY /f
+
+  Exclude e;
+  std::vector<Exclude::Range> enumerated;
+
+  e.excludeOne(name::Component("b0"));
+  BOOST_CHECK_EQUAL(e.toUri(), "b0");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].isSingular(), true);
+  BOOST_CHECK_EQUAL(enumerated[0].from, name::Component("b0"));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(enumerated[0]), "{b0}");
+  BOOST_CHECK_EQUAL(enumerated[0], (Exclude::Range{false, name::Component("b0"), false, name::Component("b0")}));
+  BOOST_CHECK_NE(enumerated[0], (Exclude::Range{false, name::Component("b0"), false, name::Component("b1")}));
+
+  e.excludeBefore(name::Component("b1"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,b1");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("b1"));
+
+  e.excludeBefore(name::Component("c0"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,c0");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("c0"));
+
+  e.excludeRange(name::Component("a0"), name::Component("c0"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,c0");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("c0"));
+
+  e.excludeRange(name::Component("d0"), name::Component("e0"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,c0,d0,*,e0");
+  BOOST_CHECK_EQUAL(e.size(), 2);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 2);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("c0"));
+  BOOST_CHECK_EQUAL(enumerated[1].fromInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[1].from, name::Component("d0"));
+  BOOST_CHECK_EQUAL(enumerated[1].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[1].to, name::Component("e0"));
+
+  e.excludeRange(name::Component("c1"), name::Component("d1"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,c0,c1,*,e0");
+  BOOST_CHECK_EQUAL(e.size(), 2);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 2);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("c0"));
+  BOOST_CHECK_EQUAL(enumerated[1].fromInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[1].from, name::Component("c1"));
+  BOOST_CHECK_EQUAL(enumerated[1].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[1].to, name::Component("e0"));
+
+  e.excludeRange(name::Component("a1"), name::Component("d1"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,e0");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("e0"));
+
+  e.excludeBefore(name::Component("e2"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,e2");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("e2"));
+
+  e.excludeAfter(name::Component("f0"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,e2,f0,*");
+  BOOST_CHECK_EQUAL(e.size(), 2);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 2);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("e2"));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(enumerated[0]), "(-∞,e2]");
+  BOOST_CHECK_EQUAL(enumerated[0], (Exclude::Range{true, name::Component("ignore"), false, name::Component("e2")}));
+  BOOST_CHECK_EQUAL(enumerated[1].fromInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[1].from, name::Component("f0"));
+  BOOST_CHECK_EQUAL(enumerated[1].toInfinity, true);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(enumerated[1]), "[f0,+∞)");
+  BOOST_CHECK_EQUAL(enumerated[1], (Exclude::Range{false, name::Component("f0"), true, name::Component("ignore")}));
+
+  e.excludeAfter(name::Component("e5"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*,e2,e5,*");
+  BOOST_CHECK_EQUAL(e.size(), 2);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 2);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component("e2"));
+  BOOST_CHECK_EQUAL(enumerated[1].fromInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[1].from, name::Component("e5"));
+  BOOST_CHECK_EQUAL(enumerated[1].toInfinity, true);
+
+  e.excludeAfter(name::Component("b2"));
+  BOOST_CHECK_EQUAL(e.toUri(), "*");
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  enumerated.clear();
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, true);
+
+  BOOST_REQUIRE_THROW(e.excludeRange(name::Component("d0"), name::Component("a0")),
+                      Exclude::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // GenericComponent
+
+BOOST_AUTO_TEST_SUITE(ImplicitDigest) // exclude ImplicitSha256DigestComponent
+
+/** \brief make a name::Component with an octet repeated util::Sha256::DIGEST_SIZE times
+ *  \param octet the octet to fill the component
+ *  \param isDigest whether to make an ImplicitSha256DigestComponent or a GenericNameComponent
+ *  \param lastOctet if non-negative, set the last octet to a different value
+ */
+static name::Component
+makeComponent(uint8_t octet, bool isDigest, int lastOctet = -1)
+{
+  uint8_t wire[util::Sha256::DIGEST_SIZE];
+  std::memset(wire, octet, sizeof(wire));
+  if (lastOctet >= 0) {
+    wire[util::Sha256::DIGEST_SIZE - 1] = static_cast<uint8_t>(lastOctet);
+  }
+
+  if (isDigest) {
+    return name::Component::fromImplicitSha256Digest(wire, sizeof(wire));
+  }
+  else {
+    return name::Component(wire, sizeof(wire));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(One)
+{
+  name::Component digestC = makeComponent(0xCC, true);;
+  name::Component genericC = makeComponent(0xCC, false);
+  name::Component digestD = makeComponent(0xDD, true);
+
+  Exclude e;
+  e.excludeOne(digestC);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestC), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(genericC), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestD), false);
+
+  e.clear();
+  e.excludeOne(genericC);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestC), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(genericC), true);
+}
+
+BOOST_AUTO_TEST_CASE(BeforeDigest)
+{
+  name::Component digestBA = makeComponent(0xBB, true, 0xBA);
+  name::Component digestBB = makeComponent(0xBB, true);
+  name::Component digestBC = makeComponent(0xBB, true, 0xBC);
+
+  Exclude e;
+  e.excludeBefore(digestBB);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestBA), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestBB), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestBC), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), false);
+
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  std::vector<Exclude::Range> enumerated;
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, true);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, digestBB);
+}
+
+BOOST_AUTO_TEST_CASE(BeforeGeneric)
+{
+  name::Component digest0 = makeComponent(0x00, true);
+  name::Component digest9 = makeComponent(0x99, true);
+  name::Component digestF = makeComponent(0xFF, true);
+
+  Exclude e;
+  e.excludeBefore(name::Component(""));
+  BOOST_CHECK_EQUAL(e.isExcluded(digest0), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest9), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestF), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), false);
+}
+
+BOOST_AUTO_TEST_CASE(AfterDigest)
+{
+  name::Component digestBA = makeComponent(0xBB, true, 0xBA);
+  name::Component digestBB = makeComponent(0xBB, true);
+  name::Component digestBC = makeComponent(0xBB, true, 0xBC);
+
+  Exclude e;
+  e.excludeAfter(digestBB);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestBA), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestBB), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestBC), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), true);
+
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  std::vector<Exclude::Range> enumerated;
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].from, digestBB);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, true);
+}
+
+BOOST_AUTO_TEST_CASE(AfterDigestFF)
+{
+  name::Component digest00 = makeComponent(0x00, true);
+  name::Component digest99 = makeComponent(0x99, true);
+  name::Component digestFE = makeComponent(0xFF, true, 0xFE);
+  name::Component digestFF = makeComponent(0xFF, true);
+
+  Exclude e;
+  e.excludeAfter(digestFF);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest00), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest99), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestFE), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestFF), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), true);
+}
+
+BOOST_AUTO_TEST_CASE(AfterGeneric)
+{
+  name::Component digest0 = makeComponent(0x00, true);
+  name::Component digest9 = makeComponent(0x99, true);
+  name::Component digestF = makeComponent(0xFF, true);
+
+  Exclude e;
+  e.excludeAfter(name::Component(""));
+  BOOST_CHECK_EQUAL(e.isExcluded(digest0), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest9), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestF), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), true);
+}
+
+BOOST_AUTO_TEST_CASE(RangeDigest)
+{
+  name::Component digest0 = makeComponent(0x00, true);
+  name::Component digest7 = makeComponent(0x77, true);
+  name::Component digest8 = makeComponent(0x88, true);
+  name::Component digest9 = makeComponent(0x99, true);
+  name::Component digestF = makeComponent(0xFF, true);
+
+  Exclude e;
+  e.excludeRange(digest7, digest9);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest0), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest7), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest8), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest9), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestF), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), false);
+}
+
+BOOST_AUTO_TEST_CASE(RangeDigestReverse)
+{
+  name::Component digest7 = makeComponent(0x77, true);
+  name::Component digest9 = makeComponent(0x99, true);
+
+  Exclude e;
+  BOOST_CHECK_THROW(e.excludeRange(digest9, digest7), Exclude::Error);
+}
+
+BOOST_AUTO_TEST_CASE(RangeDigestGeneric)
+{
+  name::Component digest0 = makeComponent(0x00, true);
+  name::Component digest7 = makeComponent(0x77, true);
+  name::Component digest9 = makeComponent(0x99, true);
+  name::Component digestF = makeComponent(0xFF, true);
+
+  Exclude e;
+  e.excludeRange(digest9, name::Component(""));
+  BOOST_CHECK_EQUAL(e.isExcluded(digest0), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest7), false);
+  BOOST_CHECK_EQUAL(e.isExcluded(digest9), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(digestF), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("")), true);
+  BOOST_CHECK_EQUAL(e.isExcluded(name::Component("generic")), false);
+
+  BOOST_CHECK_EQUAL(e.size(), 1);
+  std::vector<Exclude::Range> enumerated;
+  std::copy(e.begin(), e.end(), std::back_inserter(enumerated));
+  BOOST_REQUIRE_EQUAL(enumerated.size(), 1);
+  BOOST_CHECK_EQUAL(enumerated[0].fromInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].from, digest9);
+  BOOST_CHECK_EQUAL(enumerated[0].toInfinity, false);
+  BOOST_CHECK_EQUAL(enumerated[0].to, name::Component(""));
+}
+
+BOOST_AUTO_TEST_CASE(RangeGenericDigest)
+{
+  name::Component digestF = makeComponent(0xFF, true);
+
+  Exclude e;
+  BOOST_CHECK_THROW(e.excludeRange(name::Component(""), digestF), Exclude::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ImplicitDigest
+
+BOOST_AUTO_TEST_SUITE(WireCompare) // wireEncode, wireDecode, operator==, operator!=
+
+BOOST_AUTO_TEST_CASE(EqualityComparable)
+{
+  Exclude e1;
+  Exclude e2;
+  BOOST_CHECK_EQUAL(e1, e2);
+
+  e1.excludeOne(name::Component("T"));
+  BOOST_CHECK_NE(e1, e2);
+
+  e2.excludeOne(name::Component("D"));
+  BOOST_CHECK_NE(e1, e2);
+
+  e2.clear();
+  e2.excludeOne(name::Component("T"));
+  BOOST_CHECK_EQUAL(e1, e2);
+
+  e2.clear();
+  const uint8_t EXCLUDE[] = { 0x10, 0x15, 0x13, 0x00, 0x08, 0x01, 0x41, 0x08, 0x01, 0x42,
+                              0x08, 0x01, 0x43, 0x13, 0x00, 0x08, 0x01, 0x44, 0x08, 0x01,
+                              0x45, 0x13, 0x00 };
+  e2.wireDecode(Block(EXCLUDE, sizeof(EXCLUDE)));
+
+  e1.clear();
+  e1.excludeBefore(name::Component("A"));
+  e1.excludeOne(name::Component("B"));
+  e1.excludeRange(name::Component("C"), name::Component("D"));
+  e1.excludeAfter(name::Component("E"));
+  BOOST_CHECK_EQUAL(e1, e2);
+}
+
+BOOST_AUTO_TEST_CASE(Malformed)
+{
+  Exclude e1;
+  BOOST_CHECK_THROW(e1.wireEncode(), Exclude::Error);
+
+  Exclude e2;
+
+  // top-level TLV-TYPE is not tlv::Exclude
+  const uint8_t NON_EXCLUDE[] = { 0x01, 0x02, 0x13, 0x00 };
+  BOOST_CHECK_THROW(e2.wireDecode(Block(NON_EXCLUDE, sizeof(NON_EXCLUDE))),
+                    tlv::Error);
+
+  // Exclude element is empty
+  const uint8_t EMPTY_EXCLUDE[] = { 0x10, 0x00 };
+  BOOST_CHECK_THROW(e2.wireDecode(Block(EMPTY_EXCLUDE, sizeof(EMPTY_EXCLUDE))),
+                    Exclude::Error);
+
+  // Exclude element contains unknown element
+  const uint8_t UNKNOWN_COMP1[] = { 0x10, 0x02, 0xAA, 0x00 };
+  BOOST_CHECK_THROW(e2.wireDecode(Block(UNKNOWN_COMP1, sizeof(UNKNOWN_COMP1))),
+                    Exclude::Error);
+
+  // Exclude element contains unknown element
+  const uint8_t UNKNOWN_COMP2[] = { 0x10, 0x05, 0x08, 0x01, 0x54, 0xAA, 0x00 };
+  BOOST_CHECK_THROW(e2.wireDecode(Block(UNKNOWN_COMP2, sizeof(UNKNOWN_COMP2))),
+                    Exclude::Error);
+
+  // // <Exclude><Any/></Exclude>
+  // const uint8_t ONLY_ANY[] = { 0x10, 0x02, 0x13, 0x00 };
+  // BOOST_CHECK_THROW(e2.wireDecode(Block(ONLY_ANY, sizeof(ONLY_ANY))),
+  //                   Exclude::Error);
+
+  // <Exclude><Any/><Any/></Exclude>
+  const uint8_t ANY_ANY[] = { 0x10, 0x04, 0x13, 0x00, 0x13, 0x00 };
+  BOOST_CHECK_THROW(e2.wireDecode(Block(ANY_ANY, sizeof(ANY_ANY))),
+                                  Exclude::Error);
+
+  // // <Exclude><Any/><GenericNameComponent>T</GenericNameComponent><Any/></Exclude>
+  // const uint8_t ANY_COMPONENT_ANY[] = { 0x10, 0x07, 0x13, 0x00, 0x08, 0x01, 0x54, 0x13, 0x00 };
+  // BOOST_CHECK_THROW(e2.wireDecode(Block(ANY_COMPONENT_ANY, sizeof(ANY_COMPONENT_ANY))),
+  //                   Exclude::Error);
+
+  uint8_t WIRE[] = {
+    0x10, 0x20, // Exclude
+          0x01, 0x1E, // ImplicitSha256DigestComponent with incorrect length
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd
+  };
+
+  BOOST_CHECK_THROW(Exclude().wireDecode(Block(WIRE, sizeof(WIRE))), Exclude::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ImplicitSha256Digest)
+{
+  uint8_t WIRE[] = {
+    0x10, 0x22, // Exclude
+          0x01, 0x20, // ImplicitSha256DigestComponent
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+                0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd
+  };
+
+  Block block(WIRE, sizeof(WIRE));
+
+  Exclude exclude;
+  BOOST_CHECK_NO_THROW(exclude.wireDecode(block));
+  BOOST_CHECK(exclude.wireEncode() == block);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyComponent) // Bug #2660
+{
+  Exclude e1, e2;
+
+  e1.excludeOne(name::Component());
+  e2.excludeOne(name::Component(""));
+
+  BOOST_CHECK_EQUAL(e1, e2);
+  BOOST_CHECK_EQUAL(e1.toUri(), e2.toUri());
+  BOOST_CHECK(e1.wireEncode() == e2.wireEncode());
+
+  BOOST_CHECK_EQUAL("...", e1.toUri());
+
+  uint8_t WIRE[] {0x10, 0x02, 0x08, 0x00};
+  BOOST_CHECK_EQUAL_COLLECTIONS(e1.wireEncode().begin(), e1.wireEncode().end(),
+                                WIRE, WIRE + sizeof(WIRE));
+
+  Exclude e3(Block(WIRE, sizeof(WIRE)));
+  BOOST_CHECK_EQUAL(e1, e3);
+  BOOST_CHECK_EQUAL(e1.toUri(), e3.toUri());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // WireCompare
+
+BOOST_AUTO_TEST_SUITE_END() // TestExclude
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/face.t.cpp b/tests/unit/face.t.cpp
new file mode 100644
index 0000000..3f2b82a
--- /dev/null
+++ b/tests/unit/face.t.cpp
@@ -0,0 +1,917 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "face.hpp"
+#include "lp/tags.hpp"
+#include "transport/tcp-transport.hpp"
+#include "transport/unix-transport.hpp"
+#include "util/dummy-client-face.hpp"
+#include "util/scheduler.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+#include "identity-management-time-fixture.hpp"
+#include "test-home-fixture.hpp"
+
+namespace ndn {
+namespace tests {
+
+using ndn::util::DummyClientFace;
+
+class FaceFixture : public IdentityManagementTimeFixture
+{
+public:
+  explicit
+  FaceFixture(bool enableRegistrationReply = true)
+    : face(io, m_keyChain, {true, enableRegistrationReply})
+  {
+  }
+
+public:
+  DummyClientFace face;
+};
+
+class FacesNoRegistrationReplyFixture : public FaceFixture
+{
+public:
+  FacesNoRegistrationReplyFixture()
+    : FaceFixture(false)
+  {
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestFace, FaceFixture)
+
+BOOST_AUTO_TEST_SUITE(Consumer)
+
+BOOST_AUTO_TEST_CASE(ExpressInterestData)
+{
+  size_t nData = 0;
+  face.expressInterest(*makeInterest("/Hello/World", true, 50_ms),
+                       [&] (const Interest& i, const Data& d) {
+                         BOOST_CHECK(i.getName().isPrefixOf(d.getName()));
+                         BOOST_CHECK_EQUAL(i.getName(), "/Hello/World");
+                         ++nData;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(40_ms);
+
+  face.receive(*makeData("/Bye/World/a"));
+  face.receive(*makeData("/Hello/World/a"));
+
+  advanceClocks(50_ms, 2);
+
+  BOOST_CHECK_EQUAL(nData, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+
+  size_t nTimeouts = 0;
+  face.expressInterest(*makeInterest("/Hello/World/a/2", false, 50_ms),
+                       bind([]{}),
+                       bind([]{}),
+                       bind([&nTimeouts] { ++nTimeouts; }));
+  advanceClocks(200_ms, 5);
+  BOOST_CHECK_EQUAL(nTimeouts, 1);
+}
+
+BOOST_AUTO_TEST_CASE(ExpressMultipleInterestData)
+{
+  size_t nData = 0;
+
+  face.expressInterest(*makeInterest("/Hello/World", true, 50_ms),
+                       [&] (const Interest& i, const Data& d) {
+                         ++nData;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  face.expressInterest(*makeInterest("/Hello/World/a", true, 50_ms),
+                       [&] (const Interest& i, const Data& d) {
+                         ++nData;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(40_ms);
+
+  face.receive(*makeData("/Hello/World/a/b"));
+
+  advanceClocks(50_ms, 2);
+
+  BOOST_CHECK_EQUAL(nData, 2);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(ExpressInterestEmptyDataCallback)
+{
+  face.expressInterest(*makeInterest("/Hello/World", true),
+                       nullptr,
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(1_ms);
+
+  BOOST_CHECK_NO_THROW(do {
+    face.receive(*makeData("/Hello/World/a"));
+    advanceClocks(1_ms);
+  } while (false));
+}
+
+BOOST_AUTO_TEST_CASE(ExpressInterestTimeout)
+{
+  size_t nTimeouts = 0;
+  face.expressInterest(*makeInterest("/Hello/World", false, 50_ms),
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       [&nTimeouts] (const Interest& i) {
+                         BOOST_CHECK_EQUAL(i.getName(), "/Hello/World");
+                         ++nTimeouts;
+                       });
+
+  advanceClocks(200_ms, 5);
+
+  BOOST_CHECK_EQUAL(nTimeouts, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+  BOOST_CHECK_EQUAL(face.sentNacks.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(ExpressInterestEmptyTimeoutCallback)
+{
+  face.expressInterest(*makeInterest("/Hello/World", false, 50_ms),
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       nullptr);
+  advanceClocks(40_ms);
+
+  BOOST_CHECK_NO_THROW(do {
+    advanceClocks(6_ms, 2);
+  } while (false));
+}
+
+BOOST_AUTO_TEST_CASE(ExpressInterestNack)
+{
+  size_t nNacks = 0;
+
+  auto interest = makeInterest("/Hello/World", false, 50_ms);
+
+  face.expressInterest(*interest,
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       [&] (const Interest& i, const lp::Nack& n) {
+                         BOOST_CHECK(i.getName().isPrefixOf(n.getInterest().getName()));
+                         BOOST_CHECK_EQUAL(i.getName(), "/Hello/World");
+                         BOOST_CHECK_EQUAL(n.getReason(), lp::NackReason::DUPLICATE);
+                         ++nNacks;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(40_ms);
+
+  face.receive(makeNack(face.sentInterests.at(0), lp::NackReason::DUPLICATE));
+
+  advanceClocks(50_ms, 2);
+
+  BOOST_CHECK_EQUAL(nNacks, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ExpressMultipleInterestNack)
+{
+  size_t nNacks = 0;
+
+  auto interest = makeInterest("/Hello/World", false, 50_ms, 1);
+  face.expressInterest(*interest,
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       [&] (const Interest& i, const lp::Nack& n) {
+                         ++nNacks;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  interest->setNonce(2);
+  face.expressInterest(*interest,
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       [&] (const Interest& i, const lp::Nack& n) {
+                         ++nNacks;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(40_ms);
+
+  face.receive(makeNack(face.sentInterests.at(1), lp::NackReason::DUPLICATE));
+
+  advanceClocks(50_ms, 2);
+
+  BOOST_CHECK_EQUAL(nNacks, 2);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(ExpressInterestEmptyNackCallback)
+{
+  face.expressInterest(*makeInterest("/Hello/World"),
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       nullptr,
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(1_ms);
+
+  BOOST_CHECK_NO_THROW(do {
+    face.receive(makeNack(face.sentInterests.at(0), lp::NackReason::DUPLICATE));
+    advanceClocks(1_ms);
+  } while (false));
+}
+
+BOOST_AUTO_TEST_CASE(RemovePendingInterest)
+{
+  const PendingInterestId* interestId =
+    face.expressInterest(*makeInterest("/Hello/World", true, 50_ms),
+                         bind([] { BOOST_FAIL("Unexpected data"); }),
+                         bind([] { BOOST_FAIL("Unexpected nack"); }),
+                         bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(10_ms);
+
+  face.removePendingInterest(interestId);
+  advanceClocks(10_ms);
+
+  face.receive(*makeData("/Hello/World/%21"));
+  advanceClocks(200_ms, 5);
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_CASE(RemoveAllPendingInterests)
+{
+  face.expressInterest(*makeInterest("/Hello/World/0", false, 50_ms),
+                       bind([] { BOOST_FAIL("Unexpected data"); }),
+                       bind([] { BOOST_FAIL("Unexpected nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  face.expressInterest(*makeInterest("/Hello/World/1", false, 50_ms),
+                       bind([] { BOOST_FAIL("Unexpected data"); }),
+                       bind([] { BOOST_FAIL("Unexpected nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(10_ms);
+
+  face.removeAllPendingInterests();
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(face.getNPendingInterests(), 0);
+
+  face.receive(*makeData("/Hello/World/0"));
+  face.receive(*makeData("/Hello/World/1"));
+  advanceClocks(200_ms, 5);
+}
+
+BOOST_AUTO_TEST_CASE(DestructionWithoutCancellingPendingInterests) // Bug #2518
+{
+  {
+    DummyClientFace face2(io, m_keyChain);
+    face2.expressInterest(*makeInterest("/Hello/World", false, 50_ms),
+                          nullptr, nullptr, nullptr);
+    advanceClocks(50_ms, 2);
+  }
+
+  advanceClocks(50_ms, 2); // should not crash
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_CASE(DataCallbackPutData) // Bug 4596
+{
+  face.expressInterest(*makeInterest("/localhost/notification/1"),
+                       [&] (const Interest& i, const Data& d) {
+                         face.put(*makeData("/chronosync/sampleDigest/1"));
+                       }, nullptr, nullptr);
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName(), "/localhost/notification/1");
+
+  face.receive(*makeInterest("/chronosync/sampleDigest", true));
+  advanceClocks(10_ms);
+
+  face.put(*makeData("/localhost/notification/1"));
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(face.sentData.back().getName(), "/chronosync/sampleDigest/1");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Consumer
+
+BOOST_AUTO_TEST_SUITE(Producer)
+
+BOOST_AUTO_TEST_CASE(PutData)
+{
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+
+  Data data("/4g7xxcuEow/KFvK5Kf2m");
+  signData(data);
+  face.put(data);
+
+  lp::CachePolicy cachePolicy;
+  cachePolicy.setPolicy(lp::CachePolicyType::NO_CACHE);
+  data.setTag(make_shared<lp::CachePolicyTag>(cachePolicy));
+  data.setTag(make_shared<lp::CongestionMarkTag>(1));
+  face.put(data);
+
+  advanceClocks(10_ms);
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 2);
+  BOOST_CHECK(face.sentData[0].getTag<lp::CachePolicyTag>() == nullptr);
+  BOOST_CHECK(face.sentData[0].getTag<lp::CongestionMarkTag>() == nullptr);
+  BOOST_CHECK(face.sentData[1].getTag<lp::CachePolicyTag>() != nullptr);
+  BOOST_CHECK(face.sentData[1].getTag<lp::CongestionMarkTag>() != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(PutDataLoopback)
+{
+  bool hasInterest1 = false, hasData = false;
+
+  // first InterestFilter allows loopback and should receive Interest
+  face.setInterestFilter("/", [&] (const InterestFilter&, const Interest& interest) {
+    hasInterest1 = true;
+    // do not respond with Data right away, so Face must send Interest to forwarder
+  });
+  // second InterestFilter disallows loopback and should not receive Interest
+  face.setInterestFilter(InterestFilter("/").allowLoopback(false),
+    bind([] { BOOST_ERROR("Unexpected Interest on second InterestFilter"); }));
+
+  face.expressInterest(*makeInterest("/A", true),
+                       bind([&] { hasData = true; }),
+                       bind([] { BOOST_FAIL("Unexpected nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(hasInterest1, true); // Interest looped back
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1); // Interest sent to forwarder
+  BOOST_CHECK_EQUAL(hasData, false); // waiting for Data
+
+  face.put(*makeData("/A/B")); // first InterestFilter responds with Data
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(hasData, true);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0); // do not spill Data to forwarder
+}
+
+BOOST_AUTO_TEST_CASE(PutMultipleData)
+{
+  bool hasInterest1 = false;
+  // register two Interest destinations
+  face.setInterestFilter("/", bind([&] {
+    hasInterest1 = true;
+    // sending Data right away from the first destination, don't care whether Interest goes to second destination
+    face.put(*makeData("/A/B"));
+  }));
+  face.setInterestFilter("/", bind([]{}));
+  advanceClocks(10_ms);
+
+  face.receive(*makeInterest("/A", true));
+  advanceClocks(10_ms);
+  BOOST_CHECK(hasInterest1);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData.at(0).getName(), "/A/B");
+
+  face.put(*makeData("/A/C"));
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1); // additional Data are ignored
+}
+
+BOOST_AUTO_TEST_CASE(PutNack)
+{
+  face.setInterestFilter("/", bind([]{})); // register one Interest destination so that face can accept Nacks
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(face.sentNacks.size(), 0);
+
+  face.put(makeNack(*makeInterest("/unsolicited", false, DEFAULT_INTEREST_LIFETIME, 18645250),
+                    lp::NackReason::NO_ROUTE));
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(face.sentNacks.size(), 0); // unsolicited Nack would not be sent
+
+  auto interest1 = makeInterest("/Hello/World", false, DEFAULT_INTEREST_LIFETIME, 14247162);
+  face.receive(*interest1);
+  auto interest2 = makeInterest("/another/prefix", false, DEFAULT_INTEREST_LIFETIME, 92203002);
+  face.receive(*interest2);
+  advanceClocks(10_ms);
+
+  face.put(makeNack(*interest1, lp::NackReason::DUPLICATE));
+  advanceClocks(10_ms);
+  BOOST_REQUIRE_EQUAL(face.sentNacks.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentNacks[0].getReason(), lp::NackReason::DUPLICATE);
+  BOOST_CHECK(face.sentNacks[0].getTag<lp::CongestionMarkTag>() == nullptr);
+
+  auto nack = makeNack(*interest2, lp::NackReason::NO_ROUTE);
+  nack.setTag(make_shared<lp::CongestionMarkTag>(1));
+  face.put(nack);
+  advanceClocks(10_ms);
+  BOOST_REQUIRE_EQUAL(face.sentNacks.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentNacks[1].getReason(), lp::NackReason::NO_ROUTE);
+  BOOST_CHECK(face.sentNacks[1].getTag<lp::CongestionMarkTag>() != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(PutMultipleNack)
+{
+  bool hasInterest1 = false, hasInterest2 = false;
+  // register two Interest destinations
+  face.setInterestFilter("/", [&] (const InterestFilter&, const Interest& interest) {
+    hasInterest1 = true;
+    // sending Nack right away from the first destination, Interest should still go to second destination
+    face.put(makeNack(interest, lp::NackReason::CONGESTION));
+  });
+  face.setInterestFilter("/", bind([&] { hasInterest2 = true; }));
+  advanceClocks(10_ms);
+
+  auto interest = makeInterest("/A", false, DEFAULT_INTEREST_LIFETIME, 14333271);
+  face.receive(*interest);
+  advanceClocks(10_ms);
+  BOOST_CHECK(hasInterest1);
+  BOOST_CHECK(hasInterest2);
+
+  // Nack from first destination is received, should wait for a response from the other destination
+  BOOST_CHECK_EQUAL(face.sentNacks.size(), 0);
+
+  face.put(makeNack(*interest, lp::NackReason::NO_ROUTE)); // Nack from second destination
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(face.sentNacks.size(), 1); // sending Nack after both destinations Nacked
+  BOOST_CHECK_EQUAL(face.sentNacks.at(0).getReason(), lp::NackReason::CONGESTION); // least severe reason
+
+  face.put(makeNack(*interest, lp::NackReason::DUPLICATE));
+  BOOST_CHECK_EQUAL(face.sentNacks.size(), 1); // additional Nacks are ignored
+}
+
+BOOST_AUTO_TEST_CASE(PutMultipleNackLoopback)
+{
+  bool hasInterest1 = false, hasNack = false;
+
+  // first InterestFilter allows loopback and should receive Interest
+  face.setInterestFilter("/", [&] (const InterestFilter&, const Interest& interest) {
+    hasInterest1 = true;
+    face.put(makeNack(interest, lp::NackReason::CONGESTION));
+  });
+  // second InterestFilter disallows loopback and should not receive Interest
+  face.setInterestFilter(InterestFilter("/").allowLoopback(false),
+    bind([] { BOOST_ERROR("Unexpected Interest on second InterestFilter"); }));
+
+  auto interest = makeInterest("/A", false, DEFAULT_INTEREST_LIFETIME, 28395852);
+  face.expressInterest(*interest,
+                       bind([] { BOOST_FAIL("Unexpected data"); }),
+                       [&] (const Interest&, const lp::Nack& nack) {
+                         hasNack = true;
+                         BOOST_CHECK_EQUAL(nack.getReason(), lp::NackReason::CONGESTION);
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(hasInterest1, true); // Interest looped back
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1); // Interest sent to forwarder
+  BOOST_CHECK_EQUAL(hasNack, false); // waiting for Nack from forwarder
+
+  face.receive(makeNack(*interest, lp::NackReason::NO_ROUTE));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(hasNack, true);
+}
+
+BOOST_AUTO_TEST_CASE(SetUnsetInterestFilter)
+{
+  size_t nInterests = 0;
+  size_t nRegs = 0;
+  const RegisteredPrefixId* regPrefixId =
+    face.setInterestFilter("/Hello/World",
+                           bind([&nInterests] { ++nInterests; }),
+                           bind([&nRegs] { ++nRegs; }),
+                           bind([] {  BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nRegs, 1);
+  BOOST_CHECK_EQUAL(nInterests, 0);
+
+  face.receive(*makeInterest("/Hello/World/%21"));
+  advanceClocks(25_ms, 4);
+
+  BOOST_CHECK_EQUAL(nRegs, 1);
+  BOOST_CHECK_EQUAL(nInterests, 1);
+
+  face.receive(*makeInterest("/Bye/World/%21"));
+  advanceClocks(10000_ms, 10);
+  BOOST_CHECK_EQUAL(nInterests, 1);
+
+  face.receive(*makeInterest("/Hello/World/%21/2"));
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nInterests, 2);
+
+  // removing filter
+  face.unsetInterestFilter(regPrefixId);
+  advanceClocks(25_ms, 4);
+
+  face.receive(*makeInterest("/Hello/World/%21/3"));
+  BOOST_CHECK_EQUAL(nInterests, 2);
+
+  face.unsetInterestFilter(static_cast<const RegisteredPrefixId*>(nullptr));
+  advanceClocks(25_ms, 4);
+
+  face.unsetInterestFilter(static_cast<const InterestFilterId*>(nullptr));
+  advanceClocks(25_ms, 4);
+}
+
+BOOST_AUTO_TEST_CASE(SetInterestFilterEmptyInterestCallback)
+{
+  face.setInterestFilter("/A", nullptr);
+  advanceClocks(1_ms);
+
+  BOOST_CHECK_NO_THROW(do {
+    face.receive(*makeInterest("/A/1"));
+    advanceClocks(1_ms);
+  } while (false));
+}
+
+BOOST_AUTO_TEST_CASE(SetUnsetInterestFilterWithoutSucessCallback)
+{
+  size_t nInterests = 0;
+  const RegisteredPrefixId* regPrefixId =
+    face.setInterestFilter("/Hello/World",
+                           bind([&nInterests] { ++nInterests; }),
+                           bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nInterests, 0);
+
+  face.receive(*makeInterest("/Hello/World/%21"));
+  advanceClocks(25_ms, 4);
+
+  BOOST_CHECK_EQUAL(nInterests, 1);
+
+  face.receive(*makeInterest("/Bye/World/%21"));
+  advanceClocks(10000_ms, 10);
+  BOOST_CHECK_EQUAL(nInterests, 1);
+
+  face.receive(*makeInterest("/Hello/World/%21/2"));
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nInterests, 2);
+
+  // removing filter
+  face.unsetInterestFilter(regPrefixId);
+  advanceClocks(25_ms, 4);
+
+  face.receive(*makeInterest("/Hello/World/%21/3"));
+  BOOST_CHECK_EQUAL(nInterests, 2);
+
+  face.unsetInterestFilter(static_cast<const RegisteredPrefixId*>(nullptr));
+  advanceClocks(25_ms, 4);
+
+  face.unsetInterestFilter(static_cast<const InterestFilterId*>(nullptr));
+  advanceClocks(25_ms, 4);
+}
+
+BOOST_FIXTURE_TEST_CASE(SetInterestFilterFail, FacesNoRegistrationReplyFixture)
+{
+  // don't enable registration reply
+  size_t nRegFailed = 0;
+  face.setInterestFilter("/Hello/World",
+                         bind([] { BOOST_FAIL("Unexpected Interest"); }),
+                         bind([] { BOOST_FAIL("Unexpected success of setInterestFilter"); }),
+                         bind([&nRegFailed] { ++nRegFailed; }));
+
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nRegFailed, 0);
+
+  advanceClocks(2000_ms, 5);
+  BOOST_CHECK_EQUAL(nRegFailed, 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(SetInterestFilterFailWithoutSuccessCallback, FacesNoRegistrationReplyFixture)
+{
+  // don't enable registration reply
+  size_t nRegFailed = 0;
+  face.setInterestFilter("/Hello/World",
+                         bind([] { BOOST_FAIL("Unexpected Interest"); }),
+                         bind([&nRegFailed] { ++nRegFailed; }));
+
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nRegFailed, 0);
+
+  advanceClocks(2000_ms, 5);
+  BOOST_CHECK_EQUAL(nRegFailed, 1);
+}
+
+BOOST_AUTO_TEST_CASE(RegisterUnregisterPrefix)
+{
+  size_t nRegSuccesses = 0;
+  const RegisteredPrefixId* regPrefixId =
+    face.registerPrefix("/Hello/World",
+                        bind([&nRegSuccesses] { ++nRegSuccesses; }),
+                        bind([] { BOOST_FAIL("Unexpected registerPrefix failure"); }));
+
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nRegSuccesses, 1);
+
+  size_t nUnregSuccesses = 0;
+  face.unregisterPrefix(regPrefixId,
+                        bind([&nUnregSuccesses] { ++nUnregSuccesses; }),
+                        bind([] { BOOST_FAIL("Unexpected unregisterPrefix failure"); }));
+
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nUnregSuccesses, 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(RegisterUnregisterPrefixFail, FacesNoRegistrationReplyFixture)
+{
+  // don't enable registration reply
+  size_t nRegFailures = 0;
+  face.registerPrefix("/Hello/World",
+                      bind([] { BOOST_FAIL("Unexpected registerPrefix success"); }),
+                      bind([&nRegFailures] { ++nRegFailures; }));
+
+  advanceClocks(5000_ms, 20);
+  BOOST_CHECK_EQUAL(nRegFailures, 1);
+}
+
+BOOST_AUTO_TEST_CASE(SimilarFilters)
+{
+  size_t nInInterests1 = 0;
+  face.setInterestFilter("/Hello/World",
+                         bind([&nInInterests1] { ++nInInterests1; }),
+                         nullptr,
+                         bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  size_t nInInterests2 = 0;
+  face.setInterestFilter("/Hello",
+                         bind([&nInInterests2] { ++nInInterests2; }),
+                         nullptr,
+                         bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  size_t nInInterests3 = 0;
+  face.setInterestFilter("/Los/Angeles/Lakers",
+                         bind([&nInInterests3] { ++nInInterests3; }),
+                         nullptr,
+                         bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  advanceClocks(25_ms, 4);
+
+  face.receive(*makeInterest("/Hello/World/%21"));
+  advanceClocks(25_ms, 4);
+
+  BOOST_CHECK_EQUAL(nInInterests1, 1);
+  BOOST_CHECK_EQUAL(nInInterests2, 1);
+  BOOST_CHECK_EQUAL(nInInterests3, 0);
+}
+
+BOOST_AUTO_TEST_CASE(SetRegexFilterError)
+{
+  face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
+                         [] (const Name&, const Interest&) {
+                           BOOST_FAIL("InterestFilter::Error should have been triggered");
+                         },
+                         nullptr,
+                         bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  advanceClocks(25_ms, 4);
+
+  BOOST_REQUIRE_THROW(face.receive(*makeInterest("/Hello/World/XXX/b/c")), InterestFilter::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SetRegexFilter)
+{
+  size_t nInInterests = 0;
+  face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
+                         bind([&nInInterests] { ++nInInterests; }),
+                         nullptr,
+                         bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  advanceClocks(25_ms, 4);
+
+  face.receive(*makeInterest("/Hello/World/a"));     // shouldn't match
+  BOOST_CHECK_EQUAL(nInInterests, 0);
+
+  face.receive(*makeInterest("/Hello/World/a/b"));   // should match
+  BOOST_CHECK_EQUAL(nInInterests, 1);
+
+  face.receive(*makeInterest("/Hello/World/a/b/c")); // should match
+  BOOST_CHECK_EQUAL(nInInterests, 2);
+
+  face.receive(*makeInterest("/Hello/World/a/b/d")); // should not match
+  BOOST_CHECK_EQUAL(nInInterests, 2);
+}
+
+BOOST_AUTO_TEST_CASE(SetRegexFilterAndRegister)
+{
+  size_t nInInterests = 0;
+  face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
+                         bind([&nInInterests] { ++nInInterests; }));
+
+  size_t nRegSuccesses = 0;
+  face.registerPrefix("/Hello/World",
+                      bind([&nRegSuccesses] { ++nRegSuccesses; }),
+                      bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  advanceClocks(25_ms, 4);
+  BOOST_CHECK_EQUAL(nRegSuccesses, 1);
+
+  face.receive(*makeInterest("/Hello/World/a")); // shouldn't match
+  BOOST_CHECK_EQUAL(nInInterests, 0);
+
+  face.receive(*makeInterest("/Hello/World/a/b")); // should match
+  BOOST_CHECK_EQUAL(nInInterests, 1);
+
+  face.receive(*makeInterest("/Hello/World/a/b/c")); // should match
+  BOOST_CHECK_EQUAL(nInInterests, 2);
+
+  face.receive(*makeInterest("/Hello/World/a/b/d")); // should not match
+  BOOST_CHECK_EQUAL(nInInterests, 2);
+}
+
+BOOST_FIXTURE_TEST_CASE(SetInterestFilterNoReg, FacesNoRegistrationReplyFixture) // Bug 2318
+{
+  // This behavior is specific to DummyClientFace.
+  // Regular Face won't accept incoming packets until something is sent.
+
+  int hit = 0;
+  face.setInterestFilter(Name("/"), bind([&hit] { ++hit; }));
+  face.processEvents(time::milliseconds(-1));
+
+  face.receive(*makeInterest("/A"));
+  face.processEvents(time::milliseconds(-1));
+
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Producer
+
+BOOST_AUTO_TEST_SUITE(IoRoutines)
+
+BOOST_AUTO_TEST_CASE(ProcessEvents)
+{
+  face.processEvents(time::milliseconds(-1)); // io_service::reset()/poll() inside
+
+  size_t nRegSuccesses = 0;
+  face.registerPrefix("/Hello/World",
+                      bind([&nRegSuccesses] { ++nRegSuccesses; }),
+                      bind([] { BOOST_FAIL("Unexpected setInterestFilter failure"); }));
+
+  // io_service::poll() without reset
+  face.getIoService().poll();
+  BOOST_CHECK_EQUAL(nRegSuccesses, 0);
+
+  face.processEvents(time::milliseconds(-1)); // io_service::reset()/poll() inside
+  BOOST_CHECK_EQUAL(nRegSuccesses, 1);
+}
+
+BOOST_AUTO_TEST_CASE(DestroyWithoutProcessEvents) // Bug 3248
+{
+  auto face2 = make_unique<Face>(io);
+  face2.reset();
+
+  io.poll(); // should not crash
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // IoRoutines
+
+BOOST_AUTO_TEST_SUITE(Transport)
+
+using ndn::Transport;
+
+struct PibDirWithDefaultTpm
+{
+  const std::string PATH = "build/keys-with-default-tpm";
+};
+
+BOOST_FIXTURE_TEST_CASE(FaceTransport, IdentityManagementTimeFixture)
+{
+  BOOST_CHECK(Face().getTransport() != nullptr);
+
+  BOOST_CHECK(Face(shared_ptr<Transport>()).getTransport() != nullptr);
+  BOOST_CHECK(Face(shared_ptr<Transport>(), io).getTransport() != nullptr);
+  BOOST_CHECK(Face(shared_ptr<Transport>(), io, m_keyChain).getTransport() != nullptr);
+
+  auto transport = make_shared<TcpTransport>("localhost", "6363"); // no real io operations will be scheduled
+  BOOST_CHECK(Face(transport).getTransport() == transport);
+  BOOST_CHECK(Face(transport, io).getTransport() == transport);
+  BOOST_CHECK(Face(transport, io, m_keyChain).getTransport() == transport);
+}
+
+class WithEnv : private IdentityManagementTimeFixture
+{
+public:
+  WithEnv()
+  {
+    if (getenv("NDN_CLIENT_TRANSPORT") != nullptr) {
+      m_oldTransport = getenv("NDN_CLIENT_TRANSPORT");
+      unsetenv("NDN_CLIENT_TRANSPORT");
+    }
+  }
+
+  void
+  configure(const std::string& faceUri)
+  {
+    setenv("NDN_CLIENT_TRANSPORT", faceUri.c_str(), true);
+  }
+
+  ~WithEnv()
+  {
+    if (!m_oldTransport.empty()) {
+      setenv("NDN_CLIENT_TRANSPORT", m_oldTransport.c_str(), true);
+    }
+    else {
+      unsetenv("NDN_CLIENT_TRANSPORT");
+    }
+  }
+
+private:
+  std::string m_oldTransport;
+};
+
+class WithConfig : private TestHomeFixture<DefaultPibDir>
+{
+public:
+  void
+  configure(const std::string& faceUri)
+  {
+    createClientConf({"transport=" + faceUri});
+  }
+};
+
+class WithEnvAndConfig : public WithEnv, public WithConfig
+{
+};
+
+typedef boost::mpl::vector<WithEnv, WithConfig> ConfigOptions;
+
+BOOST_FIXTURE_TEST_CASE(NoConfig, WithEnvAndConfig) // fixture configures test HOME and PIB/TPM path
+{
+  shared_ptr<Face> face;
+  BOOST_REQUIRE_NO_THROW(face = make_shared<Face>());
+  BOOST_CHECK(dynamic_pointer_cast<UnixTransport>(face->getTransport()) != nullptr);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Unix, T, ConfigOptions, T)
+{
+  this->configure("unix://some/path");
+
+  shared_ptr<Face> face;
+  BOOST_REQUIRE_NO_THROW(face = make_shared<Face>());
+  BOOST_CHECK(dynamic_pointer_cast<UnixTransport>(face->getTransport()) != nullptr);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Tcp, T, ConfigOptions, T)
+{
+  this->configure("tcp://127.0.0.1:6000");
+
+  shared_ptr<Face> face;
+  BOOST_REQUIRE_NO_THROW(face = make_shared<Face>());
+  BOOST_CHECK(dynamic_pointer_cast<TcpTransport>(face->getTransport()) != nullptr);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(WrongTransport, T, ConfigOptions, T)
+{
+  this->configure("wrong-transport:");
+
+  BOOST_CHECK_THROW(make_shared<Face>(), ConfigFile::Error);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(WrongUri, T, ConfigOptions, T)
+{
+  this->configure("wrong-uri");
+
+  BOOST_CHECK_THROW(make_shared<Face>(), ConfigFile::Error);
+}
+
+BOOST_FIXTURE_TEST_CASE(EnvOverride, WithEnvAndConfig)
+{
+  this->WithEnv::configure("tcp://127.0.0.1:6000");
+  this->WithConfig::configure("unix://some/path");
+
+  shared_ptr<Face> face;
+  BOOST_REQUIRE_NO_THROW(face = make_shared<Face>());
+  BOOST_CHECK(dynamic_pointer_cast<TcpTransport>(face->getTransport()) != nullptr);
+}
+
+BOOST_FIXTURE_TEST_CASE(ExplicitTransport, WithEnvAndConfig)
+{
+  this->WithEnv::configure("wrong-uri");
+  this->WithConfig::configure("wrong-transport:");
+
+  auto transport = make_shared<UnixTransport>("unix://some/path");
+  shared_ptr<Face> face;
+  BOOST_REQUIRE_NO_THROW(face = make_shared<Face>(transport));
+  BOOST_CHECK(dynamic_pointer_cast<UnixTransport>(face->getTransport()) != nullptr);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Transport
+
+BOOST_AUTO_TEST_SUITE_END() // TestFace
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/identity-management-time-fixture.hpp b/tests/unit/identity-management-time-fixture.hpp
new file mode 100644
index 0000000..cafa9c6
--- /dev/null
+++ b/tests/unit/identity-management-time-fixture.hpp
@@ -0,0 +1,39 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_UNIT_TESTS_IDENTITY_MANAGEMENT_TIME_FIXTURE_HPP
+#define NDN_TESTS_UNIT_TESTS_IDENTITY_MANAGEMENT_TIME_FIXTURE_HPP
+
+#include "identity-management-fixture.hpp"
+#include "unit-test-time-fixture.hpp"
+
+namespace ndn {
+namespace tests {
+
+class IdentityManagementTimeFixture : public UnitTestTimeFixture
+                                    , public IdentityManagementFixture
+{
+};
+
+} // namespace tests
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_IDENTITY_MANAGEMENT_TIME_FIXTURE_HPP
diff --git a/tests/unit/ims/in-memory-storage-fifo.t.cpp b/tests/unit/ims/in-memory-storage-fifo.t.cpp
new file mode 100644
index 0000000..00cde40
--- /dev/null
+++ b/tests/unit/ims/in-memory-storage-fifo.t.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "ims/in-memory-storage-fifo.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Ims)
+BOOST_AUTO_TEST_SUITE(TestInMemoryStorageFifo)
+
+BOOST_AUTO_TEST_CASE(ArrivalQueue)
+{
+  InMemoryStorageFifo ims;
+
+  ims.insert(*makeData("/1"));
+  ims.insert(*makeData("/2"));
+  ims.insert(*makeData("/3"));
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<Interest> interest = makeInterest("/1");
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_CHECK(found == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(ArrivalQueue2)
+{
+  InMemoryStorageFifo ims;
+
+  ims.insert(*makeData("/1"));
+  ims.insert(*makeData("/2"));
+  ims.insert(*makeData("/3"));
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<Interest> interest1 = makeInterest("/1");
+  shared_ptr<const Data> found1 = ims.find(*interest1);
+  BOOST_CHECK(found1 == nullptr);
+
+  ims.insert(*makeData("/4"));
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<Interest> interest2 = makeInterest("/2");
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_CHECK(found2 == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(MemoryPoolSizeZeroBug) // Bug #4769
+{
+  InMemoryStorageFifo ims;
+
+  BOOST_CHECK_EQUAL(ims.getCapacity(), 16);
+  for (int i = 1; i < 5; ++i) {
+    ims.insert(*makeData(to_string(i)));
+    ims.erase(Name(to_string(i)));
+  }
+
+  BOOST_CHECK_EQUAL(ims.getCapacity(), 16);
+  ims.insert(*makeData("/5"));
+  BOOST_CHECK_EQUAL(ims.getCapacity(), 16);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInMemoryStorageFifo
+BOOST_AUTO_TEST_SUITE_END() // Ims
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/ims/in-memory-storage-lfu.t.cpp b/tests/unit/ims/in-memory-storage-lfu.t.cpp
new file mode 100644
index 0000000..ef58ce7
--- /dev/null
+++ b/tests/unit/ims/in-memory-storage-lfu.t.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "ims/in-memory-storage-lfu.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Ims)
+BOOST_AUTO_TEST_SUITE(TestInMemoryStorageLfu)
+
+BOOST_AUTO_TEST_CASE(FrequencyQueue)
+{
+  InMemoryStorageLfu ims;
+
+  Name name1("/insert/1");
+  shared_ptr<Data> data1 = makeData(name1);
+  ims.insert(*data1);
+
+  Name name2("/insert/2");
+  shared_ptr<Data> data2 = makeData(name2);
+  ims.insert(*data2);
+
+  Name name3("/insert/3");
+  shared_ptr<Data> data3 = makeData(name3);
+  ims.insert(*data3);
+
+  shared_ptr<Interest> interest1 = makeInterest(name1);
+  shared_ptr<Interest> interest2 = makeInterest(name2);
+  shared_ptr<Interest> interest3 = makeInterest(name3);
+
+  ims.find(*interest1);
+  ims.find(*interest2);
+  ims.find(*interest3);
+  ims.find(*interest1);
+  ims.find(*interest3);
+  ims.find(*interest3);
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_CHECK(found2 == nullptr);
+
+  shared_ptr<const Data> found1 = ims.find(*interest1);
+  BOOST_CHECK_EQUAL(found1->getName(), name1);
+  shared_ptr<const Data> found3 = ims.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), name3);
+}
+
+BOOST_AUTO_TEST_CASE(FrequencyQueue2)
+{
+  InMemoryStorageLfu ims;
+
+  Name name1("/insert/1");
+  shared_ptr<Data> data1 = makeData(name1);
+  ims.insert(*data1);
+
+  Name name2("/insert/2");
+  shared_ptr<Data> data2 = makeData(name2);
+  ims.insert(*data2);
+
+  Name name3("/insert/3");
+  shared_ptr<Data> data3 = makeData(name3);
+  ims.insert(*data3);
+
+  shared_ptr<Interest> interest1 = makeInterest(name1);
+  shared_ptr<Interest> interest2 = makeInterest(name2);
+  shared_ptr<Interest> interest3 = makeInterest(name3);
+
+  ims.find(*interest1);
+  ims.find(*interest2);
+  ims.find(*interest3);
+  ims.find(*interest1);
+  ims.find(*interest3);
+  ims.find(*interest3);
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_CHECK(found2 == nullptr);
+
+  shared_ptr<const Data> found1 = ims.find(*interest1);
+  BOOST_CHECK_EQUAL(found1->getName(), name1);
+  shared_ptr<const Data> found3 = ims.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), name3);
+
+  Name name4("/insert/4");
+  shared_ptr<Data> data4 = makeData(name4);
+  ims.insert(*data4);
+
+  shared_ptr<Interest> interest4 = makeInterest(name4);
+  ims.find(*interest4);
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<const Data> found4 = ims.find(*interest4);
+  BOOST_CHECK(found4 == nullptr);
+
+  found1 = ims.find(*interest1);
+  BOOST_CHECK_EQUAL(found1->getName(), name1);
+  found3 = ims.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), name3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInMemoryStorageLfu
+BOOST_AUTO_TEST_SUITE_END() // Ims
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/ims/in-memory-storage-lru.t.cpp b/tests/unit/ims/in-memory-storage-lru.t.cpp
new file mode 100644
index 0000000..9058d66
--- /dev/null
+++ b/tests/unit/ims/in-memory-storage-lru.t.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "ims/in-memory-storage-lru.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Ims)
+BOOST_AUTO_TEST_SUITE(TestInMemoryStorageLru)
+
+BOOST_AUTO_TEST_CASE(UsedTimeQueue)
+{
+  InMemoryStorageLru ims;
+
+  Name name1("/insert/1");
+  shared_ptr<Data> data1 = makeData(name1);
+  ims.insert(*data1);
+
+  Name name2("/insert/2");
+  shared_ptr<Data> data2 = makeData(name2);
+  ims.insert(*data2);
+
+  Name name3("/insert/3");
+  shared_ptr<Data> data3 = makeData(name3);
+  ims.insert(*data3);
+
+  shared_ptr<Interest> interest1 = makeInterest(name1);
+  shared_ptr<Interest> interest2 = makeInterest(name2);
+  shared_ptr<Interest> interest3 = makeInterest(name3);
+
+  ims.find(*interest1);
+  ims.find(*interest2);
+  ims.find(*interest3);
+  ims.find(*interest1);
+  ims.find(*interest3);
+  ims.find(*interest3);
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_CHECK(found2 == nullptr);
+
+  shared_ptr<const Data> found1 = ims.find(*interest1);
+  BOOST_CHECK_EQUAL(found1->getName(), name1);
+  shared_ptr<const Data> found3 = ims.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), name3);
+}
+
+BOOST_AUTO_TEST_CASE(UsedTimeQueue2)
+{
+  InMemoryStorageLru ims;
+
+  Name name1("/insert/1");
+  shared_ptr<Data> data = makeData(name1);
+  ims.insert(*data);
+
+  Name name2("/insert/2");
+  shared_ptr<Data> data2 = makeData(name2);
+  ims.insert(*data2);
+
+  Name name3("/insert/3");
+  shared_ptr<Data> data3 = makeData(name3);
+  ims.insert(*data3);
+
+  shared_ptr<Interest> interest1 = makeInterest(name1);
+  shared_ptr<Interest> interest2 = makeInterest(name2);
+  shared_ptr<Interest> interest3 = makeInterest(name3);
+
+  ims.find(*interest1);
+  ims.find(*interest2);
+  ims.find(*interest3);
+  ims.find(*interest1);
+  ims.find(*interest3);
+  ims.find(*interest3);
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_CHECK(found2 == nullptr);
+
+  shared_ptr<const Data> found1 = ims.find(*interest1);
+  BOOST_CHECK_EQUAL(found1->getName(), name1);
+  shared_ptr<const Data> found3 = ims.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), name3);
+
+  Name name4("/insert/4");
+  shared_ptr<Data> data4 = makeData(name4);
+  ims.insert(*data4);
+
+  shared_ptr<Interest> interest4 = makeInterest(name4);
+  ims.find(*interest4);
+  ims.find(*interest1);
+  ims.find(*interest3);
+
+  ims.evictItem();
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<const Data> found4 = ims.find(*interest4);
+  BOOST_CHECK(found4 == nullptr);
+
+  found1 = ims.find(*interest1);
+  BOOST_CHECK_EQUAL(found1->getName(), name1);
+  found3 = ims.find(*interest3);
+  BOOST_CHECK_EQUAL(found3->getName(), name3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInMemoryStorageLru
+BOOST_AUTO_TEST_SUITE_END() // Ims
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/ims/in-memory-storage-persistent.t.cpp b/tests/unit/ims/in-memory-storage-persistent.t.cpp
new file mode 100644
index 0000000..d63061f
--- /dev/null
+++ b/tests/unit/ims/in-memory-storage-persistent.t.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "ims/in-memory-storage-persistent.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Ims)
+BOOST_AUTO_TEST_SUITE(TestInMemoryStoragePersistent)
+
+BOOST_AUTO_TEST_CASE(GetLimit)
+{
+  InMemoryStoragePersistent ims;
+
+  BOOST_CHECK_EQUAL(ims.getLimit(), -1);
+}
+
+BOOST_AUTO_TEST_CASE(InsertAndDouble)
+{
+  InMemoryStoragePersistent ims;
+  size_t initialCapacity = ims.getCapacity();
+
+  for (size_t i = 0; i < initialCapacity + 1; i++) {
+    shared_ptr<Data> data = makeData(to_string(i));
+    data->setFreshnessPeriod(5000_ms);
+    signData(data);
+    ims.insert(*data);
+  }
+
+  BOOST_CHECK_EQUAL(ims.size(), initialCapacity + 1);
+
+  BOOST_CHECK_EQUAL(ims.getCapacity(), initialCapacity * 2);
+}
+
+BOOST_AUTO_TEST_CASE(EraseAndShrink)
+{
+  InMemoryStoragePersistent ims;
+
+  auto capacity = ims.getCapacity() * 2;
+  ims.setCapacity(capacity);
+
+  Name name("/1");
+  shared_ptr<Data> data = makeData(name);
+  data->setFreshnessPeriod(5000_ms);
+  signData(data);
+  ims.insert(*data);
+  BOOST_CHECK_EQUAL(ims.size(), 1);
+
+  ims.erase(name);
+
+  BOOST_CHECK_EQUAL(ims.size(), 0);
+  BOOST_CHECK_EQUAL(ims.getCapacity(), capacity / 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInMemoryStoragePersistent
+BOOST_AUTO_TEST_SUITE_END() // Ims
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/ims/in-memory-storage.t.cpp b/tests/unit/ims/in-memory-storage.t.cpp
new file mode 100644
index 0000000..d955d72
--- /dev/null
+++ b/tests/unit/ims/in-memory-storage.t.cpp
@@ -0,0 +1,1058 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "ims/in-memory-storage.hpp"
+#include "ims/in-memory-storage-fifo.hpp"
+#include "ims/in-memory-storage-lfu.hpp"
+#include "ims/in-memory-storage-lru.hpp"
+#include "ims/in-memory-storage-persistent.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+#include "util/sha256.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+#include "../unit-test-time-fixture.hpp"
+
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Ims)
+BOOST_AUTO_TEST_SUITE(TestInMemoryStorage)
+
+using InMemoryStorages = boost::mpl::vector<InMemoryStoragePersistent,
+                                            InMemoryStorageFifo,
+                                            InMemoryStorageLfu,
+                                            InMemoryStorageLru>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Insertion, T, InMemoryStorages)
+{
+  T ims;
+
+  ims.insert(*makeData("/a"));
+  ims.insert(*makeData("/b"));
+  ims.insert(*makeData("/c"));
+  ims.insert(*makeData("/d"));
+
+  BOOST_CHECK_EQUAL(ims.size(), 4);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Insertion2, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/a");
+
+  uint32_t content1 = 1;
+  shared_ptr<Data> data1 = makeData(name);
+  data1->setFreshnessPeriod(99999_ms);
+  data1->setContent(reinterpret_cast<const uint8_t*>(&content1), sizeof(content1));
+  signData(data1);
+  ims.insert(*data1);
+
+  uint32_t content2 = 2;
+  shared_ptr<Data> data2 = makeData(name);
+  data2->setFreshnessPeriod(99999_ms);
+  data2->setContent(reinterpret_cast<const uint8_t*>(&content2), sizeof(content2));
+  signData(data2);
+  ims.insert(*data2);
+
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(DuplicateInsertion, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data0 = makeData("/insert/smth");
+  ims.insert(*data0);
+
+  shared_ptr<Data> data = makeData("/insert/duplicate");
+  ims.insert(*data);
+
+  ims.insert(*data);
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(DuplicateInsertion2, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/insert/duplicate");
+  ims.insert(*data);
+
+  ims.insert(*data);
+  BOOST_CHECK_EQUAL(ims.size(), 1);
+
+  shared_ptr<Data> data2 = makeData("/insert/original");
+  ims.insert(*data2);
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndFind, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insert/and/find");
+
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  shared_ptr<Interest> interest = makeInterest(name);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_CHECK(found != nullptr);
+  BOOST_CHECK_EQUAL(data->getName(), found->getName());
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndNotFind, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insert/and/find");
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  Name name2("/not/find");
+  shared_ptr<Interest> interest = makeInterest(name2);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+
+  BOOST_CHECK_EQUAL(found.get(), static_cast<const Data*>(0));
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndFindByName, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insert/and/find");
+
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  shared_ptr<const Data> found = ims.find(name);
+  BOOST_CHECK(found != nullptr);
+  BOOST_CHECK_EQUAL(data->getName(), found->getName());
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndFindByFullName, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insert/and/find");
+
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  shared_ptr<const Data> found = ims.find(data->getFullName());
+  BOOST_CHECK(found != nullptr);
+  BOOST_CHECK_EQUAL(data->getFullName(), found->getFullName());
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndNotFindByName, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insert/and/find");
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  Name name2("/not/find");
+
+  shared_ptr<const Data> found = ims.find(name2);
+  BOOST_CHECK(found == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndNotFindByFullName, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/a");
+  uint32_t content1 = 1;
+  shared_ptr<Data> data1 = makeData(name);
+  data1->setContent(reinterpret_cast<const uint8_t*>(&content1), sizeof(content1));
+  signData(data1);
+  ims.insert(*data1);
+
+  uint32_t content2 = 2;
+  shared_ptr<Data> data2 = makeData(name);
+  data2->setContent(reinterpret_cast<const uint8_t*>(&content2), sizeof(content2));
+  signData(data2);
+
+  shared_ptr<const Data> found = ims.find(data2->getFullName());
+  BOOST_CHECK(found == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndEraseByName, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insertandremovebyname");
+
+  uint32_t content1 = 1;
+  shared_ptr<Data> data1 = makeData(name);
+  data1->setFreshnessPeriod(99999_ms);
+  data1->setContent(reinterpret_cast<const uint8_t*>(&content1), sizeof(content1));
+  signData(data1);
+  ims.insert(*data1);
+
+  uint32_t content2 = 2;
+  shared_ptr<Data> data2 = makeData(name);
+  data2->setFreshnessPeriod(99999_ms);
+  data2->setContent(reinterpret_cast<const uint8_t*>(&content2), sizeof(content2));
+  signData(data2);
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/insertandremovebyname/1");
+  ims.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/insertandremovebyname/2");
+  ims.insert(*data4);
+
+  BOOST_CHECK_EQUAL(ims.size(), 4);
+
+  ims.erase(data1->getFullName(), false);
+  BOOST_CHECK_EQUAL(ims.size(), 3);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndEraseByPrefix, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  ims.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  ims.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  ims.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/1/2/3");
+  ims.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1");
+  ims.insert(*data7);
+
+  BOOST_CHECK_EQUAL(ims.size(), 7);
+
+  Name name("/c");
+  ims.erase(name);
+  BOOST_CHECK_EQUAL(ims.size(), 3);
+  BOOST_CHECK_EQUAL(ims.getCapacity(), 16);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(DigestCalculation, T, InMemoryStorages)
+{
+  shared_ptr<Data> data = makeData("/digest/compute");
+
+  ConstBufferPtr digest1 = util::Sha256::computeDigest(data->wireEncode().wire(), data->wireEncode().size());
+  BOOST_CHECK_EQUAL(digest1->size(), 32);
+
+  InMemoryStorageEntry entry;
+  entry.setData(*data);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(digest1->begin(), digest1->end(),
+                                entry.getFullName()[-1].value_begin(),
+                                entry.getFullName()[-1].value_end());
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Iterator, T, InMemoryStorages)
+{
+  T ims;
+
+  BOOST_CONCEPT_ASSERT((boost::InputIterator<InMemoryStorage::const_iterator>));
+
+  for (int i = 0; i < 10; i++) {
+    std::ostringstream convert;
+    convert << i;
+    Name name("/" + convert.str());
+    shared_ptr<Data> data = makeData(name);
+    ims.insert(*data);
+  }
+
+  InMemoryStorage::const_iterator it = ims.begin();
+  InMemoryStorage::const_iterator tmp1 = it;
+  BOOST_REQUIRE(tmp1 == it);
+  InMemoryStorage::const_iterator tmp2 = tmp1++;
+  BOOST_REQUIRE(tmp2 != tmp1);
+  tmp2 = ++tmp1;
+  BOOST_REQUIRE(tmp2 == tmp1);
+
+  int i = 0;
+  for (; it != ims.end(); it++) {
+    std::ostringstream convert;
+    convert << i;
+    Name name("/" + convert.str());
+    BOOST_CHECK_EQUAL(it->getName(), name);
+    BOOST_CHECK_EQUAL((*it).getName(), name);
+    i++;
+  }
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertCanonical, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  ims.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  ims.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  ims.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/1/2/3");
+  ims.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1");
+  ims.insert(*data7);
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(EraseCanonical, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c");
+  ims.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  ims.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  ims.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/1/2/3");
+  ims.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1");
+  ims.insert(*data7);
+
+  ConstBufferPtr digest1 = util::Sha256::computeDigest(data->wireEncode().wire(), data->wireEncode().size());
+
+  Name name("/a");
+  ims.erase(name);
+  BOOST_CHECK_EQUAL(ims.size(), 6);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(ImplicitDigestSelector, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/digest/works");
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/a");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/z/z/z");
+  ims.insert(*data3);
+
+  ConstBufferPtr digest1 = util::Sha256::computeDigest(data->wireEncode().wire(), data->wireEncode().size());
+
+  shared_ptr<Interest> interest = makeInterest("");
+  interest->setName(Name(name).appendImplicitSha256Digest(digest1->data(), digest1->size()));
+  interest->setMinSuffixComponents(0);
+  interest->setMaxSuffixComponents(0);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_REQUIRE(found != nullptr);
+  BOOST_CHECK_EQUAL(found->getName(), name);
+
+  shared_ptr<Interest> interest2 = makeInterest("");
+  uint8_t digest2[32] = {0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1};
+  interest2->setName(Name(name).appendImplicitSha256Digest(digest2, 32));
+  interest2->setMinSuffixComponents(0);
+  interest2->setMaxSuffixComponents(0);
+
+  shared_ptr<const Data> notfound = ims.find(*interest2);
+  BOOST_CHECK(notfound == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(ChildSelector, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  ims.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c");
+  ims.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/f");
+  ims.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/n");
+  ims.insert(*data7);
+
+  shared_ptr<Interest> interest = makeInterest("/c", true);
+  interest->setChildSelector(1);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_REQUIRE(found != nullptr);
+  BOOST_CHECK_EQUAL(found->getName(), "/c/n");
+
+  shared_ptr<Interest> interest2 = makeInterest("/c", true);
+  interest2->setChildSelector(0);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_REQUIRE(found2 != nullptr);
+  BOOST_CHECK_EQUAL(found2->getName(), "/c/c");
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(ChildSelector2, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a/b/1");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/a/b/2");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/a/z/1");
+  ims.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/a/z/2");
+  ims.insert(*data4);
+
+  shared_ptr<Interest> interest = makeInterest("/a", true);
+  interest->setChildSelector(1);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_REQUIRE(found != nullptr);
+  BOOST_CHECK_EQUAL(found->getName(), "/a/z/1");
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(PublisherKeySelector, T, InMemoryStorages)
+{
+  T ims;
+
+  Name name("/insert/withkey");
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  shared_ptr<Interest> interest = makeInterest(name);
+  Name keyName("/somewhere/key");
+
+  KeyLocator locator(keyName);
+  interest->setPublisherPublicKeyLocator(locator);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_CHECK(found == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(PublisherKeySelector2, T, InMemoryStorages)
+{
+  T ims;
+  Name name("/insert/withkey");
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  Name name2("/insert/withkey2");
+  shared_ptr<Data> data2 = make_shared<Data>(name2);
+
+  Name keyName("/somewhere/key");
+  const KeyLocator locator(keyName);
+
+  SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(makeEmptyBlock(tlv::SignatureValue));
+  fakeSignature.setKeyLocator(locator);
+  data2->setSignature(fakeSignature);
+  data2->wireEncode();
+
+  ims.insert(*data2);
+
+  shared_ptr<Interest> interest = makeInterest(name2);
+  interest->setPublisherPublicKeyLocator(locator);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_CHECK(found != nullptr);
+  BOOST_CHECK_EQUAL(found->getName(), data2->getName());
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(MinMaxComponentsSelector, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  ims.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c/1/2/3/4/5/6");
+  ims.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/c/6/7/8/9");
+  ims.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/c/1/2/3");
+  ims.insert(*data7);
+
+  shared_ptr<Data> data8 = makeData("/c/c/1");
+  ims.insert(*data8);
+
+  shared_ptr<Interest> interest = makeInterest("/c/c", true);
+  interest->setMinSuffixComponents(3);
+  interest->setChildSelector(0);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_REQUIRE(found != nullptr);
+  BOOST_CHECK_EQUAL(found->getName(), "/c/c/1/2/3");
+
+  shared_ptr<Interest> interest2 = makeInterest("/c/c", true);
+  interest2->setMinSuffixComponents(4);
+  interest2->setChildSelector(1);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_REQUIRE(found2 != nullptr);
+  BOOST_CHECK_EQUAL(found2->getName(), "/c/c/6/7/8/9");
+
+  shared_ptr<Interest> interest3 = makeInterest("/c/c");
+  interest3->setMaxSuffixComponents(2);
+  interest3->setChildSelector(1);
+
+  shared_ptr<const Data> found3 = ims.find(*interest3);
+  BOOST_REQUIRE(found3 != nullptr);
+  BOOST_CHECK_EQUAL(found3->getName(), "/c/c/1");
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(ExcludeSelector, T, InMemoryStorages)
+{
+  T ims;
+
+  shared_ptr<Data> data = makeData("/a");
+  ims.insert(*data);
+
+  shared_ptr<Data> data2 = makeData("/b");
+  ims.insert(*data2);
+
+  shared_ptr<Data> data3 = makeData("/c/a");
+  ims.insert(*data3);
+
+  shared_ptr<Data> data4 = makeData("/d");
+  ims.insert(*data4);
+
+  shared_ptr<Data> data5 = makeData("/c/c");
+  ims.insert(*data5);
+
+  shared_ptr<Data> data6 = makeData("/c/f");
+  ims.insert(*data6);
+
+  shared_ptr<Data> data7 = makeData("/c/n");
+  ims.insert(*data7);
+
+  shared_ptr<Interest> interest = makeInterest("/c", true);
+  interest->setChildSelector(1);
+  Exclude e;
+  e.excludeOne(Name::Component("n"));
+  interest->setExclude(e);
+
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_REQUIRE(found != nullptr);
+  BOOST_CHECK_EQUAL(found->getName(), "/c/f");
+
+  shared_ptr<Interest> interest2 = makeInterest("/c", true);
+  interest2->setChildSelector(0);
+  Exclude e2;
+  e2.excludeOne(Name::Component("a"));
+  interest2->setExclude(e2);
+
+  shared_ptr<const Data> found2 = ims.find(*interest2);
+  BOOST_REQUIRE(found2 != nullptr);
+  BOOST_CHECK_EQUAL(found2->getName(), "/c/c");
+
+  shared_ptr<Interest> interest3 = makeInterest("/c", true);
+  interest3->setChildSelector(0);
+  Exclude e3;
+  e3.excludeOne(Name::Component("c"));
+  interest3->setExclude(e3);
+
+  shared_ptr<const Data> found3 = ims.find(*interest3);
+  BOOST_REQUIRE(found3 != nullptr);
+  BOOST_CHECK_EQUAL(found3->getName(), "/c/a");
+}
+
+using InMemoryStoragesLimited = boost::mpl::vector<InMemoryStorageFifo,
+                                                   InMemoryStorageLfu,
+                                                   InMemoryStorageLru>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(SetCapacity, T, InMemoryStoragesLimited)
+{
+  T ims;
+
+  ims.setCapacity(18);
+  for (int i = 1; i < 19; ++i) {
+    ims.insert(*makeData(to_string(i)));
+  }
+  BOOST_CHECK_EQUAL(ims.size(), 18);
+
+  ims.setCapacity(16);
+  BOOST_CHECK_EQUAL(ims.size(), 16);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(GetLimit, T, InMemoryStoragesLimited)
+{
+  T ims(10000);
+  BOOST_CHECK_EQUAL(ims.getLimit(), 10000);
+
+  T ims2(4);
+  BOOST_CHECK_EQUAL(ims2.getLimit(), 4);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndDouble, T, InMemoryStoragesLimited)
+{
+  T ims(40);
+  size_t initialCapacity = ims.getCapacity();
+
+  for (size_t i = 0; i < initialCapacity + 1; i++) {
+    shared_ptr<Data> data = makeData(to_string(i));
+    data->setFreshnessPeriod(5000_ms);
+    signData(data);
+    ims.insert(*data);
+  }
+
+  BOOST_CHECK_EQUAL(ims.size(), initialCapacity + 1);
+  BOOST_CHECK_EQUAL(ims.getCapacity(), initialCapacity * 2);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(InsertAndEvict, T, InMemoryStoragesLimited)
+{
+  T ims(2);
+
+  Name name("/insert/1");
+  shared_ptr<Data> data = makeData(name);
+  ims.insert(*data);
+
+  Name name2("/insert/2");
+  shared_ptr<Data> data2 = makeData(name2);
+  ims.insert(*data2);
+
+  Name name3("/insert/3");
+  shared_ptr<Data> data3 = makeData(name3);
+  ims.insert(*data3);
+
+  BOOST_CHECK_EQUAL(ims.size(), 2);
+
+  shared_ptr<Interest> interest = makeInterest(name);
+  shared_ptr<const Data> found = ims.find(*interest);
+  BOOST_CHECK(found == nullptr);
+}
+
+// Find function is implemented at the base case, so it's sufficient to test for one derived class.
+class FindFixture : public tests::UnitTestTimeFixture
+{
+protected:
+  FindFixture()
+    : m_ims(io)
+  {
+  }
+
+  Name
+  insert(uint32_t id, const Name& name,
+         const time::milliseconds& freshWindow = InMemoryStorage::INFINITE_WINDOW)
+  {
+    shared_ptr<Data> data = makeData(name);
+    data->setFreshnessPeriod(99999_ms);
+    data->setContent(reinterpret_cast<const uint8_t*>(&id), sizeof(id));
+    signData(data);
+
+    m_ims.insert(*data, freshWindow);
+
+    return data->getFullName();
+  }
+
+  Interest&
+  startInterest(const Name& name)
+  {
+    m_interest = makeInterest(name, true);
+    return *m_interest;
+  }
+
+  uint32_t
+  find()
+  {
+    shared_ptr<const Data> found = m_ims.find(*m_interest);
+    if (found == 0) {
+      return 0;
+    }
+    const Block& content = found->getContent();
+    if (content.value_size() != sizeof(uint32_t)) {
+      return 0;
+    }
+    uint32_t id = 0;
+    std::memcpy(&id, content.value(), sizeof(id));
+    return id;
+  }
+
+protected:
+  InMemoryStoragePersistent m_ims;
+  shared_ptr<Interest> m_interest;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Find, FindFixture)
+
+BOOST_AUTO_TEST_CASE(EmptyDataName)
+{
+  insert(1, "ndn:/");
+
+  startInterest("ndn:/");
+  BOOST_CHECK_EQUAL(find(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInterestName)
+{
+  insert(1, "ndn:/A");
+
+  startInterest("ndn:/");
+  BOOST_CHECK_EQUAL(find(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ExactName)
+{
+  insert(1, "ndn:/");
+  insert(2, "ndn:/A");
+  insert(3, "ndn:/A/B");
+  insert(4, "ndn:/A/C");
+  insert(5, "ndn:/D");
+
+  startInterest("ndn:/A");
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(FullName)
+{
+  Name n1 = insert(1, "ndn:/A");
+  Name n2 = insert(2, "ndn:/A");
+
+  startInterest(n1);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest(n2);
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Leftmost)
+{
+  insert(1, "ndn:/A");
+  insert(2, "ndn:/B/p/1");
+  insert(3, "ndn:/B/p/2");
+  insert(4, "ndn:/B/q/1");
+  insert(5, "ndn:/B/q/2");
+  insert(6, "ndn:/C");
+
+  startInterest("ndn:/B");
+  BOOST_CHECK_EQUAL(find(), 2);
+}
+
+BOOST_AUTO_TEST_CASE(Rightmost)
+{
+  insert(1, "ndn:/A");
+  insert(2, "ndn:/B/p/1");
+  insert(3, "ndn:/B/p/2");
+  insert(4, "ndn:/B/q/1");
+  insert(5, "ndn:/B/q/2");
+  insert(6, "ndn:/C");
+
+  startInterest("ndn:/B")
+    .setChildSelector(1);
+  BOOST_CHECK_EQUAL(find(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(MinSuffixComponents)
+{
+  insert(1, "ndn:/");
+  insert(2, "ndn:/A");
+  insert(3, "ndn:/B/1");
+  insert(4, "ndn:/C/1/2");
+  insert(5, "ndn:/D/1/2/3");
+  insert(6, "ndn:/E/1/2/3/4");
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(0);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(1);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(2);
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(3);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(4);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(5);
+  BOOST_CHECK_EQUAL(find(), 5);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(6);
+  BOOST_CHECK_EQUAL(find(), 6);
+
+  startInterest("ndn:/")
+    .setMinSuffixComponents(7);
+  BOOST_CHECK_EQUAL(find(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(MaxSuffixComponents)
+{
+  insert(1, "ndn:/");
+  insert(2, "ndn:/A");
+  insert(3, "ndn:/B/2");
+  insert(4, "ndn:/C/2/3");
+  insert(5, "ndn:/D/2/3/4");
+  insert(6, "ndn:/E/2/3/4/5");
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(0);
+  BOOST_CHECK_EQUAL(find(), 0);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(1);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(2);
+  BOOST_CHECK_EQUAL(find(), 2);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(3);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(4);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(5);
+  BOOST_CHECK_EQUAL(find(), 5);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(6);
+  BOOST_CHECK_EQUAL(find(), 6);
+
+  startInterest("ndn:/")
+    .setChildSelector(1)
+    .setMaxSuffixComponents(7);
+  BOOST_CHECK_EQUAL(find(), 6);
+}
+
+BOOST_AUTO_TEST_CASE(DigestOrder)
+{
+  insert(1, "ndn:/A");
+  insert(2, "ndn:/A");
+  // We don't know which comes first, but there must be some order
+
+  startInterest("ndn:/A")
+    .setChildSelector(0);
+  uint32_t leftmost = find();
+
+  startInterest("ndn:/A")
+    .setChildSelector(1);
+  uint32_t rightmost = find();
+
+  BOOST_CHECK_NE(leftmost, rightmost);
+}
+
+BOOST_AUTO_TEST_CASE(DigestExclude)
+{
+  insert(1, "ndn:/A");
+  Name n2 = insert(2, "ndn:/A");
+  insert(3, "ndn:/A/B");
+
+  uint8_t digest00[util::Sha256::DIGEST_SIZE];
+  std::fill_n(digest00, sizeof(digest00), 0x00);
+  uint8_t digestFF[util::Sha256::DIGEST_SIZE];
+  std::fill_n(digestFF, sizeof(digestFF), 0xFF);
+
+  Exclude excludeDigest;
+  excludeDigest.excludeRange(
+    name::Component::fromImplicitSha256Digest(digest00, sizeof(digest00)),
+    name::Component::fromImplicitSha256Digest(digestFF, sizeof(digestFF)));
+
+  startInterest("ndn:/A")
+    .setChildSelector(0)
+    .setExclude(excludeDigest);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  startInterest("ndn:/A")
+    .setChildSelector(1)
+    .setExclude(excludeDigest);
+  BOOST_CHECK_EQUAL(find(), 3);
+
+  Exclude excludeGeneric;
+  excludeGeneric.excludeAfter(name::Component(static_cast<uint8_t*>(nullptr), 0));
+
+  startInterest("ndn:/A")
+    .setChildSelector(0)
+    .setExclude(excludeGeneric);
+  int found1 = find();
+  BOOST_CHECK(found1 == 1 || found1 == 2);
+
+  startInterest("ndn:/A")
+    .setChildSelector(1)
+    .setExclude(excludeGeneric);
+  int found2 = find();
+  BOOST_CHECK(found2 == 1 || found2 == 2);
+
+  Exclude exclude2 = excludeGeneric;
+  exclude2.excludeOne(n2.get(-1));
+
+  startInterest("ndn:/A")
+    .setChildSelector(0)
+    .setExclude(exclude2);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/A")
+    .setChildSelector(1)
+    .setExclude(exclude2);
+  BOOST_CHECK_EQUAL(find(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(MustBeFresh)
+{
+  Name data1Name = insert(1, "ndn:/A/1", 500_ms);
+  insert(2, "ndn:/A/2", 2500_ms);
+  insert(3, "ndn:/A/3", 3500_ms);
+  insert(4, "ndn:/A/4", 1500_ms);
+
+  // @0s, all Data are fresh
+  startInterest("ndn:/A/1")
+    .setMustBeFresh(true);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/A/1")
+    .setMustBeFresh(false);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/A")
+    .setMustBeFresh(true)
+    .setChildSelector(0);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/A")
+    .setMustBeFresh(true)
+    .setChildSelector(1);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  advanceClocks(1000_ms);
+  // @1s, /A/1 is stale
+  startInterest("ndn:/A/1")
+    .setMustBeFresh(true);
+  BOOST_CHECK_EQUAL(find(), 0);
+  startInterest("ndn:/A/1")
+    .setMustBeFresh(false);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  // MustBeFresh is ignored when full Name is specified
+  startInterest(data1Name)
+    .setMustBeFresh(true);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  startInterest("ndn:/A")
+    .setMustBeFresh(true)
+    .setChildSelector(0);
+  BOOST_CHECK_EQUAL(find(), 2);
+  startInterest("ndn:/A")
+    .setMustBeFresh(false)
+    .setChildSelector(0);
+  BOOST_CHECK_EQUAL(find(), 1);
+
+  advanceClocks(1000_ms);
+  // @2s, /A/1 and /A/4 are stale
+  startInterest("ndn:/A")
+    .setMustBeFresh(true)
+    .setChildSelector(1);
+  BOOST_CHECK_EQUAL(find(), 3);
+  startInterest("ndn:/A")
+    .setMustBeFresh(false)
+    .setChildSelector(1);
+  BOOST_CHECK_EQUAL(find(), 4);
+
+  advanceClocks(2000_ms);
+  // @4s, all Data are stale
+  startInterest("ndn:/A")
+    .setMustBeFresh(true)
+    .setChildSelector(0);
+  BOOST_CHECK_EQUAL(find(), 0);
+  startInterest("ndn:/A")
+    .setMustBeFresh(true)
+    .setChildSelector(1);
+  BOOST_CHECK_EQUAL(find(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Find
+BOOST_AUTO_TEST_SUITE_END() // TestInMemoryStorage
+BOOST_AUTO_TEST_SUITE_END() // Ims
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/interest-filter.t.cpp b/tests/unit/interest-filter.t.cpp
new file mode 100644
index 0000000..96f4ae9
--- /dev/null
+++ b/tests/unit/interest-filter.t.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "interest-filter.hpp"
+#include "data.hpp"
+#include "encoding/buffer-stream.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+#include "security/digest-sha256.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestInterestFilter)
+
+BOOST_AUTO_TEST_CASE(Matching)
+{
+  BOOST_CHECK_EQUAL(InterestFilter("/a").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b/c").doesMatch("/a/b"), false);
+
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b>").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b", "<b>").doesMatch("/a/b"), false);
+
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b", "<b>").doesMatch("/a/b/c/b"), false);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b", "<>*<b>").doesMatch("/a/b/c/b"), true);
+
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b>").doesMatch("/a/b/c/d"), false);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>*").doesMatch("/a/b/c/d"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>*").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>+").doesMatch("/a/b"), false);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>+").doesMatch("/a/b/c"), true);
+}
+
+BOOST_AUTO_TEST_CASE(RegexConvertToName)
+{
+  util::DummyClientFace face;
+  face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
+    [] (const Name&, const Interest&) { BOOST_ERROR("unexpected Interest"); });
+  face.processEvents(1_ms);
+  BOOST_CHECK_THROW(face.receive(*makeInterest("/Hello/World/a/b/c")), InterestFilter::Error);
+}
+
+BOOST_AUTO_TEST_CASE(AllowLoopback)
+{
+  InterestFilter filter("/A");
+  BOOST_CHECK_EQUAL(filter.allowsLoopback(), true);
+  BOOST_CHECK_EQUAL(&filter.allowLoopback(false), &filter);
+  BOOST_CHECK_EQUAL(filter.allowsLoopback(), false);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInterestFilter
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/interest.t.cpp b/tests/unit/interest.t.cpp
new file mode 100644
index 0000000..16e2ba2
--- /dev/null
+++ b/tests/unit/interest.t.cpp
@@ -0,0 +1,664 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "interest.hpp"
+#include "data.hpp"
+#include "security/digest-sha256.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestInterest)
+
+// ---- constructor, encode, decode ----
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+  Interest i;
+  BOOST_CHECK(!i.hasWire());
+  BOOST_CHECK_EQUAL(i.getName(), "/");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  BOOST_CHECK(i.getForwardingHint().empty());
+  BOOST_CHECK(!i.hasNonce());
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(!i.hasSelectors());
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDecode02Basic)
+{
+  const uint8_t WIRE[] = {
+    0x05, 0x1c, // Interest
+          0x07, 0x14, // Name
+                0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
+                0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
+                0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+          0x0a, 0x04, // Nonce
+                0x01, 0x00, 0x00, 0x00
+  };
+
+  Interest i1("/local/ndn/prefix");
+  i1.setCanBePrefix(true);
+  i1.setNonce(1);
+  Block wire1 = i1.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+  Interest i2(wire1);
+  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+  BOOST_CHECK(i2.getSelectors().empty());
+  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+
+  BOOST_CHECK_EQUAL(i1, i2);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDecode02Full)
+{
+  const uint8_t WIRE[] = {
+    0x05, 0x31, // Interest
+          0x07, 0x14, // Name
+                0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
+                0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
+                0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+          0x09, 0x03, // Selectors
+                0x0d, 0x01, 0x01,  // MinSuffixComponents
+          0x0a, 0x04, // Nonce
+                0x01, 0x00, 0x00, 0x00,
+          0x0c, 0x02, // InterestLifetime
+                0x03, 0xe8,
+          0x1e, 0x0a, // ForwardingHint
+                0x1f, 0x08, // Delegation
+                      0x1e, 0x01, 0x01, // Preference=1
+                      0x07, 0x03, 0x08, 0x01, 0x41 // Name=/A
+  };
+
+  Interest i1;
+  i1.setName("/local/ndn/prefix");
+  i1.setCanBePrefix(true);
+  i1.setMinSuffixComponents(1);
+  i1.setNonce(1);
+  i1.setInterestLifetime(1000_ms);
+  i1.setForwardingHint({{1, "/A"}});
+  Block wire1 = i1.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+  Interest i2(wire1);
+  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+  BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), 1);
+  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 1000_ms);
+  BOOST_CHECK_EQUAL(i2.getForwardingHint(), DelegationList({{1, "/A"}}));
+
+  BOOST_CHECK_EQUAL(i1, i2);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDecode03Basic)
+{
+  const uint8_t WIRE[] = {
+    0x05, 0x22, // Interest
+          0x07, 0x14, // Name
+                0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
+                0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
+                0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+          0x0a, 0x04, // Nonce
+                0x01, 0x00, 0x00, 0x00,
+          0x23, 0x04, // Parameters
+                0xc0, 0xc1, 0xc2, 0xc3};
+
+  Interest i1;
+  i1.setName("/local/ndn/prefix");
+  i1.setCanBePrefix(false);
+  i1.setNonce(1);
+  i1.setParameters("2304C0C1C2C3"_block);
+  Block wire1 = i1.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+  Interest i2(wire1);
+  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+  BOOST_CHECK_EQUAL(i2.getCanBePrefix(), false);
+  BOOST_CHECK_EQUAL(i2.getMustBeFresh(), false);
+  BOOST_CHECK(i2.getForwardingHint().empty());
+  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i2.hasParameters());
+  BOOST_CHECK_EQUAL(i2.getParameters(), "2304C0C1C2C3"_block);
+  BOOST_CHECK(i2.getPublisherPublicKeyLocator().empty());
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDecode03Full)
+{
+  const uint8_t WIRE[] = {
+    0x05, 0x37, // Interest
+          0x07, 0x14, // Name
+                0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
+                0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
+                0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+          0x21, 0x00, // CanBePrefix
+          0x12, 0x00, // MustBeFresh
+          0x1e, 0x0b, // ForwardingHint
+                0x1f, 0x09, // Delegation List
+                      0x1e, 0x02,
+                            0x3e, 0x15,
+                      0x07, 0x03,
+                            0x08, 0x01, 0x48,
+          0x0a, 0x04, // Nonce
+                0x4a, 0xcb, 0x1e, 0x4c,
+          0x0c, 0x02, // Interest Lifetime
+                0x76, 0xa1,
+          0x23, 0x04, // Parameters
+                0xc0, 0xc1, 0xc2, 0xc3};
+  Interest i1;
+  i1.setName("/local/ndn/prefix");
+  i1.setMustBeFresh(true);
+  i1.setCanBePrefix(true);
+  i1.setForwardingHint(DelegationList({{15893, "/H"}}));
+  i1.setNonce(0x4c1ecb4a);
+  i1.setInterestLifetime(30369_ms);
+  i1.setParameters("2304C0C1C2C3"_block);
+  i1.setMinSuffixComponents(1); // v0.2-only elements will not be encoded
+  i1.setExclude(Exclude().excludeAfter(name::Component("J"))); // v0.2-only elements will not be encoded
+  Block wire1 = i1.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+  Interest i2(wire1);
+  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+  BOOST_CHECK_EQUAL(i2.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i2.getMustBeFresh(), true);
+  BOOST_CHECK_EQUAL(i2.getForwardingHint(), DelegationList({{15893, "/H"}}));
+  BOOST_CHECK(i2.hasNonce());
+  BOOST_CHECK_EQUAL(i2.getNonce(), 0x4c1ecb4a);
+  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 30369_ms);
+  BOOST_CHECK_EQUAL(i2.getParameters(), "2304C0C1C2C3"_block);
+  BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), -1); // Default because minSuffixComponents was not encoded
+  BOOST_CHECK(i2.getExclude().empty()); // Exclude was not encoded
+}
+
+class Decode03Fixture
+{
+protected:
+  Decode03Fixture()
+  {
+    // initialize all elements to non-empty, to verify wireDecode clears them
+    i.setName("/A");
+    i.setForwardingHint({{10309, "/F"}});
+    i.setNonce(0x03d645a8);
+    i.setInterestLifetime(18554_ms);
+    i.setPublisherPublicKeyLocator(Name("/K"));
+    i.setParameters("2304A0A1A2A3"_block);
+  }
+
+protected:
+  Interest i;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Decode03, Decode03Fixture)
+
+BOOST_AUTO_TEST_CASE(Minimal)
+{
+  i.wireDecode("0505 0703080149"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), false);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  BOOST_CHECK(i.getForwardingHint().empty());
+  BOOST_CHECK(i.hasNonce()); // a random nonce is generated
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i.getPublisherPublicKeyLocator().empty());
+  BOOST_CHECK(!i.hasParameters());
+
+  BOOST_CHECK(!i.hasWire()); // nonce generation resets wire encoding
+
+  // modify then re-encode as v0.2 format
+  i.setNonce(0x54657c95);
+  BOOST_CHECK_EQUAL(i.wireEncode(), "0510 0703080149 09030E0101 0A04957C6554"_block);
+}
+
+BOOST_AUTO_TEST_CASE(Full)
+{
+  i.wireDecode("0531 0703080149 FC00 2100 FC00 1200 "
+               "FC00 1E0B(1F09 1E023E15 0703080148) FC00 0A044ACB1E4C "
+               "FC00 0C0276A1 FC00 2201D6 FC00"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), true);
+  BOOST_CHECK_EQUAL(i.getForwardingHint(), DelegationList({{15893, "/H"}}));
+  BOOST_CHECK(i.hasNonce());
+  BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
+  // HopLimit=214 is not stored
+
+  // encode without modification: retain original wire encoding
+  BOOST_CHECK_EQUAL(i.wireEncode().value_size(), 49);
+
+  // modify then re-encode as v0.2 format
+  i.setName("/J");
+  BOOST_CHECK_EQUAL(i.wireEncode(),
+    "0520 070308014A 09021200 0A044ACB1E4C 0C0276A1 1E0B(1F09 1E023E15 0703080148)"_block);
+}
+
+BOOST_AUTO_TEST_CASE(CriticalElementOutOfOrder)
+{
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 2100 0703080149 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 1200 2100 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1E0B(1F09 1E023E15 0703080148) 1200 "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1200 0A044ACB1E4C "
+    "1E0B(1F09 1E023E15 0703080148) 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0C0276A1 0A044ACB1E4C 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 2201D6 0C0276A1 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "052F 0703080149 2100 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3 2304C0C1C2C3"_block),
+    tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(HopLimitOutOfOrder)
+{
+  // HopLimit is non-critical, its out-of-order appearances are ignored
+  i.wireDecode("0514 0703080149 2201D6 2200 2304C0C1C2C3 22020101"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  // HopLimit=214 is not stored
+  BOOST_CHECK_EQUAL(i.getParameters(), "2304C0C1C2C3"_block);
+}
+
+BOOST_AUTO_TEST_CASE(NameMissing)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0500"_block), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode("0502 1200"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NameEmpty)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0502 0700"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadCanBePrefix)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0508 0703080149 210102"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadMustBeFresh)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0508 0703080149 120102"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadNonce)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0507 0703080149 0A00"_block), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode("050A 0703080149 0A0304C263"_block), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode("050C 0703080149 0A05EFA420B262"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnrecognizedNonCriticalElementBeforeName)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0507 FC00 0703080149"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnrecognizedCriticalElement)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0507 0703080149 FB00"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Decode03
+
+// ---- matching ----
+
+BOOST_AUTO_TEST_CASE(MatchesData)
+{
+  Interest interest;
+  interest.setName("ndn:/A")
+          .setMinSuffixComponents(2)
+          .setMaxSuffixComponents(2)
+          .setPublisherPublicKeyLocator(KeyLocator("ndn:/B"))
+          .setExclude(Exclude().excludeAfter(name::Component("J")));
+
+  Data data("ndn:/A/D");
+  SignatureSha256WithRsa signature(KeyLocator("ndn:/B"));
+  signature.setValue(encoding::makeEmptyBlock(tlv::SignatureValue));
+  data.setSignature(signature);
+  data.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data), true);
+
+  Data data1 = data;
+  data1.setName("ndn:/A"); // violates MinSuffixComponents
+  data1.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data1), false);
+
+  Interest interest1 = interest;
+  interest1.setMinSuffixComponents(1);
+  BOOST_CHECK_EQUAL(interest1.matchesData(data1), true);
+
+  Data data2 = data;
+  data2.setName("ndn:/A/E/F"); // violates MaxSuffixComponents
+  data2.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data2), false);
+
+  Interest interest2 = interest;
+  interest2.setMaxSuffixComponents(3);
+  BOOST_CHECK_EQUAL(interest2.matchesData(data2), true);
+
+  Data data3 = data;
+  SignatureSha256WithRsa signature3(KeyLocator("ndn:/G")); // violates PublisherPublicKeyLocator
+  signature3.setValue(encoding::makeEmptyBlock(tlv::SignatureValue));
+  data3.setSignature(signature3);
+  data3.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data3), false);
+
+  Interest interest3 = interest;
+  interest3.setPublisherPublicKeyLocator(KeyLocator("ndn:/G"));
+  BOOST_CHECK_EQUAL(interest3.matchesData(data3), true);
+
+  Data data4 = data;
+  DigestSha256 signature4; // violates PublisherPublicKeyLocator
+  signature4.setValue(encoding::makeEmptyBlock(tlv::SignatureValue));
+  data4.setSignature(signature4);
+  data4.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data4), false);
+
+  Interest interest4 = interest;
+  interest4.setPublisherPublicKeyLocator(KeyLocator());
+  BOOST_CHECK_EQUAL(interest4.matchesData(data4), true);
+
+  Data data5 = data;
+  data5.setName("ndn:/A/J"); // violates Exclude
+  data5.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data5), false);
+
+  Interest interest5 = interest;
+  interest5.setExclude(Exclude().excludeAfter(name::Component("K")));
+  BOOST_CHECK_EQUAL(interest5.matchesData(data5), true);
+
+  Data data6 = data;
+  data6.setName("ndn:/H/I"); // violates Name
+  data6.wireEncode();
+  BOOST_CHECK_EQUAL(interest.matchesData(data6), false);
+
+  Data data7 = data;
+  data7.setName("ndn:/A/B");
+  data7.wireEncode();
+
+  Interest interest7("/A/B/sha256digest=54008e240a7eea2714a161dfddf0dd6ced223b3856e9da96792151e180f3b128");
+  BOOST_CHECK_EQUAL(interest7.matchesData(data7), true);
+
+  Interest interest7b("/A/B/sha256digest=0000000000000000000000000000000000000000000000000000000000000000");
+  BOOST_CHECK_EQUAL(interest7b.matchesData(data7), false); // violates implicit digest
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(MatchesInterest, 1)
+BOOST_AUTO_TEST_CASE(MatchesInterest)
+{
+  Interest interest;
+  interest
+    .setName("/A")
+    .setMinSuffixComponents(2)
+    .setMaxSuffixComponents(2)
+    .setPublisherPublicKeyLocator(KeyLocator("/B"))
+    .setExclude(Exclude().excludeAfter(name::Component("J")))
+    .setNonce(10)
+    .setInterestLifetime(5_s)
+    .setForwardingHint({{1, "/H"}});
+
+  Interest other;
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), false);
+
+  other.setName(interest.getName());
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), false);
+
+  other.setSelectors(interest.getSelectors());
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), false); // will match until #3162 implemented
+
+  other.setForwardingHint({{1, "/H"}});
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+
+  other.setNonce(200);
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+
+  other.setInterestLifetime(5_h);
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+}
+
+// ---- field accessors ----
+
+BOOST_AUTO_TEST_CASE(CanBePrefix)
+{
+  Interest i;
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  i.setCanBePrefix(false);
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), false);
+  BOOST_CHECK_EQUAL(i.getSelectors().getMaxSuffixComponents(), 1);
+  i.setCanBePrefix(true);
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i.getSelectors().getMaxSuffixComponents(), -1);
+}
+
+BOOST_AUTO_TEST_CASE(MustBeFresh)
+{
+  Interest i;
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  i.setMustBeFresh(true);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), true);
+  BOOST_CHECK_EQUAL(i.getSelectors().getMustBeFresh(), true);
+  i.setMustBeFresh(false);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  BOOST_CHECK_EQUAL(i.getSelectors().getMustBeFresh(), false);
+}
+
+BOOST_AUTO_TEST_CASE(ModifyForwardingHint)
+{
+  Interest i;
+  i.setCanBePrefix(false);
+  i.setForwardingHint({{1, "/A"}});
+  i.wireEncode();
+  BOOST_CHECK(i.hasWire());
+
+  i.modifyForwardingHint([] (DelegationList& fh) { fh.insert(2, "/B"); });
+  BOOST_CHECK(!i.hasWire());
+  BOOST_CHECK_EQUAL(i.getForwardingHint(), DelegationList({{1, "/A"}, {2, "/B"}}));
+}
+
+BOOST_AUTO_TEST_CASE(GetNonce)
+{
+  unique_ptr<Interest> i1, i2;
+
+  // getNonce automatically assigns a random Nonce.
+  // It's possible to assign the same Nonce to two Interest, but it's unlikely to get 100 pairs of
+  // same Nonces in a row.
+  int nIterations = 0;
+  uint32_t nonce1 = 0, nonce2 = 0;
+  do {
+    i1 = make_unique<Interest>();
+    nonce1 = i1->getNonce();
+    i2 = make_unique<Interest>();
+    nonce2 = i2->getNonce();
+  }
+  while (nonce1 == nonce2 && ++nIterations < 100);
+  BOOST_CHECK_NE(nonce1, nonce2);
+  BOOST_CHECK(i1->hasNonce());
+  BOOST_CHECK(i2->hasNonce());
+
+  // Once a Nonce is assigned, it should not change.
+  BOOST_CHECK_EQUAL(i1->getNonce(), nonce1);
+}
+
+BOOST_AUTO_TEST_CASE(SetNonce)
+{
+  Interest i1("/A");
+  i1.setCanBePrefix(false);
+  i1.setNonce(1);
+  i1.wireEncode();
+  BOOST_CHECK_EQUAL(i1.getNonce(), 1);
+
+  Interest i2(i1);
+  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+
+  i2.setNonce(2);
+  BOOST_CHECK_EQUAL(i2.getNonce(), 2);
+  BOOST_CHECK_EQUAL(i1.getNonce(), 1); // should not affect i1 Nonce (Bug #4168)
+}
+
+BOOST_AUTO_TEST_CASE(RefreshNonce)
+{
+  Interest i;
+  BOOST_CHECK(!i.hasNonce());
+  i.refreshNonce();
+  BOOST_CHECK(!i.hasNonce());
+
+  i.setNonce(1);
+  BOOST_CHECK(i.hasNonce());
+  i.refreshNonce();
+  BOOST_CHECK(i.hasNonce());
+  BOOST_CHECK_NE(i.getNonce(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(SetInterestLifetime)
+{
+  BOOST_CHECK_THROW(Interest("/A", time::milliseconds(-1)), std::invalid_argument);
+  BOOST_CHECK_NO_THROW(Interest("/A", 0_ms));
+
+  Interest i("/local/ndn/prefix");
+  i.setNonce(1);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK_THROW(i.setInterestLifetime(time::milliseconds(-1)), std::invalid_argument);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  i.setInterestLifetime(0_ms);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), 0_ms);
+  i.setInterestLifetime(1_ms);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), 1_ms);
+}
+
+BOOST_AUTO_TEST_CASE(SetParameters)
+{
+  const uint8_t PARAMETERS1[] = {0xc1};
+  const uint8_t PARAMETERS2[] = {0xc2};
+
+  Interest i;
+  BOOST_CHECK(!i.hasParameters());
+  i.setParameters("2300"_block);
+  BOOST_CHECK(i.hasParameters());
+  i.unsetParameters();
+  BOOST_CHECK(!i.hasParameters());
+
+  i.setParameters("2301C0"_block); // Block overload
+  BOOST_CHECK_EQUAL(i.getParameters(), "2301C0"_block);
+  i.setParameters(PARAMETERS1, sizeof(PARAMETERS1)); // raw buffer overload
+  BOOST_CHECK_EQUAL(i.getParameters(), "2301C1"_block);
+  i.setParameters(make_shared<Buffer>(PARAMETERS2, sizeof(PARAMETERS2))); // ConstBufferPtr overload
+  BOOST_CHECK_EQUAL(i.getParameters(), "2301C2"_block);
+  i.setParameters("8001C1"_block); // Block of non-Parameters type
+  BOOST_CHECK_EQUAL(i.getParameters(), "23038001C1"_block);
+}
+
+// ---- operators ----
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  Interest a;
+  Interest b;
+
+  // if nonce is not set, it would be set to a random value
+  a.setNonce(1);
+  b.setNonce(1);
+
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // compare Name
+  a.setName("/A");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setName("/B");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setName("/A");
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // compare Selectors
+  a.setChildSelector(1);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setChildSelector(1);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // compare Nonce
+  a.setNonce(100);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setNonce(100);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // compare InterestLifetime
+  a.setInterestLifetime(10_s);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setInterestLifetime(10_s);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // compare ForwardingHint
+  a.setForwardingHint({{1, "/H"}});
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setForwardingHint({{1, "/H"}});
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // compare Parameters
+  a.setParameters("2304C0C1C2C3"_block);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setParameters("2304C0C1C2C3"_block);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestInterest
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/key-locator.t.cpp b/tests/unit/key-locator.t.cpp
new file mode 100644
index 0000000..ce3c6ae
--- /dev/null
+++ b/tests/unit/key-locator.t.cpp
@@ -0,0 +1,187 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "key-locator.hpp"
+#include "encoding/block-helpers.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestKeyLocator)
+
+BOOST_AUTO_TEST_CASE(TypeNone)
+{
+  KeyLocator a;
+  BOOST_CHECK_EQUAL(a.getType(), KeyLocator::KeyLocator_None);
+  BOOST_CHECK_THROW(a.getName(), KeyLocator::Error);
+  BOOST_CHECK_THROW(a.getKeyDigest(), KeyLocator::Error);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = a.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (auto it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x1c, 0x00
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  KeyLocator b(wire);
+  BOOST_CHECK_EQUAL(a, b);
+  BOOST_CHECK_EQUAL(b.getType(), KeyLocator::KeyLocator_None);
+  BOOST_CHECK_THROW(b.getName(), KeyLocator::Error);
+  BOOST_CHECK_THROW(b.getKeyDigest(), KeyLocator::Error);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "None");
+}
+
+BOOST_AUTO_TEST_CASE(TypeName)
+{
+  KeyLocator a;
+  a.setName("/N");
+  BOOST_CHECK_EQUAL(a.getType(), KeyLocator::KeyLocator_Name);
+  BOOST_CHECK_EQUAL(a.getName(), Name("/N"));
+  BOOST_CHECK_THROW(a.getKeyDigest(), KeyLocator::Error);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = a.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (auto it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x1c, 0x05, 0x07, 0x03, 0x08, 0x01, 0x4e
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  KeyLocator b(wire);
+  BOOST_CHECK_EQUAL(a, b);
+  BOOST_CHECK_EQUAL(b.getType(), KeyLocator::KeyLocator_Name);
+  BOOST_CHECK_EQUAL(b.getName(), Name("/N"));
+  BOOST_CHECK_THROW(b.getKeyDigest(), KeyLocator::Error);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "Name=/N");
+}
+
+BOOST_AUTO_TEST_CASE(TypeKeyDigest)
+{
+  std::string digestOctets = "\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45";
+  ConstBufferPtr digestBuffer = make_shared<Buffer>(digestOctets.c_str(), digestOctets.size());
+  Block expectedDigestBlock = makeBinaryBlock(tlv::KeyDigest, digestOctets.c_str(), digestOctets.size());
+
+  KeyLocator a;
+  a.setKeyDigest(digestBuffer);
+  BOOST_CHECK_EQUAL(a.getType(), KeyLocator::KeyLocator_KeyDigest);
+  BOOST_CHECK_EQUAL(a.getKeyDigest(), expectedDigestBlock);
+  BOOST_CHECK_THROW(a.getName(), KeyLocator::Error);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = a.wireEncode());
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (auto it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x1c, 0x0c, 0x1d, 0x0a, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf1, 0x23, 0x45
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  KeyLocator b(wire);
+  BOOST_CHECK_EQUAL(a, b);
+  BOOST_CHECK_EQUAL(b.getType(), KeyLocator::KeyLocator_KeyDigest);
+  BOOST_CHECK_EQUAL(b.getKeyDigest(), expectedDigestBlock);
+  BOOST_CHECK_THROW(b.getName(), KeyLocator::Error);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "KeyDigest=123456789A...");
+
+  std::string shortDigest = "\xbc\xde\xf1";
+  b.setKeyDigest(make_shared<Buffer>(shortDigest.c_str(), shortDigest.size()));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "KeyDigest=BCDEF1");
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  KeyLocator a;
+  KeyLocator b;
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  a.setName("ndn:/A");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setName("ndn:/B");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setName("ndn:/A");
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  char digestOctets[] = "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD";
+  ConstBufferPtr digestBuffer = make_shared<Buffer>(digestOctets, 8);
+
+  a.setKeyDigest(digestBuffer);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setKeyDigest(digestBuffer);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+}
+
+BOOST_AUTO_TEST_CASE(UnknownType)
+{
+  static const uint8_t wireOctets[] = {
+    0x1c, 0x03, 0x7f, 0x01, 0xcc
+  };
+  Block wire(wireOctets, sizeof(wireOctets));
+  KeyLocator a(wire);
+  BOOST_CHECK_EQUAL(a.getType(), KeyLocator::KeyLocator_Unknown);
+
+  KeyLocator b(wire);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "Unknown");
+
+  b.setName("/N");
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestKeyLocator
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/link.t.cpp b/tests/unit/link.t.cpp
new file mode 100644
index 0000000..347fb67
--- /dev/null
+++ b/tests/unit/link.t.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "link.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestLink)
+
+const uint8_t GOOD_LINK[] = {
+0x06, 0xda, // Data
+    0x07, 0x14, // Name
+        0x08, 0x05,
+            0x6c, 0x6f, 0x63, 0x61, 0x6c,
+        0x08, 0x03,
+            0x6e, 0x64, 0x6e,
+        0x08, 0x06,
+            0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
+    0x14, 0x07, // MetaInfo
+        0x18, 0x01, // ContentType
+            0x01,
+        0x19, 0x02, // FreshnessPeriod
+            0x27, 0x10,
+    0x15, 0x1a, // Content
+        0x1f, 0x0c, // LinkDelegation
+            0x1e, 0x01, // LinkPreference
+                0x0a,
+            0x07, 0x07, // Name
+                0x08, 0x05,
+                    0x6c, 0x6f, 0x63, 0x61, 0x6c,
+        0x1f, 0x0a, // LinkDelegation
+            0x1e, 0x01, // LinkPreference
+                0x14,
+            0x07, 0x05, // Name
+                0x08, 0x03,
+                    0x6e, 0x64, 0x6e,
+    0x16, 0x1b, // SignatureInfo
+        0x1b, 0x01, // SignatureType
+            0x01,
+        0x1c, 0x16, // KeyLocator
+            0x07, 0x14, // Name
+                0x08, 0x04,
+                    0x74, 0x65, 0x73, 0x74,
+                0x08, 0x03,
+                    0x6b, 0x65, 0x79,
+                0x08, 0x07,
+                    0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+    0x17, 0x80, // SignatureValue
+        0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+        0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+        0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+        0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+        0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+        0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+        0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+        0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+        0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+        0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1
+};
+
+BOOST_AUTO_TEST_SUITE(EncodeDecode)
+
+BOOST_AUTO_TEST_CASE(Decode)
+{
+  Link link(Block(GOOD_LINK, sizeof(GOOD_LINK)));
+  BOOST_CHECK_EQUAL(link.getName(), Name("/local/ndn/prefix"));
+  BOOST_CHECK_EQUAL(link.getDelegationList(),
+                    DelegationList({{10, Name("/local")}, {20, Name("/ndn")}}));
+}
+
+BOOST_AUTO_TEST_CASE(DecodeBadContentType)
+{
+  Data linkData(Block(GOOD_LINK, sizeof(GOOD_LINK)));
+  linkData.setContentType(tlv::ContentType_Key);
+  Block badLink = linkData.wireEncode();
+
+  BOOST_CHECK_THROW((Link(badLink)), Link::Error);
+  Link link;
+  BOOST_CHECK_THROW(link.wireDecode(badLink), Link::Error);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  Link link1("/test", {{10, "/test1"}, {20, "/test2"}, {100, "/test3"}});
+  signData(link1);
+  Block wire = link1.wireEncode();
+
+  Link link2(wire);
+  BOOST_CHECK_EQUAL(link2.getName(), "/test");
+  BOOST_CHECK_EQUAL(link2.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {20, "/test2"}, {100, "/test3"}}));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // EncodeDecode
+
+BOOST_AUTO_TEST_SUITE(Modify)
+
+BOOST_AUTO_TEST_CASE(SetDelegationList)
+{
+  Link link("/test");
+  BOOST_CHECK_EQUAL(link.getDelegationList(), DelegationList());
+
+  link.setDelegationList(DelegationList({{10, "/test1"}, {20, "/test2"}}));
+  BOOST_CHECK_EQUAL(link.getDelegationList(), DelegationList({{10, "/test1"}, {20, "/test2"}}));
+}
+
+BOOST_AUTO_TEST_CASE(AddDelegation)
+{
+  Link link1("/test", {{10, "/test1"}, {20, "/test2"}, {100, "/test3"}});
+  BOOST_CHECK_EQUAL(link1.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {20, "/test2"}, {100, "/test3"}}));
+
+  link1.addDelegation(30, "/test4");
+  BOOST_CHECK_EQUAL(link1.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {20, "/test2"}, {30, "/test4"}, {100, "/test3"}}));
+
+  link1.addDelegation(40, "/test2");
+  BOOST_CHECK_EQUAL(link1.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {30, "/test4"}, {40, "/test2"}, {100, "/test3"}}));
+
+  signData(link1);
+  Link link2(link1.wireEncode());
+  BOOST_CHECK_EQUAL(link2.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {30, "/test4"}, {40, "/test2"}, {100, "/test3"}}));
+}
+
+BOOST_AUTO_TEST_CASE(RemoveDelegation)
+{
+  Link link1("/test", {{10, "/test1"}, {20, "/test2"}, {100, "/test3"}});
+  BOOST_CHECK_EQUAL(link1.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {20, "/test2"}, {100, "/test3"}}));
+
+  link1.removeDelegation("/test4"); // non-existent
+  BOOST_CHECK_EQUAL(link1.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {20, "/test2"}, {100, "/test3"}}));
+
+  link1.removeDelegation("/test2");
+  BOOST_CHECK_EQUAL(link1.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {100, "/test3"}}));
+
+  signData(link1);
+  Link link2(link1.wireEncode());
+  BOOST_CHECK_EQUAL(link2.getDelegationList(),
+                    DelegationList({{10, "/test1"}, {100, "/test3"}}));
+
+  link1.removeDelegation("/test1");
+  link1.removeDelegation("/test3");
+  BOOST_CHECK_EQUAL(link1.getDelegationList(), DelegationList());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Modify
+
+BOOST_AUTO_TEST_SUITE_END() // TestLink
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/lp/cache-policy.t.cpp b/tests/unit/lp/cache-policy.t.cpp
new file mode 100644
index 0000000..6f1050d
--- /dev/null
+++ b/tests/unit/lp/cache-policy.t.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Eric Newberry <enewberry@email.arizona.edu>
+ */
+
+#include "lp/cache-policy.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace lp {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Lp)
+BOOST_AUTO_TEST_SUITE(TestCachePolicy)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  CachePolicy policy;
+  policy.setPolicy(CachePolicyType::NO_CACHE);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = policy.wireEncode());
+
+  // Sample encoded value obtained with:
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+
+  // Contains CachePolicyType::NO_CACHE
+  static const uint8_t expectedBlock[] = {
+    0xfd, 0x03, 0x34, 0x05, 0xfd, 0x03, 0x35, 0x01, 0x01
+  };
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(policy.wireDecode(wire));
+}
+
+BOOST_AUTO_TEST_CASE(DecodeUnknownPolicyError)
+{
+  static const uint8_t expectedBlock[] = {
+    0xfd, 0x03, 0x34, 0x08, 0xfd, 0x03, 0x35, 0x04, 0xff, 0xff, 0xff, 0xff
+  };
+
+  CachePolicy policy;
+  Block wire(expectedBlock, sizeof(expectedBlock));
+  BOOST_REQUIRE_THROW(policy.wireDecode(wire), CachePolicy::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeMissingPolicyError)
+{
+  static const uint8_t inputBlock[] = {
+    0xfd, 0x03, 0x34, 0x00
+  };
+
+  CachePolicy policy;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_REQUIRE_THROW(policy.wireDecode(wire), CachePolicy::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeInvalidPolicyError)
+{
+  static const uint8_t inputBlock[] = {
+    0xfd, 0x03, 0x34, 0x05, 0xfd, 0x03, 0x35, 0x01, 0x00
+  };
+
+  CachePolicy policy;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_REQUIRE_THROW(policy.wireDecode(wire), CachePolicy::Error);
+}
+
+BOOST_AUTO_TEST_CASE(Policy)
+{
+  CachePolicy policy;
+  BOOST_CHECK_EQUAL(policy.getPolicy(), CachePolicyType::NONE);
+
+  policy.setPolicy(CachePolicyType::NO_CACHE);
+  BOOST_CHECK_EQUAL(policy.getPolicy(), CachePolicyType::NO_CACHE);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCachePolicy
+BOOST_AUTO_TEST_SUITE_END() // Lp
+
+} // namespace tests
+} // namespace lp
+} // namespace ndn
diff --git a/tests/unit/lp/nack-header.t.cpp b/tests/unit/lp/nack-header.t.cpp
new file mode 100644
index 0000000..0645f40
--- /dev/null
+++ b/tests/unit/lp/nack-header.t.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Eric Newberry <enewberry@email.arizona.edu>
+ */
+
+#include "lp/nack-header.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace lp {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Lp)
+BOOST_AUTO_TEST_SUITE(TestNackHeader)
+
+BOOST_AUTO_TEST_CASE(IsLessSevere)
+{
+  BOOST_CHECK_EQUAL(isLessSevere(NackReason::NONE, NackReason::NONE), false);
+  BOOST_CHECK_EQUAL(isLessSevere(NackReason::CONGESTION, NackReason::CONGESTION), false);
+
+  BOOST_CHECK_EQUAL(isLessSevere(NackReason::CONGESTION, NackReason::NONE), true);
+  BOOST_CHECK_EQUAL(isLessSevere(NackReason::NONE, NackReason::CONGESTION), false);
+
+  BOOST_CHECK_EQUAL(isLessSevere(NackReason::CONGESTION, NackReason::NO_ROUTE), true);
+  BOOST_CHECK_EQUAL(isLessSevere(NackReason::NO_ROUTE, NackReason::CONGESTION), false);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  NackHeader header;
+  header.setReason(NackReason::DUPLICATE);
+
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = header.wireEncode());
+
+  // Sample encoded value obtained with:
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+
+  // Contains NackReason::DUPLICATE
+  static const uint8_t expectedBlock[] = {
+    0xfd, 0x03, 0x20, 0x05, 0xfd, 0x03, 0x21, 0x01, 0x64,
+  };
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+
+  BOOST_REQUIRE_NO_THROW(header.wireDecode(wire));
+}
+
+BOOST_AUTO_TEST_CASE(DecodeUnknownReasonCode)
+{
+  static const uint8_t expectedBlock[] = {
+    0xfd, 0x03, 0x20, 0x08, 0xfd, 0x03, 0x21, 0x04, 0xff, 0xff, 0xff, 0xff,
+  };
+
+  NackHeader header;
+  Block wire(expectedBlock, sizeof(expectedBlock));
+  BOOST_REQUIRE_NO_THROW(header.wireDecode(wire));
+  Block wireEncoded;
+  BOOST_REQUIRE_NO_THROW(wireEncoded = header.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wireEncoded.begin(), wireEncoded.end());
+  BOOST_CHECK_EQUAL(header.getReason(), NackReason::NONE);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeOmitReason)
+{
+  static const uint8_t expectedBlock[] = {
+    0xfd, 0x03, 0x20, 0x00,
+  };
+
+  NackHeader header;
+  Block wire(expectedBlock, sizeof(expectedBlock));
+  BOOST_REQUIRE_NO_THROW(header.wireDecode(wire));
+  Block wireEncoded;
+  BOOST_REQUIRE_NO_THROW(wireEncoded = header.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wireEncoded.begin(), wireEncoded.end());
+  BOOST_CHECK_EQUAL(header.getReason(), NackReason::NONE);
+}
+
+BOOST_AUTO_TEST_CASE(Reason)
+{
+  NackHeader header;
+  BOOST_CHECK_EQUAL(header.getReason(), NackReason::NONE);
+
+  header.setReason(NackReason::DUPLICATE);
+  BOOST_CHECK_EQUAL(header.getReason(), NackReason::DUPLICATE);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNackHeader
+BOOST_AUTO_TEST_SUITE_END() // Lp
+
+} // namespace tests
+} // namespace lp
+} // namespace ndn
diff --git a/tests/unit/lp/nack.t.cpp b/tests/unit/lp/nack.t.cpp
new file mode 100644
index 0000000..436a649
--- /dev/null
+++ b/tests/unit/lp/nack.t.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Eric Newberry <enewberry@email.arizona.edu>
+ */
+
+#include "lp/nack.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace lp {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Lp)
+BOOST_AUTO_TEST_SUITE(TestNack)
+
+BOOST_AUTO_TEST_CASE(Members)
+{
+  Name name("ndn:/test");
+  Interest interest(name);
+  Nack nack(interest);
+
+  BOOST_CHECK_EQUAL(nack.getInterest().getName(), name);
+
+  NackHeader header;
+  header.setReason(NackReason::CONGESTION);
+  nack.setHeader(header);
+  BOOST_CHECK_EQUAL(nack.getHeader().getReason(), header.getReason());
+
+  BOOST_CHECK_EQUAL(nack.getHeader().getReason(), nack.getReason());
+
+  nack.setReason(NackReason::DUPLICATE);
+  BOOST_CHECK_EQUAL(nack.getReason(), NackReason::DUPLICATE);
+
+  nack.setReason(NackReason::NO_ROUTE);
+  BOOST_CHECK_EQUAL(nack.getReason(), NackReason::NO_ROUTE);
+
+  Nack nack2(interest);
+  BOOST_CHECK_EQUAL(nack2.getReason(), NackReason::NONE);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNack
+BOOST_AUTO_TEST_SUITE_END() // Lp
+
+} // namespace tests
+} // namespace lp
+} // namespace ndn
diff --git a/tests/unit/lp/packet.t.cpp b/tests/unit/lp/packet.t.cpp
new file mode 100644
index 0000000..1583772
--- /dev/null
+++ b/tests/unit/lp/packet.t.cpp
@@ -0,0 +1,453 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "lp/packet.hpp"
+#include "prefix-announcement.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+namespace ndn {
+namespace lp {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Lp)
+BOOST_AUTO_TEST_SUITE(TestPacket)
+
+BOOST_AUTO_TEST_CASE(FieldAccess)
+{
+  Packet packet;
+
+  BOOST_CHECK(packet.empty());
+  BOOST_CHECK(!packet.has<FragIndexField>());
+  BOOST_CHECK_EQUAL(0, packet.count<FragIndexField>());
+
+  packet.set<FragIndexField>(1234);
+  BOOST_CHECK(!packet.empty());
+  BOOST_CHECK(packet.has<FragIndexField>());
+  BOOST_CHECK_THROW(packet.add<FragIndexField>(5678), std::length_error);
+  BOOST_CHECK_EQUAL(1, packet.count<FragIndexField>());
+  BOOST_CHECK_EQUAL(1234, packet.get<FragIndexField>(0));
+  BOOST_CHECK_THROW(packet.get<FragIndexField>(1), std::out_of_range);
+  BOOST_CHECK_THROW(packet.remove<FragIndexField>(1), std::out_of_range);
+
+  packet.remove<FragIndexField>(0);
+  BOOST_CHECK_EQUAL(0, packet.count<FragIndexField>());
+
+  packet.add<FragIndexField>(832);
+  std::vector<uint64_t> fragIndexes = packet.list<FragIndexField>();
+  BOOST_CHECK_EQUAL(1, fragIndexes.size());
+  BOOST_CHECK_EQUAL(832, fragIndexes.at(0));
+
+  packet.clear<FragIndexField>();
+  BOOST_CHECK_EQUAL(0, packet.count<FragIndexField>());
+  BOOST_CHECK(packet.empty());
+}
+
+/// \todo test field access methods with a REPEATABLE field
+
+BOOST_AUTO_TEST_CASE(EncodeFragment)
+{
+  static const uint8_t expectedBlock[] = {
+    0x64, 0x0e, // LpPacket
+          0x51, 0x08, // Sequence
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Buffer buf(2);
+  buf[0] = 0x03;
+  buf[1] = 0xe8;
+
+  Packet packet;
+  packet.add<FragmentField>(std::make_pair(buf.begin(), buf.end()));
+  packet.add<SequenceField>(1000);
+  Block wire = packet.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(EncodeSubTlv)
+{
+  static const uint8_t expectedBlock[] = {
+    0x64, 0x09, // LpPacket
+          0xfd, 0x03, 0x20, 0x05, // Nack
+                0xfd, 0x03, 0x21, 0x01, // NackReason
+                      0x64,
+  };
+
+  NackHeader nack;
+  nack.setReason(NackReason::DUPLICATE);
+
+  Packet packet;
+  BOOST_CHECK_NO_THROW(packet.add<NackField>(nack));
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = packet.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(EncodeZeroLengthTlv)
+{
+  static const uint8_t expectedBlock[] = {
+    0x64, 0x04, // LpPacket
+          0xfd, 0x03, 0x4c, 0x00, // NonDiscovery
+  };
+
+  Packet packet1, packet2;
+  BOOST_CHECK_NO_THROW(packet1.set<NonDiscoveryField>(EmptyValue{}));
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = packet1.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+
+  BOOST_CHECK_NO_THROW(packet2.add<NonDiscoveryField>(EmptyValue{}));
+  BOOST_REQUIRE_NO_THROW(wire = packet2.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(EncodeSortOrder)
+{
+  static const uint8_t expectedBlock[] = {
+    0x64, 0x2e, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0x53, 0x01, // FragCount
+                0x01,
+          0xfd, 0x03, 0x44, 0x08, // Ack
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+          0xfd, 0x03, 0x44, 0x08, // Ack
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+          0xfd, 0x03, 0x44, 0x08, // Ack
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Buffer frag(2);
+  frag[0] = 0x03;
+  frag[1] = 0xe8;
+
+  Packet packet;
+  BOOST_CHECK_NO_THROW(packet.add<FragmentField>(std::make_pair(frag.begin(), frag.end())));
+  BOOST_CHECK_NO_THROW(packet.add<FragIndexField>(0));
+  BOOST_CHECK_NO_THROW(packet.add<AckField>(2));
+  BOOST_REQUIRE_NO_THROW(packet.wireEncode());
+  BOOST_CHECK_NO_THROW(packet.add<FragCountField>(1));
+  BOOST_REQUIRE_NO_THROW(packet.wireEncode());
+  BOOST_CHECK_NO_THROW(packet.add<AckField>(4));
+  BOOST_REQUIRE_NO_THROW(packet.wireEncode());
+  BOOST_CHECK_NO_THROW(packet.add<AckField>(3));
+  BOOST_REQUIRE_NO_THROW(packet.wireEncode());
+  Block wire;
+  BOOST_REQUIRE_NO_THROW(wire = packet.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(expectedBlock, expectedBlock + sizeof(expectedBlock),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(DecodeNormal)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x0a, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0x53, 0x01, // FragCount
+                0x01,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(1, packet.count<FragmentField>());
+  BOOST_CHECK_EQUAL(1, packet.count<FragIndexField>());
+  BOOST_CHECK_EQUAL(1, packet.count<FragCountField>());
+  Buffer::const_iterator first, last;
+  BOOST_REQUIRE_NO_THROW(std::tie(first, last) = packet.get<FragmentField>(0));
+  BOOST_CHECK_EQUAL(2, last - first);
+  BOOST_CHECK_EQUAL(0x03, *first);
+  BOOST_CHECK_EQUAL(0xe8, *(last - 1));
+  BOOST_CHECK_EQUAL(0, packet.get<FragIndexField>(0));
+  BOOST_CHECK_EQUAL(1, packet.get<FragCountField>(0));
+}
+
+BOOST_AUTO_TEST_CASE(DecodeIdle)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x06, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0x53, 0x01, // FragCount
+                0x01,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(0, packet.count<FragmentField>());
+  BOOST_CHECK_EQUAL(1, packet.count<FragIndexField>());
+  BOOST_CHECK_EQUAL(1, packet.count<FragCountField>());
+  BOOST_CHECK_EQUAL(0, packet.get<FragIndexField>(0));
+  BOOST_CHECK_EQUAL(1, packet.get<FragCountField>(0));
+}
+
+BOOST_AUTO_TEST_CASE(DecodeFragment)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x04, // LpPacket
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(1, packet.count<FragmentField>());
+  BOOST_CHECK_EQUAL(0, packet.count<FragIndexField>());
+  Buffer::const_iterator first, last;
+  BOOST_REQUIRE_NO_THROW(std::tie(first, last) = packet.get<FragmentField>(0));
+  BOOST_CHECK_EQUAL(2, last - first);
+  BOOST_CHECK_EQUAL(0x03, *first);
+  BOOST_CHECK_EQUAL(0xe8, *(last - 1));
+}
+
+BOOST_AUTO_TEST_CASE(DecodeNonDiscoveryHeader)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x04, // LpPacket
+          0xfd, 0x03, 0x4c, 0x00, // NonDiscovery
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(true, packet.has<NonDiscoveryField>());
+  BOOST_CHECK_NO_THROW(packet.get<NonDiscoveryField>());
+}
+
+BOOST_AUTO_TEST_CASE(DecodeEmpty)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x00, // LpPacket
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(0, packet.count<FragmentField>());
+  BOOST_CHECK_EQUAL(0, packet.count<FragIndexField>());
+  BOOST_CHECK_EQUAL(false, packet.has<NonDiscoveryField>());
+}
+
+BOOST_AUTO_TEST_CASE(DecodeRepeatedNonRepeatableHeader)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x06, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0x52, 0x01, // FragIndex
+                0x01,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_THROW(packet.wireDecode(wire), Packet::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeRepeatedRepeatableHeader)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x0f, // LpPacket
+          0xfd, 0x03, 0x44, 0x01, // Ack
+                0x01,
+          0xfd, 0x03, 0x44, 0x01, // Ack
+                0x03,
+          0xfd, 0x03, 0x44, 0x01, // Ack
+                0x02,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_REQUIRE_EQUAL(packet.count<AckField>(), 3);
+  BOOST_CHECK_EQUAL(packet.get<AckField>(), 1);
+  BOOST_CHECK_EQUAL(packet.get<AckField>(0), 1);
+  BOOST_CHECK_EQUAL(packet.get<AckField>(1), 3);
+  BOOST_CHECK_EQUAL(packet.get<AckField>(2), 2);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeRepeatedFragment)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x08, // LpPacket
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+          0x50, 0x02, // Fragment
+                0x03, 0xe9,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_THROW(packet.wireDecode(wire), Packet::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeWrongOrderAmongHeaders)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x0a, // LpPacket
+          0x53, 0x01, // FragCount
+                0x01,
+          0x52, 0x01, // FragIndex
+                0x00,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_THROW(packet.wireDecode(wire), Packet::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeWrongOrderFragment)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x0a, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+          0x53, 0x01, // FragCount
+                0x01,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_THROW(packet.wireDecode(wire), Packet::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeIgnoredHeader)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x0c, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0xfd, 0x03, 0x24, 0x01, // unknown TLV-TYPE 804 (ignored)
+                0x02,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(1, packet.count<FragmentField>());
+  BOOST_CHECK_EQUAL(1, packet.count<FragIndexField>());
+}
+
+BOOST_AUTO_TEST_CASE(DecodeUnrecognizedHeader)
+{
+  static const uint8_t inputBlock[] = {
+    0x64, 0x0c, // LpPacket
+          0x52, 0x01, // FragIndex
+                0x00,
+          0xfd, 0x03, 0x22, 0x01, // unknown TLV-TYPE 802 (cannot ignore)
+                0x02,
+          0x50, 0x02, // Fragment
+                0x03, 0xe8,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_THROW(packet.wireDecode(wire), Packet::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeBareNetworkLayerPacket)
+{
+  static const uint8_t inputBlock[] = {
+    0x05, 0x0a, // Interest
+          0x07, 0x02, // Name
+                0x03, 0xe8,
+          0x0a, 0x04, // Nonce
+                0x01, 0x02, 0x03, 0x04,
+  };
+
+  Packet packet;
+  Block wire(inputBlock, sizeof(inputBlock));
+  BOOST_CHECK_NO_THROW(packet.wireDecode(wire));
+  BOOST_CHECK_EQUAL(1, packet.count<FragmentField>());
+
+  Block encoded;
+  BOOST_CHECK_NO_THROW(encoded = packet.wireEncode());
+  BOOST_CHECK_EQUAL_COLLECTIONS(inputBlock, inputBlock + sizeof(inputBlock),
+                                encoded.begin(), encoded.end());
+}
+
+BOOST_AUTO_TEST_CASE(DecodeUnrecognizedTlvType)
+{
+  Packet packet;
+  Block wire = encoding::makeEmptyBlock(ndn::tlv::Name);
+  BOOST_CHECK_THROW(packet.wireDecode(wire), Packet::Error);
+}
+
+BOOST_FIXTURE_TEST_CASE(DecodePrefixAnnouncement, ndn::tests::IdentityManagementFixture)
+{
+  // Construct Data which prefix announcement is attached to
+  Data data0("/edu/ua/cs/news/index.html");
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::encoding::makeEmptyBlock(ndn::tlv::SignatureValue));
+  data0.setSignature(fakeSignature);
+
+  Block wire;
+  wire = data0.wireEncode();
+  Packet packet0;
+  packet0.wireDecode(wire);
+
+  // Construct Prefix Announcement
+  PrefixAnnouncement pa;
+  pa.setAnnouncedName("/net/example");
+  pa.setExpiration(5_min);
+  pa.setValidityPeriod(security::ValidityPeriod(time::fromIsoString("20181030T000000"),
+                                                time::fromIsoString("20181124T235959")));
+  pa.toData(m_keyChain, signingWithSha256(), 1);
+  PrefixAnnouncementHeader pah0(pa);
+  BOOST_CHECK_NO_THROW(packet0.add<PrefixAnnouncementField>(pah0));
+  Block encoded;
+  BOOST_CHECK_NO_THROW(encoded = packet0.wireEncode());
+
+  // check decoding
+  Packet packet1;
+  BOOST_CHECK_NO_THROW(packet1.wireDecode(encoded));
+  BOOST_CHECK_EQUAL(true, packet1.has<PrefixAnnouncementField>());
+  PrefixAnnouncementHeader pah1;
+  BOOST_CHECK_NO_THROW(pah1 = packet1.get<PrefixAnnouncementField>());
+  BOOST_CHECK_EQUAL(pah1.getPrefixAnn()->getAnnouncedName(), "/net/example");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPacket
+BOOST_AUTO_TEST_SUITE_END() // Lp
+
+} // namespace tests
+} // namespace lp
+} // namespace ndn
diff --git a/tests/unit/lp/prefix-announcement-header.t.cpp b/tests/unit/lp/prefix-announcement-header.t.cpp
new file mode 100644
index 0000000..38a75f3
--- /dev/null
+++ b/tests/unit/lp/prefix-announcement-header.t.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "lp/prefix-announcement-header.hpp"
+#include "lp/tlv.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+namespace ndn {
+namespace lp {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Lp)
+BOOST_FIXTURE_TEST_SUITE(TestPrefixAnnouncementHeader, ndn::tests::IdentityManagementFixture)
+
+BOOST_AUTO_TEST_CASE(EncodeDecode)
+{
+  EncodingEstimator estimator;
+  PrefixAnnouncementHeader header;
+  BOOST_CHECK_THROW(header.wireEncode(estimator), PrefixAnnouncementHeader::Error);
+  BOOST_CHECK_THROW(PrefixAnnouncementHeader{PrefixAnnouncement()}, PrefixAnnouncementHeader::Error);
+
+  PrefixAnnouncement pa;
+  pa.setAnnouncedName("/net/example");
+  pa.setExpiration(1_h);
+  const Data& data = pa.toData(m_keyChain, signingWithSha256(), 1);
+  Block encodedData = data.wireEncode();
+
+  Block expectedBlock(tlv::PrefixAnnouncement);
+  expectedBlock.push_back(encodedData);
+  expectedBlock.encode();
+
+  PrefixAnnouncementHeader pah0(pa);
+  size_t estimatedSize = pah0.wireEncode(estimator);
+  EncodingBuffer buffer(estimatedSize, 0);
+  pah0.wireEncode(buffer);
+  Block wire = buffer.block();
+  BOOST_CHECK_EQUAL(expectedBlock, wire);
+
+  PrefixAnnouncementHeader pah1;
+  pah1.wireDecode(wire);
+  BOOST_CHECK_EQUAL(*pah0.getPrefixAnn(), *pah1.getPrefixAnn());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPrefixAnnouncementHeader
+BOOST_AUTO_TEST_SUITE_END() // Lp
+
+} // namespace tests
+} // namespace lp
+} // namespace ndn
diff --git a/tests/unit/meta-info.t.cpp b/tests/unit/meta-info.t.cpp
new file mode 100644
index 0000000..2a2dac3
--- /dev/null
+++ b/tests/unit/meta-info.t.cpp
@@ -0,0 +1,164 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "meta-info.hpp"
+#include "data.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestMetaInfo)
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(EncodeDecodeEquality, 1)
+BOOST_AUTO_TEST_CASE(EncodeDecodeEquality)
+{
+  // default values
+  MetaInfo a("1406 type=180100 freshness=190100"_block);
+  BOOST_CHECK_EQUAL(a.getType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(a.getFreshnessPeriod(), 0_ms);
+  BOOST_CHECK(!a.getFinalBlock());
+  BOOST_CHECK_EQUAL(a, a);
+
+  MetaInfo b;
+  BOOST_CHECK_NE(a, b);
+  b.setType(a.getType());
+  b.setFreshnessPeriod(a.getFreshnessPeriod());
+  b.setFinalBlock(a.getFinalBlock());
+  BOOST_CHECK_EQUAL(b.wireEncode(), "1400"_block);
+  BOOST_CHECK_EQUAL(a, b); // expected failure #4569
+
+  // non-default values
+  Block wire2 = "140C type=180101 freshness=190266B2 finalblock=1A03080141"_block;
+  a.wireDecode(wire2);
+  BOOST_CHECK_EQUAL(a.getType(), tlv::ContentType_Link);
+  BOOST_CHECK_EQUAL(a.getFreshnessPeriod(), 26290_ms);
+  BOOST_CHECK_EQUAL(*a.getFinalBlock(), name::Component("A"));
+  BOOST_CHECK_NE(a, b);
+
+  b.setType(a.getType());
+  b.setFreshnessPeriod(a.getFreshnessPeriod());
+  b.setFinalBlock(a.getFinalBlock());
+  BOOST_CHECK_EQUAL(b.wireEncode(), wire2);
+  BOOST_CHECK_EQUAL(a, b);
+
+  // FinalBlockId is typed name component
+  Block wire3 = "1405 finalblock=1A03DD0141"_block;
+  a.wireDecode(wire3);
+  BOOST_CHECK_EQUAL(a.getType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(a.getFreshnessPeriod(), 0_ms);
+  BOOST_CHECK_EQUAL(*a.getFinalBlock(), name::Component::fromEscapedString("221=A"));
+  BOOST_CHECK_NE(a, b);
+
+  b.setType(a.getType());
+  b.setFreshnessPeriod(a.getFreshnessPeriod());
+  b.setFinalBlock(a.getFinalBlock());
+  BOOST_CHECK_EQUAL(b.wireEncode(), wire3);
+  BOOST_CHECK_EQUAL(a, b);
+}
+
+BOOST_AUTO_TEST_CASE(AppMetaInfo)
+{
+  MetaInfo info1;
+  info1.setType(196);
+  info1.setFreshnessPeriod(3600_ms);
+  info1.setFinalBlock(name::Component("/att/final"));
+
+  uint32_t ints[5] = {128, 129, 130, 131, 132};
+  std::string ss[5] = {"h", "hello", "hello, world", "hello, world, alex",
+                       "hello, world, alex, I am Xiaoke Jiang"};
+
+  for (size_t i = 0; i < 5; i++) {
+    uint32_t type = 128 + i * 10;
+    info1.addAppMetaInfo(makeNonNegativeIntegerBlock(type, ints[i]));
+    type += 5;
+    info1.addAppMetaInfo(makeStringBlock(type, ss[i]));
+  }
+
+  BOOST_CHECK(info1.findAppMetaInfo(252) == nullptr);
+
+  info1.addAppMetaInfo(makeNonNegativeIntegerBlock(252, 1000));
+  BOOST_CHECK(info1.findAppMetaInfo(252) != nullptr);
+
+  info1.addAppMetaInfo(makeNonNegativeIntegerBlock(252, 1000));
+  BOOST_CHECK(info1.findAppMetaInfo(252) != nullptr);
+
+  info1.removeAppMetaInfo(252);
+  BOOST_CHECK(info1.findAppMetaInfo(252) != nullptr);
+
+  info1.removeAppMetaInfo(252);
+  BOOST_CHECK(info1.findAppMetaInfo(252) == nullptr);
+
+  // // These octets are obtained by the snippet below.
+  // // This check is intended to detect unexpected encoding change in the future.
+  // const Block& wire = info1.wireEncode();
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  const uint8_t METAINFO[] = {0x14, 0x77, 0x18, 0x01, 0xc4, 0x19, 0x02, 0x0e, 0x10, 0x1a, 0x0c,
+                              0x08, 0x0a, 0x2f, 0x61, 0x74, 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x61,
+                              0x6c, 0x80, 0x01, 0x80, 0x85, 0x01, 0x68, 0x8a, 0x01, 0x81, 0x8f,
+                              0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x94, 0x01, 0x82, 0x99, 0x0c,
+                              0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c,
+                              0x64, 0x9e, 0x01, 0x83, 0xa3, 0x12, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+                              0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2c, 0x20, 0x61, 0x6c,
+                              0x65, 0x78, 0xa8, 0x01, 0x84, 0xad, 0x25, 0x68, 0x65, 0x6c, 0x6c,
+                              0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2c, 0x20, 0x61,
+                              0x6c, 0x65, 0x78, 0x2c, 0x20, 0x49, 0x20, 0x61, 0x6d, 0x20, 0x58,
+                              0x69, 0x61, 0x6f, 0x6b, 0x65, 0x20, 0x4a, 0x69, 0x61, 0x6e, 0x67};
+
+  BOOST_REQUIRE_EQUAL_COLLECTIONS(info1.wireEncode().begin(), info1.wireEncode().end(),
+                                  METAINFO, METAINFO + sizeof(METAINFO));
+
+  MetaInfo info2;
+  info2.wireDecode(Block(METAINFO, sizeof(METAINFO)));
+
+  for (size_t i = 0; i < 5; i++) {
+    uint32_t tlvType = 128 + i * 10;
+    const Block* block = info2.findAppMetaInfo(tlvType);
+    BOOST_REQUIRE(block != nullptr);
+    BOOST_CHECK_EQUAL(readNonNegativeInteger(*block), ints[i]);
+    tlvType += 5;
+
+    block = info2.findAppMetaInfo(tlvType);
+    BOOST_REQUIRE(block != nullptr);
+
+    std::string s3(reinterpret_cast<const char*>(block->value()), block->value_size());
+    BOOST_CHECK_EQUAL(s3, ss[i]);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(AppMetaInfoTypeRange)
+{
+  MetaInfo info;
+
+  BOOST_CHECK_NO_THROW(info.addAppMetaInfo(makeNonNegativeIntegerBlock(128, 1000)));
+  BOOST_CHECK_NO_THROW(info.addAppMetaInfo(makeNonNegativeIntegerBlock(252, 1000)));
+
+  BOOST_CHECK_THROW(info.addAppMetaInfo(makeNonNegativeIntegerBlock(127, 1000)), MetaInfo::Error);
+  BOOST_CHECK_THROW(info.addAppMetaInfo(makeNonNegativeIntegerBlock(253, 1000)), MetaInfo::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestMetaInfo
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/mgmt/control-response.t.cpp b/tests/unit/mgmt/control-response.t.cpp
new file mode 100644
index 0000000..86aa780
--- /dev/null
+++ b/tests/unit/mgmt/control-response.t.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/control-response.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestControlResponse)
+
+static const uint8_t WIRE[] = {
+  0x65, 0x17, // ControlResponse
+        0x66, 0x02, // StatusCode
+              0x01, 0x94,
+        0x67, 0x11, // StatusText
+              0x4e, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x6f, 0x74, 0x20,
+              0x66, 0x6f, 0x75, 0x6e, 0x64};
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ControlResponse cr(404, "Nothing not found");
+  const Block& wire = cr.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(WIRE, WIRE + sizeof(WIRE),
+                                wire.begin(), wire.end());
+}
+
+BOOST_AUTO_TEST_CASE(Decode)
+{
+  ControlResponse cr(Block(WIRE, sizeof(WIRE)));
+  BOOST_CHECK_EQUAL(cr.getCode(), 404);
+  BOOST_CHECK_EQUAL(cr.getText(), "Nothing not found");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlResponse
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn
diff --git a/tests/unit/mgmt/dispatcher.t.cpp b/tests/unit/mgmt/dispatcher.t.cpp
new file mode 100644
index 0000000..c75af4d
--- /dev/null
+++ b/tests/unit/mgmt/dispatcher.t.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/dispatcher.hpp"
+#include "mgmt/nfd/control-parameters.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+using namespace ndn::tests;
+
+class DispatcherFixture : public IdentityManagementTimeFixture
+{
+public:
+  DispatcherFixture()
+    : face(io, m_keyChain, {true, true})
+    , dispatcher(face, m_keyChain, security::SigningInfo())
+    , storage(dispatcher.m_storage)
+  {
+  }
+
+public:
+  util::DummyClientFace face;
+  mgmt::Dispatcher dispatcher;
+  InMemoryStorageFifo& storage;
+};
+
+class VoidParameters : public mgmt::ControlParameters
+{
+public:
+  explicit
+  VoidParameters(const Block& wire)
+  {
+    wireDecode(wire);
+  }
+
+  Block
+  wireEncode() const final
+  {
+    return Block(128);
+  }
+
+  void
+  wireDecode(const Block& wire) final
+  {
+    if (wire.type() != 128)
+      BOOST_THROW_EXCEPTION(tlv::Error("Expecting TLV type 128"));
+  }
+};
+
+static Authorization
+makeTestAuthorization()
+{
+  return [] (const Name& prefix,
+             const Interest& interest,
+             const ControlParameters* params,
+             AcceptContinuation accept,
+             RejectContinuation reject) {
+    if (interest.getName()[-1] == name::Component("valid")) {
+      accept("");
+    }
+    else {
+      if (interest.getName()[-1] == name::Component("silent")) {
+        reject(RejectReply::SILENT);
+      }
+      else {
+        reject(RejectReply::STATUS403);
+      }
+    }
+  };
+}
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_FIXTURE_TEST_SUITE(TestDispatcher, DispatcherFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(dispatcher
+                         .addControlCommand<VoidParameters>("test/1", makeAcceptAllAuthorization(),
+                                                            bind([] { return true; }),
+                                                            bind([]{})));
+  BOOST_CHECK_NO_THROW(dispatcher
+                         .addControlCommand<VoidParameters>("test/2", makeAcceptAllAuthorization(),
+                                                            bind([] { return true; }),
+                                                            bind([]{})));
+
+  BOOST_CHECK_THROW(dispatcher
+                      .addControlCommand<VoidParameters>("test", makeAcceptAllAuthorization(),
+                                                         bind([] { return true; }),
+                                                         bind([]{})),
+                    std::out_of_range);
+
+  BOOST_CHECK_NO_THROW(dispatcher.addStatusDataset("status/1",
+                                                   makeAcceptAllAuthorization(), bind([]{})));
+  BOOST_CHECK_NO_THROW(dispatcher.addStatusDataset("status/2",
+                                                   makeAcceptAllAuthorization(), bind([]{})));
+  BOOST_CHECK_THROW(dispatcher.addStatusDataset("status",
+                                                makeAcceptAllAuthorization(), bind([]{})),
+                    std::out_of_range);
+
+  BOOST_CHECK_NO_THROW(dispatcher.addNotificationStream("stream/1"));
+  BOOST_CHECK_NO_THROW(dispatcher.addNotificationStream("stream/2"));
+  BOOST_CHECK_THROW(dispatcher.addNotificationStream("stream"), std::out_of_range);
+
+
+  BOOST_CHECK_NO_THROW(dispatcher.addTopPrefix("/root/1"));
+  BOOST_CHECK_NO_THROW(dispatcher.addTopPrefix("/root/2"));
+  BOOST_CHECK_THROW(dispatcher.addTopPrefix("/root"), std::out_of_range);
+
+  BOOST_CHECK_THROW(dispatcher
+                      .addControlCommand<VoidParameters>("test/3", makeAcceptAllAuthorization(),
+                                                         bind([] { return true; }),
+                                                         bind([]{})),
+                    std::domain_error);
+
+  BOOST_CHECK_THROW(dispatcher.addStatusDataset("status/3",
+                                                makeAcceptAllAuthorization(), bind([]{})),
+                    std::domain_error);
+
+  BOOST_CHECK_THROW(dispatcher.addNotificationStream("stream/3"), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(AddRemoveTopPrefix)
+{
+  std::map<std::string, size_t> nCallbackCalled;
+  dispatcher
+    .addControlCommand<VoidParameters>("test/1", makeAcceptAllAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled["test/1"]; }));
+
+  dispatcher
+    .addControlCommand<VoidParameters>("test/2", makeAcceptAllAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled["test/2"]; }));
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 0);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 0);
+
+  dispatcher.addTopPrefix("/root/1");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 0);
+
+  face.receive(*makeInterest("/root/1/test/2/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 1);
+
+  face.receive(*makeInterest("/root/2/test/1/%80%00"));
+  face.receive(*makeInterest("/root/2/test/2/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 1);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/2"], 1);
+
+  dispatcher.addTopPrefix("/root/2");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 2);
+
+  face.receive(*makeInterest("/root/2/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 3);
+
+  dispatcher.removeTopPrefix("/root/1");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/1/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 3);
+
+  face.receive(*makeInterest("/root/2/test/1/%80%00"));
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled["test/1"], 4);
+}
+
+BOOST_AUTO_TEST_CASE(ControlCommand)
+{
+  size_t nCallbackCalled = 0;
+  dispatcher
+    .addControlCommand<VoidParameters>("test",
+                                       makeTestAuthorization(),
+                                       bind([] { return true; }),
+                                       bind([&nCallbackCalled] { ++nCallbackCalled; }));
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+  face.sentData.clear();
+
+  face.receive(*makeInterest("/root/test/%80%00")); // returns 403
+  face.receive(*makeInterest("/root/test/%80%00/invalid")); // returns 403
+  face.receive(*makeInterest("/root/test/%80%00/silent")); // silently ignored
+  face.receive(*makeInterest("/root/test/.../invalid")); // silently ignored (wrong format)
+  face.receive(*makeInterest("/root/test/.../valid"));  // silently ignored (wrong format)
+  advanceClocks(1_ms, 20);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 0);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 2);
+
+  BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 403);
+  BOOST_CHECK_EQUAL(face.sentData[1].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[1].getContent().blockFromValue()).getCode(), 403);
+
+  face.receive(*makeInterest("/root/test/%80%00/valid"));
+  advanceClocks(1_ms, 10);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 1);
+}
+
+class StatefulParameters : public mgmt::ControlParameters
+{
+public:
+  explicit
+  StatefulParameters(const Block& wire)
+  {
+    wireDecode(wire);
+  }
+
+  Block
+  wireEncode() const final
+  {
+    return Block();
+  }
+
+  void
+  wireDecode(const Block& wire) final
+  {
+    m_state = EXPECTED_STATE;
+  }
+
+  bool
+  check() const
+  {
+    return m_state == EXPECTED_STATE;
+  }
+
+private:
+  static constexpr int EXPECTED_STATE = 12602;
+  int m_state = 0;
+};
+
+BOOST_AUTO_TEST_CASE(ControlCommandAsyncAuthorization) // Bug 4059
+{
+  AcceptContinuation authorizationAccept;
+  auto authorization =
+    [&authorizationAccept] (const Name& prefix, const Interest& interest, const ControlParameters* params,
+        AcceptContinuation accept, RejectContinuation reject) {
+      authorizationAccept = accept;
+    };
+
+  auto validateParameters =
+    [] (const ControlParameters& params) {
+      return dynamic_cast<const StatefulParameters&>(params).check();
+    };
+
+  size_t nCallbackCalled = 0;
+  dispatcher
+    .addControlCommand<StatefulParameters>("test",
+                                           authorization,
+                                           validateParameters,
+                                           bind([&nCallbackCalled] { ++nCallbackCalled; }));
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+
+  face.receive(*makeInterest("/root/test/%80%00"));
+  BOOST_CHECK_EQUAL(nCallbackCalled, 0);
+  BOOST_REQUIRE(authorizationAccept != nullptr);
+
+  advanceClocks(1_ms);
+  authorizationAccept("");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(nCallbackCalled, 1);
+}
+
+BOOST_AUTO_TEST_CASE(StatusDataset)
+{
+  const uint8_t smallBuf[] = {0x81, 0x01, 0x01};
+  const Block smallBlock(smallBuf, sizeof(smallBuf));
+  Block largeBlock;
+  {
+    EncodingBuffer encoder;
+    for (size_t i = 0; i < 2500; ++i) {
+      encoder.prependByte(1);
+    }
+    encoder.prependVarNumber(2500);
+    encoder.prependVarNumber(129);
+    largeBlock = encoder.block();
+  }
+
+  dispatcher.addStatusDataset("test/small",
+                              makeTestAuthorization(),
+                              [&smallBlock] (const Name& prefix, const Interest& interest,
+                                             StatusDatasetContext& context) {
+                                context.append(smallBlock);
+                                context.append(smallBlock);
+                                context.append(smallBlock);
+                                context.end();
+                              });
+
+  dispatcher.addStatusDataset("test/large",
+                              makeTestAuthorization(),
+                              [&largeBlock] (const Name& prefix, const Interest& interest,
+                                             StatusDatasetContext& context) {
+                                context.append(largeBlock);
+                                context.append(largeBlock);
+                                context.append(largeBlock);
+                                context.end();
+                              });
+
+  dispatcher.addStatusDataset("test/reject",
+                              makeTestAuthorization(),
+                              [] (const Name& prefix, const Interest& interest,
+                                  StatusDatasetContext& context) {
+                                context.reject();
+                              });
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+  face.sentData.clear();
+
+  face.receive(*makeInterest("/root/test/small/%80%00")); // returns 403
+  face.receive(*makeInterest("/root/test/small/%80%00/invalid")); // returns 403
+  face.receive(*makeInterest("/root/test/small/%80%00/silent")); // silently ignored
+  advanceClocks(1_ms, 20);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 2);
+
+  BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 403);
+  BOOST_CHECK_EQUAL(face.sentData[1].getContentType(), tlv::ContentType_Blob);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[1].getContent().blockFromValue()).getCode(), 403);
+
+  face.sentData.clear();
+
+  auto interestSmall = *makeInterest("/root/test/small/valid", true);
+  face.receive(interestSmall);
+  advanceClocks(1_ms, 10);
+
+  // one data packet is generated and sent to both places
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 1);
+
+  auto fetchedData = storage.find(interestSmall);
+  BOOST_REQUIRE(fetchedData != nullptr);
+  BOOST_CHECK_EQUAL(face.sentData[0].wireEncode(), fetchedData->wireEncode());
+
+  face.receive(*makeInterest(Name("/root/test/small/valid").appendVersion(10))); // should be ignored
+  face.receive(*makeInterest(Name("/root/test/small/valid").appendSegment(20))); // should be ignored
+  advanceClocks(1_ms, 10);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 1);
+
+  Block content = face.sentData[0].getContent();
+  BOOST_CHECK_NO_THROW(content.parse());
+
+  BOOST_REQUIRE_EQUAL(content.elements().size(), 3);
+  BOOST_CHECK_EQUAL(content.elements()[0], smallBlock);
+  BOOST_CHECK_EQUAL(content.elements()[1], smallBlock);
+  BOOST_CHECK_EQUAL(content.elements()[2], smallBlock);
+
+  storage.erase("/", true); // clear the storage
+  face.sentData.clear();
+  face.receive(*makeInterest("/root/test/large/valid"));
+  advanceClocks(1_ms, 10);
+
+  // two data packets are generated, the first one will be sent to both places
+  // while the second one will only be inserted into the in-memory storage
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 2);
+
+  // segment0 should be sent through the face
+  const auto& component = face.sentData[0].getName().at(-1);
+  BOOST_CHECK(component.isSegment());
+  BOOST_CHECK_EQUAL(component.toSegment(), 0);
+
+  std::vector<Data> dataInStorage;
+  std::copy(storage.begin(), storage.end(), std::back_inserter(dataInStorage));
+
+  // the Data sent through the face should be the same as the first Data in the storage
+  BOOST_CHECK_EQUAL(face.sentData[0].getName(), dataInStorage[0].getName());
+  BOOST_CHECK_EQUAL(face.sentData[0].getContent(), dataInStorage[0].getContent());
+
+  content = [&dataInStorage] () -> Block {
+    EncodingBuffer encoder;
+    size_t valueLength = encoder.prependByteArray(dataInStorage[1].getContent().value(),
+                                                  dataInStorage[1].getContent().value_size());
+    valueLength += encoder.prependByteArray(dataInStorage[0].getContent().value(),
+                                            dataInStorage[0].getContent().value_size());
+    encoder.prependVarNumber(valueLength);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }();
+
+  BOOST_CHECK_NO_THROW(content.parse());
+  BOOST_REQUIRE_EQUAL(content.elements().size(), 3);
+  BOOST_CHECK_EQUAL(content.elements()[0], largeBlock);
+  BOOST_CHECK_EQUAL(content.elements()[1], largeBlock);
+  BOOST_CHECK_EQUAL(content.elements()[2], largeBlock);
+
+  storage.erase("/", true);// clear the storage
+  face.sentData.clear();
+  face.receive(*makeInterest("/root/test/reject/%80%00/valid")); // returns nack
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData[0].getContentType(), tlv::ContentType_Nack);
+  BOOST_CHECK_EQUAL(ControlResponse(face.sentData[0].getContent().blockFromValue()).getCode(), 400);
+  BOOST_CHECK_EQUAL(storage.size(), 0); // the nack packet will not be inserted into the in-memory storage
+}
+
+BOOST_AUTO_TEST_CASE(NotificationStream)
+{
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  const Block block(buf, sizeof(buf));
+  auto post = dispatcher.addNotificationStream("test");
+
+  post(block);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+
+  dispatcher.addTopPrefix("/root");
+  advanceClocks(1_ms);
+  face.sentData.clear();
+
+  post(block);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(storage.size(), 1);
+
+  post(block);
+  post(block);
+  post(block);
+  advanceClocks(1_ms, 10);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 4);
+  BOOST_CHECK_EQUAL(face.sentData[0].getName(), "/root/test/%FE%00");
+  BOOST_CHECK_EQUAL(face.sentData[1].getName(), "/root/test/%FE%01");
+  BOOST_CHECK_EQUAL(face.sentData[2].getName(), "/root/test/%FE%02");
+  BOOST_CHECK_EQUAL(face.sentData[3].getName(), "/root/test/%FE%03");
+
+  BOOST_CHECK_EQUAL(face.sentData[0].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(face.sentData[1].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(face.sentData[2].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(face.sentData[3].getContent().blockFromValue(), block);
+
+  // each version of notification will be sent to both places
+  std::vector<Data> dataInStorage;
+  std::copy(storage.begin(), storage.end(), std::back_inserter(dataInStorage));
+  BOOST_REQUIRE_EQUAL(dataInStorage.size(), 4);
+  BOOST_CHECK_EQUAL(dataInStorage[0].getName(), "/root/test/%FE%00");
+  BOOST_CHECK_EQUAL(dataInStorage[1].getName(), "/root/test/%FE%01");
+  BOOST_CHECK_EQUAL(dataInStorage[2].getName(), "/root/test/%FE%02");
+  BOOST_CHECK_EQUAL(dataInStorage[3].getName(), "/root/test/%FE%03");
+
+  BOOST_CHECK_EQUAL(dataInStorage[0].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(dataInStorage[1].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(dataInStorage[2].getContent().blockFromValue(), block);
+  BOOST_CHECK_EQUAL(dataInStorage[3].getContent().blockFromValue(), block);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDispatcher
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/channel-status.t.cpp b/tests/unit/mgmt/nfd/channel-status.t.cpp
new file mode 100644
index 0000000..b3afde8
--- /dev/null
+++ b/tests/unit/mgmt/nfd/channel-status.t.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/channel-status.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestChannelStatus)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ChannelStatus status1;
+  status1.setLocalUri("udp4://192.168.2.1");
+  Block wire = status1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x82, 0x14, 0x81, 0x12, 0x75, 0x64, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  ChannelStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  ChannelStatus status1, status2;
+
+  status1.setLocalUri("udp4://127.0.0.1:6363");
+  status2 = status1;
+  BOOST_CHECK_EQUAL(status1, status2);
+
+  status2.setLocalUri("dev://eth0");
+  BOOST_CHECK_NE(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  ChannelStatus status;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Channel(LocalUri: )");
+
+  status.setLocalUri("udp4://127.0.0.1:6363");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Channel(LocalUri: udp4://127.0.0.1:6363)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestChannelStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/command-options.t.cpp b/tests/unit/mgmt/nfd/command-options.t.cpp
new file mode 100644
index 0000000..e8fe76e
--- /dev/null
+++ b/tests/unit/mgmt/nfd/command-options.t.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/command-options.hpp"
+#include "security/signing-helpers.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestCommandOptions)
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getTimeout(), CommandOptions::DEFAULT_TIMEOUT);
+
+  co.setTimeout(7414_ms);
+  BOOST_CHECK_EQUAL(co.getTimeout(), 7414_ms);
+
+  BOOST_CHECK_THROW(co.setTimeout(time::milliseconds::zero()), std::out_of_range);
+  BOOST_CHECK_THROW(co.setTimeout(time::milliseconds(-1)), std::out_of_range);
+  BOOST_CHECK_EQUAL(co.getTimeout(), 7414_ms); // unchanged after throw
+
+  co.setTimeout(1_ms);
+  BOOST_CHECK_EQUAL(co.getTimeout(), 1_ms);
+}
+
+BOOST_AUTO_TEST_CASE(Prefix)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getPrefix(), CommandOptions::DEFAULT_PREFIX);
+
+  co.setPrefix(Name()); // empty Name is okay
+  BOOST_CHECK_EQUAL(co.getPrefix(), Name());
+
+  co.setPrefix("ndn:/localhop/net/example/nfd");
+  BOOST_CHECK_EQUAL(co.getPrefix(), Name("ndn:/localhop/net/example/nfd"));
+}
+
+BOOST_AUTO_TEST_CASE(SigningInfo)
+{
+  CommandOptions co;
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerType(), security::SigningInfo::SIGNER_TYPE_NULL);
+
+  co.setSigningInfo(signingByIdentity("ndn:/tmp/identity"));
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerType(), security::SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(co.getSigningInfo().getSignerName(), "ndn:/tmp/identity");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandOptions
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/control-command.t.cpp b/tests/unit/mgmt/nfd/control-command.t.cpp
new file mode 100644
index 0000000..4db3752
--- /dev/null
+++ b/tests/unit/mgmt/nfd/control-command.t.cpp
@@ -0,0 +1,479 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-command.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlCommand)
+
+BOOST_AUTO_TEST_CASE(FaceCreateRequest)
+{
+  FaceCreateCommand command;
+
+  // good with required fields only
+  ControlParameters p1;
+  p1.setUri("tcp4://192.0.2.1:6363");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK(Name("/PREFIX/faces/create").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good with optional fields
+  ControlParameters p2(p1);
+  p2.setLocalUri("tcp4://192.0.2.2:32114")
+    .setFacePersistency(FACE_PERSISTENCY_PERMANENT)
+    .setBaseCongestionMarkingInterval(100_ms)
+    .setDefaultCongestionThreshold(10000)
+    .setMtu(8192)
+    .setFlags(0x3)
+    .setMask(0x1);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+
+  // Uri is required
+  ControlParameters p3;
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // Name is forbidden
+  ControlParameters p4(p1);
+  p4.setName("/example");
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+
+  // Flags and Mask must be specified together
+  ControlParameters p5(p1);
+  p5.setFlags(0x3);
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+
+  ControlParameters p6(p1);
+  p6.setMask(0x1);
+  BOOST_CHECK_THROW(command.validateRequest(p6), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceCreateResponse)
+{
+  FaceCreateCommand command;
+
+  // good
+  ControlParameters p1;
+  p1.setFaceId(3208)
+    .setUri("tcp4://192.0.2.1:6363")
+    .setLocalUri("tcp4://192.0.2.2:32114")
+    .setFacePersistency(FACE_PERSISTENCY_PERMANENT)
+    .setBaseCongestionMarkingInterval(500_ns)
+    .setDefaultCongestionThreshold(12345)
+    .setMtu(2048)
+    .setFlags(0x3);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+
+  // Name is forbidden
+  ControlParameters p2(p1);
+  p2.setName("/example");
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  // Mask is forbidden
+  ControlParameters p3(p1);
+  p3.setMask(0x1);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+
+  // FaceId must be valid
+  ControlParameters p4(p1);
+  p4.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  // LocalUri is required
+  ControlParameters p5(p1);
+  p5.unsetLocalUri();
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceUpdate)
+{
+  FaceUpdateCommand command;
+
+  ControlParameters p1;
+  p1.setFaceId(0);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  p1.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  p1.setFaceId(1);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  command.applyDefaultsToRequest(p1);
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 1);
+
+  ControlParameters p2;
+  p2.setFaceId(1)
+    .setFacePersistency(FACE_PERSISTENCY_PERSISTENT)
+    .setBaseCongestionMarkingInterval(765_ns)
+    .setDefaultCongestionThreshold(54321)
+    .setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError); // Mask forbidden but present
+
+  // Flags without Mask
+  p2.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+
+  p2.setFlagBit(BIT_LOCAL_FIELDS_ENABLED, false);
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+
+  ControlParameters p3;
+  p3.setFaceId(1)
+    .setName("/ndn/name");
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+
+  ControlParameters p4;
+  p4.setFaceId(1)
+    .setUri("tcp4://192.0.2.1");
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  ControlParameters p5;
+  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK(!p5.hasFaceId());
+  command.applyDefaultsToRequest(p5);
+  BOOST_REQUIRE(p5.hasFaceId());
+  BOOST_CHECK_NO_THROW(command.validateRequest(p5));
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+  BOOST_CHECK_EQUAL(p5.getFaceId(), 0);
+
+  ControlParameters p6;
+  p6.setFaceId(1)
+    .setMtu(1024);
+  BOOST_CHECK_THROW(command.validateRequest(p6), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p6), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(FaceDestroy)
+{
+  FaceDestroyCommand command;
+
+  ControlParameters p1;
+  p1.setUri("tcp4://192.0.2.1")
+    .setFaceId(4);
+  BOOST_CHECK_THROW(command.validateRequest(p1), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  ControlParameters p2;
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setFaceId(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p3));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p3));
+  Name n3;
+  BOOST_CHECK_NO_THROW(n3 = command.getRequestName("/PREFIX", p3));
+  BOOST_CHECK(Name("ndn:/PREFIX/faces/destroy").isPrefixOf(n3));
+}
+
+BOOST_AUTO_TEST_CASE(FibAddNextHop)
+{
+  FibAddNextHopCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/fib/add-nexthop").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0)
+    .setCost(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasCost());
+  BOOST_CHECK_EQUAL(p1.getCost(), 0);
+
+  p1.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasFaceId());
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FibRemoveNextHop)
+{
+  FibRemoveNextHopCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/fib/remove-nexthop").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  p1.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasFaceId());
+  BOOST_CHECK_EQUAL(p1.getFaceId(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(CsConfigRequest)
+{
+  CsConfigCommand command;
+
+  // good empty request
+  ControlParameters p1;
+  command.validateRequest(p1);
+  BOOST_CHECK(Name("/PREFIX/cs/config").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good full request
+  ControlParameters p2;
+  p2.setCapacity(1574);
+  p2.setFlagBit(BIT_CS_ENABLE_ADMIT, true);
+  p2.setFlagBit(BIT_CS_ENABLE_SERVE, true);
+  command.validateRequest(p2);
+
+  // bad request: Flags but no Mask
+  ControlParameters p3(p2);
+  p3.unsetMask();
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // bad request: Mask but no Flags
+  ControlParameters p4(p2);
+  p4.unsetFlags();
+  BOOST_CHECK_THROW(command.validateRequest(p4), ControlCommand::ArgumentError);
+
+  // bad request: forbidden field
+  ControlParameters p5(p2);
+  p5.setName("/example");
+  BOOST_CHECK_THROW(command.validateRequest(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(CsConfigResponse)
+{
+  CsConfigCommand command;
+
+  // bad empty response
+  ControlParameters p1;
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+
+  // bad response: Mask not allowed
+  ControlParameters p2;
+  p2.setCapacity(1574);
+  p2.setFlagBit(BIT_CS_ENABLE_ADMIT, true);
+  p2.setFlagBit(BIT_CS_ENABLE_SERVE, true);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  // good response
+  ControlParameters p3(p2);
+  p3.unsetMask();
+  command.validateResponse(p3);
+}
+
+BOOST_AUTO_TEST_CASE(CsEraseRequest)
+{
+  CsEraseCommand command;
+
+  // good no-limit request
+  ControlParameters p1;
+  p1.setName("/u4LYPNU8Q");
+  command.validateRequest(p1);
+  BOOST_CHECK(Name("/PREFIX/cs/erase").isPrefixOf(command.getRequestName("/PREFIX", p1)));
+
+  // good limited request
+  ControlParameters p2;
+  p2.setName("/IMw1RaLF");
+  p2.setCount(177);
+  command.validateRequest(p2);
+
+  // bad request: zero entry
+  ControlParameters p3;
+  p3.setName("/ahMID1jcib");
+  p3.setCount(0);
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+
+  // bad request: forbidden field
+  ControlParameters p4(p2);
+  p4.setCapacity(278);
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(CsEraseResponse)
+{
+  CsEraseCommand command;
+
+  // good normal response
+  ControlParameters p1;
+  p1.setName("/TwiIwCdR");
+  p1.setCount(1);
+  command.validateResponse(p1);
+
+  // good limit exceeded request
+  ControlParameters p2;
+  p2.setName("/NMsiy44pr");
+  p2.setCapacity(360);
+  p2.setCount(360);
+  command.validateResponse(p2);
+
+  // good zero-entry response
+  ControlParameters p3;
+  p3.setName("/5f1LRPh1L");
+  p3.setCount(0);
+  command.validateResponse(p3);
+
+  // bad request: missing Count
+  ControlParameters p4(p1);
+  p4.unsetCount();
+  BOOST_CHECK_THROW(command.validateResponse(p4), ControlCommand::ArgumentError);
+
+  // bad request: zero capacity
+  ControlParameters p5(p1);
+  p5.setCapacity(0);
+  BOOST_CHECK_THROW(command.validateResponse(p5), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceSet)
+{
+  StrategyChoiceSetCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setStrategy("ndn:/strategy/P");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/set").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceUnset)
+{
+  StrategyChoiceUnsetCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/example");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/strategy-choice/unset").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setStrategy("ndn:/strategy/P");
+  BOOST_CHECK_THROW(command.validateRequest(p2), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  ControlParameters p3;
+  p3.setName("ndn:/");
+  BOOST_CHECK_THROW(command.validateRequest(p3), ControlCommand::ArgumentError);
+  BOOST_CHECK_THROW(command.validateResponse(p3), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(RibRegister)
+{
+  RibRegisterCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/");
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_THROW(command.validateResponse(p1), ControlCommand::ArgumentError);
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/register").isPrefixOf(n1));
+
+  command.applyDefaultsToRequest(p1);
+  BOOST_REQUIRE(p1.hasOrigin());
+  BOOST_CHECK_EQUAL(p1.getOrigin(), ROUTE_ORIGIN_APP);
+  BOOST_REQUIRE(p1.hasCost());
+  BOOST_CHECK_EQUAL(p1.getCost(), 0);
+  BOOST_REQUIRE(p1.hasFlags());
+  BOOST_CHECK_EQUAL(p1.getFlags(), static_cast<uint64_t>(ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(p1.hasExpirationPeriod(), false);
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(2)
+    .setCost(6);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  command.applyDefaultsToRequest(p2);
+  BOOST_CHECK_EQUAL(p2.hasExpirationPeriod(), false);
+  BOOST_CHECK_NO_THROW(command.validateResponse(p2));
+}
+
+BOOST_AUTO_TEST_CASE(RibUnregister)
+{
+  RibUnregisterCommand command;
+
+  ControlParameters p1;
+  p1.setName("ndn:/")
+    .setFaceId(22)
+    .setOrigin(ROUTE_ORIGIN_STATIC);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p1));
+  BOOST_CHECK_NO_THROW(command.validateResponse(p1));
+  Name n1;
+  BOOST_CHECK_NO_THROW(n1 = command.getRequestName("/PREFIX", p1));
+  BOOST_CHECK(Name("ndn:/PREFIX/rib/unregister").isPrefixOf(n1));
+
+  ControlParameters p2;
+  p2.setName("ndn:/example")
+    .setFaceId(0)
+    .setOrigin(ROUTE_ORIGIN_APP);
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  p2.setFaceId(INVALID_FACE_ID);
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+
+  p2.unsetFaceId();
+  BOOST_CHECK_NO_THROW(command.validateRequest(p2));
+  BOOST_CHECK_THROW(command.validateResponse(p2), ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlCommand
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/control-parameters.t.cpp b/tests/unit/mgmt/nfd/control-parameters.t.cpp
new file mode 100644
index 0000000..6030060
--- /dev/null
+++ b/tests/unit/mgmt/nfd/control-parameters.t.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/control-parameters.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestControlParameters)
+
+BOOST_AUTO_TEST_CASE(Fields)
+{
+  ControlParameters input;
+
+  ControlParameters decoded(input.wireEncode());
+  BOOST_CHECK_EQUAL(decoded.hasName(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCapacity(), false);
+  BOOST_CHECK_EQUAL(decoded.hasCount(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), false);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), false);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), false);
+
+  input.setName("/name");
+  input.setFaceId(2634);
+  input.setUri("udp4://192.0.2.1:6363");
+  input.setLocalUri("udp4://192.0.2.2:6363");
+  input.setOrigin(ROUTE_ORIGIN_NLSR);
+  input.setCost(1388);
+  input.setCapacity(2632);
+  input.setCount(3100);
+  input.setFlags(0xAFC4);
+  input.setMask(0xF7A1);
+  input.setStrategy("/strategy-name");
+  input.setExpirationPeriod(1800000_ms);
+  input.setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  decoded.wireDecode(input.wireEncode());
+  BOOST_CHECK_EQUAL(decoded.hasName(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFaceId(), true);
+  BOOST_CHECK_EQUAL(decoded.hasUri(), true);
+  BOOST_CHECK_EQUAL(decoded.hasLocalUri(), true);
+  BOOST_CHECK_EQUAL(decoded.hasOrigin(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCost(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCapacity(), true);
+  BOOST_CHECK_EQUAL(decoded.hasCount(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFlags(), true);
+  BOOST_CHECK_EQUAL(decoded.hasMask(), true);
+  BOOST_CHECK_EQUAL(decoded.hasStrategy(), true);
+  BOOST_CHECK_EQUAL(decoded.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(decoded.hasFacePersistency(), true);
+
+  BOOST_CHECK_EQUAL(decoded.getName(), "/name");
+  BOOST_CHECK_EQUAL(decoded.getFaceId(), 2634);
+  BOOST_CHECK_EQUAL(decoded.getUri(), "udp4://192.0.2.1:6363");
+  BOOST_CHECK_EQUAL(decoded.getLocalUri(), "udp4://192.0.2.2:6363");
+  BOOST_CHECK_EQUAL(decoded.getOrigin(), ROUTE_ORIGIN_NLSR);
+  BOOST_CHECK_EQUAL(decoded.getCost(), 1388);
+  BOOST_CHECK_EQUAL(decoded.getCapacity(), 2632);
+  BOOST_CHECK_EQUAL(decoded.getCount(), 3100);
+  BOOST_CHECK_EQUAL(decoded.getFlags(), 0xAFC4);
+  BOOST_CHECK_EQUAL(decoded.getMask(), 0xF7A1);
+  BOOST_CHECK_EQUAL(decoded.getStrategy(), "/strategy-name");
+  BOOST_CHECK_EQUAL(decoded.getExpirationPeriod(), 1800000_ms);
+  BOOST_CHECK_EQUAL(decoded.getFacePersistency(), FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  input.unsetName();
+  input.unsetFaceId();
+  input.unsetUri();
+  input.unsetLocalUri();
+  input.unsetOrigin();
+  input.unsetCost();
+  input.unsetCapacity();
+  input.unsetCount();
+  input.unsetFlags();
+  input.unsetMask();
+  input.unsetStrategy();
+  input.unsetExpirationPeriod();
+  input.unsetFacePersistency();
+  BOOST_CHECK_EQUAL(input.hasName(), false);
+  BOOST_CHECK_EQUAL(input.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(input.hasUri(), false);
+  BOOST_CHECK_EQUAL(input.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(input.hasOrigin(), false);
+  BOOST_CHECK_EQUAL(input.hasCost(), false);
+  BOOST_CHECK_EQUAL(input.hasCapacity(), false);
+  BOOST_CHECK_EQUAL(input.hasCount(), false);
+  BOOST_CHECK_EQUAL(input.hasFlags(), false);
+  BOOST_CHECK_EQUAL(input.hasMask(), false);
+  BOOST_CHECK_EQUAL(input.hasStrategy(), false);
+  BOOST_CHECK_EQUAL(input.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(input.hasFacePersistency(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FlagsAndMask)
+{
+  ControlParameters p;
+
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  // Set bit 2 to true (change Mask)
+  p.setFlagBit(2, true);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getFlags(), 4);
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 3 to true (no change to Mask)
+  p.setFlagBit(3, true, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 = 4
+  BOOST_CHECK_EQUAL(p.getMask(), 4);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+
+  // Set bit 1 to false (change Mask)
+  p.setFlagBit(1, false);
+  // 2^3 + 2^2 = 12
+  BOOST_CHECK_EQUAL(p.getFlags(), 12);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 2 to false (change Mask)
+  p.setFlagBit(2, false);
+  // 2^3 = 8
+  BOOST_CHECK_EQUAL(p.getFlags(), 8);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+  BOOST_CHECK(p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+
+  // Set bit 0 to true (change Mask)
+  p.setFlagBit(0, true);
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 + 2^0 = 7
+  BOOST_CHECK_EQUAL(p.getMask(), 7);
+  BOOST_CHECK(p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 0
+  p.unsetFlagBit(0);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(p.hasFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(p.getFlagBit(0));
+
+  // Unset bit 3 (already unset in Mask, so no change)
+  p.unsetFlagBit(3);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^2 + 2^1 = 6
+  BOOST_CHECK_EQUAL(p.getMask(), 6);
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(p.getFlagBit(3));
+
+  // Unset bit 2
+  p.unsetFlagBit(2);
+  BOOST_REQUIRE(p.hasFlags());
+  BOOST_REQUIRE(p.hasMask());
+  // 2^3 + 2^0 = 9
+  BOOST_CHECK_EQUAL(p.getFlags(), 9);
+  // 2^1 = 2
+  BOOST_CHECK_EQUAL(p.getMask(), 2);
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+
+  // Unset bit 1
+  // Flags and Mask fields will be deleted as Mask is now 0
+  p.unsetFlagBit(1);
+  BOOST_CHECK(!p.hasFlags());
+  BOOST_CHECK(!p.hasMask());
+  BOOST_CHECK(!p.hasFlagBit(3));
+  BOOST_CHECK(!p.getFlagBit(3));
+  BOOST_CHECK(!p.hasFlagBit(2));
+  BOOST_CHECK(!p.getFlagBit(2));
+  BOOST_CHECK(!p.hasFlagBit(1));
+  BOOST_CHECK(!p.getFlagBit(1));
+  BOOST_CHECK(!p.hasFlagBit(0));
+  BOOST_CHECK(!p.getFlagBit(0));
+
+  BOOST_CHECK_THROW(p.setFlagBit(64, true), std::out_of_range);
+  BOOST_CHECK_THROW(p.unsetFlagBit(64), std::out_of_range);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestControlParameters
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/controller-fixture.hpp b/tests/unit/mgmt/nfd/controller-fixture.hpp
new file mode 100644
index 0000000..0d14296
--- /dev/null
+++ b/tests/unit/mgmt/nfd/controller-fixture.hpp
@@ -0,0 +1,90 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
+#define NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
+
+#include "mgmt/nfd/controller.hpp"
+#include "util/dummy-client-face.hpp"
+#include "security/v2/certificate-fetcher-offline.hpp"
+
+#include "boost-test.hpp"
+#include "dummy-validator.hpp"
+#include "../../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+class ControllerFixture : public IdentityManagementTimeFixture
+{
+protected:
+  ControllerFixture()
+    : face(io, m_keyChain)
+    , m_validator(true)
+    , controller(face, m_keyChain, m_validator)
+    , commandFailCallback(bind(&ControllerFixture::recordCommandFail, this, _1))
+    , datasetFailCallback(bind(&ControllerFixture::recordDatasetFail, this, _1, _2))
+  {
+    Name identityName("/localhost/ControllerFixture");
+    m_keyChain.setDefaultIdentity(this->addIdentity(identityName));
+  }
+
+  /** \brief controls whether Controller's validator should accept or reject validation requests
+   *
+   *  Initially, the validator accepts all requests.
+   *  Setting \p false causes validator to reject all requests.
+   */
+  void
+  setValidationResult(bool shouldAccept)
+  {
+    m_validator.getPolicy().setResult(shouldAccept);
+  }
+
+private:
+  void
+  recordCommandFail(const ControlResponse& response)
+  {
+    failCodes.push_back(response.getCode());
+  }
+
+  void
+  recordDatasetFail(uint32_t code, const std::string& reason)
+  {
+    failCodes.push_back(code);
+  }
+
+protected:
+  ndn::util::DummyClientFace face;
+  DummyValidator m_validator;
+  Controller controller;
+  Controller::CommandFailCallback commandFailCallback;
+  Controller::DatasetFailCallback datasetFailCallback;
+  std::vector<uint32_t> failCodes;
+};
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
+
+#endif // NDN_TESTS_MGMT_NFD_CONTROLLER_FIXTURE_HPP
diff --git a/tests/unit/mgmt/nfd/controller.t.cpp b/tests/unit/mgmt/nfd/controller.t.cpp
new file mode 100644
index 0000000..e9a3c4f
--- /dev/null
+++ b/tests/unit/mgmt/nfd/controller.t.cpp
@@ -0,0 +1,273 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/controller.hpp"
+#include "mgmt/nfd/control-response.hpp"
+
+#include "controller-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+
+class CommandFixture : public ControllerFixture
+{
+protected:
+  CommandFixture()
+    : succeedCallback(bind(&CommandFixture::succeed, this, _1))
+  {
+  }
+
+  void
+  respond(const ControlResponse& responsePayload)
+  {
+    auto responseData = makeData(face.sentInterests.at(0).getName());
+    responseData->setContent(responsePayload.wireEncode());
+    face.receive(*responseData);
+    this->advanceClocks(1_ms);
+  }
+
+private:
+  void
+  succeed(const ControlParameters& parameters)
+  {
+    succeeds.push_back(parameters);
+  }
+
+protected:
+  Controller::CommandSucceedCallback succeedCallback;
+  std::vector<ControlParameters> succeeds;
+};
+
+// This test suite focuses on ControlCommand functionality of Controller.
+// Individual commands are tested in nfd-control-command.t.cpp
+// StatusDataset functionality is tested in nfd-status-dataset.t.cpp
+BOOST_FIXTURE_TEST_SUITE(TestController, CommandFixture)
+
+static ControlParameters
+makeFaceCreateResponse()
+{
+  ControlParameters resp;
+  resp.setFaceId(22)
+      .setUri("tcp4://192.0.2.1:6363")
+      .setLocalUri("tcp4://192.0.2.2:10847")
+      .setFacePersistency(ndn::nfd::FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+      .setFlags(0x7);
+  return resp;
+}
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(Name("/localhost/nfd/faces/create").isPrefixOf(requestInterest.getName()));
+  // 9 components: ndn:/localhost/nfd/faces/create/<parameters>/<signed Interest x4>
+  BOOST_REQUIRE_EQUAL(requestInterest.getName().size(), 9);
+  ControlParameters request;
+  // 4th component: <parameters>
+  BOOST_REQUIRE_NO_THROW(request.wireDecode(requestInterest.getName().at(4).blockFromValue()));
+  BOOST_CHECK_NO_THROW(command.validateRequest(request));
+  BOOST_CHECK_EQUAL(request.getUri(), parameters.getUri());
+  BOOST_CHECK_EQUAL(requestInterest.getInterestLifetime(), CommandOptions::DEFAULT_TIMEOUT);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+  BOOST_REQUIRE_EQUAL(succeeds.size(), 1);
+  BOOST_CHECK_EQUAL(succeeds.back().getUri(), responseBody.getUri());
+  BOOST_CHECK_EQUAL(succeeds.back().getFaceId(), responseBody.getFaceId());
+}
+
+BOOST_AUTO_TEST_CASE(SuccessNoCallback)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, nullptr, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(OptionsPrefix)
+{
+  ControlParameters parameters;
+  parameters.setName("/ndn/com/example");
+  parameters.setFaceId(400);
+
+  CommandOptions options;
+  options.setPrefix("/localhop/net/example/router1/nfd");
+
+  controller.start<RibRegisterCommand>(parameters, succeedCallback, commandFailCallback, options);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  FaceCreateCommand command;
+  BOOST_CHECK(Name("/localhop/net/example/router1/nfd/rib/register").isPrefixOf(requestInterest.getName()));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidRequest)
+{
+  ControlParameters parameters;
+  parameters.setName("ndn:/should-not-have-this-field");
+  // Uri is missing
+
+  BOOST_CHECK_THROW(controller.start<FaceCreateCommand>(
+                      parameters, succeedCallback, commandFailCallback),
+                    ControlCommand::ArgumentError);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  this->setValidationResult(false);
+
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_VALIDATION);
+}
+
+BOOST_AUTO_TEST_CASE(ErrorCode)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlResponse responsePayload(401, "Not Authenticated");
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), 401);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidResponse)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  ControlParameters responseBody = makeFaceCreateResponse();
+  responseBody.unsetFaceId() // FaceId is missing
+              .setName("ndn:/should-not-have-this-field");
+  ControlResponse responsePayload(201, "created");
+  responsePayload.setBody(responseBody.wireEncode());
+  this->respond(responsePayload);
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  const Interest& requestInterest = face.sentInterests[0];
+
+  auto responseNack = makeNack(requestInterest, lp::NackReason::NO_ROUTE);
+  face.receive(responseNack);
+  this->advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_NACK);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  CommandOptions options;
+  options.setTimeout(50_ms);
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, commandFailCallback, options);
+  this->advanceClocks(1_ms); // express Interest
+  this->advanceClocks(51_ms); // timeout
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_TIMEOUT);
+}
+
+BOOST_AUTO_TEST_CASE(FailureNoCallback)
+{
+  ControlParameters parameters;
+  parameters.setUri("tcp4://192.0.2.1:6363");
+
+  CommandOptions options;
+  options.setTimeout(50_ms);
+
+  controller.start<FaceCreateCommand>(parameters, succeedCallback, nullptr, options);
+  this->advanceClocks(1_ms); // express Interest
+  this->advanceClocks(51_ms); // timeout
+
+  BOOST_CHECK_EQUAL(succeeds.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestController
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/cs-info.t.cpp b/tests/unit/mgmt/nfd/cs-info.t.cpp
new file mode 100644
index 0000000..46a0ed3
--- /dev/null
+++ b/tests/unit/mgmt/nfd/cs-info.t.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/cs-info.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestCsInfo)
+
+static CsInfo
+makeCsInfo()
+{
+  return CsInfo()
+    .setCapacity(20177)
+    .setEnableAdmit(false)
+    .setEnableServe(true)
+    .setNEntries(5509)
+    .setNHits(12951)
+    .setNMisses(28179);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  CsInfo csi1 = makeCsInfo();
+  Block wire = csi1.wireEncode();
+
+  static const uint8_t EXPECTED[] = {
+    0x80, 0x13, // CsInfo
+          0x83, 0x02, 0x4E, 0xD1, // Capacity
+          0x6C, 0x01, 0x02,       // Flags
+          0x87, 0x02, 0x15, 0x85, // NCsEntries
+          0x81, 0x02, 0x32, 0x97, // NHits
+          0x82, 0x02, 0x6E, 0x13, // NMisses
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire.begin(), wire.end(), EXPECTED, EXPECTED + sizeof(EXPECTED));
+
+  CsInfo csi2(wire);
+  BOOST_CHECK_EQUAL(csi2.getCapacity(), 20177);
+  BOOST_CHECK_EQUAL(csi2.getEnableAdmit(), false);
+  BOOST_CHECK_EQUAL(csi2.getEnableServe(), true);
+  BOOST_CHECK_EQUAL(csi2.getNEntries(), 5509);
+  BOOST_CHECK_EQUAL(csi2.getNHits(), 12951);
+  BOOST_CHECK_EQUAL(csi2.getNMisses(), 28179);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  CsInfo csi1, csi2;
+  BOOST_CHECK_EQUAL(csi1, csi2);
+
+  csi1 = makeCsInfo();
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+  BOOST_CHECK_EQUAL(csi1, csi2);
+
+  csi2.setCapacity(csi2.getCapacity() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setEnableAdmit(!csi2.getEnableAdmit());
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setEnableServe(!csi2.getEnableServe());
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setNEntries(csi2.getNEntries() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setNHits(csi2.getNHits() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+
+  csi2.setNMisses(csi2.getNMisses() + 1);
+  BOOST_CHECK_NE(csi1, csi2);
+  csi2 = csi1;
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  CsInfo csi;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(csi),
+    "CS: 0 entries, 0 max, admit disabled, serve disabled, 0 hits, 0 misses");
+
+  csi = makeCsInfo();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(csi),
+    "CS: 5509 entries, 20177 max, admit disabled, serve enabled, 12951 hits, 28179 misses");
+
+  csi.setEnableAdmit(true).setNHits(1).setNMisses(1);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(csi),
+    "CS: 5509 entries, 20177 max, admit enabled, serve enabled, 1 hit, 1 miss");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCsInfo
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/face-event-notification.t.cpp b/tests/unit/mgmt/nfd/face-event-notification.t.cpp
new file mode 100644
index 0000000..4da762b
--- /dev/null
+++ b/tests/unit/mgmt/nfd/face-event-notification.t.cpp
@@ -0,0 +1,269 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-event-notification.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceEventNotification)
+
+BOOST_AUTO_TEST_CASE(Traits)
+{
+  FaceEventNotification notification;
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFaceScope(FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFacePersistency(FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setFacePersistency(FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_POINT_TO_POINT);
+
+  notification.setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(notification.getFaceScope(), FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(notification.getFacePersistency(), FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(notification.getLinkType(), LINK_TYPE_MULTI_ACCESS);
+}
+
+BOOST_AUTO_TEST_CASE(Created)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_CREATED)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x3);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x01, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x03,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: created,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x3\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Destroyed)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_DESTROYED)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x4);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x02, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x04,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: destroyed,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x4\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Up)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_UP)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x05);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x03, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x05,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: up,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x5\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Down)
+{
+  FaceEventNotification notification1;
+  notification1.setKind(FACE_EVENT_DOWN)
+               .setFaceId(20)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363")
+               .setFaceScope(FACE_SCOPE_LOCAL)
+               .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+               .setLinkType(LINK_TYPE_MULTI_ACCESS)
+               .setFlags(0x06);
+  Block wire = notification1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0xc0, 0x41, 0xc1, 0x01, 0x04, 0x69, 0x01, 0x14, 0x72, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x81, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f,
+    0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32,
+    0x3a, 0x36, 0x33, 0x36, 0x33, 0x84, 0x01, 0x01, 0x85, 0x01,
+    0x01, 0x86, 0x01, 0x01, 0x6c, 0x01, 0x06,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceEventNotification notification2(wire);
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(notification2),
+                    "FaceEvent(Kind: down,\n"
+                    "          FaceId: 20,\n"
+                    "          RemoteUri: tcp4://192.0.2.1:55555,\n"
+                    "          LocalUri: tcp4://192.0.2.2:6363,\n"
+                    "          FaceScope: local,\n"
+                    "          FacePersistency: on-demand,\n"
+                    "          LinkType: multi-access,\n"
+                    "          Flags: 0x6\n"
+                    "          )");
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  FaceEventNotification notification1, notification2;
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  notification1.setKind(FACE_EVENT_CREATED)
+               .setFaceId(123)
+               .setRemoteUri("tcp4://192.0.2.1:55555")
+               .setLocalUri("tcp4://192.0.2.2:6363");
+  notification2 = notification1;
+  BOOST_CHECK_EQUAL(notification1, notification2);
+
+  notification2.setFaceId(42);
+  BOOST_CHECK_NE(notification1, notification2);
+
+  notification2 = notification1;
+  notification2.setKind(FACE_EVENT_DESTROYED);
+  BOOST_CHECK_NE(notification1, notification2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceEventNotification
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/face-query-filter.t.cpp b/tests/unit/mgmt/nfd/face-query-filter.t.cpp
new file mode 100644
index 0000000..3b9b2a2
--- /dev/null
+++ b/tests/unit/mgmt/nfd/face-query-filter.t.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-query-filter.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceQueryFilter)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  FaceQueryFilter filter1;
+  BOOST_CHECK_EQUAL(filter1.hasFaceId(), false);
+  BOOST_CHECK_EQUAL(filter1.hasUriScheme(), false);
+  BOOST_CHECK_EQUAL(filter1.hasRemoteUri(), false);
+  BOOST_CHECK_EQUAL(filter1.hasLocalUri(), false);
+  BOOST_CHECK_EQUAL(filter1.hasFaceScope(), false);
+  BOOST_CHECK_EQUAL(filter1.hasFacePersistency(), false);
+  BOOST_CHECK_EQUAL(filter1.hasLinkType(), false);
+
+  filter1.setFaceId(100)
+         .setUriScheme("tcp4")
+         .setRemoteUri("tcp4://192.0.2.1:6363")
+         .setLocalUri("tcp4://192.0.2.2:55555")
+         .setFaceScope(FACE_SCOPE_LOCAL)
+         .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+         .setLinkType(LINK_TYPE_MULTI_ACCESS);
+
+  Block wire = filter1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //  printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x96, 0x41, 0x69, 0x01, 0x64, 0x83, 0x04, 0x74, 0x63, 0x70,
+    0x34, 0x72, 0x15, 0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f,
+    0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x31, 0x3a,
+    0x36, 0x33, 0x36, 0x33, 0x81, 0x16, 0x74, 0x63, 0x70, 0x34,
+    0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e, 0x32,
+    0x2e, 0x32, 0x3a, 0x35, 0x35, 0x35, 0x35, 0x35, 0x84, 0x01,
+    0x01, 0x85, 0x01, 0x01, 0x86, 0x01, 0x01,
+  };
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceQueryFilter filter2(wire);
+  BOOST_CHECK_EQUAL(filter1.getFaceId(), filter2.getFaceId());
+  BOOST_CHECK_EQUAL(filter1.getUriScheme(), filter2.getUriScheme());
+  BOOST_CHECK_EQUAL(filter1.getRemoteUri(), filter2.getRemoteUri());
+  BOOST_CHECK_EQUAL(filter1.getLocalUri(), filter2.getLocalUri());
+  BOOST_CHECK_EQUAL(filter1.getFaceScope(), filter2.getFaceScope());
+  BOOST_CHECK_EQUAL(filter1.getFacePersistency(), filter2.getFacePersistency());
+  BOOST_CHECK_EQUAL(filter1.getLinkType(), filter2.getLinkType());
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  FaceQueryFilter filter1, filter2;
+  BOOST_CHECK_EQUAL(filter1.empty(), true);
+  BOOST_CHECK_EQUAL(filter1, filter2);
+
+  filter1.setFaceId(100)
+         .setUriScheme("tcp4")
+         .setRemoteUri("tcp4://192.0.2.1:6363")
+         .setLocalUri("tcp4://192.0.2.2:55555")
+         .setFaceScope(FACE_SCOPE_LOCAL)
+         .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+         .setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(filter1.empty(), false);
+  BOOST_CHECK_NE(filter1, filter2);
+
+  filter2 = filter1;
+  BOOST_CHECK_EQUAL(filter1, filter2);
+
+  filter2.setFaceScope(FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_NE(filter1, filter2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  FaceQueryFilter filter;
+  filter.setFaceId(100)
+        .setUriScheme("tcp4")
+        .setRemoteUri("tcp4://192.0.2.1:6363")
+        .setLocalUri("tcp4://192.0.2.2:55555")
+        .setFaceScope(FACE_SCOPE_LOCAL)
+        .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+        .setLinkType(LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(filter),
+                    "FaceQueryFilter(FaceID: 100,\n"
+                    "UriScheme: tcp4,\n"
+                    "RemoteUri: tcp4://192.0.2.1:6363,\n"
+                    "LocalUri: tcp4://192.0.2.2:55555,\n"
+                    "FaceScope: local,\n"
+                    "FacePersistency: on-demand,\n"
+                    "LinkType: multi-access,\n"
+                    ")");
+
+  filter.unsetFaceId()
+        .unsetUriScheme()
+        .unsetRemoteUri()
+        .unsetLocalUri()
+        .unsetFaceScope()
+        .unsetFacePersistency()
+        .unsetLinkType();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(filter), "FaceQueryFilter()");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceQueryFilter
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/face-status.t.cpp b/tests/unit/mgmt/nfd/face-status.t.cpp
new file mode 100644
index 0000000..0ed7aca
--- /dev/null
+++ b/tests/unit/mgmt/nfd/face-status.t.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/face-status.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFaceStatus)
+
+static FaceStatus
+makeFaceStatus()
+{
+  return FaceStatus()
+      .setFaceId(100)
+      .setRemoteUri("tcp4://192.0.2.1:6363")
+      .setLocalUri("tcp4://192.0.2.2:55555")
+      .setFaceScope(FACE_SCOPE_LOCAL)
+      .setFacePersistency(FACE_PERSISTENCY_ON_DEMAND)
+      .setLinkType(LINK_TYPE_MULTI_ACCESS)
+      .setExpirationPeriod(10_s)
+      .setBaseCongestionMarkingInterval(5_ns)
+      .setDefaultCongestionThreshold(7)
+      .setMtu(9)
+      .setNInInterests(10)
+      .setNInData(200)
+      .setNInNacks(1)
+      .setNOutInterests(3000)
+      .setNOutData(4)
+      .setNOutNacks(2)
+      .setNInBytes(1329719163)
+      .setNOutBytes(999110448)
+      .setFlags(0x7);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  FaceStatus status1 = makeFaceStatus();
+  Block wire = status1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x80, 0x6a, 0x69, 0x01, 0x64, 0x72, 0x15, 0x74, 0x63, 0x70,
+    0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x30, 0x2e,
+    0x32, 0x2e, 0x31, 0x3a, 0x36, 0x33, 0x36, 0x33, 0x81, 0x16,
+    0x74, 0x63, 0x70, 0x34, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+    0x2e, 0x30, 0x2e, 0x32, 0x2e, 0x32, 0x3a, 0x35, 0x35, 0x35,
+    0x35, 0x35, 0x6d, 0x02, 0x27, 0x10, 0x84, 0x01, 0x01, 0x85,
+    0x01, 0x01, 0x86, 0x01, 0x01, 0x87, 0x01, 0x05, 0x88, 0x01,
+    0x07, 0x89, 0x01, 0x09, 0x90, 0x01, 0x0a, 0x91, 0x01, 0xc8,
+    0x97, 0x01, 0x01, 0x92, 0x02, 0x0b, 0xb8, 0x93, 0x01, 0x04,
+    0x98, 0x01, 0x02, 0x94, 0x04, 0x4f, 0x41, 0xe7, 0x7b, 0x95,
+    0x04, 0x3b, 0x8d, 0x37, 0x30, 0x6c, 0x01, 0x07,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FaceStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  FaceStatus status1, status2;
+
+  status1 = makeFaceStatus();
+  status2 = status1;
+  BOOST_CHECK_EQUAL(status1, status2);
+
+  status2.setFaceId(42);
+  BOOST_CHECK_NE(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  FaceStatus status;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Face(FaceId: 0,\n"
+                    "     RemoteUri: ,\n"
+                    "     LocalUri: ,\n"
+                    "     ExpirationPeriod: infinite,\n"
+                    "     FaceScope: non-local,\n"
+                    "     FacePersistency: persistent,\n"
+                    "     LinkType: point-to-point,\n"
+                    "     Flags: 0x0,\n"
+                    "     Counters: {Interests: {in: 0, out: 0},\n"
+                    "                Data: {in: 0, out: 0},\n"
+                    "                Nacks: {in: 0, out: 0},\n"
+                    "                bytes: {in: 0, out: 0}}\n"
+                    "     )");
+
+  status = makeFaceStatus();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "Face(FaceId: 100,\n"
+                    "     RemoteUri: tcp4://192.0.2.1:6363,\n"
+                    "     LocalUri: tcp4://192.0.2.2:55555,\n"
+                    "     ExpirationPeriod: 10000 milliseconds,\n"
+                    "     FaceScope: local,\n"
+                    "     FacePersistency: on-demand,\n"
+                    "     LinkType: multi-access,\n"
+                    "     BaseCongestionMarkingInterval: 5 nanoseconds,\n"
+                    "     DefaultCongestionThreshold: 7 bytes,\n"
+                    "     Mtu: 9 bytes,\n"
+                    "     Flags: 0x7,\n"
+                    "     Counters: {Interests: {in: 10, out: 3000},\n"
+                    "                Data: {in: 200, out: 4},\n"
+                    "                Nacks: {in: 1, out: 2},\n"
+                    "                bytes: {in: 1329719163, out: 999110448}}\n"
+                    "     )");
+}
+
+BOOST_AUTO_TEST_CASE(ExpirationPeriod)
+{
+  FaceStatus status;
+  BOOST_CHECK_EQUAL(status.hasExpirationPeriod(), false);
+
+  status.setExpirationPeriod(1_min);
+  BOOST_REQUIRE_EQUAL(status.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(status.getExpirationPeriod(), 1_min);
+
+  status.unsetExpirationPeriod();
+  BOOST_CHECK_EQUAL(status.hasExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FlagBit)
+{
+  FaceStatus status;
+  status.setFlags(0x7);
+  BOOST_CHECK_EQUAL(status.getFlags(), 0x7);
+
+  BOOST_CHECK(status.getFlagBit(0));
+  BOOST_CHECK(status.getFlagBit(1));
+  BOOST_CHECK(status.getFlagBit(2));
+  BOOST_CHECK(!status.getFlagBit(3));
+
+  status.setFlagBit(3, true);
+  BOOST_CHECK_EQUAL(status.getFlags(), 0xf);
+  BOOST_CHECK(status.getFlagBit(3));
+
+  status.setFlagBit(1, false);
+  BOOST_CHECK_EQUAL(status.getFlags(), 0xd);
+  BOOST_CHECK(!status.getFlagBit(1));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/fib-entry.t.cpp b/tests/unit/mgmt/nfd/fib-entry.t.cpp
new file mode 100644
index 0000000..fa7ec69
--- /dev/null
+++ b/tests/unit/mgmt/nfd/fib-entry.t.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/fib-entry.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestFibEntry)
+
+static FibEntry
+makeFibEntry()
+{
+  std::vector<NextHopRecord> nexthops;
+  for (size_t i = 1; i < 4; i++) {
+    nexthops.push_back(NextHopRecord()
+                       .setFaceId(i * 10)
+                       .setCost(i * 100 + 100));
+  }
+
+  return FibEntry()
+      .setPrefix("/this/is/a/test")
+      .setNextHopRecords(nexthops.begin(), nexthops.end());
+}
+
+BOOST_AUTO_TEST_CASE(NextHopRecordEncode)
+{
+  NextHopRecord record1;
+  record1.setFaceId(10)
+      .setCost(200);
+  const Block& wire = record1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x81, 0x06, 0x69, 0x01, 0x0a, 0x6a, 0x01, 0xc8
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  NextHopRecord record2(wire);
+  BOOST_CHECK_EQUAL(record1, record2);
+}
+
+BOOST_AUTO_TEST_CASE(NextHopRecordEquality)
+{
+  NextHopRecord record1, record2;
+
+  record1.setFaceId(10)
+      .setCost(200);
+  record2 = record1;
+  BOOST_CHECK_EQUAL(record1, record2);
+
+  record2.setFaceId(42);
+  BOOST_CHECK_NE(record1, record2);
+
+  record2 = record1;
+  record2.setCost(42);
+  BOOST_CHECK_NE(record1, record2);
+}
+
+BOOST_AUTO_TEST_CASE(FibEntryNoNextHopsEncode)
+{
+  FibEntry entry1;
+  entry1.setPrefix("/this/is/a/test");
+  BOOST_REQUIRE(entry1.getNextHopRecords().empty());
+  const Block& wire = entry1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x80, 0x15, 0x07, 0x13, 0x08, 0x04, 0x74, 0x68, 0x69, 0x73,
+    0x08, 0x02, 0x69, 0x73, 0x08, 0x01, 0x61, 0x08, 0x04, 0x74,
+    0x65, 0x73, 0x74
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
+}
+
+BOOST_AUTO_TEST_CASE(FibEntryEncode)
+{
+  FibEntry entry1 = makeFibEntry();
+  NextHopRecord oneMore;
+  oneMore.setFaceId(40);
+  oneMore.setCost(500);
+  entry1.addNextHopRecord(oneMore);
+  const Block& wire = entry1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x80, 0x38, 0x07, 0x13, 0x08, 0x04, 0x74, 0x68, 0x69, 0x73, 0x08, 0x02, 0x69, 0x73, 0x08, 0x01,
+    0x61, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x81, 0x06, 0x69, 0x01, 0x0a, 0x6a, 0x01, 0xc8, 0x81,
+    0x07, 0x69, 0x01, 0x14, 0x6a, 0x02, 0x01, 0x2c, 0x81, 0x07, 0x69, 0x01, 0x1e, 0x6a, 0x02, 0x01,
+    0x90, 0x81, 0x07, 0x69, 0x01, 0x28, 0x6a, 0x02, 0x01, 0xf4
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  FibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
+}
+
+BOOST_AUTO_TEST_CASE(FibEntryEquality)
+{
+  FibEntry entry1, entry2;
+  BOOST_CHECK_EQUAL(entry1, entry2);
+
+  entry1 = entry2 = makeFibEntry();
+  BOOST_CHECK_EQUAL(entry1, entry2);
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry2.setPrefix("/another/prefix");
+  BOOST_CHECK_NE(entry1, entry2);
+
+  entry2 = entry1;
+  std::vector<NextHopRecord> empty;
+  entry2.setNextHopRecords(empty.begin(), empty.end());
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  entry2 = entry1;
+  auto nh1 = NextHopRecord()
+             .setFaceId(1)
+             .setCost(1000);
+  entry1.addNextHopRecord(nh1);
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  auto nh42 = NextHopRecord()
+              .setFaceId(42)
+              .setCost(42);
+  entry1.addNextHopRecord(nh42);
+  entry2.addNextHopRecord(nh42)
+      .addNextHopRecord(nh1);
+  BOOST_CHECK_EQUAL(entry1, entry2); // order of NextHopRecords is irrelevant
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry1 = entry2 = makeFibEntry();
+  entry1.addNextHopRecord(nh1)
+      .addNextHopRecord(nh42);
+  entry2.addNextHopRecord(nh42)
+      .addNextHopRecord(nh42);
+  BOOST_CHECK_NE(entry1, entry2); // match each NextHopRecord at most once
+  BOOST_CHECK_NE(entry2, entry1);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  NextHopRecord record;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(record),
+                    "NextHopRecord(FaceId: 0, Cost: 0)");
+
+  FibEntry entry;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "FibEntry(Prefix: /,\n"
+                    "         NextHops: []\n"
+                    "         )");
+
+  entry = makeFibEntry();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "FibEntry(Prefix: /this/is/a/test,\n"
+                    "         NextHops: [NextHopRecord(FaceId: 10, Cost: 200),\n"
+                    "                    NextHopRecord(FaceId: 20, Cost: 300),\n"
+                    "                    NextHopRecord(FaceId: 30, Cost: 400)]\n"
+                    "         )");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFibEntry
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/forwarder-status.t.cpp b/tests/unit/mgmt/nfd/forwarder-status.t.cpp
new file mode 100644
index 0000000..b2c3d48
--- /dev/null
+++ b/tests/unit/mgmt/nfd/forwarder-status.t.cpp
@@ -0,0 +1,141 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/forwarder-status.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestForwarderStatus)
+
+static ForwarderStatus
+makeForwarderStatus()
+{
+  return ForwarderStatus()
+      .setNfdVersion("0.5.1-14-g05dd444")
+      .setStartTimestamp(time::fromUnixTimestamp(time::milliseconds(375193249325LL)))
+      .setCurrentTimestamp(time::fromUnixTimestamp(time::milliseconds(886109034272LL)))
+      .setNNameTreeEntries(1849943160)
+      .setNFibEntries(621739748)
+      .setNPitEntries(482129741)
+      .setNMeasurementsEntries(1771725298)
+      .setNCsEntries(1264968688)
+      .setNInInterests(612811615)
+      .setNInData(1843576050)
+      .setNInNacks(1234)
+      .setNOutInterests(952144445)
+      .setNOutData(138198826)
+      .setNOutNacks(4321)
+      .setNSatisfiedInterests(961020)
+      .setNUnsatisfiedInterests(941024);
+}
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  ForwarderStatus status1 = makeForwarderStatus();
+  Block wire = status1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x15, 0x71, 0x80, 0x11, 0x30, 0x2e, 0x35, 0x2e, 0x31, 0x2d, 0x31, 0x34,
+    0x2d, 0x67, 0x30, 0x35, 0x64, 0x64, 0x34, 0x34, 0x34, 0x81, 0x08, 0x00,
+    0x00, 0x00, 0x57, 0x5b, 0x42, 0xa6, 0x2d, 0x82, 0x08, 0x00, 0x00, 0x00,
+    0xce, 0x50, 0x36, 0xd7, 0x20, 0x83, 0x04, 0x6e, 0x43, 0xe4, 0x78, 0x84,
+    0x04, 0x25, 0x0e, 0xfe, 0xe4, 0x85, 0x04, 0x1c, 0xbc, 0xb7, 0x4d, 0x86,
+    0x04, 0x69, 0x9a, 0x61, 0xf2, 0x87, 0x04, 0x4b, 0x65, 0xe3, 0xf0, 0x90,
+    0x04, 0x24, 0x86, 0xc3, 0x5f, 0x91, 0x04, 0x6d, 0xe2, 0xbc, 0xf2, 0x97,
+    0x02, 0x04, 0xd2, 0x92, 0x04, 0x38, 0xc0, 0x92, 0x3d, 0x93, 0x04, 0x08,
+    0x3c, 0xbf, 0x2a, 0x98, 0x02, 0x10, 0xe1, 0x99, 0x04, 0x00, 0x0e, 0xa9,
+    0xfc, 0x9a, 0x04, 0x00, 0x0e, 0x5b, 0xe0,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  ForwarderStatus status2(wire);
+  BOOST_CHECK_EQUAL(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  ForwarderStatus status1, status2;
+
+  status1 = makeForwarderStatus();
+  status2 = status1;
+  BOOST_CHECK_EQUAL(status1, status2);
+
+  status2.setNPitEntries(42);
+  BOOST_CHECK_NE(status1, status2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  ForwarderStatus status;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "GeneralStatus(NfdVersion: ,\n"
+                    "              StartTimestamp: 0 nanoseconds since Jan 1, 1970,\n"
+                    "              CurrentTimestamp: 0 nanoseconds since Jan 1, 1970,\n"
+                    "              Counters: {NameTreeEntries: 0,\n"
+                    "                         FibEntries: 0,\n"
+                    "                         PitEntries: 0,\n"
+                    "                         MeasurementsEntries: 0,\n"
+                    "                         CsEntries: 0,\n"
+                    "                         Interests: {in: 0, out: 0},\n"
+                    "                         Data: {in: 0, out: 0},\n"
+                    "                         Nacks: {in: 0, out: 0},\n"
+                    "                         SatisfiedInterests: 0,\n"
+                    "                         UnsatisfiedInterests: 0}\n"
+                    "              )");
+
+  status = makeForwarderStatus();
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(status),
+                    "GeneralStatus(NfdVersion: 0.5.1-14-g05dd444,\n"
+                    "              StartTimestamp: 375193249325000000 nanoseconds since Jan 1, 1970,\n"
+                    "              CurrentTimestamp: 886109034272000000 nanoseconds since Jan 1, 1970,\n"
+                    "              Counters: {NameTreeEntries: 1849943160,\n"
+                    "                         FibEntries: 621739748,\n"
+                    "                         PitEntries: 482129741,\n"
+                    "                         MeasurementsEntries: 1771725298,\n"
+                    "                         CsEntries: 1264968688,\n"
+                    "                         Interests: {in: 612811615, out: 952144445},\n"
+                    "                         Data: {in: 1843576050, out: 138198826},\n"
+                    "                         Nacks: {in: 1234, out: 4321},\n"
+                    "                         SatisfiedInterests: 961020,\n"
+                    "                         UnsatisfiedInterests: 941024}\n"
+                    "              )");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestForwarderStatus
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/rib-entry.t.cpp b/tests/unit/mgmt/nfd/rib-entry.t.cpp
new file mode 100644
index 0000000..f2ab51a
--- /dev/null
+++ b/tests/unit/mgmt/nfd/rib-entry.t.cpp
@@ -0,0 +1,240 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/rib-entry.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestRibEntry)
+
+static Route
+makeRoute()
+{
+  return Route()
+      .setFaceId(1)
+      .setOrigin(ROUTE_ORIGIN_NLSR)
+      .setCost(100)
+      .setFlags(ROUTE_FLAG_CAPTURE);
+}
+
+static RibEntry
+makeRibEntry()
+{
+  return RibEntry()
+      .setName("/hello/world")
+      .addRoute(makeRoute()
+                .setExpirationPeriod(10_s));
+}
+
+BOOST_AUTO_TEST_CASE(RouteEncode)
+{
+  Route route1 = makeRoute();
+  route1.setExpirationPeriod(10_s);
+  const Block& wire = route1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02,
+    0x6d, 0x02, 0x27, 0x10
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  Route route2(wire);
+  BOOST_CHECK_EQUAL(route1, route2);
+}
+
+BOOST_AUTO_TEST_CASE(RouteNoExpirationPeriodEncode)
+{
+  Route route1 = makeRoute();
+  const Block& wire = route1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x81, 0x0C, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01, 0x64, 0x6c, 0x01, 0x02
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  Route route2(wire);
+  BOOST_CHECK_EQUAL(route1, route2);
+}
+
+BOOST_AUTO_TEST_CASE(RouteExpirationPeriod)
+{
+  Route route;
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(route.getExpirationPeriod(), time::milliseconds::max());
+
+  route.setExpirationPeriod(1_min);
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), true);
+  BOOST_CHECK_EQUAL(route.getExpirationPeriod(), 1_min);
+
+  route.setExpirationPeriod(time::milliseconds::max());
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), false);
+  BOOST_CHECK_EQUAL(route.getExpirationPeriod(), time::milliseconds::max());
+
+  route.setExpirationPeriod(1_min);
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), true);
+
+  route.unsetExpirationPeriod();
+  BOOST_CHECK_EQUAL(route.hasExpirationPeriod(), false);
+}
+
+BOOST_AUTO_TEST_CASE(RouteEquality)
+{
+  Route route1, route2;
+
+  route1 = makeRoute();
+  route2 = route1;
+  BOOST_CHECK_EQUAL(route1, route2);
+
+  route2.setFaceId(42);
+  BOOST_CHECK_NE(route1, route2);
+
+  route2 = route1;
+  route2.setExpirationPeriod(1_min);
+  BOOST_CHECK_NE(route1, route2);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryEncode)
+{
+  RibEntry entry1 = makeRibEntry();
+  entry1.addRoute(Route()
+                  .setFaceId(2)
+                  .setOrigin(ROUTE_ORIGIN_APP)
+                  .setCost(32)
+                  .setFlags(ROUTE_FLAG_CHILD_INHERIT)
+                  .setExpirationPeriod(5_s));
+  const Block& wire = entry1.wireEncode();
+
+  static const uint8_t expected[] = {
+    0x80, 0x34, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x08, 0x05, 0x77,
+    0x6f, 0x72, 0x6c, 0x64, 0x81, 0x10, 0x69, 0x01, 0x01, 0x6f, 0x01, 0x80, 0x6a, 0x01,
+    0x64, 0x6c, 0x01, 0x02, 0x6d, 0x02, 0x27, 0x10, 0x81, 0x10, 0x69, 0x01, 0x02, 0x6f,
+    0x01, 0x00, 0x6a, 0x01, 0x20, 0x6c, 0x01, 0x01, 0x6d, 0x02, 0x13, 0x88
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  RibEntry entry2(wire);
+  BOOST_CHECK_EQUAL(entry1, entry2);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryClearRoutes)
+{
+  RibEntry entry;
+  entry.setName("/hello/world");
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
+
+  Route route1;
+  route1.setFaceId(42);
+  entry.addRoute(route1);
+  BOOST_REQUIRE_EQUAL(entry.getRoutes().size(), 1);
+  BOOST_CHECK_EQUAL(entry.getRoutes().front(), route1);
+
+  entry.clearRoutes();
+  BOOST_CHECK_EQUAL(entry.getRoutes().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibEntryEquality)
+{
+  RibEntry entry1, entry2;
+  BOOST_CHECK_EQUAL(entry1, entry2);
+
+  entry1 = entry2 = makeRibEntry();
+  BOOST_CHECK_EQUAL(entry1, entry2);
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry2.setName("/different/name");
+  BOOST_CHECK_NE(entry1, entry2);
+
+  entry2 = entry1;
+  std::vector<Route> empty;
+  entry2.setRoutes(empty.begin(), empty.end());
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  entry2 = entry1;
+  auto r1 = Route()
+            .setFaceId(1)
+            .setCost(1000);
+  entry1.addRoute(r1);
+  BOOST_CHECK_NE(entry1, entry2);
+  BOOST_CHECK_NE(entry2, entry1);
+
+  auto r42 = Route()
+             .setFaceId(42)
+             .setCost(42);
+  entry1.addRoute(r42);
+  entry2.addRoute(r42)
+      .addRoute(r1);
+  BOOST_CHECK_EQUAL(entry1, entry2); // order of Routes is irrelevant
+  BOOST_CHECK_EQUAL(entry2, entry1);
+
+  entry1 = entry2 = makeRibEntry();
+  entry1.addRoute(r1)
+      .addRoute(r42);
+  entry2.addRoute(r42)
+      .addRoute(r42);
+  BOOST_CHECK_NE(entry1, entry2); // match each Route at most once
+  BOOST_CHECK_NE(entry2, entry1);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  Route route;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(route),
+                    "Route(FaceId: 0, Origin: app, Cost: 0, Flags: 0x1, ExpirationPeriod: infinite)");
+
+  RibEntry entry;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "RibEntry(Prefix: /,\n"
+                    "         Routes: []\n"
+                    "         )");
+
+  entry = makeRibEntry();
+  entry.addRoute(Route()
+                 .setFaceId(2)
+                 .setOrigin(ROUTE_ORIGIN_STATIC)
+                 .setCost(32)
+                 .setFlags(ROUTE_FLAG_CHILD_INHERIT));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(entry),
+                    "RibEntry(Prefix: /hello/world,\n"
+                    "         Routes: [Route(FaceId: 1, Origin: nlsr, Cost: 100, Flags: 0x2, "
+                    "ExpirationPeriod: 10000 milliseconds),\n"
+                    "                  Route(FaceId: 2, Origin: static, Cost: 32, Flags: 0x1, "
+                    "ExpirationPeriod: infinite)]\n"
+                    "         )");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRibEntry
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/status-dataset.t.cpp b/tests/unit/mgmt/nfd/status-dataset.t.cpp
new file mode 100644
index 0000000..a436de0
--- /dev/null
+++ b/tests/unit/mgmt/nfd/status-dataset.t.cpp
@@ -0,0 +1,486 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/status-dataset.hpp"
+#include "mgmt/nfd/controller.hpp"
+
+#include "controller-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+
+class ControllerStatusDatasetFixture : public ControllerFixture
+{
+protected:
+  /** \brief send one WireEncodable as Data reply
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload payload block
+   *  \note payload must fit in one Data
+   */
+  template<typename T>
+  void
+  sendDataset(const Name& prefix, const T& payload)
+  {
+    BOOST_CONCEPT_ASSERT((WireEncodable<T>));
+
+    auto data = this->prepareDatasetReply(prefix);
+    data->setContent(payload.wireEncode());
+    face.receive(*signData(data));
+  }
+
+  /** \brief send two WireEncodables as Data reply
+   *  \param prefix dataset prefix without version and segment
+   *  \param payload1 first vector item
+   *  \param payload2 second vector item
+   *  \note all payloads must fit in one Data
+   */
+  template<typename T1, typename T2>
+  void
+  sendDataset(const Name& prefix, const T1& payload1, const T2& payload2)
+  {
+    // The test suite allows up to two items, and put them in the same Data packet,
+    // because this test suite focuses on Controller::fetch<StatusDataset>,
+    // and is not intended to cover SegmentFetcher behavior.
+
+    BOOST_CONCEPT_ASSERT((WireEncodable<T1>));
+    BOOST_CONCEPT_ASSERT((WireEncodable<T2>));
+
+    ndn::encoding::EncodingBuffer buffer;
+    payload2.wireEncode(buffer);
+    payload1.wireEncode(buffer);
+
+    auto data = this->prepareDatasetReply(prefix);
+    data->setContent(buffer.buf(), buffer.size());
+    face.receive(*signData(data));
+  }
+
+private:
+  shared_ptr<Data>
+  prepareDatasetReply(const Name& prefix)
+  {
+    Name name = prefix;
+    name.appendVersion().appendSegment(0);
+
+    // These warnings assist in debugging a `hasResult` check failure.
+    // They usually indicate a misspelled prefix or incorrect timing in the test case.
+    if (face.sentInterests.size() < 1) {
+      BOOST_WARN_MESSAGE(false, "no Interest expressed");
+    }
+    else {
+      BOOST_WARN_MESSAGE(face.sentInterests.back().getName().isPrefixOf(name),
+                         "last Interest " << face.sentInterests.back().getName() <<
+                         " cannot be satisfied by this Data " << name);
+    }
+
+    auto data = make_shared<Data>(name);
+    data->setFinalBlock(name[-1]);
+    return data;
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestStatusDataset, ControllerStatusDatasetFixture)
+
+BOOST_AUTO_TEST_SUITE(Failures)
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  CommandOptions options;
+  options.setTimeout(3000_ms);
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(500_ms);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 1);
+
+  this->advanceClocks(500_ms, 6);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 0);
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_TIMEOUT);
+}
+
+BOOST_AUTO_TEST_CASE(DataHasNoSegment)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  face.receive(*makeData("/localhost/nfd/faces/list/%FD%00"));
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  this->setValidationResult(false);
+
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FaceStatus payload;
+  payload.setFaceId(5744);
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_VALIDATION);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  face.receive(lp::Nack(face.sentInterests.back()));
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_NACK);
+}
+
+BOOST_AUTO_TEST_CASE(ParseError1)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  Name payload; // Name is not valid FaceStatus
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_CASE(ParseError2)
+{
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FaceStatus payload1;
+  payload1.setFaceId(10930);
+  Name payload2; // Name is not valid FaceStatus
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_REQUIRE_EQUAL(failCodes.size(), 1);
+  BOOST_CHECK_EQUAL(failCodes.back(), Controller::ERROR_SERVER);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Failures
+
+BOOST_AUTO_TEST_SUITE(NoCallback)
+
+BOOST_AUTO_TEST_CASE(Success)
+{
+  controller.fetch<FaceDataset>(
+    nullptr,
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 1);
+
+  FaceStatus payload;
+  payload.setFaceId(2577);
+  this->sendDataset("/localhost/nfd/faces/list", payload);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(500_ms));
+
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+  BOOST_CHECK_EQUAL(controller.m_fetchers.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Failure)
+{
+  CommandOptions options;
+  options.setTimeout(3000_ms);
+  controller.fetch<FaceDataset>(
+    [] (const std::vector<FaceStatus>& result) { BOOST_FAIL("fetchDataset should not succeed"); },
+    nullptr,
+    options);
+  BOOST_CHECK_NO_THROW(this->advanceClocks(500_ms, 7));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // NoCallback
+
+BOOST_AUTO_TEST_SUITE(Datasets)
+
+BOOST_AUTO_TEST_CASE(StatusGeneral)
+{
+  bool hasResult = false;
+  controller.fetch<ForwarderGeneralStatusDataset>(
+    [&hasResult] (const ForwarderStatus& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.getNfdVersion(), "0.4.2");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  ForwarderStatus payload;
+  payload.setNfdVersion("0.4.2");
+  this->sendDataset("/localhost/nfd/status/general", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceList)
+{
+  bool hasResult = false;
+  controller.fetch<FaceDataset>(
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 24485);
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FaceStatus payload1;
+  payload1.setFaceId(24485);
+  FaceStatus payload2;
+  payload2.setFaceId(12987);
+  this->sendDataset("/localhost/nfd/faces/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceQuery)
+{
+  FaceQueryFilter filter;
+  filter.setUriScheme("udp4");
+  bool hasResult = false;
+  controller.fetch<FaceQueryDataset>(
+    filter,
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 8795);
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  Name prefix("/localhost/nfd/faces/query");
+  prefix.append(filter.wireEncode());
+  FaceStatus payload;
+  payload.setFaceId(8795);
+  this->sendDataset(prefix, payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceQueryWithOptions)
+{
+  FaceQueryFilter filter;
+  filter.setUriScheme("udp4");
+  CommandOptions options;
+  options.setTimeout(3000_ms);
+  bool hasResult = false;
+  controller.fetch<FaceQueryDataset>(
+    filter,
+    [&hasResult] (const std::vector<FaceStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getFaceId(), 14022);
+    },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(500_ms);
+
+  Name prefix("/localhost/nfd/faces/query");
+  prefix.append(filter.wireEncode());
+  FaceStatus payload;
+  payload.setFaceId(14022);
+  this->sendDataset(prefix, payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FaceChannels)
+{
+  bool hasResult = false;
+  controller.fetch<ChannelDataset>(
+    [&hasResult] (const std::vector<ChannelStatus>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getLocalUri(), "tcp4://192.0.2.1:6363");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  ChannelStatus payload1;
+  payload1.setLocalUri("tcp4://192.0.2.1:6363");
+  ChannelStatus payload2;
+  payload2.setLocalUri("udp4://192.0.2.1:6363");
+  this->sendDataset("/localhost/nfd/faces/channels", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(FibList)
+{
+  bool hasResult = false;
+  controller.fetch<FibDataset>(
+    [&hasResult] (const std::vector<FibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getPrefix(), "/wYs7fzYcfG");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  FibEntry payload1;
+  payload1.setPrefix("/wYs7fzYcfG");
+  FibEntry payload2;
+  payload2.setPrefix("/LKvmnzY5S");
+  this->sendDataset("/localhost/nfd/fib/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(CsInfo)
+{
+  using ndn::nfd::CsInfo;
+
+  bool hasResult = false;
+  controller.fetch<CsInfoDataset>(
+    [&hasResult] (const CsInfo& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.getNHits(), 4539);
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  CsInfo payload;
+  payload.setNHits(4539);
+  this->sendDataset("/localhost/nfd/cs/info", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(StrategyChoiceList)
+{
+  bool hasResult = false;
+  controller.fetch<StrategyChoiceDataset>(
+    [&hasResult] (const std::vector<StrategyChoice>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/8MLz6N3B");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  StrategyChoice payload1;
+  payload1.setName("/8MLz6N3B");
+  StrategyChoice payload2;
+  payload2.setName("/svqcBu0YwU");
+  this->sendDataset("/localhost/nfd/strategy-choice/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibList)
+{
+  bool hasResult = false;
+  controller.fetch<RibDataset>(
+    [&hasResult] (const std::vector<RibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 2);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/zXxBth97ee");
+    },
+    datasetFailCallback);
+  this->advanceClocks(500_ms);
+
+  RibEntry payload1;
+  payload1.setName("/zXxBth97ee");
+  RibEntry payload2;
+  payload2.setName("/rJ8CvUpr4G");
+  this->sendDataset("/localhost/nfd/rib/list", payload1, payload2);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(RibListWithOptions)
+{
+  CommandOptions options;
+  options.setPrefix("/localhop/nfd");
+  bool hasResult = false;
+  controller.fetch<RibDataset>(
+    [&hasResult] (const std::vector<RibEntry>& result) {
+      hasResult = true;
+      BOOST_CHECK_EQUAL(result.size(), 1);
+      BOOST_CHECK_EQUAL(result.front().getName(), "/e6L5K4ascd");
+    },
+    datasetFailCallback,
+    options);
+  this->advanceClocks(500_ms);
+
+  RibEntry payload;
+  payload.setName("/e6L5K4ascd");
+  this->sendDataset("/localhop/nfd/rib/list", payload);
+  this->advanceClocks(500_ms);
+
+  BOOST_CHECK(hasResult);
+  BOOST_CHECK_EQUAL(failCodes.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Datasets
+
+BOOST_AUTO_TEST_SUITE_END() // TestStatusDataset
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/nfd/strategy-choice.t.cpp b/tests/unit/mgmt/nfd/strategy-choice.t.cpp
new file mode 100644
index 0000000..9996541
--- /dev/null
+++ b/tests/unit/mgmt/nfd/strategy-choice.t.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/nfd/strategy-choice.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(Nfd)
+BOOST_AUTO_TEST_SUITE(TestStrategyChoice)
+
+BOOST_AUTO_TEST_CASE(Encode)
+{
+  StrategyChoice sc1;
+  sc1.setName("/hello/world")
+     .setStrategy("/some/non/existing/strategy/name");
+  Block wire = sc1.wireEncode();
+
+  // These octets are obtained by the snippet below.
+  // This check is intended to detect unexpected encoding change in the future.
+  // for (Buffer::const_iterator it = wire.begin(); it != wire.end(); ++it) {
+  //   printf("0x%02x, ", *it);
+  // }
+  static const uint8_t expected[] = {
+    0x80, 0x39, 0x07, 0x0e, 0x08, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x08, 0x05, 0x77,
+    0x6f, 0x72, 0x6c, 0x64, 0x6b, 0x27, 0x07, 0x25, 0x08, 0x04, 0x73, 0x6f, 0x6d, 0x65,
+    0x08, 0x03, 0x6e, 0x6f, 0x6e, 0x08, 0x08, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e,
+    0x67, 0x08, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x08, 0x04, 0x6e,
+    0x61, 0x6d, 0x65
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected, expected + sizeof(expected),
+                                wire.begin(), wire.end());
+
+  StrategyChoice sc2(wire);
+  BOOST_CHECK_EQUAL(sc1, sc2);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  StrategyChoice sc1, sc2;
+
+  sc1.setName("/A")
+     .setStrategy("/strategyP");
+  sc2 = sc1;
+  BOOST_CHECK_EQUAL(sc1, sc2);
+
+  sc2.setName("/B");
+  BOOST_CHECK_NE(sc1, sc2);
+
+  sc2 = sc1;
+  sc2.setStrategy("/strategyQ");
+  BOOST_CHECK_NE(sc1, sc2);
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  StrategyChoice sc;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(sc),
+                    "StrategyChoice(Name: /, Strategy: /)");
+
+  sc.setName("/A")
+    .setStrategy("/strategyP");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(sc),
+                    "StrategyChoice(Name: /A, Strategy: /strategyP)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStrategyChoice
+BOOST_AUTO_TEST_SUITE_END() // Nfd
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
+} // namespace ndn
diff --git a/tests/unit/mgmt/status-dataset-context.t.cpp b/tests/unit/mgmt/status-dataset-context.t.cpp
new file mode 100644
index 0000000..34e6fc1
--- /dev/null
+++ b/tests/unit/mgmt/status-dataset-context.t.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "mgmt/status-dataset-context.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace mgmt {
+namespace tests {
+
+using namespace ndn::tests;
+
+class StatusDatasetContextFixture
+{
+private:
+  struct SendDataArgs
+  {
+    Name dataName;
+    Block content;
+    time::milliseconds imsFresh;
+    bool isFinalBlock;
+  };
+
+protected:
+  StatusDatasetContextFixture()
+    : interest(makeInterest("/test/context/interest"))
+    , contentBlock(makeStringBlock(tlv::Content, "/test/data/content"))
+    , context(*interest,
+              [this] (const Name& dataName, const Block& content,
+                      time::milliseconds imsFresh, bool isFinalBlock) {
+                SendDataArgs args{dataName, content, imsFresh, isFinalBlock};
+                sendDataHistory.push_back(args);
+              },
+              [this] (const ControlResponse& resp) {
+                sendNackHistory.push_back(resp);
+              })
+    , defaultImsFresh(1000_ms)
+  {
+  }
+
+  Name
+  makeSegmentName(size_t segmentNo) const
+  {
+    auto name = context.getPrefix();
+    return name.appendSegment(segmentNo);
+  }
+
+  Block
+  concatenateDataContent() const
+  {
+    EncodingBuffer encoder;
+    size_t valueLength = 0;
+    for (const auto& args : sendDataHistory) {
+      const auto& content = args.content;
+      valueLength += encoder.appendByteArray(content.value(), content.value_size());
+    }
+    encoder.prependVarNumber(valueLength);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }
+
+protected:
+  std::vector<SendDataArgs> sendDataHistory;
+  std::vector<ControlResponse> sendNackHistory;
+  shared_ptr<Interest> interest;
+  Block contentBlock;
+  mgmt::StatusDatasetContext context;
+  time::milliseconds defaultImsFresh;
+};
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_FIXTURE_TEST_SUITE(TestStatusDatasetContext, StatusDatasetContextFixture)
+
+BOOST_AUTO_TEST_SUITE(Prefix)
+
+BOOST_AUTO_TEST_CASE(Get)
+{
+  Name dataName = context.getPrefix();
+  BOOST_CHECK(dataName[-1].isVersion());
+  BOOST_CHECK_EQUAL(dataName.getPrefix(-1), interest->getName());
+}
+
+BOOST_AUTO_TEST_CASE(SetValid)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  BOOST_CHECK_NO_THROW(context.setPrefix(validPrefix));
+  BOOST_CHECK(context.getPrefix()[-1].isVersion());
+  BOOST_CHECK_EQUAL(context.getPrefix().getPrefix(-1), validPrefix);
+}
+
+BOOST_AUTO_TEST_CASE(SetInvalid)
+{
+  Name invalidPrefix = Name(interest->getName()).getPrefix(-1).append("/invalid");
+  BOOST_CHECK_THROW(context.setPrefix(invalidPrefix), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(SetValidWithAppendCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.append(contentBlock);
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(SetValidWithEndCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.end();
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(SetValidWithRejectCalled)
+{
+  Name validPrefix = Name(interest->getName()).append("/valid");
+  context.reject();
+  BOOST_CHECK_THROW(context.setPrefix(validPrefix), std::domain_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Prefix
+
+BOOST_AUTO_TEST_SUITE(Expiry)
+
+BOOST_AUTO_TEST_CASE(GetAndSet)
+{
+  auto period = 9527_ms;
+  BOOST_CHECK_EQUAL(context.getExpiry(), 1000_ms);
+  BOOST_CHECK_EQUAL(context.setExpiry(period).getExpiry(), period);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Expiry
+
+BOOST_AUTO_TEST_SUITE(Respond)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(context.append(contentBlock));
+  BOOST_CHECK(sendDataHistory.empty()); // does not call end yet
+
+  BOOST_CHECK_NO_THROW(context.end());
+
+  BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 1);
+  const auto& args = sendDataHistory[0];
+
+  BOOST_CHECK_EQUAL(args.dataName, makeSegmentName(0));
+  BOOST_CHECK_EQUAL(args.content.blockFromValue(), contentBlock);
+  BOOST_CHECK_EQUAL(args.imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(args.isFinalBlock, true);
+}
+
+BOOST_AUTO_TEST_CASE(Large)
+{
+  static Block largeBlock = [] () -> Block {
+    EncodingBuffer encoder;
+    size_t maxBlockSize = MAX_NDN_PACKET_SIZE >> 1;
+    for (size_t i = 0; i < maxBlockSize; ++i) {
+      encoder.prependByte(1);
+    }
+    encoder.prependVarNumber(maxBlockSize);
+    encoder.prependVarNumber(tlv::Content);
+    return encoder.block();
+  }();
+
+  BOOST_CHECK_NO_THROW(context.append(largeBlock));
+  BOOST_CHECK_NO_THROW(context.end());
+
+  // two segments are generated
+  BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 2);
+
+  // check segment0
+  BOOST_CHECK_EQUAL(sendDataHistory[0].dataName, makeSegmentName(0));
+  BOOST_CHECK_EQUAL(sendDataHistory[0].imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(sendDataHistory[0].isFinalBlock, false);
+
+  // check segment1
+  BOOST_CHECK_EQUAL(sendDataHistory[1].dataName, makeSegmentName(1));
+  BOOST_CHECK_EQUAL(sendDataHistory[1].imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(sendDataHistory[1].isFinalBlock, true);
+
+  // check data content
+  auto contentLargeBlock = concatenateDataContent();
+  BOOST_CHECK_NO_THROW(contentLargeBlock.parse());
+  BOOST_REQUIRE_EQUAL(contentLargeBlock.elements().size(), 1);
+  BOOST_CHECK_EQUAL(contentLargeBlock.elements()[0], largeBlock);
+}
+
+BOOST_AUTO_TEST_CASE(MultipleSmall)
+{
+  size_t nBlocks = 100;
+  for (size_t i = 0 ; i < nBlocks ; i ++) {
+    BOOST_CHECK_NO_THROW(context.append(contentBlock));
+  }
+  BOOST_CHECK_NO_THROW(context.end());
+
+  // check data to in-memory storage
+  BOOST_REQUIRE_EQUAL(sendDataHistory.size(), 1);
+  BOOST_CHECK_EQUAL(sendDataHistory[0].dataName, makeSegmentName(0));
+  BOOST_CHECK_EQUAL(sendDataHistory[0].imsFresh, defaultImsFresh);
+  BOOST_CHECK_EQUAL(sendDataHistory[0].isFinalBlock, true);
+
+  auto contentMultiBlocks = concatenateDataContent();
+  BOOST_CHECK_NO_THROW(contentMultiBlocks.parse());
+  BOOST_CHECK_EQUAL(contentMultiBlocks.elements().size(), nBlocks);
+  for (auto&& element : contentMultiBlocks.elements()) {
+    BOOST_CHECK_EQUAL(element, contentBlock);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Respond
+
+BOOST_AUTO_TEST_SUITE(Reject)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_REQUIRE_EQUAL(sendNackHistory.size(), 1);
+  BOOST_CHECK_EQUAL(sendNackHistory[0].getCode(), 400);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Reject
+
+class AbnormalStateTestFixture
+{
+protected:
+  AbnormalStateTestFixture()
+    : context(Interest("/abnormal-state"), bind([]{}), bind([]{}))
+  {
+  }
+
+protected:
+  mgmt::StatusDatasetContext context;
+};
+
+BOOST_FIXTURE_TEST_SUITE(AbnormalState, AbnormalStateTestFixture)
+
+BOOST_AUTO_TEST_CASE(AppendReject)
+{
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_NO_THROW(context.append(Block(buf, sizeof(buf))));
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(AppendEndReject)
+{
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_NO_THROW(context.append(Block(buf, sizeof(buf))));
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndAppend)
+{
+  BOOST_CHECK_NO_THROW(context.end());
+  // end, append -> error
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_THROW(context.append(Block(buf, sizeof(buf))), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndEnd)
+{
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.end(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(EndReject)
+{
+  BOOST_CHECK_NO_THROW(context.end());
+  BOOST_CHECK_THROW(context.reject(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(RejectAppend)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  const uint8_t buf[] = {0x82, 0x01, 0x02};
+  BOOST_CHECK_THROW(context.append(Block(buf, sizeof(buf))), std::domain_error);
+}
+
+BOOST_AUTO_TEST_CASE(RejectEnd)
+{
+  BOOST_CHECK_NO_THROW(context.reject());
+  BOOST_CHECK_THROW(context.end(), std::domain_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // AbnormalState
+
+BOOST_AUTO_TEST_SUITE_END() // TestStatusDatasetContext
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace mgmt
+} // namespace ndn
diff --git a/tests/unit/name-component.t.cpp b/tests/unit/name-component.t.cpp
new file mode 100644
index 0000000..031ccb8
--- /dev/null
+++ b/tests/unit/name-component.t.cpp
@@ -0,0 +1,387 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "name-component.hpp"
+#include "name.hpp"
+#include "util/string-helper.hpp"
+
+#include "boost-test.hpp"
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace name {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestNameComponent)
+
+BOOST_AUTO_TEST_SUITE(Decode)
+
+BOOST_AUTO_TEST_CASE(Generic)
+{
+  Component comp("0807 6E646E2D637878"_block);
+  BOOST_CHECK_EQUAL(comp.type(), tlv::GenericNameComponent);
+  BOOST_CHECK_EQUAL(comp.toUri(), "ndn-cxx");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("ndn-cxx"), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("8=ndn-cxx"), comp);
+
+  comp.wireDecode("0800"_block);
+  BOOST_CHECK_EQUAL(comp.toUri(), "...");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("..."), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("8=..."), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString(".%2E."), comp);
+
+  comp.wireDecode("0801 2E"_block);
+  BOOST_CHECK_EQUAL(comp.toUri(), "....");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("...."), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("%2E..%2E"), comp);
+
+  comp.wireDecode("0803 2E412E"_block);
+  BOOST_CHECK_EQUAL(comp.toUri(), ".A.");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString(".A."), comp);
+
+  comp.wireDecode("0807 666F6F25626172"_block);
+  BOOST_CHECK_EQUAL(comp.toUri(), "foo%25bar");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("foo%25bar"), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("8=foo%25bar"), comp);
+
+  comp.wireDecode("0804 2D2E5F7E"_block);
+  BOOST_CHECK_EQUAL(comp.toUri(), "-._~");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("-._~"), comp);
+
+  comp.wireDecode("0803 393D41"_block);
+  BOOST_CHECK_EQUAL(comp.toUri(), "9%3DA");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("9%3DA"), comp);
+
+  comp = Component(":/?#[]@");
+  BOOST_CHECK_EQUAL(comp.toUri(), "%3A%2F%3F%23%5B%5D%40");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("%3A%2F%3F%23%5B%5D%40"), comp);
+
+  BOOST_CHECK_THROW(Component::fromEscapedString(""), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("."), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString(".."), Component::Error);
+}
+
+static void
+testSha256(uint32_t type, const std::string& uriPrefix)
+{
+  std::string hexLower = "28bad4b5275bd392dbb670c75cf0b66f13f7942b21e80f55c0e86b374753a548";
+  std::string hexUpper = boost::to_upper_copy(hexLower);
+  std::string hexPct;
+  for (size_t i = 0; i < hexUpper.size(); i += 2) {
+    hexPct += "%" + hexUpper.substr(i, 2);
+  }
+
+  Component comp(Block(type, fromHex(hexLower)));
+  BOOST_CHECK_EQUAL(comp.type(), type);
+  BOOST_CHECK_EQUAL(comp.toUri(), uriPrefix + hexLower);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString(uriPrefix + hexLower), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString(uriPrefix + hexUpper), comp);
+  BOOST_CHECK_EQUAL(Component::fromEscapedString(to_string(type) + "=" + hexPct), comp);
+
+  BOOST_CHECK_THROW(comp.wireDecode(Block(type, fromHex("A791806951F25C4D"))), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString(uriPrefix), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString(uriPrefix + "a791806951f25c4d"),
+                    Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString(boost::to_upper_copy(uriPrefix) + hexLower),
+                    Component::Error);
+}
+
+BOOST_AUTO_TEST_CASE(Digest)
+{
+  testSha256(tlv::ImplicitSha256DigestComponent, "sha256digest=");
+}
+
+BOOST_AUTO_TEST_CASE(Params)
+{
+  testSha256(tlv::ParametersSha256DigestComponent, "params-sha256=");
+}
+
+BOOST_AUTO_TEST_CASE(OtherType)
+{
+  Component comp("0907 6E646E2D637878"_block);
+  BOOST_CHECK_EQUAL(comp.type(), 0x09);
+  BOOST_CHECK_EQUAL(comp.toUri(), "9=ndn-cxx");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("9=ndn-cxx"), comp);
+
+  comp.wireDecode("FDFFFF00"_block);
+  BOOST_CHECK_EQUAL(comp.type(), 0xFFFF);
+  BOOST_CHECK_EQUAL(comp.toUri(), "65535=...");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("65535=..."), comp);
+
+  comp.wireDecode("FD576501 2E"_block);
+  BOOST_CHECK_EQUAL(comp.type(), 0x5765);
+  BOOST_CHECK_EQUAL(comp.toUri(), "22373=....");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("22373=...."), comp);
+
+  BOOST_CHECK_THROW(Component::fromEscapedString("3="), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("3=."), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("3=.."), Component::Error);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidType)
+{
+  Component comp;
+  BOOST_CHECK_THROW(comp.wireDecode("0001 80"_block), Component::Error);
+  BOOST_CHECK_THROW(comp.wireDecode("FE0001000001 80"_block), Component::Error);
+
+  BOOST_CHECK_THROW(Component::fromEscapedString("0=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("65536=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("4294967296=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("-1=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("+=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("0x1=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("Z=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("09=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("0x3=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("+9=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString(" 9=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("9 =A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("9.0=A"), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("9E0=A"), Component::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Decode
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  std::vector<Component> comps = {
+    Component("0120 0000000000000000000000000000000000000000000000000000000000000000"_block),
+    Component("0120 0000000000000000000000000000000000000000000000000000000000000001"_block),
+    Component("0120 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"_block),
+    Component("0220 0000000000000000000000000000000000000000000000000000000000000000"_block),
+    Component("0220 0000000000000000000000000000000000000000000000000000000000000001"_block),
+    Component("0220 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"_block),
+    Component(0x03),
+    Component("0301 44"_block),
+    Component("0301 46"_block),
+    Component("0302 4141"_block),
+    Component(),
+    Component("D"),
+    Component("F"),
+    Component("AA"),
+    Component(0x53B2),
+    Component("FD53B201 44"_block),
+    Component("FD53B201 46"_block),
+    Component("FD53B202 4141"_block),
+  };
+
+  for (size_t i = 0; i < comps.size(); ++i) {
+    for (size_t j = 0; j < comps.size(); ++j) {
+      Component lhs = comps[i];
+      Component rhs = comps[j];
+      BOOST_CHECK_EQUAL(lhs == rhs, i == j);
+      BOOST_CHECK_EQUAL(lhs != rhs, i != j);
+      BOOST_CHECK_EQUAL(lhs <  rhs, i <  j);
+      BOOST_CHECK_EQUAL(lhs <= rhs, i <= j);
+      BOOST_CHECK_EQUAL(lhs >  rhs, i >  j);
+      BOOST_CHECK_EQUAL(lhs >= rhs, i >= j);
+    }
+  }
+}
+
+BOOST_AUTO_TEST_SUITE(CreateFromIterators) // Bug 2490
+
+typedef boost::mpl::vector<
+  std::vector<uint8_t>,
+  std::list<uint8_t>,
+  std::vector<int8_t>,
+  std::list<int8_t>
+> ContainerTypes;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(ZeroOctet, T, ContainerTypes)
+{
+  T bytes;
+  Component c(bytes.begin(), bytes.end());
+  BOOST_CHECK_EQUAL(c.type(), tlv::GenericNameComponent);
+  BOOST_CHECK_EQUAL(c.value_size(), 0);
+  BOOST_CHECK_EQUAL(c.size(), 2);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(OneOctet, T, ContainerTypes)
+{
+  T bytes{1};
+  Component c(0x09, bytes.begin(), bytes.end());
+  BOOST_CHECK_EQUAL(c.type(), 0x09);
+  BOOST_CHECK_EQUAL(c.value_size(), 1);
+  BOOST_CHECK_EQUAL(c.size(), 3);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(FourOctets, T, ContainerTypes)
+{
+  T bytes{1, 2, 3, 4};
+  Component c(0xFCEC, bytes.begin(), bytes.end());
+  BOOST_CHECK_EQUAL(c.type(), 0xFCEC);
+  BOOST_CHECK_EQUAL(c.value_size(), 4);
+  BOOST_CHECK_EQUAL(c.size(), 8);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // CreateFromIterators
+
+BOOST_AUTO_TEST_SUITE(NamingConvention)
+
+template<typename ArgType>
+struct ConventionTest
+{
+  function<Component(ArgType)> makeComponent;
+  function<ArgType(const Component&)> getValue;
+  function<Name&(Name&, ArgType)> append;
+  Name expected;
+  ArgType value;
+  function<bool(const Component&)> isComponent;
+};
+
+class NumberWithMarker
+{
+public:
+  ConventionTest<uint64_t>
+  operator()() const
+  {
+    return {bind(&Component::fromNumberWithMarker, 0xAA, _1),
+            bind(&Component::toNumberWithMarker, _1, 0xAA),
+            bind(&Name::appendNumberWithMarker, _1, 0xAA, _2),
+            Name("/%AA%03%E8"),
+            1000,
+            bind(&Component::isNumberWithMarker, _1, 0xAA)};
+  }
+};
+
+class Segment
+{
+public:
+  ConventionTest<uint64_t>
+  operator()() const
+  {
+    return {&Component::fromSegment,
+            bind(&Component::toSegment, _1),
+            bind(&Name::appendSegment, _1, _2),
+            Name("/%00%27%10"),
+            10000,
+            bind(&Component::isSegment, _1)};
+  }
+};
+
+class SegmentOffset
+{
+public:
+  ConventionTest<uint64_t>
+  operator()() const
+  {
+    return {&Component::fromSegmentOffset,
+            bind(&Component::toSegmentOffset, _1),
+            bind(&Name::appendSegmentOffset, _1, _2),
+            Name("/%FB%00%01%86%A0"),
+            100000,
+            bind(&Component::isSegmentOffset, _1)};
+  }
+};
+
+class Version
+{
+public:
+  ConventionTest<uint64_t>
+  operator()() const
+  {
+    return {&Component::fromVersion,
+            bind(&Component::toVersion, _1),
+            [] (Name& name, uint64_t version) -> Name& { return name.appendVersion(version); },
+            Name("/%FD%00%0FB%40"),
+            1000000,
+            bind(&Component::isVersion, _1)};
+  }
+};
+
+class Timestamp
+{
+public:
+  ConventionTest<time::system_clock::TimePoint>
+  operator()() const
+  {
+    return {&Component::fromTimestamp,
+            bind(&Component::toTimestamp, _1),
+            [] (Name& name, time::system_clock::TimePoint t) -> Name& { return name.appendTimestamp(t); },
+            Name("/%FC%00%04%7BE%E3%1B%00%00"),
+            time::getUnixEpoch() + 14600_days, // 40 years
+            bind(&Component::isTimestamp, _1)};
+  }
+};
+
+class SequenceNumber
+{
+public:
+  ConventionTest<uint64_t>
+  operator()() const
+  {
+    return {&Component::fromSequenceNumber,
+            bind(&Component::toSequenceNumber, _1),
+            bind(&Name::appendSequenceNumber, _1, _2),
+            Name("/%FE%00%98%96%80"),
+            10000000,
+            bind(&Component::isSequenceNumber, _1)};
+  }
+};
+
+using ConventionTests = boost::mpl::vector<
+  NumberWithMarker,
+  Segment,
+  SegmentOffset,
+  Version,
+  Timestamp,
+  SequenceNumber
+>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Convention, T, ConventionTests)
+{
+  Component invalidComponent1;
+  Component invalidComponent2("1234567890");
+
+  auto test = T()();
+
+  const Name& expected = test.expected;
+  BOOST_TEST_MESSAGE("Check " << expected[0].toUri());
+
+  BOOST_CHECK_EQUAL(expected[0].isGeneric(), true);
+
+  Component actualComponent = test.makeComponent(test.value);
+  BOOST_CHECK_EQUAL(actualComponent, expected[0]);
+
+  Name actualName;
+  test.append(actualName, test.value);
+  BOOST_CHECK_EQUAL(actualName, expected);
+
+  BOOST_CHECK_EQUAL(test.isComponent(expected[0]), true);
+  BOOST_CHECK_EQUAL(test.getValue(expected[0]), test.value);
+
+  BOOST_CHECK_EQUAL(test.isComponent(invalidComponent1), false);
+  BOOST_CHECK_EQUAL(test.isComponent(invalidComponent2), false);
+
+  BOOST_CHECK_THROW(test.getValue(invalidComponent1), Component::Error);
+  BOOST_CHECK_THROW(test.getValue(invalidComponent2), Component::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // NamingConvention
+
+BOOST_AUTO_TEST_SUITE_END() // TestNameComponent
+
+} // namespace tests
+} // namespace name
+} // namespace ndn
diff --git a/tests/unit/name.t.cpp b/tests/unit/name.t.cpp
new file mode 100644
index 0000000..1e0aa1d
--- /dev/null
+++ b/tests/unit/name.t.cpp
@@ -0,0 +1,452 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "name.hpp"
+
+#include "boost-test.hpp"
+#include <unordered_map>
+
+namespace ndn {
+namespace tests {
+
+using Component = name::Component;
+
+BOOST_AUTO_TEST_SUITE(TestName)
+
+// ---- encoding, decoding, and URI ----
+
+BOOST_AUTO_TEST_CASE(EncodeDecode)
+{
+  std::string uri = "/Emid/25042=P3/.../..../%1C%9F/"
+                    "sha256digest=0415e3624a151850ac686c84f155f29808c0dd73819aa4a4c20be73a4d8a874c";
+  Name name(uri);
+  BOOST_CHECK_EQUAL(name.size(), 6);
+  BOOST_CHECK_EQUAL(name[0], Component("Emid"));
+  BOOST_CHECK_EQUAL(name[1], Component("FD61D2025033"_block));
+  BOOST_CHECK_EQUAL(name[2], Component(""));
+  BOOST_CHECK_EQUAL(name[3], Component("."));
+  BOOST_CHECK_EQUAL(name[4], Component("\x1C\x9F"));
+  BOOST_CHECK(name[5].isImplicitSha256Digest());
+
+  Block wire = name.wireEncode();
+  BOOST_CHECK_EQUAL(wire,
+    "0737 0804456D6964 FD61D2025033 0800 08012E 08021C9F "
+    "01200415E3624A151850AC686C84F155F29808C0DD73819AA4A4C20BE73A4D8A874C"_block);
+
+  Name decoded(wire);
+  BOOST_CHECK_EQUAL(decoded, name);
+}
+
+BOOST_AUTO_TEST_CASE(ParseUri)
+{
+  // URI with correct scheme
+  BOOST_CHECK_EQUAL(Name("ndn:/hello/world").toUri(), "/hello/world");
+
+  // URI with incorrect scheme: auto-corrected
+  BOOST_CHECK_EQUAL(Name("ncc:/hello/world").toUri(), "/hello/world");
+
+  // URI with authority: authority ignored
+  BOOST_CHECK_EQUAL(Name("//authority/hello/world").toUri(), "/hello/world");
+  BOOST_CHECK_EQUAL(Name("ndn://authority/hello/world").toUri(), "/hello/world");
+
+  // URI containing unescaped characters: auto-corrected
+  BOOST_CHECK_EQUAL(Name("/ hello\t/\tworld \r\n").toUri(), "/%20hello%09/%09world%20%0D%0A");
+  BOOST_CHECK_EQUAL(Name("/hello/world/  ").toUri(), "/hello/world/%20%20");
+  BOOST_CHECK_EQUAL(Name("/:?#[]@").toUri(), "/%3A%3F%23%5B%5D%40");
+
+  // URI not starting with '/': accepted as PartialName
+  BOOST_CHECK_EQUAL(Name("").toUri(), "/");
+  BOOST_CHECK_EQUAL(Name(" ").toUri(), "/%20");
+  BOOST_CHECK_EQUAL(Name("  /hello/world").toUri(), "/%20%20/hello/world");
+  BOOST_CHECK_EQUAL(Name("hello/world").toUri(), "/hello/world");
+
+  // URI ending with '/': auto-corrected
+  BOOST_CHECK_EQUAL(Name("/hello/world/").toUri(), "/hello/world");
+
+  // URI containing bad component: rejected
+  BOOST_CHECK_THROW(Name("/hello//world"), name::Component::Error);
+  BOOST_CHECK_THROW(Name("/hello/./world"), name::Component::Error);
+  BOOST_CHECK_THROW(Name("/hello/../world"), name::Component::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DeepCopy)
+{
+  Name n1("/hello/world");
+  Name n2 = n1.deepCopy();
+
+  BOOST_CHECK_EQUAL(n1, n2);
+  BOOST_CHECK_NE(&n1.wireEncode(), &n2.wireEncode());
+
+  EncodingBuffer buffer(1024, 0);
+  n1.wireEncode(buffer);
+  Name n3(buffer.block());
+
+  BOOST_CHECK_EQUAL(n1, n3);
+  BOOST_CHECK_EQUAL(n3.wireEncode().getBuffer()->size(), 1024);
+  n3 = n3.deepCopy();
+
+  BOOST_CHECK_LT(n3.wireEncode().size(), 1024);
+  BOOST_CHECK_EQUAL(n3.wireEncode().getBuffer()->size(), n3.wireEncode().size());
+}
+
+// ---- access ----
+
+BOOST_AUTO_TEST_CASE(At)
+{
+  Name name("/hello/5=NDN");
+
+  BOOST_CHECK_EQUAL(name.at(0), name::Component("080568656C6C6F"_block));
+  BOOST_CHECK_EQUAL(name.at(1), name::Component("05034E444E"_block));
+  BOOST_CHECK_EQUAL(name.at(-1), name::Component("05034E444E"_block));
+  BOOST_CHECK_EQUAL(name.at(-2), name::Component("080568656C6C6F"_block));
+
+  BOOST_CHECK_THROW(name.at(2), Name::Error);
+  BOOST_CHECK_THROW(name.at(-3), Name::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SubName)
+{
+  Name name("/hello/world");
+
+  BOOST_CHECK_EQUAL("/hello/world", name.getSubName(0));
+  BOOST_CHECK_EQUAL("/world", name.getSubName(1));
+  BOOST_CHECK_EQUAL("/hello", name.getSubName(0, 1));
+}
+
+BOOST_AUTO_TEST_CASE(SubNameNegativeIndex)
+{
+  Name name("/first/second/third/last");
+
+  BOOST_CHECK_EQUAL("/last", name.getSubName(-1));
+  BOOST_CHECK_EQUAL("/third/last", name.getSubName(-2));
+  BOOST_CHECK_EQUAL("/second", name.getSubName(-3, 1));
+}
+
+BOOST_AUTO_TEST_CASE(SubNameOutOfRangeIndexes)
+{
+  Name name("/first/second/last");
+  // No length
+  BOOST_CHECK_EQUAL("/first/second/last", name.getSubName(-10));
+  BOOST_CHECK_EQUAL("/", name.getSubName(10));
+
+  // Starting after the max position
+  BOOST_CHECK_EQUAL("/", name.getSubName(10, 1));
+  BOOST_CHECK_EQUAL("/", name.getSubName(10, 10));
+
+  // Not enough components
+  BOOST_CHECK_EQUAL("/second/last", name.getSubName(1, 10));
+  BOOST_CHECK_EQUAL("/last", name.getSubName(-1, 10));
+
+  // Start before first
+  BOOST_CHECK_EQUAL("/first/second", name.getSubName(-10, 2));
+  BOOST_CHECK_EQUAL("/first/second/last", name.getSubName(-10, 10));
+}
+
+// ---- iterators ----
+
+BOOST_AUTO_TEST_CASE(ForwardIterator)
+{
+  name::Component comps[] {
+    name::Component("A"),
+    name::Component("B"),
+    name::Component("C"),
+    name::Component("D")
+  };
+
+  Name n0;
+  BOOST_CHECK_EQUAL_COLLECTIONS(n0.begin(), n0.end(), comps, comps + 0);
+
+  Name n4("/A/B/C/D");
+  BOOST_CHECK_EQUAL_COLLECTIONS(n4.begin(), n4.end(), comps, comps + 4);
+}
+
+BOOST_AUTO_TEST_CASE(ReverseIterator)
+{
+  name::Component comps[] {
+    name::Component("D"),
+    name::Component("C"),
+    name::Component("B"),
+    name::Component("A")
+  };
+
+  Name n0;
+  BOOST_CHECK_EQUAL_COLLECTIONS(n0.rbegin(), n0.rend(), comps, comps + 0);
+
+  Name n4("/A/B/C/D");
+  BOOST_CHECK_EQUAL_COLLECTIONS(n4.rbegin(), n4.rend(), comps, comps + 4);
+}
+
+// ---- modifiers ----
+
+BOOST_AUTO_TEST_CASE(AppendComponent)
+{
+  Name name;
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0700"_block);
+
+  name.append(Component("Emid"));
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 0804456D6964"_block);
+
+  name.append(25042, reinterpret_cast<const uint8_t*>("P3"), 2);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "070C 0804456D6964 FD61D2025033"_block);
+
+  name.append(reinterpret_cast<const uint8_t*>("."), 1);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "070F 0804456D6964 FD61D2025033 08012E"_block);
+
+  std::vector<uint8_t> v1{0x28, 0xF0, 0xA3, 0x6B};
+  name.append(16, v1.begin(), v1.end());
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0715 0804456D6964 FD61D2025033 08012E 100428F0A36B"_block);
+
+  BOOST_CHECK(!name.empty());
+  name.clear();
+  BOOST_CHECK(name.empty());
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0700"_block);
+
+  name.append(v1.begin(), v1.end());
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080428F0A36B"_block);
+
+  name.append("xKh");
+  BOOST_CHECK_EQUAL(name.wireEncode(), "070B 080428F0A36B 0803784B68"_block);
+
+  name.append("0100"_block);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "070F 080428F0A36B 0803784B68 08020100"_block);
+
+  name.append("080109"_block);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0712 080428F0A36B 0803784B68 08020100 080109"_block);
+}
+
+BOOST_AUTO_TEST_CASE(AppendPartialName)
+{
+  Name name("/A/B");
+  name.append(PartialName("/6=C/D"))
+      .append(PartialName("/E"));
+  BOOST_CHECK_EQUAL(name.wireEncode(), "070F 080141 080142 060143 080144 080145"_block);
+}
+
+BOOST_AUTO_TEST_CASE(AppendNumber)
+{
+  Name name;
+  for (uint32_t i = 0; i < 10; i++) {
+    name.appendNumber(i);
+  }
+  BOOST_CHECK_EQUAL(name.size(), 10);
+
+  for (uint32_t i = 0; i < 10; i++) {
+    BOOST_CHECK_EQUAL(name[i].toNumber(), i);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(Markers)
+{
+  // TestNameComponent/NamingConvention provides additional coverage for these methods,
+  // including verifications of the wire format.
+
+  Name name;
+  uint64_t number;
+
+  BOOST_REQUIRE_NO_THROW(number = name.appendSegment(30923).at(-1).toSegment());
+  BOOST_CHECK_EQUAL(number, 30923);
+
+  BOOST_REQUIRE_NO_THROW(number = name.appendSegmentOffset(589).at(-1).toSegmentOffset());
+  BOOST_CHECK_EQUAL(number, 589);
+
+  BOOST_REQUIRE_NO_THROW(number = name.appendVersion().at(-1).toVersion());
+
+  BOOST_REQUIRE_NO_THROW(number = name.appendVersion(25912).at(-1).toVersion());
+  BOOST_CHECK_EQUAL(number, 25912);
+
+  const time::system_clock::TimePoint tp = time::system_clock::now();
+  time::system_clock::TimePoint tp2;
+  BOOST_REQUIRE_NO_THROW(tp2 = name.appendTimestamp(tp).at(-1).toTimestamp());
+  BOOST_CHECK_LE(std::abs(time::duration_cast<time::microseconds>(tp2 - tp).count()), 1);
+
+  BOOST_REQUIRE_NO_THROW(number = name.appendSequenceNumber(11676).at(-1).toSequenceNumber());
+  BOOST_CHECK_EQUAL(number, 11676);
+}
+
+// ---- algorithms ----
+
+BOOST_AUTO_TEST_CASE(GetSuccessor)
+{
+  BOOST_CHECK_EQUAL(Name().getSuccessor(), "/sha256digest=0000000000000000000000000000000000000000000000000000000000000000");
+  BOOST_CHECK_EQUAL(Name("/sha256digest=0000000000000000000000000000000000000000000000000000000000000000").getSuccessor(),
+                    "/sha256digest=0000000000000000000000000000000000000000000000000000000000000001");
+  BOOST_CHECK_EQUAL(Name("/sha256digest=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").getSuccessor(),
+                    "/params-sha256=0000000000000000000000000000000000000000000000000000000000000000");
+  BOOST_CHECK_EQUAL(Name("/params-sha256=0000000000000000000000000000000000000000000000000000000000000000").getSuccessor(),
+                    "/params-sha256=0000000000000000000000000000000000000000000000000000000000000001");
+  BOOST_CHECK_EQUAL(Name("/params-sha256=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").getSuccessor(),
+                    "/3=...");
+  BOOST_CHECK_EQUAL(Name("/P/A").getSuccessor(), "/P/B");
+  BOOST_CHECK_EQUAL(Name("/P/AAA").getSuccessor(), "/P/AAB");
+  BOOST_CHECK_EQUAL(Name("/Q/...").getSuccessor(), "/Q/%00");
+  BOOST_CHECK_EQUAL(Name("/Q/%FF").getSuccessor(), "/Q/%00%00");
+  BOOST_CHECK_EQUAL(Name("/Q/%FE%FF").getSuccessor(), "/Q/%FF%00");
+  BOOST_CHECK_EQUAL(Name("/Q/%FF%FF").getSuccessor(), "/Q/%00%00%00");
+  BOOST_CHECK_EQUAL(Name("/P/3=A").getSuccessor(), "/P/3=B");
+  BOOST_CHECK_EQUAL(Name("/P/3=AAA").getSuccessor(), "/P/3=AAB");
+  BOOST_CHECK_EQUAL(Name("/Q/3=...").getSuccessor(), "/Q/3=%00");
+  BOOST_CHECK_EQUAL(Name("/Q/3=%FF").getSuccessor(), "/Q/3=%00%00");
+  BOOST_CHECK_EQUAL(Name("/Q/3=%FE%FF").getSuccessor(), "/Q/3=%FF%00");
+  BOOST_CHECK_EQUAL(Name("/Q/3=%FF%FF").getSuccessor(), "/Q/3=%00%00%00");
+}
+
+BOOST_AUTO_TEST_CASE(IsPrefixOf)
+{
+  BOOST_CHECK(Name("/").isPrefixOf("/"));
+  BOOST_CHECK(Name("/").isPrefixOf("/sha256digest=0000000000000000000000000000000000000000000000000000000000000000"));
+  BOOST_CHECK(Name("/").isPrefixOf("/params-sha256=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
+  BOOST_CHECK(Name("/").isPrefixOf("/3=D"));
+  BOOST_CHECK(Name("/").isPrefixOf("/F"));
+  BOOST_CHECK(Name("/").isPrefixOf("/21426=AA"));
+
+  BOOST_CHECK(Name("/B").isPrefixOf("/B"));
+  BOOST_CHECK(Name("/B").isPrefixOf("/B/sha256digest=0000000000000000000000000000000000000000000000000000000000000000"));
+  BOOST_CHECK(Name("/").isPrefixOf("/B/params-sha256=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
+  BOOST_CHECK(Name("/B").isPrefixOf("/B/3=D"));
+  BOOST_CHECK(Name("/B").isPrefixOf("/B/F"));
+  BOOST_CHECK(Name("/B").isPrefixOf("/B/21426=AA"));
+
+  BOOST_CHECK(!Name("/C").isPrefixOf("/"));
+  BOOST_CHECK(!Name("/C").isPrefixOf("/sha256digest=0000000000000000000000000000000000000000000000000000000000000000"));
+  BOOST_CHECK(Name("/").isPrefixOf("/params-sha256=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
+  BOOST_CHECK(!Name("/C").isPrefixOf("/3=D"));
+  BOOST_CHECK(!Name("/C").isPrefixOf("/F"));
+  BOOST_CHECK(!Name("/C").isPrefixOf("/21426=AA"));
+}
+
+BOOST_AUTO_TEST_CASE(CompareOp)
+{
+  std::vector<Name> names = {
+    Name("/"),
+    Name("/sha256digest=0000000000000000000000000000000000000000000000000000000000000000"),
+    Name("/sha256digest=0000000000000000000000000000000000000000000000000000000000000001"),
+    Name("/sha256digest=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+    Name("/params-sha256=0000000000000000000000000000000000000000000000000000000000000000"),
+    Name("/params-sha256=0000000000000000000000000000000000000000000000000000000000000001"),
+    Name("/params-sha256=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+    Name("/3=..."),
+    Name("/3=D"),
+    Name("/3=F"),
+    Name("/3=AA"),
+    Name("/..."),
+    Name("/D"),
+    Name("/D/sha256digest=0000000000000000000000000000000000000000000000000000000000000000"),
+    Name("/D/sha256digest=0000000000000000000000000000000000000000000000000000000000000001"),
+    Name("/D/sha256digest=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+    Name("/D/params-sha256=0000000000000000000000000000000000000000000000000000000000000000"),
+    Name("/D/params-sha256=0000000000000000000000000000000000000000000000000000000000000001"),
+    Name("/D/params-sha256=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
+    Name("/D/3=..."),
+    Name("/D/3=D"),
+    Name("/D/3=F"),
+    Name("/D/3=AA"),
+    Name("/D/..."),
+    Name("/D/D"),
+    Name("/D/F"),
+    Name("/D/AA"),
+    Name("/D/21426=..."),
+    Name("/D/21426=D"),
+    Name("/D/21426=F"),
+    Name("/D/21426=AA"),
+    Name("/F"),
+    Name("/AA"),
+    Name("/21426=..."),
+    Name("/21426=D"),
+    Name("/21426=F"),
+    Name("/21426=AA"),
+  };
+
+  for (size_t i = 0; i < names.size(); ++i) {
+    for (size_t j = 0; j < names.size(); ++j) {
+      Name lhs = names[i];
+      Name rhs = names[j];
+      BOOST_CHECK_EQUAL(lhs == rhs, i == j);
+      BOOST_CHECK_EQUAL(lhs != rhs, i != j);
+      BOOST_CHECK_EQUAL(lhs <  rhs, i <  j);
+      BOOST_CHECK_EQUAL(lhs <= rhs, i <= j);
+      BOOST_CHECK_EQUAL(lhs >  rhs, i >  j);
+      BOOST_CHECK_EQUAL(lhs >= rhs, i >= j);
+    }
+  }
+}
+
+BOOST_AUTO_TEST_CASE(CompareFunc)
+{
+  BOOST_CHECK_EQUAL(Name("/A")  .compare(Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/A")  .compare(Name("/B")),   0);
+  BOOST_CHECK_GT   (Name("/B")  .compare(Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/A")  .compare(Name("/AA")),  0);
+  BOOST_CHECK_GT   (Name("/AA") .compare(Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/A")  .compare(Name("/A/C")), 0);
+  BOOST_CHECK_GT   (Name("/A/C").compare(Name("/A")),   0);
+
+  BOOST_CHECK_EQUAL(Name("/Z/A/Y")  .compare(1, 1, Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/B")),   0);
+  BOOST_CHECK_GT   (Name("/Z/B/Y")  .compare(1, 1, Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/AA")),  0);
+  BOOST_CHECK_GT   (Name("/Z/AA/Y") .compare(1, 1, Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/A/C")), 0);
+  BOOST_CHECK_GT   (Name("/Z/A/C/Y").compare(1, 2, Name("/A")),   0);
+
+  BOOST_CHECK_EQUAL(Name("/Z/A")  .compare(1, Name::npos, Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/Z/A")  .compare(1, Name::npos, Name("/B")),   0);
+  BOOST_CHECK_GT   (Name("/Z/B")  .compare(1, Name::npos, Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/Z/A")  .compare(1, Name::npos, Name("/AA")),  0);
+  BOOST_CHECK_GT   (Name("/Z/AA") .compare(1, Name::npos, Name("/A")),   0);
+  BOOST_CHECK_LT   (Name("/Z/A")  .compare(1, Name::npos, Name("/A/C")), 0);
+  BOOST_CHECK_GT   (Name("/Z/A/C").compare(1, Name::npos, Name("/A")),   0);
+
+  BOOST_CHECK_EQUAL(Name("/Z/A/Y")  .compare(1, 1, Name("/X/A/W"),   1, 1), 0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/X/B/W"),   1, 1), 0);
+  BOOST_CHECK_GT   (Name("/Z/B/Y")  .compare(1, 1, Name("/X/A/W"),   1, 1), 0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/X/AA/W"),  1, 1), 0);
+  BOOST_CHECK_GT   (Name("/Z/AA/Y") .compare(1, 1, Name("/X/A/W"),   1, 1), 0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/X/A/C/W"), 1, 2), 0);
+  BOOST_CHECK_GT   (Name("/Z/A/C/Y").compare(1, 2, Name("/X/A/W"),   1, 1), 0);
+
+  BOOST_CHECK_EQUAL(Name("/Z/A/Y")  .compare(1, 1, Name("/X/A"),   1), 0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/X/B"),   1), 0);
+  BOOST_CHECK_GT   (Name("/Z/B/Y")  .compare(1, 1, Name("/X/A"),   1), 0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/X/AA"),  1), 0);
+  BOOST_CHECK_GT   (Name("/Z/AA/Y") .compare(1, 1, Name("/X/A"),   1), 0);
+  BOOST_CHECK_LT   (Name("/Z/A/Y")  .compare(1, 1, Name("/X/A/C"), 1), 0);
+  BOOST_CHECK_GT   (Name("/Z/A/C/Y").compare(1, 2, Name("/X/A"),   1), 0);
+}
+
+BOOST_AUTO_TEST_CASE(UnorderedMap)
+{
+  std::unordered_map<Name, int> map;
+  Name name1("/1");
+  Name name2("/2");
+  Name name3("/3");
+  map[name1] = 1;
+  map[name2] = 2;
+  map[name3] = 3;
+
+  BOOST_CHECK_EQUAL(map[name1], 1);
+  BOOST_CHECK_EQUAL(map[name2], 2);
+  BOOST_CHECK_EQUAL(map[name3], 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestName
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/ndebug.t.cpp b/tests/unit/ndebug.t.cpp
new file mode 100644
index 0000000..b7219c2
--- /dev/null
+++ b/tests/unit/ndebug.t.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "common.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestNdebug)
+
+BOOST_AUTO_TEST_CASE(AssertFalse)
+{
+#ifndef _DEBUG
+  // in release builds, assertion shouldn't execute
+  BOOST_ASSERT(false);
+#endif
+  // Trivial check to avoid "test case did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_CASE(SideEffect)
+{
+  int a = 1;
+  BOOST_ASSERT((a = 2) > 0);
+#ifdef _DEBUG
+  BOOST_CHECK_EQUAL(a, 2);
+#else
+  BOOST_CHECK_EQUAL(a, 1);
+#endif
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNdebug
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/net/collect-netifs.cpp b/tests/unit/net/collect-netifs.cpp
new file mode 100644
index 0000000..ca8e6ef
--- /dev/null
+++ b/tests/unit/net/collect-netifs.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "collect-netifs.hpp"
+#include "net/network-monitor.hpp"
+
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace net {
+namespace tests {
+
+std::vector<shared_ptr<const NetworkInterface>>
+collectNetworkInterfaces(bool allowCached)
+{
+  static std::vector<shared_ptr<const NetworkInterface>> cached;
+  // cached.empty() indicates there's no cached list of netifs.
+  // Although it could also mean a system without any network interface, this situation is rare
+  // because the loopback interface is present on almost all systems.
+
+  if (!allowCached || cached.empty()) {
+    boost::asio::io_service io;
+    NetworkMonitor netmon(io);
+    if ((netmon.getCapabilities() & NetworkMonitor::CAP_ENUM) == 0) {
+      BOOST_THROW_EXCEPTION(NetworkMonitor::Error("NetworkMonitor::CAP_ENUM is unavailable"));
+    }
+
+    netmon.onEnumerationCompleted.connect([&io] { io.stop(); });
+    io.run();
+    io.reset();
+
+    cached = netmon.listNetworkInterfaces();
+  }
+
+  return cached;
+}
+
+} // namespace tests
+} // namespace net
+} // namespace ndn
diff --git a/tests/unit/net/collect-netifs.hpp b/tests/unit/net/collect-netifs.hpp
new file mode 100644
index 0000000..322e323
--- /dev/null
+++ b/tests/unit/net/collect-netifs.hpp
@@ -0,0 +1,52 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NFD_TESTS_UNIT_TESTS_NET_COLLECT_NETIFS_HPP
+#define NFD_TESTS_UNIT_TESTS_NET_COLLECT_NETIFS_HPP
+
+#include "common.hpp"
+#include "net/network-interface.hpp"
+
+#include <vector>
+
+namespace ndn {
+namespace net {
+namespace tests {
+
+/** \brief Collect information about network interfaces
+ *  \param allowCached if true, previously collected information can be returned
+ *  \note This function is blocking if \p allowCached is false or no previous information exists
+ *  \throw ndn::net::NetworkMonitor::Error NetworkMonitor::CAP_ENUM is unavailable
+ */
+std::vector<shared_ptr<const NetworkInterface>>
+collectNetworkInterfaces(bool allowCached = true);
+
+} // namespace tests
+} // namespace net
+} // namespace ndn
+
+#endif // NFD_TESTS_UNIT_TESTS_NET_COLLECT_NETIFS_HPP
diff --git a/tests/unit/net/dns.t.cpp b/tests/unit/net/dns.t.cpp
new file mode 100644
index 0000000..e4afa8e
--- /dev/null
+++ b/tests/unit/net/dns.t.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "net/dns.hpp"
+
+#include "boost-test.hpp"
+#include "network-configuration-detector.hpp"
+
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace dns {
+namespace tests {
+
+using boost::asio::ip::address_v4;
+using boost::asio::ip::address_v6;
+
+class DnsFixture
+{
+public:
+  DnsFixture()
+    : m_nFailures(0)
+    , m_nSuccesses(0)
+  {
+  }
+
+  void
+  onSuccess(const IpAddress& resolvedAddress,
+            const IpAddress& expectedAddress,
+            bool isValid,
+            bool shouldCheckAddress = false)
+  {
+    ++m_nSuccesses;
+
+    if (!isValid) {
+      BOOST_FAIL("Resolved to " + resolvedAddress.to_string() + ", but should have failed");
+    }
+
+    BOOST_CHECK_EQUAL(resolvedAddress.is_v4(), expectedAddress.is_v4());
+
+    // checking address is not deterministic and should be enabled only
+    // if only one IP address will be returned by resolution
+    if (shouldCheckAddress) {
+      BOOST_CHECK_EQUAL(resolvedAddress, expectedAddress);
+    }
+  }
+
+  void
+  onFailure(bool isValid)
+  {
+    ++m_nFailures;
+
+    if (!isValid) {
+      BOOST_FAIL("Resolution should not have failed");
+    }
+
+    BOOST_CHECK_MESSAGE(true, "Resolution failed as expected");
+  }
+
+protected:
+  uint32_t m_nFailures;
+  uint32_t m_nSuccesses;
+  boost::asio::io_service m_ioService;
+};
+
+BOOST_AUTO_TEST_SUITE(Net)
+BOOST_FIXTURE_TEST_SUITE(TestDns, DnsFixture)
+
+BOOST_AUTO_TEST_CASE(Asynchronous)
+{
+  SKIP_IF_IP_UNAVAILABLE();
+
+  asyncResolve("nothost.nothost.nothost.arpa",
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v4()), false, false),
+               bind(&DnsFixture::onFailure, this, true),
+               m_ioService); // should fail
+
+  m_ioService.run();
+  BOOST_CHECK_EQUAL(m_nFailures, 1);
+  BOOST_CHECK_EQUAL(m_nSuccesses, 0);
+}
+
+BOOST_AUTO_TEST_CASE(AsynchronousV4)
+{
+  SKIP_IF_IPV4_UNAVAILABLE();
+
+  asyncResolve("192.0.2.1",
+               bind(&DnsFixture::onSuccess, this, _1,
+                    IpAddress(address_v4::from_string("192.0.2.1")), true, true),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService);
+
+  m_ioService.run();
+  BOOST_CHECK_EQUAL(m_nFailures, 0);
+  BOOST_CHECK_EQUAL(m_nSuccesses, 1);
+}
+
+BOOST_AUTO_TEST_CASE(AsynchronousV6)
+{
+  SKIP_IF_IPV6_UNAVAILABLE();
+
+  asyncResolve("ipv6.google.com", // only IPv6 address should be available
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v6()), true, false),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService);
+
+  asyncResolve("2001:db8:3f9:0:3025:ccc5:eeeb:86d3",
+               bind(&DnsFixture::onSuccess, this, _1,
+                    IpAddress(address_v6::from_string("2001:db8:3f9:0:3025:ccc5:eeeb:86d3")),
+                    true, true),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService);
+
+  m_ioService.run();
+  BOOST_CHECK_EQUAL(m_nFailures, 0);
+  BOOST_CHECK_EQUAL(m_nSuccesses, 2);
+}
+
+BOOST_AUTO_TEST_CASE(AsynchronousV4AndV6)
+{
+  SKIP_IF_IPV4_UNAVAILABLE();
+  SKIP_IF_IPV6_UNAVAILABLE();
+
+  asyncResolve("www.named-data.net",
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v4()), true, false),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService, Ipv4Only());
+
+  asyncResolve("a.root-servers.net",
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v4()), true, false),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService, Ipv4Only()); // request IPv4 address
+
+  asyncResolve("a.root-servers.net",
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v6()), true, false),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService, Ipv6Only()); // request IPv6 address
+
+  asyncResolve("ipv6.google.com", // only IPv6 address should be available
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v6()), true, false),
+               bind(&DnsFixture::onFailure, this, false),
+               m_ioService, Ipv6Only());
+
+  asyncResolve("ipv6.google.com", // only IPv6 address should be available
+               bind(&DnsFixture::onSuccess, this, _1, IpAddress(address_v6()), false, false),
+               bind(&DnsFixture::onFailure, this, true),
+               m_ioService, Ipv4Only()); // should fail
+
+  m_ioService.run();
+  BOOST_CHECK_EQUAL(m_nFailures, 1);
+  BOOST_CHECK_EQUAL(m_nSuccesses, 4);
+}
+
+BOOST_AUTO_TEST_CASE(Synchronous)
+{
+  SKIP_IF_IP_UNAVAILABLE();
+
+  IpAddress address = syncResolve("www.named-data.net", m_ioService);
+  BOOST_CHECK(address.is_v4() || address.is_v6());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDns
+BOOST_AUTO_TEST_SUITE_END() // Net
+
+} // namespace tests
+} // namespace dns
+} // namespace ndn
diff --git a/tests/unit/net/ethernet.t.cpp b/tests/unit/net/ethernet.t.cpp
new file mode 100644
index 0000000..4f76dbb
--- /dev/null
+++ b/tests/unit/net/ethernet.t.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "net/ethernet.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Net)
+BOOST_AUTO_TEST_SUITE(TestEthernet)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  ethernet::Address a;
+  BOOST_CHECK(a.isNull());
+
+  a = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
+  ethernet::Address b(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB);
+  const uint8_t bytes[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB};
+  ethernet::Address c(bytes);
+  ethernet::Address d(a);
+  ethernet::Address e;
+  e = a;
+
+  BOOST_CHECK_EQUAL(a, b);
+  BOOST_CHECK_EQUAL(a, c);
+  BOOST_CHECK_EQUAL(a, d);
+  BOOST_CHECK_EQUAL(a, e);
+
+  BOOST_CHECK(ethernet::getBroadcastAddress().isBroadcast());
+  BOOST_CHECK(ethernet::getDefaultMulticastAddress().isMulticast());
+}
+
+BOOST_AUTO_TEST_CASE(ToString)
+{
+  BOOST_CHECK_EQUAL(ethernet::Address().toString('-'),
+                    "00-00-00-00-00-00");
+  BOOST_CHECK_EQUAL(ethernet::getBroadcastAddress().toString(),
+                    "ff:ff:ff:ff:ff:ff");
+  BOOST_CHECK_EQUAL(ethernet::Address(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB).toString('-'),
+                    "01-23-45-67-89-ab");
+  BOOST_CHECK_EQUAL(ethernet::Address(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB).toString(),
+                    "01:23:45:67:89:ab");
+}
+
+BOOST_AUTO_TEST_CASE(FromString)
+{
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("0:0:0:0:0:0"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("ff-ff-ff-ff-ff-ff"),
+                    ethernet::getBroadcastAddress());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("de:ad:be:ef:1:2"),
+                    ethernet::Address(0xde, 0xad, 0xbe, 0xef, 0x01, 0x02));
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("DE:AD:BE:EF:1:2"),
+                    ethernet::Address(0xde, 0xad, 0xbe, 0xef, 0x01, 0x02));
+
+  // malformed inputs
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01.23.45.67.89.ab"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45 :67:89:ab"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45:67:89::1"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01-23-45-67-89"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45:67:89:ab:cd"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45:67-89-ab"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("qw-er-ty-12-34-56"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("this-is-not-an-ethernet-address"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("foobar"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString(""),
+                    ethernet::Address());
+}
+
+BOOST_AUTO_TEST_CASE(StdHash)
+{
+  // make sure we can use ethernet::Address as key type in std::unordered_map
+  std::hash<ethernet::Address> h;
+  BOOST_CHECK_NO_THROW(h(ethernet::getDefaultMulticastAddress()));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestEthernet
+BOOST_AUTO_TEST_SUITE_END() // Net
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/net/face-uri.t.cpp b/tests/unit/net/face-uri.t.cpp
new file mode 100644
index 0000000..71d76b1
--- /dev/null
+++ b/tests/unit/net/face-uri.t.cpp
@@ -0,0 +1,647 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "net/face-uri.hpp"
+
+#include "boost-test.hpp"
+#include "collect-netifs.hpp"
+#include "network-configuration-detector.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Net)
+BOOST_AUTO_TEST_SUITE(TestFaceUri)
+
+class CanonizeFixture : noncopyable
+{
+protected:
+  void
+  addTest(const std::string& request, bool shouldSucceed, const std::string& expectedUri)
+  {
+    ++m_nPending;
+    auto tc = make_shared<CanonizeTestCase>(request, shouldSucceed, expectedUri);
+
+    FaceUri uri(request);
+    uri.canonize(bind(&CanonizeFixture::onCanonizeSuccess, this, tc, _1),
+                 bind(&CanonizeFixture::onCanonizeFailure, this, tc, _1),
+                 m_io, 10_s);
+  }
+
+  void
+  runTests()
+  {
+    m_io.run();
+    BOOST_CHECK_EQUAL(m_nPending, 0);
+  }
+
+private:
+  class CanonizeTestCase
+  {
+  public:
+    CanonizeTestCase(const std::string& request, bool shouldSucceed, const std::string& expectedUri)
+      : m_expectedUri(expectedUri)
+      , m_message(request + " should " + (shouldSucceed ? "succeed" : "fail"))
+      , m_shouldSucceed(shouldSucceed)
+      , m_isCompleted(false)
+    {
+    }
+
+  public:
+    std::string m_expectedUri;
+    std::string m_message;
+    bool m_shouldSucceed;
+    bool m_isCompleted;
+  };
+
+  void
+  onCanonizeSuccess(const shared_ptr<CanonizeTestCase>& tc, const FaceUri& canonicalUri)
+  {
+    BOOST_CHECK_EQUAL(tc->m_isCompleted, false);
+    tc->m_isCompleted = true;
+    --m_nPending;
+
+    BOOST_CHECK_MESSAGE(tc->m_shouldSucceed, tc->m_message);
+    if (tc->m_shouldSucceed) {
+      BOOST_CHECK_EQUAL(canonicalUri.toString(), tc->m_expectedUri);
+    }
+  }
+
+  void
+  onCanonizeFailure(const shared_ptr<CanonizeTestCase>& tc, const std::string& reason)
+  {
+    BOOST_CHECK_EQUAL(tc->m_isCompleted, false);
+    tc->m_isCompleted = true;
+    --m_nPending;
+
+    BOOST_CHECK_MESSAGE(!tc->m_shouldSucceed, tc->m_message);
+  }
+
+private:
+  boost::asio::io_service m_io;
+  ssize_t m_nPending = 0;
+};
+
+BOOST_AUTO_TEST_CASE(ParseInternal)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("internal://"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "internal");
+  BOOST_CHECK_EQUAL(uri.getHost(), "");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("internal:"), false);
+  BOOST_CHECK_EQUAL(uri.parse("internal:/"), false);
+}
+
+BOOST_AUTO_TEST_CASE(ParseUdp)
+{
+  FaceUri uri("udp://hostname:6363");
+  BOOST_CHECK_THROW(FaceUri("udp//hostname:6363"), FaceUri::Error);
+  BOOST_CHECK_THROW(FaceUri("udp://hostname:port"), FaceUri::Error);
+
+  BOOST_CHECK_EQUAL(uri.parse("udp//hostname:6363"), false);
+
+  BOOST_CHECK(uri.parse("udp://hostname:80"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "udp");
+  BOOST_CHECK_EQUAL(uri.getHost(), "hostname");
+  BOOST_CHECK_EQUAL(uri.getPort(), "80");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK(uri.parse("udp4://192.0.2.1:20"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "udp4");
+  BOOST_CHECK_EQUAL(uri.getHost(), "192.0.2.1");
+  BOOST_CHECK_EQUAL(uri.getPort(), "20");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK(uri.parse("udp6://[2001:db8:3f9:0::1]:6363"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "udp6");
+  BOOST_CHECK_EQUAL(uri.getHost(), "2001:db8:3f9:0::1");
+  BOOST_CHECK_EQUAL(uri.getPort(), "6363");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK(uri.parse("udp6://[2001:db8:3f9:0:3025:ccc5:eeeb:86d3]:6363"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "udp6");
+  BOOST_CHECK_EQUAL(uri.getHost(), "2001:db8:3f9:0:3025:ccc5:eeeb:86d3");
+  BOOST_CHECK_EQUAL(uri.getPort(), "6363");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("udp6://[2001:db8:3f9:0:3025:ccc5:eeeb:86dg]:6363"), false);
+
+  namespace ip = boost::asio::ip;
+
+  ip::udp::endpoint endpoint4(ip::address_v4::from_string("192.0.2.1"), 7777);
+  uri = FaceUri(endpoint4);
+  BOOST_CHECK_EQUAL(uri.toString(), "udp4://192.0.2.1:7777");
+
+  ip::udp::endpoint endpoint6(ip::address_v6::from_string("2001:DB8::1"), 7777);
+  uri = FaceUri(endpoint6);
+  BOOST_CHECK_EQUAL(uri.toString(), "udp6://[2001:db8::1]:7777");
+
+  BOOST_CHECK(uri.parse("udp6://[fe80::1%25eth1]:6363"));
+  BOOST_CHECK_EQUAL(uri.getHost(), "fe80::1%25eth1");
+
+  BOOST_CHECK(uri.parse("udp6://[fe80::1%eth1]:6363"));
+  BOOST_CHECK_EQUAL(uri.getHost(), "fe80::1%eth1");
+
+  BOOST_CHECK(uri.parse("udp6://[fe80::1%1]:6363"));
+  BOOST_CHECK(uri.parse("udp6://[fe80::1%eth1]"));
+
+  BOOST_CHECK(uri.parse("udp6://[ff01::114%eth#1]"));
+  BOOST_CHECK(uri.parse("udp6://[ff01::114%eth.1,2]"));
+  BOOST_CHECK(uri.parse("udp6://[ff01::114%a+b-c=0]"));
+  BOOST_CHECK(uri.parse("udp6://[ff01::114%[foo]]"));
+  BOOST_CHECK(uri.parse("udp6://[ff01::114%]]"));
+  BOOST_CHECK(uri.parse("udp6://[ff01::114%%]"));
+  BOOST_CHECK(!uri.parse("udp6://[ff01::114%]"));
+  BOOST_CHECK(!uri.parse("udp6://[ff01::114%foo bar]"));
+  BOOST_CHECK(!uri.parse("udp6://[ff01::114%foo/bar]"));
+  BOOST_CHECK(!uri.parse("udp6://[ff01::114%eth0:1]"));
+}
+
+BOOST_FIXTURE_TEST_CASE(IsCanonicalUdp, CanonizeFixture)
+{
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("udp"), true);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("udp4"), true);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("udp6"), true);
+
+  BOOST_CHECK_EQUAL(FaceUri("udp4://192.0.2.1:6363").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("udp://192.0.2.1:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp4://192.0.2.1").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp4://192.0.2.1:6363/").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp6://[2001:db8::1]:6363").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("udp6://[2001:db8::01]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp://[2001:db8::1]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp://example.net:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp4://example.net:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp6://example.net:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp4://224.0.23.170:56363").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("udp4://[2001:db8::1]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp6://192.0.2.1:6363").isCanonical(), false);
+
+  const auto& networkInterfaces = ndn::net::tests::collectNetworkInterfaces();
+  if (!networkInterfaces.empty()) {
+    const auto& netif = networkInterfaces.front();
+    auto name = netif->getName();
+    auto index = to_string(netif->getIndex());
+
+    BOOST_CHECK_EQUAL(FaceUri("udp6://[fe80::1%" + name + "]:6363").isCanonical(), true);
+    BOOST_CHECK_EQUAL(FaceUri("udp6://[fe80::1%" + index + "]:6363").isCanonical(), false);
+    BOOST_CHECK_EQUAL(FaceUri("udp6://[fe80::1%" + name + "]").isCanonical(), false);
+    BOOST_CHECK_EQUAL(FaceUri("udp6://[fe80::1068:dddb:fe26:fe3f%25en0]:6363").isCanonical(), false);
+  }
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(CanonizeUdpV4, 1)
+BOOST_FIXTURE_TEST_CASE(CanonizeUdpV4, CanonizeFixture)
+{
+  SKIP_IF_IPV4_UNAVAILABLE();
+
+  // IPv4 unicast
+  addTest("udp4://192.0.2.1:6363", true, "udp4://192.0.2.1:6363");
+  addTest("udp://192.0.2.2:6363", true, "udp4://192.0.2.2:6363");
+  addTest("udp4://192.0.2.3", true, "udp4://192.0.2.3:6363");
+  addTest("udp4://192.0.2.4:6363/", true, "udp4://192.0.2.4:6363");
+  addTest("udp4://192.0.2.5:9695", true, "udp4://192.0.2.5:9695");
+  addTest("udp4://192.0.2.666:6363", false, "");
+  addTest("udp4://192.0.2.7:99999", false, ""); // Bug #3897
+  addTest("udp4://google-public-dns-a.google.com", true, "udp4://8.8.8.8:6363");
+  addTest("udp4://google-public-dns-a.google.com:70000", false, "");
+  addTest("udp4://invalid.invalid", false, "");
+
+  // IPv4 multicast
+  addTest("udp4://224.0.23.170:56363", true, "udp4://224.0.23.170:56363");
+  addTest("udp4://224.0.23.170", true, "udp4://224.0.23.170:56363");
+  addTest("udp4://all-routers.mcast.net:56363", true, "udp4://224.0.0.2:56363");
+
+  // IPv6 used with udp4 protocol - not canonical
+  addTest("udp4://[2001:db8::1]:6363", false, "");
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(CanonizeUdpV6, 1)
+BOOST_FIXTURE_TEST_CASE(CanonizeUdpV6, CanonizeFixture)
+{
+  SKIP_IF_IPV6_UNAVAILABLE();
+
+  // IPv6 unicast
+  addTest("udp6://[2001:db8::1]:6363", true, "udp6://[2001:db8::1]:6363");
+  addTest("udp6://[2001:db8::1]", true, "udp6://[2001:db8::1]:6363");
+  addTest("udp://[2001:db8::1]:6363", true, "udp6://[2001:db8::1]:6363");
+  addTest("udp6://[2001:db8::01]:6363", true, "udp6://[2001:db8::1]:6363");
+  addTest("udp6://[2001::db8::1]:6363", false, "");
+  addTest("udp6://[2001:db8::1]:99999", false, ""); // Bug #3897
+  addTest("udp6://google-public-dns-a.google.com", true, "udp6://[2001:4860:4860::8888]:6363");
+  addTest("udp6://google-public-dns-a.google.com:70000", false, "");
+  addTest("udp6://invalid.invalid", false, "");
+  addTest("udp://invalid.invalid", false, "");
+
+  // IPv6 multicast
+  addTest("udp6://[ff02::2]:56363", true, "udp6://[ff02::2]:56363");
+  addTest("udp6://[ff02::2]", true, "udp6://[ff02::2]:56363");
+
+  // IPv4 used with udp6 protocol - not canonical
+  addTest("udp6://192.0.2.1:6363", false, "");
+
+  const auto& networkInterfaces = ndn::net::tests::collectNetworkInterfaces();
+  if (!networkInterfaces.empty()) {
+    const auto& netif = networkInterfaces.front();
+    auto name = netif->getName();
+    auto index = to_string(netif->getIndex());
+
+    addTest("udp6://[fe80::1068:dddb:fe26:fe3f%25" + name + "]:6363", true,
+            "udp6://[fe80::1068:dddb:fe26:fe3f%" + name + "]:6363");
+
+    addTest("udp6://[fe80::1068:dddb:fe26:fe3f%" + index + "]:6363", true,
+            "udp6://[fe80::1068:dddb:fe26:fe3f%" + name + "]:6363");
+  }
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE(ParseTcp)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("tcp://random.host.name"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "tcp");
+  BOOST_CHECK_EQUAL(uri.getHost(), "random.host.name");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("tcp://192.0.2.1:"), false);
+  BOOST_CHECK_EQUAL(uri.parse("tcp://[::zzzz]"), false);
+
+  namespace ip = boost::asio::ip;
+
+  ip::tcp::endpoint endpoint4(ip::address_v4::from_string("192.0.2.1"), 7777);
+  uri = FaceUri(endpoint4);
+  BOOST_CHECK_EQUAL(uri.toString(), "tcp4://192.0.2.1:7777");
+
+  uri = FaceUri(endpoint4, "wsclient");
+  BOOST_CHECK_EQUAL(uri.toString(), "wsclient://192.0.2.1:7777");
+
+  ip::tcp::endpoint endpoint6(ip::address_v6::from_string("2001:DB8::1"), 7777);
+  uri = FaceUri(endpoint6);
+  BOOST_CHECK_EQUAL(uri.toString(), "tcp6://[2001:db8::1]:7777");
+
+  BOOST_CHECK(uri.parse("tcp6://[fe80::1%25eth1]:6363"));
+  BOOST_CHECK_EQUAL(uri.getHost(), "fe80::1%25eth1");
+
+  BOOST_CHECK(uri.parse("tcp6://[fe80::1%eth1]:6363"));
+  BOOST_CHECK_EQUAL(uri.getHost(), "fe80::1%eth1");
+
+  BOOST_CHECK(uri.parse("tcp6://[fe80::1%1]:6363"));
+  BOOST_CHECK(uri.parse("tcp6://[fe80::1%eth1]"));
+}
+
+BOOST_FIXTURE_TEST_CASE(IsCanonicalTcp, CanonizeFixture)
+{
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("tcp"), true);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("tcp4"), true);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("tcp6"), true);
+
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://192.0.2.1:6363").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("tcp://192.0.2.1:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://192.0.2.1").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://192.0.2.1:6363/").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp6://[2001:db8::1]:6363").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("tcp6://[2001:db8::01]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp://[2001:db8::1]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp://example.net:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://example.net:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp6://example.net:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://224.0.23.170:56363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp4://[2001:db8::1]:6363").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("tcp6://192.0.2.1:6363").isCanonical(), false);
+
+  const auto& networkInterfaces = ndn::net::tests::collectNetworkInterfaces();
+  if (!networkInterfaces.empty()) {
+    const auto& netif = networkInterfaces.front();
+    auto name = netif->getName();
+    auto index = to_string(netif->getIndex());
+
+    BOOST_CHECK_EQUAL(FaceUri("tcp6://[fe80::1%" + name + "]:6363").isCanonical(), true);
+    BOOST_CHECK_EQUAL(FaceUri("tcp6://[fe80::1%" + index + "]:6363").isCanonical(), false);
+    BOOST_CHECK_EQUAL(FaceUri("tcp6://[fe80::1%" + name + "]").isCanonical(), false);
+    BOOST_CHECK_EQUAL(FaceUri("tcp6://[fe80::1068:dddb:fe26:fe3f%25en0]:6363").isCanonical(), false);
+  }
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(CanonizeTcpV4, 1)
+BOOST_FIXTURE_TEST_CASE(CanonizeTcpV4, CanonizeFixture)
+{
+  SKIP_IF_IPV4_UNAVAILABLE();
+
+  // IPv4 unicast
+  addTest("tcp4://192.0.2.1:6363", true, "tcp4://192.0.2.1:6363");
+  addTest("tcp://192.0.2.2:6363", true, "tcp4://192.0.2.2:6363");
+  addTest("tcp4://192.0.2.3", true, "tcp4://192.0.2.3:6363");
+  addTest("tcp4://192.0.2.4:6363/", true, "tcp4://192.0.2.4:6363");
+  addTest("tcp4://192.0.2.5:9695", true, "tcp4://192.0.2.5:9695");
+  addTest("tcp4://192.0.2.666:6363", false, "");
+  addTest("tcp4://192.0.2.7:99999", false, ""); // Bug #3897
+  addTest("tcp4://google-public-dns-a.google.com", true, "tcp4://8.8.8.8:6363");
+  addTest("tcp4://google-public-dns-a.google.com:70000", false, "");
+  addTest("tcp4://invalid.invalid", false, "");
+
+  // IPv4 multicast
+  addTest("tcp4://224.0.23.170:56363", false, "");
+  addTest("tcp4://224.0.23.170", false, "");
+  addTest("tcp4://all-routers.mcast.net:56363", false, "");
+
+  // IPv6 used with tcp4 protocol - not canonical
+  addTest("tcp4://[2001:db8::1]:6363", false, "");
+
+  const auto& networkInterfaces = ndn::net::tests::collectNetworkInterfaces();
+  if (!networkInterfaces.empty()) {
+    const auto& netif = networkInterfaces.front();
+    auto name = netif->getName();
+    auto index = to_string(netif->getIndex());
+
+    addTest("tcp6://[fe80::1068:dddb:fe26:fe3f%25" + name + "]:6363", true,
+            "tcp6://[fe80::1068:dddb:fe26:fe3f%" + name + "]:6363");
+
+    addTest("tcp6://[fe80::1068:dddb:fe26:fe3f%" + index + "]:6363", true,
+            "tcp6://[fe80::1068:dddb:fe26:fe3f%" + name + "]:6363");
+  }
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(CanonizeTcpV6, 1)
+BOOST_FIXTURE_TEST_CASE(CanonizeTcpV6, CanonizeFixture)
+{
+  SKIP_IF_IPV6_UNAVAILABLE();
+
+  // IPv6 unicast
+  addTest("tcp6://[2001:db8::1]:6363", true, "tcp6://[2001:db8::1]:6363");
+  addTest("tcp6://[2001:db8::1]", true, "tcp6://[2001:db8::1]:6363");
+  addTest("tcp://[2001:db8::1]:6363", true, "tcp6://[2001:db8::1]:6363");
+  addTest("tcp6://[2001:db8::01]:6363", true, "tcp6://[2001:db8::1]:6363");
+  addTest("tcp6://[2001::db8::1]:6363", false, "");
+  addTest("tcp6://[2001:db8::1]:99999", false, ""); // Bug #3897
+  addTest("tcp6://google-public-dns-a.google.com", true, "tcp6://[2001:4860:4860::8888]:6363");
+  addTest("tcp6://google-public-dns-a.google.com:70000", false, "");
+  addTest("tcp6://invalid.invalid", false, "");
+  addTest("tcp://invalid.invalid", false, "");
+
+  // IPv6 multicast
+  addTest("tcp6://[ff02::2]:56363", false, "");
+  addTest("tcp6://[ff02::2]", false, "");
+
+  // IPv4 used with tcp6 protocol - not canonical
+  addTest("tcp6://192.0.2.1:6363", false, "");
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(ParseUnix, 1)
+BOOST_AUTO_TEST_CASE(ParseUnix)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("unix:///var/run/example.sock"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "unix");
+  BOOST_CHECK_EQUAL(uri.getHost(), "");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "/var/run/example.sock");
+
+  // This is not a valid unix:// URI, but the parse would treat "var" as host
+  BOOST_CHECK_EQUAL(uri.parse("unix://var/run/example.sock"), false); // Bug #3896
+
+#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
+  using boost::asio::local::stream_protocol;
+  stream_protocol::endpoint endpoint("/var/run/example.sock");
+  uri = FaceUri(endpoint);
+  BOOST_CHECK_EQUAL(uri.toString(), "unix:///var/run/example.sock");
+#endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
+}
+
+BOOST_AUTO_TEST_CASE(ParseFd)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("fd://6"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "fd");
+  BOOST_CHECK_EQUAL(uri.getHost(), "6");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  int fd = 21;
+  uri = FaceUri::fromFd(fd);
+  BOOST_CHECK_EQUAL(uri.toString(), "fd://21");
+}
+
+BOOST_AUTO_TEST_CASE(ParseEther)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("ether://[08:00:27:01:dd:01]"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "ether");
+  BOOST_CHECK_EQUAL(uri.getHost(), "08:00:27:01:dd:01");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("ether://[08:00:27:zz:dd:01]"), false);
+
+  auto address = ethernet::Address::fromString("33:33:01:01:01:01");
+  uri = FaceUri(address);
+  BOOST_CHECK_EQUAL(uri.toString(), "ether://[33:33:01:01:01:01]");
+}
+
+BOOST_FIXTURE_TEST_CASE(CanonizeEther, CanonizeFixture)
+{
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("ether"), true);
+
+  BOOST_CHECK_EQUAL(FaceUri("ether://[08:00:27:01:01:01]").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("ether://[08:00:27:1:1:1]").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("ether://[08:00:27:01:01:01]/").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("ether://[33:33:01:01:01:01]").isCanonical(), true);
+
+  addTest("ether://[08:00:27:01:01:01]", true, "ether://[08:00:27:01:01:01]");
+  addTest("ether://[08:00:27:1:1:1]", true, "ether://[08:00:27:01:01:01]");
+  addTest("ether://[08:00:27:01:01:01]/", true, "ether://[08:00:27:01:01:01]");
+  addTest("ether://[33:33:01:01:01:01]", true, "ether://[33:33:01:01:01:01]");
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(ParseDev, 1)
+BOOST_AUTO_TEST_CASE(ParseDev)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("dev://eth0"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "dev");
+  BOOST_CHECK_EQUAL(uri.getHost(), "eth0");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("dev://eth0:8888"), false); // Bug #3896
+
+  std::string ifname = "en1";
+  uri = FaceUri::fromDev(ifname);
+  BOOST_CHECK_EQUAL(uri.toString(), "dev://en1");
+}
+
+BOOST_AUTO_TEST_CASE(IsCanonicalDev)
+{
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("dev"), true);
+
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("dev://").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0:8888").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0/").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0/A").isCanonical(), false);
+}
+
+BOOST_FIXTURE_TEST_CASE(CanonizeDev, CanonizeFixture)
+{
+  addTest("dev://eth0", true, "dev://eth0");
+  addTest("dev://", false, "");
+  addTest("dev://eth0:8888", false, "");
+  addTest("dev://eth0/", true, "dev://eth0");
+  addTest("dev://eth0/A", false, "");
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE(ParseUdpDev)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("udp4+dev://eth0:7777"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "udp4+dev");
+  BOOST_CHECK_EQUAL(uri.getHost(), "eth0");
+  BOOST_CHECK_EQUAL(uri.getPort(), "7777");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK(uri.parse("udp6+dev://eth1:7777"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "udp6+dev");
+  BOOST_CHECK_EQUAL(uri.getHost(), "eth1");
+  BOOST_CHECK_EQUAL(uri.getPort(), "7777");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK(uri.parse("abc+efg://eth0"));
+  BOOST_CHECK(!uri.parse("abc+://eth0"));
+  BOOST_CHECK(!uri.parse("+abc://eth0"));
+
+  namespace ip = boost::asio::ip;
+
+  ip::udp::endpoint endpoint4(ip::udp::v4(), 7777);
+  uri = FaceUri::fromUdpDev(endpoint4, "en1");
+  BOOST_CHECK_EQUAL(uri.toString(), "udp4+dev://en1:7777");
+
+  ip::udp::endpoint endpoint6(ip::udp::v6(), 7777);
+  uri = FaceUri::fromUdpDev(endpoint6, "en2");
+  BOOST_CHECK_EQUAL(uri.toString(), "udp6+dev://en2:7777");
+}
+
+BOOST_FIXTURE_TEST_CASE(CanonizeUdpDev, CanonizeFixture)
+{
+  BOOST_CHECK_EQUAL(FaceUri("udp4+dev://eth0:7777").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("udp6+dev://eth1:7777").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("udp+dev://eth1:7777").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("udp6+dev://eth1").isCanonical(), false);
+
+  addTest("udp4+dev://en0:7777", true, "udp4+dev://en0:7777");
+  addTest("udp6+dev://en0:7777", true, "udp6+dev://en0:7777");
+  addTest("udp+dev://en1:7777", false, "");
+  addTest("udp6+dev://en2", false, "");
+}
+
+BOOST_AUTO_TEST_CASE(CanonizeEmptyCallback)
+{
+  boost::asio::io_service io;
+
+  // unsupported scheme
+  FaceUri("null://").canonize(FaceUri::CanonizeSuccessCallback(),
+                              FaceUri::CanonizeFailureCallback(),
+                              io, 1_ms);
+
+  // cannot resolve
+  FaceUri("udp://192.0.2.333").canonize(FaceUri::CanonizeSuccessCallback(),
+                                        FaceUri::CanonizeFailureCallback(),
+                                        io, 1_ms);
+
+  // already canonical
+  FaceUri("udp4://192.0.2.1:6363").canonize(FaceUri::CanonizeSuccessCallback(),
+                                            FaceUri::CanonizeFailureCallback(),
+                                            io, 1_ms);
+
+  // need DNS resolution
+  FaceUri("udp://192.0.2.1:6363").canonize(FaceUri::CanonizeSuccessCallback(),
+                                           FaceUri::CanonizeFailureCallback(),
+                                           io, 1_ms);
+
+  io.run(); // should not crash
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_FIXTURE_TEST_CASE(CanonizeUnsupported, CanonizeFixture)
+{
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("internal"), false);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("null"), false);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("unix"), false);
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("fd"), false);
+
+  BOOST_CHECK_EQUAL(FaceUri("internal://").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("null://").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("unix:///var/run/nfd.sock").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("fd://0").isCanonical(), false);
+
+  addTest("internal://", false, "");
+  addTest("null://", false, "");
+  addTest("unix:///var/run/nfd.sock", false, "");
+  addTest("fd://0", false, "");
+
+  runTests();
+}
+
+BOOST_AUTO_TEST_CASE(Bug1635)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("wsclient://[::ffff:76.90.11.239]:56366"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "wsclient");
+  BOOST_CHECK_EQUAL(uri.getHost(), "76.90.11.239");
+  BOOST_CHECK_EQUAL(uri.getPort(), "56366");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+  BOOST_CHECK_EQUAL(uri.toString(), "wsclient://76.90.11.239:56366");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestFaceUri
+BOOST_AUTO_TEST_SUITE_END() // Net
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/net/network-configuration-detector.cpp b/tests/unit/net/network-configuration-detector.cpp
new file mode 100644
index 0000000..3e4f128
--- /dev/null
+++ b/tests/unit/net/network-configuration-detector.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "network-configuration-detector.hpp"
+
+#include <boost/asio/ip/address.hpp>
+#include <boost/asio/ip/udp.hpp>
+#include <boost/asio/ip/basic_resolver.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/range/iterator_range_core.hpp>
+
+namespace ndn {
+namespace tests {
+
+bool NetworkConfigurationDetector::m_isInitialized = false;
+bool NetworkConfigurationDetector::m_hasIpv4 = false;
+bool NetworkConfigurationDetector::m_hasIpv6 = false;
+
+bool
+NetworkConfigurationDetector::hasIpv4()
+{
+  if (!m_isInitialized) {
+    detect();
+  }
+  return m_hasIpv4;
+}
+
+bool
+NetworkConfigurationDetector::hasIpv6()
+{
+  if (!m_isInitialized) {
+    detect();
+  }
+  return m_hasIpv6;
+}
+
+void
+NetworkConfigurationDetector::detect()
+{
+  typedef boost::asio::ip::basic_resolver<boost::asio::ip::udp> BoostResolver;
+
+  boost::asio::io_service ioService;
+  BoostResolver resolver(ioService);
+
+  // The specified hostname must contain both A and AAAA records
+  BoostResolver::query query("a.root-servers.net", "");
+
+  boost::system::error_code errorCode;
+  BoostResolver::iterator begin = resolver.resolve(query, errorCode);
+  if (errorCode) {
+    m_isInitialized = true;
+    return;
+  }
+  BoostResolver::iterator end;
+
+  for (const auto& i : boost::make_iterator_range(begin, end)) {
+    if (i.endpoint().address().is_v4()) {
+      m_hasIpv4 = true;
+    }
+    else if (i.endpoint().address().is_v6()) {
+      m_hasIpv6 = true;
+    }
+  }
+
+  m_isInitialized = true;
+}
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/net/network-configuration-detector.hpp b/tests/unit/net/network-configuration-detector.hpp
new file mode 100644
index 0000000..58715c8
--- /dev/null
+++ b/tests/unit/net/network-configuration-detector.hpp
@@ -0,0 +1,75 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_NET_NETWORK_CONFIGURATION_DETECTOR_HPP
+#define NDN_TESTS_NET_NETWORK_CONFIGURATION_DETECTOR_HPP
+
+#define SKIP_IF_IPV4_UNAVAILABLE() \
+  do { \
+    if (!::ndn::tests::NetworkConfigurationDetector::hasIpv4()) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require IPv4 support"); \
+      return; \
+    } \
+  } while (false)
+
+#define SKIP_IF_IPV6_UNAVAILABLE() \
+  do { \
+    if (!::ndn::tests::NetworkConfigurationDetector::hasIpv6()) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require IPv6 support"); \
+      return; \
+    } \
+  } while (false)
+
+#define SKIP_IF_IP_UNAVAILABLE() \
+  do { \
+    if (!::ndn::tests::NetworkConfigurationDetector::hasIpv4() && \
+        !::ndn::tests::NetworkConfigurationDetector::hasIpv6()) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require either IPv4 or IPv6 support"); \
+      return; \
+    } \
+  } while (false)
+
+namespace ndn {
+namespace tests {
+
+class NetworkConfigurationDetector
+{
+public:
+  static bool
+  hasIpv4();
+
+  static bool
+  hasIpv6();
+
+private:
+  static void
+  detect();
+
+private:
+  static bool m_isInitialized;
+  static bool m_hasIpv4;
+  static bool m_hasIpv6;
+};
+
+} // namespace tests
+} // namespace ndn
+
+#endif // NDN_TESTS_NET_NETWORK_CONFIGURATION_DETECTOR_HPP
diff --git a/tests/unit/net/network-monitor-stub.t.cpp b/tests/unit/net/network-monitor-stub.t.cpp
new file mode 100644
index 0000000..8fbf3eb
--- /dev/null
+++ b/tests/unit/net/network-monitor-stub.t.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "net/network-monitor-stub.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace net {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Net)
+BOOST_AUTO_TEST_SUITE(TestNetworkMonitorStub)
+
+BOOST_AUTO_TEST_CASE(Capabilities)
+{
+  NetworkMonitorStub stub(NetworkMonitor::CAP_ENUM | NetworkMonitor::CAP_IF_ADD_REMOVE);
+  BOOST_CHECK_EQUAL(stub.getCapabilities(),
+                    NetworkMonitor::CAP_ENUM | NetworkMonitor::CAP_IF_ADD_REMOVE);
+}
+
+class StubFixture
+{
+public:
+  StubFixture()
+    : stub(~0)
+  {
+    stub.onEnumerationCompleted.connect([this] { signals.push_back("EnumerationCompleted"); });
+    stub.onInterfaceAdded.connect([this] (shared_ptr<const NetworkInterface> netif) {
+      signals.push_back("InterfaceAdded " + netif->getName()); });
+    stub.onInterfaceRemoved.connect([this] (shared_ptr<const NetworkInterface> netif) {
+      signals.push_back("InterfaceRemoved " + netif->getName()); });
+  }
+
+public:
+  NetworkMonitorStub stub;
+  std::vector<std::string> signals;
+};
+
+BOOST_FIXTURE_TEST_CASE(AddInterface, StubFixture)
+{
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().size(), 0);
+  BOOST_CHECK(stub.getNetworkInterface("eth1") == nullptr);
+
+  shared_ptr<NetworkInterface> if1 = stub.makeNetworkInterface();
+  if1->setIndex(13697);
+  if1->setName("eth1");
+  stub.addInterface(if1);
+  if1.reset();
+  BOOST_REQUIRE(stub.getNetworkInterface("eth1") != nullptr);
+  BOOST_CHECK_EQUAL(stub.getNetworkInterface("eth1")->getIndex(), 13697);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().at(0)->getIndex(), 13697);
+  BOOST_REQUIRE_EQUAL(signals.size(), 1);
+  BOOST_CHECK_EQUAL(signals.back(), "InterfaceAdded eth1");
+
+  shared_ptr<NetworkInterface> if1b = stub.makeNetworkInterface();
+  if1b->setIndex(3280);
+  if1b->setName("eth1");
+  BOOST_CHECK_THROW(stub.addInterface(if1b), std::invalid_argument);
+  BOOST_CHECK(stub.getNetworkInterface("eth1") != nullptr);
+  BOOST_CHECK_EQUAL(stub.getNetworkInterface("eth1")->getIndex(), 13697);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().at(0)->getIndex(), 13697);
+  BOOST_CHECK_EQUAL(signals.size(), 1);
+
+  stub.emitEnumerationCompleted();
+  BOOST_REQUIRE_EQUAL(signals.size(), 2);
+  BOOST_CHECK_EQUAL(signals.back(), "EnumerationCompleted");
+
+  shared_ptr<NetworkInterface> if2 = stub.makeNetworkInterface();
+  if2->setIndex(19243);
+  if2->setName("eth2");
+  stub.addInterface(if2);
+  if2.reset();
+  BOOST_REQUIRE(stub.getNetworkInterface("eth2") != nullptr);
+  BOOST_CHECK_EQUAL(stub.getNetworkInterface("eth2")->getIndex(), 19243);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().size(), 2);
+  BOOST_REQUIRE_EQUAL(signals.size(), 3);
+  BOOST_CHECK_EQUAL(signals.back(), "InterfaceAdded eth2");
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveInterface, StubFixture)
+{
+  shared_ptr<NetworkInterface> if1 = stub.makeNetworkInterface();
+  if1->setIndex(13697);
+  if1->setName("eth1");
+  stub.addInterface(if1);
+
+  stub.emitEnumerationCompleted();
+  BOOST_REQUIRE_EQUAL(signals.size(), 2);
+  BOOST_CHECK_EQUAL(signals.back(), "EnumerationCompleted");
+
+  stub.removeInterface("eth1");
+  BOOST_CHECK(stub.getNetworkInterface("eth1") == nullptr);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().size(), 0);
+  BOOST_REQUIRE_EQUAL(signals.size(), 3);
+  BOOST_CHECK_EQUAL(signals.back(), "InterfaceRemoved eth1");
+
+  stub.removeInterface("eth2"); // non-existent
+  BOOST_CHECK_EQUAL(signals.size(), 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNetworkMonitorStub
+BOOST_AUTO_TEST_SUITE_END() // Net
+
+} // namespace tests
+} // namespace net
+} // namespace ndn
diff --git a/tests/unit/net/network-monitor.t.cpp b/tests/unit/net/network-monitor.t.cpp
new file mode 100644
index 0000000..b522ee6
--- /dev/null
+++ b/tests/unit/net/network-monitor.t.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "net/network-monitor.hpp"
+
+#include "boost-test.hpp"
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace net {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Net)
+BOOST_AUTO_TEST_SUITE(TestNetworkMonitor)
+
+#define NM_REQUIRE_CAP(capability) \
+  do { \
+    if ((nm->getCapabilities() & NetworkMonitor::CAP_ ## capability) == 0) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require " #capability " capability"); \
+      return; \
+    } \
+  } while (false)
+
+BOOST_AUTO_TEST_CASE(DestructWithoutRun)
+{
+  boost::asio::io_service io;
+  auto nm = make_unique<NetworkMonitor>(io);
+  nm.reset();
+  BOOST_CHECK(true); // if we got this far, the test passed
+}
+
+BOOST_AUTO_TEST_CASE(DestructWhileEnumerating)
+{
+  boost::asio::io_service io;
+  auto nm = make_unique<NetworkMonitor>(io);
+  NM_REQUIRE_CAP(ENUM);
+
+  nm->onInterfaceAdded.connect([&] (const shared_ptr<const NetworkInterface>&) {
+    io.post([&] { nm.reset(); });
+  });
+  nm->onEnumerationCompleted.connect([&] {
+    // make sure the test case terminates even if we have zero interfaces
+    io.post([&] { nm.reset(); });
+  });
+
+  io.run();
+  BOOST_CHECK(true); // if we got this far, the test passed
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNetworkMonitor
+BOOST_AUTO_TEST_SUITE_END() // Net
+
+} // namespace tests
+} // namespace net
+} // namespace ndn
diff --git a/tests/unit/packet-base.t.cpp b/tests/unit/packet-base.t.cpp
new file mode 100644
index 0000000..8b4dfa2
--- /dev/null
+++ b/tests/unit/packet-base.t.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "packet-base.hpp"
+
+#include "../boost-test.hpp"
+#include "interest.hpp"
+#include "lp/tags.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestPacketBase)
+
+BOOST_AUTO_TEST_CASE(CongestionMark)
+{
+  Interest interest;
+
+  BOOST_CHECK_EQUAL(interest.getCongestionMark(), 0);
+
+  auto tag = interest.getTag<lp::CongestionMarkTag>();
+  BOOST_CHECK(!tag);
+
+  interest.setCongestionMark(true);
+  tag = interest.getTag<lp::CongestionMarkTag>();
+  BOOST_REQUIRE(tag);
+  BOOST_CHECK_EQUAL(*tag, 1);
+
+  interest.setCongestionMark(false);
+  tag = interest.getTag<lp::CongestionMarkTag>();
+  BOOST_CHECK(!tag);
+
+  interest.setCongestionMark(300);
+  tag = interest.getTag<lp::CongestionMarkTag>();
+  BOOST_REQUIRE(tag);
+  BOOST_CHECK_EQUAL(*tag, 300);
+
+  interest.setCongestionMark(0);
+  tag = interest.getTag<lp::CongestionMarkTag>();
+  BOOST_CHECK(!tag);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPacketBase
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/prefix-announcement.t.cpp b/tests/unit/prefix-announcement.t.cpp
new file mode 100644
index 0000000..fa69eb3
--- /dev/null
+++ b/tests/unit/prefix-announcement.t.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "prefix-announcement.hpp"
+#include "encoding/tlv-nfd.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestPrefixAnnouncement)
+
+static Data
+makePrefixAnnData()
+{
+  return Data(
+    "067A 071A announced-name=/net/example 08036E6574 08076578616D706C65"
+    "          keyword-prefix-ann=20025041 version=0802FD01 segment=08020000"
+    "     1403 content-type=prefix-ann 180105"
+    "     1530 expire in one hour 6D040036EE80"
+    "          validity FD00FD26 FD00FE0F323031383130333054303030303030"
+    "                            FD00FF0F323031383131323454323335393539"
+    "     1603 1B0100 signature"
+    "     1720 0000000000000000000000000000000000000000000000000000000000000000"_block);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeGood)
+{
+  Data data0 = makePrefixAnnData();
+  PrefixAnnouncement pa0(data0);
+  BOOST_CHECK_EQUAL(pa0.getAnnouncedName(), "/net/example");
+  BOOST_CHECK_EQUAL(pa0.getExpiration(), 1_h);
+  BOOST_CHECK(pa0.getValidityPeriod());
+  BOOST_CHECK_EQUAL(*pa0.getValidityPeriod(),
+                    security::ValidityPeriod(time::fromIsoString("20181030T000000"),
+                                             time::fromIsoString("20181124T235959")));
+
+  // reorder ExpirationPeriod and ValidityPeriod, unrecognized non-critical element
+  Data data1 = makePrefixAnnData();
+  Block payload1 = data1.getContent();
+  payload1.parse();
+  Block expirationElement = payload1.get(tlv::nfd::ExpirationPeriod);
+  payload1.remove(tlv::nfd::ExpirationPeriod);
+  payload1.push_back(expirationElement);
+  payload1.push_back("2000"_block);
+  payload1.encode();
+  data1.setContent(payload1);
+  PrefixAnnouncement pa1(data1);
+  BOOST_CHECK_EQUAL(pa1.getAnnouncedName(), "/net/example");
+  BOOST_CHECK_EQUAL(pa1.getExpiration(), 1_h);
+  BOOST_CHECK(pa1.getValidityPeriod());
+  BOOST_CHECK_EQUAL(*pa1.getValidityPeriod(),
+                    security::ValidityPeriod(time::fromIsoString("20181030T000000"),
+                                             time::fromIsoString("20181124T235959")));
+
+  // no ValidityPeriod
+  Data data2 = makePrefixAnnData();
+  Block payload2 = data2.getContent();
+  payload2.parse();
+  payload2.remove(tlv::ValidityPeriod);
+  payload2.encode();
+  data2.setContent(payload2);
+  PrefixAnnouncement pa2(data2);
+  BOOST_CHECK_EQUAL(pa2.getAnnouncedName(), "/net/example");
+  BOOST_CHECK_EQUAL(pa2.getExpiration(), 1_h);
+  BOOST_CHECK(!pa2.getValidityPeriod());
+}
+
+BOOST_AUTO_TEST_CASE(DecodeBad)
+{
+  // wrong ContentType
+  Data data0 = makePrefixAnnData();
+  data0.setContentType(tlv::ContentType_Blob);
+  BOOST_CHECK_THROW(PrefixAnnouncement pa0(data0), tlv::Error);
+
+  // Name has no "32=PA" keyword
+  Data data1 = makePrefixAnnData();
+  setNameComponent(data1, -3, name::Component::fromEscapedString("32=not-PA"));
+  BOOST_CHECK_THROW(PrefixAnnouncement pa1(data1), tlv::Error);
+
+  // Name has no version component
+  Data data2 = makePrefixAnnData();
+  setNameComponent(data2, -2, "not-version");
+  BOOST_CHECK_THROW(PrefixAnnouncement pa2(data2), tlv::Error);
+
+  // Name has no segment number component
+  Data data3 = makePrefixAnnData();
+  setNameComponent(data3, -2, "not-segment");
+  BOOST_CHECK_THROW(PrefixAnnouncement pa3(data3), tlv::Error);
+
+  // Content has no ExpirationPeriod element
+  Data data4 = makePrefixAnnData();
+  Block payload4 = data4.getContent();
+  payload4.parse();
+  payload4.remove(tlv::nfd::ExpirationPeriod);
+  payload4.encode();
+  data4.setContent(payload4);
+  BOOST_CHECK_THROW(PrefixAnnouncement pa4(data4), tlv::Error);
+
+  // ExpirationPeriod is malformed
+  Data data5 = makePrefixAnnData();
+  Block payload5 = data5.getContent();
+  payload5.parse();
+  payload5.remove(tlv::nfd::ExpirationPeriod);
+  payload5.push_back("6D03010101"_block);
+  payload5.encode();
+  data5.setContent(payload5);
+  BOOST_CHECK_THROW(PrefixAnnouncement pa5(data5), tlv::Error);
+
+  // ValidityPeriod is malformed
+  Data data6 = makePrefixAnnData();
+  Block payload6 = data6.getContent();
+  payload6.parse();
+  payload6.remove(tlv::ValidityPeriod);
+  payload6.push_back("FD00FD00"_block);
+  payload6.encode();
+  data6.setContent(payload6);
+  BOOST_CHECK_THROW(PrefixAnnouncement pa6(data6), tlv::Error);
+
+  // Content has unrecognized critical element
+  Data data7 = makePrefixAnnData();
+  Block payload7 = data7.getContent();
+  payload7.parse();
+  payload7.push_back("0200"_block);
+  payload7.encode();
+  data7.setContent(payload7);
+  BOOST_CHECK_THROW(PrefixAnnouncement pa7(data7), tlv::Error);
+}
+
+BOOST_FIXTURE_TEST_CASE(EncodeEmpty, IdentityManagementFixture)
+{
+  PrefixAnnouncement pa;
+  BOOST_CHECK(!pa.getData());
+  BOOST_CHECK_EQUAL(pa.getAnnouncedName(), "/");
+  BOOST_CHECK_EQUAL(pa.getExpiration(), 0_ms);
+  BOOST_CHECK(!pa.getValidityPeriod());
+
+  const Data& data = pa.toData(m_keyChain, signingWithSha256(), 5);
+  BOOST_CHECK_EQUAL(data.getName(), "/32=PA/%FD%05/%00%00");
+  BOOST_CHECK_EQUAL(data.getContentType(), tlv::ContentType_PrefixAnn);
+  BOOST_REQUIRE(pa.getData());
+  BOOST_CHECK_EQUAL(*pa.getData(), data);
+
+  PrefixAnnouncement decoded(data);
+  BOOST_CHECK_EQUAL(decoded.getAnnouncedName(), "/");
+  BOOST_CHECK_EQUAL(decoded.getExpiration(), 0_s);
+  BOOST_CHECK(!decoded.getValidityPeriod());
+
+  BOOST_CHECK_EQUAL(pa, decoded);
+}
+
+BOOST_FIXTURE_TEST_CASE(EncodeNoValidity, IdentityManagementFixture)
+{
+  PrefixAnnouncement pa;
+  pa.setAnnouncedName("/net/example");
+  BOOST_CHECK_THROW(pa.setExpiration(-1_ms), std::invalid_argument);
+  pa.setExpiration(1_h);
+
+  const Data& data = pa.toData(m_keyChain, signingWithSha256(), 1);
+  BOOST_CHECK_EQUAL(data.getName(), "/net/example/32=PA/%FD%01/%00%00");
+  BOOST_CHECK_EQUAL(data.getContentType(), tlv::ContentType_PrefixAnn);
+
+  const Block& payload = data.getContent();
+  payload.parse();
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(payload.get(tlv::nfd::ExpirationPeriod)), 3600000);
+  BOOST_CHECK(payload.find(tlv::ValidityPeriod) == payload.elements_end());
+
+  PrefixAnnouncement decoded(data);
+  BOOST_CHECK_EQUAL(decoded.getAnnouncedName(), "/net/example");
+  BOOST_CHECK_EQUAL(decoded.getExpiration(), 1_h);
+  BOOST_CHECK(!decoded.getValidityPeriod());
+
+  BOOST_CHECK_EQUAL(pa, decoded);
+}
+
+BOOST_FIXTURE_TEST_CASE(EncodeWithValidity, IdentityManagementFixture)
+{
+  PrefixAnnouncement pa;
+  pa.setAnnouncedName("/net/example");
+  pa.setExpiration(1_h);
+  security::ValidityPeriod validity(time::fromIsoString("20181030T000000"),
+                                    time::fromIsoString("20181124T235959"));
+  pa.setValidityPeriod(validity);
+
+  const Data& data = pa.toData(m_keyChain);
+  const Block& payload = data.getContent();
+  payload.parse();
+  BOOST_CHECK_EQUAL(readNonNegativeInteger(payload.get(tlv::nfd::ExpirationPeriod)), 3600000);
+  BOOST_CHECK_EQUAL(payload.get(tlv::ValidityPeriod), validity.wireEncode());
+
+  PrefixAnnouncement decoded(data);
+  BOOST_CHECK_EQUAL(decoded.getAnnouncedName(), "/net/example");
+  BOOST_CHECK_EQUAL(decoded.getExpiration(), 1_h);
+  BOOST_REQUIRE(decoded.getValidityPeriod());
+  BOOST_CHECK_EQUAL(*decoded.getValidityPeriod(), validity);
+
+  BOOST_CHECK_EQUAL(pa, decoded);
+}
+
+BOOST_AUTO_TEST_CASE(Modify)
+{
+  PrefixAnnouncement pa(makePrefixAnnData());
+
+  PrefixAnnouncement pa0(pa);
+  BOOST_REQUIRE(pa0.getData());
+  BOOST_CHECK_EQUAL(*pa0.getData(), makePrefixAnnData());
+  pa0.setAnnouncedName("/com/example");
+  BOOST_CHECK(!pa0.getData());
+  BOOST_CHECK_NE(pa0, pa);
+
+  PrefixAnnouncement pa1(makePrefixAnnData());
+  pa1.setExpiration(5_min);
+  BOOST_CHECK(!pa1.getData());
+  BOOST_CHECK_NE(pa1, pa);
+
+  PrefixAnnouncement pa2(makePrefixAnnData());
+  pa2.setValidityPeriod(security::ValidityPeriod(time::fromIsoString("20180118T000000"),
+                                                 time::fromIsoString("20180212T235959")));
+  BOOST_CHECK(!pa2.getData());
+  BOOST_CHECK_NE(pa2, pa);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPrefixAnnouncement
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/security/command-interest-signer.t.cpp b/tests/unit/security/command-interest-signer.t.cpp
new file mode 100644
index 0000000..784a209
--- /dev/null
+++ b/tests/unit/security/command-interest-signer.t.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/command-interest-signer.hpp"
+#include "security/signing-helpers.hpp"
+
+#include "boost-test.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_FIXTURE_TEST_SUITE(TestCommandInterestSigner, IdentityManagementTimeFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  addIdentity("/test");
+
+  CommandInterestSigner signer(m_keyChain);
+  Interest i1 = signer.makeCommandInterest("/hello/world");
+  BOOST_CHECK_EQUAL(i1.getName().size(), 6);
+  BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_SIG_VALUE).blockFromValue().type(), tlv::SignatureValue);
+  BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_SIG_INFO).blockFromValue().type(), tlv::SignatureInfo);
+
+  time::milliseconds timestamp = toUnixTimestamp(time::system_clock::now());
+  BOOST_CHECK_EQUAL(i1.getName().at(command_interest::POS_TIMESTAMP).toNumber(), timestamp.count());
+
+  Interest i2 = signer.makeCommandInterest("/hello/world/!", signingByIdentity("/test"));
+  BOOST_CHECK_EQUAL(i2.getName().size(), 7);
+  BOOST_CHECK_EQUAL(i2.getName().at(command_interest::POS_SIG_VALUE).blockFromValue().type(), tlv::SignatureValue);
+  BOOST_CHECK_EQUAL(i2.getName().at(command_interest::POS_SIG_INFO).blockFromValue().type(), tlv::SignatureInfo);
+  BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP), i1.getName().at(command_interest::POS_TIMESTAMP));
+  BOOST_CHECK_NE(i2.getName().at(command_interest::POS_RANDOM_VAL),
+                 i1.getName().at(command_interest::POS_RANDOM_VAL)); // this sometimes can fail
+
+  advanceClocks(100_s);
+
+  i2 = signer.makeCommandInterest("/hello/world/!");
+  BOOST_CHECK_GT(i2.getName().at(command_interest::POS_TIMESTAMP), i1.getName().at(command_interest::POS_TIMESTAMP));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandInterestSigner
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/config-file-readme.txt b/tests/unit/security/config-file-readme.txt
new file mode 100644
index 0000000..44e5f61
--- /dev/null
+++ b/tests/unit/security/config-file-readme.txt
@@ -0,0 +1,11 @@
+In test, we set a test-specific "HOME", which cause OS X keychain look for the
+default keychain of a "different" user. If the default keychain does not exist,
+all subsequent calls to OS X keychain will fail. User interaction (such as
+specifying password) is required to create a keychain. However, user interaction
+is not feasible in automated tests.
+
+This problem is caused by the OS X system assumption that one user must have a
+login keychain, which is also the user's default keychain, because a user
+account is always created with a login keychain as default. Thus OS X system
+infers a user according to the HOME env, and did not expect user to change the
+HOME env in normal use.
diff --git a/tests/unit/security/digest-sha256.t.cpp b/tests/unit/security/digest-sha256.t.cpp
new file mode 100644
index 0000000..7c1d236
--- /dev/null
+++ b/tests/unit/security/digest-sha256.t.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/digest-sha256.hpp"
+#include "security/verification-helpers.hpp"
+#include "util/sha256.hpp"
+#include "util/string-helper.hpp"
+
+#include "identity-management-fixture.hpp"
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_FIXTURE_TEST_SUITE(TestDigestSha256, IdentityManagementFixture)
+
+BOOST_AUTO_TEST_CASE(Sha256)
+{
+  char content[6] = "1234\n";
+  ConstBufferPtr buf = util::Sha256::computeDigest(reinterpret_cast<uint8_t*>(content), 5);
+
+  BOOST_CHECK_EQUAL(toHex(buf->data(), buf->size(), false),
+                    "a883dafc480d466ee04e0d6da986bd78eb1fdd2178d04693723da3a8f95d42f4");
+}
+
+BOOST_AUTO_TEST_CASE(DataSignature)
+{
+  Name name("/TestSignatureSha/Basic");
+  Data testData(name);
+  char content[5] = "1234";
+  testData.setContent(reinterpret_cast<uint8_t*>(content), 5);
+  m_keyChain.sign(testData, security::SigningInfo(security::SigningInfo::SIGNER_TYPE_SHA256));
+
+  BOOST_CHECK_THROW(testData.getSignature().getKeyLocator(), ndn::SignatureInfo::Error);
+  verifyDigest(testData, DigestAlgorithm::SHA256);
+}
+
+BOOST_AUTO_TEST_CASE(InterestSignature)
+{
+  Name name("/SecurityTestDigestSha256/InterestSignature/Interest1");
+  Interest testInterest(name);
+
+  m_keyChain.sign(testInterest, security::SigningInfo(security::SigningInfo::SIGNER_TYPE_SHA256));
+  verifyDigest(testInterest, DigestAlgorithm::SHA256);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDigestSha256
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/key-params.t.cpp b/tests/unit/security/key-params.t.cpp
new file mode 100644
index 0000000..991c346
--- /dev/null
+++ b/tests/unit/security/key-params.t.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/key-params.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestKeyParams)
+
+BOOST_AUTO_TEST_CASE(Rsa)
+{
+  RsaKeyParams params;
+  BOOST_CHECK_EQUAL(params.getKeyType(), KeyType::RSA);
+  BOOST_CHECK_EQUAL(params.getKeySize(), 2048);
+  BOOST_CHECK(params.getKeyIdType() == KeyIdType::RANDOM);
+
+  RsaKeyParams params2(4096, KeyIdType::SHA256);
+  BOOST_CHECK_EQUAL(params2.getKeyType(), KeyType::RSA);
+  BOOST_CHECK_EQUAL(params2.getKeySize(), 4096);
+  BOOST_CHECK(params2.getKeyIdType() == KeyIdType::SHA256);
+
+  BOOST_CHECK_THROW(RsaKeyParams(1024), KeyParams::Error);
+
+  name::Component keyId("keyId");
+  RsaKeyParams params4(keyId);
+  BOOST_CHECK(params4.getKeyType() == KeyType::RSA);
+  BOOST_CHECK_EQUAL(params4.getKeySize(), 2048);
+  BOOST_CHECK(params4.getKeyIdType() == KeyIdType::USER_SPECIFIED);
+  BOOST_CHECK_EQUAL(params4.getKeyId(), keyId);
+}
+
+BOOST_AUTO_TEST_CASE(Ec)
+{
+  EcKeyParams params;
+  BOOST_CHECK_EQUAL(params.getKeyType(), KeyType::EC);
+  BOOST_CHECK_EQUAL(params.getKeySize(), 256);
+  BOOST_CHECK(params.getKeyIdType() == KeyIdType::RANDOM);
+
+  EcKeyParams params2(384, KeyIdType::SHA256);
+  BOOST_CHECK_EQUAL(params2.getKeyType(), KeyType::EC);
+  BOOST_CHECK_EQUAL(params2.getKeySize(), 384);
+  BOOST_CHECK(params2.getKeyIdType() == KeyIdType::SHA256);
+
+  BOOST_CHECK_THROW(EcKeyParams(3), KeyParams::Error);
+
+  name::Component keyId("keyId");
+  EcKeyParams params4(keyId);
+  BOOST_CHECK(params4.getKeyType() == KeyType::EC);
+  BOOST_CHECK_EQUAL(params4.getKeySize(), 256);
+  BOOST_CHECK(params4.getKeyIdType() == KeyIdType::USER_SPECIFIED);
+  BOOST_CHECK_EQUAL(params4.getKeyId(), keyId);
+}
+
+BOOST_AUTO_TEST_CASE(Aes)
+{
+  name::Component keyId("keyId");
+  AesKeyParams params(keyId);
+  BOOST_CHECK_EQUAL(params.getKeyType(), KeyType::AES);
+  BOOST_CHECK_EQUAL(params.getKeySize(), 128);
+  BOOST_CHECK_EQUAL(params.getKeyIdType(), KeyIdType::USER_SPECIFIED);
+
+  AesKeyParams params2(keyId, 192);
+  BOOST_CHECK(params2.getKeyType() == KeyType::AES);
+  BOOST_CHECK_EQUAL(params2.getKeySize(), 192);
+  BOOST_CHECK(params.getKeyIdType() == KeyIdType::USER_SPECIFIED);
+
+  AesKeyParams params3(keyId, 256);
+  BOOST_CHECK_EQUAL(params3.getKeyType(), KeyType::AES);
+  BOOST_CHECK_EQUAL(params3.getKeySize(), 256);
+  BOOST_CHECK(params.getKeyIdType() == KeyIdType::USER_SPECIFIED);
+
+  BOOST_CHECK_THROW(AesKeyParams(keyId, 4), KeyParams::Error);
+
+  AesKeyParams params5(keyId);
+  BOOST_CHECK_EQUAL(params5.getKeyType(), KeyType::AES);
+  BOOST_CHECK_EQUAL(params5.getKeySize(), 128);
+  BOOST_CHECK_EQUAL(params5.getKeyIdType(), KeyIdType::USER_SPECIFIED);
+  BOOST_CHECK_EQUAL(params5.getKeyId(), keyId);
+
+  AesKeyParams params6(192);
+  BOOST_CHECK(params6.getKeyType() == KeyType::AES);
+  BOOST_CHECK_EQUAL(params6.getKeySize(), 192);
+  BOOST_CHECK(params6.getKeyIdType() == KeyIdType::RANDOM);
+}
+
+BOOST_AUTO_TEST_CASE(KeyIdTypeInfo)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(KeyIdType::USER_SPECIFIED), "USER_SPECIFIED");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(KeyIdType::SHA256), "SHA256");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(KeyIdType::RANDOM), "RANDOM");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<KeyIdType>(12345)), "12345");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestKeyParams
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/security/pib/certificate-container.t.cpp b/tests/unit/security/pib/certificate-container.t.cpp
new file mode 100644
index 0000000..6614531
--- /dev/null
+++ b/tests/unit/security/pib/certificate-container.t.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/certificate-container.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_FIXTURE_TEST_SUITE(TestCertificateContainer, PibDataFixture)
+
+using pib::Pib;
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  auto pibImpl = make_shared<PibMemory>();
+
+  // start with an empty container
+  CertificateContainer container(id1Key1Name, pibImpl);
+  BOOST_CHECK_EQUAL(container.size(), 0);
+  BOOST_CHECK_EQUAL(container.getCache().size(), 0);
+
+  // add one cert
+  container.add(id1Key1Cert1);
+  BOOST_CHECK_EQUAL(container.size(), 1);
+  BOOST_CHECK_EQUAL(container.getCache().size(), 1);
+  BOOST_CHECK(container.find(id1Key1Cert1.getName()) != container.end());
+
+  // add the same cert again
+  container.add(id1Key1Cert1);
+  BOOST_CHECK_EQUAL(container.size(), 1);
+  BOOST_CHECK_EQUAL(container.getCache().size(), 1);
+  BOOST_CHECK(container.find(id1Key1Cert1.getName()) != container.end());
+
+  // add another cert
+  container.add(id1Key1Cert2);
+  BOOST_CHECK_EQUAL(container.size(), 2);
+  BOOST_CHECK_EQUAL(container.getCache().size(), 2);
+  BOOST_CHECK(container.find(id1Key1Cert1.getName()) != container.end());
+  BOOST_CHECK(container.find(id1Key1Cert2.getName()) != container.end());
+
+  // get certs
+  BOOST_REQUIRE_NO_THROW(container.get(id1Key1Cert1.getName()));
+  BOOST_REQUIRE_NO_THROW(container.get(id1Key1Cert2.getName()));
+  Name id1Key1Cert3Name = id1Key1Name;
+  id1Key1Cert3Name.append("issuer").appendVersion(3);
+  BOOST_CHECK_THROW(container.get(id1Key1Cert3Name), Pib::Error);
+
+  // check cert
+  v2::Certificate cert1 = container.get(id1Key1Cert1.getName());
+  v2::Certificate cert2 = container.get(id1Key1Cert2.getName());
+  BOOST_CHECK_EQUAL(cert1, id1Key1Cert1);
+  BOOST_CHECK_EQUAL(cert2, id1Key1Cert2);
+
+  // create another container from the same PibImpl
+  // cache should be empty
+  CertificateContainer container2(id1Key1Name, pibImpl);
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getCache().size(), 0);
+
+  // get certificate, cache should be filled
+  BOOST_REQUIRE_NO_THROW(container2.get(id1Key1Cert1.getName()));
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getCache().size(), 1);
+
+  BOOST_REQUIRE_NO_THROW(container2.get(id1Key1Cert2.getName()));
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getCache().size(), 2);
+
+  // remove a certificate
+  container2.remove(id1Key1Cert1.getName());
+  BOOST_CHECK_EQUAL(container2.size(), 1);
+  BOOST_CHECK_EQUAL(container2.getCache().size(), 1);
+  BOOST_CHECK(container2.find(id1Key1Cert1.getName()) == container2.end());
+  BOOST_CHECK(container2.find(id1Key1Cert2.getName()) != container2.end());
+
+  // remove another certificate
+  container2.remove(id1Key1Cert2.getName());
+  BOOST_CHECK_EQUAL(container2.size(), 0);
+  BOOST_CHECK_EQUAL(container2.getCache().size(), 0);
+  BOOST_CHECK(container2.find(id1Key1Cert2.getName()) == container2.end());
+}
+
+BOOST_AUTO_TEST_CASE(Errors)
+{
+  auto pibImpl = make_shared<PibMemory>();
+
+  CertificateContainer container(id1Key1Name, pibImpl);
+
+  BOOST_CHECK_THROW(container.add(id1Key2Cert1), std::invalid_argument);
+  BOOST_CHECK_THROW(container.remove(id1Key2Cert1.getName()), std::invalid_argument);
+  BOOST_CHECK_THROW(container.get(id1Key2Cert1.getName()), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(Iterator)
+{
+  auto pibImpl = make_shared<PibMemory>();
+
+  // start with an empty container
+  CertificateContainer container(id1Key1Name, pibImpl);
+  container.add(id1Key1Cert1);
+  container.add(id1Key1Cert2);
+
+  std::set<Name> certNames;
+  certNames.insert(id1Key1Cert1.getName());
+  certNames.insert(id1Key1Cert2.getName());
+
+  CertificateContainer::const_iterator it = container.begin();
+  std::set<Name>::const_iterator testIt = certNames.begin();
+  BOOST_CHECK_EQUAL((*it).getName(), *testIt);
+  it++;
+  testIt++;
+  BOOST_CHECK_EQUAL((*it).getName(), *testIt);
+  ++it;
+  testIt++;
+  BOOST_CHECK(it == container.end());
+
+  size_t count = 0;
+  testIt = certNames.begin();
+  for (const auto& cert : container) {
+    BOOST_CHECK_EQUAL(cert.getName(), *testIt);
+    testIt++;
+    count++;
+  }
+  BOOST_CHECK_EQUAL(count, 2);
+
+  BOOST_CHECK(CertificateContainer::const_iterator() == CertificateContainer::const_iterator());
+  BOOST_CHECK(CertificateContainer::const_iterator() == container.end());
+  BOOST_CHECK(container.end() == CertificateContainer::const_iterator());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateContainer
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/detail/identity-impl.t.cpp b/tests/unit/security/pib/detail/identity-impl.t.cpp
new file mode 100644
index 0000000..209a6b6
--- /dev/null
+++ b/tests/unit/security/pib/detail/identity-impl.t.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/detail/identity-impl.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+
+#include "boost-test.hpp"
+#include "../pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace detail {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_AUTO_TEST_SUITE(Detail)
+BOOST_FIXTURE_TEST_SUITE(TestIdentityImpl, ndn::security::tests::PibDataFixture)
+
+using security::Pib;
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+  IdentityImpl identity1(id1, pibImpl, true);
+
+  BOOST_CHECK_EQUAL(identity1.getName(), id1);
+}
+
+BOOST_AUTO_TEST_CASE(KeyOperation)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+  IdentityImpl identity1(id1, pibImpl, true);
+  BOOST_CHECK_NO_THROW(IdentityImpl(id1, pibImpl, false));
+
+  // identity does not have any key
+  BOOST_CHECK_EQUAL(identity1.getKeys().size(), 0);
+
+  // get non-existing key, throw Pib::Error
+  BOOST_CHECK_THROW(identity1.getKey(id1Key1Name), Pib::Error);
+  // get default key, throw Pib::Error
+  BOOST_CHECK_THROW(identity1.getDefaultKey(), Pib::Error);
+  // set non-existing key as default key, throw Pib::Error
+  BOOST_REQUIRE_THROW(identity1.setDefaultKey(id1Key1Name), Pib::Error);
+
+  // add key
+  identity1.addKey(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK_NO_THROW(identity1.getKey(id1Key1Name));
+
+  // new key becomes default key when there is no default key
+  BOOST_REQUIRE_NO_THROW(identity1.getDefaultKey());
+  const Key& defaultKey0 = identity1.getDefaultKey();
+  BOOST_CHECK_EQUAL(defaultKey0.getName(), id1Key1Name);
+  BOOST_CHECK(defaultKey0.getPublicKey() == id1Key1);
+
+  // remove key
+  identity1.removeKey(id1Key1Name);
+  BOOST_CHECK_THROW(identity1.getKey(id1Key1Name), Pib::Error);
+  BOOST_CHECK_THROW(identity1.getDefaultKey(), Pib::Error);
+
+  // set default key directly
+  BOOST_REQUIRE_NO_THROW(identity1.setDefaultKey(id1Key1.data(), id1Key1.size(), id1Key1Name));
+  BOOST_REQUIRE_NO_THROW(identity1.getDefaultKey());
+  BOOST_CHECK_NO_THROW(identity1.getKey(id1Key1Name));
+
+  // check default key
+  const Key& defaultKey1 = identity1.getDefaultKey();
+  BOOST_CHECK_EQUAL(defaultKey1.getName(), id1Key1Name);
+  BOOST_CHECK(defaultKey1.getPublicKey() == id1Key1);
+
+  // add another key
+  identity1.addKey(id1Key2.data(), id1Key2.size(), id1Key2Name);
+  BOOST_CHECK_EQUAL(identity1.getKeys().size(), 2);
+
+  // set default key through name
+  BOOST_REQUIRE_NO_THROW(identity1.setDefaultKey(id1Key2Name));
+  BOOST_REQUIRE_NO_THROW(identity1.getDefaultKey());
+  const Key& defaultKey2 = identity1.getDefaultKey();
+  BOOST_CHECK_EQUAL(defaultKey2.getName(), id1Key2Name);
+  BOOST_CHECK(defaultKey2.getPublicKey() == id1Key2);
+
+  // remove key
+  identity1.removeKey(id1Key1Name);
+  BOOST_CHECK_THROW(identity1.getKey(id1Key1Name), Pib::Error);
+  BOOST_CHECK_EQUAL(identity1.getKeys().size(), 1);
+
+  // set default key directly again, change the default setting
+  BOOST_REQUIRE_NO_THROW(identity1.setDefaultKey(id1Key1.data(), id1Key1.size(), id1Key1Name));
+  const Key& defaultKey3 = identity1.getDefaultKey();
+  BOOST_CHECK_EQUAL(defaultKey3.getName(), id1Key1Name);
+  BOOST_CHECK(defaultKey3.getPublicKey() == id1Key1);
+  BOOST_CHECK_EQUAL(identity1.getKeys().size(), 2);
+
+  // remove all keys
+  identity1.removeKey(id1Key1Name);
+  BOOST_CHECK_THROW(identity1.getKey(id1Key1Name), Pib::Error);
+  BOOST_CHECK_EQUAL(identity1.getKeys().size(), 1);
+  identity1.removeKey(id1Key2Name);
+  BOOST_CHECK_THROW(identity1.getKey(id1Key2Name), Pib::Error);
+  BOOST_CHECK_EQUAL(identity1.getKeys().size(), 0);
+  BOOST_CHECK_THROW(identity1.getDefaultKey(), Pib::Error);
+}
+
+BOOST_AUTO_TEST_CASE(Overwrite)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+  IdentityImpl identity1(id1, pibImpl, true);
+
+  identity1.addKey(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK(identity1.getKey(id1Key1Name).getPublicKey() == id1Key1);
+
+  identity1.addKey(id1Key2.data(), id1Key2.size(), id1Key1Name); // overwriting key should work
+  BOOST_CHECK(identity1.getKey(id1Key1Name).getPublicKey() == id1Key2);
+}
+
+BOOST_AUTO_TEST_CASE(Errors)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+
+  BOOST_CHECK_THROW(IdentityImpl(id1, pibImpl, false), Pib::Error);
+  IdentityImpl identity1(id1, pibImpl, true);
+
+  identity1.addKey(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK_THROW(identity1.addKey(id2Key1.data(), id2Key1.size(), id2Key1Name), std::invalid_argument);
+  BOOST_CHECK_THROW(identity1.removeKey(id2Key1Name), std::invalid_argument);
+  BOOST_CHECK_THROW(identity1.getKey(id2Key1Name), std::invalid_argument);
+  BOOST_CHECK_THROW(identity1.setDefaultKey(id2Key1.data(), id2Key1.size(), id2Key1Name), std::invalid_argument);
+  BOOST_CHECK_THROW(identity1.setDefaultKey(id2Key1Name), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIdentityImpl
+BOOST_AUTO_TEST_SUITE_END() // Detail
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace detail
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/detail/key-impl.t.cpp b/tests/unit/security/pib/detail/key-impl.t.cpp
new file mode 100644
index 0000000..517ea60
--- /dev/null
+++ b/tests/unit/security/pib/detail/key-impl.t.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/detail/key-impl.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+#include "../pib-data-fixture.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace detail {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_AUTO_TEST_SUITE(Detail)
+BOOST_FIXTURE_TEST_SUITE(TestKeyImpl, security::tests::PibDataFixture)
+
+using security::Pib;
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+  KeyImpl key11(id1Key1Name, id1Key1.data(), id1Key1.size(), pibImpl);
+
+  BOOST_CHECK_EQUAL(key11.getName(), id1Key1Name);
+  BOOST_CHECK_EQUAL(key11.getIdentity(), id1);
+  BOOST_CHECK_EQUAL(key11.getKeyType(), KeyType::EC);
+  BOOST_CHECK(key11.getPublicKey() == id1Key1);
+
+  KeyImpl key11Bak(id1Key1Name, pibImpl);
+  BOOST_CHECK_EQUAL(key11Bak.getName(), id1Key1Name);
+  BOOST_CHECK_EQUAL(key11Bak.getIdentity(), id1);
+  BOOST_CHECK_EQUAL(key11Bak.getKeyType(), KeyType::EC);
+  BOOST_CHECK(key11Bak.getPublicKey() == id1Key1);
+}
+
+BOOST_AUTO_TEST_CASE(CertificateOperation)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+  KeyImpl key11(id1Key1Name, id1Key1.data(), id1Key1.size(), pibImpl);
+  BOOST_CHECK_NO_THROW(KeyImpl(id1Key1Name, pibImpl));
+
+  // key does not have any certificate
+  BOOST_CHECK_EQUAL(key11.getCertificates().size(), 0);
+
+  // get non-existing certificate, throw Pib::Error
+  BOOST_CHECK_THROW(key11.getCertificate(id1Key1Cert1.getName()), Pib::Error);
+  // get default certificate, throw Pib::Error
+  BOOST_CHECK_THROW(key11.getDefaultCertificate(), Pib::Error);
+  // set non-existing certificate as default certificate, throw Pib::Error
+  BOOST_REQUIRE_THROW(key11.setDefaultCertificate(id1Key1Cert1.getName()), Pib::Error);
+
+  // add certificate
+  key11.addCertificate(id1Key1Cert1);
+  BOOST_CHECK_NO_THROW(key11.getCertificate(id1Key1Cert1.getName()));
+
+  // new certificate becomes default certificate when there was no default certificate
+  BOOST_REQUIRE_NO_THROW(key11.getDefaultCertificate());
+  const auto& defaultCert0 = key11.getDefaultCertificate();
+  BOOST_CHECK_EQUAL(defaultCert0.getName(), id1Key1Cert1.getName());
+  BOOST_CHECK_EQUAL(defaultCert0, id1Key1Cert1);
+
+  // remove certificate
+  key11.removeCertificate(id1Key1Cert1.getName());
+  BOOST_CHECK_THROW(key11.getCertificate(id1Key1Cert1.getName()), Pib::Error);
+  BOOST_CHECK_THROW(key11.getDefaultCertificate(), Pib::Error);
+
+  // set default certificate directly
+  BOOST_REQUIRE_NO_THROW(key11.setDefaultCertificate(id1Key1Cert1));
+  BOOST_REQUIRE_NO_THROW(key11.getDefaultCertificate());
+  BOOST_CHECK_NO_THROW(key11.getCertificate(id1Key1Cert1.getName()));
+
+  // check default cert
+  const auto& defaultCert1 = key11.getDefaultCertificate();
+  BOOST_CHECK_EQUAL(defaultCert1.getName(), id1Key1Cert1.getName());
+  BOOST_CHECK_EQUAL(defaultCert1, id1Key1Cert1);
+
+  // add another certificate
+  key11.addCertificate(id1Key1Cert2);
+  BOOST_CHECK_EQUAL(key11.getCertificates().size(), 2);
+
+  // set default certificate through name
+  BOOST_REQUIRE_NO_THROW(key11.setDefaultCertificate(id1Key1Cert2.getName()));
+  BOOST_REQUIRE_NO_THROW(key11.getDefaultCertificate());
+  const auto& defaultCert2 = key11.getDefaultCertificate();
+  BOOST_CHECK_EQUAL(defaultCert2.getName(), id1Key1Cert2.getName());
+  BOOST_CHECK_EQUAL(defaultCert2, id1Key1Cert2);
+
+  // remove certificate
+  key11.removeCertificate(id1Key1Cert1.getName());
+  BOOST_CHECK_THROW(key11.getCertificate(id1Key1Cert1.getName()), Pib::Error);
+  BOOST_CHECK_EQUAL(key11.getCertificates().size(), 1);
+
+  // set default certificate directly again, change the default setting
+  BOOST_REQUIRE_NO_THROW(key11.setDefaultCertificate(id1Key1Cert1));
+  const auto& defaultCert3 = key11.getDefaultCertificate();
+  BOOST_CHECK_EQUAL(defaultCert3.getName(), id1Key1Cert1.getName());
+  BOOST_CHECK_EQUAL(defaultCert3, id1Key1Cert1);
+  BOOST_CHECK_EQUAL(key11.getCertificates().size(), 2);
+
+  // remove all certificates
+  key11.removeCertificate(id1Key1Cert1.getName());
+  BOOST_CHECK_THROW(key11.getCertificate(id1Key1Cert1.getName()), Pib::Error);
+  BOOST_CHECK_EQUAL(key11.getCertificates().size(), 1);
+  key11.removeCertificate(id1Key1Cert2.getName());
+  BOOST_CHECK_THROW(key11.getCertificate(id1Key1Cert2.getName()), Pib::Error);
+  BOOST_CHECK_THROW(key11.getDefaultCertificate(), Pib::Error);
+  BOOST_CHECK_EQUAL(key11.getCertificates().size(), 0);
+}
+
+class OverwriteFixture : public ndn::security::tests::PibDataFixture,
+                         public ndn::tests::IdentityManagementFixture
+{
+};
+
+BOOST_FIXTURE_TEST_CASE(Overwrite, OverwriteFixture)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+
+  BOOST_CHECK_THROW(KeyImpl(id1Key1Name, pibImpl), Pib::Error);
+  KeyImpl(id1Key1Name, id1Key1.data(), id1Key1.size(), pibImpl);
+  KeyImpl key1(id1Key1Name, pibImpl);
+
+  KeyImpl(id1Key1Name, id1Key2.data(), id1Key2.size(), pibImpl); // overwriting of the key should work
+  KeyImpl key2(id1Key1Name, pibImpl);
+
+  BOOST_CHECK(key1.getPublicKey() != key2.getPublicKey()); // key1 cached the original public key
+  BOOST_CHECK(key2.getPublicKey() == id1Key2);
+
+  key1.addCertificate(id1Key1Cert1);
+  BOOST_CHECK_EQUAL(key1.getCertificate(id1Key1Cert1.getName()), id1Key1Cert1);
+
+  auto otherCert = id1Key1Cert1;
+  SignatureInfo info;
+  info.setValidityPeriod(ValidityPeriod(time::system_clock::now(),
+                                        time::system_clock::now() + 1_s));
+  m_keyChain.sign(otherCert, SigningInfo().setSignatureInfo(info));
+
+  BOOST_CHECK_EQUAL(otherCert.getName(), id1Key1Cert1.getName());
+  BOOST_CHECK(otherCert.getContent() == id1Key1Cert1.getContent());
+  BOOST_CHECK_NE(otherCert, id1Key1Cert1);
+
+  key1.addCertificate(otherCert);
+
+  BOOST_CHECK_EQUAL(key1.getCertificate(id1Key1Cert1.getName()), otherCert);
+}
+
+BOOST_AUTO_TEST_CASE(Errors)
+{
+  auto pibImpl = make_shared<pib::PibMemory>();
+
+  BOOST_CHECK_THROW(KeyImpl(id1Key1Name, pibImpl), Pib::Error);
+  KeyImpl key11(id1Key1Name, id1Key1.data(), id1Key1.size(), pibImpl);
+
+  BOOST_CHECK_THROW(KeyImpl(Name("/wrong"), pibImpl), std::invalid_argument);
+  BOOST_CHECK_THROW(KeyImpl(Name("/wrong"), id1Key1.data(), id1Key1.size(), pibImpl), std::invalid_argument);
+  Buffer wrongKey;
+  BOOST_CHECK_THROW(KeyImpl(id1Key2Name, wrongKey.data(), wrongKey.size(), pibImpl), std::invalid_argument);
+
+  key11.addCertificate(id1Key1Cert1);
+  BOOST_CHECK_THROW(key11.addCertificate(id1Key2Cert1), std::invalid_argument);
+  BOOST_CHECK_THROW(key11.removeCertificate(id1Key2Cert1.getName()), std::invalid_argument);
+  BOOST_CHECK_THROW(key11.getCertificate(id1Key2Cert1.getName()), std::invalid_argument);
+  BOOST_CHECK_THROW(key11.setDefaultCertificate(id1Key2Cert1), std::invalid_argument);
+  BOOST_CHECK_THROW(key11.setDefaultCertificate(id1Key2Cert1.getName()), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestKeyImpl
+BOOST_AUTO_TEST_SUITE_END() // Detail
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace detail
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/identity-container.t.cpp b/tests/unit/security/pib/identity-container.t.cpp
new file mode 100644
index 0000000..e821970
--- /dev/null
+++ b/tests/unit/security/pib/identity-container.t.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/identity-container.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_FIXTURE_TEST_SUITE(TestIdentityContainer, PibDataFixture)
+
+using pib::Pib;
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  auto pibImpl = make_shared<PibMemory>();
+
+  // start with an empty container
+  IdentityContainer container(pibImpl);
+  BOOST_CHECK_EQUAL(container.size(), 0);
+  BOOST_CHECK_EQUAL(container.getLoadedIdentities().size(), 0);
+
+  // add the first identity
+  Identity identity11 = container.add(id1);
+  BOOST_CHECK_EQUAL(identity11.getName(), id1);
+  BOOST_CHECK_EQUAL(container.size(), 1);
+  BOOST_CHECK_EQUAL(container.getLoadedIdentities().size(), 1);
+  BOOST_CHECK(container.find(id1) != container.end());
+
+  // add the same identity again
+  Identity identity12 = container.add(id1);
+  BOOST_CHECK_EQUAL(identity12.getName(), id1);
+  BOOST_CHECK_EQUAL(container.size(), 1);
+  BOOST_CHECK_EQUAL(container.getLoadedIdentities().size(), 1);
+  BOOST_CHECK(container.find(id1) != container.end());
+
+  // add the second identity
+  Identity identity21 = container.add(id2);
+  BOOST_CHECK_EQUAL(identity21.getName(), id2);
+  BOOST_CHECK_EQUAL(container.size(), 2);
+  BOOST_CHECK_EQUAL(container.getLoadedIdentities().size(), 2);
+  BOOST_CHECK(container.find(id1) != container.end());
+  BOOST_CHECK(container.find(id2) != container.end());
+
+  // get identities
+  BOOST_REQUIRE_NO_THROW(container.get(id1));
+  BOOST_REQUIRE_NO_THROW(container.get(id2));
+  BOOST_CHECK_THROW(container.get(Name("/non-existing")), Pib::Error);
+
+  // check identity
+  Identity identity1 = container.get(id1);
+  Identity identity2 = container.get(id2);
+  BOOST_CHECK_EQUAL(identity1.getName(), id1);
+  BOOST_CHECK_EQUAL(identity2.getName(), id2);
+
+  // create another container from the same PibImpl
+  // cache should be empty
+  IdentityContainer container2(pibImpl);
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getLoadedIdentities().size(), 0);
+
+  // get key, cache should be filled
+  BOOST_REQUIRE_NO_THROW(container2.get(id1));
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getLoadedIdentities().size(), 1);
+
+  BOOST_REQUIRE_NO_THROW(container2.get(id2));
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getLoadedIdentities().size(), 2);
+
+  // remove a key
+  container2.remove(id1);
+  BOOST_CHECK_EQUAL(container2.size(), 1);
+  BOOST_CHECK_EQUAL(container2.getLoadedIdentities().size(), 1);
+  BOOST_CHECK(container2.find(id1) == container2.end());
+  BOOST_CHECK(container2.find(id2) != container2.end());
+
+  // remove another key
+  container2.remove(id2);
+  BOOST_CHECK_EQUAL(container2.size(), 0);
+  BOOST_CHECK_EQUAL(container2.getLoadedIdentities().size(), 0);
+  BOOST_CHECK(container2.find(id2) == container2.end());
+
+}
+
+BOOST_AUTO_TEST_CASE(Iterator)
+{
+  auto pibImpl = make_shared<PibMemory>();
+  IdentityContainer container(pibImpl);
+  container.add(id1);
+  container.add(id2);
+
+  std::set<Name> idNames;
+  idNames.insert(id1);
+  idNames.insert(id2);
+
+  IdentityContainer::const_iterator it = container.begin();
+  std::set<Name>::const_iterator testIt = idNames.begin();
+  BOOST_CHECK_EQUAL((*it).getName(), *testIt);
+  it++;
+  testIt++;
+  BOOST_CHECK_EQUAL((*it).getName(), *testIt);
+  ++it;
+  testIt++;
+  BOOST_CHECK(it == container.end());
+
+  size_t count = 0;
+  testIt = idNames.begin();
+  for (const auto& identity : container) {
+    BOOST_CHECK_EQUAL(identity.getName(), *testIt);
+    testIt++;
+    count++;
+  }
+  BOOST_CHECK_EQUAL(count, 2);
+
+  BOOST_CHECK(IdentityContainer::const_iterator() == IdentityContainer::const_iterator());
+  BOOST_CHECK(IdentityContainer::const_iterator() == container.end());
+  BOOST_CHECK(container.end() == IdentityContainer::const_iterator());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIdentityContainer
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/identity.t.cpp b/tests/unit/security/pib/identity.t.cpp
new file mode 100644
index 0000000..d10c7bf
--- /dev/null
+++ b/tests/unit/security/pib/identity.t.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/identity.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+#include "security/pib/detail/identity-impl.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_FIXTURE_TEST_SUITE(TestIdentity, PibDataFixture)
+
+using pib::Pib;
+
+BOOST_AUTO_TEST_CASE(ValidityChecking)
+{
+  using security::pib::detail::IdentityImpl;
+
+  Identity id;
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(id), false);
+  BOOST_CHECK_EQUAL(!id, true);
+
+  if (id)
+    BOOST_CHECK(false);
+  else
+    BOOST_CHECK(true);
+
+  auto identityImpl = make_shared<IdentityImpl>(id1, make_shared<PibMemory>(), true);
+  id = Identity(identityImpl);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(id), true);
+  BOOST_CHECK_EQUAL(!id, false);
+
+  if (id)
+    BOOST_CHECK(true);
+  else
+    BOOST_CHECK(false);
+}
+
+/**
+ * pib::Identity is a wrapper of pib::detail::IdentityImpl.  Since the functionalities of
+ * IdentityImpl have already been tested in detail/identity-impl.t.cpp, we only test the shared
+ * property of pib::Identity in this test case.
+ */
+BOOST_AUTO_TEST_CASE(Share)
+{
+  using security::pib::detail::IdentityImpl;
+
+  auto identityImpl = make_shared<IdentityImpl>(id1, make_shared<pib::PibMemory>(), true);
+  Identity identity1(identityImpl);
+  Identity identity2(identityImpl);
+  BOOST_CHECK_EQUAL(identity1, identity2);
+  BOOST_CHECK_NE(identity1, Identity());
+  BOOST_CHECK_EQUAL(Identity(), Identity());
+
+  identity1.addKey(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK_NO_THROW(identity2.getKey(id1Key1Name));
+  identity2.removeKey(id1Key1Name);
+  BOOST_CHECK_THROW(identity1.getKey(id1Key1Name), Pib::Error);
+
+  identity1.setDefaultKey(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK_NO_THROW(identity2.getDefaultKey());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIdentity
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/key-container.t.cpp b/tests/unit/security/pib/key-container.t.cpp
new file mode 100644
index 0000000..2afd3e2
--- /dev/null
+++ b/tests/unit/security/pib/key-container.t.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/key-container.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_FIXTURE_TEST_SUITE(TestKeyContainer, PibDataFixture)
+
+using pib::Pib;
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  auto pibImpl = make_shared<PibMemory>();
+
+  // start with an empty container
+  KeyContainer container(id1, pibImpl);
+  BOOST_CHECK_EQUAL(container.size(), 0);
+  BOOST_CHECK_EQUAL(container.getLoadedKeys().size(), 0);
+
+  // add the first key
+  Key key11 = container.add(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK_EQUAL(key11.getName(), id1Key1Name);
+  BOOST_CHECK(key11.getPublicKey() == id1Key1);
+  BOOST_CHECK_EQUAL(container.size(), 1);
+  BOOST_CHECK_EQUAL(container.getLoadedKeys().size(), 1);
+  BOOST_CHECK(container.find(id1Key1Name) != container.end());
+
+  // add the same key again
+  Key key12 = container.add(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  BOOST_CHECK_EQUAL(key12.getName(), id1Key1Name);
+  BOOST_CHECK(key12.getPublicKey() == id1Key1);
+  BOOST_CHECK_EQUAL(container.size(), 1);
+  BOOST_CHECK_EQUAL(container.getLoadedKeys().size(), 1);
+  BOOST_CHECK(container.find(id1Key1Name) != container.end());
+
+  // add the second key
+  Key key21 = container.add(id1Key2.data(), id1Key2.size(), id1Key2Name);
+  BOOST_CHECK_EQUAL(key21.getName(), id1Key2Name);
+  BOOST_CHECK(key21.getPublicKey() == id1Key2);
+  BOOST_CHECK_EQUAL(container.size(), 2);
+  BOOST_CHECK_EQUAL(container.getLoadedKeys().size(), 2);
+  BOOST_CHECK(container.find(id1Key1Name) != container.end());
+  BOOST_CHECK(container.find(id1Key2Name) != container.end());
+
+  // get keys
+  BOOST_REQUIRE_NO_THROW(container.get(id1Key1Name));
+  BOOST_REQUIRE_NO_THROW(container.get(id1Key2Name));
+  Name id1Key3Name = v2::constructKeyName(id1, name::Component("non-existing-id"));
+  BOOST_CHECK_THROW(container.get(id1Key3Name), Pib::Error);
+
+  // check key
+  Key key1 = container.get(id1Key1Name);
+  Key key2 = container.get(id1Key2Name);
+  BOOST_CHECK_EQUAL(key1.getName(), id1Key1Name);
+  BOOST_CHECK(key1.getPublicKey() == id1Key1);
+  BOOST_CHECK_EQUAL(key2.getName(), id1Key2Name);
+  BOOST_CHECK(key2.getPublicKey() == id1Key2);
+
+  // create another container from the same PibImpl
+  // cache should be empty
+  KeyContainer container2(id1, pibImpl);
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getLoadedKeys().size(), 0);
+
+  // get key, cache should be filled
+  BOOST_REQUIRE_NO_THROW(container2.get(id1Key1Name));
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getLoadedKeys().size(), 1);
+
+  BOOST_REQUIRE_NO_THROW(container2.get(id1Key2Name));
+  BOOST_CHECK_EQUAL(container2.size(), 2);
+  BOOST_CHECK_EQUAL(container2.getLoadedKeys().size(), 2);
+
+  // remove a key
+  container2.remove(id1Key1Name);
+  BOOST_CHECK_EQUAL(container2.size(), 1);
+  BOOST_CHECK_EQUAL(container2.getLoadedKeys().size(), 1);
+  BOOST_CHECK(container2.find(id1Key1Name) == container2.end());
+  BOOST_CHECK(container2.find(id1Key2Name) != container2.end());
+
+  // remove another key
+  container2.remove(id1Key2Name);
+  BOOST_CHECK_EQUAL(container2.size(), 0);
+  BOOST_CHECK_EQUAL(container2.getLoadedKeys().size(), 0);
+  BOOST_CHECK(container2.find(id1Key2Name) == container2.end());
+}
+
+BOOST_AUTO_TEST_CASE(Errors)
+{
+  auto pibImpl = make_shared<PibMemory>();
+
+  KeyContainer container(id1, pibImpl);
+
+  BOOST_CHECK_THROW(container.add(id2Key1.data(), id2Key1.size(), id2Key1Name), std::invalid_argument);
+  BOOST_CHECK_THROW(container.remove(id2Key1Name), std::invalid_argument);
+  BOOST_CHECK_THROW(container.get(id2Key1Name), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(Iterator)
+{
+  auto pibImpl = make_shared<PibMemory>();
+  KeyContainer container(id1, pibImpl);
+
+  container.add(id1Key1.data(), id1Key1.size(), id1Key1Name);
+  container.add(id1Key2.data(), id1Key2.size(), id1Key2Name);
+
+  std::set<Name> keyNames;
+  keyNames.insert(id1Key1Name);
+  keyNames.insert(id1Key2Name);
+
+  KeyContainer::const_iterator it = container.begin();
+  std::set<Name>::const_iterator testIt = keyNames.begin();
+  BOOST_CHECK_EQUAL((*it).getName(), *testIt);
+  it++;
+  testIt++;
+  BOOST_CHECK_EQUAL((*it).getName(), *testIt);
+  ++it;
+  testIt++;
+  BOOST_CHECK(it == container.end());
+
+  size_t count = 0;
+  testIt = keyNames.begin();
+  for (const auto& key : container) {
+    BOOST_CHECK_EQUAL(key.getIdentity(), id1);
+    BOOST_CHECK_EQUAL(key.getName(), *testIt);
+    testIt++;
+    count++;
+  }
+  BOOST_CHECK_EQUAL(count, 2);
+
+  BOOST_CHECK(KeyContainer::const_iterator() == KeyContainer::const_iterator());
+  BOOST_CHECK(KeyContainer::const_iterator() == container.end());
+  BOOST_CHECK(container.end() == KeyContainer::const_iterator());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestKeyContainer
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/key.t.cpp b/tests/unit/security/pib/key.t.cpp
new file mode 100644
index 0000000..3c5d207
--- /dev/null
+++ b/tests/unit/security/pib/key.t.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/key.hpp"
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+#include "security/pib/detail/key-impl.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_FIXTURE_TEST_SUITE(TestKey, PibDataFixture)
+
+using pib::Pib;
+
+BOOST_AUTO_TEST_CASE(ValidityChecking)
+{
+  using security::pib::detail::KeyImpl;
+
+  Key key;
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(key), false);
+  BOOST_CHECK_EQUAL(!key, true);
+
+  if (key)
+    BOOST_CHECK(false);
+  else
+    BOOST_CHECK(true);
+
+  auto keyImpl = make_shared<KeyImpl>(id1Key1Name, id1Key1.data(), id1Key1.size(),
+                                      make_shared<pib::PibMemory>());
+  key = Key(keyImpl);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(key), true);
+  BOOST_CHECK_EQUAL(!key, false);
+
+  if (key)
+    BOOST_CHECK(true);
+  else
+    BOOST_CHECK(false);
+}
+
+/**
+ * pib::Key is a wrapper of pib::detail::KeyImpl.  Since the functionalities of KeyImpl
+ * have already been tested in detail/key-impl.t.cpp, we only test the shared property
+ * of pib::Key in this test case.
+ */
+
+BOOST_AUTO_TEST_CASE(Share)
+{
+  using security::pib::detail::KeyImpl;
+
+  auto keyImpl = make_shared<KeyImpl>(id1Key1Name, id1Key1.data(), id1Key1.size(),
+                                      make_shared<pib::PibMemory>());
+  Key key1(keyImpl);
+  Key key2(keyImpl);
+  BOOST_CHECK_EQUAL(key1, key2);
+  BOOST_CHECK_NE(key1, Key());
+  BOOST_CHECK_EQUAL(Key(), Key());
+
+  key1.addCertificate(id1Key1Cert1);
+  BOOST_CHECK_NO_THROW(key2.getCertificate(id1Key1Cert1.getName()));
+  key2.removeCertificate(id1Key1Cert1.getName());
+  BOOST_CHECK_THROW(key1.getCertificate(id1Key1Cert1.getName()), Pib::Error);
+
+  key1.setDefaultCertificate(id1Key1Cert1);
+  BOOST_CHECK_NO_THROW(key2.getDefaultCertificate());
+}
+
+BOOST_AUTO_TEST_CASE(Helpers)
+{
+  BOOST_CHECK_EQUAL(v2::constructKeyName("/hello", name::Component("world")), "/hello/KEY/world");
+
+  BOOST_CHECK_EQUAL(v2::isValidKeyName("/hello"), false);
+  BOOST_CHECK_EQUAL(v2::isValidKeyName("/hello/KEY"), false);
+  BOOST_CHECK_EQUAL(v2::isValidKeyName("/hello/KEY/world"), true);
+
+  BOOST_CHECK_EQUAL(v2::isValidKeyName("/KEY/hello"), true);
+  BOOST_CHECK_EQUAL(v2::isValidKeyName("/hello/world/KEY/!"), true);
+
+  BOOST_CHECK_EQUAL(v2::extractIdentityFromKeyName("/KEY/hello"), "/");
+  BOOST_CHECK_EQUAL(v2::extractIdentityFromKeyName("/hello/KEY/world"), "/hello");
+  BOOST_CHECK_EQUAL(v2::extractIdentityFromKeyName("/hello/world/KEY/!"), "/hello/world");
+
+  BOOST_CHECK_THROW(v2::extractIdentityFromKeyName("/hello"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestKey
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/pib-data-fixture.cpp b/tests/unit/security/pib/pib-data-fixture.cpp
new file mode 100644
index 0000000..91e5b34
--- /dev/null
+++ b/tests/unit/security/pib/pib-data-fixture.cpp
@@ -0,0 +1,429 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "pib-data-fixture.hpp"
+#include "../../identity-management-time-fixture.hpp"
+
+// #include "security/pib/pib-memory.hpp"
+// #include "security/tpm/tpm.hpp"
+// #include "security/tpm/back-end-mem.hpp"
+// #include <fstream>
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+// class TestCertDataGenerator
+// {
+// public:
+//   TestCertDataGenerator()
+//     : tpm("test", "test", make_unique<tpm::BackEndMem>())
+//   {
+//   }
+
+//   void
+//   printTestDataForId(const std::string& prefix, const Name& id)
+//   {
+//     for (int keyId : {1, 2}) {
+//       Name keyName = tpm.createKey(id, EcKeyParams(name::Component::fromNumber(keyId)));
+
+//       for (int certVersion : {1, 2}) {
+//         Name certName = keyName;
+//         certName
+//           .append("issuer")
+//           .appendVersion(certVersion);
+//         v2::Certificate cert;
+//         cert.setName(certName);
+//         cert.setFreshnessPeriod(1_h);
+//         cert.setContent(tpm.getPublicKey(keyName));
+
+//         // @TODO sign using the new KeyChain
+//         SignatureInfo info;
+//         info.setSignatureType(tlv::SignatureSha256WithEcdsa);
+//         info.setKeyLocator(KeyLocator(keyName));
+//         info.setValidityPeriod(ValidityPeriod(time::fromIsoString("20170102T000000"),
+//                                               time::fromIsoString("20180102T000000")));
+//         cert.setSignature(Signature(info, Block()));
+
+//         EncodingBuffer buf;
+//         cert.wireEncode(buf, true);
+
+//         cert.setSignatureValue(Block(tlv::SignatureValue,
+//                                      tpm.sign(buf.buf(), buf.size(), keyName, DigestAlgorithm::SHA256)));
+
+//         printBytes(prefix + "_KEY" + to_string(keyId) + "_CERT" + to_string(certVersion),
+//                    cert.wireEncode());
+//       }
+//     }
+//   }
+
+//   static void
+//   printBytes(const std::string& name, const Block& block)
+//   {
+//     printBytes(name, block.wire(), block.size());
+//   }
+
+//   static void
+//   printBytes(const std::string& name, const Buffer& buffer)
+//   {
+//     printBytes(name, buffer.buf(), buffer.size());
+//   }
+
+//   static void
+//   printBytes(const std::string& name, const uint8_t* buf, size_t size)
+//   {
+//     std::cout << "\nconst uint8_t " << name << "[] = {\n"
+//               << "  ";
+
+//     std::string hex = toHex(buf, size);
+
+//     for (size_t i = 0; i < hex.size(); i++) {
+//       if (i > 0 && i % 40 == 0)
+//         std::cout << "\n  ";
+
+//       std::cout << "0x" << hex[i];
+//       std::cout << hex[++i];
+
+//       if ((i + 1) != hex.size())
+//         std::cout << ", ";
+//     }
+//     std::cout << "\n"
+//               << "};" << std::endl;
+//   }
+
+// public:
+//   pib::PibMemory pib;
+//   Tpm tpm;
+// };
+
+// // The test data can be generated using this test case
+// BOOST_FIXTURE_TEST_CASE(GenerateTestCertData, TestCertDataGenerator)
+// {
+//   printTestDataForId("ID1", Name("/pib/interface/id/1"));
+//   printTestDataForId("ID2", Name("/pib/interface/id/2"));
+// }
+
+const uint8_t ID1_KEY1_CERT1[] = {
+  0x06, 0xFD, 0x02, 0x25, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x01, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0xCB, 0x46, 0xF7, 0x16, 0x2E,
+  0x83, 0x3D, 0x5E, 0x4A, 0x80, 0x6A, 0x78, 0xB7, 0xA8, 0x7A, 0x15, 0x95, 0x2D, 0x23, 0xA8, 0x41, 0xF7, 0x62, 0xE4, 0x0E,
+  0x66, 0x36, 0xB3, 0xF3, 0x14, 0xD6, 0xB3, 0xAB, 0x19, 0x26, 0x9D, 0x5A, 0x8A, 0x51, 0xD4, 0x4E, 0xBA, 0xBE, 0x13, 0x96,
+  0xCA, 0x38, 0x52, 0x16, 0xE4, 0x3D, 0xB0, 0x88, 0xBA, 0xBB, 0x7B, 0x97, 0x00, 0xA5, 0x95, 0x97, 0x4E, 0xE8, 0xF6, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x46, 0x30, 0x44, 0x02, 0x20, 0x53, 0xC8, 0xAD, 0x88, 0xBA, 0x52, 0x29, 0x68, 0xFF, 0x74, 0xA8, 0x39, 0x7F,
+  0x2C, 0xE2, 0x8E, 0x04, 0xC1, 0x78, 0x36, 0x46, 0x89, 0x38, 0x58, 0x45, 0x22, 0x44, 0xA3, 0xC8, 0xC1, 0xFF, 0x72, 0x02,
+  0x20, 0x23, 0x9D, 0xE4, 0x92, 0x00, 0xF1, 0x43, 0x69, 0xF7, 0x32, 0xF6, 0xAA, 0x8C, 0xFD, 0x7F, 0x2B, 0xFB, 0xD2, 0x40,
+  0x6A, 0x1E, 0xA3, 0xE5, 0xF0, 0xF8, 0x2B, 0x92, 0x99, 0x6B, 0xDB, 0xE2, 0x6D
+};
+
+const uint8_t ID1_KEY1_CERT2[] = {
+  0x06, 0xFD, 0x02, 0x26, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x02, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0xCB, 0x46, 0xF7, 0x16, 0x2E,
+  0x83, 0x3D, 0x5E, 0x4A, 0x80, 0x6A, 0x78, 0xB7, 0xA8, 0x7A, 0x15, 0x95, 0x2D, 0x23, 0xA8, 0x41, 0xF7, 0x62, 0xE4, 0x0E,
+  0x66, 0x36, 0xB3, 0xF3, 0x14, 0xD6, 0xB3, 0xAB, 0x19, 0x26, 0x9D, 0x5A, 0x8A, 0x51, 0xD4, 0x4E, 0xBA, 0xBE, 0x13, 0x96,
+  0xCA, 0x38, 0x52, 0x16, 0xE4, 0x3D, 0xB0, 0x88, 0xBA, 0xBB, 0x7B, 0x97, 0x00, 0xA5, 0x95, 0x97, 0x4E, 0xE8, 0xF6, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xB2, 0x93, 0xCD, 0x3D, 0x01, 0x00, 0xB5, 0xF1, 0x75, 0x22, 0x68, 0x9F,
+  0xE4, 0x5E, 0x0A, 0x76, 0x34, 0xBC, 0x9D, 0xCF, 0x9A, 0x4C, 0x21, 0x3F, 0xA5, 0x12, 0x51, 0xF7, 0x3A, 0x5E, 0x37, 0x7D,
+  0x02, 0x20, 0x33, 0xA9, 0xA9, 0x8F, 0xD8, 0x2E, 0xED, 0x3C, 0xE5, 0x18, 0x94, 0x59, 0x28, 0xEA, 0x82, 0x38, 0x5B, 0x20,
+  0xE4, 0xBF, 0x15, 0xF4, 0x0D, 0x45, 0xAE, 0x8B, 0x63, 0x19, 0x79, 0x78, 0x50, 0x3A
+};
+
+const uint8_t ID1_KEY2_CERT1[] = {
+  0x06, 0xFD, 0x02, 0x25, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x01, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0x34, 0xAA, 0x4B, 0x1A, 0x97,
+  0x4A, 0x6B, 0x6F, 0x3F, 0xB3, 0xC9, 0xD1, 0x39, 0x9F, 0x1E, 0x49, 0xB6, 0x6E, 0x19, 0x97, 0x13, 0x5E, 0xFA, 0xE6, 0xD3,
+  0xFE, 0xF3, 0xB0, 0xCA, 0x80, 0x09, 0x31, 0xCA, 0x50, 0x5C, 0xE6, 0x57, 0xBF, 0x13, 0x16, 0xCE, 0x3E, 0xF1, 0xD4, 0x23,
+  0xF8, 0x7F, 0x31, 0xFA, 0x13, 0x39, 0x09, 0xED, 0xC6, 0x74, 0x3D, 0xFD, 0x1A, 0x0B, 0xC7, 0xC1, 0x01, 0x15, 0x7F, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x46, 0x30, 0x44, 0x02, 0x20, 0x71, 0x3A, 0xB4, 0x19, 0x4B, 0xB3, 0x25, 0xA5, 0x03, 0x23, 0x8C, 0xC1, 0xB9,
+  0x68, 0xC1, 0x41, 0x4B, 0xED, 0x13, 0xCC, 0x87, 0x16, 0xB5, 0x13, 0x87, 0xA0, 0x54, 0xA2, 0x9F, 0xF0, 0xD7, 0x72, 0x02,
+  0x20, 0x4B, 0xEF, 0xB5, 0x6A, 0x8C, 0x40, 0x71, 0x17, 0xD2, 0x4F, 0xB6, 0x0F, 0xBE, 0x60, 0x1A, 0x46, 0x9B, 0x78, 0x15,
+  0x46, 0x09, 0xC2, 0x7A, 0x80, 0xD4, 0xE6, 0x71, 0x52, 0xD6, 0x83, 0x4B, 0x04
+};
+
+const uint8_t ID1_KEY2_CERT2[] = {
+  0x06, 0xFD, 0x02, 0x26, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x02, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0x34, 0xAA, 0x4B, 0x1A, 0x97,
+  0x4A, 0x6B, 0x6F, 0x3F, 0xB3, 0xC9, 0xD1, 0x39, 0x9F, 0x1E, 0x49, 0xB6, 0x6E, 0x19, 0x97, 0x13, 0x5E, 0xFA, 0xE6, 0xD3,
+  0xFE, 0xF3, 0xB0, 0xCA, 0x80, 0x09, 0x31, 0xCA, 0x50, 0x5C, 0xE6, 0x57, 0xBF, 0x13, 0x16, 0xCE, 0x3E, 0xF1, 0xD4, 0x23,
+  0xF8, 0x7F, 0x31, 0xFA, 0x13, 0x39, 0x09, 0xED, 0xC6, 0x74, 0x3D, 0xFD, 0x1A, 0x0B, 0xC7, 0xC1, 0x01, 0x15, 0x7F, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x31, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xD2, 0x90, 0x8C, 0xA3, 0x52, 0x2F, 0x79, 0xB3, 0xD7, 0x39, 0xE1, 0x6C,
+  0x7F, 0xA0, 0xDF, 0xD1, 0x3E, 0x0F, 0x70, 0xBE, 0xF5, 0xDB, 0x08, 0xDF, 0xE1, 0x0B, 0xDF, 0x79, 0x99, 0xFE, 0x5C, 0xDC,
+  0x02, 0x20, 0x3D, 0xD4, 0x7C, 0xD1, 0x83, 0xBE, 0x29, 0xBB, 0x73, 0xA3, 0x82, 0xE5, 0xE6, 0x83, 0xA1, 0xC1, 0xBC, 0xF6,
+  0x84, 0x42, 0x85, 0x00, 0x92, 0x4B, 0xA2, 0xA8, 0xCA, 0x10, 0x7B, 0x92, 0x89, 0xB0
+};
+
+const uint8_t ID2_KEY1_CERT1[] = {
+  0x06, 0xFD, 0x02, 0x25, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x01, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0xAC, 0x62, 0xD7, 0x31, 0x3B,
+  0x19, 0x1F, 0x44, 0x76, 0x6E, 0x79, 0x03, 0xB9, 0xC8, 0x26, 0xC4, 0x1E, 0x38, 0x3A, 0x41, 0xFE, 0xB4, 0x72, 0xA2, 0x36,
+  0xBB, 0x82, 0x9C, 0xB9, 0x07, 0x62, 0x6F, 0x1C, 0x79, 0x12, 0xCA, 0x9C, 0x3D, 0xAA, 0x7A, 0x96, 0xFF, 0xAF, 0x5B, 0x6F,
+  0xE1, 0x72, 0x60, 0xB0, 0x7F, 0x44, 0x38, 0x05, 0x21, 0xCC, 0x49, 0x78, 0x89, 0xC1, 0xEF, 0xEE, 0x81, 0x8E, 0xF5, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x46, 0x30, 0x44, 0x02, 0x20, 0x16, 0xC0, 0xF4, 0xE3, 0x15, 0x43, 0x6E, 0x27, 0x33, 0x7C, 0x46, 0x4D, 0x35,
+  0xA7, 0x8B, 0x0C, 0xE3, 0x27, 0x63, 0x4B, 0xB2, 0xB6, 0x4F, 0x06, 0x90, 0x2A, 0xD8, 0x54, 0x92, 0xE8, 0xBA, 0xBE, 0x02,
+  0x20, 0x67, 0xA6, 0x55, 0x8D, 0x16, 0x0E, 0x1E, 0x9E, 0x10, 0x0E, 0xB9, 0x3C, 0xEF, 0xEE, 0xB5, 0xF7, 0x9C, 0xB3, 0x1D,
+  0x04, 0xF1, 0xD4, 0xB5, 0x9F, 0xD4, 0x13, 0xBB, 0xFF, 0xA7, 0x58, 0xAE, 0xCB
+};
+
+const uint8_t ID2_KEY1_CERT2[] = {
+  0x06, 0xFD, 0x02, 0x26, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x02, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0xAC, 0x62, 0xD7, 0x31, 0x3B,
+  0x19, 0x1F, 0x44, 0x76, 0x6E, 0x79, 0x03, 0xB9, 0xC8, 0x26, 0xC4, 0x1E, 0x38, 0x3A, 0x41, 0xFE, 0xB4, 0x72, 0xA2, 0x36,
+  0xBB, 0x82, 0x9C, 0xB9, 0x07, 0x62, 0x6F, 0x1C, 0x79, 0x12, 0xCA, 0x9C, 0x3D, 0xAA, 0x7A, 0x96, 0xFF, 0xAF, 0x5B, 0x6F,
+  0xE1, 0x72, 0x60, 0xB0, 0x7F, 0x44, 0x38, 0x05, 0x21, 0xCC, 0x49, 0x78, 0x89, 0xC1, 0xEF, 0xEE, 0x81, 0x8E, 0xF5, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x01, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xF2, 0x0D, 0x1D, 0x60, 0x0B, 0x2D, 0x97, 0x3A, 0x6B, 0xEE, 0xEC, 0x56,
+  0xD1, 0x64, 0xBF, 0xED, 0x68, 0xB7, 0x10, 0x0B, 0xDF, 0x81, 0x29, 0xCD, 0xB0, 0xBB, 0x87, 0x0D, 0xDA, 0x12, 0x52, 0xCC,
+  0x02, 0x20, 0x64, 0x33, 0x4E, 0x91, 0xAF, 0x81, 0xF4, 0xE7, 0xAD, 0x38, 0x8E, 0xBF, 0x79, 0xA7, 0x70, 0x1E, 0xD6, 0x71,
+  0x7E, 0xF5, 0xEB, 0x92, 0x56, 0x5F, 0xC7, 0x05, 0xDC, 0x27, 0xE5, 0x11, 0xC2, 0x43
+};
+
+const uint8_t ID2_KEY2_CERT1[] = {
+  0x06, 0xFD, 0x02, 0x26, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x01, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0x2C, 0xC3, 0xAF, 0xA8, 0x73,
+  0xF2, 0x61, 0xCF, 0x48, 0x04, 0x0F, 0x9D, 0xD3, 0xAF, 0x0B, 0xC6, 0x2F, 0x4D, 0xDA, 0x0E, 0x4C, 0x66, 0x1D, 0x03, 0x9D,
+  0xFE, 0x2C, 0x0B, 0xB6, 0x25, 0x60, 0xBC, 0xFA, 0xDA, 0xFE, 0x6F, 0x43, 0xFA, 0x95, 0x45, 0x57, 0x8A, 0x25, 0xEC, 0x0F,
+  0xF2, 0xB7, 0x43, 0x85, 0x0D, 0x0B, 0x8D, 0x97, 0x40, 0x83, 0x4C, 0x28, 0x1B, 0xD4, 0x2E, 0x99, 0x2C, 0x73, 0x7D, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x47, 0x30, 0x45, 0x02, 0x20, 0x56, 0x34, 0x49, 0xAC, 0x72, 0x4E, 0x58, 0x24, 0x6F, 0x14, 0xEE, 0xD3, 0x01,
+  0x5B, 0xD4, 0x0A, 0x26, 0x2B, 0x6A, 0xD1, 0xB3, 0x33, 0x69, 0x4D, 0x64, 0x0C, 0xAA, 0xAE, 0x63, 0x59, 0x6A, 0xFD, 0x02,
+  0x21, 0x00, 0xFD, 0xB9, 0x9E, 0x37, 0x70, 0x9C, 0xE2, 0x7A, 0x0A, 0xFD, 0x64, 0x99, 0x1B, 0xA3, 0x78, 0x83, 0x09, 0xC6,
+  0xA0, 0x6D, 0x7A, 0x55, 0x8F, 0x6C, 0x35, 0xAB, 0x63, 0x78, 0x9D, 0xF3, 0xDC, 0xBC
+};
+
+const uint8_t ID2_KEY2_CERT2[] = {
+  0x06, 0xFD, 0x02, 0x27, 0x07, 0x2B, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x66, 0x61,
+  0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0x08, 0x06, 0x69,
+  0x73, 0x73, 0x75, 0x65, 0x72, 0x08, 0x02, 0xFD, 0x02, 0x14, 0x09, 0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80,
+  0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B, 0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
+  0x01, 0x30, 0x81, 0xF7, 0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF,
+  0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+  0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B, 0xCE, 0x3C, 0x3E, 0x27,
+  0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7, 0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D,
+  0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41, 0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6,
+  0xE5, 0x63, 0xA4, 0x40, 0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+  0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E, 0x16, 0x2B, 0xCE, 0x33,
+  0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51, 0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+  0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84,
+  0xF3, 0xB9, 0xCA, 0xC2, 0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0x2C, 0xC3, 0xAF, 0xA8, 0x73,
+  0xF2, 0x61, 0xCF, 0x48, 0x04, 0x0F, 0x9D, 0xD3, 0xAF, 0x0B, 0xC6, 0x2F, 0x4D, 0xDA, 0x0E, 0x4C, 0x66, 0x1D, 0x03, 0x9D,
+  0xFE, 0x2C, 0x0B, 0xB6, 0x25, 0x60, 0xBC, 0xFA, 0xDA, 0xFE, 0x6F, 0x43, 0xFA, 0x95, 0x45, 0x57, 0x8A, 0x25, 0xEC, 0x0F,
+  0xF2, 0xB7, 0x43, 0x85, 0x0D, 0x0B, 0x8D, 0x97, 0x40, 0x83, 0x4C, 0x28, 0x1B, 0xD4, 0x2E, 0x99, 0x2C, 0x73, 0x7D, 0x16,
+  0x50, 0x1B, 0x01, 0x03, 0x1C, 0x21, 0x07, 0x1F, 0x08, 0x03, 0x70, 0x69, 0x62, 0x08, 0x09, 0x69, 0x6E, 0x74, 0x65, 0x72,
+  0x66, 0x61, 0x63, 0x65, 0x08, 0x02, 0x69, 0x64, 0x08, 0x01, 0x32, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x01, 0x02, 0xFD,
+  0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x37, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x38, 0x30, 0x31, 0x30, 0x32, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x17, 0x48, 0x30, 0x46, 0x02, 0x21, 0x00, 0xB3, 0xF5, 0x96, 0x19, 0xE7, 0xF9, 0x6B, 0xCF, 0x14, 0x64, 0xB1, 0x08,
+  0xFA, 0xFF, 0xB3, 0x52, 0x8B, 0x41, 0xCB, 0xE7, 0xE0, 0x3D, 0x14, 0x7B, 0xC2, 0xD0, 0xC8, 0x89, 0x88, 0xFA, 0x95, 0x73,
+  0x02, 0x21, 0x00, 0xEC, 0x8E, 0x0C, 0x8B, 0x8C, 0x18, 0x8D, 0x00, 0x7C, 0x12, 0x68, 0x57, 0x87, 0xB1, 0x99, 0x69, 0xDA,
+  0x46, 0xEF, 0x14, 0x2D, 0x04, 0x18, 0xBE, 0x1D, 0xAE, 0x79, 0x49, 0xFD, 0x22, 0x8E, 0xBB
+};
+
+PibDataFixture::PibDataFixture()
+  : id1Key1Cert1(Block(ID1_KEY1_CERT1, sizeof(ID1_KEY1_CERT1)))
+  , id1Key1Cert2(Block(ID1_KEY1_CERT2, sizeof(ID1_KEY1_CERT2)))
+  , id1Key2Cert1(Block(ID1_KEY2_CERT1, sizeof(ID1_KEY2_CERT1)))
+  , id1Key2Cert2(Block(ID1_KEY2_CERT2, sizeof(ID1_KEY2_CERT2)))
+
+  , id2Key1Cert1(Block(ID2_KEY1_CERT1, sizeof(ID2_KEY1_CERT1)))
+  , id2Key1Cert2(Block(ID2_KEY1_CERT2, sizeof(ID2_KEY1_CERT2)))
+  , id2Key2Cert1(Block(ID2_KEY2_CERT1, sizeof(ID2_KEY2_CERT1)))
+  , id2Key2Cert2(Block(ID2_KEY2_CERT2, sizeof(ID2_KEY2_CERT2)))
+
+  , id1(id1Key1Cert1.getIdentity())
+  , id2(id2Key1Cert1.getIdentity())
+
+  , id1Key1Name(id1Key1Cert1.getKeyName())
+  , id1Key2Name(id1Key2Cert1.getKeyName())
+
+  , id2Key1Name(id2Key1Cert1.getKeyName())
+  , id2Key2Name(id2Key2Cert1.getKeyName())
+
+  , id1Key1(id1Key1Cert1.getPublicKey())
+  , id1Key2(id1Key2Cert1.getPublicKey())
+  , id2Key1(id2Key1Cert1.getPublicKey())
+  , id2Key2(id2Key2Cert1.getPublicKey())
+{
+  BOOST_ASSERT(id1Key1Cert1.getPublicKey() == id1Key1Cert2.getPublicKey());
+  BOOST_ASSERT(id1Key2Cert1.getPublicKey() == id1Key2Cert2.getPublicKey());
+  BOOST_ASSERT(id2Key1Cert1.getPublicKey() == id2Key1Cert2.getPublicKey());
+  BOOST_ASSERT(id2Key2Cert1.getPublicKey() == id2Key2Cert2.getPublicKey());
+
+  BOOST_ASSERT(id1Key1Cert1.getPublicKey() == id1Key1);
+  BOOST_ASSERT(id1Key1Cert2.getPublicKey() == id1Key1);
+  BOOST_ASSERT(id1Key2Cert1.getPublicKey() == id1Key2);
+  BOOST_ASSERT(id1Key2Cert2.getPublicKey() == id1Key2);
+
+  BOOST_ASSERT(id2Key1Cert1.getPublicKey() == id2Key1);
+  BOOST_ASSERT(id2Key1Cert2.getPublicKey() == id2Key1);
+  BOOST_ASSERT(id2Key2Cert1.getPublicKey() == id2Key2);
+  BOOST_ASSERT(id2Key2Cert2.getPublicKey() == id2Key2);
+
+  BOOST_ASSERT(id1Key1Cert2.getIdentity() == id1);
+  BOOST_ASSERT(id1Key2Cert1.getIdentity() == id1);
+  BOOST_ASSERT(id1Key2Cert2.getIdentity() == id1);
+
+  BOOST_ASSERT(id2Key1Cert2.getIdentity() == id2);
+  BOOST_ASSERT(id2Key2Cert1.getIdentity() == id2);
+  BOOST_ASSERT(id2Key2Cert2.getIdentity() == id2);
+
+  BOOST_ASSERT(id1Key1Cert2.getKeyName() == id1Key1Name);
+  BOOST_ASSERT(id1Key2Cert2.getKeyName() == id1Key2Name);
+
+  BOOST_ASSERT(id2Key1Cert2.getKeyName() == id2Key1Name);
+  BOOST_ASSERT(id2Key2Cert2.getKeyName() == id2Key2Name);
+}
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/pib-data-fixture.hpp b/tests/unit/security/pib/pib-data-fixture.hpp
new file mode 100644
index 0000000..2328702
--- /dev/null
+++ b/tests/unit/security/pib/pib-data-fixture.hpp
@@ -0,0 +1,66 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_SECURITY_PIB_DATA_FIXTURE_HPP
+#define NDN_TESTS_SECURITY_PIB_DATA_FIXTURE_HPP
+
+#include "security/v2/certificate.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+class PibDataFixture
+{
+public:
+  PibDataFixture();
+
+public:
+  v2::Certificate id1Key1Cert1;
+  v2::Certificate id1Key1Cert2;
+  v2::Certificate id1Key2Cert1;
+  v2::Certificate id1Key2Cert2;
+  v2::Certificate id2Key1Cert1;
+  v2::Certificate id2Key1Cert2;
+  v2::Certificate id2Key2Cert1;
+  v2::Certificate id2Key2Cert2;
+
+  Name id1;
+  Name id2;
+
+  Name id1Key1Name;
+  Name id1Key2Name;
+  Name id2Key1Name;
+  Name id2Key2Name;
+
+  Buffer id1Key1;
+  Buffer id1Key2;
+  Buffer id2Key1;
+  Buffer id2Key2;
+};
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_TESTS_SECURITY_PIB_DATA_FIXTURE_HPP
diff --git a/tests/unit/security/pib/pib-impl.t.cpp b/tests/unit/security/pib/pib-impl.t.cpp
new file mode 100644
index 0000000..6094b0e
--- /dev/null
+++ b/tests/unit/security/pib/pib-impl.t.cpp
@@ -0,0 +1,350 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/pib-memory.hpp"
+#include "security/pib/pib-sqlite3.hpp"
+#include "security/pib/pib.hpp"
+#include "security/security-common.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+#include <boost/filesystem.hpp>
+#include <boost/mpl/list.hpp>
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_AUTO_TEST_SUITE(TestPibImpl)
+
+using pib::Pib;
+
+class PibMemoryFixture : public PibDataFixture
+{
+public:
+  PibMemory pib;
+};
+
+class PibSqlite3Fixture : public PibDataFixture
+{
+public:
+  PibSqlite3Fixture()
+    : tmpPath(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) / "DbTest")
+    , pib(tmpPath.c_str())
+  {
+  }
+
+  ~PibSqlite3Fixture()
+  {
+    boost::filesystem::remove_all(tmpPath);
+  }
+
+public:
+  boost::filesystem::path tmpPath;
+  PibSqlite3 pib;
+};
+
+typedef boost::mpl::list<PibMemoryFixture,
+                         PibSqlite3Fixture> PibImpls;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(TpmLocator, T, PibImpls, T)
+{
+  // Basic getting and setting
+  BOOST_CHECK_NO_THROW(this->pib.getTpmLocator());
+
+  BOOST_CHECK_NO_THROW(this->pib.setTpmLocator("tpmLocator"));
+  BOOST_CHECK_EQUAL(this->pib.getTpmLocator(), "tpmLocator");
+
+  // Add cert, and do not change TPM locator
+  this->pib.addCertificate(this->id1Key1Cert1);
+  BOOST_CHECK(this->pib.hasIdentity(this->id1));
+  BOOST_CHECK(this->pib.hasKey(this->id1Key1Name));
+  BOOST_CHECK(this->pib.hasCertificate(this->id1Key1Cert1.getName()));
+
+  // Set TPM locator to the same value, nothing should change
+  this->pib.setTpmLocator("tpmLocator");
+  BOOST_CHECK(this->pib.hasIdentity(this->id1));
+  BOOST_CHECK(this->pib.hasKey(this->id1Key1Name));
+  BOOST_CHECK(this->pib.hasCertificate(this->id1Key1Cert1.getName()));
+
+  // Change TPM locator (contents of PIB should not change)
+  this->pib.setTpmLocator("newTpmLocator");
+  BOOST_CHECK(this->pib.hasIdentity(this->id1));
+  BOOST_CHECK(this->pib.hasKey(this->id1Key1Name));
+  BOOST_CHECK(this->pib.hasCertificate(this->id1Key1Cert1.getName()));
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(IdentityManagement, T, PibImpls, T)
+{
+  // no default setting, throw Error
+  BOOST_CHECK_THROW(this->pib.getDefaultIdentity(), Pib::Error);
+
+  // check id1, which should not exist
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id1), false);
+
+  // add id1, should be default
+  this->pib.addIdentity(this->id1);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id1), true);
+  BOOST_CHECK_NO_THROW(this->pib.getDefaultIdentity());
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id1);
+
+  // add id2, should not be default
+  this->pib.addIdentity(this->id2);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id2), true);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id1);
+
+  // set id2 explicitly as default
+  this->pib.setDefaultIdentity(this->id2);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id2);
+
+  // remove id2, should not have default identity
+  this->pib.removeIdentity(this->id2);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id2), false);
+  BOOST_CHECK_THROW(this->pib.getDefaultIdentity(), Pib::Error);
+
+  // add id2 again, should be default
+  this->pib.addIdentity(this->id2);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id2);
+
+  // get all identities, should contain id1 and id2
+  std::set<Name> idNames = this->pib.getIdentities();
+  BOOST_CHECK_EQUAL(idNames.size(), 2);
+  BOOST_CHECK_EQUAL(idNames.count(this->id1), 1);
+  BOOST_CHECK_EQUAL(idNames.count(this->id2), 1);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ClearIdentities, T, PibImpls, T)
+{
+  this->pib.setTpmLocator("tpmLocator");
+
+  // Add id, key, and cert
+  this->pib.addCertificate(this->id1Key1Cert1);
+  BOOST_CHECK(this->pib.hasIdentity(this->id1));
+  BOOST_CHECK(this->pib.hasKey(this->id1Key1Name));
+  BOOST_CHECK(this->pib.hasCertificate(this->id1Key1Cert1.getName()));
+
+  // Clear identities
+  this->pib.clearIdentities();
+  BOOST_CHECK_EQUAL(this->pib.getIdentities().size(), 0);
+  BOOST_CHECK_EQUAL(this->pib.getKeysOfIdentity(this->id1).size(), 0);
+  BOOST_CHECK_EQUAL(this->pib.getCertificatesOfKey(this->id1Key1Name).size(), 0);
+  BOOST_CHECK_EQUAL(this->pib.getTpmLocator(), "tpmLocator");
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(KeyManagement, T, PibImpls, T)
+{
+  // no default setting, throw Error
+  BOOST_CHECK_THROW(this->pib.getDefaultKeyOfIdentity(this->id1), Pib::Error);
+
+  // check id1Key1, should not exist, neither should id1.
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key1Name), false);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id1), false);
+
+  // add id1Key1, should be default, id1 should be added implicitly
+  this->pib.addKey(this->id1, this->id1Key1Name, this->id1Key1.data(), this->id1Key1.size());
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key1Name), true);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id1), true);
+  const Buffer& keyBits = this->pib.getKeyBits(this->id1Key1Name);
+  BOOST_CHECK(keyBits == this->id1Key1);
+  BOOST_CHECK_NO_THROW(this->pib.getDefaultKeyOfIdentity(this->id1));
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id1), this->id1Key1Name);
+
+  // add id1Key2, should not be default
+  this->pib.addKey(this->id1, this->id1Key2Name, this->id1Key2.data(), this->id1Key2.size());
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key2Name), true);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id1), this->id1Key1Name);
+
+  // set id1Key2 explicitly as default
+  this->pib.setDefaultKeyOfIdentity(this->id1, this->id1Key2Name);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id1), this->id1Key2Name);
+
+  // set a non-existing key as default, throw Error
+  BOOST_CHECK_THROW(this->pib.setDefaultKeyOfIdentity(this->id1, Name("/non-existing")),
+                    Pib::Error);
+
+  // remove id1Key2, should not have default key
+  this->pib.removeKey(this->id1Key2Name);
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key2Name), false);
+  BOOST_CHECK_THROW(this->pib.getKeyBits(this->id1Key2Name), Pib::Error);
+  BOOST_CHECK_THROW(this->pib.getDefaultKeyOfIdentity(this->id1), Pib::Error);
+
+  // add id1Key2 back, should be default
+  this->pib.addKey(this->id1, this->id1Key2Name, this->id1Key2.data(), this->id1Key2.size());
+  BOOST_CHECK_NO_THROW(this->pib.getKeyBits(this->id1Key2Name));
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id1), this->id1Key2Name);
+
+  // get all the keys: id1Key1 and id1Key2
+  std::set<Name> keyNames = this->pib.getKeysOfIdentity(this->id1);
+  BOOST_CHECK_EQUAL(keyNames.size(), 2);
+  BOOST_CHECK_EQUAL(keyNames.count(this->id1Key1Name), 1);
+  BOOST_CHECK_EQUAL(keyNames.count(this->id1Key2Name), 1);
+
+  // remove id1, should remove all the keys
+  this->pib.removeIdentity(this->id1);
+  keyNames = this->pib.getKeysOfIdentity(this->id1);
+  BOOST_CHECK_EQUAL(keyNames.size(), 0);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(CertificateManagement, T, PibImpls, T)
+{
+  // no default setting, throw Error
+  BOOST_CHECK_THROW(this->pib.getDefaultCertificateOfKey(this->id1Key1Name), Pib::Error);
+
+  // check id1Key1Cert1, should not exist, neither should id1 and id1Key1
+  BOOST_CHECK_EQUAL(this->pib.hasCertificate(this->id1Key1Cert1.getName()), false);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id1), false);
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key1Name), false);
+
+  // add id1Key1Cert1, should be default, id1 and id1Key1 should be added implicitly
+  this->pib.addCertificate(this->id1Key1Cert1);
+  BOOST_CHECK_EQUAL(this->pib.hasCertificate(this->id1Key1Cert1.getName()), true);
+  BOOST_CHECK_EQUAL(this->pib.hasIdentity(this->id1), true);
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key1Name), true);
+  BOOST_CHECK_EQUAL(this->pib.getCertificate(this->id1Key1Cert1.getName()).wireEncode(),
+                    this->id1Key1Cert1.wireEncode());
+  BOOST_CHECK_NO_THROW(this->pib.getDefaultCertificateOfKey(this->id1Key1Name));
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id1Key1Name), this->id1Key1Cert1);
+
+  // add id1Key1Cert2, should not be default
+  this->pib.addCertificate(this->id1Key1Cert2);
+  BOOST_CHECK_EQUAL(this->pib.hasCertificate(this->id1Key1Cert2.getName()), true);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id1Key1Name), this->id1Key1Cert1);
+
+  // set id1Key1Cert2 explicitly as default
+  this->pib.setDefaultCertificateOfKey(this->id1Key1Name, this->id1Key1Cert2.getName());
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id1Key1Name), this->id1Key1Cert2);
+
+  // set a non-existing cert as default, throw Error
+  BOOST_CHECK_THROW(this->pib.setDefaultCertificateOfKey(this->id1Key1Name, Name("/non-existing")),
+                    Pib::Error);
+
+  // remove id1Key1Cert2, should not have default cert
+  this->pib.removeCertificate(this->id1Key1Cert2.getName());
+  BOOST_CHECK_EQUAL(this->pib.hasCertificate(this->id1Key1Cert2.getName()), false);
+  BOOST_CHECK_THROW(this->pib.getCertificate(this->id1Key1Cert2.getName()), Pib::Error);
+  BOOST_CHECK_THROW(this->pib.getDefaultCertificateOfKey(this->id1Key1Name), Pib::Error);
+
+  // add id1Key1Cert2, should be default
+  this->pib.addCertificate(this->id1Key1Cert2);
+  BOOST_CHECK_NO_THROW(this->pib.getCertificate(this->id1Key1Cert1.getName()));
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id1Key1Name), this->id1Key1Cert2);
+
+  // get all certificates: id1Key1Cert1 and id1Key1Cert2
+  std::set<Name> certNames = this->pib.getCertificatesOfKey(this->id1Key1Name);
+  BOOST_CHECK_EQUAL(certNames.size(), 2);
+  BOOST_CHECK_EQUAL(certNames.count(this->id1Key1Cert1.getName()), 1);
+  BOOST_CHECK_EQUAL(certNames.count(this->id1Key1Cert2.getName()), 1);
+
+  // remove id1Key1, should remove all the certs
+  this->pib.removeKey(this->id1Key1Name);
+  certNames = this->pib.getCertificatesOfKey(this->id1Key1Name);
+  BOOST_CHECK_EQUAL(certNames.size(), 0);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(DefaultsManagement, T, PibImpls, T)
+{
+  this->pib.addIdentity(this->id1);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id1);
+
+  this->pib.addIdentity(this->id2);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id1);
+
+  this->pib.removeIdentity(this->id1);
+  BOOST_CHECK_THROW(this->pib.getDefaultIdentity(), Pib::Error);
+
+  this->pib.addKey(this->id2, this->id2Key1Name, this->id2Key1.data(), this->id2Key1.size());
+  BOOST_CHECK_EQUAL(this->pib.getDefaultIdentity(), this->id2);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id2), this->id2Key1Name);
+
+  this->pib.addKey(this->id2, this->id2Key2Name, this->id2Key2.data(), this->id2Key2.size());
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id2), this->id2Key1Name);
+
+  this->pib.removeKey(this->id2Key1Name);
+  BOOST_CHECK_THROW(this->pib.getDefaultKeyOfIdentity(this->id2), Pib::Error);
+
+  this->pib.addCertificate(this->id2Key2Cert1);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultKeyOfIdentity(this->id2), this->id2Key2Name);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id2Key2Name).getName(), this->id2Key2Cert1.getName());
+
+  this->pib.addCertificate(this->id2Key2Cert2);
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id2Key2Name).getName(), this->id2Key2Cert1.getName());
+
+  this->pib.removeCertificate(this->id2Key2Cert2.getName());
+  BOOST_CHECK_EQUAL(this->pib.getDefaultCertificateOfKey(this->id2Key2Name).getName(), this->id2Key2Cert1.getName());
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Overwrite, T, PibImpls, T)
+{
+  // check id1Key1, should not exist
+  this->pib.removeIdentity(this->id1);
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key1Name), false);
+
+  // add id1Key1
+  this->pib.addKey(this->id1, this->id1Key1Name, this->id1Key1.data(), this->id1Key1.size());
+  BOOST_CHECK_EQUAL(this->pib.hasKey(this->id1Key1Name), true);
+  const Buffer& keyBits = this->pib.getKeyBits(this->id1Key1Name);
+  BOOST_CHECK(keyBits == this->id1Key1);
+
+  // check overwrite, add a key with the same name.
+  this->pib.addKey(this->id1, this->id1Key1Name, this->id1Key2.data(), this->id1Key2.size());
+  const Buffer& keyBits2 = this->pib.getKeyBits(this->id1Key1Name);
+  BOOST_CHECK(keyBits2 == this->id1Key2);
+
+  // check id1Key1Cert1, should not exist
+  this->pib.removeIdentity(this->id1);
+  BOOST_CHECK_EQUAL(this->pib.hasCertificate(this->id1Key1Cert1.getName()), false);
+
+  // add id1Key1Cert1
+  this->pib.addKey(this->id1, this->id1Key1Name, this->id1Key1.data(), this->id1Key1.size());
+  this->pib.addCertificate(this->id1Key1Cert1);
+  BOOST_CHECK_EQUAL(this->pib.hasCertificate(this->id1Key1Cert1.getName()), true);
+
+  auto cert = this->pib.getCertificate(this->id1Key1Cert1.getName());
+  BOOST_CHECK_EQUAL(cert.wireEncode(), this->id1Key1Cert1.wireEncode());
+
+  // Create a fake cert with the same name
+  auto cert2 = this->id1Key2Cert1;
+  cert2.setName(this->id1Key1Cert1.getName());
+  cert2.setSignature(this->id1Key2Cert1.getSignature());
+  this->pib.addCertificate(cert2);
+
+  auto cert3 = this->pib.getCertificate(this->id1Key1Cert1.getName());
+  BOOST_CHECK_EQUAL(cert3.wireEncode(), cert2.wireEncode());
+
+  // both key and certificate are overwritten
+  Buffer keyBits3 = this->pib.getKeyBits(this->id1Key1Name);
+  BOOST_CHECK(keyBits3 == this->id1Key2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPibImpl
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/pib-memory.t.cpp b/tests/unit/security/pib/pib-memory.t.cpp
new file mode 100644
index 0000000..72b0db8
--- /dev/null
+++ b/tests/unit/security/pib/pib-memory.t.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/pib-memory.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_AUTO_TEST_SUITE(TestPibMemory)
+
+// Functionality is tested as part of pib-impl.t.cpp
+
+BOOST_AUTO_TEST_SUITE_END() // TestPibMemory
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/pib-sqlite3.t.cpp b/tests/unit/security/pib/pib-sqlite3.t.cpp
new file mode 100644
index 0000000..b46cb50
--- /dev/null
+++ b/tests/unit/security/pib/pib-sqlite3.t.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/pib-sqlite3.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_AUTO_TEST_SUITE(TestPibSqlite3)
+
+// Functionality is tested as part of pib-impl.t.cpp
+
+BOOST_AUTO_TEST_SUITE_END() // TestPibSqlite3
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/pib/pib.t.cpp b/tests/unit/security/pib/pib.t.cpp
new file mode 100644
index 0000000..0ce5afc
--- /dev/null
+++ b/tests/unit/security/pib/pib.t.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/pib/pib.hpp"
+#include "security/pib/pib-memory.hpp"
+
+#include "boost-test.hpp"
+#include "pib-data-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace pib {
+namespace tests {
+
+using namespace ndn::security::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Pib)
+BOOST_FIXTURE_TEST_SUITE(TestPib, PibDataFixture)
+
+using pib::Pib;
+
+BOOST_AUTO_TEST_CASE(ValidityChecking)
+{
+  Pib pib("pib-memory", "", make_shared<PibMemory>());
+
+  Identity id = pib.addIdentity(id1);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(id), true);
+  BOOST_CHECK_EQUAL(!id, false);
+
+  if (id)
+    BOOST_CHECK(true);
+  else
+    BOOST_CHECK(false);
+
+  // key
+  Key key = id.addKey(id1Key1.data(), id1Key1.size(), id1Key1Name);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(key), true);
+  BOOST_CHECK_EQUAL(!key, false);
+
+  if (key)
+    BOOST_CHECK(true);
+  else
+    BOOST_CHECK(false);
+}
+
+BOOST_AUTO_TEST_CASE(TpmLocator)
+{
+  Pib pib("pib-memory", "", make_shared<PibMemory>());
+
+  BOOST_CHECK_EQUAL(pib.getPibLocator(), "pib-memory:");
+  BOOST_CHECK_THROW(pib.getTpmLocator(), Pib::Error);
+
+  pib.setTpmLocator("test-tpm-locator");
+  BOOST_CHECK_NO_THROW(pib.getTpmLocator());
+
+  BOOST_CHECK_THROW(pib.getIdentity(id1), Pib::Error);
+  pib.addIdentity(id1);
+  BOOST_CHECK_NO_THROW(pib.getIdentity(id1));
+
+  pib.setTpmLocator("another-tpm-locator");
+  BOOST_CHECK_THROW(pib.getIdentity(id1), Pib::Error);
+
+  pib.addIdentity(id1);
+  BOOST_CHECK_NO_THROW(pib.getIdentity(id1));
+  pib.reset();
+  BOOST_CHECK_THROW(pib.getIdentity(id1), Pib::Error);
+  BOOST_CHECK_THROW(pib.getTpmLocator(), Pib::Error);
+}
+
+BOOST_AUTO_TEST_CASE(IdentityOperations)
+{
+  Pib pib("pib-memory", "", make_shared<PibMemory>());
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 0);
+
+  // get non-existing identity, throw Pib::Error
+  BOOST_CHECK_THROW(pib.getIdentity(id1), Pib::Error);
+  // get default identity when it is not set yet, throw Pib::Error
+  BOOST_CHECK_THROW(pib.getDefaultIdentity(), Pib::Error);
+
+  // add identity
+  pib.addIdentity(id1);
+  BOOST_CHECK_NO_THROW(pib.getIdentity(id1));
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 1);
+
+  // new key becomes default key when there was no default key
+  BOOST_REQUIRE_NO_THROW(pib.getDefaultIdentity());
+  BOOST_CHECK_EQUAL(pib.getDefaultIdentity().getName(), id1);
+
+  // remove identity
+  pib.removeIdentity(id1);
+  BOOST_CHECK_THROW(pib.getIdentity(id1), Pib::Error);
+  BOOST_CHECK_THROW(pib.getDefaultIdentity(), Pib::Error);
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 0);
+
+  // set default identity
+  BOOST_REQUIRE_NO_THROW(pib.setDefaultIdentity(id1));
+  BOOST_REQUIRE_NO_THROW(pib.getDefaultIdentity());
+  BOOST_CHECK_EQUAL(pib.getDefaultIdentity().getName(), id1);
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 1);
+  BOOST_REQUIRE_NO_THROW(pib.setDefaultIdentity(id2));
+  BOOST_REQUIRE_NO_THROW(pib.getDefaultIdentity());
+  BOOST_CHECK_EQUAL(pib.getDefaultIdentity().getName(), id2);
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 2);
+
+  // remove default identity
+  pib.removeIdentity(id2);
+  BOOST_CHECK_THROW(pib.getIdentity(id2), Pib::Error);
+  BOOST_CHECK_THROW(pib.getDefaultIdentity(), Pib::Error);
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 1);
+  pib.removeIdentity(id1);
+  BOOST_CHECK_THROW(pib.getIdentity(id1), Pib::Error);
+  BOOST_CHECK_EQUAL(pib.getIdentities().size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPib
+BOOST_AUTO_TEST_SUITE_END() // Pib
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace pib
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/safe-bag.t.cpp b/tests/unit/security/safe-bag.t.cpp
new file mode 100644
index 0000000..0423c69
--- /dev/null
+++ b/tests/unit/security/safe-bag.t.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ */
+
+#include "security/safe-bag.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestSafeBag)
+
+const uint8_t CERT[] = {
+  0x06, 0xc8, // Data
+      0x07, 0x14, // Name
+          0x08, 0x05,
+              0x6c, 0x6f, 0x63, 0x61, 0x6c,
+          0x08, 0x03,
+              0x6e, 0x64, 0x6e,
+          0x08, 0x06,
+              0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
+      0x14, 0x07, // MetaInfo
+          0x18, 0x01, // ContentType
+              0x02,
+          0x19, 0x02, // FreshnessPeriod
+              0x27, 0x10,
+      0x15, 0x08, // Content
+          0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x21,
+      0x16, 0x1b, // SignatureInfo
+          0x1b, 0x01, // SignatureType
+              0x01,
+          0x1c, 0x16, // KeyLocator
+              0x07, 0x14, // Name
+                  0x08, 0x04,
+                      0x74, 0x65, 0x73, 0x74,
+                  0x08, 0x03,
+                      0x6b, 0x65, 0x79,
+                  0x08, 0x07,
+                      0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+      0x17, 0x80, // SignatureValue
+          0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+          0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+          0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+          0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+          0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+          0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+          0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+          0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+          0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+          0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1
+};
+
+const uint8_t ENCRYPTED_KEY_BAG[] = {
+  0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe
+};
+
+const uint8_t SAFE_BAG[] = {
+  0x80, 0xd4, // SafeBag
+      0x06, 0xc8, // Data
+          0x07, 0x14, // Name
+              0x08, 0x05,
+                  0x6c, 0x6f, 0x63, 0x61, 0x6c,
+              0x08, 0x03,
+                  0x6e, 0x64, 0x6e,
+              0x08, 0x06,
+                  0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
+          0x14, 0x07, // MetaInfo
+              0x18, 0x01, // ContentType
+                  0x02,
+              0x19, 0x02, // FreshnessPeriod
+                  0x27, 0x10,
+          0x15, 0x08, // Content
+              0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x21,
+          0x16, 0x1b, // SignatureInfo
+              0x1b, 0x01, // SignatureType
+                  0x01,
+              0x1c, 0x16, // KeyLocator
+                  0x07, 0x14, // Name
+                      0x08, 0x04,
+                          0x74, 0x65, 0x73, 0x74,
+                      0x08, 0x03,
+                          0x6b, 0x65, 0x79,
+                      0x08, 0x07,
+                          0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+          0x17, 0x80, // SignatureValue
+              0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+              0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+              0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+              0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+              0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+              0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+              0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+              0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+              0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+              0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1,
+      0x81, 0x08, // EncryptedKeyBag
+          0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe
+};
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+  Block dataBlock(CERT, sizeof(CERT));
+  Data data(dataBlock);
+  SafeBag safeBag1(data, ENCRYPTED_KEY_BAG, sizeof(ENCRYPTED_KEY_BAG));
+
+  Block safeBagBlock(SAFE_BAG, sizeof(SAFE_BAG));
+  SafeBag safeBag2(safeBagBlock);
+
+  Buffer buffer(ENCRYPTED_KEY_BAG, sizeof(ENCRYPTED_KEY_BAG));
+  SafeBag safeBag3(data, buffer);
+
+  BOOST_CHECK(safeBag1.getCertificate() == data);
+  BOOST_CHECK(safeBag1.getEncryptedKeyBag() == buffer);
+  BOOST_CHECK(safeBag2.getCertificate() == data);
+  BOOST_CHECK(safeBag2.getEncryptedKeyBag() == buffer);
+  BOOST_CHECK(safeBag3.getCertificate() == data);
+  BOOST_CHECK(safeBag3.getEncryptedKeyBag() == buffer);
+}
+
+BOOST_AUTO_TEST_CASE(EncoderAndDecoder)
+{
+  Block dataBlock(CERT, sizeof(CERT));
+  Data data(dataBlock);
+  SafeBag safeBag(data, ENCRYPTED_KEY_BAG, sizeof(ENCRYPTED_KEY_BAG));
+
+  // wire encode
+  Block wireBlock = safeBag.wireEncode();
+  Block block(SAFE_BAG, sizeof(SAFE_BAG));
+
+  // check safe bag block
+  BOOST_CHECK_EQUAL(wireBlock, block);
+
+  // wire decode
+  SafeBag safeBag2;
+  safeBag2.wireDecode(wireBlock);
+
+  // check equal
+  Buffer buffer1 = safeBag2.getEncryptedKeyBag();
+  Buffer buffer2(ENCRYPTED_KEY_BAG, sizeof(ENCRYPTED_KEY_BAG));
+  BOOST_CHECK(buffer1 == buffer2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSafeBag
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/signature-sha256-with-ecdsa.t.cpp b/tests/unit/security/signature-sha256-with-ecdsa.t.cpp
new file mode 100644
index 0000000..2f90302
--- /dev/null
+++ b/tests/unit/security/signature-sha256-with-ecdsa.t.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/signature-sha256-with-ecdsa.hpp"
+#include "security/verification-helpers.hpp"
+#include "util/scheduler.hpp"
+
+#include "boost-test.hpp"
+#include "../identity-management-time-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+class SignatureSha256EcdsaTimeFixture : public IdentityManagementTimeFixture
+{
+public:
+  SignatureSha256EcdsaTimeFixture()
+    : scheduler(io)
+  {
+  }
+
+public:
+  Scheduler scheduler;
+};
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_FIXTURE_TEST_SUITE(TestSignatureSha256WithEcdsa, SignatureSha256EcdsaTimeFixture)
+
+const uint8_t sigInfo[] = {
+  0x16, 0x1b, // SignatureInfo
+    0x1b, 0x01, // SignatureType
+      0x03,
+    0x1c, 0x16, // KeyLocator
+      0x07, 0x14, // Name: /test/key/locator
+        0x08, 0x04,
+          0x74, 0x65, 0x73, 0x74,
+        0x08, 0x03,
+          0x6b, 0x65, 0x79,
+        0x08, 0x07,
+          0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+};
+
+const uint8_t sigValue[] = {
+  0x17, 0x40, // SignatureValue
+    0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+    0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+    0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+    0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+    0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b
+};
+
+
+BOOST_AUTO_TEST_CASE(Decoding)
+{
+  Block sigInfoBlock(sigInfo, sizeof(sigInfo));
+  Block sigValueBlock(sigValue, sizeof(sigValue));
+
+  Signature sig(sigInfoBlock, sigValueBlock);
+  BOOST_CHECK_NO_THROW(SignatureSha256WithEcdsa{sig});
+  BOOST_CHECK_NO_THROW(sig.getKeyLocator());
+}
+
+BOOST_AUTO_TEST_CASE(Encoding)
+{
+  Name name("/test/key/locator");
+  KeyLocator keyLocator(name);
+
+  SignatureSha256WithEcdsa sig(keyLocator);
+
+  BOOST_CHECK_NO_THROW(sig.getKeyLocator());
+
+  const Block& encodeSigInfoBlock = sig.getInfo();
+
+  Block sigInfoBlock(sigInfo, sizeof(sigInfo));
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(sigInfoBlock.wire(),
+                                sigInfoBlock.wire() + sigInfoBlock.size(),
+                                encodeSigInfoBlock.wire(),
+                                encodeSigInfoBlock.wire() + encodeSigInfoBlock.size());
+
+  sig.setKeyLocator(Name("/test/another/key/locator"));
+
+  const Block& encodeSigInfoBlock2 = sig.getInfo();
+  BOOST_CHECK_NE(sigInfoBlock, encodeSigInfoBlock2);
+}
+
+BOOST_AUTO_TEST_CASE(DataSignature)
+{
+  Identity identity = addIdentity("/SecurityTestSignatureSha256WithEcdsa/DataSignature", EcKeyParams());
+
+  Data testData("/SecurityTestSignatureSha256WithEcdsa/DataSignature/Data1");
+  char content[5] = "1234";
+  testData.setContent(reinterpret_cast<uint8_t*>(content), 5);
+  BOOST_CHECK_NO_THROW(m_keyChain.sign(testData, security::SigningInfo(identity)));
+  Block dataBlock(testData.wireEncode().wire(), testData.wireEncode().size());
+
+  Data testData2;
+  testData2.wireDecode(dataBlock);
+  BOOST_CHECK(verifySignature(testData2, identity.getDefaultKey()));
+}
+
+BOOST_AUTO_TEST_CASE(InterestSignature)
+{
+  Identity identity = addIdentity("/SecurityTestSignatureSha256WithEcdsa/InterestSignature", EcKeyParams());
+
+  auto interest = makeInterest("/SecurityTestSignatureSha256WithEcdsa/InterestSignature/Interest1");
+  auto interest11 = makeInterest("/SecurityTestSignatureSha256WithEcdsa/InterestSignature/Interest1");
+
+  scheduler.scheduleEvent(100_ms, [&] {
+      m_keyChain.sign(*interest, security::SigningInfo(identity));
+    });
+
+  advanceClocks(100_ms);
+  scheduler.scheduleEvent(100_ms, [&] {
+      m_keyChain.sign(*interest11, security::SigningInfo(identity));
+    });
+
+  advanceClocks(100_ms);
+
+  Block interestBlock(interest->wireEncode().wire(), interest->wireEncode().size());
+
+  Interest interest2;
+  interest2.wireDecode(interestBlock);
+  BOOST_CHECK(verifySignature(interest2, identity.getDefaultKey()));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSignatureSha256WithEcdsa
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/signature-sha256-with-rsa.t.cpp b/tests/unit/security/signature-sha256-with-rsa.t.cpp
new file mode 100644
index 0000000..7de3774
--- /dev/null
+++ b/tests/unit/security/signature-sha256-with-rsa.t.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/signature-sha256-with-rsa.hpp"
+#include "security/verification-helpers.hpp"
+#include "util/scheduler.hpp"
+
+#include "boost-test.hpp"
+#include "../identity-management-time-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+class SignatureSha256RsaTimeFixture : public IdentityManagementTimeFixture
+{
+public:
+  SignatureSha256RsaTimeFixture()
+    : scheduler(io)
+  {
+  }
+
+public:
+  Scheduler scheduler;
+};
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_FIXTURE_TEST_SUITE(TestSignatureSha256WithRsa, SignatureSha256RsaTimeFixture)
+
+const uint8_t sigInfo[] = {
+  0x16, 0x1b, // SignatureInfo
+    0x1b, 0x01, // SignatureType
+      0x01,
+    0x1c, 0x16, // KeyLocator
+      0x07, 0x14, // Name
+        0x08, 0x04,
+          0x74, 0x65, 0x73, 0x74,
+        0x08, 0x03,
+          0x6b, 0x65, 0x79,
+        0x08, 0x07,
+          0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+};
+
+const uint8_t sigValue[] = {
+0x17, 0x80, // SignatureValue
+  0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+  0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+  0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+  0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+  0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+  0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+  0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+  0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+  0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+  0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1
+};
+
+
+BOOST_AUTO_TEST_CASE(Decoding)
+{
+  Block sigInfoBlock(sigInfo, sizeof(sigInfo));
+  Block sigValueBlock(sigValue, sizeof(sigValue));
+
+  Signature sig(sigInfoBlock, sigValueBlock);
+  BOOST_CHECK_NO_THROW(SignatureSha256WithRsa{sig});
+  BOOST_CHECK_NO_THROW(sig.getKeyLocator());
+}
+
+BOOST_AUTO_TEST_CASE(Encoding)
+{
+  Name name("/test/key/locator");
+  KeyLocator keyLocator(name);
+
+  SignatureSha256WithRsa sig(keyLocator);
+
+  BOOST_CHECK_NO_THROW(sig.getKeyLocator());
+
+  const Block& encodeSigInfoBlock = sig.getInfo();
+
+  Block sigInfoBlock(sigInfo, sizeof(sigInfo));
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(sigInfoBlock.wire(),
+                                sigInfoBlock.wire() + sigInfoBlock.size(),
+                                encodeSigInfoBlock.wire(),
+                                encodeSigInfoBlock.wire() + encodeSigInfoBlock.size());
+
+  sig.setKeyLocator(Name("/test/another/key/locator"));
+
+  const Block& encodeSigInfoBlock2 = sig.getInfo();
+  BOOST_CHECK_NE(sigInfoBlock, encodeSigInfoBlock2);
+}
+
+BOOST_AUTO_TEST_CASE(DataSignature)
+{
+  Identity identity = addIdentity("/SecurityTestSignatureSha256WithRsa/DataSignature", RsaKeyParams());
+
+  Data testData("/SecurityTestSignatureSha256WithRsa/DataSignature/Data1");
+  char content[5] = "1234";
+  testData.setContent(reinterpret_cast<uint8_t*>(content), 5);
+  BOOST_CHECK_NO_THROW(m_keyChain.sign(testData, security::SigningInfo(identity)));
+  Block dataBlock(testData.wireEncode().wire(), testData.wireEncode().size());
+
+  Data testData2;
+  testData2.wireDecode(dataBlock);
+  BOOST_CHECK(verifySignature(testData2, identity.getDefaultKey()));
+}
+
+BOOST_AUTO_TEST_CASE(InterestSignature)
+{
+  Identity identity = addIdentity("/SecurityTestSignatureSha256WithRsa/InterestSignature", RsaKeyParams());
+
+  auto interest = makeInterest("/SecurityTestSignatureSha256WithRsa/InterestSignature/Interest1");
+  auto interest11 = makeInterest("/SecurityTestSignatureSha256WithRsa/InterestSignature/Interest1");
+
+  scheduler.scheduleEvent(100_ms, [&] {
+      m_keyChain.sign(*interest, security::SigningInfo(identity));
+    });
+
+  advanceClocks(100_ms);
+  scheduler.scheduleEvent(100_ms, [&] {
+      m_keyChain.sign(*interest11, security::SigningInfo(identity));
+    });
+
+  advanceClocks(100_ms);
+
+  Block interestBlock(interest->wireEncode().wire(), interest->wireEncode().size());
+
+  Interest interest2;
+  interest2.wireDecode(interestBlock);
+  BOOST_CHECK(verifySignature(interest2, identity.getDefaultKey()));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSignatureSha256WithRsa
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/signing-helpers.t.cpp b/tests/unit/security/signing-helpers.t.cpp
new file mode 100644
index 0000000..af2c568
--- /dev/null
+++ b/tests/unit/security/signing-helpers.t.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/signing-helpers.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestSigningHelpers)
+
+// update of this test case deferred until the new IdentityManagementFixture is available
+
+BOOST_AUTO_TEST_CASE(Identity)
+{
+  Name identity("/identity");
+  SigningInfo info = signingByIdentity(identity);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(info.getSignerName(), identity);
+}
+
+BOOST_AUTO_TEST_CASE(Key)
+{
+  Name key("/key");
+  SigningInfo info = signingByKey(key);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_KEY);
+  BOOST_CHECK_EQUAL(info.getSignerName(), key);
+}
+
+BOOST_AUTO_TEST_CASE(Certificate)
+{
+  Name cert("/cert");
+  SigningInfo info = signingByCertificate(cert);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_CERT);
+  BOOST_CHECK_EQUAL(info.getSignerName(), cert);
+}
+
+BOOST_AUTO_TEST_CASE(Sha256)
+{
+  SigningInfo info = signingWithSha256();
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_SHA256);
+  BOOST_CHECK_EQUAL(info.getSignerName(), SigningInfo::getEmptyName());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSigningHelpers
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/signing-info.t.cpp b/tests/unit/security/signing-info.t.cpp
new file mode 100644
index 0000000..fc6f627
--- /dev/null
+++ b/tests/unit/security/signing-info.t.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/signing-info.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestSigningInfo)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  Name id("/my-identity");
+  Name key("/my-key");
+  Name cert("/my-cert");
+
+  SigningInfo info;
+
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_NULL);
+  BOOST_CHECK_EQUAL(info.getSignerName(), SigningInfo::getEmptyName());
+  BOOST_CHECK_EQUAL(info.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  const SignatureInfo& sigInfo = info.getSignatureInfo();
+  BOOST_CHECK_EQUAL(sigInfo.getSignatureType(), -1);
+  BOOST_CHECK_EQUAL(sigInfo.hasKeyLocator(), false);
+
+  info.setSigningIdentity(id);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(info.getSignerName(), id);
+  BOOST_CHECK_EQUAL(info.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoId(SigningInfo::SIGNER_TYPE_ID, id);
+  BOOST_CHECK_EQUAL(infoId.getSignerType(), SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(infoId.getSignerName(), id);
+  BOOST_CHECK_EQUAL(infoId.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  info.setSigningKeyName(key);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_KEY);
+  BOOST_CHECK_EQUAL(info.getSignerName(), key);
+  BOOST_CHECK_EQUAL(info.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoKey(SigningInfo::SIGNER_TYPE_KEY, key);
+  BOOST_CHECK_EQUAL(infoKey.getSignerType(), SigningInfo::SIGNER_TYPE_KEY);
+  BOOST_CHECK_EQUAL(infoKey.getSignerName(), key);
+  BOOST_CHECK_EQUAL(infoKey.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  info.setSigningCertName(cert);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_CERT);
+  BOOST_CHECK_EQUAL(info.getSignerName(), cert);
+  BOOST_CHECK_EQUAL(info.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoCert(SigningInfo::SIGNER_TYPE_CERT, cert);
+  BOOST_CHECK_EQUAL(infoCert.getSignerType(), SigningInfo::SIGNER_TYPE_CERT);
+  BOOST_CHECK_EQUAL(infoCert.getSignerName(), cert);
+  BOOST_CHECK_EQUAL(infoCert.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  info.setSha256Signing();
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_SHA256);
+  BOOST_CHECK_EQUAL(info.getSignerName(), SigningInfo::getEmptyName());
+  BOOST_CHECK_EQUAL(info.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoSha(SigningInfo::SIGNER_TYPE_SHA256);
+  BOOST_CHECK_EQUAL(infoSha.getSignerType(), SigningInfo::SIGNER_TYPE_SHA256);
+  BOOST_CHECK_EQUAL(infoSha.getSignerName(), SigningInfo::getEmptyName());
+  BOOST_CHECK_EQUAL(infoSha.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+}
+
+BOOST_AUTO_TEST_CASE(CustomSignatureInfo)
+{
+  SigningInfo info1;
+  BOOST_CHECK_EQUAL(info1.getSignatureInfo(), SignatureInfo());
+
+  SignatureInfo si;
+  si.setKeyLocator(Name("ndn:/test/key/locator"));
+  info1.setSignatureInfo(si);
+
+  BOOST_CHECK_EQUAL(info1.getSignatureInfo(), si);
+
+  SigningInfo info2(SigningInfo::SIGNER_TYPE_NULL, SigningInfo::getEmptyName(), si);
+  BOOST_CHECK_EQUAL(info2.getSignatureInfo(), si);
+}
+
+BOOST_AUTO_TEST_CASE(FromString)
+{
+  SigningInfo infoDefault("");
+  BOOST_CHECK_EQUAL(infoDefault.getSignerType(), SigningInfo::SIGNER_TYPE_NULL);
+  BOOST_CHECK_EQUAL(infoDefault.getSignerName(), SigningInfo::getEmptyName());
+  BOOST_CHECK_EQUAL(infoDefault.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoId("id:/my-identity");
+  BOOST_CHECK_EQUAL(infoId.getSignerType(), SigningInfo::SIGNER_TYPE_ID);
+  BOOST_CHECK_EQUAL(infoId.getSignerName(), "/my-identity");
+  BOOST_CHECK_EQUAL(infoId.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoKey("key:/my-key");
+  BOOST_CHECK_EQUAL(infoKey.getSignerType(), SigningInfo::SIGNER_TYPE_KEY);
+  BOOST_CHECK_EQUAL(infoKey.getSignerName(), "/my-key");
+  BOOST_CHECK_EQUAL(infoKey.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoCert("cert:/my-cert");
+  BOOST_CHECK_EQUAL(infoCert.getSignerType(), SigningInfo::SIGNER_TYPE_CERT);
+  BOOST_CHECK_EQUAL(infoCert.getSignerName(), "/my-cert");
+  BOOST_CHECK_EQUAL(infoCert.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoSha("id:/localhost/identity/digest-sha256");
+  BOOST_CHECK_EQUAL(infoSha.getSignerType(), SigningInfo::SIGNER_TYPE_SHA256);
+  BOOST_CHECK_EQUAL(infoSha.getSignerName(), SigningInfo::getEmptyName());
+  BOOST_CHECK_EQUAL(infoSha.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+}
+
+BOOST_AUTO_TEST_CASE(ToString)
+{
+  // We can't use lexical_cast due to Boost Bug 6298.
+  std::stringstream ss;
+  ss << SigningInfo();
+  BOOST_CHECK_EQUAL(ss.str(), "");
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(
+                    SigningInfo(SigningInfo::SIGNER_TYPE_ID, "/my-identity")), "id:/my-identity");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(
+                    SigningInfo(SigningInfo::SIGNER_TYPE_KEY, "/my-key")), "key:/my-key");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(
+                    SigningInfo(SigningInfo::SIGNER_TYPE_CERT, "/my-cert")), "cert:/my-cert");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(
+                    SigningInfo(SigningInfo::SIGNER_TYPE_SHA256)),
+                    "id:/localhost/identity/digest-sha256");
+}
+
+BOOST_AUTO_TEST_CASE(Chaining)
+{
+  SigningInfo info = SigningInfo()
+    .setSigningIdentity("/identity")
+    .setSigningKeyName("/key/name")
+    .setSigningCertName("/cert/name")
+    .setPibIdentity(Identity())
+    .setPibKey(Key())
+    .setSha256Signing()
+    .setDigestAlgorithm(DigestAlgorithm::SHA256)
+    .setSignatureInfo(SignatureInfo());
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(info), "id:/localhost/identity/digest-sha256");
+}
+
+BOOST_AUTO_TEST_CASE(OperatorEquals)
+{
+  // Check name equality
+  SigningInfo info1("id:/my-id");
+  SigningInfo info2("id:/my-id");
+  BOOST_CHECK_EQUAL(info1, info2);
+  // Change name, check inequality
+  info2 = SigningInfo("id:/not-same-id");
+  BOOST_CHECK_NE(info1, info2);
+
+  // Check name, digest algo equality
+  info1 = SigningInfo("id:/my-id");
+  info2 = SigningInfo("id:/my-id");
+  info1.setDigestAlgorithm(DigestAlgorithm::SHA256);
+  info2.setDigestAlgorithm(DigestAlgorithm::SHA256);
+  BOOST_CHECK_EQUAL(info1, info2);
+  // Change digest algo, check inequality
+  info2.setDigestAlgorithm(DigestAlgorithm::NONE);
+  BOOST_CHECK_NE(info1, info2);
+
+  // Check name, digest algo, signature info equality
+  info1 = SigningInfo("id:/my-id");
+  info2 = SigningInfo("id:/my-id");
+  info1.setDigestAlgorithm(DigestAlgorithm::SHA256);
+  info2.setDigestAlgorithm(DigestAlgorithm::SHA256);
+  SignatureInfo sigInfo1(tlv::DigestSha256);
+  info1.setSignatureInfo(sigInfo1);
+  info2.setSignatureInfo(sigInfo1);
+  BOOST_CHECK_EQUAL(info1, info2);
+  // Change signature info, check inequality
+  SignatureInfo sigInfo2(tlv::SignatureSha256WithRsa);
+  info2.setSignatureInfo(sigInfo2);
+  BOOST_CHECK_NE(info1, info2);
+}
+
+BOOST_AUTO_TEST_CASE(OperatorEqualsDifferentTypes)
+{
+  SigningInfo info1("key:/my-id/KEY/1");
+  SigningInfo info2("key:/my-id/KEY/1");
+  // Check equality for key type
+  BOOST_CHECK_EQUAL(info1, info2);
+  info2 = SigningInfo("id:/my-id");
+  // Change signature type, check inequality
+  BOOST_CHECK_NE(info1, info2);
+  info2 = SigningInfo("key:/not-same-id/KEY/1");
+  // Change key name, check inequality
+  BOOST_CHECK_NE(info1, info2);
+
+  info1 = SigningInfo("cert:/my-id/KEY/1/self/%FD01");
+  info2 = SigningInfo("cert:/my-id/KEY/1/self/%FD01");
+  // Check equality for cert type
+  BOOST_CHECK_EQUAL(info1, info2);
+  info2 = SigningInfo("cert:/not-my-id/KEY/1/other/%FD01");
+  // Change cert name, check inequality
+  BOOST_CHECK_NE(info1, info2);
+  info2 = SigningInfo("id:/my-id");
+  // Change signature type, check inequality
+  BOOST_CHECK_NE(info1, info2);
+
+  info1 = SigningInfo(SigningInfo::SIGNER_TYPE_NULL);
+  info2 = SigningInfo(SigningInfo::SIGNER_TYPE_NULL);
+  // Check equality for null type
+  BOOST_CHECK_EQUAL(info1, info2);
+  info2 = SigningInfo("id:/my-id");
+  // Change signature type, check inequality
+  BOOST_CHECK_NE(info1, info2);
+
+  info1 = SigningInfo(SigningInfo::SIGNER_TYPE_SHA256);
+  info2 = SigningInfo(SigningInfo::SIGNER_TYPE_SHA256);
+  // Check equality for SHA256 digest type
+  BOOST_CHECK_EQUAL(info1, info2);
+  info2 = SigningInfo("id:/my-id");
+  // Change signature type, check inequality
+  BOOST_CHECK_NE(info1, info2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSigningInfo
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/tmp-home/.ndn/client.conf b/tests/unit/security/tmp-home/.ndn/client.conf
new file mode 100644
index 0000000..b832cfc
--- /dev/null
+++ b/tests/unit/security/tmp-home/.ndn/client.conf
@@ -0,0 +1,2 @@
+pib=pib-sqlite3:/tmp/test/ndn-cxx/keychain
+tpm=tpm-file:/tmp/test/ndn-cxx/keychain
\ No newline at end of file
diff --git a/tests/unit/security/tpm/back-end-wrapper-file.hpp b/tests/unit/security/tpm/back-end-wrapper-file.hpp
new file mode 100644
index 0000000..dabd6d3
--- /dev/null
+++ b/tests/unit/security/tpm/back-end-wrapper-file.hpp
@@ -0,0 +1,72 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_FILE_HPP
+#define NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_FILE_HPP
+
+#include "security/tpm/back-end-file.hpp"
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace security {
+namespace tpm {
+namespace tests {
+
+/**
+ * @brief A wrapper of tpm::BackEndFile for unit test template.
+ */
+class BackEndWrapperFile
+{
+public:
+  BackEndWrapperFile()
+    : m_tmpPath(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) / "TpmFileTest")
+    , m_impl(make_unique<BackEndFile>(m_tmpPath.string()))
+  {
+  }
+
+  ~BackEndWrapperFile()
+  {
+    boost::filesystem::remove_all(m_tmpPath);
+  }
+
+  BackEnd&
+  getTpm()
+  {
+    return *m_impl;
+  }
+
+  std::string
+  getScheme() const
+  {
+    return "tpm-file";
+  }
+
+private:
+  const boost::filesystem::path m_tmpPath;
+  const unique_ptr<BackEnd> m_impl;
+};
+
+} // namespace tests
+} // namespace tpm
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_FILE_HPP
diff --git a/tests/unit/security/tpm/back-end-wrapper-mem.hpp b/tests/unit/security/tpm/back-end-wrapper-mem.hpp
new file mode 100644
index 0000000..ddf629b
--- /dev/null
+++ b/tests/unit/security/tpm/back-end-wrapper-mem.hpp
@@ -0,0 +1,64 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_MEM_HPP
+#define NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_MEM_HPP
+
+#include "security/tpm/back-end-mem.hpp"
+
+namespace ndn {
+namespace security {
+namespace tpm {
+namespace tests {
+
+/**
+ * @brief A wrapper of tpm::BackEndMem for unit test template.
+ */
+class BackEndWrapperMem
+{
+public:
+  BackEndWrapperMem()
+    : m_impl(make_unique<BackEndMem>())
+  {
+  }
+
+  BackEnd&
+  getTpm()
+  {
+    return *m_impl;
+  }
+
+  std::string
+  getScheme() const
+  {
+    return "tpm-memory";
+  }
+
+private:
+  const unique_ptr<BackEnd> m_impl;
+};
+
+} // namespace tests
+} // namespace tpm
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_MEM_HPP
diff --git a/tests/unit/security/tpm/back-end-wrapper-osx.hpp b/tests/unit/security/tpm/back-end-wrapper-osx.hpp
new file mode 100644
index 0000000..6d7d35b
--- /dev/null
+++ b/tests/unit/security/tpm/back-end-wrapper-osx.hpp
@@ -0,0 +1,88 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_OSX_HPP
+#define NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_OSX_HPP
+
+#include "security/tpm/back-end-osx.hpp"
+#include "security/tpm/key-handle-osx.hpp"
+
+#include <cstdlib>
+
+namespace ndn {
+namespace security {
+namespace tpm {
+namespace tests {
+
+/**
+ * @brief A wrapper of tpm::BackEndOsx for unit test template.
+ */
+class BackEndWrapperOsx
+{
+public:
+  BackEndWrapperOsx()
+  {
+    std::string oldHOME;
+    if (std::getenv("OLD_HOME"))
+      oldHOME = std::getenv("OLD_HOME");
+
+    if (std::getenv("HOME"))
+      m_HOME = std::getenv("HOME");
+
+    if (!oldHOME.empty())
+      setenv("HOME", oldHOME.data(), 1);
+    else
+      unsetenv("HOME");
+
+    m_impl = make_unique<BackEndOsx>();
+  }
+
+  ~BackEndWrapperOsx()
+  {
+    if (!m_HOME.empty())
+      setenv("HOME", m_HOME.data(), 1);
+    else
+      unsetenv("HOME");
+  }
+
+  BackEnd&
+  getTpm()
+  {
+    return *m_impl;
+  }
+
+  std::string
+  getScheme() const
+  {
+    return "tpm-osxkeychain";
+  }
+
+private:
+  std::string m_HOME;
+  unique_ptr<BackEnd> m_impl;
+};
+
+} // namespace tests
+} // namespace tpm
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_TESTS_SECURITY_TPM_BACK_END_WRAPPER_OSX_HPP
diff --git a/tests/unit/security/tpm/back-end.t.cpp b/tests/unit/security/tpm/back-end.t.cpp
new file mode 100644
index 0000000..5512ebd
--- /dev/null
+++ b/tests/unit/security/tpm/back-end.t.cpp
@@ -0,0 +1,279 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/tpm/back-end.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/pib/key.hpp"
+#include "security/tpm/key-handle.hpp"
+#include "security/tpm/tpm.hpp"
+#include "security/transform/bool-sink.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/private-key.hpp"
+#include "security/transform/public-key.hpp"
+#include "security/transform/verifier-filter.hpp"
+
+#include "back-end-wrapper-file.hpp"
+#include "back-end-wrapper-mem.hpp"
+#ifdef NDN_CXX_HAVE_OSX_FRAMEWORKS
+#include "back-end-wrapper-osx.hpp"
+#endif // NDN_CXX_HAVE_OSX_FRAMEWORKS
+
+#include "boost-test.hpp"
+
+#include <boost/mpl/vector.hpp>
+#include <set>
+
+namespace ndn {
+namespace security {
+namespace tpm {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Tpm)
+BOOST_AUTO_TEST_SUITE(TestBackEnd)
+
+using tpm::Tpm;
+
+using TestBackEnds = boost::mpl::vector<
+#ifdef NDN_CXX_HAVE_OSX_FRAMEWORKS
+  BackEndWrapperOsx,
+#endif // NDN_CXX_HAVE_OSX_FRAMEWORKS
+  BackEndWrapperMem,
+  BackEndWrapperFile>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(KeyManagement, T, TestBackEnds)
+{
+  T wrapper;
+  BackEnd& tpm = wrapper.getTpm();
+
+  Name identity("/Test/KeyName");
+  name::Component keyId("1");
+  Name keyName = v2::constructKeyName(identity, keyId);
+
+  // key should not exist
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), false);
+  BOOST_CHECK(tpm.getKeyHandle(keyName) == nullptr);
+
+  // create key, should exist
+  BOOST_CHECK(tpm.createKey(identity, RsaKeyParams(keyId)) != nullptr);
+  BOOST_CHECK(tpm.hasKey(keyName));
+  BOOST_CHECK(tpm.getKeyHandle(keyName) != nullptr);
+
+  // create a key with the same name, should throw error
+  BOOST_CHECK_THROW(tpm.createKey(identity, RsaKeyParams(keyId)), Tpm::Error);
+
+  // delete key, should not exist
+  tpm.deleteKey(keyName);
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), false);
+  BOOST_CHECK(tpm.getKeyHandle(keyName) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(RsaSigning, T, TestBackEnds)
+{
+  T wrapper;
+  BackEnd& tpm = wrapper.getTpm();
+
+  // create an RSA key
+  Name identity("/Test/KeyName");
+  unique_ptr<KeyHandle> key = tpm.createKey(identity, RsaKeyParams());
+  Name keyName = key->getKeyName();
+
+  const uint8_t content[] = {0x01, 0x02, 0x03, 0x04};
+  Block sigBlock(tlv::SignatureValue, key->sign(DigestAlgorithm::SHA256, content, sizeof(content)));
+
+  transform::PublicKey pubKey;
+  ConstBufferPtr pubKeyBits = key->derivePublicKey();
+  pubKey.loadPkcs8(pubKeyBits->data(), pubKeyBits->size());
+
+  bool result;
+  {
+    using namespace transform;
+    bufferSource(content, sizeof(content)) >>
+      verifierFilter(DigestAlgorithm::SHA256, pubKey, sigBlock.value(), sigBlock.value_size()) >>
+      boolSink(result);
+  }
+  BOOST_CHECK_EQUAL(result, true);
+
+  tpm.deleteKey(keyName);
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), false);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(RsaDecryption, T, TestBackEnds)
+{
+  T wrapper;
+  BackEnd& tpm = wrapper.getTpm();
+
+  // create an RSA key
+  Name identity("/Test/KeyName");
+  unique_ptr<KeyHandle> key = tpm.createKey(identity, RsaKeyParams());
+  Name keyName = key->getKeyName();
+
+  const uint8_t content[] = {0x01, 0x02, 0x03, 0x04};
+
+  transform::PublicKey pubKey;
+  ConstBufferPtr pubKeyBits = key->derivePublicKey();
+  pubKey.loadPkcs8(pubKeyBits->data(), pubKeyBits->size());
+
+  ConstBufferPtr cipherText = pubKey.encrypt(content, sizeof(content));
+  ConstBufferPtr plainText = key->decrypt(cipherText->data(), cipherText->size());
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(content, content + sizeof(content),
+                                plainText->begin(), plainText->end());
+
+  tpm.deleteKey(keyName);
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), false);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(EcdsaSigning, T, TestBackEnds)
+{
+  T wrapper;
+  BackEnd& tpm = wrapper.getTpm();
+
+  // create an EC key
+  Name identity("/Test/Ec/KeyName");
+  unique_ptr<KeyHandle> key = tpm.createKey(identity, EcKeyParams());
+  Name ecKeyName = key->getKeyName();
+
+  const uint8_t content[] = {0x01, 0x02, 0x03, 0x04};
+  Block sigBlock(tlv::SignatureValue, key->sign(DigestAlgorithm::SHA256, content, sizeof(content)));
+
+  transform::PublicKey pubKey;
+  ConstBufferPtr pubKeyBits = key->derivePublicKey();
+  pubKey.loadPkcs8(pubKeyBits->data(), pubKeyBits->size());
+
+  bool result;
+  {
+    using namespace transform;
+    bufferSource(content, sizeof(content)) >> verifierFilter(DigestAlgorithm::SHA256, pubKey,
+                                                             sigBlock.value(), sigBlock.value_size())
+                                           >> boolSink(result);
+  }
+  BOOST_CHECK_EQUAL(result, true);
+
+  tpm.deleteKey(ecKeyName);
+  BOOST_CHECK_EQUAL(tpm.hasKey(ecKeyName), false);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(ImportExport, T, TestBackEnds)
+{
+  const std::string privKeyPkcs1 =
+    "MIIEpAIBAAKCAQEAw0WM1/WhAxyLtEqsiAJgWDZWuzkYpeYVdeeZcqRZzzfRgBQT\n"
+    "sNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobws3iigohnM9yTK+KKiayPhIAm/+5H\n"
+    "GT6SgFJhYhqo1/upWdueojil6RP4/AgavHhopxlAVbk6G9VdVnlQcQ5Zv0OcGi73\n"
+    "c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9gZwIL5PuE9BiO6I39cL9z7EK1SfZh\n"
+    "OWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA9rH58ynaAix0tcR/nBMRLUX+e3rU\n"
+    "RHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0ywIDAQABAoIBADQkckOIl4IZMUTn\n"
+    "W8LFv6xOdkJwMKC8G6bsPRFbyY+HvC2TLt7epSvfS+f4AcYWaOPcDu2E49vt2sNr\n"
+    "cASly8hgwiRRAB3dHH9vcsboiTo8bi2RFvMqvjv9w3tK2yMxVDtmZamzrrnaV3YV\n"
+    "Q+5nyKo2F/PMDjQ4eUAKDOzjhBuKHsZBTFnA1MFNI+UKj5X4Yp64DFmKlxTX/U2b\n"
+    "wzVywo5hzx2Uhw51jmoLls4YUvMJXD0wW5ZtYRuPogXvXb/of9ef/20/wU11WFKg\n"
+    "Xb4gfR8zUXaXS1sXcnVm3+24vIs9dApUwykuoyjOqxWqcHRec2QT2FxVGkFEraze\n"
+    "CPa4rMECgYEA5Y8CywomIcTgerFGFCeMHJr8nQGqY2V/owFb3k9maczPnC9p4a9R\n"
+    "c5szLxA9FMYFxurQZMBWSEG2JS1HR2mnjigx8UKjYML/A+rvvjZOMe4M6Sy2ggh4\n"
+    "SkLZKpWTzjTe07ByM/j5v/SjNZhWAG7sw4/LmPGRQkwJv+KZhGojuOkCgYEA2cOF\n"
+    "T6cJRv6kvzTz9S0COZOVm+euJh/BXp7oAsAmbNfOpckPMzqHXy8/wpdKl6AAcB57\n"
+    "OuztlNfV1D7qvbz7JuRlYwQ0cEfBgbZPcz1p18HHDXhwn57ZPb8G33Yh9Omg0HNA\n"
+    "Imb4LsVuSqxA6NwSj7cpRekgTedrhLFPJ+Ydb5MCgYEAsM3Q7OjILcIg0t6uht9e\n"
+    "vrlwTsz1mtCV2co2I6crzdj9HeI2vqf1KAElDt6G7PUHhglcr/yjd8uEqmWRPKNX\n"
+    "ddnnfVZB10jYeP/93pac6z/Zmc3iU4yKeUe7U10ZFf0KkiiYDQd59CpLef/2XScS\n"
+    "HB0oRofnxRQjfjLc4muNT+ECgYEAlcDk06MOOTly+F8lCc1bA1dgAmgwFd2usDBd\n"
+    "Y07a3e0HGnGLN3Kfl7C5i0tZq64HvxLnMd2vgLVxQlXGPpdQrC1TH+XLXg+qnlZO\n"
+    "ivSH7i0/gx75bHvj75eH1XK65V8pDVDEoSPottllAIs21CxLw3N1ObOZWJm2EfmR\n"
+    "cuHICmsCgYAtFJ1idqMoHxES3mlRpf2JxyQudP3SCm2WpGmqVzhRYInqeatY5sUd\n"
+    "lPLHm/p77RT7EyxQHTlwn8FJPuM/4ZH1rQd/vB+Y8qAtYJCexDMsbvLW+Js+VOvk\n"
+    "jweEC0nrcL31j9mF0vz5E6tfRu4hhJ6L4yfWs0gSejskeVB/w8QY4g==\n";
+  const std::string password("password");
+  const std::string wrongPassword("wrong");
+
+  T wrapper;
+  BackEnd& tpm = wrapper.getTpm();
+
+  Name keyName("/Test/KeyName/KEY/1");
+  tpm.deleteKey(keyName);
+  BOOST_REQUIRE_EQUAL(tpm.hasKey(keyName), false);
+
+  transform::PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(privKeyPkcs1.data()), privKeyPkcs1.size());
+  OBufferStream os;
+  sKey.savePkcs8(os, password.data(), password.size());
+  auto pkcs8 = os.buf();
+
+  // import with wrong password
+  BOOST_CHECK_THROW(tpm.importKey(keyName, pkcs8->data(), pkcs8->size(), wrongPassword.data(), wrongPassword.size()),
+                    BackEnd::Error);
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), false);
+
+  // import with correct password
+  tpm.importKey(keyName, pkcs8->data(), pkcs8->size(), password.data(), password.size());
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), true);
+
+  // import already present key
+  BOOST_CHECK_THROW(tpm.importKey(keyName, pkcs8->data(), pkcs8->size(), password.data(), password.size()),
+                    BackEnd::Error);
+
+  // test derivePublicKey with the imported key
+  auto keyHdl = tpm.getKeyHandle(keyName);
+  auto pubKey = keyHdl->derivePublicKey();
+  BOOST_CHECK(pubKey != nullptr);
+
+  // export
+  auto exportedKey = tpm.exportKey(keyName, password.data(), password.size());
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), true);
+
+  transform::PrivateKey sKey2;
+  sKey2.loadPkcs8(exportedKey->data(), exportedKey->size(), password.data(), password.size());
+  OBufferStream os2;
+  sKey.savePkcs1Base64(os2);
+  auto pkcs1 = os2.buf();
+
+  // verify that the exported key is identical to the key that was imported
+  BOOST_CHECK_EQUAL_COLLECTIONS(privKeyPkcs1.begin(), privKeyPkcs1.end(),
+                                pkcs1->begin(), pkcs1->end());
+
+  // export nonexistent key
+  tpm.deleteKey(keyName);
+  BOOST_CHECK_EQUAL(tpm.hasKey(keyName), false);
+  BOOST_CHECK_THROW(tpm.exportKey(keyName, password.data(), password.size()), BackEnd::Error);
+}
+
+BOOST_AUTO_TEST_CASE(RandomKeyId)
+{
+  BackEndWrapperMem wrapper;
+  BackEnd& tpm = wrapper.getTpm();
+  Name identity("/Test/KeyName");
+
+  std::set<Name> keyNames;
+  for (int i = 0; i < 100; i++) {
+    auto key = tpm.createKey(identity, RsaKeyParams());
+    Name keyName = key->getKeyName();
+    BOOST_CHECK(keyNames.insert(keyName).second);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBackEnd
+BOOST_AUTO_TEST_SUITE_END() // Tpm
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace tpm
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform.t.cpp b/tests/unit/security/transform.t.cpp
new file mode 100644
index 0000000..1d55464
--- /dev/null
+++ b/tests/unit/security/transform.t.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestTransform)
+
+BOOST_AUTO_TEST_CASE(SymbolVisibility)
+{
+  transform::BufferSource* bufferSource = nullptr;
+  BOOST_CHECK(bufferSource == nullptr);
+
+  transform::StreamSource* streamSource = nullptr;
+  BOOST_CHECK(streamSource == nullptr);
+
+  transform::StepSource* stepSource = nullptr;
+  BOOST_CHECK(stepSource == nullptr);
+
+  transform::BoolSink* boolSink = nullptr;
+  BOOST_CHECK(boolSink == nullptr);
+
+  transform::StreamSink* streamSink = nullptr;
+  BOOST_CHECK(streamSink == nullptr);
+
+  transform::HexEncode* hexEncode = nullptr;
+  BOOST_CHECK(hexEncode == nullptr);
+
+  transform::StripSpace* stripSpace = nullptr;
+  BOOST_CHECK(stripSpace == nullptr);
+
+  transform::HexDecode* hexDecode = nullptr;
+  BOOST_CHECK(hexDecode == nullptr);
+
+  transform::Base64Encode* base64Encode = nullptr;
+  BOOST_CHECK(base64Encode == nullptr);
+
+  transform::Base64Decode* base64Decode = nullptr;
+  BOOST_CHECK(base64Decode == nullptr);
+
+  transform::DigestFilter* digestFilter = nullptr;
+  BOOST_CHECK(digestFilter == nullptr);
+
+  transform::HmacFilter* hmacFilter = nullptr;
+  BOOST_CHECK(hmacFilter == nullptr);
+
+  transform::BlockCipher* blockCipher = nullptr;
+  BOOST_CHECK(blockCipher == nullptr);
+
+  transform::SignerFilter* signerFilter = nullptr;
+  BOOST_CHECK(signerFilter == nullptr);
+
+  transform::VerifierFilter* verifierFilter = nullptr;
+  BOOST_CHECK(verifierFilter == nullptr);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTransform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/base64-decode.t.cpp b/tests/unit/security/transform/base64-decode.t.cpp
new file mode 100644
index 0000000..ecaa17c
--- /dev/null
+++ b/tests/unit/security/transform/base64-decode.t.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/base64-decode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBase64Decode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  std::string in =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=\n";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> base64Decode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(NoNewLine)
+{
+  std::string in =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> base64Decode(false) >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  std::string in =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+  OBufferStream os;
+  StepSource source;
+  source >> base64Decode() >> streamSink(os);
+  source.write(input, 65);       // complete line with "\n"
+  source.write(input + 65, 64);  // complete line without "\n"
+  source.write(input + 129, 1);  // single "\n"
+  source.write(input + 130, 35); // front of a line
+  source.write(input + 165, 30); // end of a line with "\n"
+  source.write(input + 195, 25); // front of a line
+  source.write(input + 220, 20); // middle of a line
+  source.write(input + 240, 19); // end of a line without "\n"
+  source.write(input + 259, 101); // "\n" plus one and half line
+  source.write(input + 360, 65); // end of a line plus front of another line
+  source.write(input + 425, 95); // remaining
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> base64Decode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBase64Decode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/base64-encode.t.cpp b/tests/unit/security/transform/base64-encode.t.cpp
new file mode 100644
index 0000000..d52ac0b
--- /dev/null
+++ b/tests/unit/security/transform/base64-encode.t.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/base64-encode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBase64Encode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=\n";
+
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> base64Encode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(NoNewLine)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=";
+
+  OBufferStream os;
+  BufferSource(in, sizeof(in)) >> base64Encode(false) >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n";
+
+  OBufferStream os;
+  StepSource source;
+  source >> base64Encode() >> streamSink(os);
+  source.write(in, 64); // complete chunk
+  source.write(in + 64, 32); // first half of a chunk
+  source.write(in + 96, 32); // second half of a chunk
+  source.write(in + 128, 24); // front of a chunk
+  source.write(in + 152, 20); // middle of a chunk
+  source.write(in + 172, 20); // end of a chunk
+  source.write(in + 192, 63); // odd number of bytes
+  source.write(in + 255, 85); // one and half chunk
+  source.write(in + 340, 44); // remaining part
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> base64Encode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBase64Encode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/block-cipher.t.cpp b/tests/unit/security/transform/block-cipher.t.cpp
new file mode 100644
index 0000000..379093d
--- /dev/null
+++ b/tests/unit/security/transform/block-cipher.t.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/block-cipher.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBlockCipher)
+
+BOOST_AUTO_TEST_CASE(AesCbc)
+{
+  const uint8_t key[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+  const uint8_t iv[] = {
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
+  };
+  const uint8_t plainText[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+  //
+  // You can use the following shell one-liner to calculate the ciphertext:
+  //   echo ${plaintext} | xxd -p -r | openssl enc -aes-128-cbc -K ${key} -iv ${iv} | xxd -i
+  //
+  const uint8_t cipherText[] = {
+    0x07, 0x4d, 0x32, 0x68, 0xc3, 0x40, 0x64, 0x43,
+    0x1e, 0x66, 0x4c, 0x25, 0x66, 0x42, 0x0f, 0x59,
+    0x0a, 0x51, 0x19, 0x07, 0x67, 0x5c, 0x0e, 0xfa,
+    0xa6, 0x8c, 0xbb, 0xaf, 0xfd, 0xea, 0x47, 0xd4,
+    0xc7, 0x2c, 0x12, 0x34, 0x79, 0xde, 0xec, 0xc8,
+    0x75, 0x33, 0x8f, 0x6b, 0xd6, 0x55, 0xf3, 0xfa
+  };
+
+  // encrypt
+  OBufferStream os;
+  bufferSource(plainText, sizeof(plainText)) >>
+    blockCipher(BlockCipherAlgorithm::AES_CBC, CipherOperator::ENCRYPT,
+                key, sizeof(key), iv, sizeof(iv)) >> streamSink(os);
+
+  auto buf = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(cipherText, cipherText + sizeof(cipherText),
+                                buf->begin(), buf->end());
+
+  // decrypt
+  OBufferStream os2;
+  bufferSource(cipherText, sizeof(cipherText)) >>
+    blockCipher(BlockCipherAlgorithm::AES_CBC, CipherOperator::DECRYPT,
+                key, sizeof(key), iv, sizeof(iv)) >> streamSink(os2);
+
+  auto buf2 = os2.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(plainText, plainText + sizeof(plainText),
+                                buf2->begin(), buf2->end());
+
+  // invalid key length
+  const uint8_t badKey[] = {0x00, 0x01, 0x02, 0x03};
+  BOOST_CHECK_THROW(BlockCipher(BlockCipherAlgorithm::AES_CBC, CipherOperator::ENCRYPT,
+                                badKey, sizeof(badKey), iv, sizeof(iv)), Error);
+
+  // wrong iv length
+  const uint8_t badIv[] = {0x00, 0x01, 0x02, 0x03};
+  BOOST_CHECK_THROW(BlockCipher(BlockCipherAlgorithm::AES_CBC, CipherOperator::ENCRYPT,
+                                key, sizeof(key), badIv, sizeof(badIv)), Error);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidAlgorithm)
+{
+  BOOST_CHECK_THROW(BlockCipher(BlockCipherAlgorithm::NONE, CipherOperator::ENCRYPT,
+                                nullptr, 0, nullptr, 0), Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBlockCipher
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/bool-sink.t.cpp b/tests/unit/security/transform/bool-sink.t.cpp
new file mode 100644
index 0000000..2ce633f
--- /dev/null
+++ b/tests/unit/security/transform/bool-sink.t.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/bool-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBoolSink)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in1[] = {0x00, 0x01};
+  bool value1 = true;
+  BoolSink sink1(value1);
+  BOOST_CHECK_EQUAL(sink1.write(in1, 1), 1);
+  BOOST_CHECK_EQUAL(sink1.write(in1 + 1, 1), 1);
+  sink1.end();
+  BOOST_CHECK_EQUAL(value1, false);
+  BOOST_CHECK_THROW(sink1.write(in1 + 1, 1), transform::Error);
+
+  uint8_t in2[] = {0x01, 0x00};
+  bool value2 = false;
+  BoolSink sink2(value2);
+  BOOST_CHECK_EQUAL(sink2.write(in2, 1), 1);
+  BOOST_CHECK_EQUAL(sink2.write(in2 + 1, 1), 1);
+  sink2.end();
+  BOOST_CHECK_EQUAL(value2, true);
+  BOOST_CHECK_THROW(sink2.write(in2 + 1, 1), transform::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBoolSink
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/buffer-source.t.cpp b/tests/unit/security/transform/buffer-source.t.cpp
new file mode 100644
index 0000000..cb7e7f4
--- /dev/null
+++ b/tests/unit/security/transform/buffer-source.t.cpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBufferSource)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in[16] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::ostringstream os1;
+  bufferSource(in, sizeof(in)) >> streamSink(os1);
+  std::string out1 = os1.str();
+  BOOST_CHECK_EQUAL_COLLECTIONS(in, in + sizeof(in), out1.begin(), out1.end());
+
+  std::string in2 =
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567";
+
+  std::ostringstream os2;
+  bufferSource(in2) >> streamSink(os2);
+  std::string out2 = os2.str();
+  BOOST_CHECK_EQUAL_COLLECTIONS(in2.begin(), in2.end(), out2.begin(), out2.end());
+
+  Buffer in3(in, sizeof(in));
+  std::ostringstream os3;
+  bufferSource(in3) >> streamSink(os3);
+  std::string out3 = os3.str();
+  BOOST_CHECK_EQUAL_COLLECTIONS(in3.begin(), in3.end(), out3.begin(), out3.end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBufferSource
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/digest-filter.t.cpp b/tests/unit/security/transform/digest-filter.t.cpp
new file mode 100644
index 0000000..5c0aaca
--- /dev/null
+++ b/tests/unit/security/transform/digest-filter.t.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/digest-filter.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/detail/openssl.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestDigestFilter)
+
+static const uint8_t in[] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+static const uint8_t out[] = {
+  0x3e, 0x14, 0xfd, 0x66, 0x9a, 0x79, 0x80, 0x65,
+  0xc4, 0x0d, 0x61, 0xf8, 0x6a, 0xc7, 0x98, 0x29,
+  0xc0, 0x6b, 0x90, 0x8f, 0xbb, 0x19, 0xa0, 0x85,
+  0xf7, 0xfa, 0x7b, 0x2d, 0xd6, 0x8c, 0xd5, 0xa3
+};
+
+BOOST_AUTO_TEST_CASE(BufferInput)
+{
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> digestFilter(DigestAlgorithm::SHA256) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepInput)
+{
+  StepSource source;
+  OBufferStream os;
+  source >> digestFilter(DigestAlgorithm::SHA256) >> streamSink(os);
+  source.write(in, 32);
+  source.write(in + 32, 1);
+  source.write(in + 33, 2);
+  source.write(in + 35, 3);
+  source.write(in + 38, 26);
+  source.write(in + 64, 64);
+  source.end();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmNone)
+{
+  BOOST_CHECK_THROW(DigestFilter{DigestAlgorithm::NONE}, Error);
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha224)
+{
+  const uint8_t out[] = {
+    0xd1, 0x4a, 0x02, 0x8c, 0x2a, 0x3a, 0x2b, 0xc9, 0x47, 0x61, 0x02, 0xbb, 0x28, 0x82, 0x34, 0xc4,
+    0x15, 0xa2, 0xb0, 0x1f, 0x82, 0x8e, 0xa6, 0x2a, 0xc5, 0xb3, 0xe4, 0x2f,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA224) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha256)
+{
+  const uint8_t out[] = {
+    0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
+    0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA256) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha384)
+{
+  const uint8_t out[] = {
+    0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a,
+    0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda,
+    0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA384) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha512)
+{
+  const uint8_t out[] = {
+    0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07,
+    0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce,
+    0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f,
+    0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA512) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(OPENSSL_NO_BLAKE2)
+BOOST_AUTO_TEST_CASE(AlgorithmBlake2b_512)
+{
+  const uint8_t out[] = {
+    0x78, 0x6a, 0x02, 0xf7, 0x42, 0x01, 0x59, 0x03, 0xc6, 0xc6, 0xfd, 0x85, 0x25, 0x52, 0xd2, 0x72,
+    0x91, 0x2f, 0x47, 0x40, 0xe1, 0x58, 0x47, 0x61, 0x8a, 0x86, 0xe2, 0x17, 0xf7, 0x1f, 0x54, 0x19,
+    0xd2, 0x5e, 0x10, 0x31, 0xaf, 0xee, 0x58, 0x53, 0x13, 0x89, 0x64, 0x44, 0x93, 0x4e, 0xb0, 0x4b,
+    0x90, 0x3a, 0x68, 0x5b, 0x14, 0x48, 0xb7, 0x55, 0xd5, 0x6f, 0x70, 0x1a, 0xfe, 0x9b, 0xe2, 0xce,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::BLAKE2B_512) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmBlake2s_256)
+{
+  const uint8_t out[] = {
+    0x69, 0x21, 0x7a, 0x30, 0x79, 0x90, 0x80, 0x94, 0xe1, 0x11, 0x21, 0xd0, 0x42, 0x35, 0x4a, 0x7c,
+    0x1f, 0x55, 0xb6, 0x48, 0x2c, 0xa1, 0xa5, 0x1e, 0x1b, 0x25, 0x0d, 0xfd, 0x1e, 0xd0, 0xee, 0xf9,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::BLAKE2S_256) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+#endif // OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(OPENSSL_NO_BLAKE2)
+
+#if OPENSSL_VERSION_NUMBER >= 0x10101001L
+BOOST_AUTO_TEST_CASE(AlgorithmSha3_224)
+{
+  const uint8_t out[] = {
+    0x6b, 0x4e, 0x03, 0x42, 0x36, 0x67, 0xdb, 0xb7, 0x3b, 0x6e, 0x15, 0x45, 0x4f, 0x0e, 0xb1, 0xab,
+    0xd4, 0x59, 0x7f, 0x9a, 0x1b, 0x07, 0x8e, 0x3f, 0x5b, 0x5a, 0x6b, 0xc7,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA3_224) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha3_256)
+{
+  const uint8_t out[] = {
+    0xa7, 0xff, 0xc6, 0xf8, 0xbf, 0x1e, 0xd7, 0x66, 0x51, 0xc1, 0x47, 0x56, 0xa0, 0x61, 0xd6, 0x62,
+    0xf5, 0x80, 0xff, 0x4d, 0xe4, 0x3b, 0x49, 0xfa, 0x82, 0xd8, 0x0a, 0x4b, 0x80, 0xf8, 0x43, 0x4a,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA3_256) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha3_384)
+{
+  const uint8_t out[] = {
+    0x0c, 0x63, 0xa7, 0x5b, 0x84, 0x5e, 0x4f, 0x7d, 0x01, 0x10, 0x7d, 0x85, 0x2e, 0x4c, 0x24, 0x85,
+    0xc5, 0x1a, 0x50, 0xaa, 0xaa, 0x94, 0xfc, 0x61, 0x99, 0x5e, 0x71, 0xbb, 0xee, 0x98, 0x3a, 0x2a,
+    0xc3, 0x71, 0x38, 0x31, 0x26, 0x4a, 0xdb, 0x47, 0xfb, 0x6b, 0xd1, 0xe0, 0x58, 0xd5, 0xf0, 0x04,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA3_384) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(AlgorithmSha3_512)
+{
+  const uint8_t out[] = {
+    0xa6, 0x9f, 0x73, 0xcc, 0xa2, 0x3a, 0x9a, 0xc5, 0xc8, 0xb5, 0x67, 0xdc, 0x18, 0x5a, 0x75, 0x6e,
+    0x97, 0xc9, 0x82, 0x16, 0x4f, 0xe2, 0x58, 0x59, 0xe0, 0xd1, 0xdc, 0xc1, 0x47, 0x5c, 0x80, 0xa6,
+    0x15, 0xb2, 0x12, 0x3a, 0xf1, 0xf5, 0xf9, 0x4c, 0x11, 0xe3, 0xe9, 0x40, 0x2c, 0x3a, 0xc5, 0x58,
+    0xf5, 0x00, 0x19, 0x9d, 0x95, 0xb6, 0xd3, 0xe3, 0x01, 0x75, 0x85, 0x86, 0x28, 0x1d, 0xcd, 0x26,
+  };
+  OBufferStream os;
+  bufferSource("") >> digestFilter(DigestAlgorithm::SHA3_512) >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), os.buf()->begin(), os.buf()->end());
+}
+#endif // OPENSSL_VERSION_NUMBER >= 0x10101001L
+
+BOOST_AUTO_TEST_SUITE_END() // TestDigestFilter
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/hex-decode.t.cpp b/tests/unit/security/transform/hex-decode.t.cpp
new file mode 100644
index 0000000..3c05ab4
--- /dev/null
+++ b/tests/unit/security/transform/hex-decode.t.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/hex-decode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestHexDecode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  std::string in =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> hexDecode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(UpperCase)
+{
+  std::string in =
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> hexDecode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(MixCase)
+{
+  std::string in =
+    "000102030405060708090a0b0c0d0e0f"
+    "101112131415161718191a1b1c1d1e1f"
+    "202122232425262728292a2b2c2d2e2f"
+    "303132333435363738393a3b3c3d3e3f"
+    "404142434445464748494a4b4c4d4e4f"
+    "505152535455565758595a5b5c5d5e5f"
+    "606162636465666768696a6b6c6d6e6f"
+    "707172737475767778797a7b7c7d7e7f"
+    "808182838485868788898a8b8c8d8e8f"
+    "909192939495969798999a9b9c9d9e9f"
+    "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+    "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+    "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+    "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+    "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+    "000102030405060708090A0B0C0D0E0F"
+    "101112131415161718191A1B1C1D1E1F"
+    "202122232425262728292A2B2C2D2E2F"
+    "303132333435363738393A3B3C3D3E3F"
+    "404142434445464748494A4B4C4D4E4F"
+    "505152535455565758595A5B5C5D5E5F"
+    "606162636465666768696A6B6C6D6E6F"
+    "707172737475767778797A7B7C7D7E7F"
+    "808182838485868788898A8B8C8D8E8F"
+    "909192939495969798999A9B9C9D9E9F"
+    "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"
+    "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"
+    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"
+    "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"
+    "E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"
+    "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"
+    "aaabacadaeafaAaBaCaD"
+    "babbbcbdbebfbAbBbCbD"
+    "cacbcccdcecfcAcBcCcD"
+    "dadbdcdddedfdAdBdCdD"
+    "eaebecedeeefeAeBeCeD"
+    "fafbfcfdfefffAfBfCfD"
+    "AaAbAcAdAeAfAAABACAD"
+    "BaBbBcBdBeBfBABBBCBD"
+    "CaCbCcCdCeCfCACBCCCD"
+    "DaDbDcDdDeDfDADBDCDD";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+    0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xAA, 0xAB, 0xAC, 0xAD,
+    0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xBA, 0xBB, 0xBC, 0xBD,
+    0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xCA, 0xCB, 0xCC, 0xCD,
+    0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xDA, 0xDB, 0xDC, 0xDD,
+    0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xEA, 0xEB, 0xEC, 0xED,
+    0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFA, 0xFB, 0xFC, 0xFD,
+    0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xAA, 0xAB, 0xAC, 0xAD,
+    0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xBA, 0xBB, 0xBC, 0xBD,
+    0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xCA, 0xCB, 0xCC, 0xCD,
+    0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xDA, 0xDB, 0xDC, 0xDD
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> hexDecode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  std::string in =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+  OBufferStream os;
+  StepSource source;
+  source >> hexDecode() >> streamSink(os);
+  source.write(input, 128);       // complete chunk
+  source.write(input + 128, 64);  // first half of a chunk
+  source.write(input + 192, 64);  // second half of a chunk
+  source.write(input + 256, 127); // odd number of byets
+  source.write(input + 383, 192); // one and half chunk
+  source.write(input + 575, 193); // remaining part
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(OddByte)
+{
+  std::string in1 = "0001020304050";
+
+  OBufferStream os1;
+  BOOST_REQUIRE_THROW(bufferSource(in1) >> hexDecode() >> streamSink(os1), transform::Error);
+
+  std::string in2 = "0001020304xy";
+
+  OBufferStream os2;
+  BOOST_REQUIRE_THROW(bufferSource(in2) >> hexDecode() >> streamSink(os2), transform::Error);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> hexDecode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestHexDecode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/hex-encode.t.cpp b/tests/unit/security/transform/hex-encode.t.cpp
new file mode 100644
index 0000000..26f6321
--- /dev/null
+++ b/tests/unit/security/transform/hex-encode.t.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/hex-encode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestHexEncode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in[128] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> hexEncode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(UpperCase)
+{
+  uint8_t in[128] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F";
+
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> hexEncode(true) >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  OBufferStream os;
+  StepSource source;
+  source >> hexEncode() >> streamSink(os);
+  source.write(in, 64);       // complete chunk
+  source.write(in + 64, 32);  // first half of a chunk
+  source.write(in + 96, 32);  // second half of a chunk
+  source.write(in + 128, 20); // front of a chunk
+  source.write(in + 148, 20); // middle of a chunk
+  source.write(in + 168, 24); // end of a chunk
+  source.write(in + 192, 63); // odd number of bytes
+  source.write(in + 255, 85); // one and half chunk
+  source.write(in + 340, 44); // remaining part
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> hexEncode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestHexEncode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/hmac-filter.t.cpp b/tests/unit/security/transform/hmac-filter.t.cpp
new file mode 100644
index 0000000..1ed5e25
--- /dev/null
+++ b/tests/unit/security/transform/hmac-filter.t.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/hmac-filter.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestHmacFilter)
+
+static const uint8_t key[] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+static const uint8_t data[] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+};
+static const uint8_t digest[] = {
+  0x9f, 0x3a, 0xa2, 0x88, 0x26, 0xb3, 0x74, 0x85,
+  0xca, 0x05, 0x01, 0x4d, 0x71, 0x42, 0xb3, 0xea,
+  0x3f, 0xfb, 0xda, 0x5a, 0x35, 0xbf, 0xd2, 0x0f,
+  0x2f, 0x9c, 0x8f, 0xcc, 0x6d, 0x30, 0x48, 0x54
+};
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  OBufferStream os;
+  bufferSource(data, sizeof(data)) >> hmacFilter(DigestAlgorithm::SHA256, key, sizeof(key)) >> streamSink(os);
+
+  ConstBufferPtr buf = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(digest, digest + sizeof(digest), buf->begin(), buf->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> hmacFilter(DigestAlgorithm::SHA256, key, sizeof(key)) >> streamSink(os);
+  source.write(data, 1);
+  source.write(data + 1, 2);
+  source.write(data + 3, 3);
+  source.write(data + 6, 4);
+  source.write(data + 10, 5);
+  source.write(data + 15, 1);
+  source.end();
+
+  ConstBufferPtr buf = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(digest, digest + sizeof(digest), buf->begin(), buf->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  const uint8_t digest[] = {
+    0x07, 0xEF, 0xF8, 0xB3, 0x26, 0xB7, 0x79, 0x8C,
+    0x9C, 0xCF, 0xCB, 0xDB, 0xE5, 0x79, 0x48, 0x9A,
+    0xC7, 0x85, 0xA7, 0x99, 0x5A, 0x04, 0x61, 0x8B,
+    0x1A, 0x28, 0x13, 0xC2, 0x67, 0x44, 0x77, 0x7D
+  };
+
+  OBufferStream os;
+  StepSource source;
+  source >> hmacFilter(DigestAlgorithm::SHA256, key, sizeof(key)) >> streamSink(os);
+  source.end();
+
+  ConstBufferPtr buf = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(digest, digest + sizeof(digest), buf->begin(), buf->end());
+}
+
+BOOST_AUTO_TEST_CASE(InvalidAlgorithm)
+{
+  BOOST_CHECK_THROW(HmacFilter(DigestAlgorithm::NONE, key, sizeof(key)), Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestHmacFilter
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/private-key.t.cpp b/tests/unit/security/transform/private-key.t.cpp
new file mode 100644
index 0000000..ee956f9
--- /dev/null
+++ b/tests/unit/security/transform/private-key.t.cpp
@@ -0,0 +1,401 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/private-key.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/key-params.hpp"
+#include "security/transform.hpp"
+
+#include "boost-test.hpp"
+#include <boost/mpl/vector.hpp>
+
+#include <sstream>
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestPrivateKey)
+
+struct RsaKeyTestData
+{
+  const std::string privateKeyPkcs1 =
+      "MIIEpAIBAAKCAQEAw0WM1/WhAxyLtEqsiAJgWDZWuzkYpeYVdeeZcqRZzzfRgBQT\n"
+      "sNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobws3iigohnM9yTK+KKiayPhIAm/+5H\n"
+      "GT6SgFJhYhqo1/upWdueojil6RP4/AgavHhopxlAVbk6G9VdVnlQcQ5Zv0OcGi73\n"
+      "c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9gZwIL5PuE9BiO6I39cL9z7EK1SfZh\n"
+      "OWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA9rH58ynaAix0tcR/nBMRLUX+e3rU\n"
+      "RHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0ywIDAQABAoIBADQkckOIl4IZMUTn\n"
+      "W8LFv6xOdkJwMKC8G6bsPRFbyY+HvC2TLt7epSvfS+f4AcYWaOPcDu2E49vt2sNr\n"
+      "cASly8hgwiRRAB3dHH9vcsboiTo8bi2RFvMqvjv9w3tK2yMxVDtmZamzrrnaV3YV\n"
+      "Q+5nyKo2F/PMDjQ4eUAKDOzjhBuKHsZBTFnA1MFNI+UKj5X4Yp64DFmKlxTX/U2b\n"
+      "wzVywo5hzx2Uhw51jmoLls4YUvMJXD0wW5ZtYRuPogXvXb/of9ef/20/wU11WFKg\n"
+      "Xb4gfR8zUXaXS1sXcnVm3+24vIs9dApUwykuoyjOqxWqcHRec2QT2FxVGkFEraze\n"
+      "CPa4rMECgYEA5Y8CywomIcTgerFGFCeMHJr8nQGqY2V/owFb3k9maczPnC9p4a9R\n"
+      "c5szLxA9FMYFxurQZMBWSEG2JS1HR2mnjigx8UKjYML/A+rvvjZOMe4M6Sy2ggh4\n"
+      "SkLZKpWTzjTe07ByM/j5v/SjNZhWAG7sw4/LmPGRQkwJv+KZhGojuOkCgYEA2cOF\n"
+      "T6cJRv6kvzTz9S0COZOVm+euJh/BXp7oAsAmbNfOpckPMzqHXy8/wpdKl6AAcB57\n"
+      "OuztlNfV1D7qvbz7JuRlYwQ0cEfBgbZPcz1p18HHDXhwn57ZPb8G33Yh9Omg0HNA\n"
+      "Imb4LsVuSqxA6NwSj7cpRekgTedrhLFPJ+Ydb5MCgYEAsM3Q7OjILcIg0t6uht9e\n"
+      "vrlwTsz1mtCV2co2I6crzdj9HeI2vqf1KAElDt6G7PUHhglcr/yjd8uEqmWRPKNX\n"
+      "ddnnfVZB10jYeP/93pac6z/Zmc3iU4yKeUe7U10ZFf0KkiiYDQd59CpLef/2XScS\n"
+      "HB0oRofnxRQjfjLc4muNT+ECgYEAlcDk06MOOTly+F8lCc1bA1dgAmgwFd2usDBd\n"
+      "Y07a3e0HGnGLN3Kfl7C5i0tZq64HvxLnMd2vgLVxQlXGPpdQrC1TH+XLXg+qnlZO\n"
+      "ivSH7i0/gx75bHvj75eH1XK65V8pDVDEoSPottllAIs21CxLw3N1ObOZWJm2EfmR\n"
+      "cuHICmsCgYAtFJ1idqMoHxES3mlRpf2JxyQudP3SCm2WpGmqVzhRYInqeatY5sUd\n"
+      "lPLHm/p77RT7EyxQHTlwn8FJPuM/4ZH1rQd/vB+Y8qAtYJCexDMsbvLW+Js+VOvk\n"
+      "jweEC0nrcL31j9mF0vz5E6tfRu4hhJ6L4yfWs0gSejskeVB/w8QY4g==\n";
+  const std::string privateKeyPkcs8 =
+      "MIIFCzA9BgkqhkiG9w0BBQ0wMDAbBgkqhkiG9w0BBQwwDgQIOKYJXvB6p8kCAggA\n"
+      "MBEGBSsOAwIHBAiQgMK8kQXTyASCBMjeNiKYYw5/yHgs9BfSGrpqvV0LkkgMQNUW\n"
+      "R4ZY8fuNjZynd+PxDuw2pyrv1Yv3jc+tupwUehZEzYOnGd53wQAuLO+Z0TBgRFN7\n"
+      "Lhk+AxlT7hu0xaB3ZpJ/uvWpgEJHsq/aB/GYgyzXcQo2AiqzERVpMCWJVmE1L977\n"
+      "CHwJmLm5mxclVLYp1UK5lkIBFu/M4nPavmNmYNUU1LOrXRo56TlJ2kUp8gQyQI1P\n"
+      "VPxi4chmlsr/OnQ2d1eZN+euFm0CS+yP+LFgI9ZqdyH1w+J43SXdHDzauVcZp7oa\n"
+      "Kw24OrhHfolLAnQIECXEJYeT7tZmhC4O9V6B18PFVyxWnEU4eFNpFE8kYSmm8Um2\n"
+      "buvDKI71q43hm23moYT9uIM1f4M8UkoOliJGrlf4xgEcmDuokEX01PdOq1gc4nvG\n"
+      "0DCwDI9cOsyn8cxhk9UVtFgzuG/seuznxIv1F5H0hzYOyloStXxRisJES0kgByBt\n"
+      "FFTfyoFKRrmCjRIygwVKUSkSDR0DlQS5ZLvQyIswnSQFwxAHqfvoSL4dB9UAIAQ+\n"
+      "ALVF1maaHgptbL6Ifqf0GFCv0hdNCVNDNCdy8R+S6nEYE+YdYSIdT1L88KD5PjU3\n"
+      "YY/CMnxhTncMaT4acPO1UUYuSGRZ/JL6E0ihoqIU+bqUgLSHNzhPySPfN9uqN61Y\n"
+      "HFBtxeEPWKU0f/JPkRBMmZdMI1/OVmA3QHSRBydI+CQN8no2gZRFoVbHTkG8IMpE\n"
+      "1fiDJpwFkpzIv/JPiTSE7DeBH5NJk1bgu7TcuZfa4unyAqss0UuLnXzS06TppkUj\n"
+      "QGft0g8VPW56eli6B4xrSzzuvAdbrxsVfxdmtHPyYxLb3/UG1g4x/H/yULhx7x9P\n"
+      "iI6cw6JUE+8bwJV2ZIlHXXHO+wUp/gCFJ6MHo9wkR1QvnHP2ClJAzBm9OvYnUx2Y\n"
+      "SX0HxEowW8BkhxOF184LEmxeua0yyZUqCdrYmErp7x9EY/LhD1zBwH8OGRa0qzmR\n"
+      "VKxAPKihkb9OgxcUKbvKePx3k2cQ7fbCUspGPm4Kn1zwMgRAZ4fz/o8Lnwc8MSY3\n"
+      "lPWnmLTFu420SRH2g9N0o/r195hiZ5cc+KfF4pwZWKbEbKFk/UfXA9vmOi7BBtDJ\n"
+      "RWshOINhzMU6Ij3KuaEpHni1HoHjw0SQ97ow2x/aB8k2QC28tbsa49lD2KKJku6b\n"
+      "2Or89adwFKqMgS2IXfXMXs/iG5EFLYN6r8e40Dn5f1vJfRLJl03XByIfT2n92pw3\n"
+      "fP7muOIKLUsEKjOrmn94NwMlfeW13oQHEH2KjPOWFS/tyJHDdVU+of4COH5yg59a\n"
+      "TZqFkOTGeliE1O+6sfF9fRuVxFUF3D8Hpr0JIjdc6+3RgIlGsXc8BwiSjDSI2XW+\n"
+      "vo75/2zPU9t8OeXEIJk2CQGyqLwUJ6dyi/yDRrvZAgjrUvbpcxydnBAHrLbLUGXJ\n"
+      "aEHH2tjEtnTqVyTchr1yHoupcFOCkA0dAA66XqwcssQxJiMGrWTpCbgd9mrTXQaZ\n"
+      "U7afFN1jpO78tgBQUUpImXdHLLsqdN5tefqjileZGZ9x3/C6TNAfDwYJdsicNNn5\n"
+      "y+JVsbltfLWlJxb9teb3dtQiFlJ7ofprLJnJVqI/Js8lozY+KaxV2vtbZkcD4dM=\n";
+  const std::string publicKeyPkcs8 =
+      "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0WM1/WhAxyLtEqsiAJg\n"
+      "WDZWuzkYpeYVdeeZcqRZzzfRgBQTsNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobw\n"
+      "s3iigohnM9yTK+KKiayPhIAm/+5HGT6SgFJhYhqo1/upWdueojil6RP4/AgavHho\n"
+      "pxlAVbk6G9VdVnlQcQ5Zv0OcGi73c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9g\n"
+      "ZwIL5PuE9BiO6I39cL9z7EK1SfZhOWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA\n"
+      "9rH58ynaAix0tcR/nBMRLUX+e3rURHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0\n"
+      "ywIDAQAB\n";
+};
+
+struct EcKeyTestData
+{
+  const std::string privateKeyPkcs1 =
+      "MIIBaAIBAQQgRxwcbzK9RV6AHYFsDcykI86o3M/a1KlJn0z8PcLMBZOggfowgfcC\n"
+      "AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////\n"
+      "MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr\n"
+      "vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE\n"
+      "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W\n"
+      "K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8\n"
+      "YyVRAgEBoUQDQgAEaG4WJuDAt0QkEM4t29KDUdzkQlMPGrqWzkWhgt9OGnwc6O7A\n"
+      "ZLPSrDyhwyrKS7XLRXml5DisQ93RvByll32y8A==\n";
+  const std::string privateKeyPkcs8 =
+      "MIIBwzA9BgkqhkiG9w0BBQ0wMDAbBgkqhkiG9w0BBQwwDgQIVHkBzLGtDvICAggA\n"
+      "MBEGBSsOAwIHBAhk6g9eI3toNwSCAYDd+LWPDBTrKV7vUyxTvDbpUd0eXfh73DKA\n"
+      "MHkdHuVmhpmpBbsF9XvaFuL8J/1xi1Yl2XGw8j3WyrprD2YEhl/+zKjNbdTDJmNO\n"
+      "SlomuwWb5AVCJ9reT94zIXKCnexUcyBFS7ep+P4dwuef0VjzprjfmnAZHrP+u594\n"
+      "ELHpKwi0ZpQLtcJjjud13bn43vbXb+aU7jmPV5lU2XP8TxaQJiYIibNEh1Y3TZGr\n"
+      "akJormYvhaYbiZkKLHQ9AvQMEjhoIW5WCB3q+tKZUKTzcQpjNnf9FOTeKN3jk3Kd\n"
+      "2OmibPZcbMJdgCD/nRVn1cBo7Hjn3IMjgtszQHtEUphOQiAkOJUnKmy9MTYqtcNN\n"
+      "6cuFItbu4QvbVwailgdUjOYwIJCmIxExlPV0ohS24pFGsO03Yn7W8rBB9VWENYmG\n"
+      "HkZIbGsHv7O9Wy7fv+FJgZkjeti0807IsNXSJl8LUK0ZIhAR7OU8uONWMsbHdQnk\n"
+      "q1HB1ZKa52ugACl7g/DF9b7CoSAjFeE=\n";
+  const std::string publicKeyPkcs8 =
+      "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
+      "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
+      "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
+      "NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n"
+      "RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n"
+      "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGhuFibgwLdEJBDOLdvSg1Hc\n"
+      "5EJTDxq6ls5FoYLfThp8HOjuwGSz0qw8ocMqyku1y0V5peQ4rEPd0bwcpZd9svA=\n";
+};
+
+using KeyTestDataSets = boost::mpl::vector<RsaKeyTestData, EcKeyTestData>;
+
+static void
+checkPkcs8Encoding(ConstBufferPtr encoding, const std::string& password, ConstBufferPtr pkcs1)
+{
+  PrivateKey sKey;
+  sKey.loadPkcs8(encoding->data(), encoding->size(), password.c_str(), password.size());
+  OBufferStream os;
+  sKey.savePkcs1(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(pkcs1->begin(), pkcs1->end(),
+                                os.buf()->begin(), os.buf()->end());
+}
+
+static void
+checkPkcs8Base64Encoding(ConstBufferPtr encoding, const std::string& password, ConstBufferPtr pkcs1)
+{
+  OBufferStream os;
+  bufferSource(*encoding) >> base64Decode() >> streamSink(os);
+  checkPkcs8Encoding(os.buf(), password, pkcs1);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(SaveLoad, T, KeyTestDataSets)
+{
+  T dataSet;
+
+  const uint8_t* sKeyPkcs1Base64 = reinterpret_cast<const uint8_t*>(dataSet.privateKeyPkcs1.c_str());
+  size_t sKeyPkcs1Base64Len = dataSet.privateKeyPkcs1.size();
+  OBufferStream os;
+  bufferSource(sKeyPkcs1Base64, sKeyPkcs1Base64Len) >> base64Decode() >> streamSink(os);
+  ConstBufferPtr sKeyPkcs1Buf = os.buf();
+  const uint8_t* sKeyPkcs1 = sKeyPkcs1Buf->data();
+  size_t sKeyPkcs1Len = sKeyPkcs1Buf->size();
+
+  // load key in base64-encoded pkcs1 format
+  PrivateKey sKey;
+  BOOST_CHECK_NO_THROW(sKey.loadPkcs1Base64(sKeyPkcs1Base64, sKeyPkcs1Base64Len));
+
+  std::stringstream ss2(dataSet.privateKeyPkcs1);
+  PrivateKey sKey2;
+  BOOST_CHECK_NO_THROW(sKey2.loadPkcs1Base64(ss2));
+
+  // load key in pkcs1 format
+  PrivateKey sKey3;
+  BOOST_CHECK_NO_THROW(sKey3.loadPkcs1(sKeyPkcs1, sKeyPkcs1Len));
+
+  std::stringstream ss4;
+  ss4.write(reinterpret_cast<const char*>(sKeyPkcs1), sKeyPkcs1Len);
+  PrivateKey sKey4;
+  BOOST_CHECK_NO_THROW(sKey4.loadPkcs1(ss4));
+
+  // save key in base64-encoded pkcs1 format
+  OBufferStream os2;
+  BOOST_REQUIRE_NO_THROW(sKey.savePkcs1Base64(os2));
+  BOOST_CHECK_EQUAL_COLLECTIONS(sKeyPkcs1Base64, sKeyPkcs1Base64 + sKeyPkcs1Base64Len,
+                                os2.buf()->begin(), os2.buf()->end());
+
+  // save key in pkcs1 format
+  OBufferStream os3;
+  BOOST_REQUIRE_NO_THROW(sKey.savePkcs1(os3));
+  BOOST_CHECK_EQUAL_COLLECTIONS(sKeyPkcs1, sKeyPkcs1 + sKeyPkcs1Len,
+                                os3.buf()->begin(), os3.buf()->end());
+
+  const uint8_t* sKeyPkcs8Base64 = reinterpret_cast<const uint8_t*>(dataSet.privateKeyPkcs8.c_str());
+  size_t sKeyPkcs8Base64Len = dataSet.privateKeyPkcs8.size();
+  OBufferStream os4;
+  bufferSource(sKeyPkcs8Base64, sKeyPkcs8Base64Len) >> base64Decode() >> streamSink(os4);
+  const uint8_t* sKeyPkcs8 = os4.buf()->data();
+  size_t sKeyPkcs8Len = os4.buf()->size();
+
+  std::string password("password");
+  std::string wrongpw("wrongpw");
+  auto pwCallback = [&password] (char* buf, size_t size, int) -> int {
+    BOOST_REQUIRE_LE(password.size(), size);
+    std::copy(password.begin(), password.end(), buf);
+    return static_cast<int>(password.size());
+  };
+
+  // load key in base64-encoded pkcs8 format
+  PrivateKey sKey5;
+  BOOST_CHECK_NO_THROW(sKey5.loadPkcs8Base64(sKeyPkcs8Base64, sKeyPkcs8Base64Len,
+                                             password.c_str(), password.size()));
+
+  PrivateKey sKey6;
+  BOOST_CHECK_NO_THROW(sKey6.loadPkcs8Base64(sKeyPkcs8Base64, sKeyPkcs8Base64Len, pwCallback));
+
+  std::stringstream ss7(dataSet.privateKeyPkcs8);
+  PrivateKey sKey7;
+  BOOST_CHECK_NO_THROW(sKey7.loadPkcs8Base64(ss7, password.c_str(), password.size()));
+
+  std::stringstream ss8(dataSet.privateKeyPkcs8);
+  PrivateKey sKey8;
+  BOOST_CHECK_NO_THROW(sKey8.loadPkcs8Base64(ss8, pwCallback));
+
+  // load key in pkcs8 format
+  PrivateKey sKey9;
+  BOOST_CHECK_NO_THROW(sKey9.loadPkcs8(sKeyPkcs8, sKeyPkcs8Len, password.c_str(), password.size()));
+
+  PrivateKey sKey10;
+  BOOST_CHECK_NO_THROW(sKey10.loadPkcs8(sKeyPkcs8, sKeyPkcs8Len, pwCallback));
+
+  std::stringstream ss11;
+  ss11.write(reinterpret_cast<const char*>(sKeyPkcs8), sKeyPkcs8Len);
+  PrivateKey sKey11;
+  BOOST_CHECK_NO_THROW(sKey11.loadPkcs8(ss11, password.c_str(), password.size()));
+
+  std::stringstream ss12;
+  ss12.write(reinterpret_cast<const char*>(sKeyPkcs8), sKeyPkcs8Len);
+  PrivateKey sKey12;
+  BOOST_CHECK_NO_THROW(sKey12.loadPkcs8(ss12, pwCallback));
+
+  // load key using wrong password, Error is expected
+  PrivateKey sKey13;
+  BOOST_CHECK_THROW(sKey13.loadPkcs8Base64(sKeyPkcs8Base64, sKeyPkcs8Base64Len, wrongpw.c_str(), wrongpw.size()),
+                    PrivateKey::Error);
+
+  // save key in base64-encoded pkcs8 format
+  OBufferStream os14;
+  BOOST_REQUIRE_NO_THROW(sKey.savePkcs8Base64(os14, password.c_str(), password.size()));
+  checkPkcs8Base64Encoding(os14.buf(), password, sKeyPkcs1Buf);
+
+  OBufferStream os15;
+  BOOST_REQUIRE_NO_THROW(sKey.savePkcs8Base64(os15, pwCallback));
+  checkPkcs8Base64Encoding(os15.buf(), password, sKeyPkcs1Buf);
+
+  // save key in pkcs8 format
+  OBufferStream os16;
+  BOOST_REQUIRE_NO_THROW(sKey.savePkcs8(os16, password.c_str(), password.size()));
+  checkPkcs8Encoding(os16.buf(), password, sKeyPkcs1Buf);
+
+  OBufferStream os17;
+  BOOST_REQUIRE_NO_THROW(sKey.savePkcs8(os17, pwCallback));
+  checkPkcs8Encoding(os17.buf(), password, sKeyPkcs1Buf);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(DerivePublicKey, T, KeyTestDataSets)
+{
+  T dataSet;
+
+  const uint8_t* sKeyPkcs1Base64 = reinterpret_cast<const uint8_t*>(dataSet.privateKeyPkcs1.c_str());
+  size_t sKeyPkcs1Base64Len = dataSet.privateKeyPkcs1.size();
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(sKeyPkcs1Base64, sKeyPkcs1Base64Len);
+
+  // derive public key and compare
+  ConstBufferPtr pKeyBits = sKey.derivePublicKey();
+  OBufferStream os;
+  bufferSource(dataSet.publicKeyPkcs8) >> base64Decode() >> streamSink(os);
+  BOOST_CHECK_EQUAL_COLLECTIONS(pKeyBits->begin(), pKeyBits->end(),
+                                os.buf()->begin(), os.buf()->end());
+}
+
+BOOST_AUTO_TEST_CASE(RsaDecryption)
+{
+  RsaKeyTestData dataSet;
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(dataSet.privateKeyPkcs1.c_str()),
+                       dataSet.privateKeyPkcs1.size());
+  BOOST_CHECK_EQUAL(sKey.getKeyType(), KeyType::RSA);
+
+  const uint8_t plainText[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
+
+  const std::string cipherTextBase64 =
+    "i2XNpZ2JbLa4JmBTdDrGmsd4/0C+p+BSCpW3MuPBNe5uChQ0eRO1dvjTnEqwSECY\n"
+    "38en9JZwcyb0It/TSFNXHlq+Z1ZpffnjIJxQR9HcgwvwQJh6WRH0vu38tvGkGuNv\n"
+    "60Rdn85hqSy1CikmXCeWXL9yCqeqcP21R94G/T3FuA+c1FtFko8KOzCwvrTXMO6n\n"
+    "5PNsqlLXabSGr+jz4EwOsSCgPkiDf9U6tXoSPRA2/YvqFQdaiUXIVlomESvaqqZ8\n"
+    "FxPs2BON0lobM8gT+xdzbRKofp+rNjNK+5uWyeOnXJwzCszh17cdJl2BH1dZwaVD\n"
+    "PmTiSdeDQXZ94U5boDQ4Aw==\n";
+  OBufferStream os;
+  bufferSource(cipherTextBase64) >> base64Decode() >> streamSink(os);
+
+  auto decrypted = sKey.decrypt(os.buf()->data(), os.buf()->size());
+  BOOST_CHECK_EQUAL_COLLECTIONS(plainText, plainText + sizeof(plainText),
+                                decrypted->begin(), decrypted->end());
+}
+
+BOOST_AUTO_TEST_CASE(RsaEncryptDecrypt)
+{
+  RsaKeyTestData dataSet;
+
+  PublicKey pKey;
+  pKey.loadPkcs8Base64(reinterpret_cast<const uint8_t*>(dataSet.publicKeyPkcs8.c_str()),
+                       dataSet.publicKeyPkcs8.size());
+  BOOST_CHECK_EQUAL(pKey.getKeyType(), KeyType::RSA);
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(dataSet.privateKeyPkcs1.c_str()),
+                       dataSet.privateKeyPkcs1.size());
+  BOOST_CHECK_EQUAL(sKey.getKeyType(), KeyType::RSA);
+
+  const uint8_t plainText[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
+
+  auto cipherText = pKey.encrypt(plainText, sizeof(plainText));
+  auto decrypted = sKey.decrypt(cipherText->data(), cipherText->size());
+  BOOST_CHECK_EQUAL_COLLECTIONS(plainText, plainText + sizeof(plainText),
+                                decrypted->begin(), decrypted->end());
+}
+
+BOOST_AUTO_TEST_CASE(UnsupportedEcDecryption)
+{
+  EcKeyTestData dataSet;
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(dataSet.privateKeyPkcs1.c_str()),
+                       dataSet.privateKeyPkcs1.size());
+  BOOST_CHECK_EQUAL(sKey.getKeyType(), KeyType::EC);
+
+  OBufferStream os;
+  bufferSource("Y2lhbyFob2xhIWhlbGxvIQ==") >> base64Decode() >> streamSink(os);
+
+  BOOST_CHECK_THROW(sKey.decrypt(os.buf()->data(), os.buf()->size()), PrivateKey::Error);
+}
+
+using KeyParams = boost::mpl::vector<RsaKeyParams, EcKeyParams>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(GenerateKey, T, KeyParams)
+{
+  unique_ptr<PrivateKey> sKey = generatePrivateKey(T());
+  PublicKey pKey;
+  ConstBufferPtr pKeyBits = sKey->derivePublicKey();
+  pKey.loadPkcs8(pKeyBits->data(), pKeyBits->size());
+
+  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
+  OBufferStream os;
+  BOOST_REQUIRE_NO_THROW(bufferSource(data, sizeof(data)) >>
+                         signerFilter(DigestAlgorithm::SHA256, *sKey) >>
+                         streamSink(os));
+
+  ConstBufferPtr sig = os.buf();
+  bool result = false;
+  BOOST_REQUIRE_NO_THROW(bufferSource(data, sizeof(data)) >>
+                         verifierFilter(DigestAlgorithm::SHA256, pKey, sig->data(), sig->size()) >>
+                         boolSink(result));
+  BOOST_CHECK(result);
+
+  unique_ptr<PrivateKey> sKey2 = generatePrivateKey(T());
+
+  OBufferStream os1;
+  sKey->savePkcs1(os1);
+  ConstBufferPtr key1Pkcs1 = os1.buf();
+
+  OBufferStream os2;
+  sKey2->savePkcs1(os2);
+  ConstBufferPtr key2Pkcs1 = os2.buf();
+
+  BOOST_CHECK(*key1Pkcs1 != *key2Pkcs1);
+}
+
+BOOST_AUTO_TEST_CASE(UnsupportedKeyType)
+{
+  BOOST_CHECK_THROW(generatePrivateKey(AesKeyParams()), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPrivateKey
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/public-key.t.cpp b/tests/unit/security/transform/public-key.t.cpp
new file mode 100644
index 0000000..f9d3304
--- /dev/null
+++ b/tests/unit/security/transform/public-key.t.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/public-key.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/transform.hpp"
+
+#include "boost-test.hpp"
+#include <boost/mpl/vector.hpp>
+
+#include <sstream>
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestPublicKey)
+
+struct RsaKeyTestData
+{
+  const std::string publicKeyPkcs8 =
+      "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0WM1/WhAxyLtEqsiAJg\n"
+      "WDZWuzkYpeYVdeeZcqRZzzfRgBQTsNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobw\n"
+      "s3iigohnM9yTK+KKiayPhIAm/+5HGT6SgFJhYhqo1/upWdueojil6RP4/AgavHho\n"
+      "pxlAVbk6G9VdVnlQcQ5Zv0OcGi73c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9g\n"
+      "ZwIL5PuE9BiO6I39cL9z7EK1SfZhOWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA\n"
+      "9rH58ynaAix0tcR/nBMRLUX+e3rURHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0\n"
+      "ywIDAQAB\n";
+};
+
+struct EcKeyTestData
+{
+  const std::string publicKeyPkcs8 =
+      "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
+      "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
+      "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
+      "NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n"
+      "RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n"
+      "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGhuFibgwLdEJBDOLdvSg1Hc\n"
+      "5EJTDxq6ls5FoYLfThp8HOjuwGSz0qw8ocMqyku1y0V5peQ4rEPd0bwcpZd9svA=\n";
+};
+
+using KeyTestDataSets = boost::mpl::vector<RsaKeyTestData, EcKeyTestData>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(SaveLoad, T, KeyTestDataSets)
+{
+  T dataSet;
+
+  const uint8_t* pKeyPkcs8Base64 = reinterpret_cast<const uint8_t*>(dataSet.publicKeyPkcs8.c_str());
+  size_t pKeyPkcs8Base64Len = dataSet.publicKeyPkcs8.size();
+  OBufferStream os;
+  bufferSource(pKeyPkcs8Base64, pKeyPkcs8Base64Len) >> base64Decode() >> streamSink(os);
+  ConstBufferPtr pKeyPkcs8Buf = os.buf();
+  const uint8_t* pKeyPkcs8 = pKeyPkcs8Buf->data();
+  size_t pKeyPkcs8Len = pKeyPkcs8Buf->size();
+
+  PublicKey pKey1;
+  BOOST_CHECK_NO_THROW(pKey1.loadPkcs8Base64(pKeyPkcs8Base64, pKeyPkcs8Base64Len));
+
+  std::stringstream ss2(dataSet.publicKeyPkcs8);
+  PublicKey pKey2;
+  BOOST_CHECK_NO_THROW(pKey2.loadPkcs8Base64(ss2));
+
+  PublicKey pKey3;
+  BOOST_CHECK_NO_THROW(pKey3.loadPkcs8(pKeyPkcs8, pKeyPkcs8Len));
+
+  std::stringstream ss4;
+  ss4.write(reinterpret_cast<const char*>(pKeyPkcs8), pKeyPkcs8Len);
+  PublicKey pKey4;
+  BOOST_CHECK_NO_THROW(pKey4.loadPkcs8(ss4));
+
+  OBufferStream os5;
+  BOOST_REQUIRE_NO_THROW(pKey1.savePkcs8Base64(os5));
+  BOOST_CHECK_EQUAL_COLLECTIONS(pKeyPkcs8Base64, pKeyPkcs8Base64 + pKeyPkcs8Base64Len,
+                                os5.buf()->begin(), os5.buf()->end());
+
+  OBufferStream os6;
+  BOOST_REQUIRE_NO_THROW(pKey1.savePkcs8(os6));
+  BOOST_CHECK_EQUAL_COLLECTIONS(pKeyPkcs8, pKeyPkcs8 + pKeyPkcs8Len,
+                                os6.buf()->begin(), os6.buf()->end());
+}
+
+// NOTE: We cannot test RSA encryption by comparing the computed ciphertext to
+//       a known-good one, because OAEP padding is randomized and would produce
+//       different results every time. An encrypt/decrypt round-trip test is
+//       performed in private-key.t.cpp
+
+BOOST_AUTO_TEST_CASE(UnsupportedEcEncryption)
+{
+  EcKeyTestData dataSet;
+
+  PublicKey pKey;
+  pKey.loadPkcs8Base64(reinterpret_cast<const uint8_t*>(dataSet.publicKeyPkcs8.c_str()),
+                       dataSet.publicKeyPkcs8.size());
+  BOOST_CHECK_EQUAL(pKey.getKeyType(), KeyType::EC);
+
+  OBufferStream os;
+  bufferSource("Y2lhbyFob2xhIWhlbGxvIQ==") >> base64Decode() >> streamSink(os);
+
+  BOOST_CHECK_THROW(pKey.encrypt(os.buf()->data(), os.buf()->size()), PublicKey::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestPublicKey
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/signer-filter.t.cpp b/tests/unit/security/transform/signer-filter.t.cpp
new file mode 100644
index 0000000..51a98f0
--- /dev/null
+++ b/tests/unit/security/transform/signer-filter.t.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/signer-filter.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/transform/base64-decode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/private-key.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "security/verification-helpers.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestSignerFilter)
+
+BOOST_AUTO_TEST_CASE(Rsa)
+{
+  const std::string publicKeyPkcs8 =
+    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0WM1/WhAxyLtEqsiAJg\n"
+    "WDZWuzkYpeYVdeeZcqRZzzfRgBQTsNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobw\n"
+    "s3iigohnM9yTK+KKiayPhIAm/+5HGT6SgFJhYhqo1/upWdueojil6RP4/AgavHho\n"
+    "pxlAVbk6G9VdVnlQcQ5Zv0OcGi73c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9g\n"
+    "ZwIL5PuE9BiO6I39cL9z7EK1SfZhOWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA\n"
+    "9rH58ynaAix0tcR/nBMRLUX+e3rURHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0\n"
+    "ywIDAQAB\n";
+  const std::string privateKeyPkcs1 =
+    "MIIEpAIBAAKCAQEAw0WM1/WhAxyLtEqsiAJgWDZWuzkYpeYVdeeZcqRZzzfRgBQT\n"
+    "sNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobws3iigohnM9yTK+KKiayPhIAm/+5H\n"
+    "GT6SgFJhYhqo1/upWdueojil6RP4/AgavHhopxlAVbk6G9VdVnlQcQ5Zv0OcGi73\n"
+    "c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9gZwIL5PuE9BiO6I39cL9z7EK1SfZh\n"
+    "OWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA9rH58ynaAix0tcR/nBMRLUX+e3rU\n"
+    "RHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0ywIDAQABAoIBADQkckOIl4IZMUTn\n"
+    "W8LFv6xOdkJwMKC8G6bsPRFbyY+HvC2TLt7epSvfS+f4AcYWaOPcDu2E49vt2sNr\n"
+    "cASly8hgwiRRAB3dHH9vcsboiTo8bi2RFvMqvjv9w3tK2yMxVDtmZamzrrnaV3YV\n"
+    "Q+5nyKo2F/PMDjQ4eUAKDOzjhBuKHsZBTFnA1MFNI+UKj5X4Yp64DFmKlxTX/U2b\n"
+    "wzVywo5hzx2Uhw51jmoLls4YUvMJXD0wW5ZtYRuPogXvXb/of9ef/20/wU11WFKg\n"
+    "Xb4gfR8zUXaXS1sXcnVm3+24vIs9dApUwykuoyjOqxWqcHRec2QT2FxVGkFEraze\n"
+    "CPa4rMECgYEA5Y8CywomIcTgerFGFCeMHJr8nQGqY2V/owFb3k9maczPnC9p4a9R\n"
+    "c5szLxA9FMYFxurQZMBWSEG2JS1HR2mnjigx8UKjYML/A+rvvjZOMe4M6Sy2ggh4\n"
+    "SkLZKpWTzjTe07ByM/j5v/SjNZhWAG7sw4/LmPGRQkwJv+KZhGojuOkCgYEA2cOF\n"
+    "T6cJRv6kvzTz9S0COZOVm+euJh/BXp7oAsAmbNfOpckPMzqHXy8/wpdKl6AAcB57\n"
+    "OuztlNfV1D7qvbz7JuRlYwQ0cEfBgbZPcz1p18HHDXhwn57ZPb8G33Yh9Omg0HNA\n"
+    "Imb4LsVuSqxA6NwSj7cpRekgTedrhLFPJ+Ydb5MCgYEAsM3Q7OjILcIg0t6uht9e\n"
+    "vrlwTsz1mtCV2co2I6crzdj9HeI2vqf1KAElDt6G7PUHhglcr/yjd8uEqmWRPKNX\n"
+    "ddnnfVZB10jYeP/93pac6z/Zmc3iU4yKeUe7U10ZFf0KkiiYDQd59CpLef/2XScS\n"
+    "HB0oRofnxRQjfjLc4muNT+ECgYEAlcDk06MOOTly+F8lCc1bA1dgAmgwFd2usDBd\n"
+    "Y07a3e0HGnGLN3Kfl7C5i0tZq64HvxLnMd2vgLVxQlXGPpdQrC1TH+XLXg+qnlZO\n"
+    "ivSH7i0/gx75bHvj75eH1XK65V8pDVDEoSPottllAIs21CxLw3N1ObOZWJm2EfmR\n"
+    "cuHICmsCgYAtFJ1idqMoHxES3mlRpf2JxyQudP3SCm2WpGmqVzhRYInqeatY5sUd\n"
+    "lPLHm/p77RT7EyxQHTlwn8FJPuM/4ZH1rQd/vB+Y8qAtYJCexDMsbvLW+Js+VOvk\n"
+    "jweEC0nrcL31j9mF0vz5E6tfRu4hhJ6L4yfWs0gSejskeVB/w8QY4g==\n";
+  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
+
+  OBufferStream os1;
+  bufferSource(publicKeyPkcs8) >> base64Decode() >> streamSink(os1);
+  auto pubKey = os1.buf();
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(privateKeyPkcs1.data()), privateKeyPkcs1.size());
+
+  BOOST_CHECK_THROW(SignerFilter(DigestAlgorithm::NONE, sKey), Error);
+
+  OBufferStream os2;
+  bufferSource(data, sizeof(data)) >> signerFilter(DigestAlgorithm::SHA256, sKey) >> streamSink(os2);
+  auto sig = os2.buf();
+
+  BOOST_CHECK(verifySignature(data, sizeof(data), sig->data(), sig->size(), pubKey->data(), pubKey->size()));
+}
+
+BOOST_AUTO_TEST_CASE(Ecdsa)
+{
+  const std::string privateKeyPkcs1 =
+    "MIIBaAIBAQQgRxwcbzK9RV6AHYFsDcykI86o3M/a1KlJn0z8PcLMBZOggfowgfcC\n"
+    "AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////\n"
+    "MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr\n"
+    "vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE\n"
+    "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W\n"
+    "K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8\n"
+    "YyVRAgEBoUQDQgAEaG4WJuDAt0QkEM4t29KDUdzkQlMPGrqWzkWhgt9OGnwc6O7A\n"
+    "ZLPSrDyhwyrKS7XLRXml5DisQ93RvByll32y8A==\n";
+  const std::string publicKeyPkcs8 =
+    "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
+    "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
+    "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
+    "NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n"
+    "RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n"
+    "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGhuFibgwLdEJBDOLdvSg1Hc\n"
+    "5EJTDxq6ls5FoYLfThp8HOjuwGSz0qw8ocMqyku1y0V5peQ4rEPd0bwcpZd9svA=\n";
+  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
+
+  OBufferStream os1;
+  bufferSource(publicKeyPkcs8) >> base64Decode() >> streamSink(os1);
+  auto pubKey = os1.buf();
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(privateKeyPkcs1.data()), privateKeyPkcs1.size());
+
+  BOOST_CHECK_THROW(SignerFilter(DigestAlgorithm::NONE, sKey), Error);
+
+  OBufferStream os2;
+  bufferSource(data, sizeof(data)) >> signerFilter(DigestAlgorithm::SHA256, sKey) >> streamSink(os2);
+  auto sig = os2.buf();
+
+  BOOST_CHECK(verifySignature(data, sizeof(data), sig->data(), sig->size(), pubKey->data(), pubKey->size()));
+}
+
+BOOST_AUTO_TEST_CASE(InvalidKey)
+{
+  PrivateKey sKey;
+  BOOST_CHECK_THROW(SignerFilter(DigestAlgorithm::SHA256, sKey), Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSignerFilter
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/step-source.t.cpp b/tests/unit/security/transform/step-source.t.cpp
new file mode 100644
index 0000000..43c6655
--- /dev/null
+++ b/tests/unit/security/transform/step-source.t.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+#include <sstream>
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestStepSource)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  const std::string input =
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567";
+  const uint8_t* buf = reinterpret_cast<const uint8_t*>(input.data());
+
+  std::ostringstream os;
+  StepSource ss;
+  ss >> streamSink(os);
+  BOOST_CHECK_EQUAL(ss.write(buf, 320), 320);
+  BOOST_CHECK_EQUAL(ss.write(buf + 320, 320), 320);
+  BOOST_CHECK_EQUAL(ss.write(buf + 640, 320), 320);
+  BOOST_CHECK_EQUAL(ss.write(buf + 960, 320), 320);
+  ss.end();
+  BOOST_CHECK_THROW(ss.write(buf + 960, 320), transform::Error);
+  BOOST_CHECK_EQUAL(os.str(), input);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  std::ostringstream os;
+  StepSource ss;
+  ss >> streamSink(os);
+  ss.end();
+  BOOST_CHECK_EQUAL(os.str(), "");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStepSource
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/stream-sink.t.cpp b/tests/unit/security/transform/stream-sink.t.cpp
new file mode 100644
index 0000000..753ea35
--- /dev/null
+++ b/tests/unit/security/transform/stream-sink.t.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestStreamSink)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x20, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+  std::ostringstream os;
+  StreamSink sink(os);
+  BOOST_CHECK_EQUAL(sink.write(in, 4), 4);
+  BOOST_CHECK_EQUAL(sink.write(in + 4, 4), 4);
+  BOOST_CHECK_EQUAL(sink.write(in + 8, 4), 4);
+  BOOST_CHECK_EQUAL(sink.write(in + 12, 4), 4);
+  sink.end();
+  std::string out = os.str();
+  BOOST_CHECK_EQUAL_COLLECTIONS(in, in + sizeof(in), out.begin(), out.end());
+  BOOST_CHECK_THROW(sink.write(in + 8, 8), transform::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStreamSink
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/stream-source.t.cpp b/tests/unit/security/transform/stream-source.t.cpp
new file mode 100644
index 0000000..ae385b1
--- /dev/null
+++ b/tests/unit/security/transform/stream-source.t.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/stream-source.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+#include <boost/mpl/integral_c.hpp>
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestStreamSource)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  std::string input =
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "01234567012345670123456701234567 1234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567"
+    "0123456701234567012345670123456701234567012345670123456701234567";
+
+  std::stringstream is(input);
+  std::stringstream os;
+  streamSource(is) >> streamSink(os);
+  std::string output = os.str();
+
+  BOOST_CHECK_EQUAL(input, output);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyStream)
+{
+  std::stringstream is;
+  std::stringstream os;
+  streamSource(is) >> streamSink(os);
+
+  BOOST_CHECK_EQUAL(os.str(), "");
+}
+
+typedef boost::mpl::vector<
+  boost::mpl::integral_c<std::ios_base::iostate, std::ios_base::badbit>,
+  boost::mpl::integral_c<std::ios_base::iostate, std::ios_base::failbit>
+> BadBits;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(BadStream, BadBit, BadBits)
+{
+  std::stringstream is;
+  is.setstate(BadBit::value);
+  std::stringstream os;
+  BOOST_CHECK_THROW(streamSource(is) >> streamSink(os), transform::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStreamSource
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/strip-space.t.cpp b/tests/unit/security/transform/strip-space.t.cpp
new file mode 100644
index 0000000..db8fa84
--- /dev/null
+++ b/tests/unit/security/transform/strip-space.t.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/strip-space.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestStripSpace)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  const char* input = R"STR(
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+    incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
+    exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
+    irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
+    deserunt mollit anim id est laborum.
+  )STR";
+  size_t inputLen = strlen(input);
+
+  OBufferStream os;
+  StepSource source;
+  source >> stripSpace() >> streamSink(os);
+
+  for (size_t offset = 0; offset < inputLen; offset += 40) {
+    source.write(reinterpret_cast<const uint8_t*>(input + offset),
+                 std::min<size_t>(40, inputLen - offset));
+  }
+  source.end();
+
+  std::string expected(
+    "Loremipsumdolorsitamet,consecteturadipiscingelit,seddoeiusmodtemporincididuntutl"
+    "aboreetdoloremagnaaliqua.Utenimadminimveniam,quisnostrudexercitationullamcolabor"
+    "isnisiutaliquipexeacommodoconsequat.Duisauteiruredolorinreprehenderitinvoluptate"
+    "velitessecillumdoloreeufugiatnullapariatur.Excepteursintoccaecatcupidatatnonproi"
+    "dent,suntinculpaquiofficiadeseruntmollitanimidestlaborum.");
+  ConstBufferPtr buf = os.buf();
+  BOOST_CHECK_EQUAL(std::string(buf->get<char>(), buf->size()), expected);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStripSpace
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/transform/verifier-filter.t.cpp b/tests/unit/security/transform/verifier-filter.t.cpp
new file mode 100644
index 0000000..1b74690
--- /dev/null
+++ b/tests/unit/security/transform/verifier-filter.t.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/transform/verifier-filter.hpp"
+
+#include "encoding/buffer-stream.hpp"
+#include "security/transform/base64-decode.hpp"
+#include "security/transform/bool-sink.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/private-key.hpp"
+#include "security/transform/public-key.hpp"
+#include "security/transform/signer-filter.hpp"
+#include "security/transform/stream-sink.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestVerifierFilter)
+
+BOOST_AUTO_TEST_CASE(Rsa)
+{
+  const std::string publicKeyPkcs8 =
+    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0WM1/WhAxyLtEqsiAJg\n"
+    "WDZWuzkYpeYVdeeZcqRZzzfRgBQTsNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobw\n"
+    "s3iigohnM9yTK+KKiayPhIAm/+5HGT6SgFJhYhqo1/upWdueojil6RP4/AgavHho\n"
+    "pxlAVbk6G9VdVnlQcQ5Zv0OcGi73c+EnYD/YgURYGSngUi/Ynsh779p2U69/te9g\n"
+    "ZwIL5PuE9BiO6I39cL9z7EK1SfZhOWvDe/qH7YhD/BHwcWit8FjRww1glwRVTJsA\n"
+    "9rH58ynaAix0tcR/nBMRLUX+e3rURHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0\n"
+    "ywIDAQAB\n";
+  const std::string privateKeyPkcs1 =
+    "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDRYzX9aEDHIu0\n"
+    "SqyIAmBYNla7ORil5hV155lypFnPN9GAFBOw2jNLm3gefBNmHDBdsfuTdA3SRFNX\n"
+    "zbpehvCzeKKCiGcz3JMr4oqJrI+EgCb/7kcZPpKAUmFiGqjX+6lZ256iOKXpE/j8\n"
+    "CBq8eGinGUBVuTob1V1WeVBxDlm/Q5waLvdz4SdgP9iBRFgZKeBSL9ieyHvv2nZT\n"
+    "r3+172BnAgvk+4T0GI7ojf1wv3PsQrVJ9mE5a8N7+oftiEP8EfBxaK3wWNHDDWCX\n"
+    "BFVMmwD2sfnzKdoCLHS1xH+cExEtRf57etREeDpRtKMlt1v2qYozV9MYcpTMv/mk\n"
+    "wbq4FTTLAgMBAAECggEANCRyQ4iXghkxROdbwsW/rE52QnAwoLwbpuw9EVvJj4e8\n"
+    "LZMu3t6lK99L5/gBxhZo49wO7YTj2+3aw2twBKXLyGDCJFEAHd0cf29yxuiJOjxu\n"
+    "LZEW8yq+O/3De0rbIzFUO2ZlqbOuudpXdhVD7mfIqjYX88wONDh5QAoM7OOEG4oe\n"
+    "xkFMWcDUwU0j5QqPlfhinrgMWYqXFNf9TZvDNXLCjmHPHZSHDnWOaguWzhhS8wlc\n"
+    "PTBblm1hG4+iBe9dv+h/15//bT/BTXVYUqBdviB9HzNRdpdLWxdydWbf7bi8iz10\n"
+    "ClTDKS6jKM6rFapwdF5zZBPYXFUaQUStrN4I9riswQKBgQDljwLLCiYhxOB6sUYU\n"
+    "J4wcmvydAapjZX+jAVveT2ZpzM+cL2nhr1FzmzMvED0UxgXG6tBkwFZIQbYlLUdH\n"
+    "aaeOKDHxQqNgwv8D6u++Nk4x7gzpLLaCCHhKQtkqlZPONN7TsHIz+Pm/9KM1mFYA\n"
+    "buzDj8uY8ZFCTAm/4pmEaiO46QKBgQDZw4VPpwlG/qS/NPP1LQI5k5Wb564mH8Fe\n"
+    "nugCwCZs186lyQ8zOodfLz/Cl0qXoABwHns67O2U19XUPuq9vPsm5GVjBDRwR8GB\n"
+    "tk9zPWnXwccNeHCfntk9vwbfdiH06aDQc0AiZvguxW5KrEDo3BKPtylF6SBN52uE\n"
+    "sU8n5h1vkwKBgQCwzdDs6MgtwiDS3q6G316+uXBOzPWa0JXZyjYjpyvN2P0d4ja+\n"
+    "p/UoASUO3obs9QeGCVyv/KN3y4SqZZE8o1d12ed9VkHXSNh4//3elpzrP9mZzeJT\n"
+    "jIp5R7tTXRkV/QqSKJgNB3n0Kkt5//ZdJxIcHShGh+fFFCN+Mtzia41P4QKBgQCV\n"
+    "wOTTow45OXL4XyUJzVsDV2ACaDAV3a6wMF1jTtrd7QcacYs3cp+XsLmLS1mrrge/\n"
+    "Eucx3a+AtXFCVcY+l1CsLVMf5cteD6qeVk6K9IfuLT+DHvlse+Pvl4fVcrrlXykN\n"
+    "UMShI+i22WUAizbULEvDc3U5s5lYmbYR+ZFy4cgKawKBgC0UnWJ2oygfERLeaVGl\n"
+    "/YnHJC50/dIKbZakaapXOFFgiep5q1jmxR2U8seb+nvtFPsTLFAdOXCfwUk+4z/h\n"
+    "kfWtB3+8H5jyoC1gkJ7EMyxu8tb4mz5U6+SPB4QLSetwvfWP2YXS/PkTq19G7iGE\n"
+    "novjJ9azSBJ6OyR5UH/DxBji\n";
+  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
+
+  OBufferStream os1;
+  bufferSource(publicKeyPkcs8) >> base64Decode() >> streamSink(os1);
+  auto pubKey = os1.buf();
+
+  PublicKey pKey;
+  pKey.loadPkcs8(pubKey->data(), pubKey->size());
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(privateKeyPkcs1.data()), privateKeyPkcs1.size());
+
+  OBufferStream os2;
+  bufferSource(data, sizeof(data)) >> signerFilter(DigestAlgorithm::SHA256, sKey) >> streamSink(os2);
+  auto sig = os2.buf();
+
+  BOOST_CHECK_THROW(VerifierFilter(DigestAlgorithm::NONE, pKey, sig->data(), sig->size()), Error);
+
+  bool result = false;
+  bufferSource(data, sizeof(data)) >>
+    verifierFilter(DigestAlgorithm::SHA256, pKey, sig->data(), sig->size()) >>
+    boolSink(result);
+
+  BOOST_CHECK_EQUAL(result, true);
+}
+
+BOOST_AUTO_TEST_CASE(Ecdsa)
+{
+  const std::string privateKeyPkcs1 =
+    "MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB\n"
+    "AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA\n"
+    "///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV\n"
+    "AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg\n"
+    "9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A\n"
+    "AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQgRxwcbzK9RV6A\n"
+    "HYFsDcykI86o3M/a1KlJn0z8PcLMBZOhRANCAARobhYm4MC3RCQQzi3b0oNR3ORC\n"
+    "Uw8aupbORaGC304afBzo7sBks9KsPKHDKspLtctFeaXkOKxD3dG8HKWXfbLw\n";
+  const std::string publicKeyPkcs8 =
+    "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
+    "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
+    "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
+    "NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n"
+    "RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n"
+    "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGhuFibgwLdEJBDOLdvSg1Hc\n"
+    "5EJTDxq6ls5FoYLfThp8HOjuwGSz0qw8ocMqyku1y0V5peQ4rEPd0bwcpZd9svA=\n";
+  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
+
+  OBufferStream os1;
+  bufferSource(publicKeyPkcs8) >> base64Decode() >> streamSink(os1);
+  auto pubKey = os1.buf();
+
+  PublicKey pKey;
+  pKey.loadPkcs8(pubKey->data(), pubKey->size());
+
+  PrivateKey sKey;
+  sKey.loadPkcs1Base64(reinterpret_cast<const uint8_t*>(privateKeyPkcs1.data()), privateKeyPkcs1.size());
+
+  OBufferStream os2;
+  bufferSource(data, sizeof(data)) >> signerFilter(DigestAlgorithm::SHA256, sKey) >> streamSink(os2);
+  auto sig = os2.buf();
+
+  BOOST_CHECK_THROW(VerifierFilter(DigestAlgorithm::NONE, pKey, sig->data(), sig->size()), Error);
+
+  bool result = false;
+  bufferSource(data, sizeof(data)) >>
+    verifierFilter(DigestAlgorithm::SHA256, pKey, sig->data(), sig->size()) >>
+    boolSink(result);
+
+  BOOST_CHECK_EQUAL(result, true);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidKey)
+{
+  PublicKey pKey;
+  BOOST_CHECK_THROW(VerifierFilter(DigestAlgorithm::SHA256, pKey, nullptr, 0), Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestVerifierFilter
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/additional-description.t.cpp b/tests/unit/security/v2/additional-description.t.cpp
new file mode 100644
index 0000000..7ceee8c
--- /dev/null
+++ b/tests/unit/security/v2/additional-description.t.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/additional-description.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestAdditionalDescription)
+
+const uint8_t description[] = {
+  0xfd, 0x01, 0x02, 0x28,
+    0xfd, 0x02, 0x00, 0x10, // DescriptionEntry
+      0xfd, 0x02, 0x01, 0x04, // DescriptionKey
+        0x6b, 0x65, 0x79, 0x31, // "key1"
+      0xfd, 0x02, 0x02, 0x04, // DescriptionValue
+        0x76, 0x61, 0x6c, 0x31, // "val1"
+    0xfd, 0x02, 0x00, 0x10, // DescriptionEntry
+      0xfd, 0x02, 0x01, 0x04, // DescriptionKey
+        0x6b, 0x65, 0x79, 0x32, // "key2"
+      0xfd, 0x02, 0x02, 0x04, // DescriptionValue
+        0x76, 0x61, 0x6c, 0x32, // "val2"
+};
+
+const std::string text = "((key1:val1), (key2:val2))";
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  AdditionalDescription aDescription;
+
+  aDescription.set("key2", "val2");
+  aDescription.set("key1", "val1");
+
+  BOOST_REQUIRE_NO_THROW(aDescription.get("key1"));
+  BOOST_REQUIRE_NO_THROW(aDescription.get("key2"));
+  BOOST_REQUIRE_THROW(aDescription.get("key3"), AdditionalDescription::Error);
+
+  BOOST_CHECK_EQUAL(aDescription.has("key1"), true);
+  BOOST_CHECK_EQUAL(aDescription.has("key2"), true);
+  BOOST_CHECK_EQUAL(aDescription.has("key3"), false);
+
+  auto val1 = aDescription.get("key1");
+  auto val2 = aDescription.get("key2");
+
+  BOOST_CHECK_EQUAL(val1, "val1");
+  BOOST_CHECK_EQUAL(val2, "val2");
+
+  auto it = aDescription.begin();
+  BOOST_CHECK_EQUAL(it->second, "val1");
+  it++;
+  BOOST_CHECK_EQUAL(it->second, "val2");
+  it++;
+  BOOST_CHECK(it == aDescription.end());
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(aDescription.wireEncode().wire(),
+                                aDescription.wireEncode().wire() + aDescription.wireEncode().size(),
+                                description,
+                                description + sizeof(description));
+
+  BOOST_REQUIRE_NO_THROW(AdditionalDescription(Block(description, sizeof(description))));
+  AdditionalDescription aDescription2(Block(description, sizeof(description)));
+
+  BOOST_CHECK_EQUAL(aDescription2, aDescription);
+
+  AdditionalDescription aDescription3;
+  aDescription3.set("key3", "val3");
+  aDescription3.set("key2", "val2");
+
+  BOOST_CHECK_NE(aDescription2, aDescription3);
+
+  std::ostringstream os;
+  os << aDescription;
+  BOOST_CHECK_EQUAL(os.str(), text);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestAdditionalDescription
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/certificate-bundle-fetcher.t.cpp b/tests/unit/security/v2/certificate-bundle-fetcher.t.cpp
new file mode 100644
index 0000000..d2e0565
--- /dev/null
+++ b/tests/unit/security/v2/certificate-bundle-fetcher.t.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/certificate-bundle-fetcher.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+#include "util/regex/regex-pattern-list-matcher.hpp"
+#include "lp/nack.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(TestCertificateBundleFetcher)
+
+class CertificateBundleFetcherWrapper : public CertificateBundleFetcher
+{
+public:
+  CertificateBundleFetcherWrapper(Face& face)
+    : CertificateBundleFetcher(make_unique<CertificateFetcherFromNetwork>(face), face)
+  {
+  }
+};
+
+class Bundle
+{
+};
+
+class Cert
+{
+};
+
+class Timeout
+{
+};
+
+class Nack
+{
+};
+
+template<class Response>
+class CertificateBundleFetcherFixture : public HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy,
+                                                                            CertificateBundleFetcherWrapper>
+{
+public:
+  CertificateBundleFetcherFixture()
+    : data("/Security/V2/ValidatorFixture/Sub1/Sub3/Data")
+  {
+    subSubIdentity = addSubCertificate("/Security/V2/ValidatorFixture/Sub1/Sub3", subIdentity);
+    cache.insert(subSubIdentity.getDefaultKey().getDefaultCertificate());
+
+    m_keyChain.sign(data, signingByIdentity(subSubIdentity));
+    bundleRegexMatcher = make_shared<RegexPatternListMatcher>("<>*<_BUNDLE><>*", nullptr);
+    processInterest = [this] (const Interest& interest) {
+      // check if the interest is for Bundle or individual certificates
+      if (bundleRegexMatcher->match(interest.getName(), 0, interest.getName().size())) {
+        makeResponse(interest);
+      }
+      else {
+        auto cert = cache.find(interest);
+        if (cert == nullptr) {
+          return;
+        }
+        face.receive(*cert);
+      }
+    };
+  }
+
+  void
+  makeResponse(const Interest& interest);
+
+public:
+  Data data;
+  Identity subSubIdentity;
+  shared_ptr<RegexPatternListMatcher> bundleRegexMatcher;
+};
+
+template<>
+void
+CertificateBundleFetcherFixture<Bundle>::makeResponse(const Interest& interest)
+{
+  Block certList = Block(tlv::Content);
+  Name bundleName(interest.getName());
+
+  if (!bundleName.get(-1).isSegment() || bundleName.get(-1).toSegment() == 0) {
+    Block subSubCert = subSubIdentity.getDefaultKey().getDefaultCertificate().wireEncode();
+    certList.push_back(subSubCert);
+
+    if (!bundleName.get(-1).isSegment()) {
+      bundleName
+        .appendVersion()
+        .appendSegment(0);
+    }
+  }
+  else {
+    Block subCert = subIdentity.getDefaultKey().getDefaultCertificate().wireEncode();
+    Block anchor = identity.getDefaultKey().getDefaultCertificate().wireEncode();
+    certList.push_back(subCert);
+    certList.push_back(anchor);
+  }
+
+  shared_ptr<Data> certBundle = make_shared<Data>();
+  certBundle->setName(bundleName);
+  certBundle->setFreshnessPeriod(100_s);
+  certBundle->setContent(certList);
+  certBundle->setFinalBlock(name::Component::fromSegment(1));
+
+  m_keyChain.sign(*certBundle, signingWithSha256());
+
+  face.receive(*certBundle);
+}
+
+template<>
+void
+CertificateBundleFetcherFixture<Timeout>::makeResponse(const Interest& interest)
+{
+  this->advanceClocks(200_s);
+}
+
+template<>
+void
+CertificateBundleFetcherFixture<Nack>::makeResponse(const Interest& interest)
+{
+  lp::Nack nack(interest);
+  nack.setHeader(lp::NackHeader().setReason(lp::NackReason::NO_ROUTE));
+  face.receive(nack);
+}
+
+BOOST_FIXTURE_TEST_CASE(ValidateSuccessWithBundle, CertificateBundleFetcherFixture<Bundle>)
+{
+  VALIDATE_SUCCESS(this->data, "Should get accepted, as interest brings the bundle segments");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 2); // produced bundle has 2 segments
+
+  for (const auto& sentInterest : this->face.sentInterests) {
+    BOOST_CHECK(this->bundleRegexMatcher->match(sentInterest.getName(), 0, sentInterest.getName().size()));
+  }
+}
+
+using SuccessWithoutBundle = boost::mpl::vector<Nack, Timeout>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateSuccessWithoutBundle, T, SuccessWithoutBundle, CertificateBundleFetcherFixture<T>)
+{
+  VALIDATE_SUCCESS(this->data, "Should get accepted, as interest brings the certs");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4); // since interest for Bundle fails, each cert is retrieved
+
+  bool toggle = true;
+  for (const auto& sentInterest : this->face.sentInterests) {
+    if (toggle) {
+      // every alternate interest is going to be that of a bundle
+      BOOST_CHECK(this->bundleRegexMatcher->match(sentInterest.getName(), 0, sentInterest.getName().size()));
+    }
+    else {
+      BOOST_CHECK(!this->bundleRegexMatcher->match(sentInterest.getName(), 0, sentInterest.getName().size()));
+    }
+    toggle = !toggle;
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateBundleFetcher
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/certificate-cache.t.cpp b/tests/unit/security/v2/certificate-cache.t.cpp
new file mode 100644
index 0000000..4ae3505
--- /dev/null
+++ b/tests/unit/security/v2/certificate-cache.t.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/certificate-cache.hpp"
+
+#include "../../identity-management-time-fixture.hpp"
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+
+class CertificateCacheFixture : public ndn::tests::IdentityManagementTimeFixture
+{
+public:
+  CertificateCacheFixture()
+    : certCache(10_s)
+  {
+    identity = addIdentity("/TestCertificateCache/");
+    cert = identity.getDefaultKey().getDefaultCertificate();
+  }
+
+public:
+  CertificateCache certCache;
+  Identity identity;
+  Certificate cert;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestCertificateCache, CertificateCacheFixture)
+
+BOOST_AUTO_TEST_CASE(RemovalTime)
+{
+  // Cache lifetime is capped to 10 seconds during cache construction
+
+  BOOST_CHECK_NO_THROW(certCache.insert(cert));
+  BOOST_CHECK(certCache.find(cert.getName()) != nullptr);
+
+  advanceClocks(11_s, 1);
+  BOOST_CHECK(certCache.find(cert.getName()) == nullptr);
+
+  BOOST_CHECK_NO_THROW(certCache.insert(cert));
+  BOOST_CHECK(certCache.find(cert.getName()) != nullptr);
+
+  advanceClocks(5_s);
+  BOOST_CHECK(certCache.find(cert.getName()) != nullptr);
+
+  advanceClocks(15_s);
+  BOOST_CHECK(certCache.find(cert.getName()) == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(FindByInterest)
+{
+  BOOST_CHECK_NO_THROW(certCache.insert(cert));
+
+  // Find by interest
+  BOOST_CHECK(certCache.find(Interest(cert.getIdentity())) != nullptr);
+  BOOST_CHECK(certCache.find(Interest(cert.getKeyName())) != nullptr);
+  BOOST_CHECK(certCache.find(Interest(Name(cert.getName()).appendVersion())) == nullptr);
+
+  advanceClocks(12_s);
+  BOOST_CHECK(certCache.find(Interest(cert.getIdentity())) == nullptr);
+
+  Certificate cert3 = addCertificate(identity.getDefaultKey(), "3");
+  Certificate cert4 = addCertificate(identity.getDefaultKey(), "4");
+  Certificate cert5 = addCertificate(identity.getDefaultKey(), "5");
+
+  certCache.insert(cert3);
+  certCache.insert(cert4);
+  certCache.insert(cert5);
+
+  Interest interest4(cert3.getKeyName());
+  interest4.setExclude(Exclude().excludeOne(cert3.getName().at(Certificate::ISSUER_ID_OFFSET)));
+  BOOST_CHECK(certCache.find(interest4) != nullptr);
+  BOOST_CHECK_NE(certCache.find(interest4)->getName(), cert3.getName());
+
+  // TODO cover more cases with different interests
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateCache
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/certificate-fetcher-direct-fetch.t.cpp b/tests/unit/security/v2/certificate-fetcher-direct-fetch.t.cpp
new file mode 100644
index 0000000..68fe6b1
--- /dev/null
+++ b/tests/unit/security/v2/certificate-fetcher-direct-fetch.t.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/certificate-fetcher-direct-fetch.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+#include "lp/nack.hpp"
+#include "lp/tags.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+#include <boost/range/adaptor/strided.hpp>
+#include <boost/range/adaptor/sliced.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(TestCertificateFetcherDirectFetch)
+
+class Cert
+{
+};
+
+class Timeout
+{
+};
+
+class Nack
+{
+};
+
+template<class Response>
+class CertificateFetcherDirectFetchFixture : public HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy,
+                                                                                 CertificateFetcherDirectFetch>
+{
+public:
+  CertificateFetcherDirectFetchFixture()
+    : data("/Security/V2/ValidatorFixture/Sub1/Sub3/Data")
+    , interest("/Security/V2/ValidatorFixture/Sub1/Sub3/Interest")
+    , interestNoTag("/Security/V2/ValidatorFixture/Sub1/Sub3/Interest2")
+  {
+    Identity subSubIdentity = addSubCertificate("/Security/V2/ValidatorFixture/Sub1/Sub3", subIdentity);
+    cache.insert(subSubIdentity.getDefaultKey().getDefaultCertificate());
+
+    m_keyChain.sign(data, signingByIdentity(subSubIdentity));
+    m_keyChain.sign(interest, signingByIdentity(subSubIdentity));
+    m_keyChain.sign(interestNoTag, signingByIdentity(subSubIdentity));
+
+    data.setTag(make_shared<lp::IncomingFaceIdTag>(123));
+    interest.setTag(make_shared<lp::IncomingFaceIdTag>(123));
+
+    processInterest = [this] (const Interest& interest) {
+      auto nextHopFaceIdTag = interest.template getTag<lp::NextHopFaceIdTag>();
+      if (nextHopFaceIdTag == nullptr) {
+        makeResponse(interest); // respond only to the "infrastructure" interest
+      }
+    };
+  }
+
+  void
+  makeResponse(const Interest& interest);
+
+public:
+  Data data;
+  Interest interest;
+  Interest interestNoTag;
+};
+
+template<>
+void
+CertificateFetcherDirectFetchFixture<Cert>::makeResponse(const Interest& interest)
+{
+  auto cert = cache.find(interest);
+  if (cert == nullptr) {
+    return;
+  }
+  face.receive(*cert);
+}
+
+template<>
+void
+CertificateFetcherDirectFetchFixture<Timeout>::makeResponse(const Interest& interest)
+{
+  // do nothing
+}
+
+template<>
+void
+CertificateFetcherDirectFetchFixture<Nack>::makeResponse(const Interest& interest)
+{
+  lp::Nack nack(interest);
+  nack.setHeader(lp::NackHeader().setReason(lp::NackReason::NO_ROUTE));
+  face.receive(nack);
+}
+
+using Failures = boost::mpl::vector<Timeout, Nack>;
+
+BOOST_FIXTURE_TEST_CASE(ValidateSuccessData, CertificateFetcherDirectFetchFixture<Cert>)
+{
+  VALIDATE_SUCCESS(this->data, "Should get accepted, normal and/or direct interests bring certs");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
+
+  // odd interests
+  for (const auto& sentInterest : this->face.sentInterests | boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() != nullptr);
+  }
+
+  // even interests
+  for (const auto& sentInterest : this->face.sentInterests |
+                                    boost::adaptors::sliced(1, this->face.sentInterests.size()) |
+                                    boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() == nullptr);
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailureData, T, Failures, CertificateFetcherDirectFetchFixture<T>)
+{
+  VALIDATE_FAILURE(this->data, "Should fail, as all interests either NACKed or timeout");
+  // Direct fetcher sends two interests each time - to network and face
+  // 3 retries on nack or timeout (2 * (1 + 3) = 4)
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 8);
+
+  // odd interests
+  for (const auto& sentInterest : this->face.sentInterests | boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() != nullptr);
+  }
+
+  // even interests
+  for (const auto& sentInterest : this->face.sentInterests |
+                                    boost::adaptors::sliced(1, this->face.sentInterests.size()) |
+                                    boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() == nullptr);
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE(ValidateSuccessInterest, CertificateFetcherDirectFetchFixture<Cert>)
+{
+  VALIDATE_SUCCESS(this->interest, "Should get accepted, normal and/or direct interests bring certs");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
+
+  // odd interests
+  for (const auto& sentInterest : this->face.sentInterests | boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() != nullptr);
+  }
+
+  // even interests
+  for (const auto& sentInterest : this->face.sentInterests |
+                                    boost::adaptors::sliced(1, this->face.sentInterests.size()) |
+                                    boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() == nullptr);
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailureInterest, T, Failures, CertificateFetcherDirectFetchFixture<T>)
+{
+  VALIDATE_FAILURE(this->interest, "Should fail, as all interests either NACKed or timeout");
+  // Direct fetcher sends two interests each time - to network and face
+  // 3 retries on nack or timeout (2 * (1 + 3) = 4)
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 8);
+
+  // odd interests
+  for (const auto& sentInterest : this->face.sentInterests | boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() != nullptr);
+  }
+
+  // even interests
+  for (const auto& sentInterest : this->face.sentInterests |
+                                    boost::adaptors::sliced(1, this->face.sentInterests.size()) |
+                                    boost::adaptors::strided(2)) {
+    BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() == nullptr);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateFetcherDirectFetch
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/certificate-fetcher-from-network.t.cpp b/tests/unit/security/v2/certificate-fetcher-from-network.t.cpp
new file mode 100644
index 0000000..5a6b395
--- /dev/null
+++ b/tests/unit/security/v2/certificate-fetcher-from-network.t.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/certificate-fetcher-from-network.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+#include "lp/nack.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(TestCertificateFetcherFromNetwork)
+
+class Cert
+{
+};
+
+class Timeout
+{
+};
+
+class Nack
+{
+};
+
+template<class Response>
+class CertificateFetcherFromNetworkFixture : public HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy,
+                                                                                 CertificateFetcherFromNetwork>
+{
+public:
+  CertificateFetcherFromNetworkFixture()
+    : data("/Security/V2/ValidatorFixture/Sub1/Sub3/Data")
+    , interest("/Security/V2/ValidatorFixture/Sub1/Sub3/Interest")
+  {
+    Identity subSubIdentity = addSubCertificate("/Security/V2/ValidatorFixture/Sub1/Sub3", subIdentity);
+    cache.insert(subSubIdentity.getDefaultKey().getDefaultCertificate());
+
+    m_keyChain.sign(data, signingByIdentity(subSubIdentity));
+    m_keyChain.sign(interest, signingByIdentity(subSubIdentity));
+
+    processInterest = bind(&CertificateFetcherFromNetworkFixture<Response>::makeResponse, this, _1);
+  }
+
+  void
+  makeResponse(const Interest& interest);
+
+public:
+  Data data;
+  Interest interest;
+};
+
+template<>
+void
+CertificateFetcherFromNetworkFixture<Cert>::makeResponse(const Interest& interest)
+{
+  auto cert = cache.find(interest);
+  if (cert == nullptr) {
+    return;
+  }
+  face.receive(*cert);
+}
+
+template<>
+void
+CertificateFetcherFromNetworkFixture<Timeout>::makeResponse(const Interest& interest)
+{
+  // do nothing
+}
+
+template<>
+void
+CertificateFetcherFromNetworkFixture<Nack>::makeResponse(const Interest& interest)
+{
+  lp::Nack nack(interest);
+  nack.setHeader(lp::NackHeader().setReason(lp::NackReason::NO_ROUTE));
+  face.receive(nack);
+}
+
+using Failures = boost::mpl::vector<Timeout, Nack>;
+
+BOOST_FIXTURE_TEST_CASE(ValidateSuccess, CertificateFetcherFromNetworkFixture<Cert>)
+{
+  VALIDATE_SUCCESS(this->data, "Should get accepted, as normal interests bring cert");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 2);
+  this->face.sentInterests.clear();
+
+  this->advanceClocks(1_h, 2); // expire validator caches
+
+  VALIDATE_SUCCESS(this->interest, "Should get accepted, as interests bring certs");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 2);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailure, T, Failures, CertificateFetcherFromNetworkFixture<T>)
+{
+  VALIDATE_FAILURE(this->data, "Should fail, as interests don't bring data");
+  // first interest + 3 retries
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
+
+  this->face.sentInterests.clear();
+
+  this->advanceClocks(1_h, 2); // expire validator caches
+
+  VALIDATE_FAILURE(this->interest, "Should fail, as interests don't bring data");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateFetcherFromNetwork
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/certificate-fetcher-offline.t.cpp b/tests/unit/security/v2/certificate-fetcher-offline.t.cpp
new file mode 100644
index 0000000..102a308
--- /dev/null
+++ b/tests/unit/security/v2/certificate-fetcher-offline.t.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/certificate-fetcher-offline.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+
+class CertificateFetcherOfflineWrapper : public CertificateFetcherOffline
+{
+public:
+  CertificateFetcherOfflineWrapper(Face&)
+  {
+  }
+};
+
+using CertificateFetcherOfflineFixture = HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy,
+                                                                      CertificateFetcherOfflineWrapper>;
+
+BOOST_FIXTURE_TEST_SUITE(TestCertificateFetcherOffline, CertificateFetcherOfflineFixture)
+
+typedef boost::mpl::vector<Interest, Data> Packets;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
+{
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Packet");
+
+  Packet packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(subIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as no cert should be requested");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 0);
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(identity));
+  VALIDATE_SUCCESS(packet, "Should succeed, as signed by trust anchor");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateFetcherOffline
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/certificate.t.cpp b/tests/unit/security/v2/certificate.t.cpp
new file mode 100644
index 0000000..e677aed
--- /dev/null
+++ b/tests/unit/security/v2/certificate.t.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ */
+
+#include "security/v2/certificate.hpp"
+
+#include "boost-test.hpp"
+#include "../../unit-test-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_FIXTURE_TEST_SUITE(TestCertificate, UnitTestTimeFixture)
+
+const uint8_t PUBLIC_KEY[] = {
+  0x30, 0x81, 0x9d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x81, 0x8b, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0x9e,
+  0x06, 0x3e, 0x47, 0x85, 0xb2, 0x34, 0x37, 0xaa, 0x85, 0x47, 0xac, 0x03, 0x24, 0x83, 0xb5,
+  0x9c, 0xa8, 0x05, 0x3a, 0x24, 0x1e, 0xeb, 0x89, 0x01, 0xbb, 0xe9, 0x9b, 0xb2, 0xc3, 0x22,
+  0xac, 0x68, 0xe3, 0xf0, 0x6c, 0x02, 0xce, 0x68, 0xa6, 0xc4, 0xd0, 0xa7, 0x06, 0x90, 0x9c,
+  0xaa, 0x1b, 0x08, 0x1d, 0x8b, 0x43, 0x9a, 0x33, 0x67, 0x44, 0x6d, 0x21, 0xa3, 0x1b, 0x88,
+  0x9a, 0x97, 0x5e, 0x59, 0xc4, 0x15, 0x0b, 0xd9, 0x2c, 0xbd, 0x51, 0x07, 0x61, 0x82, 0xad,
+  0xc1, 0xb8, 0xd7, 0xbf, 0x9b, 0xcf, 0x7d, 0x24, 0xc2, 0x63, 0xf3, 0x97, 0x17, 0xeb, 0xfe,
+  0x62, 0x25, 0xba, 0x5b, 0x4d, 0x8a, 0xc2, 0x7a, 0xbd, 0x43, 0x8a, 0x8f, 0xb8, 0xf2, 0xf1,
+  0xc5, 0x6a, 0x30, 0xd3, 0x50, 0x8c, 0xc8, 0x9a, 0xdf, 0xef, 0xed, 0x35, 0xe7, 0x7a, 0x62,
+  0xea, 0x76, 0x7c, 0xbb, 0x08, 0x26, 0xc7, 0x02, 0x01, 0x11
+};
+
+const uint8_t SIG_INFO[] = {
+  0x16, 0x55, 0x1B, 0x01, 0x01, 0x1C, 0x26, 0x07, 0x24, 0x08, 0x03, 0x6E, 0x64, 0x6E, 0x08, 0x05,
+  0x73, 0x69, 0x74, 0x65, 0x31, 0x08, 0x11, 0x6B, 0x73, 0x6B, 0x2D, 0x32, 0x35, 0x31, 0x36, 0x34,
+  0x32, 0x35, 0x33, 0x37, 0x37, 0x30, 0x39, 0x34, 0x08, 0x03, 0x4B, 0x45, 0x59, 0xFD, 0x00, 0xFD,
+  0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x32, 0x30, 0x31, 0x35, 0x30, 0x38, 0x31, 0x34, 0x54, 0x32, 0x32,
+  0x33, 0x37, 0x33, 0x39, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x31, 0x35, 0x30, 0x38, 0x31, 0x38,
+  0x54, 0x32, 0x32, 0x33, 0x37, 0x33, 0x38
+};
+
+const uint8_t SIG_VALUE[] = {
+  0x17, 0x80, // SignatureValue
+    0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+    0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+    0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+    0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+    0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+    0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+    0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+    0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+    0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+    0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1
+};
+
+const uint8_t CERT[] = {
+  0x06, 0xFD, 0x01, 0xBB, // Data
+    0x07, 0x33, // Name /ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B
+      0x08, 0x03, 0x6E, 0x64, 0x6E,
+      0x08, 0x05, 0x73, 0x69, 0x74, 0x65, 0x31,
+      0x08, 0x03, 0x4B, 0x45, 0x59,
+      0x08, 0x11,
+        0x6B, 0x73, 0x6B, 0x2D, 0x31, 0x34, 0x31, 0x36, 0x34, 0x32, 0x35, 0x33, 0x37, 0x37, 0x30, 0x39,
+        0x34,
+      0x08, 0x04, 0x30, 0x31, 0x32, 0x33,
+      0x08, 0x07, 0xFD, 0x00, 0x00, 0x01, 0x49, 0xC9, 0x8B,
+    0x14, 0x09, // MetaInfo
+      0x18, 0x01, 0x02, // ContentType = Key
+      0x19, 0x04, 0x00, 0x36, 0xEE, 0x80, // FreshnessPeriod = 3600000 ms
+    0x15, 0xA0, // Content
+      0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
+      0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0x9E, 0x06, 0x3E,
+      0x47, 0x85, 0xB2, 0x34, 0x37, 0xAA, 0x85, 0x47, 0xAC, 0x03, 0x24, 0x83, 0xB5, 0x9C, 0xA8, 0x05,
+      0x3A, 0x24, 0x1E, 0xEB, 0x89, 0x01, 0xBB, 0xE9, 0x9B, 0xB2, 0xC3, 0x22, 0xAC, 0x68, 0xE3, 0xF0,
+      0x6C, 0x02, 0xCE, 0x68, 0xA6, 0xC4, 0xD0, 0xA7, 0x06, 0x90, 0x9C, 0xAA, 0x1B, 0x08, 0x1D, 0x8B,
+      0x43, 0x9A, 0x33, 0x67, 0x44, 0x6D, 0x21, 0xA3, 0x1B, 0x88, 0x9A, 0x97, 0x5E, 0x59, 0xC4, 0x15,
+      0x0B, 0xD9, 0x2C, 0xBD, 0x51, 0x07, 0x61, 0x82, 0xAD, 0xC1, 0xB8, 0xD7, 0xBF, 0x9B, 0xCF, 0x7D,
+      0x24, 0xC2, 0x63, 0xF3, 0x97, 0x17, 0xEB, 0xFE, 0x62, 0x25, 0xBA, 0x5B, 0x4D, 0x8A, 0xC2, 0x7A,
+      0xBD, 0x43, 0x8A, 0x8F, 0xB8, 0xF2, 0xF1, 0xC5, 0x6A, 0x30, 0xD3, 0x50, 0x8C, 0xC8, 0x9A, 0xDF,
+      0xEF, 0xED, 0x35, 0xE7, 0x7A, 0x62, 0xEA, 0x76, 0x7C, 0xBB, 0x08, 0x26, 0xC7, 0x02, 0x01, 0x11,
+    0x16, 0x55, // SignatureInfo
+      0x1B, 0x01, 0x01, // SignatureType
+      0x1C, 0x26, // KeyLocator: /ndn/site1/KEY/ksk-2516425377094
+        0x07, 0x24,
+          0x08, 0x03, 0x6E, 0x64, 0x6E,
+          0x08, 0x05, 0x73, 0x69, 0x74, 0x65, 0x31,
+          0x08, 0x03, 0x4B, 0x45, 0x59,
+          0x08, 0x11,
+            0x6B, 0x73, 0x6B, 0x2D, 0x32, 0x35, 0x31, 0x36, 0x34, 0x32, 0x35, 0x33, 0x37, 0x37, 0x30, 0x39,
+            0x34,
+      0xFD, 0x00, 0xFD, 0x26, // ValidityPeriod: (20150814T223739, 20150818T223738)
+        0xFD, 0x00, 0xFE, 0x0F,
+          0x32, 0x30, 0x31, 0x35, 0x30, 0x38, 0x31, 0x34, 0x54, 0x32, 0x32, 0x33, 0x37, 0x33, 0x39,
+        0xFD, 0x00, 0xFF, 0x0F,
+          0x32, 0x30, 0x31, 0x35, 0x30, 0x38, 0x31, 0x38, 0x54, 0x32, 0x32, 0x33, 0x37, 0x33, 0x38,
+    0x17, 0x80, // SignatureValue
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+Signature
+generateFakeSignature()
+{
+  Block block1(SIG_INFO, sizeof(SIG_INFO));
+  SignatureInfo signatureInfo(block1);
+
+  Name keyLocatorName("/ndn/site1/KEY/ksk-2516425377094");
+  KeyLocator keyLocator(keyLocatorName);
+  signatureInfo.setKeyLocator(keyLocator);
+
+  ValidityPeriod period(time::fromIsoString("20141111T050000"), time::fromIsoString("20141111T060000"));
+  signatureInfo.setValidityPeriod(period);
+
+  Signature signature(signatureInfo);
+  Block block2(SIG_VALUE, sizeof(SIG_VALUE));
+  signature.setValue(block2);
+
+  return signature;
+}
+
+BOOST_AUTO_TEST_CASE(Construction)
+{
+  Block block(CERT, sizeof(CERT));
+  Certificate certificate(block);
+
+  BOOST_CHECK_EQUAL(certificate.getName(), "/ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B");
+  BOOST_CHECK_EQUAL(certificate.getKeyName(), "/ndn/site1/KEY/ksk-1416425377094");
+  BOOST_CHECK_EQUAL(certificate.getIdentity(), "/ndn/site1");
+  BOOST_CHECK_EQUAL(certificate.getIssuerId(), name::Component("0123"));
+  BOOST_CHECK_EQUAL(certificate.getKeyId(), name::Component("ksk-1416425377094"));
+  BOOST_CHECK_EQUAL(certificate.getSignature().getKeyLocator().getName(), "/ndn/site1/KEY/ksk-2516425377094");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(certificate.getValidityPeriod()), "(20150814T223739, 20150818T223738)");
+
+  BOOST_CHECK_THROW(certificate.getExtension(12345), ndn::SignatureInfo::Error);
+  BOOST_CHECK_NO_THROW(certificate.getPublicKey());
+
+  Data data(block);
+  Certificate certificate2(std::move(data));
+  BOOST_CHECK_EQUAL(certificate, certificate2);
+}
+
+BOOST_AUTO_TEST_CASE(Setters)
+{
+  Certificate certificate;
+  certificate.setName("/ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B");
+  certificate.setFreshnessPeriod(1_h);
+  certificate.setContent(PUBLIC_KEY, sizeof(PUBLIC_KEY));
+  certificate.setSignature(generateFakeSignature());
+
+  BOOST_CHECK_EQUAL(certificate.getName(), "/ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B");
+  BOOST_CHECK_EQUAL(certificate.getKeyName(), "/ndn/site1/KEY/ksk-1416425377094");
+  BOOST_CHECK_EQUAL(certificate.getIdentity(), "/ndn/site1");
+  BOOST_CHECK_EQUAL(certificate.getIssuerId(), name::Component("0123"));
+  BOOST_CHECK_EQUAL(certificate.getKeyId(), name::Component("ksk-1416425377094"));
+  BOOST_CHECK_EQUAL(certificate.getSignature().getKeyLocator().getName(), "/ndn/site1/KEY/ksk-2516425377094");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(certificate.getValidityPeriod()), "(20141111T050000, 20141111T060000)");
+
+  BOOST_CHECK_THROW(certificate.getExtension(12345), ndn::SignatureInfo::Error);
+  BOOST_CHECK_NO_THROW(certificate.getPublicKey());
+}
+
+BOOST_AUTO_TEST_CASE(ValidityPeriodChecking)
+{
+  Certificate certificate;
+  certificate.setName("/ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B");
+  certificate.setFreshnessPeriod(1_h);
+  certificate.setContent(PUBLIC_KEY, sizeof(PUBLIC_KEY));
+  certificate.setSignature(generateFakeSignature());
+
+  BOOST_CHECK_EQUAL(certificate.isValid(), true);
+  BOOST_CHECK_EQUAL(certificate.isValid(time::fromIsoString("20141111T045959")), false);
+  BOOST_CHECK_EQUAL(certificate.isValid(time::fromIsoString("20141111T060001")), false);
+}
+
+// This fixture prepares a well-formed certificate. A test case then modifies one of the
+// fields, and verifies the Certificate class correctly identifies the certificate as
+// malformed.
+class InvalidCertFixture
+{
+public:
+  InvalidCertFixture()
+  {
+    Certificate certBase(Block(CERT, sizeof(CERT)));
+    BOOST_CHECK_NO_THROW((Certificate(certBase)));
+
+    m_certBase = Data(certBase);
+    m_certBase.setSignature(generateFakeSignature());
+
+    BOOST_CHECK_NO_THROW((Certificate(m_certBase)));
+  }
+
+public:
+  Data m_certBase;
+};
+
+BOOST_FIXTURE_TEST_CASE(InvalidName, InvalidCertFixture)
+{
+  Data data(m_certBase);
+  data.setName("/ndn/site1/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B");
+  data.setSignature(generateFakeSignature());
+
+  BOOST_CHECK_THROW((Certificate(data)), Certificate::Error);
+  BOOST_CHECK_THROW((Certificate(std::move(data))), Certificate::Error);
+}
+
+BOOST_FIXTURE_TEST_CASE(InvalidType, InvalidCertFixture)
+{
+  Data data(m_certBase);
+  data.setContentType(tlv::ContentType_Blob);
+  data.setSignature(generateFakeSignature());
+
+  BOOST_CHECK_THROW((Certificate(data)), Certificate::Error);
+  BOOST_CHECK_THROW((Certificate(std::move(data))), Certificate::Error);
+}
+
+BOOST_FIXTURE_TEST_CASE(EmptyContent, InvalidCertFixture)
+{
+  Data data(m_certBase);
+  data.setContent(nullptr, 0);
+  data.setSignature(generateFakeSignature());
+
+  BOOST_CHECK_THROW((Certificate(data)), Certificate::Error);
+  BOOST_CHECK_THROW((Certificate(std::move(data))), Certificate::Error);
+
+  Certificate cert(m_certBase);
+  cert.setContent(nullptr, 0);
+  cert.setSignature(generateFakeSignature());
+  BOOST_CHECK_THROW(cert.getPublicKey(), Certificate::Error);
+}
+
+BOOST_AUTO_TEST_CASE(PrintCertificateInfo)
+{
+  const std::string expectedCertificateInfo = std::string(R"INFO(
+Certificate name:
+  /ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B
+Validity:
+  NotBefore: 20150814T223739
+  NotAfter: 20150818T223738
+Public key bits:
+  MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCeBj5HhbI0N6qFR6wDJIO1nKgF
+  OiQe64kBu+mbssMirGjj8GwCzmimxNCnBpCcqhsIHYtDmjNnRG0hoxuImpdeWcQV
+  C9ksvVEHYYKtwbjXv5vPfSTCY/OXF+v+YiW6W02Kwnq9Q4qPuPLxxWow01CMyJrf
+  7+0153pi6nZ8uwgmxwIBEQ==
+Signature Information:
+  Signature Type: SignatureSha256WithRsa
+  Key Locator: Name=/ndn/site1/KEY/ksk-2516425377094
+)INFO").substr(1);
+
+  Certificate certificate(Block(CERT, sizeof(CERT)));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(certificate), expectedCertificateInfo);
+
+  // @todo Check output formats of other certificates
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificate
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/key-chain.t.cpp b/tests/unit/security/v2/key-chain.t.cpp
new file mode 100644
index 0000000..649ef7b
--- /dev/null
+++ b/tests/unit/security/v2/key-chain.t.cpp
@@ -0,0 +1,445 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/key-chain.hpp"
+#include "security/signing-helpers.hpp"
+#include "security/verification-helpers.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include "../../test-home-env-saver.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_FIXTURE_TEST_SUITE(TestKeyChain, TestHomeEnvSaver)
+
+template<class Path>
+class TestHomeAndPibFixture : public TestHomeFixture<Path>
+{
+public:
+  TestHomeAndPibFixture()
+  {
+    unsetenv("NDN_CLIENT_PIB");
+    unsetenv("NDN_CLIENT_TPM");
+  }
+
+  ~TestHomeAndPibFixture()
+  {
+    try {
+      const_cast<std::string&>(KeyChain::getDefaultPibLocator()).clear();
+    }
+    catch (const KeyChain::Error&) {
+      // ignore
+    }
+
+    try {
+      const_cast<std::string&>(KeyChain::getDefaultTpmLocator()).clear();
+    }
+    catch (const KeyChain::Error&) {
+      // ignore
+    }
+  }
+};
+
+struct PibPathConfigFileHome
+{
+  const std::string PATH = "build/config-file-home/";
+};
+
+BOOST_FIXTURE_TEST_CASE(ConstructorNormalConfig, TestHomeAndPibFixture<PibPathConfigFileHome>)
+{
+  createClientConf({"pib=pib-memory:", "tpm=tpm-memory:"});
+
+  BOOST_REQUIRE_NO_THROW(KeyChain());
+
+  KeyChain keyChain;
+  BOOST_CHECK_EQUAL(keyChain.getPib().getPibLocator(), "pib-memory:");
+  BOOST_CHECK_EQUAL(keyChain.getPib().getTpmLocator(), "tpm-memory:");
+  BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-memory:");
+}
+
+struct PibPathConfigFileEmptyHome
+{
+  const std::string PATH = "build/config-file-empty-home/";
+};
+
+BOOST_FIXTURE_TEST_CASE(ConstructorEmptyConfig, TestHomeAndPibFixture<PibPathConfigFileEmptyHome>)
+{
+  createClientConf({"pib=pib-memory:"});
+
+#if defined(NDN_CXX_HAVE_OSX_FRAMEWORKS)
+  std::string oldHOME;
+  if (std::getenv("OLD_HOME"))
+    oldHOME = std::getenv("OLD_HOME");
+
+  std::string HOME;
+  if (std::getenv("HOME"))
+    HOME = std::getenv("HOME");
+
+  if (!oldHOME.empty())
+    setenv("HOME", oldHOME.c_str(), 1);
+  else
+    unsetenv("HOME");
+#endif
+
+  BOOST_REQUIRE_NO_THROW(KeyChain());
+  KeyChain keyChain;
+  BOOST_CHECK_EQUAL(keyChain.getPib().getPibLocator(), "pib-memory:");
+
+#if defined(NDN_CXX_HAVE_OSX_FRAMEWORKS)
+  BOOST_CHECK_EQUAL(keyChain.getPib().getTpmLocator(), "tpm-osxkeychain:");
+  BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-osxkeychain:");
+#else
+  BOOST_CHECK_EQUAL(keyChain.getPib().getTpmLocator(), "tpm-file:");
+  BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-file:");
+#endif
+
+#if defined(NDN_CXX_HAVE_OSX_FRAMEWORKS)
+  if (!HOME.empty())
+    setenv("HOME", HOME.c_str(), 1);
+  else
+    unsetenv("HOME");
+
+  if (!oldHOME.empty())
+    setenv("OLD_HOME", oldHOME.c_str(), 1);
+  else
+    unsetenv("OLD_HOME");
+#endif
+}
+
+struct PibPathConfigFileEmpty2Home
+{
+  const std::string PATH = "build/config-file-empty2-home/";
+};
+
+BOOST_FIXTURE_TEST_CASE(ConstructorEmpty2Config, TestHomeAndPibFixture<PibPathConfigFileEmpty2Home>)
+{
+  createClientConf({"tpm=tpm-memory:"});
+
+  BOOST_REQUIRE_NO_THROW(KeyChain());
+
+  KeyChain keyChain;
+  BOOST_CHECK_EQUAL(keyChain.getPib().getPibLocator(), "pib-sqlite3:");
+  BOOST_CHECK_EQUAL(keyChain.getPib().getTpmLocator(), "tpm-memory:");
+  BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-memory:");
+}
+
+struct PibPathConfigFileMalformedHome
+{
+  const std::string PATH = "build/config-file-malformed-home/";
+};
+
+BOOST_FIXTURE_TEST_CASE(ConstructorMalConfig, TestHomeAndPibFixture<PibPathConfigFileMalformedHome>)
+{
+  createClientConf({"pib=lord", "tpm=ring"});
+
+  BOOST_REQUIRE_THROW(KeyChain(), KeyChain::Error); // Wrong configuration. Error expected.
+}
+
+struct PibPathConfigFileMalformed2Home
+{
+  const std::string PATH = "build/config-file-malformed2-home/";
+};
+
+BOOST_FIXTURE_TEST_CASE(ConstructorMal2Config, TestHomeAndPibFixture<PibPathConfigFileMalformed2Home>)
+{
+  createClientConf({"pib=pib-sqlite3:%PATH%", "tpm=just-wrong"});
+
+  BOOST_REQUIRE_THROW(KeyChain(), KeyChain::Error); // Wrong configuration. Error expected.
+}
+
+struct PibPathConfigFileNonCanonicalTpm
+{
+  const std::string PATH = "build/config-file-non-canonical-tpm/";
+};
+
+BOOST_FIXTURE_TEST_CASE(ConstructorNonCanonicalTpm, TestHomeAndPibFixture<PibPathConfigFileNonCanonicalTpm>) // Bug 4297
+{
+  createClientConf({"pib=pib-sqlite3:", "tpm=tpm-file"});
+
+  {
+    KeyChain keyChain;
+    keyChain.createIdentity("/test");
+    BOOST_CHECK_EQUAL(keyChain.getPib().getPibLocator(), "pib-sqlite3:");
+    BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-file:");
+  }
+
+  {
+    KeyChain keyChain;
+    BOOST_CHECK_EQUAL(keyChain.getPib().getPibLocator(), "pib-sqlite3:");
+    BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-file:");
+    BOOST_CHECK(keyChain.getPib().getIdentities().find("/test") != keyChain.getPib().getIdentities().end());
+  }
+}
+
+BOOST_AUTO_TEST_CASE(KeyChainWithCustomTpmAndPib)
+{
+  BOOST_REQUIRE_NO_THROW((KeyChain("pib-memory", "tpm-memory")));
+  BOOST_REQUIRE_NO_THROW((KeyChain("pib-memory:", "tpm-memory:")));
+  BOOST_REQUIRE_NO_THROW((KeyChain("pib-memory:/something", "tpm-memory:/something")));
+
+  KeyChain keyChain("pib-memory", "tpm-memory");
+  BOOST_CHECK_EQUAL(keyChain.getPib().getPibLocator(), "pib-memory:");
+  BOOST_CHECK_EQUAL(keyChain.getPib().getTpmLocator(), "tpm-memory:");
+  BOOST_CHECK_EQUAL(keyChain.getTpm().getTpmLocator(), "tpm-memory:");
+}
+
+BOOST_FIXTURE_TEST_CASE(Management, IdentityManagementFixture)
+{
+  Name identityName("/test/id");
+  Name identity2Name("/test/id2");
+
+  BOOST_CHECK_EQUAL(m_keyChain.getPib().getIdentities().size(), 0);
+  BOOST_REQUIRE_THROW(m_keyChain.getPib().getDefaultIdentity(), Pib::Error);
+
+  // Create identity
+  Identity id = m_keyChain.createIdentity(identityName);
+  BOOST_CHECK(id);
+  BOOST_CHECK(m_keyChain.getPib().getIdentities().find(identityName) != m_keyChain.getPib().getIdentities().end());
+  // The first added identity becomes the default identity
+  BOOST_REQUIRE_NO_THROW(m_keyChain.getPib().getDefaultIdentity());
+  // The default key of the added identity must exist
+  Key key;
+  BOOST_REQUIRE_NO_THROW(key = id.getDefaultKey());
+  // The default certificate of the default key must exist
+  BOOST_REQUIRE_NO_THROW(key.getDefaultCertificate());
+
+  // Delete key
+  Name key1Name = key.getName();
+  BOOST_CHECK_NO_THROW(id.getKey(key1Name));
+  BOOST_CHECK_EQUAL(id.getKeys().size(), 1);
+  m_keyChain.deleteKey(id, key);
+  // The key instance should not be valid any more
+  BOOST_CHECK(!key);
+  BOOST_CHECK_THROW(id.getKey(key1Name), Pib::Error);
+  BOOST_CHECK_EQUAL(id.getKeys().size(), 0);
+
+  // Create another key
+  m_keyChain.createKey(id);
+  // The added key becomes the default key.
+  BOOST_REQUIRE_NO_THROW(id.getDefaultKey());
+  Key key2 = id.getDefaultKey();
+  BOOST_REQUIRE(key2);
+  BOOST_CHECK_NE(key2.getName(), key1Name);
+  BOOST_CHECK_EQUAL(id.getKeys().size(), 1);
+  BOOST_REQUIRE_NO_THROW(key2.getDefaultCertificate());
+
+  // Create the third key
+  Key key3 = m_keyChain.createKey(id);
+  BOOST_CHECK_NE(key3.getName(), key2.getName());
+  // The added key will not be the default key, because the default key already exists
+  BOOST_CHECK_EQUAL(id.getDefaultKey().getName(), key2.getName());
+  BOOST_CHECK_EQUAL(id.getKeys().size(), 2);
+  BOOST_REQUIRE_NO_THROW(key3.getDefaultCertificate());
+
+  // Delete cert
+  BOOST_CHECK_EQUAL(key3.getCertificates().size(), 1);
+  Certificate key3Cert1 = *key3.getCertificates().begin();
+  Name key3CertName = key3Cert1.getName();
+  m_keyChain.deleteCertificate(key3, key3CertName);
+  BOOST_CHECK_EQUAL(key3.getCertificates().size(), 0);
+  BOOST_REQUIRE_THROW(key3.getDefaultCertificate(), Pib::Error);
+
+  // Add cert
+  m_keyChain.addCertificate(key3, key3Cert1);
+  BOOST_CHECK_EQUAL(key3.getCertificates().size(), 1);
+  BOOST_REQUIRE_NO_THROW(key3.getDefaultCertificate());
+  m_keyChain.addCertificate(key3, key3Cert1); // overwriting the cert should work
+  BOOST_CHECK_EQUAL(key3.getCertificates().size(), 1);
+  // Add another cert
+  Certificate key3Cert2 = key3Cert1;
+  Name key3Cert2Name = key3.getName();
+  key3Cert2Name.append("Self");
+  key3Cert2Name.appendVersion();
+  key3Cert2.setName(key3Cert2Name);
+  m_keyChain.addCertificate(key3, key3Cert2);
+  BOOST_CHECK_EQUAL(key3.getCertificates().size(), 2);
+
+  // Default certificate setting
+  BOOST_CHECK_EQUAL(key3.getDefaultCertificate().getName(), key3CertName);
+  m_keyChain.setDefaultCertificate(key3, key3Cert2);
+  BOOST_CHECK_EQUAL(key3.getDefaultCertificate().getName(), key3Cert2Name);
+
+  // Default key setting
+  BOOST_CHECK_EQUAL(id.getDefaultKey().getName(), key2.getName());
+  m_keyChain.setDefaultKey(id, key3);
+  BOOST_CHECK_EQUAL(id.getDefaultKey().getName(), key3.getName());
+
+  // Default identity setting
+  Identity id2 = m_keyChain.createIdentity(identity2Name);
+  BOOST_CHECK_EQUAL(m_keyChain.getPib().getDefaultIdentity().getName(), id.getName());
+  m_keyChain.setDefaultIdentity(id2);
+  BOOST_CHECK_EQUAL(m_keyChain.getPib().getDefaultIdentity().getName(), id2.getName());
+
+  // Delete identity
+  m_keyChain.deleteIdentity(id);
+  // The identity instance should not be valid any more
+  BOOST_CHECK(!id);
+  BOOST_REQUIRE_THROW(m_keyChain.getPib().getIdentity(identityName), Pib::Error);
+  BOOST_CHECK(m_keyChain.getPib().getIdentities().find(identityName) == m_keyChain.getPib().getIdentities().end());
+}
+
+BOOST_FIXTURE_TEST_CASE(GeneralSigningInterface, IdentityManagementFixture)
+{
+  Identity id = addIdentity("/id");
+  Key key = id.getDefaultKey();
+  Certificate cert = key.getDefaultCertificate();
+
+  std::list<SigningInfo> signingInfos = {
+    SigningInfo(),
+
+    SigningInfo(SigningInfo::SIGNER_TYPE_ID, id.getName()),
+    signingByIdentity(id.getName()),
+
+    SigningInfo(id),
+    signingByIdentity(id),
+
+    SigningInfo(SigningInfo::SIGNER_TYPE_KEY, key.getName()),
+    signingByKey(key.getName()),
+
+    SigningInfo(key),
+    signingByKey(key),
+
+    SigningInfo(SigningInfo::SIGNER_TYPE_CERT, cert.getName()),
+    signingByCertificate(cert.getName()),
+    signingByCertificate(cert),
+
+    SigningInfo(SigningInfo::SIGNER_TYPE_SHA256),
+    signingWithSha256()
+  };
+
+  for (const auto& signingInfo : signingInfos) {
+    BOOST_TEST_MESSAGE("SigningInfo: " << signingInfo);
+    Data data("/data");
+    Interest interest("/interest");
+
+    if (signingInfo.getSignerType() == SigningInfo::SIGNER_TYPE_NULL) {
+      m_keyChain.sign(data);
+      m_keyChain.sign(interest);
+    }
+    else {
+      m_keyChain.sign(data, signingInfo);
+      m_keyChain.sign(interest, signingInfo);
+    }
+
+    Signature interestSignature(interest.getName()[-2].blockFromValue(), interest.getName()[-1].blockFromValue());
+
+    if (signingInfo.getSignerType() == SigningInfo::SIGNER_TYPE_SHA256) {
+      BOOST_CHECK_EQUAL(data.getSignature().getType(), tlv::DigestSha256);
+      BOOST_CHECK_EQUAL(interestSignature.getType(), tlv::DigestSha256);
+
+      BOOST_CHECK(verifyDigest(data, DigestAlgorithm::SHA256));
+      BOOST_CHECK(verifyDigest(interest, DigestAlgorithm::SHA256));
+    }
+    else {
+      BOOST_CHECK_EQUAL(data.getSignature().getType(), tlv::SignatureSha256WithEcdsa);
+      BOOST_CHECK_EQUAL(interestSignature.getType(), tlv::SignatureSha256WithEcdsa);
+
+      BOOST_CHECK_EQUAL(data.getSignature().getKeyLocator().getName(), cert.getName().getPrefix(-2));
+      BOOST_CHECK_EQUAL(interestSignature.getKeyLocator().getName(), cert.getName().getPrefix(-2));
+
+      BOOST_CHECK(verifySignature(data, key));
+      BOOST_CHECK(verifySignature(interest, key));
+    }
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE(PublicKeySigningDefaults, IdentityManagementFixture)
+{
+  Data data("/test/data");
+
+  // Identity will be created with generated key and self-signed cert with default parameters
+  BOOST_CHECK_THROW(m_keyChain.sign(data, signingByIdentity("/non-existing/identity")), KeyChain::InvalidSigningInfoError);
+
+  // Create identity with EC key and the corresponding self-signed certificate
+  Identity id = addIdentity("/ndn/test/ec", EcKeyParams());
+  BOOST_CHECK_NO_THROW(m_keyChain.sign(data, signingByIdentity(id.getName())));
+  BOOST_CHECK_EQUAL(data.getSignature().getType(),
+                    KeyChain::getSignatureType(EcKeyParams().getKeyType(), DigestAlgorithm::SHA256));
+  BOOST_CHECK(id.getName().isPrefixOf(data.getSignature().getKeyLocator().getName()));
+
+  // Create identity with RSA key and the corresponding self-signed certificate
+  id = addIdentity("/ndn/test/rsa", RsaKeyParams());
+  BOOST_CHECK_NO_THROW(m_keyChain.sign(data, signingByIdentity(id.getName())));
+  BOOST_CHECK_EQUAL(data.getSignature().getType(),
+                    KeyChain::getSignatureType(RsaKeyParams().getKeyType(), DigestAlgorithm::SHA256));
+  BOOST_CHECK(id.getName().isPrefixOf(data.getSignature().getKeyLocator().getName()));
+}
+
+BOOST_FIXTURE_TEST_CASE(ExportImport, IdentityManagementFixture)
+{
+  Identity id = addIdentity("/TestKeyChain/ExportIdentity/");
+  Certificate cert = id.getDefaultKey().getDefaultCertificate();
+
+  shared_ptr<SafeBag> exported = m_keyChain.exportSafeBag(cert, "1234", 4);
+  Block block = exported->wireEncode();
+
+  m_keyChain.deleteIdentity(id);
+  BOOST_CHECK_THROW(m_keyChain.exportSafeBag(cert, "1234", 4), KeyChain::Error);
+
+  BOOST_CHECK_EQUAL(m_keyChain.getTpm().hasKey(cert.getKeyName()), false);
+  BOOST_CHECK_EQUAL(m_keyChain.getPib().getIdentities().size(), 0);
+
+  SafeBag imported;
+  imported.wireDecode(block);
+  m_keyChain.importSafeBag(imported, "1234", 4);
+  BOOST_CHECK_THROW(m_keyChain.importSafeBag(imported, "1234", 4), KeyChain::Error);
+
+  BOOST_CHECK_EQUAL(m_keyChain.getTpm().hasKey(cert.getKeyName()), true);
+  BOOST_CHECK_EQUAL(m_keyChain.getPib().getIdentities().size(), 1);
+  BOOST_REQUIRE_NO_THROW(m_keyChain.getPib().getIdentity(cert.getIdentity()));
+  Identity newId = m_keyChain.getPib().getIdentity(cert.getIdentity());
+  BOOST_CHECK_EQUAL(newId.getKeys().size(), 1);
+  BOOST_REQUIRE_NO_THROW(newId.getKey(cert.getKeyName()));
+  Key newKey = newId.getKey(cert.getKeyName());
+  BOOST_CHECK_EQUAL(newKey.getCertificates().size(), 1);
+  BOOST_REQUIRE_NO_THROW(newKey.getCertificate(cert.getName()));
+
+  m_keyChain.deleteIdentity(newId);
+  BOOST_CHECK_EQUAL(m_keyChain.getPib().getIdentities().size(), 0);
+  BOOST_CHECK_EQUAL(m_keyChain.getTpm().hasKey(cert.getKeyName()), false);
+}
+
+BOOST_FIXTURE_TEST_CASE(SelfSignedCertValidity, IdentityManagementFixture)
+{
+  Certificate cert = addIdentity("/Security/V2/TestKeyChain/SelfSignedCertValidity")
+                       .getDefaultKey()
+                       .getDefaultCertificate();
+  BOOST_CHECK(cert.isValid());
+  BOOST_CHECK(cert.isValid(time::system_clock::now() + 10 * 365_days));
+  BOOST_CHECK_GT(cert.getValidityPeriod().getPeriod().second, time::system_clock::now() + 10 * 365_days);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestKeyChain
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/trust-anchor-container.t.cpp b/tests/unit/security/v2/trust-anchor-container.t.cpp
new file mode 100644
index 0000000..efd6ca3
--- /dev/null
+++ b/tests/unit/security/v2/trust-anchor-container.t.cpp
@@ -0,0 +1,198 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/trust-anchor-container.hpp"
+#include "util/io.hpp"
+
+#include "../../identity-management-time-fixture.hpp"
+#include "boost-test.hpp"
+
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+
+/**
+ * This fixture creates a directory and prepares two certificates.
+ * cert1 is written to a file under the directory, while cert2 is not.
+ */
+class AnchorContainerTestFixture : public IdentityManagementTimeFixture
+{
+public:
+  AnchorContainerTestFixture()
+  {
+    namespace fs = boost::filesystem;
+
+    fs::create_directory(fs::path(UNIT_TEST_CONFIG_PATH));
+
+    certDirPath = fs::path(UNIT_TEST_CONFIG_PATH) / "test-cert-dir";
+    fs::create_directory(certDirPath);
+
+    certPath1 = fs::path(UNIT_TEST_CONFIG_PATH) / "test-cert-dir" / "trust-anchor-1.cert";
+    certPath2 = fs::path(UNIT_TEST_CONFIG_PATH) / "test-cert-dir" / "trust-anchor-2.cert";
+
+    identity1 = addIdentity("/TestAnchorContainer/First");
+    cert1 = identity1.getDefaultKey().getDefaultCertificate();
+    saveCertToFile(cert1, certPath1.string());
+
+    identity2 = addIdentity("/TestAnchorContainer/Second");
+    cert2 = identity2.getDefaultKey().getDefaultCertificate();
+    saveCertToFile(cert2, certPath2.string());
+  }
+
+  ~AnchorContainerTestFixture()
+  {
+    boost::filesystem::remove_all(UNIT_TEST_CONFIG_PATH);
+  }
+
+public:
+  TrustAnchorContainer anchorContainer;
+
+  boost::filesystem::path certDirPath;
+  boost::filesystem::path certPath1;
+  boost::filesystem::path certPath2;
+
+  Identity identity1;
+  Identity identity2;
+
+  Certificate cert1;
+  Certificate cert2;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestTrustAnchorContainer, AnchorContainerTestFixture)
+
+// one static group and one dynamic group created from file
+BOOST_AUTO_TEST_CASE(Insert)
+{
+  // Static
+  anchorContainer.insert("group1", Certificate(cert1));
+  BOOST_CHECK(anchorContainer.find(cert1.getName()) != nullptr);
+  BOOST_CHECK(anchorContainer.find(identity1.getName()) != nullptr);
+  const Certificate* cert = anchorContainer.find(cert1.getName());
+  BOOST_CHECK_NO_THROW(anchorContainer.insert("group1", Certificate(cert1)));
+  BOOST_CHECK_EQUAL(cert, anchorContainer.find(cert1.getName())); // still the same instance of the certificate
+  // cannot add dynamic group when static already exists
+  BOOST_CHECK_THROW(anchorContainer.insert("group1", certPath1.string(), 1_s), TrustAnchorContainer::Error);
+  BOOST_CHECK_EQUAL(anchorContainer.getGroup("group1").size(), 1);
+  BOOST_CHECK_EQUAL(anchorContainer.size(), 1);
+
+  // From file
+  anchorContainer.insert("group2", certPath2.string(), 1_s);
+  BOOST_CHECK(anchorContainer.find(cert2.getName()) != nullptr);
+  BOOST_CHECK(anchorContainer.find(identity2.getName()) != nullptr);
+  BOOST_CHECK_THROW(anchorContainer.insert("group2", Certificate(cert2)), TrustAnchorContainer::Error);
+  BOOST_CHECK_THROW(anchorContainer.insert("group2", certPath2.string(), 1_s), TrustAnchorContainer::Error);
+  BOOST_CHECK_EQUAL(anchorContainer.getGroup("group2").size(), 1);
+  BOOST_CHECK_EQUAL(anchorContainer.size(), 2);
+
+  boost::filesystem::remove(certPath2);
+  advanceClocks(1_s, 11);
+
+  BOOST_CHECK(anchorContainer.find(identity2.getName()) == nullptr);
+  BOOST_CHECK(anchorContainer.find(cert2.getName()) == nullptr);
+  BOOST_CHECK_EQUAL(anchorContainer.getGroup("group2").size(), 0);
+  BOOST_CHECK_EQUAL(anchorContainer.size(), 1);
+
+  TrustAnchorGroup& group = anchorContainer.getGroup("group1");
+  auto staticGroup = dynamic_cast<StaticTrustAnchorGroup*>(&group);
+  BOOST_REQUIRE(staticGroup != nullptr);
+  BOOST_CHECK_EQUAL(staticGroup->size(), 1);
+  staticGroup->remove(cert1.getName());
+  BOOST_CHECK_EQUAL(staticGroup->size(), 0);
+  BOOST_CHECK_EQUAL(anchorContainer.size(), 0);
+
+  BOOST_CHECK_THROW(anchorContainer.getGroup("non-existing-group"), TrustAnchorContainer::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DynamicAnchorFromDir)
+{
+  boost::filesystem::remove(certPath2);
+
+  anchorContainer.insert("group", certDirPath.string(), 1_s, true /* isDir */);
+
+  BOOST_CHECK(anchorContainer.find(identity1.getName()) != nullptr);
+  BOOST_CHECK(anchorContainer.find(identity2.getName()) == nullptr);
+  BOOST_CHECK_EQUAL(anchorContainer.getGroup("group").size(), 1);
+
+  saveCertToFile(cert2, certPath2.string());
+
+  advanceClocks(100_ms, 11);
+
+  BOOST_CHECK(anchorContainer.find(identity1.getName()) != nullptr);
+  BOOST_CHECK(anchorContainer.find(identity2.getName()) != nullptr);
+  BOOST_CHECK_EQUAL(anchorContainer.getGroup("group").size(), 2);
+
+  boost::filesystem::remove_all(certDirPath);
+
+  advanceClocks(100_ms, 11);
+
+  BOOST_CHECK(anchorContainer.find(identity1.getName()) == nullptr);
+  BOOST_CHECK(anchorContainer.find(identity2.getName()) == nullptr);
+  BOOST_CHECK_EQUAL(anchorContainer.getGroup("group").size(), 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(FindByInterest, AnchorContainerTestFixture)
+{
+  anchorContainer.insert("group1", certPath1.string(), 1_s);
+  Interest interest(identity1.getName());
+  BOOST_CHECK(anchorContainer.find(interest) != nullptr);
+  Interest interest1(identity1.getName().getPrefix(-1));
+  BOOST_CHECK(anchorContainer.find(interest1) != nullptr);
+  Interest interest2(Name(identity1.getName()).appendVersion());
+  BOOST_CHECK(anchorContainer.find(interest2) == nullptr);
+
+  Certificate cert3 = addCertificate(identity1.getDefaultKey(), "3");
+  Certificate cert4 = addCertificate(identity1.getDefaultKey(), "4");
+  Certificate cert5 = addCertificate(identity1.getDefaultKey(), "5");
+
+  Certificate cert3Copy = cert3;
+  anchorContainer.insert("group2", std::move(cert3Copy));
+  anchorContainer.insert("group3", std::move(cert4));
+  anchorContainer.insert("group4", std::move(cert5));
+
+  Interest interest3(cert3.getKeyName());
+  const Certificate* foundCert = anchorContainer.find(interest3);
+  BOOST_REQUIRE(foundCert != nullptr);
+  BOOST_CHECK(interest3.getName().isPrefixOf(foundCert->getName()));
+  BOOST_CHECK_EQUAL(foundCert->getName(), cert3.getName());
+
+  interest3.setExclude(Exclude().excludeOne(cert3.getName().at(Certificate::ISSUER_ID_OFFSET)));
+  foundCert = anchorContainer.find(interest3);
+  BOOST_REQUIRE(foundCert != nullptr);
+  BOOST_CHECK(interest3.getName().isPrefixOf(foundCert->getName()));
+  BOOST_CHECK_NE(foundCert->getName(), cert3.getName());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTrustAnchorContainer
+BOOST_AUTO_TEST_SUITE_END() // Detail
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validation-error.t.cpp b/tests/unit/security/v2/validation-error.t.cpp
new file mode 100644
index 0000000..1db3aac
--- /dev/null
+++ b/tests/unit/security/v2/validation-error.t.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validation-error.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(TestValidationError)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  ValidationError e1{ValidationError::Code::INVALID_SIGNATURE};
+  BOOST_CHECK_EQUAL(e1.getCode(), 1);
+  BOOST_CHECK_EQUAL(e1.getInfo(), "");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(e1), "Invalid signature");
+
+  ValidationError e2{ValidationError::Code::NO_SIGNATURE, "message"};
+  BOOST_CHECK_EQUAL(e2.getCode(), 2);
+  BOOST_CHECK_EQUAL(e2.getInfo(), "message");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(e2), "Missing signature (message)");
+
+  ValidationError e3{65535, "other message"};
+  BOOST_CHECK_EQUAL(e3.getCode(), 65535);
+  BOOST_CHECK_EQUAL(e3.getInfo(), "other message");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(e3), "Custom error code 65535 (other message)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidationError
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validation-policy-accept-all.t.cpp b/tests/unit/security/v2/validation-policy-accept-all.t.cpp
new file mode 100644
index 0000000..61466d6
--- /dev/null
+++ b/tests/unit/security/v2/validation-policy-accept-all.t.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validation-policy-accept-all.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+
+class ValidationPolicyAcceptAllFixture : public ValidatorFixture<ValidationPolicyAcceptAll>
+{
+public:
+  ValidationPolicyAcceptAllFixture()
+  {
+    identity = addIdentity("/Security/V2/TestValidationPolicyAcceptAll");
+    // don't add trust anchors
+  }
+
+public:
+  Identity identity;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestValidationPolicyAcceptAll, ValidationPolicyAcceptAllFixture)
+
+typedef boost::mpl::vector<Interest, Data> Packets;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
+{
+  Packet unsignedPacket("/Security/V2/TestValidationPolicyAcceptAll/Sub/Packet");
+
+  Packet packet = unsignedPacket;
+  VALIDATE_SUCCESS(packet, "Should accept unsigned");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingWithSha256());
+  VALIDATE_SUCCESS(packet, "Should accept Sha256Digest signature");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(identity));
+  VALIDATE_SUCCESS(packet, "Should accept signature while no trust anchors configured");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicyAcceptAll
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validation-policy-command-interest.t.cpp b/tests/unit/security/v2/validation-policy-command-interest.t.cpp
new file mode 100644
index 0000000..7bd42ea
--- /dev/null
+++ b/tests/unit/security/v2/validation-policy-command-interest.t.cpp
@@ -0,0 +1,485 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validation-policy-command-interest.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+#include "security/v2/validation-policy-accept-all.hpp"
+#include "security/command-interest-signer.hpp"
+#include "security/signing-helpers.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+#include "make-interest-data.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+
+class DefaultOptions
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    return {};
+  }
+};
+
+template<class T, class InnerPolicy>
+class CommandInterestPolicyWrapper : public ValidationPolicyCommandInterest
+{
+public:
+  CommandInterestPolicyWrapper()
+    : ValidationPolicyCommandInterest(make_unique<InnerPolicy>(), T::getOptions())
+  {
+  }
+};
+
+template<class T, class InnerPolicy = ValidationPolicySimpleHierarchy>
+class ValidationPolicyCommandInterestFixture : public HierarchicalValidatorFixture<CommandInterestPolicyWrapper<T, InnerPolicy>>
+{
+public:
+  ValidationPolicyCommandInterestFixture()
+    : m_signer(this->m_keyChain)
+  {
+  }
+
+  Interest
+  makeCommandInterest(const Identity& identity)
+  {
+    return m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"),
+                                        signingByIdentity(identity));
+  }
+
+public:
+  CommandInterestSigner m_signer;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestValidationPolicyCommandInterest, ValidationPolicyCommandInterestFixture<DefaultOptions>)
+
+BOOST_AUTO_TEST_SUITE(Accepts)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  auto i1 = makeCommandInterest(identity);
+  VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
+  VALIDATE_FAILURE(i1, "Should fail (replay attack)");
+
+  advanceClocks(5_ms);
+  auto i2 = makeCommandInterest(identity);
+  VALIDATE_SUCCESS(i2, "Should succeed (timestamp larger than previous)");
+
+  auto i3 =  m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"), signingWithSha256());
+  VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
+}
+
+BOOST_AUTO_TEST_CASE(DataPassthru)
+{
+  Data d1("/Security/V2/ValidatorFixture/Sub1");
+  m_keyChain.sign(d1);
+  VALIDATE_SUCCESS(d1, "Should succeed (fallback on inner validation policy for data)");
+}
+
+using ValidationPolicyAcceptAllCommands = ValidationPolicyCommandInterestFixture<DefaultOptions,
+                                                                                 ValidationPolicyAcceptAll>;
+
+BOOST_FIXTURE_TEST_CASE(SignedWithSha256, ValidationPolicyAcceptAllCommands) // Bug 4635
+{
+  auto i1 = m_signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
+  VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
+  VALIDATE_FAILURE(i1, "Should fail (replay attack)");
+
+  advanceClocks(5_ms);
+  auto i2 = m_signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
+  VALIDATE_SUCCESS(i2, "Should succeed (timestamp larger than previous)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Accepts
+
+BOOST_AUTO_TEST_SUITE(Rejects)
+
+BOOST_AUTO_TEST_CASE(NameTooShort)
+{
+  auto i1 = makeInterest("/name/too/short");
+  VALIDATE_FAILURE(*i1, "Should fail (name is too short)");
+}
+
+BOOST_AUTO_TEST_CASE(BadTimestamp)
+{
+  auto i1 = makeCommandInterest(identity);
+  setNameComponent(i1, command_interest::POS_TIMESTAMP, "not-timestamp");
+  VALIDATE_FAILURE(i1, "Should fail (timestamp is missing)");
+}
+
+BOOST_AUTO_TEST_CASE(BadSigInfo)
+{
+  auto i1 = makeCommandInterest(identity);
+  setNameComponent(i1, command_interest::POS_SIG_INFO, "not-SignatureInfo");
+  VALIDATE_FAILURE(i1, "Should fail (signature info is missing)");
+}
+
+BOOST_AUTO_TEST_CASE(MissingKeyLocator)
+{
+  auto i1 = makeCommandInterest(identity);
+  SignatureInfo sigInfo(tlv::SignatureSha256WithRsa);
+  setNameComponent(i1, command_interest::POS_SIG_INFO,
+                   sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
+  VALIDATE_FAILURE(i1, "Should fail (missing KeyLocator)");
+}
+
+BOOST_AUTO_TEST_CASE(BadKeyLocatorType)
+{
+  auto i1 = makeCommandInterest(identity);
+  KeyLocator kl;
+  kl.setKeyDigest(makeBinaryBlock(tlv::KeyDigest, "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD", 8));
+  SignatureInfo sigInfo(tlv::SignatureSha256WithRsa);
+  sigInfo.setKeyLocator(kl);
+  setNameComponent(i1, command_interest::POS_SIG_INFO,
+                   sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
+  VALIDATE_FAILURE(i1, "Should fail (bad KeyLocator type)");
+}
+
+BOOST_AUTO_TEST_CASE(BadCertName)
+{
+  auto i1 = makeCommandInterest(identity);
+  KeyLocator kl;
+  kl.setName("/bad/cert/name");
+  SignatureInfo sigInfo(tlv::SignatureSha256WithRsa);
+  sigInfo.setKeyLocator(kl);
+  setNameComponent(i1, command_interest::POS_SIG_INFO,
+                   sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
+  VALIDATE_FAILURE(i1, "Should fail (bad certificate name)");
+}
+
+BOOST_AUTO_TEST_CASE(InnerPolicyReject)
+{
+  auto i1 = makeCommandInterest(otherIdentity);
+  VALIDATE_FAILURE(i1, "Should fail (inner policy should reject)");
+}
+
+class GracePeriod15Sec
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = 15_s;
+    return options;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(TimestampOutOfGracePositive, ValidationPolicyCommandInterestFixture<GracePeriod15Sec>)
+{
+  auto i1 = makeCommandInterest(identity); // signed at 0s
+  advanceClocks(16_s); // verifying at +16s
+  VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
+  rewindClockAfterValidation();
+
+  auto i2 = makeCommandInterest(identity); // signed at +16s
+  VALIDATE_SUCCESS(i2, "Should succeed");
+}
+
+BOOST_FIXTURE_TEST_CASE(TimestampOutOfGraceNegative, ValidationPolicyCommandInterestFixture<GracePeriod15Sec>)
+{
+  auto i1 = makeCommandInterest(identity); // signed at 0s
+  advanceClocks(1_s);
+  auto i2 = makeCommandInterest(identity); // signed at +1s
+  advanceClocks(1_s);
+  auto i3 = makeCommandInterest(identity); // signed at +2s
+
+  systemClock->advance(-18_s); // verifying at -16s
+  VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
+  rewindClockAfterValidation();
+
+  // CommandInterestValidator should not remember i1's timestamp
+  VALIDATE_FAILURE(i2, "Should fail (timestamp outside the grace period)");
+  rewindClockAfterValidation();
+
+  // CommandInterestValidator should not remember i2's timestamp, and should treat i3 as initial
+  advanceClocks(18_s); // verifying at +2s
+  VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
+{
+  auto i1 = makeCommandInterest(identity); // signed at 0s
+  VALIDATE_SUCCESS(i1, "Should succeed");
+
+  auto i2 = makeCommandInterest(identity); // signed at 0s
+  setNameComponent(i2, command_interest::POS_TIMESTAMP,
+                   i1.getName()[command_interest::POS_TIMESTAMP]);
+  VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
+
+  advanceClocks(2_s);
+  auto i3 = makeCommandInterest(identity); // signed at +2s
+  VALIDATE_SUCCESS(i3, "Should succeed");
+}
+
+BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
+{
+  auto i2 = makeCommandInterest(identity); // signed at 0ms
+  advanceClocks(200_ms);
+  auto i3 = makeCommandInterest(identity); // signed at +200ms
+  advanceClocks(900_ms);
+  auto i1 = makeCommandInterest(identity); // signed at +1100ms
+  advanceClocks(300_ms);
+  auto i4 = makeCommandInterest(identity); // signed at +1400ms
+
+  systemClock->advance(-300_ms); // verifying at +1100ms
+  VALIDATE_SUCCESS(i1, "Should succeed");
+  rewindClockAfterValidation();
+
+  systemClock->advance(-1100_ms); // verifying at 0ms
+  VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
+  rewindClockAfterValidation();
+
+  // CommandInterestValidator should not remember i2's timestamp
+  advanceClocks(200_ms); // verifying at +200ms
+  VALIDATE_FAILURE(i3, "Should fail (timestamp reordered)");
+  rewindClockAfterValidation();
+
+  advanceClocks(1200_ms); // verifying at 1400ms
+  VALIDATE_SUCCESS(i4, "Should succeed");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Rejects
+
+BOOST_AUTO_TEST_SUITE(Options)
+
+template<class T>
+class GracePeriod
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = time::seconds(T::value);
+    return options;
+  }
+};
+
+typedef boost::mpl::vector<
+  GracePeriod<boost::mpl::int_<0>>,
+  GracePeriod<boost::mpl::int_<-1>>
+> GraceNonPositiveValues;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositiveValues,
+                                 ValidationPolicyCommandInterestFixture<GracePeriod>)
+{
+  auto i1 = this->makeCommandInterest(this->identity); // signed at 0ms
+  auto i2 = this->makeCommandInterest(this->subIdentity); // signed at 0ms
+  for (auto interest : {&i1, &i2}) {
+    setNameComponent(*interest, command_interest::POS_TIMESTAMP,
+                     name::Component::fromNumber(time::toUnixTimestamp(time::system_clock::now()).count()));
+  } // ensure timestamps are exactly 0ms
+
+  VALIDATE_SUCCESS(i1, "Should succeed when validating at 0ms");
+  this->rewindClockAfterValidation();
+
+  this->advanceClocks(1_ms);
+  VALIDATE_FAILURE(i2, "Should fail when validating at 1ms");
+}
+
+class LimitedRecordsOptions
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = 15_s;
+    options.maxRecords = 3;
+    return options;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(LimitedRecords, ValidationPolicyCommandInterestFixture<LimitedRecordsOptions>)
+{
+  Identity id1 = this->addSubCertificate("/Security/V2/ValidatorFixture/Sub1", identity);
+  this->cache.insert(id1.getDefaultKey().getDefaultCertificate());
+  Identity id2 = this->addSubCertificate("/Security/V2/ValidatorFixture/Sub2", identity);
+  this->cache.insert(id2.getDefaultKey().getDefaultCertificate());
+  Identity id3 = this->addSubCertificate("/Security/V2/ValidatorFixture/Sub3", identity);
+  this->cache.insert(id3.getDefaultKey().getDefaultCertificate());
+  Identity id4 = this->addSubCertificate("/Security/V2/ValidatorFixture/Sub4", identity);
+  this->cache.insert(id4.getDefaultKey().getDefaultCertificate());
+
+  auto i1 = makeCommandInterest(id2);
+  auto i2 = makeCommandInterest(id3);
+  auto i3 = makeCommandInterest(id4);
+  auto i00 = makeCommandInterest(id1); // signed at 0s
+  advanceClocks(1_s);
+  auto i01 = makeCommandInterest(id1); // signed at 1s
+  advanceClocks(1_s);
+  auto i02 = makeCommandInterest(id1); // signed at 2s
+
+  VALIDATE_SUCCESS(i00, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i02, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i1, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i2, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i3, "Should succeed, forgets identity id1");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i01, "Should succeed despite timestamp is reordered, because record has been evicted");
+}
+
+class UnlimitedRecordsOptions
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = 15_s;
+    options.maxRecords = -1;
+    return options;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicyCommandInterestFixture<UnlimitedRecordsOptions>)
+{
+  std::vector<Identity> identities;
+  for (int i = 0; i < 20; ++i) {
+    Identity id = this->addSubCertificate("/Security/V2/ValidatorFixture/Sub" + to_string(i), identity);
+    this->cache.insert(id.getDefaultKey().getDefaultCertificate());
+    identities.push_back(id);
+  }
+
+  auto i1 = makeCommandInterest(identities.at(0)); // signed at 0s
+  advanceClocks(1_s);
+  for (int i = 0; i < 20; ++i) {
+    auto i2 = makeCommandInterest(identities.at(i)); // signed at +1s
+
+    VALIDATE_SUCCESS(i2, "Should succeed");
+    rewindClockAfterValidation();
+  }
+  VALIDATE_FAILURE(i1, "Should fail (timestamp reorder)");
+}
+
+class ZeroRecordsOptions
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = 15_s;
+    options.maxRecords = 0;
+    return options;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(ZeroRecords, ValidationPolicyCommandInterestFixture<ZeroRecordsOptions>)
+{
+  auto i1 = makeCommandInterest(identity); // signed at 0s
+  advanceClocks(1_s);
+  auto i2 = makeCommandInterest(identity); // signed at +1s
+  VALIDATE_SUCCESS(i2, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i1, "Should succeed despite timestamp is reordered, because record isn't kept");
+}
+
+class LimitedRecordLifetimeOptions
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = 400_s;
+    options.recordLifetime = 300_s;
+    return options;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(LimitedRecordLifetime, ValidationPolicyCommandInterestFixture<LimitedRecordLifetimeOptions>)
+{
+  auto i1 = makeCommandInterest(identity); // signed at 0s
+  advanceClocks(240_s);
+  auto i2 = makeCommandInterest(identity); // signed at +240s
+  advanceClocks(120_s);
+  auto i3 = makeCommandInterest(identity); // signed at +360s
+
+  systemClock->advance(-360_s); // rewind system clock to 0s
+  VALIDATE_SUCCESS(i1, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i3, "Should succeed");
+  rewindClockAfterValidation();
+
+  advanceClocks(30_s, 301_s); // advance steady clock by 301s, and system clock to +301s
+  VALIDATE_SUCCESS(i2, "Should succeed despite timestamp is reordered, because record has been expired");
+}
+
+class ZeroRecordLifetimeOptions
+{
+public:
+  static ValidationPolicyCommandInterest::Options
+  getOptions()
+  {
+    ValidationPolicyCommandInterest::Options options;
+    options.gracePeriod = 15_s;
+    options.recordLifetime = time::seconds::zero();
+    return options;
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(ZeroRecordLifetime, ValidationPolicyCommandInterestFixture<ZeroRecordLifetimeOptions>)
+{
+  auto i1 = makeCommandInterest(identity); // signed at 0s
+  advanceClocks(1_s);
+  auto i2 = makeCommandInterest(identity); // signed at +1s
+  VALIDATE_SUCCESS(i2, "Should succeed");
+  rewindClockAfterValidation();
+
+  VALIDATE_SUCCESS(i1, "Should succeed despite timestamp is reordered, because record has been expired");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Options
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicyCommandInterest
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validation-policy-config.t.cpp b/tests/unit/security/v2/validation-policy-config.t.cpp
new file mode 100644
index 0000000..cca3e7a
--- /dev/null
+++ b/tests/unit/security/v2/validation-policy-config.t.cpp
@@ -0,0 +1,539 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validation-policy-config.hpp"
+#include "security/transform/base64-encode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "util/logger.hpp"
+#include "util/io.hpp"
+
+#include "boost-test.hpp"
+#include "validator-config/common.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+namespace tests {
+
+using namespace ndn::tests;
+using namespace ndn::security::v2::tests;
+
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(TestValidationPolicyConfig)
+
+template<typename Packet>
+class PacketName;
+
+template<>
+class PacketName<Interest>
+{
+public:
+  static std::string
+  getName()
+  {
+    return "interest";
+  }
+};
+
+template<>
+class PacketName<Data>
+{
+public:
+  static std::string
+  getName()
+  {
+    return "data";
+  }
+};
+
+template<typename PacketType>
+class ValidationPolicyConfigFixture : public HierarchicalValidatorFixture<ValidationPolicyConfig>
+{
+public:
+  ValidationPolicyConfigFixture()
+    : path(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) / "security" / "v2" / "validation-policy-config")
+  {
+    boost::filesystem::create_directories(path);
+    baseConfig = R"CONF(
+        rule
+        {
+          id test-rule-id
+          for )CONF" + PacketName<Packet>::getName() + R"CONF(
+          filter
+          {
+            type name
+            name )CONF" + identity.getName().toUri() + R"CONF(
+            relation is-prefix-of
+          }
+          checker
+          {
+            type hierarchical
+            sig-type rsa-sha256
+          }
+        }
+      )CONF";
+  }
+
+  ~ValidationPolicyConfigFixture()
+  {
+    boost::filesystem::remove_all(path);
+  }
+
+public:
+  using Packet = PacketType;
+
+  const boost::filesystem::path path;
+  std::string baseConfig;
+};
+
+template<typename PacketType>
+class LoadStringWithFileAnchor : public ValidationPolicyConfigFixture<PacketType>
+{
+public:
+  LoadStringWithFileAnchor()
+  {
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false);
+
+    this->saveCertificate(this->identity, (this->path / "identity.ndncert").string());
+    this->policy.load(this->baseConfig + R"CONF(
+        trust-anchor
+        {
+          type file
+          file-name "trust-anchor.ndncert"
+        }
+      )CONF", (this->path / "test-config").string());
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+    BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false);
+  }
+};
+
+template<typename PacketType>
+class LoadFileWithFileAnchor : public ValidationPolicyConfigFixture<PacketType>
+{
+public:
+  LoadFileWithFileAnchor()
+  {
+    std::string configFile = (this->path / "config.conf").string();
+    {
+      std::ofstream config(configFile.c_str());
+      config << this->baseConfig << R"CONF(
+        trust-anchor
+        {
+          type file
+          file-name "trust-anchor.ndncert"
+        }
+      )CONF";
+    }
+    this->saveCertificate(this->identity, (this->path / "identity.ndncert").string());
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false);
+
+    this->policy.load(configFile);
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+    BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false);
+  }
+};
+
+template<typename PacketType>
+class LoadSectionWithFileAnchor : public ValidationPolicyConfigFixture<PacketType>
+{
+public:
+  LoadSectionWithFileAnchor()
+  {
+    auto section = makeSection(this->baseConfig + R"CONF(
+        trust-anchor
+        {
+          type file
+          file-name "trust-anchor.ndncert"
+        }
+      )CONF");
+
+    this->saveCertificate(this->identity, (this->path / "identity.ndncert").string());
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false);
+
+    this->policy.load(section, (this->path / "test-config").string());
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+    BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false);
+  }
+};
+
+template<typename PacketType>
+class LoadStringWithBase64Anchor : public ValidationPolicyConfigFixture<PacketType>
+{
+public:
+  LoadStringWithBase64Anchor()
+  {
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false);
+
+    std::ostringstream os;
+    using namespace ndn::security::transform;
+    const auto& cert = this->identity.getDefaultKey().getDefaultCertificate();
+    bufferSource(cert.wireEncode().wire(), cert.wireEncode().size()) >> base64Encode(false) >> streamSink(os);
+
+    this->policy.load(this->baseConfig + R"CONF(
+        trust-anchor
+        {
+          type base64
+          base64-string ")CONF" + os.str() + R"CONF("
+        }
+      )CONF", (this->path / "test-config").string());
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+    BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false);
+  }
+};
+
+class NoRefresh
+{
+public:
+  static std::string
+  getRefreshString()
+  {
+    return "";
+  }
+};
+
+class Refresh1h
+{
+public:
+  static std::string
+  getRefreshString()
+  {
+    return "refresh 1h";
+  }
+
+  static time::milliseconds
+  getRefreshTime()
+  {
+    return 1_h;
+  }
+};
+
+class Refresh1m
+{
+public:
+  static std::string
+  getRefreshString()
+  {
+    return "refresh 1m";
+  }
+
+  static time::milliseconds
+  getRefreshTime()
+  {
+    return 1_min;
+  }
+};
+
+class Refresh1s
+{
+public:
+  static std::string
+  getRefreshString()
+  {
+    return "refresh 1s";
+  }
+
+  static time::milliseconds
+  getRefreshTime()
+  {
+    return 1_s;
+  }
+};
+
+template<typename PacketType, typename Refresh = NoRefresh>
+class LoadStringWithDirAnchor : public ValidationPolicyConfigFixture<PacketType>
+{
+public:
+  LoadStringWithDirAnchor()
+  {
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false);
+
+    boost::filesystem::create_directories(this->path / "keys");
+    this->saveCertificate(this->identity, (this->path / "keys" / "identity.ndncert").string());
+
+    this->policy.load(this->baseConfig + R"CONF(
+        trust-anchor
+        {
+          type dir
+          dir keys
+          )CONF" + Refresh::getRefreshString() + R"CONF(
+        }
+      )CONF", (this->path / "test-config").string());
+
+    BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+    BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false);
+  }
+};
+
+using DataPolicies = boost::mpl::vector<LoadStringWithFileAnchor<Data>,
+                                        LoadFileWithFileAnchor<Data>,
+                                        LoadSectionWithFileAnchor<Data>,
+                                        LoadStringWithBase64Anchor<Data>,
+                                        LoadStringWithDirAnchor<Data>,
+                                        LoadStringWithDirAnchor<Data, Refresh1h>,
+                                        LoadStringWithDirAnchor<Data, Refresh1m>,
+                                        LoadStringWithDirAnchor<Data, Refresh1s>
+                                        >;
+
+using InterestPolicies = boost::mpl::vector<LoadStringWithFileAnchor<Interest>,
+                                            LoadFileWithFileAnchor<Interest>,
+                                            LoadSectionWithFileAnchor<Interest>,
+                                            LoadStringWithBase64Anchor<Interest>,
+                                            LoadStringWithDirAnchor<Interest>,
+                                            LoadStringWithDirAnchor<Interest, Refresh1h>,
+                                            LoadStringWithDirAnchor<Interest, Refresh1m>,
+                                            LoadStringWithDirAnchor<Interest, Refresh1s>
+                                            >;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateData, Policy, DataPolicies, Policy)
+{
+  BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 1);
+  BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0);
+
+  using Packet = typename Policy::Packet;
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Sub2/Packet");
+
+  Packet packet = unsignedPacket;
+  VALIDATE_FAILURE(packet, "Unsigned");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingWithSha256());
+  VALIDATE_FAILURE(packet, "Policy doesn't accept Sha256Digest signature");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->identity));
+  VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the anchor");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity));
+  VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the policy-compliant cert");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->otherIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as signed by the policy-violating cert");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subSelfSignedIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, because subSelfSignedIdentity is not a trust anchor");
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateInterest, Policy, InterestPolicies, Policy)
+{
+  BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0);
+  BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 1);
+
+  using Packet = typename Policy::Packet;
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Sub2/Packet");
+
+  Packet packet = unsignedPacket;
+  VALIDATE_FAILURE(packet, "Unsigned");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingWithSha256());
+  VALIDATE_FAILURE(packet, "Policy doesn't accept Sha256Digest signature");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->identity));
+  VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the anchor");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as there is no matching rule for data");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->otherIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as signed by the policy-violating cert");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subSelfSignedIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, because subSelfSignedIdentity is not a trust anchor");
+}
+
+BOOST_FIXTURE_TEST_CASE(Reload, HierarchicalValidatorFixture<ValidationPolicyConfig>)
+{
+  BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false);
+  this->policy.load(R"CONF(
+      rule
+      {
+        id test-rule-data-id
+        for data
+        filter
+        {
+          type name
+          name /foo/bar
+          relation is-prefix-of
+        }
+        checker
+        {
+          type hierarchical
+          sig-type rsa-sha256
+        }
+      }
+      rule
+      {
+        id test-rule-interest-id
+        for interest
+        filter
+        {
+          type name
+          name /foo/bar
+          relation is-prefix-of
+        }
+        checker
+        {
+          type hierarchical
+          sig-type rsa-sha256
+        }
+      }
+      trust-anchor
+      {
+        type dir
+        dir keys
+        refresh 1h
+      }
+    )CONF", "test-config");
+  BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+  BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false);
+  BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 1);
+  BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 1);
+
+  this->policy.load(R"CONF(
+      trust-anchor
+      {
+        type any
+      }
+    )CONF", "test-config");
+  BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+  BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, true);
+  BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0);
+  BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0);
+}
+
+using Packets = boost::mpl::vector<Interest, Data>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(TrustAnchorWildcard, Packet, Packets, ValidationPolicyConfigFixture<Packet>)
+{
+  this->policy.load(R"CONF(
+      trust-anchor
+      {
+        type any
+      }
+    )CONF", "test-config");
+
+  BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true);
+  BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, true);
+  BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0);
+  BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0);
+
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Sub2/Packet");
+
+  Packet packet = unsignedPacket;
+  VALIDATE_SUCCESS(packet, "Policy should accept everything");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingWithSha256());
+  VALIDATE_SUCCESS(packet, "Policy should accept everything");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->identity));
+  VALIDATE_SUCCESS(packet, "Policy should accept everything");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity));
+  VALIDATE_SUCCESS(packet, "Policy should accept everything");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->otherIdentity));
+  VALIDATE_SUCCESS(packet, "Policy should accept everything");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subSelfSignedIdentity));
+  VALIDATE_SUCCESS(packet, "Policy should accept everything");
+}
+
+using RefreshPolicies = boost::mpl::vector<Refresh1h, Refresh1m, Refresh1s>;
+
+// Somehow, didn't work without this wrapper
+template<typename RefreshPolicy>
+class RefreshPolicyFixture : public LoadStringWithDirAnchor<Data, RefreshPolicy>
+{
+public:
+};
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateRefresh, Refresh, RefreshPolicies, RefreshPolicyFixture<Refresh>)
+{
+  using Packet = Data;
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Sub2/Packet");
+
+  boost::filesystem::remove(this->path / "keys" / "identity.ndncert");
+  this->advanceClocks(Refresh::getRefreshTime(), 3);
+
+  Packet packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->identity));
+  VALIDATE_FAILURE(packet, "Should fail, as the trust anchor should no longer exist");
+
+  packet = unsignedPacket;
+  this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as the trust anchor should no longer exist");
+}
+
+BOOST_FIXTURE_TEST_CASE(OrphanedPolicyLoad, HierarchicalValidatorFixture<ValidationPolicyConfig>) // Bug #4758
+{
+  ValidationPolicyConfig policy1;
+  BOOST_CHECK_THROW(policy1.load("trust-anchor { type any }", "test-config"), Error);
+
+  // Reloading would have triggered a segfault
+  BOOST_CHECK_THROW(policy1.load("trust-anchor { type any }", "test-config"), Error);
+
+  ValidationPolicyConfig policy2;
+
+  std::string config = R"CONF(
+      trust-anchor
+      {
+        type dir
+        dir keys
+        refresh 1h
+      }
+    )CONF";
+
+  // Inserting trust anchor would have triggered a segfault
+  BOOST_CHECK_THROW(policy2.load(config, "test-config"), Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicyConfig
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validation-policy-simple-hierarchy.t.cpp b/tests/unit/security/v2/validation-policy-simple-hierarchy.t.cpp
new file mode 100644
index 0000000..8661ac5
--- /dev/null
+++ b/tests/unit/security/v2/validation-policy-simple-hierarchy.t.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_FIXTURE_TEST_SUITE(TestValidationPolicySimpleHierarchy,
+                         HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy>)
+
+typedef boost::mpl::vector<Interest, Data> Packets;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
+{
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Sub2/Packet");
+
+  Packet packet = unsignedPacket;
+  VALIDATE_FAILURE(packet, "Unsigned");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingWithSha256());
+  VALIDATE_FAILURE(packet, "Policy doesn't accept Sha256Digest signature");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(identity));
+  VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the anchor");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(subIdentity));
+  VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the policy-compliant cert");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(otherIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as signed by the policy-violating cert");
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(subSelfSignedIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, because subSelfSignedIdentity is not a trust anchor");
+
+  // TODO add checks with malformed packets
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidator
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validator-config/checker.t.cpp b/tests/unit/security/v2/validator-config/checker.t.cpp
new file mode 100644
index 0000000..2170908
--- /dev/null
+++ b/tests/unit/security/v2/validator-config/checker.t.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validator-config/checker.hpp"
+#include "security/command-interest-signer.hpp"
+#include "security/v2/validation-policy.hpp"
+#include "security/v2/validation-state.hpp"
+
+#include "boost-test.hpp"
+#include "common.hpp"
+#include "identity-management-fixture.hpp"
+#include "../validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+namespace tests {
+
+using namespace ndn::tests;
+using namespace ndn::security::v2::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(ValidatorConfig)
+
+class CheckerFixture : public IdentityManagementFixture
+{
+public:
+  CheckerFixture()
+  {
+    names.push_back("/foo/bar");
+    names.push_back("/foo/bar/bar");
+    names.push_back("/foo");
+    names.push_back("/other/prefix");
+  }
+
+  Name
+  makeSignedInterestName(const Name& name)
+  {
+    return Name(name).append("SignatureInfo").append("SignatureValue");
+  }
+
+  Name
+  makeKeyLocatorName(const Name& name)
+  {
+    return Name(name).append("KEY").append("v=1");
+  }
+
+public:
+  std::vector<Name> names;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestChecker, CheckerFixture)
+
+class NameRelationEqual : public CheckerFixture
+{
+public:
+  NameRelationEqual()
+    : checker("/foo/bar", NameRelation::EQUAL)
+  {
+  }
+
+public:
+  NameRelationChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false}};
+};
+
+class NameRelationIsPrefixOf : public CheckerFixture
+{
+public:
+  NameRelationIsPrefixOf()
+    : checker("/foo/bar", NameRelation::IS_PREFIX_OF)
+  {
+  }
+
+public:
+  NameRelationChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{true, true, false, false},
+                                             {true, true, false, false},
+                                             {true, true, false, false},
+                                             {true, true, false, false}};
+};
+
+class NameRelationIsStrictPrefixOf : public CheckerFixture
+{
+public:
+  NameRelationIsStrictPrefixOf()
+    : checker("/foo/bar", NameRelation::IS_STRICT_PREFIX_OF)
+  {
+  }
+
+public:
+  NameRelationChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{false, true, false, false},
+                                             {false, true, false, false},
+                                             {false, true, false, false},
+                                             {false, true, false, false}};
+};
+
+class RegexEqual : public CheckerFixture
+{
+public:
+  RegexEqual()
+    : checker(Regex("^<foo><bar><KEY><>$"))
+  {
+  }
+
+public:
+  RegexChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false}};
+};
+
+class RegexIsPrefixOf : public CheckerFixture
+{
+public:
+  RegexIsPrefixOf()
+    : checker(Regex("^<foo><bar><>*<KEY><>$"))
+  {
+  }
+
+public:
+  RegexChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{true, true, false, false},
+                                             {true, true, false, false},
+                                             {true, true, false, false},
+                                             {true, true, false, false}};
+};
+
+class RegexIsStrictPrefixOf : public CheckerFixture
+{
+public:
+  RegexIsStrictPrefixOf()
+    : checker(Regex("^<foo><bar><>+<KEY><>$"))
+  {
+  }
+
+public:
+  RegexChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{false, true, false, false},
+                                             {false, true, false, false},
+                                             {false, true, false, false},
+                                             {false, true, false, false}};
+};
+
+class HyperRelationEqual : public CheckerFixture
+{
+public:
+  HyperRelationEqual()
+    : checker("^(<>+)$", "\\1", "^(<>+)<KEY><>$", "\\1", NameRelation::EQUAL)
+  {
+  }
+
+public:
+  HyperRelationChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{true,  false, false, false},
+                                             {false, true,  false, false},
+                                             {false, false, true,  false},
+                                             {false, false, false, true}};
+};
+
+class HyperRelationIsPrefixOf : public CheckerFixture
+{
+public:
+  HyperRelationIsPrefixOf()
+    : checker("^(<>+)$", "\\1", "^(<>+)<KEY><>$", "\\1", NameRelation::IS_PREFIX_OF)
+  {
+  }
+
+public:
+  HyperRelationChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{true,  false, true,  false},
+                                             {true,  true,  true,  false},
+                                             {false, false, true,  false},
+                                             {false, false, false, true}};
+};
+
+class HyperRelationIsStrictPrefixOf : public CheckerFixture
+{
+public:
+  HyperRelationIsStrictPrefixOf()
+    : checker("^(<>+)$", "\\1", "^(<>+)<KEY><>$", "\\1", NameRelation::IS_STRICT_PREFIX_OF)
+  {
+  }
+
+public:
+  HyperRelationChecker checker;
+  std::vector<std::vector<bool>> outcomes = {{false, false, true,  false},
+                                             {true,  false, true,  false},
+                                             {false, false, false, false},
+                                             {false, false, false, false}};
+};
+
+class Hierarchical : public CheckerFixture
+{
+public:
+  Hierarchical()
+    : checkerPtr(Checker::create(makeSection(R"CONF(
+          type hierarchical
+          sig-type rsa-sha256
+        )CONF"), "test-config"))
+    , checker(*checkerPtr)
+  {
+  }
+
+public:
+  std::unique_ptr<Checker> checkerPtr;
+  Checker& checker;
+
+  std::vector<std::vector<bool>> outcomes = {{true,  false, true,  false},
+                                             {true,  true,  true,  false},
+                                             {false, false, true,  false},
+                                             {false, false, false, true}};
+};
+
+class CustomizedNameRelation : public CheckerFixture
+{
+public:
+  CustomizedNameRelation()
+    : checkerPtr(Checker::create(makeSection(R"CONF(
+          type customized
+          sig-type rsa-sha256
+          key-locator
+          {
+            type name
+            name /foo/bar
+            relation equal
+          }
+        )CONF"), "test-config"))
+    , checker(*checkerPtr)
+  {
+  }
+
+public:
+  std::unique_ptr<Checker> checkerPtr;
+  Checker& checker;
+
+  std::vector<std::vector<bool>> outcomes = {{true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false}};
+};
+
+class CustomizedRegex : public CheckerFixture
+{
+public:
+  CustomizedRegex()
+    : checkerPtr(Checker::create(makeSection(R"CONF(
+          type customized
+          sig-type rsa-sha256
+          key-locator
+          {
+            type name
+            regex ^<foo><bar><KEY><>$
+          }
+        )CONF"), "test-config"))
+    , checker(*checkerPtr)
+  {
+  }
+
+public:
+  std::unique_ptr<Checker> checkerPtr;
+  Checker& checker;
+
+  std::vector<std::vector<bool>> outcomes = {{true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false},
+                                             {true, false, false, false}};
+};
+
+class CustomizedHyperRelation : public CheckerFixture
+{
+public:
+  CustomizedHyperRelation()
+    : checkerPtr(Checker::create(makeSection(R"CONF(
+          type customized
+          sig-type rsa-sha256
+          key-locator
+          {
+            type name
+            hyper-relation
+            {
+              k-regex ^(<>+)<KEY><>$
+              k-expand \\1
+              h-relation is-prefix-of
+              p-regex ^(<>+)$
+              p-expand \\1
+            }
+          }
+        )CONF"), "test-config"))
+    , checker(*checkerPtr)
+  {
+  }
+
+public:
+  std::unique_ptr<Checker> checkerPtr;
+  Checker& checker;
+
+  std::vector<std::vector<bool>> outcomes = {{true,  false, true,  false},
+                                             {true,  true,  true,  false},
+                                             {false, false, true,  false},
+                                             {false, false, false, true}};
+};
+
+
+using Tests = boost::mpl::vector<NameRelationEqual, NameRelationIsPrefixOf, NameRelationIsStrictPrefixOf,
+                                 RegexEqual, RegexIsPrefixOf, RegexIsStrictPrefixOf,
+                                 HyperRelationEqual, HyperRelationIsPrefixOf, HyperRelationIsStrictPrefixOf,
+                                 Hierarchical,
+                                 CustomizedNameRelation, CustomizedRegex, CustomizedHyperRelation>;
+
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Checks, T, Tests, T)
+{
+  BOOST_ASSERT(this->outcomes.size() == this->names.size());
+  for (size_t i = 0; i < this->names.size(); ++i) {
+    BOOST_ASSERT(this->outcomes[i].size() == this->names.size());
+    for (size_t j = 0; j < this->names.size(); ++j) {
+      const Name& pktName = this->names[i];
+      Name klName = this->makeKeyLocatorName(this->names[j]);
+      bool expectedOutcome = this->outcomes[i][j];
+
+      auto dataState = make_shared<DummyValidationState>();
+      BOOST_CHECK_EQUAL(this->checker.check(tlv::Data, pktName, klName, dataState), expectedOutcome);
+      BOOST_CHECK_EQUAL(boost::logic::indeterminate(dataState->getOutcome()), expectedOutcome);
+      if (boost::logic::indeterminate(dataState->getOutcome()) == !expectedOutcome) {
+        BOOST_CHECK_EQUAL(dataState->getOutcome(), !expectedOutcome);
+      }
+
+      auto interestState = make_shared<DummyValidationState>();
+      BOOST_CHECK_EQUAL(this->checker.check(tlv::Interest, this->makeSignedInterestName(pktName),
+                                            klName, interestState), expectedOutcome);
+      BOOST_CHECK_EQUAL(boost::logic::indeterminate(interestState->getOutcome()), expectedOutcome);
+      if (boost::logic::indeterminate(interestState->getOutcome()) == !expectedOutcome) {
+        BOOST_CHECK_EQUAL(interestState->getOutcome(), !expectedOutcome);
+      }
+    }
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestChecker
+BOOST_AUTO_TEST_SUITE_END() // ValidatorConfig
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validator-config/common.hpp b/tests/unit/security/v2/validator-config/common.hpp
new file mode 100644
index 0000000..231e8eb
--- /dev/null
+++ b/tests/unit/security/v2/validator-config/common.hpp
@@ -0,0 +1,48 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_SECURITY_V2_VALIDATOR_CONFIG_COMMON_HPP
+#define NDN_TESTS_SECURITY_V2_VALIDATOR_CONFIG_COMMON_HPP
+
+#include <boost/property_tree/info_parser.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+namespace tests {
+
+inline ConfigSection
+makeSection(const std::string& config)
+{
+  std::istringstream inputStream(config);
+  ConfigSection section;
+  boost::property_tree::read_info(inputStream, section);
+  return section;
+}
+
+} // namespace tests
+} // namespace validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_TESTS_SECURITY_V2_VALIDATOR_CONFIG_COMMON_HPP
diff --git a/tests/unit/security/v2/validator-config/filter.t.cpp b/tests/unit/security/v2/validator-config/filter.t.cpp
new file mode 100644
index 0000000..fbd1264
--- /dev/null
+++ b/tests/unit/security/v2/validator-config/filter.t.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validator-config/filter.hpp"
+#include "security/command-interest-signer.hpp"
+
+#include "boost-test.hpp"
+#include "common.hpp"
+#include "identity-management-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(ValidatorConfig)
+
+class FilterFixture : public IdentityManagementFixture
+{
+public:
+  Interest
+  makeSignedInterest(const Name& name)
+  {
+    Interest interest(name);
+    m_keyChain.sign(interest);
+    return interest;
+  }
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestFilter, FilterFixture)
+
+#define CHECK_FOR_MATCHES(filter, same, longer, shorter, different)     \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/foo/bar").getName()), same); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/foo/bar").getName()), same);   \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/foo/bar/bar").getName()), longer); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/foo/bar/bar").getName()), longer);       \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/foo").getName()), shorter); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/foo").getName()), shorter);              \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Interest, makeSignedInterest("/other/prefix").getName()), different); \
+  BOOST_CHECK_EQUAL(filter.match(tlv::Data, Data("/other/prefix").getName()), different);
+
+BOOST_AUTO_TEST_CASE(RelationName)
+{
+  RelationNameFilter f1("/foo/bar", NameRelation::EQUAL);
+  CHECK_FOR_MATCHES(f1, true, false, false, false);
+
+  RelationNameFilter f2("/foo/bar", NameRelation::IS_PREFIX_OF);
+  CHECK_FOR_MATCHES(f2, true, true, false, false);
+
+  RelationNameFilter f3("/foo/bar", NameRelation::IS_STRICT_PREFIX_OF);
+  CHECK_FOR_MATCHES(f3, false, true, false, false);
+}
+
+BOOST_AUTO_TEST_CASE(RegexName)
+{
+  RegexNameFilter f1(Regex("^<foo><bar>$"));
+  CHECK_FOR_MATCHES(f1, true, false, false, false);
+
+  RegexNameFilter f2(Regex("^<foo><bar><>*$"));
+  CHECK_FOR_MATCHES(f2, true, true, false, false);
+
+  RegexNameFilter f3(Regex("^<foo><bar><>+$"));
+  CHECK_FOR_MATCHES(f3, false, true, false, false);
+}
+
+BOOST_FIXTURE_TEST_SUITE(Create, FilterFixture)
+
+BOOST_AUTO_TEST_CASE(Errors)
+{
+  BOOST_CHECK_THROW(Filter::create(makeSection(""), "test-config"), Error);
+  BOOST_CHECK_THROW(Filter::create(makeSection("type unknown"), "test-config"), Error);
+  BOOST_CHECK_THROW(Filter::create(makeSection("type name"), "test-config"), Error);
+
+  std::string config = R"CONF(
+      type name
+      not-name-or-regex stuff
+    )CONF";
+  BOOST_CHECK_THROW(Filter::create(makeSection(config), "test-config"), Error);
+
+  config = R"CONF(
+      type name
+      name /foo/bar
+    )CONF";
+  BOOST_CHECK_THROW(Filter::create(makeSection(config), "test-config"), Error);
+
+  config = R"CONF(
+      type name
+      name /foo/bar
+      not-relation stuff
+    )CONF";
+  BOOST_CHECK_THROW(Filter::create(makeSection(config), "test-config"), Error);
+
+  config = R"CONF(
+      type name
+      name /foo/bar
+      relation equal
+      not-end stuff
+    )CONF";
+  BOOST_CHECK_THROW(Filter::create(makeSection(config), "test-config"), Error);
+
+  config = R"CONF(
+      type name
+      regex ^<foo><bar>$
+      not-end stuff
+    )CONF";
+  BOOST_CHECK_THROW(Filter::create(makeSection(config), "test-config"), Error);
+}
+
+BOOST_AUTO_TEST_CASE(NameFilter)
+{
+  std::string config = R"CONF(
+      type name
+      name /foo/bar
+      relation equal
+    )CONF";
+  auto f1 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f1), true, false, false, false);
+
+  config = R"CONF(
+      type name
+      name /foo/bar
+      relation is-prefix-of
+    )CONF";
+  auto f2 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f2), true, true, false, false);
+
+  config = R"CONF(
+      type name
+      name /foo/bar
+      relation is-strict-prefix-of
+    )CONF";
+  auto f3 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f3), false, true, false, false);
+}
+
+BOOST_AUTO_TEST_CASE(RegexFilter)
+{
+  std::string config = R"CONF(
+      type name
+      regex ^<foo><bar>$
+    )CONF";
+  auto f1 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f1), true, false, false, false);
+
+  config = R"CONF(
+      type name
+      regex ^<foo><bar><>*$
+    )CONF";
+  auto f2 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f2), true, true, false, false);
+
+  config = R"CONF(
+      type name
+      regex ^<foo><bar><>+$
+    )CONF";
+  auto f3 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f3), false, true, false, false);
+
+  config = R"CONF(
+      type name
+      regex ^<>*$
+    )CONF";
+  auto f4 = Filter::create(makeSection(config), "test-config");
+  CHECK_FOR_MATCHES((*f4), true, true, true, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Create
+
+BOOST_AUTO_TEST_SUITE_END() // TestFilter
+BOOST_AUTO_TEST_SUITE_END() // ValidatorConfig
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validator-config/name-relation.t.cpp b/tests/unit/security/v2/validator-config/name-relation.t.cpp
new file mode 100644
index 0000000..157b55a
--- /dev/null
+++ b/tests/unit/security/v2/validator-config/name-relation.t.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validator-config/name-relation.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(ValidatorConfig)
+BOOST_AUTO_TEST_SUITE(TestNameRelation)
+
+BOOST_AUTO_TEST_CASE(ToString)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(NameRelation::EQUAL), "equal");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(NameRelation::IS_PREFIX_OF),
+                    "is-prefix-of");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(NameRelation::IS_STRICT_PREFIX_OF),
+                    "is-strict-prefix-of");
+}
+
+BOOST_AUTO_TEST_CASE(FromString)
+{
+  BOOST_CHECK_EQUAL(getNameRelationFromString("equal"), NameRelation::EQUAL);
+  BOOST_CHECK_EQUAL(getNameRelationFromString("is-prefix-of"), NameRelation::IS_PREFIX_OF);
+  BOOST_CHECK_EQUAL(getNameRelationFromString("is-strict-prefix-of"), NameRelation::IS_STRICT_PREFIX_OF);
+  BOOST_CHECK_THROW(getNameRelationFromString("unknown"), validator_config::Error);
+}
+
+BOOST_AUTO_TEST_CASE(CheckRelation)
+{
+  BOOST_CHECK(checkNameRelation(NameRelation::EQUAL, "/prefix", "/prefix"));
+  BOOST_CHECK(!checkNameRelation(NameRelation::EQUAL, "/prefix", "/prefix/other"));
+
+  BOOST_CHECK(checkNameRelation(NameRelation::IS_PREFIX_OF, "/prefix", "/prefix"));
+  BOOST_CHECK(checkNameRelation(NameRelation::IS_PREFIX_OF, "/prefix", "/prefix/other"));
+  BOOST_CHECK(!checkNameRelation(NameRelation::IS_PREFIX_OF, "/prefix/other", "/prefix"));
+
+  BOOST_CHECK(!checkNameRelation(NameRelation::IS_STRICT_PREFIX_OF, "/prefix", "/prefix"));
+  BOOST_CHECK(checkNameRelation(NameRelation::IS_STRICT_PREFIX_OF, "/prefix", "/prefix/other"));
+  BOOST_CHECK(!checkNameRelation(NameRelation::IS_STRICT_PREFIX_OF, "/prefix/other", "/prefix"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNameRelation
+BOOST_AUTO_TEST_SUITE_END() // ValidatorConfig
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validator-config/rule.t.cpp b/tests/unit/security/v2/validator-config/rule.t.cpp
new file mode 100644
index 0000000..bb78dfe
--- /dev/null
+++ b/tests/unit/security/v2/validator-config/rule.t.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validator-config/rule.hpp"
+
+#include "boost-test.hpp"
+#include "common.hpp"
+#include "identity-management-fixture.hpp"
+#include "../validator-fixture.hpp"
+
+#include <boost/mpl/vector_c.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+namespace tests {
+
+using namespace ndn::tests;
+using namespace ndn::security::v2::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(ValidatorConfig)
+
+template<uint32_t PktType>
+class RuleFixture : public IdentityManagementFixture
+{
+public:
+  RuleFixture()
+    : rule(ruleId, PktType)
+    , pktName("/foo/bar")
+  {
+    if (PktType == tlv::Interest) {
+      pktName = Name("/foo/bar/SigInfo/SigValue");
+    }
+  }
+
+public:
+  const std::string ruleId = "rule-id";
+  Rule rule;
+  Name pktName;
+};
+
+using PktTypes = boost::mpl::vector_c<uint32_t, tlv::Data, tlv::Interest>;
+
+BOOST_AUTO_TEST_SUITE(TestRule)
+
+BOOST_FIXTURE_TEST_CASE(Errors, RuleFixture<tlv::Data>)
+{
+  BOOST_CHECK_THROW(rule.match(tlv::Interest, this->pktName), Error);
+
+  auto state = make_shared<DummyValidationState>();
+  BOOST_CHECK_THROW(rule.check(tlv::Interest, this->pktName, "/foo/bar", state), Error);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Constructor, PktType, PktTypes, RuleFixture<PktType::value>)
+{
+  BOOST_CHECK_EQUAL(this->rule.getId(), this->ruleId);
+  BOOST_CHECK_EQUAL(this->rule.getPktType(), PktType::value);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(EmptyRule, PktType, PktTypes, RuleFixture<PktType::value>)
+{
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, this->pktName), true);
+
+  auto state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), false);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Filters, PktType, PktTypes, RuleFixture<PktType::value>)
+{
+  this->rule.addFilter(make_unique<RegexNameFilter>(Regex("^<foo><bar>$")));
+
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, this->pktName), true);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, "/not" + this->pktName.toUri()), false);
+
+  this->rule.addFilter(make_unique<RegexNameFilter>(Regex("^<not><foo><bar>$")));
+
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, this->pktName), true);
+  BOOST_CHECK_EQUAL(this->rule.match(PktType::value, "/not" + this->pktName.toUri()), true);
+
+  auto state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), false);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Checkers, PktType, PktTypes, RuleFixture<PktType::value>)
+{
+  this->rule.addChecker(make_unique<HyperRelationChecker>("^(<>+)$", "\\1",
+                                                        "^<not>?(<>+)$", "\\1",
+                                                        NameRelation::EQUAL));
+
+  auto state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), true);
+
+  state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/not/foo/bar", state), true);
+
+  this->rule.addChecker(make_unique<HyperRelationChecker>("^(<>+)$", "\\1",
+                                                        "^(<>+)$", "\\1",
+                                                        NameRelation::EQUAL));
+  state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/foo/bar", state), true);
+
+  state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(this->rule.check(PktType::value, this->pktName, "/not/foo/bar", state), false);
+}
+
+BOOST_AUTO_TEST_SUITE(Create)
+
+BOOST_AUTO_TEST_CASE(Errors)
+{
+  BOOST_CHECK_THROW(Rule::create(makeSection(""), "test-config"), Error);
+
+  std::string config = R"CONF(
+      id rule-id
+      for something
+    )CONF";
+  BOOST_CHECK_THROW(Rule::create(makeSection(config), "test-config"), Error);
+
+  config = R"CONF(
+      id rule-id
+      for data
+    )CONF";
+  BOOST_CHECK_THROW(Rule::create(makeSection(config), "test-config"), Error); // at least one checker required
+
+  config = R"CONF(
+      id rule-id
+      for data
+      checker
+      {
+        type hierarchical
+        sig-type rsa-sha256
+      }
+      other stuff
+    )CONF";
+  BOOST_CHECK_THROW(Rule::create(makeSection(config), "test-config"), Error);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(FilterAndChecker, PktType, PktTypes, RuleFixture<PktType::value>)
+{
+  std::string config = R"CONF(
+      id rule-id
+      for )CONF" + (PktType::value == tlv::Data ? "data"s : "interest"s) + R"CONF(
+      filter
+      {
+        type name
+        regex ^<foo><bar>$
+      }
+      checker
+      {
+        type customized
+        sig-type rsa-sha256
+        key-locator
+        {
+          type name
+          hyper-relation
+          {
+            k-regex ^(<>+)$
+            k-expand \\1
+            h-relation equal
+            p-regex ^(<>+)$
+            p-expand \\1
+          }
+        }
+      }
+    )CONF";
+  auto rule = Rule::create(makeSection(config), "test-config");
+
+  BOOST_CHECK_EQUAL(rule->match(PktType::value, this->pktName), true);
+  BOOST_CHECK_EQUAL(rule->match(PktType::value, "/not" + this->pktName.toUri()), false);
+
+  auto state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(rule->check(PktType::value, this->pktName, "/foo/bar", state), true);
+
+  state = make_shared<DummyValidationState>();
+  BOOST_CHECK_EQUAL(rule->check(PktType::value, this->pktName, "/not/foo/bar", state), false);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Create
+
+BOOST_AUTO_TEST_SUITE_END() // TestRule
+BOOST_AUTO_TEST_SUITE_END() // ValidatorConfig
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/v2/validator-fixture.hpp b/tests/unit/security/v2/validator-fixture.hpp
new file mode 100644
index 0000000..c9a5763
--- /dev/null
+++ b/tests/unit/security/v2/validator-fixture.hpp
@@ -0,0 +1,183 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_SECURITY_V2_VALIDATOR_FIXTURE_HPP
+#define NDN_TESTS_SECURITY_V2_VALIDATOR_FIXTURE_HPP
+
+#include "security/v2/validator.hpp"
+#include "security/v2/certificate-fetcher-from-network.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "../../identity-management-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+template<class ValidationPolicy, class CertificateFetcher = CertificateFetcherFromNetwork>
+class ValidatorFixture : public ndn::tests::IdentityManagementTimeFixture
+{
+public:
+  ValidatorFixture()
+    : face(io, {true, true})
+    , validator(make_unique<ValidationPolicy>(), make_unique<CertificateFetcher>(face))
+    , policy(static_cast<ValidationPolicy&>(validator.getPolicy()))
+    , cache(100_days)
+  {
+    processInterest = [this] (const Interest& interest) {
+      auto cert = cache.find(interest);
+      if (cert != nullptr) {
+        face.receive(*cert);
+      }
+    };
+  }
+
+  virtual
+  ~ValidatorFixture() = default;
+
+  template<class Packet>
+  void
+  validate(const Packet& packet, const std::string& msg, bool expectSuccess, int line)
+  {
+    std::string detailedInfo = msg + " on line " + to_string(line);
+    size_t nCallbacks = 0;
+    this->validator.validate(packet,
+      [&] (const Packet&) {
+        ++nCallbacks;
+        BOOST_CHECK_MESSAGE(expectSuccess,
+                            (expectSuccess ? "OK: " : "FAILED: ") + detailedInfo);
+      },
+      [&] (const Packet&, const ValidationError& error) {
+        ++nCallbacks;
+        BOOST_CHECK_MESSAGE(!expectSuccess,
+                            (!expectSuccess ? "OK: " : "FAILED: ") + detailedInfo +
+                            (expectSuccess ? " (" + boost::lexical_cast<std::string>(error) + ")" : ""));
+      });
+
+    mockNetworkOperations();
+    BOOST_CHECK_EQUAL(nCallbacks, 1);
+  }
+
+  void
+  mockNetworkOperations()
+  {
+    util::signal::ScopedConnection connection = face.onSendInterest.connect([this] (const Interest& interest) {
+        if (processInterest != nullptr) {
+          io.post(bind(processInterest, interest));
+        }
+      });
+    advanceClocks(time::milliseconds(s_mockPeriod), s_mockTimes);
+  }
+
+  /** \brief undo clock advancement of mockNetworkOperations
+   */
+  void
+  rewindClockAfterValidation()
+  {
+    this->systemClock->advance(time::milliseconds(s_mockPeriod * s_mockTimes * -1));
+  }
+
+public:
+  util::DummyClientFace face;
+  std::function<void(const Interest& interest)> processInterest;
+  Validator validator;
+  ValidationPolicy& policy;
+
+  CertificateCache cache;
+
+private:
+  const static int s_mockPeriod;
+  const static int s_mockTimes;
+};
+
+template<class ValidationPolicy, class CertificateFetcher>
+const int ValidatorFixture<ValidationPolicy, CertificateFetcher>::s_mockPeriod = 250;
+
+template<class ValidationPolicy, class CertificateFetcher>
+const int ValidatorFixture<ValidationPolicy, CertificateFetcher>::s_mockTimes = 200;
+
+template<class ValidationPolicy, class CertificateFetcher = CertificateFetcherFromNetwork>
+class HierarchicalValidatorFixture : public ValidatorFixture<ValidationPolicy, CertificateFetcher>
+{
+public:
+  HierarchicalValidatorFixture()
+  {
+    identity = this->addIdentity("/Security/V2/ValidatorFixture");
+    subIdentity = this->addSubCertificate("/Security/V2/ValidatorFixture/Sub1", identity);
+    subSelfSignedIdentity = this->addIdentity("/Security/V2/ValidatorFixture/Sub1/Sub2");
+    otherIdentity = this->addIdentity("/Security/V2/OtherIdentity");
+
+    this->validator.loadAnchor("", Certificate(identity.getDefaultKey().getDefaultCertificate()));
+
+    this->cache.insert(identity.getDefaultKey().getDefaultCertificate());
+    this->cache.insert(subIdentity.getDefaultKey().getDefaultCertificate());
+    this->cache.insert(subSelfSignedIdentity.getDefaultKey().getDefaultCertificate());
+    this->cache.insert(otherIdentity.getDefaultKey().getDefaultCertificate());
+  }
+
+public:
+  Identity identity;
+  Identity subIdentity;
+  Identity subSelfSignedIdentity;
+  Identity otherIdentity;
+};
+
+#define VALIDATE_SUCCESS(packet, message) this->template validate(packet, message, true, __LINE__)
+#define VALIDATE_FAILURE(packet, message) this->template validate(packet, message, false, __LINE__)
+
+class DummyValidationState : public ValidationState
+{
+public:
+  ~DummyValidationState()
+  {
+    m_outcome = false;
+  }
+
+  void
+  fail(const ValidationError& error) override
+  {
+    // BOOST_TEST_MESSAGE(error);
+    m_outcome = false;
+  }
+
+private:
+  void
+  verifyOriginalPacket(const Certificate& trustedCert) override
+  {
+    // do nothing
+  }
+
+  void
+  bypassValidation() override
+  {
+    // do nothing
+  }
+};
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_TESTS_SECURITY_V2_VALIDATOR_FIXTURE_HPP
diff --git a/tests/unit/security/v2/validator.t.cpp b/tests/unit/security/v2/validator.t.cpp
new file mode 100644
index 0000000..ff33984
--- /dev/null
+++ b/tests/unit/security/v2/validator.t.cpp
@@ -0,0 +1,344 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/v2/validator.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_FIXTURE_TEST_SUITE(TestValidator, HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy>)
+
+BOOST_AUTO_TEST_CASE(ConstructorSetValidator)
+{
+  auto middlePolicy = make_unique<ValidationPolicySimpleHierarchy>();
+  auto innerPolicy = make_unique<ValidationPolicySimpleHierarchy>();
+
+  validator.getPolicy().setInnerPolicy(std::move(middlePolicy));
+  validator.getPolicy().setInnerPolicy(std::move(innerPolicy));
+
+  BOOST_CHECK(validator.getPolicy().m_validator != nullptr);
+  BOOST_CHECK(validator.getPolicy().getInnerPolicy().m_validator != nullptr);
+  BOOST_CHECK(validator.getPolicy().getInnerPolicy().getInnerPolicy().m_validator != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(Timeouts)
+{
+  processInterest = nullptr; // no response for all interests
+
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+
+  VALIDATE_FAILURE(data, "Should fail to retrieve certificate");
+  BOOST_CHECK_GT(face.sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(NackedInterests)
+{
+  processInterest = [this] (const Interest& interest) {
+    lp::Nack nack(interest);
+    nack.setReason(lp::NackReason::NO_ROUTE);
+    face.receive(nack);
+  };
+
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+
+  VALIDATE_FAILURE(data, "All interests should get NACKed");
+  // 1 for the first interest, 3 for the retries on nack
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(MalformedCert)
+{
+  Data malformedCert = subIdentity.getDefaultKey().getDefaultCertificate();
+  malformedCert.setContentType(tlv::ContentType_Blob);
+  m_keyChain.sign(malformedCert, signingByIdentity(identity));
+  // wrong content type & missing ValidityPeriod
+  BOOST_REQUIRE_THROW(Certificate(malformedCert.wireEncode()), tlv::Error);
+
+  auto originalProcessInterest = processInterest;
+  processInterest = [this, &originalProcessInterest, &malformedCert] (const Interest& interest) {
+    if (interest.getName().isPrefixOf(malformedCert.getName())) {
+      face.receive(malformedCert);
+    }
+    else {
+      originalProcessInterest(interest);
+    }
+  };
+
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+
+  VALIDATE_FAILURE(data, "Signed by a malformed certificate");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ExpiredCert)
+{
+  Data expiredCert = subIdentity.getDefaultKey().getDefaultCertificate();
+  SignatureInfo info;
+  info.setValidityPeriod(ValidityPeriod(time::system_clock::now() - 2_h,
+                                        time::system_clock::now() - 1_h));
+  m_keyChain.sign(expiredCert, signingByIdentity(identity).setSignatureInfo(info));
+  BOOST_REQUIRE_NO_THROW(Certificate(expiredCert.wireEncode()));
+
+  auto originalProcessInterest = processInterest;
+  processInterest = [this, &originalProcessInterest, &expiredCert] (const Interest& interest) {
+    if (interest.getName().isPrefixOf(expiredCert.getName())) {
+      face.receive(expiredCert);
+    }
+    else {
+      originalProcessInterest(interest);
+    }
+  };
+
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+
+  VALIDATE_FAILURE(data, "Signed by an expired certificate");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ResetAnchors)
+{
+  validator.resetAnchors();
+
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+  VALIDATE_FAILURE(data, "Should fail, as no anchors configured");
+}
+
+BOOST_AUTO_TEST_CASE(TrustedCertCaching)
+{
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+
+  VALIDATE_SUCCESS(data, "Should get accepted, as signed by the policy-compliant cert");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+  face.sentInterests.clear();
+
+  processInterest = nullptr; // disable data responses from mocked network
+
+  VALIDATE_SUCCESS(data, "Should get accepted, based on the cached trusted cert");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+  face.sentInterests.clear();
+
+  advanceClocks(1_h, 2); // expire trusted cache
+
+  VALIDATE_FAILURE(data, "Should try and fail to retrieve certs");
+  BOOST_CHECK_GT(face.sentInterests.size(), 1);
+  face.sentInterests.clear();
+}
+
+BOOST_AUTO_TEST_CASE(ResetVerifiedCertificates)
+{
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+  VALIDATE_SUCCESS(data, "Should get accepted, as signed by the policy-compliant cert");
+
+  // reset anchors
+  validator.resetAnchors();
+  VALIDATE_SUCCESS(data, "Should get accepted, as signed by the cert in trusted cache");
+
+  // reset trusted cache
+  validator.resetVerifiedCertificates();
+  VALIDATE_FAILURE(data, "Should fail, as no trusted cache or anchors");
+}
+
+BOOST_AUTO_TEST_CASE(UntrustedCertCaching)
+{
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subSelfSignedIdentity));
+
+  VALIDATE_FAILURE(data, "Should fail, as signed by the policy-violating cert");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+  face.sentInterests.clear();
+
+  processInterest = nullptr; // disable data responses from mocked network
+
+  VALIDATE_FAILURE(data, "Should fail again, but no network operations expected");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+  face.sentInterests.clear();
+
+  advanceClocks(10_min, 2); // expire untrusted cache
+
+  VALIDATE_FAILURE(data, "Should try and fail to retrieve certs");
+  BOOST_CHECK_GT(face.sentInterests.size(), 1);
+  face.sentInterests.clear();
+}
+
+class ValidationPolicySimpleHierarchyForInterestOnly : public ValidationPolicySimpleHierarchy
+{
+public:
+  void
+  checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
+              const ValidationContinuation& continueValidation) override
+  {
+    continueValidation(nullptr, state);
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(ValidateInterestsButBypassForData,
+                        HierarchicalValidatorFixture<ValidationPolicySimpleHierarchyForInterestOnly>)
+{
+  Interest interest("/Security/V2/ValidatorFixture/Sub1/Sub2/Interest");
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Interest");
+
+  VALIDATE_FAILURE(interest, "Unsigned");
+  VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+  face.sentInterests.clear();
+
+  interest = Interest("/Security/V2/ValidatorFixture/Sub1/Sub2/Interest");
+  m_keyChain.sign(interest, signingWithSha256());
+  m_keyChain.sign(data, signingWithSha256());
+  VALIDATE_FAILURE(interest, "Required KeyLocator/Name missing (not passed to policy)");
+  VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+  face.sentInterests.clear();
+
+  m_keyChain.sign(interest, signingByIdentity(identity));
+  m_keyChain.sign(data, signingByIdentity(identity));
+  VALIDATE_SUCCESS(interest, "Should get accepted, as signed by the anchor");
+  VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+  face.sentInterests.clear();
+
+  m_keyChain.sign(interest, signingByIdentity(subIdentity));
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+  VALIDATE_FAILURE(interest, "Should fail, as policy is not allowed to create new trust anchors");
+  VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+  face.sentInterests.clear();
+
+  m_keyChain.sign(interest, signingByIdentity(otherIdentity));
+  m_keyChain.sign(data, signingByIdentity(otherIdentity));
+  VALIDATE_FAILURE(interest, "Should fail, as signed by the policy-violating cert");
+  VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
+  // no network operations expected, as certificate is not validated by the policy
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
+  face.sentInterests.clear();
+
+  advanceClocks(1_h, 2); // expire trusted cache
+
+  m_keyChain.sign(interest, signingByIdentity(subSelfSignedIdentity));
+  m_keyChain.sign(data, signingByIdentity(subSelfSignedIdentity));
+  VALIDATE_FAILURE(interest, "Should fail, as policy is not allowed to create new trust anchors");
+  VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+  face.sentInterests.clear();
+}
+
+BOOST_AUTO_TEST_CASE(InfiniteCertChain)
+{
+  processInterest = [this] (const Interest& interest) {
+    // create another key for the same identity and sign it properly
+    Key parentKey = m_keyChain.createKey(subIdentity);
+    Key requestedKey = subIdentity.getKey(interest.getName());
+
+    Name certificateName = requestedKey.getName();
+    certificateName
+    .append("looper")
+    .appendVersion();
+    v2::Certificate certificate;
+    certificate.setName(certificateName);
+
+    // set metainfo
+    certificate.setContentType(tlv::ContentType_Key);
+    certificate.setFreshnessPeriod(1_h);
+
+    // set content
+    certificate.setContent(requestedKey.getPublicKey().data(), requestedKey.getPublicKey().size());
+
+    // set signature-info
+    SignatureInfo info;
+    info.setValidityPeriod(security::ValidityPeriod(time::system_clock::now() - 10_days,
+                                                    time::system_clock::now() + 10_days));
+
+    m_keyChain.sign(certificate, signingByKey(parentKey).setSignatureInfo(info));
+    face.receive(certificate);
+  };
+
+  Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
+  m_keyChain.sign(data, signingByIdentity(subIdentity));
+
+  validator.setMaxDepth(40);
+  BOOST_CHECK_EQUAL(validator.getMaxDepth(), 40);
+  VALIDATE_FAILURE(data, "Should fail, as certificate should be looped");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 40);
+  face.sentInterests.clear();
+
+  advanceClocks(1_h, 5); // expire caches
+
+  validator.setMaxDepth(30);
+  BOOST_CHECK_EQUAL(validator.getMaxDepth(), 30);
+  VALIDATE_FAILURE(data, "Should fail, as certificate chain is infinite");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 30);
+}
+
+BOOST_AUTO_TEST_CASE(LoopedCertChain)
+{
+  auto s1 = addIdentity("/loop");
+  auto k1 = m_keyChain.createKey(s1, RsaKeyParams(name::Component("key1")));
+  auto k2 = m_keyChain.createKey(s1, RsaKeyParams(name::Component("key2")));
+  auto k3 = m_keyChain.createKey(s1, RsaKeyParams(name::Component("key3")));
+
+  auto makeCert = [this] (Key& key, const Key& signer) {
+    v2::Certificate request = key.getDefaultCertificate();
+    request.setName(Name(key.getName()).append("looper").appendVersion());
+
+    SignatureInfo info;
+    info.setValidityPeriod({time::system_clock::now() - 100_days,
+                            time::system_clock::now() + 100_days});
+    m_keyChain.sign(request, signingByKey(signer).setSignatureInfo(info));
+    m_keyChain.addCertificate(key, request);
+
+    cache.insert(request);
+  };
+
+  makeCert(k1, k2);
+  makeCert(k2, k3);
+  makeCert(k3, k1);
+
+  Data data("/loop/Data");
+  m_keyChain.sign(data, signingByKey(k1));
+  VALIDATE_FAILURE(data, "Should fail, as certificate chain loops");
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidator
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/validator-config.t.cpp b/tests/unit/security/validator-config.t.cpp
new file mode 100644
index 0000000..3e49d24
--- /dev/null
+++ b/tests/unit/security/validator-config.t.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/validator-config.hpp"
+#include "security/command-interest-signer.hpp"
+#include "security/v2/certificate-fetcher-offline.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include "v2/validator-config/common.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_FIXTURE_TEST_SUITE(TestValidatorConfig, IdentityManagementFixture)
+
+// This test only for API, actual tests are in ValidationPolicyConfig and corresponding CertificateFetchers
+
+BOOST_AUTO_TEST_CASE(Construct)
+{
+  util::DummyClientFace face;
+
+  ValidatorConfig v1(face);
+  BOOST_CHECK_EQUAL(v1.m_policyConfig.m_isConfigured, false);
+
+  ValidatorConfig v2(make_unique<v2::CertificateFetcherOffline>());
+  BOOST_CHECK_EQUAL(v2.m_policyConfig.m_isConfigured, false);
+}
+
+class ValidatorConfigFixture : public IdentityManagementFixture
+{
+public:
+  ValidatorConfigFixture()
+    : path(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) / "security" / "validator-config")
+    , validator(make_unique<v2::CertificateFetcherOffline>())
+  {
+    boost::filesystem::create_directories(path);
+    config = R"CONF(
+        trust-anchor
+        {
+          type any
+        }
+      )CONF";
+    configFile = (this->path / "config.conf").string();
+    std::ofstream f(configFile.c_str());
+    f << config;
+  }
+
+  ~ValidatorConfigFixture()
+  {
+    boost::filesystem::remove_all(path);
+  }
+
+public:
+  const boost::filesystem::path path;
+  std::string config;
+  std::string configFile;
+  ValidatorConfig validator;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Loads, ValidatorConfigFixture)
+
+BOOST_AUTO_TEST_CASE(FromFile)
+{
+  validator.load(configFile);
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+
+  // should reload policy
+  validator.load(configFile);
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+}
+
+BOOST_AUTO_TEST_CASE(FromString)
+{
+  validator.load(config, "config-file-from-string");
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+
+  // should reload policy
+  validator.load(config, "config-file-from-string");
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+}
+
+BOOST_AUTO_TEST_CASE(FromIstream)
+{
+  std::istringstream is(config);
+  validator.load(is, "config-file-from-istream");
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+
+  // should reload policy
+  std::istringstream is2(config);
+  validator.load(is2, "config-file-from-istream");
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+}
+
+BOOST_AUTO_TEST_CASE(FromSection)
+{
+  validator.load(v2::validator_config::tests::makeSection(config), "config-file-from-section");
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+
+  // should reload policy
+  validator.load(v2::validator_config::tests::makeSection(config), "config-file-from-section");
+  BOOST_CHECK_EQUAL(validator.m_policyConfig.m_isConfigured, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Loads
+
+
+BOOST_FIXTURE_TEST_CASE(ValidateCommandInterestWithDigestSha256, ValidatorConfigFixture) // Bug 4635
+{
+  validator.load(configFile);
+
+  CommandInterestSigner signer(m_keyChain);
+  auto i = signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
+  size_t nValidated = 0, nFailed = 0;
+
+  validator.validate(i, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+  BOOST_CHECK_EQUAL(nValidated, 1);
+  BOOST_CHECK_EQUAL(nFailed, 0);
+
+  validator.validate(i, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+  BOOST_CHECK_EQUAL(nValidated, 1);
+  BOOST_CHECK_EQUAL(nFailed, 1);
+
+  i = signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
+  validator.validate(i, [&] (auto&&...) { ++nValidated; }, [&] (auto&&...) { ++nFailed; });
+  BOOST_CHECK_EQUAL(nValidated, 2);
+  BOOST_CHECK_EQUAL(nFailed, 1);
+}
+
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidatorConfig
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/validator-null.t.cpp b/tests/unit/security/validator-null.t.cpp
new file mode 100644
index 0000000..69cef48
--- /dev/null
+++ b/tests/unit/security/validator-null.t.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/validator-null.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_FIXTURE_TEST_SUITE(TestValidatorNull, IdentityManagementFixture)
+
+BOOST_AUTO_TEST_CASE(ValidateData)
+{
+  auto identity = addIdentity("/TestValidator/Null");
+  Data data("/Some/Other/Data/Name");
+  m_keyChain.sign(data, signingByIdentity(identity));
+
+  ValidatorNull validator;
+  validator.validate(data,
+                     bind([] { BOOST_CHECK_MESSAGE(true, "Validation should succeed"); }),
+                     bind([] { BOOST_CHECK_MESSAGE(false, "Validation should not have failed"); }));
+}
+
+BOOST_AUTO_TEST_CASE(ValidateInterest)
+{
+  auto identity = addIdentity("/TestValidator/Null");
+  Interest interest("/Some/Other/Interest/Name");
+  m_keyChain.sign(interest, signingByIdentity(identity));
+
+  ValidatorNull validator;
+  validator.validate(interest,
+                     bind([] { BOOST_CHECK_MESSAGE(true, "Validation should succeed"); }),
+                     bind([] { BOOST_CHECK_MESSAGE(false, "Validation should not have failed"); }));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidatorNull
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/validity-period.t.cpp b/tests/unit/security/validity-period.t.cpp
new file mode 100644
index 0000000..14d2a0f
--- /dev/null
+++ b/tests/unit/security/validity-period.t.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/validity-period.hpp"
+
+#include "boost-test.hpp"
+#include "../unit-test-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestValidityPeriod)
+
+BOOST_FIXTURE_TEST_CASE(ConstructorSetter, UnitTestTimeFixture)
+{
+  time::system_clock::TimePoint now = this->systemClock->getNow();
+
+  time::system_clock::TimePoint notBefore = now - 1_day;
+  time::system_clock::TimePoint notAfter = notBefore + 2_days;
+
+  ValidityPeriod validity1 = ValidityPeriod(notBefore, notAfter);
+
+  auto period = validity1.getPeriod();
+  BOOST_CHECK_GE(period.first, notBefore); // fractional seconds will be removed
+  BOOST_CHECK_LT(period.first, notBefore + 1_s);
+
+  BOOST_CHECK_LE(period.second, notAfter); // fractional seconds will be removed
+  BOOST_CHECK_GT(period.second, notAfter - 1_s);
+  BOOST_CHECK_EQUAL(validity1.isValid(), true);
+
+  BOOST_CHECK_EQUAL(ValidityPeriod(now - 2_days, now - 1_day).isValid(), false);
+
+  BOOST_CHECK_NO_THROW((ValidityPeriod()));
+  ValidityPeriod validity2;
+  BOOST_CHECK_EQUAL(validity2.isValid(), false);
+
+  validity2.setPeriod(notBefore, notAfter);
+  BOOST_CHECK(validity2.getPeriod() != std::make_pair(time::getUnixEpoch(), time::getUnixEpoch()));
+  BOOST_CHECK_EQUAL(validity2, validity1);
+
+  validity1.setPeriod(time::getUnixEpoch(), time::getUnixEpoch() + 10 * 365_days);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(validity1),
+                    "(19700101T000000, 19791230T000000)");
+
+  validity1.setPeriod(time::getUnixEpoch() + 1_ns,
+                      time::getUnixEpoch() + (10 * 365_days) + 1_ns);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(validity1),
+                    "(19700101T000001, 19791230T000000)");
+
+  BOOST_CHECK_EQUAL(ValidityPeriod(now, now).isValid(), true);
+  BOOST_CHECK_EQUAL(ValidityPeriod(now + 1_s, now).isValid(), false);
+}
+
+const uint8_t VP1[] = {
+  0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+BOOST_AUTO_TEST_CASE(EncodingDecoding)
+{
+  time::system_clock::TimePoint notBefore = time::getUnixEpoch();
+  time::system_clock::TimePoint notAfter = notBefore + 1_day;
+
+  ValidityPeriod v1(notBefore, notAfter);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(v1.wireEncode().begin(), v1.wireEncode().end(),
+                                VP1, VP1 + sizeof(VP1));
+
+  BOOST_REQUIRE_NO_THROW(ValidityPeriod(Block(VP1, sizeof(VP1))));
+  Block block(VP1, sizeof(VP1));
+  ValidityPeriod v2(block);
+  BOOST_CHECK(v1.getPeriod() == v2.getPeriod());
+}
+
+const uint8_t VP_E1[] = {
+  0xfd, 0x00, 0xff, 0x26, // ValidityPeriod (error)
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+const uint8_t VP_E2[] = {
+  0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
+    0xfd, 0x00, 0xff, 0x0f, // NotBefore (error)
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+const uint8_t VP_E3[] = {
+  0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xfe, 0x0f, // NotAfter (error)
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+const uint8_t VP_E4[] = {
+  0xfd, 0x00, 0xfd, 0x39, // ValidityPeriod
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter (error)
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+const uint8_t VP_E5[] = {
+  0xfd, 0x00, 0xfd, 0x13, // ValidityPeriod
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+const uint8_t VP_E6[] = {
+  0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
+    0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T00000\xFF
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFF,
+    0xfd, 0x00, 0xff, 0x0f, // NotAfter
+      0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+      0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+};
+
+
+BOOST_AUTO_TEST_CASE(DecodingError)
+{
+  BOOST_CHECK_THROW(ValidityPeriod(Block(VP_E1, sizeof(VP_E1))), ValidityPeriod::Error);
+
+  BOOST_CHECK_THROW(ValidityPeriod(Block(VP_E2, sizeof(VP_E2))), ValidityPeriod::Error);
+  BOOST_CHECK_THROW(ValidityPeriod(Block(VP_E3, sizeof(VP_E3))), ValidityPeriod::Error);
+
+  BOOST_CHECK_THROW(ValidityPeriod(Block(VP_E4, sizeof(VP_E4))), ValidityPeriod::Error);
+  BOOST_CHECK_THROW(ValidityPeriod(Block(VP_E5, sizeof(VP_E5))), ValidityPeriod::Error);
+
+  Block emptyBlock;
+  BOOST_CHECK_THROW((ValidityPeriod(emptyBlock)), ValidityPeriod::Error);
+
+  BOOST_CHECK_THROW(ValidityPeriod(Block(VP_E6, sizeof(VP_E6))), ValidityPeriod::Error);
+}
+
+BOOST_AUTO_TEST_CASE(Comparison)
+{
+  time::system_clock::TimePoint notBefore = time::getUnixEpoch();
+  time::system_clock::TimePoint notAfter = notBefore + 1_day;
+  time::system_clock::TimePoint notAfter2 = notBefore + 2_days;
+
+  ValidityPeriod validity1(notBefore, notAfter);
+  ValidityPeriod validity2(notBefore, notAfter);
+  BOOST_CHECK(validity1 == validity2);
+
+  ValidityPeriod validity3(notBefore, notAfter2);
+  BOOST_CHECK(validity1 != validity3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestValidityPeriod
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/security/verification-helpers.t.cpp b/tests/unit/security/verification-helpers.t.cpp
new file mode 100644
index 0000000..d06580a
--- /dev/null
+++ b/tests/unit/security/verification-helpers.t.cpp
@@ -0,0 +1,504 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "security/verification-helpers.hpp"
+#include "security/transform/public-key.hpp"
+// #include "util/string-helper.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+#include "make-interest-data.hpp"
+
+#include <boost/mpl/list.hpp>
+
+namespace ndn {
+namespace security {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(TestVerificationHelpers)
+
+// // Using this test case to generate dataset if signature format changes
+// BOOST_FIXTURE_TEST_CASE(Generator, IdentityManagementV2Fixture)
+// {
+//   Identity wrongIdentity = addIdentity("/Security/TestVerificationHelpers/Wrong");
+//   std::map<std::string, SigningInfo> identities = {
+//     {"Ecdsa", signingByIdentity(addIdentity("/Security/TestVerificationHelpers/EC", EcKeyParams()))},
+//     {"Rsa", signingByIdentity(addIdentity("/Security/TestVerificationHelpers/RSA", RsaKeyParams()))},
+//     {"Sha256", signingWithSha256()}
+//   };
+
+//   auto print = [] (const std::string& name, const uint8_t* buf, size_t size) {
+//     std::cout << "  std::vector<uint8_t> " + name + " = {\n    ";
+
+//     std::string hex = toHex(buf, size);
+
+//     for (size_t i = 0; i < hex.size(); i++) {
+//       if (i > 0 && i % 32 == 0)
+//         std::cout << "\n    ";
+
+//       std::cout << "0x" << hex[i];
+//       std::cout << hex[++i];
+
+//       if ((i + 1) != hex.size())
+//         std::cout << ", ";
+//     }
+//     std::cout << "\n  };";
+//   };
+
+//   for (const auto& i : identities) {
+//     const std::string& type = i.first;
+//     const SigningInfo& signingInfo = i.second;
+
+//     std::cout << "struct " + type + "Dataset\n{\n";
+//     std::cout << "  const std::string name = \"" << type << "\";\n";
+
+//     if (signingInfo.getSignerType() == SigningInfo::SIGNER_TYPE_ID) {
+//       print("cert", signingInfo.getPibIdentity().getDefaultKey().getDefaultCertificate().wireEncode().wire(),
+//             signingInfo.getPibIdentity().getDefaultKey().getDefaultCertificate().wireEncode().size());
+//     }
+//     else {
+//       print("cert", nullptr, 0);
+//     }
+//     std::cout << "\n";
+
+//     // Create data that can be verified by cert
+//     Data data(Name("/test/data").append(type));
+//     m_keyChain.sign(data, signingInfo);
+//     print("goodData", data.wireEncode().wire(), data.wireEncode().size());
+//     std::cout << "\n";
+
+//     // Create data that cannot be verified by cert
+//     m_keyChain.sign(data, signingByIdentity(wrongIdentity));
+//     print("badSigData", data.wireEncode().wire(), data.wireEncode().size());
+//     std::cout << "\n";
+
+//     // Create interest that can be verified by cert
+//     Interest interest(Name("/test/interest/").append(type));
+//     m_keyChain.sign(interest, signingInfo);
+//     print("goodInterest", interest.wireEncode().wire(), interest.wireEncode().size());
+//     std::cout << "\n";
+
+//     // Create interest that cannot be verified by cert
+//     m_keyChain.sign(interest, signingByIdentity(wrongIdentity));
+//     print("badSigInterest", interest.wireEncode().wire(), interest.wireEncode().size());
+//     std::cout << "\n};\n\n";
+//   }
+// }
+
+struct EcdsaDataset
+{
+  const std::string name = "Ecdsa";
+  std::vector<uint8_t> cert = {
+    0x06, 0xFD, 0x02, 0x59, 0x07, 0x47, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79,
+    0x08, 0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+    0x6F, 0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x02, 0x45, 0x43, 0x08, 0x03, 0x4B,
+    0x45, 0x59, 0x08, 0x08, 0xDC, 0x9E, 0x3B, 0x11, 0x2A, 0x81, 0x7E, 0x92, 0x08, 0x04, 0x73, 0x65,
+    0x6C, 0x66, 0x08, 0x09, 0xFD, 0x00, 0x00, 0x01, 0x59, 0x90, 0x5F, 0x6F, 0x4F, 0x14, 0x09, 0x18,
+    0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80, 0x15, 0xFD, 0x01, 0x4F, 0x30, 0x82, 0x01, 0x4B,
+    0x30, 0x82, 0x01, 0x03, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x30, 0x81, 0xF7,
+    0x02, 0x01, 0x01, 0x30, 0x2C, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x01, 0x01, 0x02, 0x21,
+    0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+    0xFF, 0x30, 0x5B, 0x04, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x04, 0x20, 0x5A, 0xC6, 0x35, 0xD8, 0xAA, 0x3A, 0x93, 0xE7, 0xB3,
+    0xEB, 0xBD, 0x55, 0x76, 0x98, 0x86, 0xBC, 0x65, 0x1D, 0x06, 0xB0, 0xCC, 0x53, 0xB0, 0xF6, 0x3B,
+    0xCE, 0x3C, 0x3E, 0x27, 0xD2, 0x60, 0x4B, 0x03, 0x15, 0x00, 0xC4, 0x9D, 0x36, 0x08, 0x86, 0xE7,
+    0x04, 0x93, 0x6A, 0x66, 0x78, 0xE1, 0x13, 0x9D, 0x26, 0xB7, 0x81, 0x9F, 0x7E, 0x90, 0x04, 0x41,
+    0x04, 0x6B, 0x17, 0xD1, 0xF2, 0xE1, 0x2C, 0x42, 0x47, 0xF8, 0xBC, 0xE6, 0xE5, 0x63, 0xA4, 0x40,
+    0xF2, 0x77, 0x03, 0x7D, 0x81, 0x2D, 0xEB, 0x33, 0xA0, 0xF4, 0xA1, 0x39, 0x45, 0xD8, 0x98, 0xC2,
+    0x96, 0x4F, 0xE3, 0x42, 0xE2, 0xFE, 0x1A, 0x7F, 0x9B, 0x8E, 0xE7, 0xEB, 0x4A, 0x7C, 0x0F, 0x9E,
+    0x16, 0x2B, 0xCE, 0x33, 0x57, 0x6B, 0x31, 0x5E, 0xCE, 0xCB, 0xB6, 0x40, 0x68, 0x37, 0xBF, 0x51,
+    0xF5, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2,
+    0xFC, 0x63, 0x25, 0x51, 0x02, 0x01, 0x01, 0x03, 0x42, 0x00, 0x04, 0x78, 0xF2, 0x68, 0x89, 0x92,
+    0x8D, 0x84, 0x17, 0xF1, 0x2B, 0x50, 0x4C, 0x9B, 0xFA, 0x6C, 0x4D, 0x8D, 0x29, 0x7D, 0x85, 0xDC,
+    0x03, 0x09, 0x72, 0xFA, 0x06, 0xD4, 0x5C, 0xCB, 0xA6, 0x62, 0x0A, 0x7E, 0x2D, 0x50, 0xF0, 0x07,
+    0xDE, 0xE0, 0x34, 0xF6, 0xC2, 0xAE, 0xA9, 0x32, 0x9B, 0x2C, 0xBD, 0x25, 0xAF, 0xB7, 0xE1, 0x7C,
+    0xCD, 0x85, 0xF4, 0x6D, 0x31, 0xD6, 0xC0, 0x01, 0xC3, 0xEF, 0xB3, 0x16, 0x67, 0x1B, 0x01, 0x03,
+    0x1C, 0x38, 0x07, 0x36, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17,
+    0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E,
+    0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x02, 0x45, 0x43, 0x08, 0x03, 0x4B, 0x45, 0x59,
+    0x08, 0x08, 0xDC, 0x9E, 0x3B, 0x11, 0x2A, 0x81, 0x7E, 0x92, 0xFD, 0x00, 0xFD, 0x26, 0xFD, 0x00,
+    0xFE, 0x0F, 0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30, 0x33, 0x37, 0x30, 0x31, 0x30, 0x37, 0x54, 0x30, 0x31,
+    0x35, 0x31, 0x33, 0x30, 0x17, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xC3, 0xB4, 0x2A, 0x00, 0x58,
+    0x97, 0x42, 0xDA, 0x54, 0x4C, 0xA6, 0xEF, 0x0C, 0x40, 0x51, 0x88, 0x5C, 0x86, 0x99, 0xF2, 0xBC,
+    0x15, 0xEA, 0x06, 0x64, 0xA5, 0xD5, 0xFF, 0x2B, 0xFA, 0xD1, 0xD3, 0x02, 0x20, 0x35, 0x4C, 0xC0,
+    0x0D, 0x0F, 0x8E, 0x43, 0x56, 0x12, 0x60, 0x8C, 0x98, 0xF1, 0x5F, 0xC4, 0xD5, 0xF2, 0x25, 0x21,
+    0xD4, 0x9C, 0x94, 0x55, 0x0D, 0x4F, 0xDE, 0x14, 0x51, 0x82, 0xB3, 0x8E, 0xA1
+  };
+  std::vector<uint8_t> goodData = {
+    0x06, 0x9E, 0x07, 0x10, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x04, 0x64, 0x61, 0x74, 0x61,
+    0x08, 0x02, 0x45, 0x63, 0x14, 0x00, 0x15, 0x00, 0x16, 0x3D, 0x1B, 0x01, 0x03, 0x1C, 0x38, 0x07,
+    0x36, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54, 0x65, 0x73,
+    0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65, 0x6C,
+    0x70, 0x65, 0x72, 0x73, 0x08, 0x02, 0x45, 0x43, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x08, 0xDC,
+    0x9E, 0x3B, 0x11, 0x2A, 0x81, 0x7E, 0x92, 0x17, 0x47, 0x30, 0x45, 0x02, 0x20, 0x29, 0x8B, 0xE5,
+    0x0B, 0xD4, 0x24, 0x92, 0xA7, 0x5A, 0x36, 0x73, 0x5A, 0xC2, 0xBE, 0x17, 0x0D, 0xCA, 0x6C, 0xBB,
+    0xF9, 0x15, 0x60, 0xBD, 0x08, 0x8E, 0xA9, 0x5B, 0x31, 0x94, 0xF2, 0xB7, 0x97, 0x02, 0x21, 0x00,
+    0xA9, 0x02, 0xEF, 0x0F, 0x45, 0xCB, 0xC8, 0x5C, 0xF5, 0xD6, 0xCB, 0x90, 0x8E, 0x42, 0x07, 0xA0,
+    0xA0, 0x34, 0x6A, 0x61, 0xAD, 0x24, 0x9E, 0xB7, 0x73, 0x98, 0xA4, 0x6C, 0x2D, 0xD6, 0x12, 0xE2
+  };
+  std::vector<uint8_t> badSigData = {
+    0x06, 0xA2, 0x07, 0x10, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x04, 0x64, 0x61, 0x74, 0x61,
+    0x08, 0x02, 0x45, 0x63, 0x14, 0x00, 0x15, 0x00, 0x16, 0x40, 0x1B, 0x01, 0x03, 0x1C, 0x3B, 0x07,
+    0x39, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54, 0x65, 0x73,
+    0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65, 0x6C,
+    0x70, 0x65, 0x72, 0x73, 0x08, 0x05, 0x57, 0x72, 0x6F, 0x6E, 0x67, 0x08, 0x03, 0x4B, 0x45, 0x59,
+    0x08, 0x08, 0x57, 0x76, 0xDC, 0x93, 0x8D, 0x34, 0x59, 0x9C, 0x17, 0x48, 0x30, 0x46, 0x02, 0x21,
+    0x00, 0xB9, 0x4B, 0x40, 0x62, 0xAD, 0xAD, 0xFA, 0x75, 0x69, 0xDE, 0x48, 0x90, 0xC4, 0x11, 0x6B,
+    0xC7, 0x9E, 0x6C, 0x3E, 0x04, 0x78, 0x53, 0xAB, 0xF2, 0x18, 0x16, 0x9F, 0x8D, 0x1A, 0x14, 0x68,
+    0x57, 0x02, 0x21, 0x00, 0xC1, 0xA4, 0xC8, 0x00, 0x0B, 0x61, 0xA0, 0x2C, 0x88, 0x33, 0x7C, 0xE1,
+    0x84, 0xE7, 0xF8, 0xAC, 0x8B, 0x46, 0x19, 0x9C, 0x03, 0xE4, 0xF1, 0xBE, 0x83, 0x09, 0x97, 0xD5,
+    0xA8, 0x98, 0x1A, 0x47
+  };
+  std::vector<uint8_t> goodInterest = {
+    0x05, 0xA9, 0x07, 0xA1, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x08, 0x69, 0x6E, 0x74, 0x65,
+    0x72, 0x65, 0x73, 0x74, 0x08, 0x02, 0x45, 0x63, 0x08, 0x3F, 0x16, 0x3D, 0x1B, 0x01, 0x03, 0x1C,
+    0x38, 0x07, 0x36, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54,
+    0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48,
+    0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x02, 0x45, 0x43, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08,
+    0x08, 0xDC, 0x9E, 0x3B, 0x11, 0x2A, 0x81, 0x7E, 0x92, 0x08, 0x4A, 0x17, 0x48, 0x30, 0x46, 0x02,
+    0x21, 0x00, 0xE9, 0x35, 0xF0, 0x7B, 0x51, 0xB5, 0x2E, 0xB7, 0xE8, 0x6B, 0xE8, 0xAC, 0xC7, 0xC1,
+    0x90, 0x35, 0x64, 0x21, 0x53, 0x0F, 0x6D, 0x03, 0xB5, 0x1A, 0x58, 0xEA, 0xC4, 0x8A, 0xCA, 0xAF,
+    0xDB, 0x4B, 0x02, 0x21, 0x00, 0x95, 0xF3, 0xEB, 0xF9, 0xF9, 0x66, 0x51, 0x87, 0x97, 0xB0, 0xBF,
+    0xF8, 0x07, 0x5F, 0xF0, 0x70, 0x90, 0x36, 0xD8, 0x57, 0x65, 0xEA, 0xDB, 0x91, 0x79, 0x0E, 0x7E,
+    0x0E, 0xD0, 0x20, 0x96, 0x19, 0x0A, 0x04, 0xF7, 0x2C, 0x8A, 0x4B
+  };
+  std::vector<uint8_t> badSigInterest = {
+    0x05, 0xFD, 0x01, 0x3A, 0x07, 0xFD, 0x01, 0x30, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x08,
+    0x69, 0x6E, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x08, 0x02, 0x45, 0x63, 0x08, 0x3F, 0x16, 0x3D,
+    0x1B, 0x01, 0x03, 0x1C, 0x38, 0x07, 0x36, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
+    0x79, 0x08, 0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+    0x69, 0x6F, 0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x02, 0x45, 0x43, 0x08, 0x03,
+    0x4B, 0x45, 0x59, 0x08, 0x08, 0xDC, 0x9E, 0x3B, 0x11, 0x2A, 0x81, 0x7E, 0x92, 0x08, 0x4A, 0x17,
+    0x48, 0x30, 0x46, 0x02, 0x21, 0x00, 0xE9, 0x35, 0xF0, 0x7B, 0x51, 0xB5, 0x2E, 0xB7, 0xE8, 0x6B,
+    0xE8, 0xAC, 0xC7, 0xC1, 0x90, 0x35, 0x64, 0x21, 0x53, 0x0F, 0x6D, 0x03, 0xB5, 0x1A, 0x58, 0xEA,
+    0xC4, 0x8A, 0xCA, 0xAF, 0xDB, 0x4B, 0x02, 0x21, 0x00, 0x95, 0xF3, 0xEB, 0xF9, 0xF9, 0x66, 0x51,
+    0x87, 0x97, 0xB0, 0xBF, 0xF8, 0x07, 0x5F, 0xF0, 0x70, 0x90, 0x36, 0xD8, 0x57, 0x65, 0xEA, 0xDB,
+    0x91, 0x79, 0x0E, 0x7E, 0x0E, 0xD0, 0x20, 0x96, 0x19, 0x08, 0x42, 0x16, 0x40, 0x1B, 0x01, 0x03,
+    0x1C, 0x3B, 0x07, 0x39, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17,
+    0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E,
+    0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x05, 0x57, 0x72, 0x6F, 0x6E, 0x67, 0x08, 0x03,
+    0x4B, 0x45, 0x59, 0x08, 0x08, 0x57, 0x76, 0xDC, 0x93, 0x8D, 0x34, 0x59, 0x9C, 0x08, 0x49, 0x17,
+    0x47, 0x30, 0x45, 0x02, 0x20, 0x30, 0x34, 0xB8, 0x9C, 0x33, 0x4C, 0x54, 0x56, 0xF0, 0x34, 0x7A,
+    0x95, 0x72, 0x20, 0xEC, 0xF5, 0x0F, 0xBB, 0x90, 0x3D, 0xC4, 0x21, 0x90, 0xB8, 0x3D, 0xA1, 0x10,
+    0x5A, 0x56, 0x71, 0xC3, 0x3F, 0x02, 0x21, 0x00, 0xAE, 0xE0, 0x60, 0xCF, 0x2C, 0xF7, 0x54, 0xC2,
+    0x1C, 0xC5, 0x54, 0x88, 0xDA, 0x31, 0xD2, 0xDE, 0x53, 0x56, 0xEC, 0xE2, 0xC6, 0x4F, 0xDD, 0xF2,
+    0x78, 0x5D, 0xB3, 0xD4, 0x8E, 0x45, 0x36, 0x23, 0x0A, 0x04, 0xF7, 0x2C, 0x8A, 0x4B
+  };
+};
+
+struct RsaDataset
+{
+  const std::string name = "Rsa";
+  std::vector<uint8_t> cert = {
+    0x06, 0xFD, 0x02, 0xED, 0x07, 0x48, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79,
+    0x08, 0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+    0x6F, 0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x03, 0x52, 0x53, 0x41, 0x08, 0x03,
+    0x4B, 0x45, 0x59, 0x08, 0x08, 0xE1, 0x7A, 0x42, 0xD0, 0x60, 0x6F, 0x0F, 0xDF, 0x08, 0x04, 0x73,
+    0x65, 0x6C, 0x66, 0x08, 0x09, 0xFD, 0x00, 0x00, 0x01, 0x59, 0x90, 0x5F, 0x6F, 0x9F, 0x14, 0x09,
+    0x18, 0x01, 0x02, 0x19, 0x04, 0x00, 0x36, 0xEE, 0x80, 0x15, 0xFD, 0x01, 0x26, 0x30, 0x82, 0x01,
+    0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00,
+    0x03, 0x82, 0x01, 0x0F, 0x00, 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xE7, 0x7B,
+    0x1F, 0x75, 0x3C, 0xCD, 0xB4, 0x62, 0x80, 0xC3, 0xA4, 0x5E, 0xA9, 0x8A, 0x7B, 0xC6, 0x4C, 0x43,
+    0x27, 0x0D, 0xA4, 0x54, 0xEB, 0x67, 0x4F, 0xB4, 0x6E, 0xEF, 0x6D, 0x54, 0x9B, 0x85, 0x44, 0xE9,
+    0x6B, 0x4D, 0x09, 0x31, 0xC0, 0xDC, 0xC5, 0x06, 0x5E, 0x88, 0x6B, 0xFC, 0x0B, 0x08, 0xDE, 0x14,
+    0x0F, 0xAB, 0xE8, 0xA7, 0xED, 0x93, 0x5D, 0x17, 0x19, 0x4B, 0x2D, 0x7D, 0x29, 0xE7, 0x43, 0x42,
+    0x19, 0xE0, 0x3C, 0xBA, 0x8D, 0xAB, 0xE9, 0x4A, 0xBF, 0x21, 0x1E, 0x13, 0xD5, 0x0D, 0x9A, 0xC5,
+    0xD8, 0x67, 0x4A, 0x7F, 0x2D, 0xA6, 0xA9, 0xCE, 0x31, 0x82, 0x62, 0x6B, 0x89, 0xB1, 0x78, 0xE0,
+    0x6E, 0x19, 0x8B, 0xE6, 0x5C, 0x1A, 0x10, 0x8B, 0xC2, 0x9D, 0xF4, 0xB6, 0x66, 0xE9, 0x73, 0xD0,
+    0x93, 0xE9, 0x0A, 0xA8, 0xDA, 0x68, 0xC1, 0x23, 0xBC, 0xBE, 0x17, 0xA0, 0x8E, 0x88, 0xC6, 0x71,
+    0xFF, 0x25, 0x83, 0x75, 0x2C, 0x0E, 0x49, 0x76, 0x27, 0xA0, 0x9E, 0x08, 0x55, 0xA2, 0xE1, 0x60,
+    0xAC, 0x5E, 0x03, 0xC3, 0x9E, 0xF3, 0x2B, 0x56, 0x80, 0xE0, 0x30, 0xD8, 0x0A, 0x5A, 0xAB, 0x92,
+    0xA4, 0x32, 0xF2, 0xEC, 0xE8, 0xFB, 0x6D, 0xE2, 0xE6, 0x2F, 0x87, 0x94, 0xEA, 0xA3, 0x39, 0x47,
+    0x70, 0x71, 0x08, 0xBC, 0x11, 0x0B, 0xC5, 0x9A, 0xCF, 0x13, 0xCA, 0x68, 0x7C, 0x22, 0xF8, 0x33,
+    0xCA, 0x5F, 0xEB, 0x98, 0xF3, 0x29, 0x12, 0xF0, 0x33, 0xDE, 0x30, 0xC4, 0x56, 0xB5, 0x3B, 0x0B,
+    0x0C, 0x23, 0xF2, 0x0F, 0x93, 0x3D, 0x60, 0xC9, 0xD2, 0xAA, 0x5A, 0x94, 0x3E, 0xAB, 0xFB, 0xD5,
+    0x9B, 0xF0, 0xC9, 0x79, 0x11, 0xAD, 0x78, 0x4E, 0xE9, 0x91, 0x1E, 0x62, 0x8D, 0x81, 0x71, 0xC9,
+    0xE4, 0x5D, 0x41, 0xDD, 0x19, 0xC6, 0x77, 0x81, 0xF6, 0xA7, 0x38, 0xD4, 0xE1, 0xB3, 0x02, 0x03,
+    0x01, 0x00, 0x01, 0x16, 0x68, 0x1B, 0x01, 0x01, 0x1C, 0x39, 0x07, 0x37, 0x08, 0x08, 0x53, 0x65,
+    0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69,
+    0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08,
+    0x03, 0x52, 0x53, 0x41, 0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x08, 0xE1, 0x7A, 0x42, 0xD0, 0x60,
+    0x6F, 0x0F, 0xDF, 0xFD, 0x00, 0xFD, 0x26, 0xFD, 0x00, 0xFE, 0x0F, 0x31, 0x39, 0x37, 0x30, 0x30,
+    0x31, 0x30, 0x31, 0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFD, 0x00, 0xFF, 0x0F, 0x32, 0x30,
+    0x33, 0x37, 0x30, 0x31, 0x30, 0x37, 0x54, 0x30, 0x31, 0x35, 0x31, 0x33, 0x30, 0x17, 0xFD, 0x01,
+    0x00, 0xD1, 0xEB, 0xB4, 0x09, 0x2B, 0x9D, 0xFA, 0x82, 0x88, 0x1A, 0xB6, 0x71, 0x99, 0xD0, 0x14,
+    0x2B, 0xB5, 0xD8, 0xB8, 0x36, 0xA5, 0x70, 0xC0, 0xDD, 0x7E, 0xD1, 0xAD, 0x93, 0xEC, 0xCE, 0x9F,
+    0x3E, 0x75, 0x1F, 0x7F, 0x95, 0x0A, 0x34, 0xCC, 0x8C, 0x5A, 0x45, 0x37, 0xE2, 0x65, 0xE4, 0x1D,
+    0xEC, 0xE2, 0xC2, 0x39, 0x7F, 0xD7, 0xB7, 0x9B, 0x78, 0xDF, 0x0B, 0x39, 0x2B, 0xC6, 0x5E, 0x49,
+    0x6F, 0xDB, 0xB0, 0x44, 0xFD, 0xC7, 0x5B, 0x00, 0xB4, 0xDD, 0x23, 0xBC, 0xA8, 0xC8, 0x9C, 0xCB,
+    0xAD, 0x63, 0x3C, 0x40, 0x57, 0x07, 0x09, 0xC7, 0x26, 0x84, 0xBF, 0x49, 0x8B, 0xD0, 0x5A, 0xFD,
+    0xBA, 0xA0, 0x0C, 0x06, 0xE5, 0x84, 0xA3, 0x9B, 0x36, 0x5E, 0x95, 0xBF, 0x34, 0xF5, 0x5A, 0x70,
+    0x31, 0x3B, 0x70, 0x3E, 0x99, 0x84, 0xBA, 0x03, 0xE7, 0x5A, 0x9D, 0x6F, 0x46, 0x33, 0xA3, 0x95,
+    0xAD, 0xB5, 0xC9, 0x20, 0x28, 0xA8, 0x6E, 0x52, 0x02, 0x97, 0x49, 0xDA, 0x89, 0x55, 0x6B, 0x5D,
+    0xCB, 0x84, 0x75, 0x5F, 0x1F, 0x51, 0x0C, 0x59, 0x49, 0xC8, 0xCE, 0x2D, 0xC3, 0xA2, 0x2F, 0x7F,
+    0x42, 0x71, 0x26, 0x4F, 0xF4, 0x1F, 0xBB, 0x09, 0x7D, 0xF8, 0x7E, 0x6D, 0x44, 0x74, 0xB1, 0x7F,
+    0x76, 0xAE, 0x7B, 0x1C, 0x56, 0x75, 0x9B, 0xE7, 0x23, 0xF4, 0xF3, 0xA0, 0x3C, 0x47, 0xF0, 0x98,
+    0x2B, 0xC1, 0x54, 0xDA, 0x75, 0x1B, 0x2E, 0x94, 0xFB, 0xB5, 0xDD, 0x44, 0xE9, 0x16, 0x27, 0xE5,
+    0xE9, 0xB5, 0xA5, 0x70, 0x5E, 0xBC, 0x48, 0xB1, 0x02, 0x92, 0x64, 0x73, 0x08, 0x8A, 0x5C, 0xD8,
+    0xF7, 0x58, 0x07, 0x66, 0xEF, 0x4A, 0x9E, 0xB5, 0x77, 0xDF, 0xDE, 0x54, 0xF1, 0x0A, 0xDF, 0xE1,
+    0xE1, 0xF1, 0x76, 0x91, 0xEB, 0x78, 0xB6, 0x9A, 0x40, 0x57, 0x97, 0x9C, 0x2C, 0x8C, 0x00, 0x05,
+    0x01
+  };
+  std::vector<uint8_t> goodData = {
+    0x06, 0xFD, 0x01, 0x5B, 0x07, 0x11, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x04, 0x64, 0x61,
+    0x74, 0x61, 0x08, 0x03, 0x52, 0x73, 0x61, 0x14, 0x00, 0x15, 0x00, 0x16, 0x3E, 0x1B, 0x01, 0x01,
+    0x1C, 0x39, 0x07, 0x37, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17,
+    0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E,
+    0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x03, 0x52, 0x53, 0x41, 0x08, 0x03, 0x4B, 0x45,
+    0x59, 0x08, 0x08, 0xE1, 0x7A, 0x42, 0xD0, 0x60, 0x6F, 0x0F, 0xDF, 0x17, 0xFD, 0x01, 0x00, 0x88,
+    0xEA, 0x30, 0x2E, 0xF2, 0x40, 0xA4, 0x2F, 0xF6, 0x95, 0x83, 0x1B, 0xD7, 0x89, 0xB2, 0xDB, 0x73,
+    0xB8, 0x88, 0x97, 0x48, 0xDC, 0xC4, 0x8F, 0xBD, 0x83, 0x49, 0x5E, 0x39, 0x14, 0xE5, 0x15, 0xE2,
+    0x94, 0xB3, 0x7A, 0x4A, 0x46, 0x1F, 0xEE, 0x50, 0xF5, 0x1F, 0x46, 0xE9, 0xAB, 0x31, 0x94, 0x9C,
+    0x41, 0xF1, 0xA7, 0x1E, 0xA7, 0x2F, 0x2C, 0x26, 0x5E, 0x97, 0x7D, 0x06, 0x86, 0x77, 0x63, 0x4B,
+    0xD0, 0x38, 0x91, 0x0F, 0xB6, 0x2B, 0xFD, 0xF0, 0x77, 0xB1, 0xA8, 0x73, 0xBB, 0xF9, 0x8A, 0xED,
+    0x8D, 0xDD, 0x32, 0x8E, 0x8A, 0xC2, 0xEE, 0x83, 0xA6, 0x72, 0xFF, 0xDB, 0x91, 0xA8, 0x83, 0x1D,
+    0xDC, 0x37, 0x1F, 0x58, 0xF6, 0x16, 0x5D, 0x89, 0x50, 0xDD, 0x1D, 0x42, 0x96, 0x81, 0x75, 0x94,
+    0xA7, 0x8D, 0x2E, 0x8C, 0xB2, 0x75, 0x36, 0x99, 0x7A, 0x65, 0x34, 0x07, 0xC8, 0x28, 0x98, 0xA2,
+    0x46, 0x9D, 0x6F, 0x30, 0x8E, 0x32, 0x49, 0x20, 0xE6, 0xFC, 0x1A, 0x05, 0x4F, 0x6F, 0xE8, 0x5D,
+    0x34, 0x1D, 0x8D, 0x7F, 0x09, 0xA8, 0xDD, 0xA7, 0x48, 0xB1, 0x14, 0xDC, 0x5A, 0xAF, 0xAA, 0x12,
+    0xEA, 0x57, 0xDE, 0x47, 0x6C, 0xC6, 0x07, 0xBC, 0x6B, 0x76, 0xB1, 0x40, 0x56, 0x3B, 0x12, 0x47,
+    0x1D, 0x89, 0x66, 0x12, 0x81, 0xE8, 0x8B, 0xBA, 0x70, 0x8A, 0x3E, 0x78, 0x64, 0x00, 0x3E, 0x56,
+    0x3B, 0xCF, 0x26, 0xF0, 0x3D, 0x09, 0x1D, 0xB9, 0x22, 0x9E, 0xED, 0x67, 0xB8, 0x86, 0x1F, 0x44,
+    0x92, 0x0A, 0x08, 0xE1, 0x8F, 0x4C, 0x6D, 0x9C, 0x11, 0x5F, 0x26, 0xEA, 0x3E, 0x95, 0x32, 0x13,
+    0x20, 0x51, 0x95, 0x24, 0x37, 0x18, 0x13, 0x92, 0xBD, 0x22, 0xF0, 0x0E, 0xAF, 0x34, 0x37, 0x9B,
+    0xDC, 0x61, 0x03, 0x29, 0x09, 0x9A, 0x45, 0x92, 0x1B, 0xA0, 0x29, 0x9E, 0xD8, 0x98, 0x54
+  };
+  std::vector<uint8_t> badSigData = {
+    0x06, 0xA1, 0x07, 0x11, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x04, 0x64, 0x61, 0x74, 0x61,
+    0x08, 0x03, 0x52, 0x73, 0x61, 0x14, 0x00, 0x15, 0x00, 0x16, 0x40, 0x1B, 0x01, 0x03, 0x1C, 0x3B,
+    0x07, 0x39, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54, 0x65,
+    0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65,
+    0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x05, 0x57, 0x72, 0x6F, 0x6E, 0x67, 0x08, 0x03, 0x4B, 0x45,
+    0x59, 0x08, 0x08, 0x57, 0x76, 0xDC, 0x93, 0x8D, 0x34, 0x59, 0x9C, 0x17, 0x46, 0x30, 0x44, 0x02,
+    0x20, 0x5E, 0xF9, 0x02, 0xF4, 0x78, 0xE7, 0x5E, 0x1B, 0xB9, 0x3B, 0x23, 0xDE, 0x9D, 0xB7, 0x87,
+    0xC6, 0x30, 0x7F, 0x4A, 0xE2, 0xBE, 0x11, 0xFE, 0x29, 0xC7, 0x6F, 0x70, 0x97, 0xAF, 0x45, 0xE1,
+    0x0B, 0x02, 0x20, 0x67, 0x45, 0x47, 0x52, 0xBE, 0x13, 0x59, 0x76, 0x16, 0x28, 0x70, 0xF6, 0x50,
+    0x13, 0xB2, 0xC0, 0xFA, 0x8F, 0xF3, 0x05, 0xFF, 0xBC, 0x92, 0xAC, 0xF7, 0xD0, 0x12, 0x3A, 0x6E,
+    0x31, 0x76, 0x02
+  };
+  std::vector<uint8_t> goodInterest = {
+    0x05, 0xFD, 0x01, 0x69, 0x07, 0xFD, 0x01, 0x5F, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x08,
+    0x69, 0x6E, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x08, 0x03, 0x52, 0x73, 0x61, 0x08, 0x40, 0x16,
+    0x3E, 0x1B, 0x01, 0x01, 0x1C, 0x39, 0x07, 0x37, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69,
+    0x74, 0x79, 0x08, 0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
+    0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x03, 0x52, 0x53, 0x41,
+    0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x08, 0xE1, 0x7A, 0x42, 0xD0, 0x60, 0x6F, 0x0F, 0xDF, 0x08,
+    0xFD, 0x01, 0x04, 0x17, 0xFD, 0x01, 0x00, 0x9C, 0x44, 0x23, 0xF4, 0x3A, 0x9F, 0xDD, 0x62, 0xD2,
+    0x47, 0xE4, 0x73, 0x9E, 0x58, 0x54, 0xF5, 0xE7, 0x72, 0x8D, 0x2E, 0x1B, 0x26, 0xE7, 0xA2, 0xA1,
+    0x56, 0x23, 0xBD, 0xB8, 0x75, 0x17, 0x78, 0x01, 0x67, 0xF9, 0x4D, 0xA9, 0xCC, 0xB8, 0x00, 0xF6,
+    0xCD, 0xC4, 0xB9, 0xF6, 0x50, 0xC7, 0xAB, 0x57, 0xD8, 0xA7, 0x8B, 0xA8, 0x5C, 0xED, 0xD0, 0xCC,
+    0x29, 0xC3, 0x5D, 0x80, 0x2B, 0xFA, 0xAF, 0x0D, 0xCB, 0x29, 0x1E, 0x74, 0xA8, 0x41, 0x80, 0xDE,
+    0x52, 0x94, 0xDD, 0xE8, 0xAA, 0xA9, 0x61, 0x83, 0xC1, 0x5F, 0xA3, 0x11, 0x48, 0x0B, 0xB6, 0x53,
+    0xB8, 0xE3, 0x77, 0x6A, 0xED, 0xF0, 0xFA, 0xED, 0x79, 0x43, 0x10, 0x10, 0x79, 0x98, 0x5D, 0xFD,
+    0x66, 0xBF, 0x2F, 0x14, 0x9F, 0x7D, 0xA4, 0x3C, 0xBA, 0x67, 0x5F, 0xDB, 0xE3, 0x67, 0x13, 0x96,
+    0x60, 0xC6, 0x69, 0x78, 0x5A, 0x8D, 0x52, 0xB7, 0xB7, 0x6B, 0x7F, 0xEE, 0xF4, 0x22, 0x3A, 0x64,
+    0xE4, 0xB4, 0xA1, 0x8B, 0xDD, 0x3F, 0x80, 0xCD, 0xF4, 0x9E, 0x92, 0x06, 0x98, 0x23, 0x47, 0x58,
+    0x70, 0xF0, 0xAC, 0x79, 0x76, 0x91, 0x7A, 0x78, 0xDF, 0xAD, 0xDD, 0x81, 0x30, 0x01, 0x5D, 0xCE,
+    0x37, 0xEC, 0x7E, 0xDA, 0xDA, 0x36, 0x75, 0x50, 0x52, 0x57, 0x95, 0xBF, 0xCF, 0x3A, 0xC4, 0x9F,
+    0x52, 0x97, 0x17, 0x15, 0x99, 0xA5, 0x2F, 0x68, 0x35, 0x91, 0x70, 0xDE, 0x98, 0x8A, 0xB0, 0x5F,
+    0xF4, 0x63, 0x14, 0xB9, 0xCC, 0x76, 0x81, 0x87, 0xAE, 0x10, 0x8E, 0x9F, 0xEC, 0xCB, 0xF2, 0x33,
+    0x1D, 0x50, 0xD4, 0xAB, 0x5B, 0xBB, 0xB9, 0x7F, 0x8C, 0xAD, 0xEC, 0xE3, 0xF8, 0xE1, 0x63, 0xDA,
+    0x4E, 0x0D, 0x17, 0x28, 0xCD, 0x8D, 0x16, 0x00, 0x22, 0x4A, 0x51, 0x5C, 0xB2, 0x8C, 0xE7, 0x4B,
+    0x3B, 0x00, 0x16, 0x92, 0xAD, 0x3A, 0xAB, 0x0A, 0x04, 0x10, 0x04, 0xFB, 0x38
+  };
+  std::vector<uint8_t> badSigInterest = {
+    0x05, 0xFD, 0x01, 0xF7, 0x07, 0xFD, 0x01, 0xED, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x08,
+    0x69, 0x6E, 0x74, 0x65, 0x72, 0x65, 0x73, 0x74, 0x08, 0x03, 0x52, 0x73, 0x61, 0x08, 0x40, 0x16,
+    0x3E, 0x1B, 0x01, 0x01, 0x1C, 0x39, 0x07, 0x37, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69,
+    0x74, 0x79, 0x08, 0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
+    0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x03, 0x52, 0x53, 0x41,
+    0x08, 0x03, 0x4B, 0x45, 0x59, 0x08, 0x08, 0xE1, 0x7A, 0x42, 0xD0, 0x60, 0x6F, 0x0F, 0xDF, 0x08,
+    0xFD, 0x01, 0x04, 0x17, 0xFD, 0x01, 0x00, 0x9C, 0x44, 0x23, 0xF4, 0x3A, 0x9F, 0xDD, 0x62, 0xD2,
+    0x47, 0xE4, 0x73, 0x9E, 0x58, 0x54, 0xF5, 0xE7, 0x72, 0x8D, 0x2E, 0x1B, 0x26, 0xE7, 0xA2, 0xA1,
+    0x56, 0x23, 0xBD, 0xB8, 0x75, 0x17, 0x78, 0x01, 0x67, 0xF9, 0x4D, 0xA9, 0xCC, 0xB8, 0x00, 0xF6,
+    0xCD, 0xC4, 0xB9, 0xF6, 0x50, 0xC7, 0xAB, 0x57, 0xD8, 0xA7, 0x8B, 0xA8, 0x5C, 0xED, 0xD0, 0xCC,
+    0x29, 0xC3, 0x5D, 0x80, 0x2B, 0xFA, 0xAF, 0x0D, 0xCB, 0x29, 0x1E, 0x74, 0xA8, 0x41, 0x80, 0xDE,
+    0x52, 0x94, 0xDD, 0xE8, 0xAA, 0xA9, 0x61, 0x83, 0xC1, 0x5F, 0xA3, 0x11, 0x48, 0x0B, 0xB6, 0x53,
+    0xB8, 0xE3, 0x77, 0x6A, 0xED, 0xF0, 0xFA, 0xED, 0x79, 0x43, 0x10, 0x10, 0x79, 0x98, 0x5D, 0xFD,
+    0x66, 0xBF, 0x2F, 0x14, 0x9F, 0x7D, 0xA4, 0x3C, 0xBA, 0x67, 0x5F, 0xDB, 0xE3, 0x67, 0x13, 0x96,
+    0x60, 0xC6, 0x69, 0x78, 0x5A, 0x8D, 0x52, 0xB7, 0xB7, 0x6B, 0x7F, 0xEE, 0xF4, 0x22, 0x3A, 0x64,
+    0xE4, 0xB4, 0xA1, 0x8B, 0xDD, 0x3F, 0x80, 0xCD, 0xF4, 0x9E, 0x92, 0x06, 0x98, 0x23, 0x47, 0x58,
+    0x70, 0xF0, 0xAC, 0x79, 0x76, 0x91, 0x7A, 0x78, 0xDF, 0xAD, 0xDD, 0x81, 0x30, 0x01, 0x5D, 0xCE,
+    0x37, 0xEC, 0x7E, 0xDA, 0xDA, 0x36, 0x75, 0x50, 0x52, 0x57, 0x95, 0xBF, 0xCF, 0x3A, 0xC4, 0x9F,
+    0x52, 0x97, 0x17, 0x15, 0x99, 0xA5, 0x2F, 0x68, 0x35, 0x91, 0x70, 0xDE, 0x98, 0x8A, 0xB0, 0x5F,
+    0xF4, 0x63, 0x14, 0xB9, 0xCC, 0x76, 0x81, 0x87, 0xAE, 0x10, 0x8E, 0x9F, 0xEC, 0xCB, 0xF2, 0x33,
+    0x1D, 0x50, 0xD4, 0xAB, 0x5B, 0xBB, 0xB9, 0x7F, 0x8C, 0xAD, 0xEC, 0xE3, 0xF8, 0xE1, 0x63, 0xDA,
+    0x4E, 0x0D, 0x17, 0x28, 0xCD, 0x8D, 0x16, 0x00, 0x22, 0x4A, 0x51, 0x5C, 0xB2, 0x8C, 0xE7, 0x4B,
+    0x3B, 0x00, 0x16, 0x92, 0xAD, 0x3A, 0xAB, 0x08, 0x42, 0x16, 0x40, 0x1B, 0x01, 0x03, 0x1C, 0x3B,
+    0x07, 0x39, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54, 0x65,
+    0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65,
+    0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x05, 0x57, 0x72, 0x6F, 0x6E, 0x67, 0x08, 0x03, 0x4B, 0x45,
+    0x59, 0x08, 0x08, 0x57, 0x76, 0xDC, 0x93, 0x8D, 0x34, 0x59, 0x9C, 0x08, 0x48, 0x17, 0x46, 0x30,
+    0x44, 0x02, 0x20, 0x48, 0x87, 0xFE, 0x7D, 0x43, 0x35, 0x3C, 0x55, 0xCB, 0x4A, 0x4B, 0x83, 0x50,
+    0xC2, 0x10, 0xAD, 0x01, 0x4A, 0x99, 0xED, 0x6C, 0x29, 0x38, 0xEF, 0xE4, 0x9E, 0x10, 0x23, 0x4D,
+    0x57, 0xC3, 0xE4, 0x02, 0x20, 0x7B, 0x35, 0xDC, 0xF2, 0x98, 0xD8, 0xFE, 0x13, 0x4D, 0x3B, 0x5D,
+    0xE7, 0xCE, 0xFF, 0x02, 0xBD, 0x6B, 0x50, 0x30, 0x8B, 0x93, 0x91, 0x7A, 0xC9, 0xE0, 0x95, 0x21,
+    0x5F, 0x91, 0xB6, 0xEE, 0x4E, 0x0A, 0x04, 0x10, 0x04, 0xFB, 0x38
+  };
+};
+
+struct Sha256Dataset
+{
+  const std::string name = "Sha256";
+  std::vector<uint8_t> cert = {
+
+  };
+  std::vector<uint8_t> goodData = {
+    0x06, 0x41, 0x07, 0x14, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x04, 0x64, 0x61, 0x74, 0x61,
+    0x08, 0x06, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x14, 0x00, 0x15, 0x00, 0x16, 0x03, 0x1B, 0x01,
+    0x00, 0x17, 0x20, 0xE2, 0xE2, 0x2F, 0x02, 0x70, 0xA7, 0xF7, 0x48, 0x70, 0x45, 0x29, 0x46, 0xBD,
+    0xD2, 0x62, 0x24, 0xA6, 0x1E, 0x1D, 0x75, 0x2A, 0x26, 0x98, 0x04, 0xAD, 0x9C, 0x47, 0x63, 0xF8,
+    0x98, 0x5A, 0x49
+  };
+  std::vector<uint8_t> badSigData = {
+    0x06, 0xA5, 0x07, 0x14, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x04, 0x64, 0x61, 0x74, 0x61,
+    0x08, 0x06, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x14, 0x00, 0x15, 0x00, 0x16, 0x40, 0x1B, 0x01,
+    0x03, 0x1C, 0x3B, 0x07, 0x39, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08,
+    0x17, 0x54, 0x65, 0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F,
+    0x6E, 0x48, 0x65, 0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x05, 0x57, 0x72, 0x6F, 0x6E, 0x67, 0x08,
+    0x03, 0x4B, 0x45, 0x59, 0x08, 0x08, 0x57, 0x76, 0xDC, 0x93, 0x8D, 0x34, 0x59, 0x9C, 0x17, 0x47,
+    0x30, 0x45, 0x02, 0x20, 0x5F, 0x5E, 0x1E, 0xC0, 0xB5, 0xAC, 0x13, 0xF9, 0x51, 0x2F, 0x22, 0x33,
+    0xFB, 0xDE, 0x57, 0xF6, 0xC8, 0xBF, 0xAE, 0x55, 0x3A, 0xDC, 0x30, 0x8A, 0x12, 0x61, 0xB3, 0x5D,
+    0xB9, 0x31, 0x95, 0xD3, 0x02, 0x21, 0x00, 0xFA, 0xEC, 0x54, 0xEB, 0x35, 0x4D, 0xBF, 0x87, 0x4C,
+    0xD8, 0x20, 0x3A, 0xE5, 0x05, 0x2C, 0xA1, 0x70, 0x74, 0x2E, 0xF9, 0x1E, 0xE1, 0xEF, 0xB9, 0x47,
+    0xC4, 0x53, 0x57, 0xED, 0xB5, 0xB7, 0x60
+  };
+  std::vector<uint8_t> goodInterest = {
+    0x05, 0x4B, 0x07, 0x43, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x08, 0x69, 0x6E, 0x74, 0x65,
+    0x72, 0x65, 0x73, 0x74, 0x08, 0x06, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x08, 0x05, 0x16, 0x03,
+    0x1B, 0x01, 0x00, 0x08, 0x22, 0x17, 0x20, 0x38, 0x65, 0xB5, 0x37, 0x8A, 0xF4, 0xEB, 0xB9, 0xCD,
+    0xEF, 0x18, 0x49, 0xF5, 0x79, 0x85, 0xE5, 0x76, 0x6F, 0x3C, 0x72, 0x63, 0x0E, 0x4F, 0x5D, 0xC7,
+    0x42, 0x6B, 0xDF, 0xB0, 0xE1, 0x75, 0x2C, 0x0A, 0x04, 0xEE, 0x0A, 0x69, 0x16
+  };
+  std::vector<uint8_t> badSigInterest = {
+    0x05, 0xD9, 0x07, 0xD1, 0x08, 0x04, 0x74, 0x65, 0x73, 0x74, 0x08, 0x08, 0x69, 0x6E, 0x74, 0x65,
+    0x72, 0x65, 0x73, 0x74, 0x08, 0x06, 0x53, 0x68, 0x61, 0x32, 0x35, 0x36, 0x08, 0x05, 0x16, 0x03,
+    0x1B, 0x01, 0x00, 0x08, 0x22, 0x17, 0x20, 0x38, 0x65, 0xB5, 0x37, 0x8A, 0xF4, 0xEB, 0xB9, 0xCD,
+    0xEF, 0x18, 0x49, 0xF5, 0x79, 0x85, 0xE5, 0x76, 0x6F, 0x3C, 0x72, 0x63, 0x0E, 0x4F, 0x5D, 0xC7,
+    0x42, 0x6B, 0xDF, 0xB0, 0xE1, 0x75, 0x2C, 0x08, 0x42, 0x16, 0x40, 0x1B, 0x01, 0x03, 0x1C, 0x3B,
+    0x07, 0x39, 0x08, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x08, 0x17, 0x54, 0x65,
+    0x73, 0x74, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x48, 0x65,
+    0x6C, 0x70, 0x65, 0x72, 0x73, 0x08, 0x05, 0x57, 0x72, 0x6F, 0x6E, 0x67, 0x08, 0x03, 0x4B, 0x45,
+    0x59, 0x08, 0x08, 0x57, 0x76, 0xDC, 0x93, 0x8D, 0x34, 0x59, 0x9C, 0x08, 0x48, 0x17, 0x46, 0x30,
+    0x44, 0x02, 0x20, 0x73, 0xB7, 0x0E, 0x17, 0x6C, 0x98, 0xB5, 0x6B, 0x25, 0x99, 0x2C, 0x6E, 0x41,
+    0x26, 0xE6, 0x08, 0xCF, 0x81, 0xB9, 0x51, 0x53, 0x6A, 0x6B, 0x21, 0xF3, 0x2D, 0x4D, 0x62, 0x53,
+    0x86, 0x85, 0xEE, 0x02, 0x20, 0x7D, 0x9D, 0xFF, 0xE3, 0x18, 0xF7, 0xBD, 0x7F, 0x9B, 0xC6, 0x4D,
+    0x76, 0x09, 0x58, 0x74, 0x69, 0x67, 0x9B, 0x51, 0xBC, 0x14, 0xF0, 0x1C, 0x46, 0xA7, 0xA3, 0xA7,
+    0xCC, 0x9A, 0xBB, 0x33, 0x07, 0x0A, 0x04, 0xEE, 0x0A, 0x69, 0x16
+  };
+};
+
+// Note about the datasets:
+// - .cert a valid certificate
+// - .goodData is a data packet that can be verified by .cert
+// - .badSigData a valid and signed data packet that cannot be verified by cert (signed by a
+//   different private key)
+// - .goodInterest is an interest packet that can be verified by .cert
+// - .badSigInterest a valid and signed interest packet that cannot be verified by cert
+//   (signed by a different private key)
+
+typedef boost::mpl::list<EcdsaDataset, RsaDataset> SignatureDatasets;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(VerifySignature, Dataset, SignatureDatasets)
+{
+  Dataset dataset;
+  v2::Certificate cert(Block(dataset.cert.data(), dataset.cert.size()));
+  Buffer keyRaw = cert.getPublicKey();
+  v2::PublicKey key;
+  key.loadPkcs8(keyRaw.data(), keyRaw.size());
+  Data data(Block(dataset.goodData.data(), dataset.goodData.size()));
+  Data badSigData(Block(dataset.badSigData.data(), dataset.badSigData.size()));
+  Interest interest(Block(dataset.goodInterest.data(), dataset.goodInterest.size()));
+  Interest badSigInterest(Block(dataset.badSigInterest.data(), dataset.badSigInterest.size()));
+
+  BOOST_CHECK(verifySignature(data, key));
+  BOOST_CHECK(verifySignature(data, keyRaw.data(), keyRaw.size()));
+  BOOST_CHECK(verifySignature(data, cert));
+  BOOST_CHECK(verifySignature(interest, key));
+  BOOST_CHECK(verifySignature(interest, keyRaw.data(), keyRaw.size()));
+  BOOST_CHECK(verifySignature(interest, cert));
+
+  BOOST_CHECK(!verifySignature(badSigData, key));
+  BOOST_CHECK(!verifySignature(badSigData, keyRaw.data(), keyRaw.size()));
+  BOOST_CHECK(!verifySignature(badSigData, cert));
+  BOOST_CHECK(!verifySignature(badSigInterest, key));
+  BOOST_CHECK(!verifySignature(badSigInterest, keyRaw.data(), keyRaw.size()));
+  BOOST_CHECK(!verifySignature(badSigInterest, cert));
+
+  Data unsignedData("/some/data");
+  Interest unsignedInterest1("/some/interest/with/several/name/components");
+  Interest unsignedInterest2("/interest-with-one-name-component");
+
+  BOOST_CHECK(!verifySignature(unsignedData, cert));
+  BOOST_CHECK(!verifySignature(unsignedData, key));
+  BOOST_CHECK(!verifySignature(unsignedInterest1, cert));
+  BOOST_CHECK(!verifySignature(unsignedInterest1, key));
+  BOOST_CHECK(!verifySignature(unsignedInterest2, cert));
+  BOOST_CHECK(!verifySignature(unsignedInterest2, key));
+
+  uint8_t invalidKey[] = {0x00, 0x00};
+  BOOST_CHECK(!verifySignature(unsignedData, invalidKey, sizeof(invalidKey)));
+  BOOST_CHECK(!verifySignature(unsignedInterest1, invalidKey, sizeof(invalidKey)));
+
+  // - base version of verifySignature is tested transitively
+  // - pib::Key version is tested as part of v2/key-chain.t.cpp (Security/V2/TestKeyChain)
+}
+
+typedef boost::mpl::list<Sha256Dataset> DigestDatasets;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(VerifyDigest, Dataset, DigestDatasets)
+{
+  Dataset dataset;
+  Data data(Block(dataset.goodData.data(), dataset.goodData.size()));
+  Data badSigData(Block(dataset.badSigData.data(), dataset.badSigData.size()));
+  Interest interest(Block(dataset.goodInterest.data(), dataset.goodInterest.size()));
+  Interest badSigInterest(Block(dataset.badSigInterest.data(), dataset.badSigInterest.size()));
+
+  BOOST_CHECK(verifyDigest(data, DigestAlgorithm::SHA256));
+  BOOST_CHECK(verifyDigest(interest, DigestAlgorithm::SHA256));
+
+  BOOST_CHECK(!verifyDigest(badSigData, DigestAlgorithm::SHA256));
+  BOOST_CHECK(!verifyDigest(badSigInterest, DigestAlgorithm::SHA256));
+
+  Data unsignedData("/some/data");
+  Interest unsignedInterest1("/some/interest/with/several/name/components");
+  Interest unsignedInterest2("/interest-with-one-name-component");
+
+  BOOST_CHECK(!verifyDigest(unsignedData, DigestAlgorithm::SHA256));
+  BOOST_CHECK(!verifyDigest(unsignedInterest1, DigestAlgorithm::SHA256));
+  BOOST_CHECK(!verifyDigest(unsignedInterest2, DigestAlgorithm::SHA256));
+
+  // - base version of verifyDigest is tested transitively
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestVerificationHelpers
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit/selectors.t.cpp b/tests/unit/selectors.t.cpp
new file mode 100644
index 0000000..202fd71
--- /dev/null
+++ b/tests/unit/selectors.t.cpp
@@ -0,0 +1,199 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "selectors.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestSelectors)
+
+BOOST_AUTO_TEST_CASE(DefaultConstructor)
+{
+  Selectors s;
+  BOOST_CHECK(s.empty());
+  BOOST_CHECK_EQUAL(s.getMinSuffixComponents(), -1);
+  BOOST_CHECK_EQUAL(s.getMaxSuffixComponents(), -1);
+  BOOST_CHECK(s.getPublisherPublicKeyLocator().empty());
+  BOOST_CHECK(s.getExclude().empty());
+  BOOST_CHECK_EQUAL(s.getChildSelector(), 0);
+  BOOST_CHECK_EQUAL(s.getMustBeFresh(), false);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDecodeEmpty)
+{
+  const uint8_t WIRE[] = {
+    0x09, 0x00 // Selectors
+  };
+
+  Selectors s1;
+  Block wire1 = s1.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+  Selectors s2(wire1);
+  BOOST_CHECK(s2.empty());
+  BOOST_CHECK_EQUAL(s2.getMinSuffixComponents(), -1);
+  BOOST_CHECK_EQUAL(s2.getMaxSuffixComponents(), -1);
+  BOOST_CHECK(s2.getPublisherPublicKeyLocator().empty());
+  BOOST_CHECK(s2.getExclude().empty());
+  BOOST_CHECK_EQUAL(s2.getChildSelector(), 0);
+  BOOST_CHECK_EQUAL(s2.getMustBeFresh(), false);
+
+  BOOST_CHECK(s1 == s2);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeDecodeFull)
+{
+  const uint8_t WIRE[] = {
+    0x09, 0x39, // Selectors
+          0x0d, 0x01, 0x02, // MinSuffixComponents
+          0x0e, 0x01, 0x06,  // MaxSuffixComponent
+          0x1c, 0x16, // KeyLocator
+                0x07, 0x14, // Name
+                      0x08, 0x04, 0x74, 0x65, 0x73, 0x74,
+                      0x08, 0x03, 0x6b, 0x65, 0x79,
+                      0x08, 0x07, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+          0x10, 0x14, // Exclude
+                0x08, 0x04, 0x61, 0x6c, 0x65, 0x78, // GenericNameComponent
+                0x08, 0x04, 0x78, 0x78, 0x78, 0x78, // GenericNameComponent
+                0x13, 0x00, // Any
+                0x08, 0x04, 0x79, 0x79, 0x79, 0x79, // GenericNameComponent
+          0x11, 0x01, 0x01, // ChildSelector
+          0x12, 0x00 // MustBeFresh
+  };
+
+  Selectors s1;
+  s1.setMinSuffixComponents(2);
+  s1.setMaxSuffixComponents(6);
+  s1.setPublisherPublicKeyLocator(KeyLocator("/test/key/locator"));
+  s1.setExclude(Exclude().excludeOne(name::Component("alex"))
+                .excludeRange(name::Component("xxxx"), name::Component("yyyy")));
+  s1.setChildSelector(1);
+  s1.setMustBeFresh(true);
+
+  Block wire1 = s1.wireEncode();
+  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+  Selectors s2(wire1);
+  BOOST_CHECK(!s2.empty());
+  BOOST_CHECK_EQUAL(s2.getMinSuffixComponents(), 2);
+  BOOST_CHECK_EQUAL(s2.getMaxSuffixComponents(), 6);
+  BOOST_CHECK_EQUAL(s2.getPublisherPublicKeyLocator().getType(), KeyLocator::KeyLocator_Name);
+  BOOST_CHECK_EQUAL(s2.getPublisherPublicKeyLocator().getName(), "ndn:/test/key/locator");
+  BOOST_CHECK_EQUAL(s2.getExclude().toUri(), "alex,xxxx,*,yyyy");
+  BOOST_CHECK_EQUAL(s2.getChildSelector(), 1);
+  BOOST_CHECK_EQUAL(s2.getMustBeFresh(), true);
+
+  BOOST_CHECK(s1 == s2);
+}
+
+BOOST_AUTO_TEST_CASE(SetChildSelector)
+{
+  Selectors s;
+  BOOST_CHECK_EQUAL(s.getChildSelector(), 0);
+  BOOST_CHECK_THROW(s.setChildSelector(-1), std::invalid_argument);
+  BOOST_CHECK_THROW(s.setChildSelector(2), std::invalid_argument);
+  s.setChildSelector(1);
+  BOOST_CHECK_EQUAL(s.getChildSelector(), 1);
+  s.setChildSelector(0);
+  BOOST_CHECK_EQUAL(s.getChildSelector(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  // Selectors ::= SELECTORS-TYPE TLV-LENGTH
+  //                 MinSuffixComponents?
+  //                 MaxSuffixComponents?
+  //                 PublisherPublicKeyLocator?
+  //                 Exclude?
+  //                 ChildSelector?
+  //                 MustBeFresh?
+
+  Selectors a;
+  Selectors b;
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // MinSuffixComponents
+  a.setMinSuffixComponents(1);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setMinSuffixComponents(2);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setMinSuffixComponents(1);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // MaxSuffixComponents
+  a.setMaxSuffixComponents(10);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setMaxSuffixComponents(10);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // PublisherPublicKeyLocator
+  a.setPublisherPublicKeyLocator(KeyLocator("/key/Locator/name"));
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setPublisherPublicKeyLocator(KeyLocator("/key/Locator/name"));
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // Exclude
+  a.setExclude(Exclude().excludeOne(name::Component("exclude")));
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setExclude(Exclude().excludeOne(name::Component("exclude")));
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // ChildSelector
+  a.setChildSelector(1);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setChildSelector(1);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  // MustBeFresh
+  a.setMustBeFresh(true);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setMustBeFresh(true);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSelectors
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/signature-info.t.cpp b/tests/unit/signature-info.t.cpp
new file mode 100644
index 0000000..f5cf102
--- /dev/null
+++ b/tests/unit/signature-info.t.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "signature-info.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestSignatureInfo)
+
+const uint8_t sigInfoRsa[] = {
+0x16, 0x1b, // SignatureInfo
+  0x1b, 0x01, // SignatureType
+    0x01, // Sha256WithRsa
+  0x1c, 0x16, // KeyLocator
+    0x07, 0x14, // Name
+      0x08, 0x04,
+        0x74, 0x65, 0x73, 0x74,
+      0x08, 0x03,
+        0x6b, 0x65, 0x79,
+      0x08, 0x07,
+        0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+};
+
+BOOST_AUTO_TEST_CASE(Constructor)
+{
+  SignatureInfo info;
+  BOOST_CHECK_EQUAL(info.getSignatureType(), -1);
+  BOOST_CHECK_EQUAL(info.hasKeyLocator(), false);
+  BOOST_CHECK_THROW(info.getKeyLocator(), SignatureInfo::Error);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(info), "Invalid SignatureInfo");
+
+  SignatureInfo sha256Info(tlv::DigestSha256);
+  BOOST_CHECK_EQUAL(sha256Info.getSignatureType(), tlv::DigestSha256);
+  BOOST_CHECK_EQUAL(sha256Info.hasKeyLocator(), false);
+  BOOST_CHECK_THROW(sha256Info.getKeyLocator(), SignatureInfo::Error);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(sha256Info), "DigestSha256");
+
+  KeyLocator keyLocator("/test/key/locator");
+  SignatureInfo sha256RsaInfo(tlv::SignatureSha256WithRsa, keyLocator);
+  BOOST_CHECK_EQUAL(sha256RsaInfo.getSignatureType(), tlv::SignatureSha256WithRsa);
+  BOOST_CHECK_EQUAL(sha256RsaInfo.hasKeyLocator(), true);
+  BOOST_CHECK_NO_THROW(sha256RsaInfo.getKeyLocator());
+  BOOST_CHECK_EQUAL(sha256RsaInfo.getKeyLocator().getName(), Name("/test/key/locator"));
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(sha256RsaInfo), "SignatureSha256WithRsa Name=/test/key/locator");
+
+  const Block& encoded = sha256RsaInfo.wireEncode();
+  Block sigInfoBlock(sigInfoRsa, sizeof(sigInfoRsa));
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(sigInfoBlock.wire(),
+                                sigInfoBlock.wire() + sigInfoBlock.size(),
+                                encoded.wire(),
+                                encoded.wire() + encoded.size());
+
+  sha256RsaInfo = SignatureInfo(sigInfoBlock);
+  BOOST_CHECK_EQUAL(sha256RsaInfo.getSignatureType(), tlv::SignatureSha256WithRsa);
+  BOOST_CHECK_EQUAL(sha256RsaInfo.hasKeyLocator(), true);
+  BOOST_CHECK_NO_THROW(sha256RsaInfo.getKeyLocator());
+  BOOST_CHECK_EQUAL(sha256RsaInfo.getKeyLocator().getName(), Name("/test/key/locator"));
+}
+
+BOOST_AUTO_TEST_CASE(ConstructorError)
+{
+  const uint8_t error1[] = {
+    0x15, 0x1b, // Wrong SignatureInfo (0x16, 0x1b)
+      0x1b, 0x01, // SignatureType
+        0x01, // Sha256WithRsa
+      0x1c, 0x16, // KeyLocator
+        0x07, 0x14, // Name
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x03,
+            0x6b, 0x65, 0x79,
+          0x08, 0x07,
+            0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+  };
+  Block errorBlock1(error1, sizeof(error1));
+  BOOST_CHECK_THROW(SignatureInfo info(errorBlock1), tlv::Error);
+
+  const uint8_t error2[] = {
+    0x16, 0x01, // SignatureInfo
+      0x01 // Wrong SignatureInfo value
+  };
+  Block errorBlock2(error2, sizeof(error2));
+  BOOST_CHECK_THROW(SignatureInfo info(errorBlock2), tlv::Error);
+
+  const uint8_t error3[] = {
+    0x16, 0x01, // SignatureInfo
+      0x1a, 0x01, // Wrong SignatureType (0x1b, 0x1b)
+        0x01, // Sha256WithRsa
+      0x1c, 0x16, // KeyLocator
+        0x07, 0x14, // Name
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x03,
+            0x6b, 0x65, 0x79,
+          0x08, 0x07,
+            0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+  };
+  Block errorBlock3(error3, sizeof(error3));
+  BOOST_CHECK_THROW(SignatureInfo info(errorBlock3), tlv::Error);
+
+  const uint8_t error4[] = {
+    0x16, 0x00 // Empty SignatureInfo
+  };
+  Block errorBlock4(error4, sizeof(error4));
+  BOOST_CHECK_THROW(SignatureInfo info(errorBlock4), tlv::Error);
+
+}
+
+BOOST_AUTO_TEST_CASE(SetterGetter)
+{
+  SignatureInfo info;
+  BOOST_CHECK_EQUAL(info.getSignatureType(), -1);
+  BOOST_CHECK_EQUAL(info.hasKeyLocator(), false);
+  BOOST_CHECK_THROW(info.getKeyLocator(), SignatureInfo::Error);
+
+  info.setSignatureType(tlv::SignatureSha256WithRsa);
+  BOOST_CHECK_EQUAL(info.getSignatureType(), tlv::SignatureSha256WithRsa);
+  BOOST_CHECK_EQUAL(info.hasKeyLocator(), false);
+
+  KeyLocator keyLocator("/test/key/locator");
+  info.setKeyLocator(keyLocator);
+  BOOST_CHECK_EQUAL(info.hasKeyLocator(), true);
+  BOOST_CHECK_NO_THROW(info.getKeyLocator());
+  BOOST_CHECK_EQUAL(info.getKeyLocator().getName(), Name("/test/key/locator"));
+
+  const Block& encoded = info.wireEncode();
+  Block sigInfoBlock(sigInfoRsa, sizeof(sigInfoRsa));
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(sigInfoBlock.wire(),
+                                sigInfoBlock.wire() + sigInfoBlock.size(),
+                                encoded.wire(),
+                                encoded.wire() + encoded.size());
+
+  info.unsetKeyLocator();
+  BOOST_CHECK_EQUAL(info.hasKeyLocator(), false);
+  BOOST_CHECK_THROW(info.getKeyLocator(), SignatureInfo::Error);
+}
+
+BOOST_AUTO_TEST_CASE(ValidityPeriodExtension)
+{
+  const uint8_t sigInfo[] = {
+    0x16, 0x45, // SignatureInfo
+      0x1b, 0x01, // SignatureType
+        0x01, // Sha256WithRsa
+      0x1c, 0x16, // KeyLocator
+        0x07, 0x14, // Name
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x03,
+            0x6b, 0x65, 0x79,
+          0x08, 0x07,
+            0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+      0xfd, 0x00, 0xfd, 0x26, // ValidityPeriod
+        0xfd, 0x00, 0xfe, 0x0f, // NotBefore
+          0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, // 19700101T000000
+          0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+        0xfd, 0x00, 0xff, 0x0f, // NotAfter
+          0x31, 0x39, 0x37, 0x30, 0x30, 0x31, 0x30, 0x32, // 19700102T000000
+          0x54, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30
+  };
+
+  time::system_clock::TimePoint notBefore = time::getUnixEpoch();
+  time::system_clock::TimePoint notAfter = notBefore + 1_day;
+  security::ValidityPeriod vp1(notBefore, notAfter);
+
+  // encode
+  SignatureInfo info;
+  info.setSignatureType(tlv::SignatureSha256WithRsa);
+  info.setKeyLocator(KeyLocator("/test/key/locator"));
+  info.setValidityPeriod(vp1);
+
+  BOOST_CHECK_EQUAL(info.getValidityPeriod(), vp1);
+
+  const Block& encoded = info.wireEncode();
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(sigInfo, sigInfo + sizeof(sigInfo),
+                                encoded.wire(), encoded.wire() + encoded.size());
+
+  // decode
+  Block block(sigInfo, sizeof(sigInfo));
+  SignatureInfo info2;
+  info2.wireDecode(block);
+  BOOST_CHECK_EQUAL(info2.getValidityPeriod(), vp1);
+
+  const security::ValidityPeriod& validityPeriod = info2.getValidityPeriod();
+  BOOST_CHECK(validityPeriod.getPeriod() == std::make_pair(notBefore, notAfter));
+}
+
+BOOST_AUTO_TEST_CASE(OtherTlvs)
+{
+  SignatureInfo info;
+  BOOST_CHECK_EQUAL(info.getSignatureType(), -1);
+  BOOST_CHECK_EQUAL(info.hasKeyLocator(), false);
+  BOOST_CHECK_THROW(info.getKeyLocator(), SignatureInfo::Error);
+
+  const uint8_t tlv1[] = {
+    0x81, // T
+    0x01, // L
+    0x01, // V
+  };
+  Block block1(tlv1, sizeof(tlv1));
+
+  info.appendTypeSpecificTlv(block1);
+  BOOST_CHECK_THROW(info.getTypeSpecificTlv(0x82), SignatureInfo::Error);
+  BOOST_REQUIRE_NO_THROW(info.getTypeSpecificTlv(0x81));
+}
+
+BOOST_AUTO_TEST_CASE(OtherTlvsEncoding) // Bug #3914
+{
+  SignatureInfo info1(tlv::SignatureSha256WithRsa);
+  info1.appendTypeSpecificTlv(makeStringBlock(101, "First"));
+  info1.appendTypeSpecificTlv(makeStringBlock(102, "Second"));
+  info1.appendTypeSpecificTlv(makeStringBlock(103, "Third"));
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(info1), "SignatureSha256WithRsa { 101 102 103 }");
+
+  SignatureInfo info2;
+  info2.wireDecode(info1.wireEncode());
+  BOOST_CHECK_EQUAL(info1, info2);
+
+  const uint8_t infoBytes[] = {
+    0x16, 0x19, // SignatureInfo
+          0x1b, 0x01, 0x01, // SignatureType=1
+          0x65, 0x05, 0x46, 0x69, 0x72, 0x73, 0x74, // 101 "First"
+          0x66, 0x06, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, // 102 "Second"
+          0x67, 0x05, 0x54, 0x68, 0x69, 0x72, 0x64 // 103 "Third"
+  };
+
+  SignatureInfo info3(Block(infoBytes, sizeof(infoBytes)));
+  BOOST_CHECK_EQUAL(info3, info1);
+  BOOST_CHECK_EQUAL_COLLECTIONS(infoBytes, infoBytes + sizeof(infoBytes),
+                                info1.wireEncode().begin(), info1.wireEncode().end());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSignatureInfo
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/signature.t.cpp b/tests/unit/signature.t.cpp
new file mode 100644
index 0000000..e4d8eff
--- /dev/null
+++ b/tests/unit/signature.t.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "signature.hpp"
+#include "security/digest-sha256.hpp"
+#include "security/signature-sha256-with-rsa.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestSignature)
+
+BOOST_AUTO_TEST_CASE(Equality)
+{
+  Signature a;
+  Signature b;
+
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  a = SignatureSha256WithRsa();
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b = SignatureSha256WithRsa();
+  static const uint8_t someData[256] = {};
+  Block signatureValue = makeBinaryBlock(tlv::SignatureValue, someData, sizeof(someData));
+  b.setValue(signatureValue);
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  a.setValue(signatureValue);
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
+  a = DigestSha256();
+  b = SignatureSha256WithRsa();
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b = DigestSha256();
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSignature
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/tag-host.t.cpp b/tests/unit/tag-host.t.cpp
new file mode 100644
index 0000000..432fc74
--- /dev/null
+++ b/tests/unit/tag-host.t.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "tag-host.hpp"
+#include "data.hpp"
+#include "interest.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/mpl/vector.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestTagHost)
+
+class TestTag : public Tag
+{
+public:
+  static constexpr int
+  getTypeId() noexcept
+  {
+    return 1;
+  }
+};
+
+class TestTag2 : public Tag
+{
+public:
+  static constexpr int
+  getTypeId() noexcept
+  {
+    return 2;
+  }
+};
+
+typedef boost::mpl::vector<TagHost, Interest, Data> Fixtures;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Basic, T, Fixtures, T)
+{
+  BOOST_CHECK(this->template getTag<TestTag>() == nullptr);
+  BOOST_CHECK(this->template getTag<TestTag2>() == nullptr);
+
+  this->setTag(make_shared<TestTag>());
+
+  BOOST_CHECK(this->template getTag<TestTag>() != nullptr);
+  BOOST_CHECK(this->template getTag<TestTag2>() == nullptr);
+
+  this->setTag(make_shared<TestTag2>());
+
+  BOOST_CHECK(this->template getTag<TestTag>() != nullptr);
+  BOOST_CHECK(this->template getTag<TestTag2>() != nullptr);
+
+  this->template removeTag<TestTag2>();
+
+  BOOST_CHECK(this->template getTag<TestTag>() != nullptr);
+  BOOST_CHECK(this->template getTag<TestTag2>() == nullptr);
+
+  this->template removeTag<TestTag>();
+
+  BOOST_CHECK(this->template getTag<TestTag>() == nullptr);
+  BOOST_CHECK(this->template getTag<TestTag2>() == nullptr);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTagHost
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/tag.t.cpp b/tests/unit/tag.t.cpp
new file mode 100644
index 0000000..9e5a20d
--- /dev/null
+++ b/tests/unit/tag.t.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "tag.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestTag)
+
+BOOST_AUTO_TEST_CASE(SimpleTag)
+{
+  typedef ndn::SimpleTag<int, 3> MyTag;
+
+  BOOST_CHECK_EQUAL(MyTag::getTypeId(), 3);
+  MyTag tag(23361); // explicitly convertible from value type
+  int value = tag; // implicitly convertible to value type
+  BOOST_CHECK_EQUAL(value, 23361);
+  BOOST_CHECK_EQUAL(tag.get(), 23361);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTag
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/test-home-env-saver.hpp b/tests/unit/test-home-env-saver.hpp
new file mode 100644
index 0000000..dc3d956
--- /dev/null
+++ b/tests/unit/test-home-env-saver.hpp
@@ -0,0 +1,56 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_UNIT_TESTS_TEST_HOME_ENV_SAVER_HPP
+#define NDN_TESTS_UNIT_TESTS_TEST_HOME_ENV_SAVER_HPP
+
+#include <string>
+#include <cstdlib>
+
+namespace ndn {
+namespace tests {
+
+class TestHomeEnvSaver
+{
+public:
+  TestHomeEnvSaver()
+  {
+    if (std::getenv("TEST_HOME") != nullptr)
+      m_HOME = std::getenv("TEST_HOME");
+  }
+
+  virtual
+  ~TestHomeEnvSaver()
+  {
+    if (!m_HOME.empty())
+      setenv("TEST_HOME", m_HOME.c_str(), 1);
+    else
+      unsetenv("TEST_HOME");
+  }
+
+private:
+  std::string m_HOME;
+};
+
+} // namespace tests
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_TEST_HOME_ENV_SAVER_HPP
diff --git a/tests/unit/transport/tcp-transport.t.cpp b/tests/unit/transport/tcp-transport.t.cpp
new file mode 100644
index 0000000..02bd9c3
--- /dev/null
+++ b/tests/unit/transport/tcp-transport.t.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "transport/tcp-transport.hpp"
+#include "transport-fixture.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Transport)
+BOOST_FIXTURE_TEST_SUITE(TestTcpTransport, TransportFixture)
+
+using ndn::Transport;
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketNameOk)
+{
+  const auto got = TcpTransport::getSocketHostAndPortFromUri("tcp://127.0.0.1:6000");
+
+  BOOST_CHECK_EQUAL(got.first, "127.0.0.1");
+  BOOST_CHECK_EQUAL(got.second, "6000");
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketHostAndPortBadMissingHost)
+{
+  BOOST_CHECK_EXCEPTION(TcpTransport::getSocketHostAndPortFromUri("tcp://:6000"),
+                        Transport::Error,
+                        [] (const Transport::Error& error) {
+                          return error.what() == "Malformed URI: tcp://:6000"s;
+                        });
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketHostAndPortOkOmittedPort)
+{
+  const auto got = TcpTransport::getSocketHostAndPortFromUri("tcp://127.0.0.1");
+
+  BOOST_CHECK_EQUAL(got.first, "127.0.0.1");
+  BOOST_CHECK_EQUAL(got.second, "6363");
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketHostAndPortNameOkOmittedHostOmittedPort)
+{
+  const auto got = TcpTransport::getSocketHostAndPortFromUri("tcp://");
+
+  BOOST_CHECK_EQUAL(got.first, "localhost");
+  BOOST_CHECK_EQUAL(got.second, "6363");
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketHostAndPortBadWrongTransport)
+{
+  BOOST_CHECK_EXCEPTION(TcpTransport::getSocketHostAndPortFromUri("unix://"),
+                        Transport::Error,
+                        [] (const Transport::Error& error) {
+                          return error.what() == "Cannot create TcpTransport from \"unix\" URI"s;
+                        });
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketHostAndPortBadMalformedUri)
+{
+  BOOST_CHECK_EXCEPTION(TcpTransport::getSocketHostAndPortFromUri("tcp"),
+                        Transport::Error,
+                        [] (const Transport::Error& error) {
+                          return error.what() == "Malformed URI: tcp"s;
+                        });
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTcpTransport
+BOOST_AUTO_TEST_SUITE_END() // Transport
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/transport/transport-fixture.hpp b/tests/unit/transport/transport-fixture.hpp
new file mode 100644
index 0000000..d25bb94
--- /dev/null
+++ b/tests/unit/transport/transport-fixture.hpp
@@ -0,0 +1,48 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/config-file.hpp"
+#include "../test-home-env-saver.hpp"
+
+#ifndef NDN_TESTS_UNIT_TESTS_TRANSPORT_FIXTURE_HPP
+#define NDN_TESTS_UNIT_TESTS_TRANSPORT_FIXTURE_HPP
+
+namespace ndn {
+namespace tests {
+
+class TransportFixture : public TestHomeEnvSaver
+{
+public:
+  void
+  initializeConfig(const char* path)
+  {
+    setenv("TEST_HOME", path, 1);
+    m_config = make_unique<ConfigFile>();
+  }
+
+protected:
+  unique_ptr<ConfigFile> m_config;
+};
+
+} // namespace tests
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_TRANSPORT_FIXTURE_HPP
diff --git a/tests/unit/transport/unix-transport.t.cpp b/tests/unit/transport/unix-transport.t.cpp
new file mode 100644
index 0000000..f60f9d7
--- /dev/null
+++ b/tests/unit/transport/unix-transport.t.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "transport/unix-transport.hpp"
+#include "transport-fixture.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Transport)
+BOOST_FIXTURE_TEST_SUITE(TestUnixTransport, TransportFixture)
+
+using ndn::Transport;
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketNameOk)
+{
+  BOOST_CHECK_EQUAL(UnixTransport::getSocketNameFromUri("unix:///tmp/test/nfd.sock"), "/tmp/test/nfd.sock");
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketNameOkOmittedSocketOmittedProtocol)
+{
+  BOOST_CHECK_EQUAL(UnixTransport::getSocketNameFromUri(""), "/var/run/nfd.sock");
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketNameBadWrongTransport)
+{
+  BOOST_CHECK_EXCEPTION(UnixTransport::getSocketNameFromUri("tcp://"),
+                        Transport::Error,
+                        [] (const Transport::Error& error) {
+                          return error.what() == "Cannot create UnixTransport from \"tcp\" URI"s;
+                        });
+}
+
+BOOST_AUTO_TEST_CASE(GetDefaultSocketNameBadMalformedUri)
+{
+  BOOST_CHECK_EXCEPTION(UnixTransport::getSocketNameFromUri("unix"),
+                        Transport::Error,
+                        [] (const Transport::Error& error) {
+                          return error.what() == "Malformed URI: unix"s;
+                        });
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestUnixTransport
+BOOST_AUTO_TEST_SUITE_END() // Transport
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/unit-test-time-fixture.hpp b/tests/unit/unit-test-time-fixture.hpp
new file mode 100644
index 0000000..b177145
--- /dev/null
+++ b/tests/unit/unit-test-time-fixture.hpp
@@ -0,0 +1,106 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+#define NDN_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+
+#include "util/time-unit-test-clock.hpp"
+
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace tests {
+
+/** \brief a test fixture that overrides steady clock and system clock
+ */
+class UnitTestTimeFixture
+{
+public:
+  UnitTestTimeFixture()
+    : steadyClock(make_shared<time::UnitTestSteadyClock>())
+    , systemClock(make_shared<time::UnitTestSystemClock>())
+  {
+    time::setCustomClocks(steadyClock, systemClock);
+  }
+
+  ~UnitTestTimeFixture()
+  {
+    time::setCustomClocks(nullptr, nullptr);
+  }
+
+  /** \brief advance steady and system clocks
+   *
+   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
+   *  After each tick, io_service is polled to process pending I/O events.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancing would stop in case of an exception.
+   */
+  void
+  advanceClocks(const time::nanoseconds& tick, size_t nTicks = 1)
+  {
+    this->advanceClocks(tick, tick * nTicks);
+  }
+
+  /** \brief advance steady and system clocks
+   *
+   *  Clocks are advanced in increments of \p tick for \p total time.
+   *  The last increment might be shorter than \p tick.
+   *  After each tick, io_service is polled to process pending I/O events.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancing would stop in case of an exception.
+   */
+  void
+  advanceClocks(const time::nanoseconds& tick, const time::nanoseconds& total)
+  {
+    BOOST_ASSERT(tick > time::nanoseconds::zero());
+    BOOST_ASSERT(total >= time::nanoseconds::zero());
+
+    time::nanoseconds remaining = total;
+    while (remaining > time::nanoseconds::zero()) {
+      if (remaining >= tick) {
+        steadyClock->advance(tick);
+        systemClock->advance(tick);
+        remaining -= tick;
+      }
+      else {
+        steadyClock->advance(remaining);
+        systemClock->advance(remaining);
+        remaining = time::nanoseconds::zero();
+      }
+
+      if (io.stopped())
+        io.reset();
+      io.poll();
+    }
+  }
+
+public:
+  shared_ptr<time::UnitTestSteadyClock> steadyClock;
+  shared_ptr<time::UnitTestSystemClock> systemClock;
+  boost::asio::io_service io;
+};
+
+} // namespace tests
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
diff --git a/tests/unit/util/backports.t.cpp b/tests/unit/util/backports.t.cpp
new file mode 100644
index 0000000..7a124e0
--- /dev/null
+++ b/tests/unit/util/backports.t.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/backports.hpp"
+
+#include "boost-test.hpp"
+#include <numeric>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestBackports)
+
+BOOST_AUTO_TEST_CASE(Clamp)
+{
+  int x = clamp(5, 1, 10);
+  BOOST_CHECK_EQUAL(x, 5);
+
+  x = clamp(-5, 1, 10);
+  BOOST_CHECK_EQUAL(x, 1);
+
+  x = clamp(15, 1, 10);
+  BOOST_CHECK_EQUAL(x, 10);
+
+  x = clamp(5, 10, 1, std::greater<int>());
+  BOOST_CHECK_EQUAL(x, 5);
+
+  x = clamp(-5, 10, 1, std::greater<int>());
+  BOOST_CHECK_EQUAL(x, 1);
+
+  x = clamp(15, 10, 1, std::greater<int>());
+  BOOST_CHECK_EQUAL(x, 10);
+}
+
+BOOST_AUTO_TEST_CASE(OstreamJoiner)
+{
+  boost::test_tools::output_test_stream os;
+
+  auto joiner1 = ostream_joiner<char>(os, ' ');
+  auto joiner2 = make_ostream_joiner(os, ' ');
+  static_assert(std::is_same<decltype(joiner1), decltype(joiner2)>::value, "");
+
+  std::vector<int> v(5);
+  std::iota(v.begin(), v.end(), 1);
+  std::copy(v.begin(), v.end(), joiner2);
+  BOOST_CHECK(os.is_equal("1 2 3 4 5"));
+
+  auto joiner3 = make_ostream_joiner(os, "...");
+  std::copy(v.begin(), v.end(), joiner3);
+  BOOST_CHECK(os.is_equal("1...2...3...4...5"));
+
+  joiner3 = "one";
+  BOOST_CHECK(os.is_equal("one"));
+  joiner3 = "two";
+  BOOST_CHECK(os.is_equal("...two"));
+  ++joiner3 = "three";
+  BOOST_CHECK(os.is_equal("...three"));
+  joiner3++ = "four";
+  BOOST_CHECK(os.is_equal("...four"));
+
+  std::copy(v.begin(), v.end(), make_ostream_joiner(os, ""));
+  BOOST_CHECK(os.is_equal("12345"));
+
+  std::string delimiter("_");
+  std::copy(v.begin(), v.end(), make_ostream_joiner(os, delimiter));
+  BOOST_CHECK(os.is_equal("1_2_3_4_5"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBackports
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/concepts.t.cpp b/tests/unit/util/concepts.t.cpp
new file mode 100644
index 0000000..3141ec5
--- /dev/null
+++ b/tests/unit/util/concepts.t.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/concepts.hpp"
+
+namespace ndn {
+namespace tests {
+
+class WireEncodableType
+{
+public:
+  const Block&
+  wireEncode();
+};
+BOOST_CONCEPT_ASSERT((WireEncodable<WireEncodableType>));
+
+class WireEncodableType2
+{
+public:
+  Block
+  wireEncode();
+};
+BOOST_CONCEPT_ASSERT((WireEncodable<WireEncodableType2>));
+
+class WireDecodableType
+{
+public:
+  explicit
+  WireDecodableType(const Block& wire);
+
+  void
+  wireDecode(const Block& wire);
+};
+BOOST_CONCEPT_ASSERT((WireDecodable<WireDecodableType>));
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/config-file-home/.ndn/client.conf b/tests/unit/util/config-file-home/.ndn/client.conf
new file mode 100644
index 0000000..ba9f623
--- /dev/null
+++ b/tests/unit/util/config-file-home/.ndn/client.conf
@@ -0,0 +1,2 @@
+a=/path/to/nowhere
+b=some-othervalue.01
diff --git a/tests/unit/util/config-file-malformed-home/.ndn/client.conf b/tests/unit/util/config-file-malformed-home/.ndn/client.conf
new file mode 100644
index 0000000..7898192
--- /dev/null
+++ b/tests/unit/util/config-file-malformed-home/.ndn/client.conf
@@ -0,0 +1 @@
+a
diff --git a/tests/unit/util/config-file.t.cpp b/tests/unit/util/config-file.t.cpp
new file mode 100644
index 0000000..658018b
--- /dev/null
+++ b/tests/unit/util/config-file.t.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/config-file.hpp"
+#include "../test-home-env-saver.hpp"
+
+#include "boost-test.hpp"
+
+#include <cstdlib>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestConfigFile, TestHomeEnvSaver)
+
+BOOST_AUTO_TEST_CASE(Parse)
+{
+  namespace fs = boost::filesystem;
+
+  setenv("TEST_HOME", "tests/unit/util/config-file-home", 1);
+
+  fs::path homePath(fs::absolute(std::getenv("TEST_HOME")));
+  homePath /= ".ndn/client.conf";
+
+  ConfigFile config;
+  BOOST_REQUIRE_EQUAL(config.getPath(), homePath);
+
+  const ConfigFile::Parsed& parsed = config.getParsedConfiguration();
+  BOOST_CHECK_EQUAL(parsed.get<std::string>("a"), "/path/to/nowhere");
+  BOOST_CHECK_EQUAL(parsed.get<std::string>("b"), "some-othervalue.01");
+}
+
+BOOST_AUTO_TEST_CASE(ParseEmptyPath)
+{
+  setenv("TEST_HOME", "tests/unit/util/does/not/exist", 1);
+
+  BOOST_CHECK_NO_THROW(ConfigFile config);
+}
+
+BOOST_AUTO_TEST_CASE(ParseMalformed)
+{
+  setenv("TEST_HOME", "tests/unit/util/config-file-malformed-home", 1);
+
+  BOOST_CHECK_THROW(ConfigFile config, ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestConfigFile
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/dummy-client-face.t.cpp b/tests/unit/util/dummy-client-face.t.cpp
new file mode 100644
index 0000000..aed8cd6
--- /dev/null
+++ b/tests/unit/util/dummy-client-face.t.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "../identity-management-time-fixture.hpp"
+#include "make-interest-data.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestDummyClientFace, ndn::tests::IdentityManagementTimeFixture)
+
+BOOST_AUTO_TEST_CASE(ProcessEventsOverride)
+{
+  bool isOverrideInvoked = false;
+  auto override = [&] (time::milliseconds timeout) {
+    isOverrideInvoked = true;
+    BOOST_CHECK_EQUAL(timeout, 200_ms);
+  };
+
+  DummyClientFace face(io, {false, false, override});
+  face.processEvents(200_ms);
+  BOOST_CHECK(isOverrideInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(BroadcastLink)
+{
+  DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+  face1.linkTo(face2);
+
+  int nFace1Interest = 0;
+  int nFace2Interest = 0;
+  face1.setInterestFilter("/face1",
+                          [&] (const InterestFilter&, const Interest& interest) {
+                            BOOST_CHECK_EQUAL(interest.getName().toUri(), "/face1/data");
+                            nFace1Interest++;
+                            face1.put(ndn::tests::makeNack(interest, lp::NackReason::NO_ROUTE));
+                          }, nullptr, nullptr);
+  face2.setInterestFilter("/face2",
+                          [&] (const InterestFilter&, const Interest& interest) {
+                            BOOST_CHECK_EQUAL(interest.getName().toUri(), "/face2/data");
+                            nFace2Interest++;
+                            face2.put(*ndn::tests::makeData("/face2/data"));
+                            return;
+                          }, nullptr, nullptr);
+
+  advanceClocks(25_ms, 4);
+
+  int nFace1Data = 0;
+  int nFace2Nack = 0;
+  face1.expressInterest(*makeInterest("/face2/data"),
+                        [&] (const Interest& i, const Data& d) {
+                          BOOST_CHECK_EQUAL(d.getName().toUri(), "/face2/data");
+                          nFace1Data++;
+                        }, nullptr, nullptr);
+  face2.expressInterest(*makeInterest("/face1/data"),
+                        [&] (const Interest& i, const Data& d) {
+                          BOOST_CHECK(false);
+                        },
+                        [&] (const Interest& i, const lp::Nack& n) {
+                          BOOST_CHECK_EQUAL(n.getInterest().getName().toUri(), "/face1/data");
+                          nFace2Nack++;
+                        }, nullptr);
+
+  advanceClocks(10_ms, 100);
+
+  BOOST_CHECK_EQUAL(nFace1Data, 1);
+  BOOST_CHECK_EQUAL(nFace2Nack, 1);
+  BOOST_CHECK_EQUAL(nFace1Interest, 1);
+  BOOST_CHECK_EQUAL(nFace2Interest, 1);
+}
+
+BOOST_AUTO_TEST_CASE(BroadcastLinkDestroy)
+{
+  DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+
+  face1.linkTo(face2);
+  face2.unlink();
+  BOOST_CHECK(face1.m_bcastLink == nullptr);
+
+  DummyClientFace face3(io, m_keyChain, DummyClientFace::Options{true, true});
+  face1.linkTo(face2);
+  face3.linkTo(face1);
+  face2.unlink();
+  BOOST_CHECK(face1.m_bcastLink != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(AlreadyLinkException)
+{
+  DummyClientFace face1(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face2(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face3(io, m_keyChain, DummyClientFace::Options{true, true});
+  DummyClientFace face4(io, m_keyChain, DummyClientFace::Options{true, true});
+
+  face1.linkTo(face2);
+  face3.linkTo(face4);
+
+  BOOST_CHECK_THROW(face2.linkTo(face3), DummyClientFace::AlreadyLinkedError);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDummyClientFace
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/indented-stream.t.cpp b/tests/unit/util/indented-stream.t.cpp
new file mode 100644
index 0000000..e450c4a
--- /dev/null
+++ b/tests/unit/util/indented-stream.t.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/indented-stream.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestIndentedStream)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  output_test_stream os;
+
+  os << "Hello" << std::endl;
+  {
+    IndentedStream os1(os, " [prefix] ");
+    os1 << "," << "\n";
+    {
+      IndentedStream os2(os1, " [another prefix] ");
+      os2 << "World!" << "\n";
+    }
+  }
+
+  BOOST_CHECK(os.is_equal("Hello\n"
+                          " [prefix] ,\n"
+                          " [prefix]  [another prefix] World!\n"
+                          ));
+}
+
+BOOST_AUTO_TEST_CASE(BasicWithFlushes) // Bug #2723
+{
+  output_test_stream os;
+
+  os << "Hello" << std::endl;
+  {
+    IndentedStream os1(os, " [prefix] ");
+    os1 << "," << std::endl;
+    {
+      IndentedStream os2(os1, " [another prefix] ");
+      os2 << "World!" << std::endl;
+    }
+  }
+
+  BOOST_CHECK(os.is_equal("Hello\n"
+                          " [prefix] ,\n"
+                          " [prefix]  [another prefix] World!\n"
+                          ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIndentedStream
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/io.t.cpp b/tests/unit/util/io.t.cpp
new file mode 100644
index 0000000..1b46bca
--- /dev/null
+++ b/tests/unit/util/io.t.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/io.hpp"
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace tests {
+
+class IoFixture
+{
+protected:
+  IoFixture()
+    : filepath(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) /= "TestIo")
+    , filename(filepath.string())
+  {
+    boost::filesystem::create_directories(filepath.parent_path());
+  }
+
+  ~IoFixture()
+  {
+    boost::system::error_code ec;
+    boost::filesystem::remove(filepath, ec); // ignore error
+  }
+
+  /** \brief create a directory at filename, so that it's neither readable nor writable as a file
+   */
+  void
+  mkdir() const
+  {
+    boost::filesystem::create_directory(filepath);
+  }
+
+  template<typename Container, typename CharT = typename Container::value_type>
+  Container
+  readFile() const
+  {
+    Container container;
+    std::ifstream fs(filename, std::ios_base::binary);
+    char ch;
+    while (fs.get(ch)) {
+      container.push_back(static_cast<CharT>(ch));
+    }
+    return container;
+  }
+
+  template<typename Container, typename CharT = typename Container::value_type>
+  void
+  writeFile(const Container& content) const
+  {
+    std::ofstream fs(filename, std::ios_base::binary);
+    for (CharT ch : content) {
+      fs.put(static_cast<char>(ch));
+    }
+    fs.close();
+    BOOST_REQUIRE_MESSAGE(fs, "error writing file");
+  }
+
+protected:
+  const boost::filesystem::path filepath;
+  const std::string filename;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestIo, IoFixture)
+
+class EncodableType
+{
+public:
+  Block
+  wireEncode() const
+  {
+    if (shouldThrow) {
+      BOOST_THROW_EXCEPTION(tlv::Error("encode error"));
+    }
+
+    // block will be 0xAA, 0x01, 0xDD
+    return makeNonNegativeIntegerBlock(0xAA, 0xDD);
+  }
+
+public:
+  bool shouldThrow = false;
+};
+
+template<bool SHOULD_THROW = false>
+class DecodableTypeTpl
+{
+public:
+  DecodableTypeTpl() = default;
+
+  explicit
+  DecodableTypeTpl(const Block& block)
+  {
+    this->wireDecode(block);
+  }
+
+  void
+  wireDecode(const Block& block)
+  {
+    if (m_shouldThrow) {
+      BOOST_THROW_EXCEPTION(tlv::Error("decode error"));
+    }
+
+    // block must be 0xBB, 0x01, 0xEE
+    BOOST_CHECK_EQUAL(block.type(), 0xBB);
+    BOOST_REQUIRE_EQUAL(block.value_size(), 1);
+    BOOST_CHECK_EQUAL(block.value()[0], 0xEE);
+  }
+
+private:
+  bool m_shouldThrow = SHOULD_THROW;
+};
+
+typedef DecodableTypeTpl<false> DecodableType;
+typedef DecodableTypeTpl<true> DecodableTypeThrow;
+
+BOOST_AUTO_TEST_CASE(LoadNoEncoding)
+{
+  this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
+  shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::NO_ENCODING);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64)
+{
+  this->writeFile<std::string>("uwHu\n"); // printf '\xBB\x01\xEE' | base64
+  shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64Newline64)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+    "AAAAAAAAAAAA\n");
+  // printf '\x08\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | base64
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64Newline32)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
+    "AAAAAAAAAAAA\n");
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64NewlineEnd)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadBase64NoNewline)
+{
+  this->writeFile<std::string>(
+    "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+  shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadHex)
+{
+  this->writeFile<std::string>("BB01EE");
+  shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::HEX);
+  BOOST_CHECK(decoded != nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadException)
+{
+  this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
+  shared_ptr<DecodableTypeThrow> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableTypeThrow>(filename, io::NO_ENCODING));
+  BOOST_CHECK(decoded == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadNotHex)
+{
+  this->writeFile<std::string>("not-hex");
+  shared_ptr<DecodableType> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::HEX));
+  BOOST_CHECK(decoded == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(LoadFileNotReadable)
+{
+  shared_ptr<DecodableType> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::NO_ENCODING));
+  BOOST_CHECK(decoded == nullptr);
+}
+
+BOOST_AUTO_TEST_CASE(SaveNoEncoding)
+{
+  EncodableType encoded;
+  BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::NO_ENCODING));
+  auto content = this->readFile<std::vector<uint8_t>>();
+  uint8_t expected[] = {0xAA, 0x01, 0xDD};
+  BOOST_CHECK_EQUAL_COLLECTIONS(content.begin(), content.end(),
+                                expected, expected + sizeof(expected));
+}
+
+BOOST_AUTO_TEST_CASE(SaveBase64)
+{
+  EncodableType encoded;
+  BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::BASE64));
+  auto content = this->readFile<std::string>();
+  BOOST_CHECK_EQUAL(content, "qgHd\n"); // printf '\xAA\x01\xDD' | base64
+}
+
+BOOST_AUTO_TEST_CASE(SaveHex)
+{
+  EncodableType encoded;
+  BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::HEX));
+  auto content = this->readFile<std::string>();
+  BOOST_CHECK_EQUAL(content, "AA01DD");
+}
+
+BOOST_AUTO_TEST_CASE(SaveException)
+{
+  EncodableType encoded;
+  encoded.shouldThrow = true;
+  BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SaveFileNotWritable)
+{
+  this->mkdir();
+  EncodableType encoded;
+  encoded.shouldThrow = true;
+  BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
+}
+
+class IdCertFixture : public IoFixture
+                    , public IdentityManagementFixture
+{
+};
+
+BOOST_FIXTURE_TEST_CASE(IdCert, IdCertFixture)
+{
+  auto identity = addIdentity("/TestIo/IdCert", RsaKeyParams());
+  const auto& cert = identity.getDefaultKey().getDefaultCertificate();
+  io::save(cert, filename);
+
+  auto readCert = io::load<security::v2::Certificate>(filename);
+
+  BOOST_REQUIRE(readCert != nullptr);
+  BOOST_CHECK_EQUAL(cert.getName(), readCert->getName());
+
+  this->writeFile<std::string>("");
+  readCert = io::load<security::v2::Certificate>(filename);
+  BOOST_REQUIRE(readCert == nullptr);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestIo
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/log-filter-module.cpp b/tests/unit/util/log-filter-module.cpp
new file mode 100644
index 0000000..cf3b628
--- /dev/null
+++ b/tests/unit/util/log-filter-module.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/logger.hpp"
+
+NDN_LOG_INIT(fm.FilterModule);
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+void
+logFromFilterModule()
+{
+  NDN_LOG_TRACE("traceFM");
+  NDN_LOG_DEBUG("debugFM");
+  NDN_LOG_INFO("infoFM");
+  NDN_LOG_WARN("warnFM");
+  NDN_LOG_ERROR("errorFM");
+  NDN_LOG_FATAL("fatalFM");
+}
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
+
diff --git a/tests/unit/util/log-module1.cpp b/tests/unit/util/log-module1.cpp
new file mode 100644
index 0000000..2099a75
--- /dev/null
+++ b/tests/unit/util/log-module1.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/logger.hpp"
+
+NDN_LOG_INIT(Module1);
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+void
+logFromModule1()
+{
+  NDN_LOG_TRACE("trace" << 1);
+  NDN_LOG_DEBUG("debug" << 1);
+  NDN_LOG_INFO("info" << 1);
+  NDN_LOG_WARN("warn" << 1);
+  NDN_LOG_ERROR("error" << 1);
+  NDN_LOG_FATAL("fatal" << 1);
+}
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/log-module2.cpp b/tests/unit/util/log-module2.cpp
new file mode 100644
index 0000000..273165d
--- /dev/null
+++ b/tests/unit/util/log-module2.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/logger.hpp"
+
+NDN_LOG_INIT(Module2);
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+void
+logFromModule2()
+{
+  NDN_LOG_TRACE("trace" << 2);
+  NDN_LOG_DEBUG("debug" << 2);
+  NDN_LOG_INFO("info" << 2);
+  NDN_LOG_WARN("warn" << 2);
+  NDN_LOG_ERROR("error" << 2);
+  NDN_LOG_FATAL("fatal" << 2);
+}
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/logger.t.cpp b/tests/unit/util/logger.t.cpp
new file mode 100644
index 0000000..cc48425
--- /dev/null
+++ b/tests/unit/util/logger.t.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/logger.hpp"
+#include "util/logging.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestLogger)
+
+BOOST_AUTO_TEST_CASE(LegalAlphanumeric)
+{
+  Logger logger("ndnTest123");
+  auto mNames = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(mNames.count("ndnTest123"), 1);
+  BOOST_CHECK(logger.isLevelEnabled(LogLevel::NONE));
+  Logging::get().removeLogger(logger);
+}
+
+BOOST_AUTO_TEST_CASE(AllLegalSymbols)
+{
+  Logger logger("ndn.~#%.test_<check>1-2-3");
+  auto mNames = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(mNames.count("ndn.~#%.test_<check>1-2-3"), 1);
+  BOOST_CHECK(logger.isLevelEnabled(LogLevel::NONE));
+  Logging::get().removeLogger(logger);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyLogger)
+{
+  BOOST_CHECK_THROW(Logger logger(""), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidSymbol)
+{
+  BOOST_CHECK_THROW(Logger logger("ndn.test.*"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(StartsWithPeriod)
+{
+  BOOST_CHECK_THROW(Logger logger(".ndn.test"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(EndsWithPeriod)
+{
+  BOOST_CHECK_THROW(Logger logger("ndn.test."), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(ConsecutivePeriods)
+{
+  BOOST_CHECK_THROW(Logger logger("ndn..test"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestLogger
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/logging.t.cpp b/tests/unit/util/logging.t.cpp
new file mode 100644
index 0000000..b431a05
--- /dev/null
+++ b/tests/unit/util/logging.t.cpp
@@ -0,0 +1,652 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/logging.hpp"
+#include "util/logger.hpp"
+
+#include "../unit-test-time-fixture.hpp"
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+NDN_LOG_INIT(ndn.util.tests.Logging);
+
+void
+logFromModule1();
+
+void
+logFromModule2();
+
+void
+logFromFilterModule();
+
+static void
+logFromNewLogger(const char* moduleName)
+{
+  Logger logger(moduleName);
+  auto ndn_cxx_getLogger = [&logger] () -> Logger& { return logger; };
+
+  NDN_LOG_TRACE("trace" << moduleName);
+  NDN_LOG_DEBUG("debug" << moduleName);
+  NDN_LOG_INFO("info" << moduleName);
+  NDN_LOG_WARN("warn" << moduleName);
+  NDN_LOG_ERROR("error" << moduleName);
+  NDN_LOG_FATAL("fatal" << moduleName);
+
+  BOOST_CHECK(Logging::get().removeLogger(logger));
+}
+
+namespace ns1 {
+
+NDN_LOG_INIT(ndn.util.tests.ns1);
+
+static void
+logFromNamespace1()
+{
+  NDN_LOG_INFO("hello world from ns1");
+}
+
+} // namespace ns1
+
+namespace ns2 {
+
+NDN_LOG_INIT(ndn.util.tests.ns2);
+
+static void
+logFromNamespace2()
+{
+  NDN_LOG_INFO("hi there from ns2");
+}
+
+} // namespace ns2
+
+class ClassWithLogger
+{
+public:
+  void
+  logFromConstMemberFunction() const
+  {
+    NDN_LOG_INFO("const member function");
+  }
+
+  static void
+  logFromStaticMemberFunction()
+  {
+    NDN_LOG_INFO("static member function");
+  }
+
+private:
+  NDN_LOG_MEMBER_DECL();
+};
+
+NDN_LOG_MEMBER_INIT(ClassWithLogger, ndn.util.tests.ClassWithLogger);
+
+template<class T, class U>
+class ClassTemplateWithLogger
+{
+public:
+  void
+  logFromMemberFunction()
+  {
+    NDN_LOG_INFO("class template non-static member function");
+  }
+
+  static void
+  logFromStaticMemberFunction()
+  {
+    NDN_LOG_INFO("class template static member function");
+  }
+
+private:
+  NDN_LOG_MEMBER_DECL();
+};
+
+// Technically this declaration is not necessary in this case,
+// but we want to test that the macro expands to well-formed code
+NDN_LOG_MEMBER_DECL_SPECIALIZED((ClassTemplateWithLogger<int, double>));
+
+NDN_LOG_MEMBER_INIT_SPECIALIZED((ClassTemplateWithLogger<int, double>), ndn.util.tests.Specialized1);
+NDN_LOG_MEMBER_INIT_SPECIALIZED((ClassTemplateWithLogger<int, std::string>), ndn.util.tests.Specialized2);
+
+const time::microseconds LOG_SYSTIME(1468108800311239LL);
+const std::string LOG_SYSTIME_STR("1468108800.311239");
+
+class LoggingFixture : public ndn::tests::UnitTestTimeFixture
+{
+protected:
+  LoggingFixture()
+    : m_oldEnabledLevel(Logging::get().getLevels())
+    , m_oldDestination(Logging::get().getDestination())
+  {
+    this->systemClock->setNow(LOG_SYSTIME);
+    Logging::get().resetLevels();
+    Logging::setDestination(os);
+  }
+
+  ~LoggingFixture()
+  {
+    Logging::get().setLevelImpl(m_oldEnabledLevel);
+    Logging::setDestination(m_oldDestination);
+  }
+
+protected:
+  boost::test_tools::output_test_stream os;
+
+private:
+  std::unordered_map<std::string, LogLevel> m_oldEnabledLevel;
+  shared_ptr<std::ostream> m_oldDestination;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestLogging, LoggingFixture)
+
+BOOST_AUTO_TEST_CASE(GetLoggerNames)
+{
+  // check that all names are registered from the start even if
+  // logger instances are lazily created on first use
+  auto n = Logging::getLoggerNames().size();
+  NDN_LOG_TRACE("GetLoggerNames");
+  auto names = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(names.size(), n);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.Logging"), 1);
+}
+
+BOOST_AUTO_TEST_SUITE(Severity)
+
+BOOST_AUTO_TEST_CASE(None)
+{
+  Logging::setLevel("Module1", LogLevel::NONE);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Error)
+{
+  Logging::setLevel("Module1", LogLevel::ERROR);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Warn)
+{
+  Logging::setLevel("Module1", LogLevel::WARN);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Info)
+{
+  Logging::setLevel("Module1", LogLevel::INFO);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Debug)
+{
+  Logging::setLevel("Module1", LogLevel::DEBUG);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " DEBUG: [Module1] debug1\n" +
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Trace)
+{
+  Logging::setLevel("Module1", LogLevel::TRACE);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " TRACE: [Module1] trace1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module1] debug1\n" +
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(All)
+{
+  Logging::setLevel("Module1", LogLevel::ALL);
+  logFromModule1();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " TRACE: [Module1] trace1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module1] debug1\n" +
+    LOG_SYSTIME_STR + " INFO: [Module1] info1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Severity
+
+BOOST_AUTO_TEST_CASE(NamespaceLogger)
+{
+  Logging::setLevel("ndn.util.tests.ns1", LogLevel::INFO);
+  Logging::setLevel("ndn.util.tests.ns2", LogLevel::DEBUG);
+
+  const auto& levels = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(levels.size(), 2);
+  BOOST_CHECK_EQUAL(levels.at("ndn.util.tests.ns1"), LogLevel::INFO);
+  BOOST_CHECK_EQUAL(levels.at("ndn.util.tests.ns2"), LogLevel::DEBUG);
+
+  const auto& names = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.ns1"), 1);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.ns2"), 1);
+
+  ns1::logFromNamespace1();
+  ns2::logFromNamespace2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ns1] hello world from ns1\n" +
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ns2] hi there from ns2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(MemberLogger)
+{
+  Logging::setLevel("ndn.util.tests.ClassWithLogger", LogLevel::INFO);
+  Logging::setLevel("ndn.util.tests.Specialized1", LogLevel::INFO);
+  // ndn.util.tests.Specialized2 is not enabled
+
+  const auto& names = Logging::getLoggerNames();
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.ClassWithLogger"), 1);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.Specialized1"), 1);
+  BOOST_CHECK_EQUAL(names.count("ndn.util.tests.Specialized2"), 1);
+
+  ClassWithLogger::logFromStaticMemberFunction();
+  ClassWithLogger{}.logFromConstMemberFunction();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ClassWithLogger] static member function\n" +
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.ClassWithLogger] const member function\n"
+    ));
+
+  ClassTemplateWithLogger<int, double>::logFromStaticMemberFunction();
+  ClassTemplateWithLogger<int, double>{}.logFromMemberFunction();
+  ClassTemplateWithLogger<int, std::string>::logFromStaticMemberFunction();
+  ClassTemplateWithLogger<int, std::string>{}.logFromMemberFunction();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.Specialized1] class template static member function\n" +
+    LOG_SYSTIME_STR + " INFO: [ndn.util.tests.Specialized1] class template non-static member function\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SameNameLoggers)
+{
+  Logging::setLevel("Module1", LogLevel::WARN);
+  logFromModule1();
+  logFromNewLogger("Module1");
+
+  BOOST_CHECK_EQUAL(Logging::getLoggerNames().count("Module1"), 1);
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module1] warnModule1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] errorModule1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatalModule1\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(LateRegistration)
+{
+  Logging::setLevel("Module3", LogLevel::DEBUG);
+  logFromNewLogger("Module3");
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " DEBUG: [Module3] debugModule3\n" +
+    LOG_SYSTIME_STR + " INFO: [Module3] infoModule3\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module3] warnModule3\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module3] errorModule3\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module3] fatalModule3\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE(DefaultSeverity)
+
+BOOST_AUTO_TEST_CASE(Unset)
+{
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(NoOverride)
+{
+  Logging::setLevel("*", LogLevel::WARN);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Override)
+{
+  Logging::setLevel("*", LogLevel::WARN);
+  Logging::setLevel("Module2", LogLevel::DEBUG);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module2] debug2\n" +
+    LOG_SYSTIME_STR + " INFO: [Module2] info2\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // DefaultSeverity
+
+BOOST_AUTO_TEST_SUITE(SeverityConfig)
+
+BOOST_AUTO_TEST_CASE(SetEmpty)
+{
+  Logging::setLevel("");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 0);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetDefault)
+{
+  Logging::setLevel("*=WARN");
+  const auto& prefixMap = Logging::get().getLevels();
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.size(), 1);
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::WARN);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetModule)
+{
+  Logging::setLevel("Module1=ERROR");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 1);
+  BOOST_CHECK_EQUAL(prefixMap.at("Module1"), LogLevel::ERROR);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetOverride)
+{
+  Logging::setLevel("*=WARN:Module2=DEBUG");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::WARN);
+  BOOST_CHECK_EQUAL(prefixMap.at("Module2"), LogLevel::DEBUG);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module2] debug2\n" +
+    LOG_SYSTIME_STR + " INFO: [Module2] info2\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetTwice)
+{
+  Logging::setLevel("*=WARN");
+  Logging::setLevel("Module2=DEBUG");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::WARN);
+  BOOST_CHECK_EQUAL(prefixMap.at("Module2"), LogLevel::DEBUG);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " WARNING: [Module1] warn1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [Module2] debug2\n" +
+    LOG_SYSTIME_STR + " INFO: [Module2] info2\n" +
+    LOG_SYSTIME_STR + " WARNING: [Module2] warn2\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Reset)
+{
+  Logging::setLevel("Module2=DEBUG");
+  Logging::setLevel("*=ERROR");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 1);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::ERROR);
+  logFromModule1();
+  logFromModule2();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " ERROR: [Module1] error1\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " ERROR: [Module2] error2\n" +
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(Malformed)
+{
+  BOOST_CHECK_THROW(Logging::setLevel("Module1=INVALID-LEVEL"), std::invalid_argument);
+  BOOST_CHECK_THROW(Logging::setLevel("Module1-MISSING-EQUAL-SIGN"), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(SetFilter)
+{
+  Logging::setLevel("*=FATAL:fm.*=DEBUG");
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::FATAL);
+  // "name.*" is treated as "name." internally
+  BOOST_CHECK_EQUAL(prefixMap.at("fm."), LogLevel::DEBUG);
+  logFromModule1();
+  logFromFilterModule();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " DEBUG: [fm.FilterModule] debugFM\n" +
+    LOG_SYSTIME_STR + " INFO: [fm.FilterModule] infoFM\n" +
+    LOG_SYSTIME_STR + " WARNING: [fm.FilterModule] warnFM\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.FilterModule] errorFM\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.FilterModule] fatalFM\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(SetOverrideFilter)
+{
+  Logging::setLevel("*=FATAL:fm.FilterModule=DEBUG");
+  Logging::setLevel("fm.*", LogLevel::INFO);
+  const auto& prefixMap = Logging::get().getLevels();
+  BOOST_CHECK_EQUAL(prefixMap.size(), 2);
+  // "*" is treated as "" internally
+  BOOST_CHECK_EQUAL(prefixMap.at(""), LogLevel::FATAL);
+  // "name.*" is treated as "name." internally
+  BOOST_CHECK_EQUAL(prefixMap.at("fm."), LogLevel::INFO);
+  logFromModule1();
+  logFromFilterModule();
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n" +
+    LOG_SYSTIME_STR + " INFO: [fm.FilterModule] infoFM\n" +
+    LOG_SYSTIME_STR + " WARNING: [fm.FilterModule] warnFM\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.FilterModule] errorFM\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.FilterModule] fatalFM\n"
+    ));
+}
+
+BOOST_AUTO_TEST_CASE(FindPrefixRule)
+{
+  Logging::setLevel("*=FATAL:fm.a.*=ERROR:fm.a.b=INFO");
+  logFromNewLogger("fm.a.b");
+  logFromNewLogger("fm.a.b.c");
+  logFromNewLogger("fm.a.b.d");
+  logFromNewLogger("fm.b");
+
+  Logging::flush();
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " INFO: [fm.a.b] infofm.a.b\n" +
+    LOG_SYSTIME_STR + " WARNING: [fm.a.b] warnfm.a.b\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.a.b] errorfm.a.b\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.a.b] fatalfm.a.b\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.a.b.c] errorfm.a.b.c\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.a.b.c] fatalfm.a.b.c\n" +
+    LOG_SYSTIME_STR + " ERROR: [fm.a.b.d] errorfm.a.b.d\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.a.b.d] fatalfm.a.b.d\n" +
+    LOG_SYSTIME_STR + " FATAL: [fm.b] fatalfm.b\n"
+    ));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // SeverityConfig
+
+BOOST_AUTO_TEST_CASE(ChangeDestination)
+{
+  using boost::test_tools::output_test_stream;
+
+  logFromModule1();
+
+  auto os2 = make_shared<output_test_stream>();
+  Logging::setDestination(os2);
+  weak_ptr<output_test_stream> os2weak(os2);
+  os2.reset();
+
+  logFromModule2();
+
+  Logging::flush();
+  os2 = os2weak.lock();
+  BOOST_REQUIRE(os2 != nullptr);
+
+  BOOST_CHECK(os.is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module1] fatal1\n"
+    ));
+  BOOST_CHECK(os2->is_equal(
+    LOG_SYSTIME_STR + " FATAL: [Module2] fatal2\n"
+    ));
+
+  os2.reset();
+  Logging::setDestination(os);
+  BOOST_CHECK(os2weak.expired());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestLogging
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/notification-stream.t.cpp b/tests/unit/util/notification-stream.t.cpp
new file mode 100644
index 0000000..3721091
--- /dev/null
+++ b/tests/unit/util/notification-stream.t.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/notification-stream.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "simple-notification.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestNotificationStream, ndn::tests::IdentityManagementTimeFixture)
+
+BOOST_AUTO_TEST_CASE(Post)
+{
+  DummyClientFace face(io, m_keyChain);
+  util::NotificationStream<SimpleNotification> notificationStream(face,
+    "/localhost/nfd/NotificationStreamTest", m_keyChain);
+
+  SimpleNotification event1("msg1");
+  notificationStream.postNotification(event1);
+
+  advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData[0].getName(), "/localhost/nfd/NotificationStreamTest/%FE%00");
+  SimpleNotification decoded1;
+  BOOST_CHECK_NO_THROW(decoded1.wireDecode(face.sentData[0].getContent().blockFromValue()));
+  BOOST_CHECK_EQUAL(decoded1.getMessage(), "msg1");
+
+  SimpleNotification event2("msg2");
+  notificationStream.postNotification(event2);
+
+  advanceClocks(1_ms);
+
+  BOOST_REQUIRE_EQUAL(face.sentData.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentData[1].getName(), "/localhost/nfd/NotificationStreamTest/%FE%01");
+  SimpleNotification decoded2;
+  BOOST_CHECK_NO_THROW(decoded2.wireDecode(face.sentData[1].getContent().blockFromValue()));
+  BOOST_CHECK_EQUAL(decoded2.getMessage(), "msg2");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNotificationStream
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/notification-subscriber.t.cpp b/tests/unit/util/notification-subscriber.t.cpp
new file mode 100644
index 0000000..3fe3fd6
--- /dev/null
+++ b/tests/unit/util/notification-subscriber.t.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/notification-subscriber.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "make-interest-data.hpp"
+#include "simple-notification.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using namespace ndn::tests;
+
+class NotificationSubscriberFixture : public IdentityManagementTimeFixture
+{
+public:
+  NotificationSubscriberFixture()
+    : streamPrefix("ndn:/NotificationSubscriberTest")
+    , subscriberFace(io, m_keyChain)
+    , subscriber(subscriberFace, streamPrefix, 1_s)
+    , nextSendNotificationNo(0)
+  {
+  }
+
+  /** \brief deliver one notification to subscriber
+   */
+  void
+  deliverNotification(const std::string& msg)
+  {
+    SimpleNotification notification(msg);
+
+    Name dataName = streamPrefix;
+    dataName.appendSequenceNumber(nextSendNotificationNo);
+    Data data(dataName);
+    data.setContent(notification.wireEncode());
+    data.setFreshnessPeriod(1_s);
+    m_keyChain.sign(data);
+
+    lastDeliveredSeqNo = nextSendNotificationNo;
+    lastNotification.setMessage("");
+    ++nextSendNotificationNo;
+    subscriberFace.receive(data);
+  }
+
+  /** \brief deliver a Nack to subscriber
+   */
+  void
+  deliverNack(const Interest& interest, const lp::NackReason& reason)
+  {
+    lp::Nack nack = makeNack(interest, reason);
+    subscriberFace.receive(nack);
+  }
+
+  void
+  afterNotification(const SimpleNotification& notification)
+  {
+    lastNotification = notification;
+  }
+
+  void
+  afterNack(const lp::Nack& nack)
+  {
+    lastNack = nack;
+  }
+
+  void
+  afterTimeout()
+  {
+    hasTimeout = true;
+  }
+
+  void
+  afterDecodeError(const Data& data)
+  {
+    lastDecodeErrorData = data;
+  }
+
+  void
+  connectHandlers()
+  {
+    notificationConn = subscriber.onNotification.connect(
+      bind(&NotificationSubscriberFixture::afterNotification, this, _1));
+    nackConn = subscriber.onNack.connect(
+      bind(&NotificationSubscriberFixture::afterNack, this, _1));
+    subscriber.onTimeout.connect(
+      bind(&NotificationSubscriberFixture::afterTimeout, this));
+    subscriber.onDecodeError.connect(
+      bind(&NotificationSubscriberFixture::afterDecodeError, this, _1));
+  }
+
+  void
+  disconnectHandlers()
+  {
+    notificationConn.disconnect();
+    nackConn.disconnect();
+  }
+
+  /** \return true if subscriberFace has an initial request (first sent Interest)
+   */
+  bool
+  hasInitialRequest() const
+  {
+    if (subscriberFace.sentInterests.empty())
+      return false;
+
+    const Interest& interest = subscriberFace.sentInterests[0];
+    return interest.getName() == streamPrefix &&
+           interest.getChildSelector() == 1 &&
+           interest.getMustBeFresh() &&
+           interest.getInterestLifetime() == subscriber.getInterestLifetime();
+  }
+
+  /** \return sequence number of the continuation request sent from subscriberFace
+   *          or 0 if there's no such request as sole sent Interest
+   */
+  uint64_t
+  getRequestSeqNo() const
+  {
+    if (subscriberFace.sentInterests.size() != 1)
+      return 0;
+
+    const Interest& interest = subscriberFace.sentInterests[0];
+    const Name& name = interest.getName();
+    if (streamPrefix.isPrefixOf(name) &&
+        name.size() == streamPrefix.size() + 1 &&
+        interest.getInterestLifetime() == subscriber.getInterestLifetime())
+      return name[-1].toSequenceNumber();
+    else
+      return 0;
+  }
+
+protected:
+  Name streamPrefix;
+  DummyClientFace subscriberFace;
+  util::NotificationSubscriber<SimpleNotification> subscriber;
+  util::signal::Connection notificationConn;
+  util::signal::Connection nackConn;
+  uint64_t nextSendNotificationNo;
+  uint64_t lastDeliveredSeqNo;
+  SimpleNotification lastNotification;
+  lp::Nack lastNack;
+  bool hasTimeout;
+  Data lastDecodeErrorData;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestNotificationSubscriber, NotificationSubscriberFixture)
+
+BOOST_AUTO_TEST_CASE(StartStop)
+{
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), false);
+
+  // has no effect because onNotification has no handler
+  subscriber.start();
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), false);
+
+  this->connectHandlers();
+  subscriber.start();
+  BOOST_REQUIRE_EQUAL(subscriber.isRunning(), true);
+  advanceClocks(1_ms);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  subscriberFace.sentInterests.clear();
+  this->disconnectHandlers();
+  this->deliverNotification("n1");
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Notifications)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  // respond to initial request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n1");
+  BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
+
+  // respond to continuation request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n2");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n2");
+  BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
+}
+
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  // send the first Nack to initial request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  Interest interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNack.getReason(), lp::NackReason::CONGESTION);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(300_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+
+  // send the second Nack to initial request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(301_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(200_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+
+  // send a notification to initial request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(1_ms);
+
+  // send a Nack to subsequent request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNack.getReason(), lp::NackReason::CONGESTION);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(300_ms);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+}
+
+BOOST_AUTO_TEST_CASE(Timeout)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  subscriberFace.sentInterests.clear();
+  lastNotification.setMessage("");
+  advanceClocks(subscriber.getInterestLifetime(), 2);
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(hasTimeout, true);
+  BOOST_CHECK(this->hasInitialRequest());
+
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(1_ms);
+  BOOST_CHECK_EQUAL(lastNotification.getMessage(), "n1");
+}
+
+BOOST_AUTO_TEST_CASE(SequenceError)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  Name wrongName = streamPrefix;
+  wrongName.append("%07%07");
+  Data wrongData(wrongName);
+  m_keyChain.sign(wrongData);
+  subscriberFace.receive(wrongData);
+  subscriberFace.sentInterests.clear();
+  lastNotification.setMessage("");
+  advanceClocks(1_ms);
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK_EQUAL(lastDecodeErrorData.getName(), wrongName);
+  BOOST_CHECK(this->hasInitialRequest());
+}
+
+BOOST_AUTO_TEST_CASE(PayloadError)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(1_ms);
+
+  subscriberFace.sentInterests.clear();
+  lastNotification.setMessage("");
+  this->deliverNotification("\x07n4");
+  advanceClocks(1_ms);
+  BOOST_CHECK(lastNotification.getMessage().empty());
+  BOOST_CHECK(this->hasInitialRequest());
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNotificationSubscriber
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/placeholders.t.cpp b/tests/unit/util/placeholders.t.cpp
new file mode 100644
index 0000000..0dc8c70
--- /dev/null
+++ b/tests/unit/util/placeholders.t.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+// Bug 2109 test case
+
+// interest.hpp includes common.hpp; common.hpp shouldn't be used from external program
+#include "interest.hpp"
+
+// util/config-file.hpp indirectly includes <boost/property_tree/ptree.hpp>
+// which in turn imports Boost placeholders
+#include "util/config-file.hpp"
+
+// It's intentional to write "using namespace",
+// to simulate an external program linked against ndn-cxx.
+using namespace ndn;
+
+void
+placeholdersTestFunction(int i)
+{
+}
+
+int
+placeholdersTestMain()
+{
+  auto f = std::bind(&placeholdersTestFunction, _1);
+  f(1);
+  return 0;
+}
diff --git a/tests/unit/util/placeholders2.t.cpp b/tests/unit/util/placeholders2.t.cpp
new file mode 100644
index 0000000..187daa9
--- /dev/null
+++ b/tests/unit/util/placeholders2.t.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+// Bug 2109 test case
+
+// interest.hpp includes common.hpp; common.hpp shouldn't be used from external program
+#include "interest.hpp"
+
+#include <boost/bind.hpp>
+
+void
+placeholders2TestFunction(int i)
+{
+}
+
+int
+placeholders2TestMain()
+{
+  auto f = boost::bind(&placeholders2TestFunction, _1);
+  f(1);
+  return 0;
+}
diff --git a/tests/unit/util/random.t.cpp b/tests/unit/util/random.t.cpp
new file mode 100644
index 0000000..e830f7b
--- /dev/null
+++ b/tests/unit/util/random.t.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/random.hpp"
+#include "security/detail/openssl.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/mpl/vector.hpp>
+#include <cmath>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestRandom)
+
+class PseudoRandomWord32
+{
+public:
+  static uint32_t
+  generate()
+  {
+    return random::generateWord32();
+  }
+};
+
+class PseudoRandomWord64
+{
+public:
+  static uint64_t
+  generate()
+  {
+    return random::generateWord64();
+  }
+};
+
+class SecureRandomWord32
+{
+public:
+  static uint32_t
+  generate()
+  {
+    return random::generateSecureWord32();
+  }
+};
+
+class SecureRandomWord64
+{
+public:
+  static uint64_t
+  generate()
+  {
+    return random::generateSecureWord64();
+  }
+};
+
+typedef boost::mpl::vector<PseudoRandomWord32,
+                           PseudoRandomWord64,
+                           SecureRandomWord32,
+                           SecureRandomWord64> RandomGenerators;
+
+
+static double
+getDeviation(const std::vector<uint32_t>& counts, size_t size)
+{
+  // Kolmogorov-Smirnov Goodness-of-Fit Test
+  // http://www.itl.nist.gov/div898/handbook/eda/section3/eda35g.htm
+
+  std::vector<double> edf(counts.size(), 0.0);
+  double probability = 0.0;
+  for (size_t i = 0; i < counts.size(); i++) {
+    probability += 1.0 * counts[i] / size;
+    edf[i] = probability;
+  }
+
+  double t = 0.0;
+  for (size_t i = 0; i < counts.size(); i++) {
+    t = std::max(t, std::abs(edf[i] - (i * 1.0 / counts.size())));
+  }
+
+  return t;
+}
+
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(GoodnessOfFit, RandomGenerator, RandomGenerators)
+{
+  const size_t MAX_BINS = 32;
+  const uint32_t MAX_ITERATIONS = 35;
+
+  std::vector<uint32_t> counts(MAX_BINS, 0);
+
+  for (uint32_t i = 0; i < MAX_ITERATIONS; i++) {
+    counts[RandomGenerator::generate() % MAX_BINS]++;
+  }
+
+  // Check if it is uniform distribution with confidence 0.95
+  // http://dlc.erieri.com/onlinetextbook/index.cfm?fuseaction=textbook.appendix&FileName=Table7
+  BOOST_WARN_LE(getDeviation(counts, MAX_ITERATIONS), 0.230);
+}
+
+BOOST_AUTO_TEST_CASE(GenerateRandomBytes)
+{
+  // Kolmogorov-Smirnov Goodness-of-Fit Test
+  // http://www.itl.nist.gov/div898/handbook/eda/section3/eda35g.htm
+
+  uint8_t buf[1024] = {0};
+  random::generateSecureBytes(buf, sizeof(buf));
+
+  std::vector<uint32_t> counts(256, 0);
+
+  for (size_t i = 0; i < sizeof(buf); i++) {
+    counts[buf[i]]++;
+  }
+
+  // Check if it is uniform distribution with confidence 0.95
+  // http://dlc.erieri.com/onlinetextbook/index.cfm?fuseaction=textbook.appendix&FileName=Table7
+  BOOST_WARN_LE(getDeviation(counts, sizeof(buf)), 0.230);
+}
+
+// This fixture uses OpenSSL routines to set a dummy random generator that always fails
+class FailRandMethodFixture
+{
+public:
+  FailRandMethodFixture()
+    : m_dummyRandMethod{&FailRandMethodFixture::seed,
+                        &FailRandMethodFixture::bytes,
+                        &FailRandMethodFixture::cleanup,
+                        &FailRandMethodFixture::add,
+                        &FailRandMethodFixture::pseudorand,
+                        &FailRandMethodFixture::status}
+  {
+    m_origRandMethod = RAND_get_rand_method();
+    RAND_set_rand_method(&m_dummyRandMethod);
+  }
+
+  ~FailRandMethodFixture()
+  {
+    RAND_set_rand_method(m_origRandMethod);
+  }
+
+private: // RAND_METHOD callbacks
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL
+  static void
+  seed(const void* buf, int num)
+  {
+  }
+#else
+  static int
+  seed(const void* buf, int num)
+  {
+    return 0;
+  }
+#endif // OPENSSL_VERSION_NUMBER < 0x1010000fL
+
+  static int
+  bytes(unsigned char* buf, int num)
+  {
+    return 0;
+  }
+
+  static void
+  cleanup()
+  {
+  }
+
+#if OPENSSL_VERSION_NUMBER < 0x1010000fL
+  static void
+  add(const void* buf, int num, double entropy)
+  {
+  }
+#else
+  static int
+  add(const void* buf, int num, double entropy)
+  {
+    return 0;
+  }
+#endif // OPENSSL_VERSION_NUMBER < 0x1010000fL
+
+  static int
+  pseudorand(unsigned char* buf, int num)
+  {
+    return 0;
+  }
+
+  static int
+  status()
+  {
+    return 0;
+  }
+
+private:
+  const RAND_METHOD* m_origRandMethod;
+  RAND_METHOD m_dummyRandMethod;
+};
+
+BOOST_FIXTURE_TEST_CASE(Error, FailRandMethodFixture)
+{
+  uint8_t buf[1024] = {0};
+  BOOST_CHECK_THROW(random::generateSecureBytes(buf, sizeof(buf)), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRandom
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/regex.t.cpp b/tests/unit/util/regex.t.cpp
new file mode 100644
index 0000000..777defb
--- /dev/null
+++ b/tests/unit/util/regex.t.cpp
@@ -0,0 +1,463 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>
+ */
+
+#include "util/regex.hpp"
+#include "util/regex/regex-backref-manager.hpp"
+#include "util/regex/regex-backref-matcher.hpp"
+#include "util/regex/regex-component-matcher.hpp"
+#include "util/regex/regex-component-set-matcher.hpp"
+#include "util/regex/regex-pattern-list-matcher.hpp"
+#include "util/regex/regex-repeat-matcher.hpp"
+#include "util/regex/regex-top-matcher.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+using std::string;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestRegex)
+
+BOOST_AUTO_TEST_CASE(ComponentMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexComponentMatcher> cm = make_shared<RegexComponentMatcher>("a", backRef);
+  bool res = cm->match(Name("/a/b/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentMatcher>("a", backRef);
+  res = cm->match(Name("/a/b/"), 1, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentMatcher>("(c+)\\.(cd)", backRef);
+  res = cm->match(Name("/ccc.cd/b/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("ccc.cd"));
+
+  BOOST_REQUIRE_EQUAL(backRef->size(), 2);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("ccc"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(1)->getMatchResult()[0].toUri(), string("cd"));
+}
+
+BOOST_AUTO_TEST_CASE(ComponentSetMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexComponentSetMatcher> cm = make_shared<RegexComponentSetMatcher>("<a>", backRef);
+  bool res = cm->match(Name("/a/b/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/"), 1, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentSetMatcher>("[<a><b><c>]", backRef);
+  res = cm->match(Name("/a/b/d"), 1, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/d"), 2, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexComponentSetMatcher>("[^<a><b><c>]", backRef);
+  res = cm->match(Name("/b/d"), 1, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("d"));
+}
+
+BOOST_AUTO_TEST_CASE(RepeatMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexRepeatMatcher> cm = make_shared<RegexRepeatMatcher>("[<a><b>]*", backRef, 8);
+  bool res = cm->match(Name("/a/b/c"), 0, 0);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]+", backRef, 8);
+  res = cm->match(Name("/a/b/c"), 0, 0);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("<.*>*", backRef, 4);
+  res = cm->match(Name("/a/b/c/d/e/f/"), 0, 6);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[4].toUri(), string("e"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[5].toUri(), string("f"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("<>*", backRef, 2);
+  res = cm->match(Name("/a/b/c/d/e/f/"), 0, 6);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[4].toUri(), string("e"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[5].toUri(), string("f"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("<a>?", backRef, 3);
+  res = cm->match(Name("/a/b/c"), 0, 0);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  cm = make_shared<RegexRepeatMatcher>("<a>?", backRef, 3);
+  res = cm->match(Name("/a/b/c"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  cm = make_shared<RegexRepeatMatcher>("<a>?", backRef, 3);
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{3}", backRef, 8);
+  res = cm->match(Name("/a/b/a/d/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/a/d/"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/a/d/"), 0, 4);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{2,3}", backRef, 8);
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 4);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{2,}", backRef, 8);
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 4);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexRepeatMatcher>("[<a><b>]{,2}", backRef, 8);
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 3);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  res = cm->match(Name("/a/b/a/b/e/"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  res = cm->match(Name("/a/b/a/d/e/"), 0, 0);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(BackRefMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexBackrefMatcher> cm = make_shared<RegexBackrefMatcher>("(<a><b>)", backRef);
+  backRef->pushRef(static_pointer_cast<RegexMatcher>(cm));
+  cm->lateCompile();
+  bool res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->size(), 1);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexBackrefMatcher>("(<a>(<b>))", backRef);
+  backRef->pushRef(cm);
+  cm->lateCompile();
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->size(), 2);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(1)->getMatchResult()[0].toUri(), string("b"));
+}
+
+BOOST_AUTO_TEST_CASE(BackRefMatcherAdvanced)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexRepeatMatcher> cm = make_shared<RegexRepeatMatcher>("([<a><b>])+", backRef, 10);
+  bool res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->size(), 1);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("b"));
+}
+
+BOOST_AUTO_TEST_CASE(BackRefMatcherAdvanced2)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexPatternListMatcher> cm = make_shared<RegexPatternListMatcher>("(<a>(<b>))<c>", backRef);
+  bool res = cm->match(Name("/a/b/c"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(backRef->size(), 2);
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(0)->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(backRef->getBackref(1)->getMatchResult()[0].toUri(), string("b"));
+}
+
+BOOST_AUTO_TEST_CASE(PatternListMatcher)
+{
+  shared_ptr<RegexBackrefManager> backRef = make_shared<RegexBackrefManager>();
+  shared_ptr<RegexPatternListMatcher> cm = make_shared<RegexPatternListMatcher>("<a>[<a><b>]", backRef);
+  bool res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 2);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexPatternListMatcher>("<>*<a>", backRef);
+  res = cm->match(Name("/a/b/c"), 0, 1);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 1);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexPatternListMatcher>("<>*<a>", backRef);
+  res = cm->match(Name("/a/b/c"), 0, 2);
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  backRef = make_shared<RegexBackrefManager>();
+  cm = make_shared<RegexPatternListMatcher>("<>*<a><>*", backRef);
+  res = cm->match(Name("/a/b/c"), 0, 3);
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 3);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+}
+
+BOOST_AUTO_TEST_CASE(TopMatcher)
+{
+  shared_ptr<RegexTopMatcher> cm = make_shared<RegexTopMatcher>("^<a><b><c>");
+  bool res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  cm = make_shared<RegexTopMatcher>("<b><c><d>$");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  cm = make_shared<RegexTopMatcher>("^<a><b><c><d>$");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  res = cm->match(Name("/a/b/c/d/e"));
+  BOOST_CHECK_EQUAL(res, false);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 0);
+
+  cm = make_shared<RegexTopMatcher>("<a><b><c><d>");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+
+  cm = make_shared<RegexTopMatcher>("<b><c>");
+  res = cm->match(Name("/a/b/c/d"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[0].toUri(), string("a"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[1].toUri(), string("b"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[2].toUri(), string("c"));
+  BOOST_CHECK_EQUAL(cm->getMatchResult()[3].toUri(), string("d"));
+}
+
+BOOST_AUTO_TEST_CASE(TopMatcherAdvanced)
+{
+  shared_ptr<Regex> cm = make_shared<Regex>("^(<.*>*)<.*>");
+  bool res = cm->match(Name("/n/a/b/c"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/n/a/b/"));
+
+  cm = make_shared<Regex>("^(<.*>*)<.*><c>(<.*>)<.*>");
+  res = cm->match(Name("/n/a/b/c/d/e/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->expand("\\1\\2"), Name("/n/a/d/"));
+
+  cm = make_shared<Regex>("(<.*>*)<.*>$");
+  res = cm->match(Name("/n/a/b/c/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/n/a/b/"));
+
+  cm = make_shared<Regex>("<.*>(<.*>*)<.*>$");
+  res = cm->match(Name("/n/a/b/c/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/a/b/"));
+
+  cm = make_shared<Regex>("<a>(<>*)<>$");
+  res = cm->match(Name("/n/a/b/c/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 4);
+  BOOST_CHECK_EQUAL(cm->expand("\\1"), Name("/b/"));
+
+  cm = make_shared<Regex>("^<ndn><(.*)\\.(.*)><DNS>(<>*)<>");
+  res = cm->match(Name("/ndn/ucla.edu/DNS/yingdi/mac/ksk-1/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->expand("<ndn>\\2\\1\\3"), Name("/ndn/edu/ucla/yingdi/mac/"));
+
+  cm = make_shared<Regex>("^<ndn><(.*)\\.(.*)><DNS>(<>*)<>", "<ndn>\\2\\1\\3");
+  res = cm->match(Name("/ndn/ucla.edu/DNS/yingdi/mac/ksk-1/"));
+  BOOST_CHECK_EQUAL(res, true);
+  BOOST_CHECK_EQUAL(cm->getMatchResult().size(), 6);
+  BOOST_CHECK_EQUAL(cm->expand(), Name("/ndn/edu/ucla/yingdi/mac/"));
+}
+
+BOOST_AUTO_TEST_CASE(RegexBackrefManagerMemoryLeak)
+{
+  auto re = make_unique<Regex>("^(<>)$");
+
+  weak_ptr<RegexPatternListMatcher> m1(re->m_primaryMatcher);
+  weak_ptr<RegexPatternListMatcher> m2(re->m_secondaryMatcher);
+  weak_ptr<RegexBackrefManager> b1(re->m_primaryBackrefManager);
+  weak_ptr<RegexBackrefManager> b2(re->m_secondaryBackrefManager);
+
+  re.reset();
+
+  BOOST_CHECK_EQUAL(m1.use_count(), 0);
+  BOOST_CHECK_EQUAL(m2.use_count(), 0);
+  BOOST_CHECK_EQUAL(b1.use_count(), 0);
+  BOOST_CHECK_EQUAL(b2.use_count(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestRegex
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/scheduler.t.cpp b/tests/unit/util/scheduler.t.cpp
new file mode 100644
index 0000000..05380f3
--- /dev/null
+++ b/tests/unit/util/scheduler.t.cpp
@@ -0,0 +1,417 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/scheduler.hpp"
+#include "util/scheduler-scoped-event-id.hpp"
+
+#include "boost-test.hpp"
+#include "../unit-test-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace util {
+namespace scheduler {
+namespace tests {
+
+using namespace ndn::tests;
+
+class SchedulerFixture : public UnitTestTimeFixture
+{
+public:
+  SchedulerFixture()
+    : scheduler(io)
+  {
+  }
+
+public:
+  Scheduler scheduler;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestScheduler, SchedulerFixture)
+
+BOOST_AUTO_TEST_SUITE(General)
+
+BOOST_AUTO_TEST_CASE(Events)
+{
+  size_t count1 = 0;
+  size_t count2 = 0;
+
+  scheduler.scheduleEvent(500_ms, [&] {
+      ++count1;
+      BOOST_CHECK_EQUAL(count2, 1);
+    });
+
+  EventId i = scheduler.scheduleEvent(1_s, [&] {
+      BOOST_ERROR("This event should not have been fired");
+    });
+  scheduler.cancelEvent(i);
+
+  scheduler.scheduleEvent(250_ms, [&] {
+      BOOST_CHECK_EQUAL(count1, 0);
+      ++count2;
+    });
+
+  i = scheduler.scheduleEvent(50_ms, [&] {
+      BOOST_ERROR("This event should not have been fired");
+    });
+  scheduler.cancelEvent(i);
+
+  advanceClocks(25_ms, 1000_ms);
+  BOOST_CHECK_EQUAL(count1, 1);
+  BOOST_CHECK_EQUAL(count2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(CallbackException)
+{
+  class MyException : public std::exception
+  {
+  };
+  scheduler.scheduleEvent(10_ms, [] { BOOST_THROW_EXCEPTION(MyException()); });
+
+  bool isCallbackInvoked = false;
+  scheduler.scheduleEvent(20_ms, [&isCallbackInvoked] { isCallbackInvoked = true; });
+
+  BOOST_CHECK_THROW(this->advanceClocks(6_ms, 2), MyException);
+  this->advanceClocks(6_ms, 2);
+  BOOST_CHECK(isCallbackInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(CancelEmptyEvent)
+{
+  EventId i;
+  scheduler.cancelEvent(i);
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_CASE(SelfCancel)
+{
+  EventId selfEventId;
+  selfEventId = scheduler.scheduleEvent(100_ms, [&] {
+      scheduler.cancelEvent(selfEventId);
+    });
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(100_ms, 10));
+}
+
+class SelfRescheduleFixture : public SchedulerFixture
+{
+public:
+  SelfRescheduleFixture()
+    : count(0)
+  {
+  }
+
+  void
+  reschedule()
+  {
+    EventId eventId = scheduler.scheduleEvent(100_ms,
+                                              bind(&SelfRescheduleFixture::reschedule, this));
+    scheduler.cancelEvent(selfEventId);
+    selfEventId = eventId;
+
+    if (count < 5)
+      count++;
+    else
+      scheduler.cancelEvent(selfEventId);
+  }
+
+  void
+  reschedule2()
+  {
+    scheduler.cancelEvent(selfEventId);
+
+    if (count < 5)  {
+      selfEventId = scheduler.scheduleEvent(100_ms,
+                                            bind(&SelfRescheduleFixture::reschedule2, this));
+      count++;
+    }
+  }
+
+  void
+  reschedule3()
+  {
+    scheduler.cancelEvent(selfEventId);
+
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+    scheduler.scheduleEvent(100_ms, [&] { ++count; });
+  }
+
+public:
+  EventId selfEventId;
+  size_t count;
+};
+
+BOOST_FIXTURE_TEST_CASE(Reschedule, SelfRescheduleFixture)
+{
+  selfEventId = scheduler.scheduleEvent(0_s,
+                                        bind(&SelfRescheduleFixture::reschedule, this));
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(50_ms, 1000_ms));
+
+  BOOST_CHECK_EQUAL(count, 5);
+}
+
+BOOST_FIXTURE_TEST_CASE(Reschedule2, SelfRescheduleFixture)
+{
+  selfEventId = scheduler.scheduleEvent(0_s,
+                                        bind(&SelfRescheduleFixture::reschedule2, this));
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(50_ms, 1000_ms));
+
+  BOOST_CHECK_EQUAL(count, 5);
+}
+
+BOOST_FIXTURE_TEST_CASE(Reschedule3, SelfRescheduleFixture)
+{
+  selfEventId = scheduler.scheduleEvent(0_s,
+                                        bind(&SelfRescheduleFixture::reschedule3, this));
+
+  BOOST_REQUIRE_NO_THROW(advanceClocks(50_ms, 1000_ms));
+
+  BOOST_CHECK_EQUAL(count, 6);
+}
+
+class CancelAllFixture : public SchedulerFixture
+{
+public:
+  CancelAllFixture()
+    : count(0)
+  {
+  }
+
+  void
+  event()
+  {
+    ++count;
+
+    scheduler.scheduleEvent(1_s, [&] { event(); });
+  }
+
+public:
+  uint32_t count;
+};
+
+BOOST_FIXTURE_TEST_CASE(CancelAll, CancelAllFixture)
+{
+  scheduler.scheduleEvent(500_ms, [&] { scheduler.cancelAllEvents(); });
+
+  scheduler.scheduleEvent(1_s, [&] { event(); });
+
+  scheduler.scheduleEvent(3_s, [] {
+      BOOST_ERROR("This event should have been cancelled" );
+    });
+
+  advanceClocks(100_ms, 100);
+
+  BOOST_CHECK_EQUAL(count, 0);
+}
+
+BOOST_AUTO_TEST_CASE(CancelAllWithScopedEventId) // Bug 3691
+{
+  Scheduler sched(io);
+  ScopedEventId eid(sched);
+  eid = sched.scheduleEvent(10_ms, []{});
+  sched.cancelAllEvents();
+  eid.cancel(); // should not crash
+
+  // avoid "test case [...] did not check any assertions" message from Boost.Test
+  BOOST_CHECK(true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // General
+
+BOOST_AUTO_TEST_SUITE(EventId)
+
+using scheduler::EventId;
+
+BOOST_AUTO_TEST_CASE(ConstructEmpty)
+{
+  EventId eid;
+  eid = nullptr;
+  EventId eid2(nullptr);
+
+  BOOST_CHECK(!eid && !eid2);
+}
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  EventId eid, eid2;
+  BOOST_CHECK_EQUAL(eid == eid2, true);
+  BOOST_CHECK_EQUAL(eid != eid2, false);
+  BOOST_CHECK_EQUAL(eid == nullptr, true);
+  BOOST_CHECK_EQUAL(eid != nullptr, false);
+
+  eid = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_CHECK_EQUAL(eid == eid2, false);
+  BOOST_CHECK_EQUAL(eid != eid2, true);
+  BOOST_CHECK_EQUAL(eid == nullptr, false);
+  BOOST_CHECK_EQUAL(eid != nullptr, true);
+
+  eid2 = eid;
+  BOOST_CHECK_EQUAL(eid, eid2);
+  BOOST_CHECK_EQUAL(eid != eid2, false);
+
+  eid2 = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_CHECK_EQUAL(eid == eid2, false);
+  BOOST_CHECK_NE(eid, eid2);
+}
+
+BOOST_AUTO_TEST_CASE(Valid)
+{
+  EventId eid;
+  BOOST_CHECK_EQUAL(static_cast<bool>(eid), false);
+  BOOST_CHECK_EQUAL(!eid, true);
+
+  eid = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_CHECK_EQUAL(static_cast<bool>(eid), true);
+  BOOST_CHECK_EQUAL(!eid, false);
+
+  EventId eid2 = eid;
+  scheduler.cancelEvent(eid2);
+  BOOST_CHECK(!eid);
+  BOOST_CHECK(!eid2);
+}
+
+BOOST_AUTO_TEST_CASE(DuringCallback)
+{
+  EventId eid;
+  EventId eid2 = scheduler.scheduleEvent(20_ms, []{});
+
+  bool isCallbackInvoked = false;
+  eid = scheduler.scheduleEvent(10_ms, [this, &eid, &eid2, &isCallbackInvoked] {
+    isCallbackInvoked = true;
+
+    // eid is "expired" during callback execution
+    BOOST_CHECK(!eid);
+    BOOST_CHECK(eid == nullptr);
+    BOOST_CHECK_NE(eid, eid2);
+
+    scheduler.cancelEvent(eid2);
+    BOOST_CHECK_EQUAL(eid, eid2);
+  });
+
+  this->advanceClocks(6_ms, 2);
+  BOOST_CHECK(isCallbackInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(Reset)
+{
+  bool isCallbackInvoked = false;
+  EventId eid = scheduler.scheduleEvent(10_ms, [&isCallbackInvoked] { isCallbackInvoked = true; });
+  eid.reset();
+  BOOST_CHECK(!eid);
+  BOOST_CHECK(eid == nullptr);
+
+  this->advanceClocks(6_ms, 2);
+  BOOST_CHECK(isCallbackInvoked);
+}
+
+BOOST_AUTO_TEST_CASE(ToString)
+{
+  std::string nullString = boost::lexical_cast<std::string>(shared_ptr<int>());
+
+  EventId eid;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(eid), nullString);
+
+  eid = scheduler.scheduleEvent(10_ms, []{});
+  BOOST_TEST_MESSAGE("eid=" << eid);
+  BOOST_CHECK_NE(boost::lexical_cast<std::string>(eid), nullString);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // EventId
+
+BOOST_AUTO_TEST_SUITE(ScopedEventId)
+
+using scheduler::ScopedEventId;
+
+BOOST_AUTO_TEST_CASE(Destruct)
+{
+  int hit = 0;
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 0);
+}
+
+BOOST_AUTO_TEST_CASE(Assign)
+{
+  int hit1 = 0, hit2 = 0;
+  ScopedEventId se1(scheduler);
+  se1 = scheduler.scheduleEvent(10_ms, [&] { ++hit1; });
+  se1 = scheduler.scheduleEvent(10_ms, [&] { ++hit2; });
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit1, 0);
+  BOOST_CHECK_EQUAL(hit2, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Release)
+{
+  int hit = 0;
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+    se.release();
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Move)
+{
+  int hit = 0;
+  unique_ptr<ScopedEventId> se2;
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+    se2 = make_unique<ScopedEventId>(std::move(se)); // move constructor
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 1);
+
+  ScopedEventId se3(scheduler);
+  {
+    ScopedEventId se(scheduler);
+    se = scheduler.scheduleEvent(10_ms, [&] { ++hit; });
+    se3 = std::move(se); // move assignment
+  } // se goes out of scope
+  this->advanceClocks(1_ms, 15);
+  BOOST_CHECK_EQUAL(hit, 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ScopedEventId
+
+BOOST_AUTO_TEST_SUITE_END() // TestScheduler
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace scheduler
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/segment-fetcher.t.cpp b/tests/unit/util/segment-fetcher.t.cpp
new file mode 100644
index 0000000..d52fa08
--- /dev/null
+++ b/tests/unit/util/segment-fetcher.t.cpp
@@ -0,0 +1,863 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/segment-fetcher.hpp"
+
+#include "data.hpp"
+#include "lp/nack.hpp"
+#include "util/dummy-client-face.hpp"
+
+#include "boost-test.hpp"
+#include "dummy-validator.hpp"
+#include "make-interest-data.hpp"
+#include "../identity-management-time-fixture.hpp"
+
+#include <set>
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+using namespace ndn::tests;
+
+class Fixture : public IdentityManagementTimeFixture
+{
+public:
+  Fixture()
+    : face(io, m_keyChain)
+  {
+  }
+
+  static shared_ptr<Data>
+  makeDataSegment(const Name& baseName, uint64_t segment, bool isFinal)
+  {
+    const uint8_t buffer[] = "Hello, world!";
+
+    auto data = make_shared<Data>(Name(baseName).appendSegment(segment));
+    data->setContent(buffer, sizeof(buffer));
+    if (isFinal) {
+      data->setFinalBlock(data->getName()[-1]);
+    }
+
+    return signData(data);
+  }
+
+  void
+  onError(uint32_t errorCode)
+  {
+    ++nErrors;
+    lastError = errorCode;
+  }
+
+  void
+  onComplete(ConstBufferPtr data)
+  {
+    ++nCompletions;
+    dataSize = data->size();
+    dataBuf = data;
+  }
+
+  void
+  nackLastInterest(lp::NackReason nackReason)
+  {
+    const Interest& lastInterest = face.sentInterests.back();
+    lp::Nack nack = makeNack(lastInterest, nackReason);
+    face.receive(nack);
+    advanceClocks(10_ms);
+  }
+
+  void
+  connectSignals(const shared_ptr<SegmentFetcher>& fetcher)
+  {
+    fetcher->onComplete.connect(bind(&Fixture::onComplete, this, _1));
+    fetcher->onError.connect(bind(&Fixture::onError, this, _1));
+  }
+
+  void
+  onInterest(const Interest& interest)
+  {
+    if (interest.getName().get(-1).isSegment()) {
+      if (segmentsToDropOrNack.size() > 0 &&
+          interest.getName().get(-1).toSegment() == segmentsToDropOrNack.front()) {
+        segmentsToDropOrNack.pop();
+        if (sendNackInsteadOfDropping) {
+          lp::Nack nack = makeNack(interest, nackReason);
+          face.receive(nack);
+        }
+        return;
+      }
+
+      auto data = makeDataSegment("/hello/world/version0",
+                                  interest.getName().get(-1).toSegment(),
+                                  interest.getName().get(-1).toSegment() == nSegments - 1);
+      face.receive(*data);
+
+      uniqSegmentsSent.insert(interest.getName().get(-1).toSegment());
+      if (uniqSegmentsSent.size() == nSegments) {
+        io.stop();
+      }
+    }
+    else {
+      if (segmentsToDropOrNack.size() > 0 &&
+          segmentsToDropOrNack.front() == 0) {
+        segmentsToDropOrNack.pop();
+        if (sendNackInsteadOfDropping) {
+          lp::Nack nack = makeNack(interest, nackReason);
+          face.receive(nack);
+        }
+        return;
+      }
+
+      auto data = makeDataSegment("/hello/world/version0", defaultSegmentToSend, nSegments == 1);
+      face.receive(*data);
+      uniqSegmentsSent.insert(defaultSegmentToSend);
+    }
+  }
+
+public:
+  DummyClientFace face;
+  std::set<uint64_t> uniqSegmentsSent;
+
+  int nErrors = 0;
+  uint32_t lastError = 0;
+  int nCompletions = 0;
+  size_t dataSize = 0;
+  ConstBufferPtr dataBuf;
+
+  // number of segments in fetched object
+  uint64_t nSegments = 0;
+  std::queue<uint64_t> segmentsToDropOrNack;
+  bool sendNackInsteadOfDropping = false;
+  lp::NackReason nackReason = lp::NackReason::NONE;
+  // segment that is sent in response to an Interest w/o a segment component in its name
+  uint64_t defaultSegmentToSend = 0;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestSegmentFetcher, Fixture)
+
+BOOST_AUTO_TEST_CASE(InvalidOptions)
+{
+  SegmentFetcher::Options options;
+  options.mdCoef = 1.5;
+  DummyValidator acceptValidator;
+  BOOST_CHECK_THROW(SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator, options),
+                    std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(ExceedMaxTimeout)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentTimedOut = 0;
+  SegmentFetcher::Options options;
+  options.maxTimeout = 100_ms;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator, options);
+  connectSignals(fetcher);
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  advanceClocks(1_ms);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+
+  const Interest& interest = face.sentInterests[0];
+  BOOST_CHECK_EQUAL(interest.getName(), "/hello/world");
+  BOOST_CHECK_EQUAL(interest.getMustBeFresh(), true);
+  BOOST_CHECK_EQUAL(interest.getCanBePrefix(), true);
+
+  advanceClocks(98_ms);
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+
+  advanceClocks(1_ms, 2);
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::INTEREST_TIMEOUT));
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 1);
+}
+
+BOOST_AUTO_TEST_CASE(BasicSingleSegment)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  advanceClocks(10_ms);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(ConstantCwnd)
+{
+  SegmentFetcher::Options options;
+  options.useConstantCwnd = true;
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator, options);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 1);
+  face.receive(*makeDataSegment("/hello/world/version0", 1, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 3);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 2);
+  face.receive(*makeDataSegment("/hello/world/version0", 2, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 4);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 3);
+  face.receive(*makeDataSegment("/hello/world/version0", 3, false));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 5);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 4);
+  nackLastInterest(lp::NackReason::CONGESTION);
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 6);
+  BOOST_CHECK_EQUAL(face.sentInterests.back().getName().get(-1).toSegment(), 4);
+  face.receive(*makeDataSegment("/hello/world/version0", 4, true));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+}
+
+BOOST_AUTO_TEST_CASE(BasicMultipleSegments)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  sendNackInsteadOfDropping = false;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(FirstSegmentNotZero)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  sendNackInsteadOfDropping = false;
+  defaultSegmentToSend = 47;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(WindowSize)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+
+  advanceClocks(10_ms); // T+10ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, 1.0);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, std::numeric_limits<double>::max());
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, 1);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nReceived, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), 1);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+
+  double oldCwnd = fetcher->m_cwnd;
+  double oldSsthresh = fetcher->m_ssthresh;
+  uint64_t oldNextSegmentNum = fetcher->m_nextSegmentNum;
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, false));
+
+  advanceClocks(10_ms); //T+20ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  // +2 below because m_nextSegmentNum will be incremented in the receive callback if segment 0 is
+  // the first received
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 2);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 14);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 1);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1 + fetcher->m_cwnd);
+
+  oldCwnd = fetcher->m_cwnd;
+  oldNextSegmentNum = fetcher->m_nextSegmentNum;
+
+  face.receive(*makeDataSegment("/hello/world/version0", 2, false));
+
+  advanceClocks(10_ms); //T+30ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 1);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 28);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 2);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 2 + fetcher->m_cwnd);
+
+  oldCwnd = fetcher->m_cwnd;
+  oldNextSegmentNum = fetcher->m_nextSegmentNum;
+
+  face.receive(*makeDataSegment("/hello/world/version0", 1, false));
+
+  advanceClocks(10_ms); //T+40ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 10_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 0);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum + fetcher->m_options.aiStep + 1);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd + fetcher->m_options.aiStep);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldSsthresh);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), fetcher->m_cwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 3 + fetcher->m_cwnd);
+
+  oldCwnd = fetcher->m_cwnd;
+  oldSsthresh = fetcher->m_ssthresh;
+  oldNextSegmentNum = fetcher->m_nextSegmentNum;
+  size_t oldSentInterestsSize = face.sentInterests.size();
+
+  nackLastInterest(lp::NackReason::CONGESTION); //T+50ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 20_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 1);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3);
+  // The Nacked segment will remain in pendingSegments, so the size of the structure doesn't change
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), oldCwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), oldSentInterestsSize);
+
+  advanceClocks(10_ms); //T+60ms
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 30_ms);
+  BOOST_CHECK_EQUAL(fetcher->m_retxQueue.size(), 1);
+  BOOST_CHECK_EQUAL(fetcher->m_versionedDataName, "/hello/world/version0");
+  BOOST_CHECK_EQUAL(fetcher->m_nextSegmentNum, oldNextSegmentNum);
+  BOOST_CHECK_EQUAL(fetcher->m_cwnd, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_ssthresh, oldCwnd / 2.0);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegmentsInFlight, oldCwnd - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_nSegments, 0);
+  BOOST_CHECK_EQUAL(fetcher->m_nBytesReceived, 42);
+  BOOST_CHECK_EQUAL(fetcher->m_highInterest, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_highData, 2);
+  BOOST_CHECK_EQUAL(fetcher->m_recPoint, fetcher->m_nextSegmentNum - 1);
+  BOOST_CHECK_EQUAL(fetcher->m_receivedSegments.size(), 3);
+  BOOST_CHECK_EQUAL(fetcher->m_pendingSegments.size(), oldCwnd);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), oldSentInterestsSize);
+
+  // Properly end test case
+  lp::Nack nack = makeNack(face.sentInterests[face.sentInterests.size() - 2], lp::NackReason::NO_ROUTE);
+  face.receive(nack);
+  advanceClocks(10_ms); //T+70ms
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::NACK_ERROR));
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+}
+
+BOOST_AUTO_TEST_CASE(MissingSegmentNum)
+{
+  DummyValidator acceptValidator;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+
+  advanceClocks(10_ms);
+
+  const uint8_t buffer[] = "Hello, world!";
+  auto data = makeData("/hello/world/version0/no-segment");
+  data->setContent(buffer, sizeof(buffer));
+
+  face.receive(*data);
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(lastError, static_cast<uint32_t>(SegmentFetcher::DATA_HAS_NO_SEGMENT));
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+}
+
+BOOST_AUTO_TEST_CASE(MoreSegmentsThanNSegments)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  advanceClocks(10_ms);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 1, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 2, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 3, false));
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  auto data4 = makeDataSegment("/hello/world/version0", 4, false);
+  data4->setFinalBlock(name::Component::fromSegment(2));
+  face.receive(*data4);
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 3);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 5);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(DuplicateNack)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  segmentsToDropOrNack.push(0);
+  segmentsToDropOrNack.push(200);
+  sendNackInsteadOfDropping = true;
+  nackReason = lp::NackReason::DUPLICATE;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 2);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionNack)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  nSegments = 401;
+  segmentsToDropOrNack.push(0);
+  segmentsToDropOrNack.push(200);
+  sendNackInsteadOfDropping = true;
+  nackReason = lp::NackReason::CONGESTION;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(dataSize, 14 * 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 401);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 2);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+}
+
+BOOST_AUTO_TEST_CASE(OtherNackReason)
+{
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+  segmentsToDropOrNack.push(0);
+  sendNackInsteadOfDropping = true;
+  nackReason = lp::NackReason::NO_ROUTE;
+  face.onSendInterest.connect(bind(&Fixture::onInterest, this, _1));
+
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             acceptValidator);
+  connectSignals(fetcher);
+  fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+  fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+  fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+  fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+
+  face.processEvents(1_s);
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(ValidationFailure)
+{
+  DummyValidator validator;
+  validator.getPolicy().setResultCallback([] (const Name& name) {
+      return name.at(-1).toSegment() % 2 == 0;
+    });
+  shared_ptr<SegmentFetcher> fetcher = SegmentFetcher::start(face, Interest("/hello/world"),
+                                                             validator);
+  connectSignals(fetcher);
+
+  auto data1 = makeDataSegment("/hello/world", 0, false);
+  auto data2 = makeDataSegment("/hello/world", 1, true);
+
+  size_t nRecvSegments = 0;
+  fetcher->afterSegmentReceived.connect([&nRecvSegments] (const Data& receivedSegment) {
+      ++nRecvSegments;
+    });
+
+  size_t nValidatedSegments = 0;
+  fetcher->afterSegmentValidated.connect([&nValidatedSegments] (const Data& validatedSegment) {
+      ++nValidatedSegments;
+    });
+
+  advanceClocks(10_ms, 10);
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 100_ms);
+
+  face.receive(*data1);
+
+  advanceClocks(10_ms, 10);
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 100_ms);
+
+  face.receive(*data2);
+
+  advanceClocks(10_ms, 10);
+
+  BOOST_CHECK_EQUAL(fetcher->m_timeLastSegmentReceived, time::steady_clock::now() - 200_ms);
+  BOOST_CHECK_EQUAL(nRecvSegments, 2);
+  BOOST_CHECK_EQUAL(nValidatedSegments, 1);
+  BOOST_CHECK_EQUAL(nErrors, 1);
+}
+
+BOOST_AUTO_TEST_CASE(Stop)
+{
+  DummyValidator acceptValidator;
+
+  auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+  connectSignals(fetcher);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 2);
+
+  fetcher->stop();
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 1);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+
+  fetcher.reset();
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 0);
+
+  // Make sure we can re-assign w/o any complains from ASan
+  fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+  connectSignals(fetcher);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 2);
+
+  advanceClocks(10_ms);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 1);
+
+  // Stop from callback
+  bool fetcherStopped = false;
+
+  fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+  fetcher->afterSegmentReceived.connect([&fetcher, &fetcherStopped] (const Data& data) {
+                                          fetcherStopped = true;
+                                          fetcher->stop();
+                                        });
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 2);
+
+  advanceClocks(10_ms);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+  BOOST_CHECK(fetcherStopped);
+  BOOST_CHECK_EQUAL(fetcher.use_count(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Lifetime)
+{
+  // BasicSingleSegment, but with scoped fetcher
+
+  DummyValidator acceptValidator;
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+
+  weak_ptr<SegmentFetcher> weakFetcher;
+  {
+    auto fetcher = SegmentFetcher::start(face, Interest("/hello/world"), acceptValidator);
+    weakFetcher = fetcher;
+    connectSignals(fetcher);
+
+    fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+    fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+    fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+    fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+  }
+
+  advanceClocks(10_ms);
+  BOOST_CHECK_EQUAL(weakFetcher.expired(), false);
+
+  face.receive(*makeDataSegment("/hello/world/version0", 0, true));
+
+  advanceClocks(10_ms);
+
+  BOOST_CHECK_EQUAL(nErrors, 0);
+  BOOST_CHECK_EQUAL(nCompletions, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 1);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 0);
+  BOOST_CHECK_EQUAL(weakFetcher.expired(), true);
+}
+
+BOOST_AUTO_TEST_CASE(OutOfScopeTimeout)
+{
+  DummyValidator acceptValidator;
+  SegmentFetcher::Options options;
+  options.maxTimeout = 3000_ms;
+
+  size_t nAfterSegmentReceived = 0;
+  size_t nAfterSegmentValidated = 0;
+  size_t nAfterSegmentNacked = 0;
+  size_t nAfterSegmentTimedOut = 0;
+
+  weak_ptr<SegmentFetcher> weakFetcher;
+  {
+    auto fetcher = SegmentFetcher::start(face, Interest("/localhost/nfd/faces/list"),
+                                         acceptValidator, options);
+    weakFetcher = fetcher;
+    connectSignals(fetcher);
+    fetcher->afterSegmentReceived.connect(bind([&nAfterSegmentReceived] { ++nAfterSegmentReceived; }));
+    fetcher->afterSegmentValidated.connect(bind([&nAfterSegmentValidated] { ++nAfterSegmentValidated; }));
+    fetcher->afterSegmentNacked.connect(bind([&nAfterSegmentNacked] { ++nAfterSegmentNacked; }));
+    fetcher->afterSegmentTimedOut.connect(bind([&nAfterSegmentTimedOut] { ++nAfterSegmentTimedOut; }));
+  }
+
+  advanceClocks(500_ms, 7);
+  BOOST_CHECK_EQUAL(weakFetcher.expired(), true);
+
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(nCompletions, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentReceived, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentValidated, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentNacked, 0);
+  BOOST_CHECK_EQUAL(nAfterSegmentTimedOut, 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSegmentFetcher
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/sha256.t.cpp b/tests/unit/util/sha256.t.cpp
new file mode 100644
index 0000000..094f5f6
--- /dev/null
+++ b/tests/unit/util/sha256.t.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/sha256.hpp"
+#include "util/string-helper.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/endian/conversion.hpp>
+#include <sstream>
+
+namespace ndn {
+namespace util {
+namespace test {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestSha256)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  const uint8_t input[] = {0x01, 0x02, 0x03, 0x04};
+  auto expected = fromHex("9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a");
+
+  Sha256 statefulSha256;
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), true);
+
+  statefulSha256.update(input, 1);
+  statefulSha256.update(input + 1, 1);
+  statefulSha256.update(input + 2, 1);
+  statefulSha256.update(input + 3, 1);
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+  BOOST_CHECK_EQUAL(digest->size(), Sha256::DIGEST_SIZE);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(ConstructFromStream)
+{
+  const std::string input = "Hello, world!";
+  auto expected = fromHex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3");
+
+  std::istringstream is(input);
+  Sha256 sha(is);
+  BOOST_CHECK_EQUAL(sha.empty(), false);
+  BOOST_CHECK_EQUAL(sha.toString(), "315F5BDB76D078C43B8AC0064E4A0164612B1FCE77C869345BFC94C75894EDD3");
+
+  ConstBufferPtr digest = sha.computeDigest();
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  const uint8_t origin[] = {0x01, 0x02, 0x03, 0x04};
+
+  Sha256 digest1;
+  digest1.update(origin, sizeof(origin));
+  digest1.computeDigest();
+
+  Sha256 digest2;
+  digest2.update(origin, 1);
+  digest2.update(origin + 1, 1);
+  digest2.update(origin + 2, 1);
+  digest2.update(origin + 3, 1);
+  digest2.computeDigest();
+
+  BOOST_CHECK_EQUAL(digest1 == digest2, true);
+  BOOST_CHECK_EQUAL(digest1 != digest2, false);
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorSha256)
+{
+  auto expected = fromHex("d7bd34bfe44a18d2aa755a344fe3e6b06ed0473772e6dfce16ac71ba0b0a241c");
+
+  Sha256 innerDigest;
+  innerDigest << "TEST";
+
+  Sha256 statefulSha256;
+  statefulSha256 << innerDigest;
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorString)
+{
+  const std::string input = "Hello, world!";
+  auto expected = fromHex("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3");
+
+  Sha256 statefulSha256;
+  statefulSha256 << input;
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorBlock)
+{
+  const uint8_t input[] = {
+    0x16, 0x1b, // SignatureInfo
+      0x1b, 0x01, // SignatureType
+        0x01, // Sha256WithRsa
+      0x1c, 0x16, // KeyLocator
+        0x07, 0x14, // Name
+          0x08, 0x04,
+            0x74, 0x65, 0x73, 0x74,
+          0x08, 0x03,
+            0x6b, 0x65, 0x79,
+          0x08, 0x07,
+            0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72
+  };
+  auto expected = fromHex("b372edfd4d6a4db2cfeaeead6c34fdee9b9e759f7b8d799cf8067e39e7f2886c");
+
+  Sha256 statefulSha256;
+  statefulSha256 << Block{input, sizeof(input)};
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(InsertionOperatorUint64t)
+{
+  const uint64_t input[] = {1, 2, 3, 4};
+  auto expected = fromHex("7236c00c170036c6de133a878210ddd58567aa1d0619a0f70f69e38ae6f916e9");
+
+  Sha256 statefulSha256;
+  for (size_t i = 0; i < sizeof(input) / sizeof(uint64_t); ++i) {
+    statefulSha256 << boost::endian::native_to_big(input[i]);
+  }
+  ConstBufferPtr digest = statefulSha256.computeDigest();
+
+  BOOST_CHECK_EQUAL(statefulSha256.empty(), false);
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(Reset)
+{
+  Sha256 sha;
+  BOOST_CHECK_EQUAL(sha.empty(), true);
+
+  sha << 42;
+  BOOST_CHECK_EQUAL(sha.empty(), false);
+
+  sha.computeDigest(); // finalize
+  sha.reset();
+  BOOST_CHECK_EQUAL(sha.empty(), true);
+  BOOST_CHECK_NO_THROW(sha << 42);
+}
+
+BOOST_AUTO_TEST_CASE(Error)
+{
+  Sha256 sha;
+  sha << 42;
+  sha.computeDigest(); // finalize
+  BOOST_CHECK_THROW(sha << 42, Sha256::Error);
+}
+
+BOOST_AUTO_TEST_CASE(StaticComputeDigest)
+{
+  const uint8_t input[] = {0x01, 0x02, 0x03, 0x04};
+  auto expected = fromHex("9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a");
+
+  ConstBufferPtr digest = Sha256::computeDigest(input, sizeof(input));
+  BOOST_CHECK_EQUAL_COLLECTIONS(expected->data(), expected->data() + expected->size(),
+                                digest->data(), digest->data() + digest->size());
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  const uint8_t origin[] = {0x94, 0xEE, 0x05, 0x93, 0x35, 0xE5, 0x87, 0xE5,
+                            0x01, 0xCC, 0x4B, 0xF9, 0x06, 0x13, 0xE0, 0x81,
+                            0x4F, 0x00, 0xA7, 0xB0, 0x8B, 0xC7, 0xC6, 0x48,
+                            0xFD, 0x86, 0x5A, 0x2A, 0xF6, 0xA2, 0x2C, 0xC2};
+  std::string expected = toHex(origin, sizeof(origin));
+
+  Sha256 digest;
+  digest << "TEST";
+  std::ostringstream os;
+  os << digest;
+  BOOST_CHECK_EQUAL(os.str(), expected);
+  BOOST_CHECK_EQUAL(digest.toString(), expected);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSha256
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace test
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/signal.t.cpp b/tests/unit/util/signal.t.cpp
new file mode 100644
index 0000000..c319f64
--- /dev/null
+++ b/tests/unit/util/signal.t.cpp
@@ -0,0 +1,450 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/signal.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace util {
+namespace signal {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestSignal)
+
+class SignalOwner0
+{
+public:
+  Signal<SignalOwner0> sig;
+
+public:
+  DECLARE_SIGNAL_EMIT(sig)
+
+  bool
+  isSigEmpty()
+  {
+    return sig.isEmpty();
+  }
+};
+
+BOOST_AUTO_TEST_CASE(ZeroSlot)
+{
+  SignalOwner0 so;
+  BOOST_CHECK_NO_THROW(so.emitSignal(sig));
+}
+
+BOOST_AUTO_TEST_CASE(TwoListeners)
+{
+  SignalOwner0 so;
+
+  int hit1 = 0, hit2 = 0;
+  so.sig.connect([&hit1] { ++hit1; });
+  so.sig.connect([&hit2] { ++hit2; });
+
+  so.emitSignal(sig);
+
+  BOOST_CHECK_EQUAL(hit1, 1);
+  BOOST_CHECK_EQUAL(hit2, 1);
+}
+
+class SignalOwner1
+{
+public:
+  Signal<SignalOwner1, int> sig;
+
+protected:
+  DECLARE_SIGNAL_EMIT(sig)
+};
+
+class SignalEmitter1 : public SignalOwner1
+{
+public:
+  void
+  emitTestSignal()
+  {
+    this->emitSignal(sig, 8106);
+  }
+};
+
+BOOST_AUTO_TEST_CASE(OneArgument)
+{
+  SignalEmitter1 se;
+
+  int hit = 0;
+  se.sig.connect([&hit] (int a) {
+    ++hit;
+    BOOST_CHECK_EQUAL(a, 8106);
+  });
+  se.emitTestSignal();
+
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+BOOST_AUTO_TEST_CASE(TwoArguments)
+{
+  Signal<std::remove_pointer_t<decltype(this)>, int, int> sig;
+
+  int hit = 0;
+  sig.connect([&hit] (int a, int b) {
+    ++hit;
+    BOOST_CHECK_EQUAL(a, 21);
+    BOOST_CHECK_EQUAL(b, 22);
+  });
+  sig(21, 22);
+
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
+class RefObject
+{
+public:
+  RefObject()
+  {
+  }
+
+  RefObject(const RefObject& other)
+  {
+    ++s_copyCount;
+  }
+
+public:
+  static int s_copyCount;
+};
+int RefObject::s_copyCount = 0;
+
+// Signal passes arguments by reference,
+// but it also allows a handler that accept arguments by value
+BOOST_AUTO_TEST_CASE(HandlerByVal)
+{
+  RefObject refObject;
+  RefObject::s_copyCount = 0;
+
+  Signal<std::remove_pointer_t<decltype(this)>, RefObject> sig;
+  sig.connect([] (RefObject) {});
+  sig(refObject);
+
+  BOOST_CHECK_EQUAL(RefObject::s_copyCount, 1);
+}
+
+// Signal passes arguments by reference, and no copying
+// is necessary when handler accepts arguments by reference
+BOOST_AUTO_TEST_CASE(HandlerByRef)
+{
+  RefObject refObject;
+  RefObject::s_copyCount = 0;
+
+  Signal<std::remove_pointer_t<decltype(this)>, RefObject> sig;
+  sig.connect([] (const RefObject&) {});
+  sig(refObject);
+
+  BOOST_CHECK_EQUAL(RefObject::s_copyCount, 0);
+}
+
+BOOST_AUTO_TEST_CASE(ManualDisconnect)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection c1 = so.sig.connect([&hit] { ++hit; });
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  Connection c2 = c1; // make a copy
+  BOOST_CHECK_EQUAL(c2.isConnected(), true);
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  c2.disconnect();
+  BOOST_CHECK_EQUAL(c2.isConnected(), false);
+  BOOST_CHECK_EQUAL(c1.isConnected(), false);
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+
+  BOOST_CHECK_NO_THROW(c2.disconnect());
+  BOOST_CHECK_NO_THROW(c1.disconnect());
+}
+
+BOOST_AUTO_TEST_CASE(ManualDisconnectDestructed)
+{
+  auto so = make_unique<SignalOwner0>();
+
+  int hit = 0;
+  Connection connection = so->sig.connect([&hit] { ++hit; });
+
+  so->emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  BOOST_CHECK_EQUAL(connection.isConnected(), true);
+  so.reset(); // destruct Signal
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
+  BOOST_CHECK_NO_THROW(connection.disconnect());
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnect)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+    // sc goes out of scope, disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectAssign)
+{
+  SignalOwner0 so;
+
+  int hit1 = 0, hit2 = 0;
+  ScopedConnection sc = so.sig.connect([&hit1] { ++hit1; });
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 1); // handler1 called
+
+  sc = so.sig.connect([&hit2] { ++hit2; }); // handler1 is disconnected
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 1); // handler1 not called
+  BOOST_CHECK_EQUAL(hit2, 1); // handler2 called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectAssignSame)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection c1 = so.sig.connect([&hit] { ++hit; });
+
+  ScopedConnection sc(c1);
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+  sc = c1; // assign same connection
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+  Connection c2 = c1;
+  sc = c2; // assign a copy of same connection
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 3); // handler called
+  BOOST_CHECK_EQUAL(c1.isConnected(), true);
+  BOOST_CHECK_EQUAL(c2.isConnected(), true);
+  BOOST_CHECK_EQUAL(sc.isConnected(), true);
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectRelease)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+    sc.release();
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
+    // sc goes out of scope, but not disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(AutoDisconnectMove)
+{
+  SignalOwner0 so;
+  int hit = 0;
+
+  unique_ptr<ScopedConnection> sc2;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 1); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+
+    sc2 = make_unique<ScopedConnection>(std::move(sc)); // move constructor
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
+    BOOST_CHECK_EQUAL(sc2->isConnected(), true);
+
+    // sc goes out of scope, but without disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+  sc2.reset();
+
+  ScopedConnection sc3;
+  {
+    ScopedConnection sc = so.sig.connect([&hit] { ++hit; });
+
+    so.emitSignal(sig);
+    BOOST_CHECK_EQUAL(hit, 3); // handler called
+    BOOST_CHECK_EQUAL(sc.isConnected(), true);
+    BOOST_CHECK_EQUAL(sc3.isConnected(), false);
+
+    sc3 = std::move(sc); // move assignment
+    BOOST_CHECK_EQUAL(sc.isConnected(), false);
+    BOOST_CHECK_EQUAL(sc3.isConnected(), true);
+
+    // sc goes out of scope, but without disconnecting
+  }
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 4); // handler called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShot)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  so.sig.connectSingleShot([&hit] { ++hit; });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShotDisconnected)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection conn = so.sig.connectSingleShot([&hit] { ++hit; });
+  BOOST_CHECK_EQUAL(conn.isConnected(), true);
+  conn.disconnect();
+  BOOST_CHECK_EQUAL(conn.isConnected(), false);
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 0); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectSingleShot1)
+{
+  SignalEmitter1 se;
+
+  int hit = 0;
+  se.sig.connectSingleShot([&hit] (int) { ++hit; });
+
+  se.emitTestSignal();
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  se.emitTestSignal();
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ConnectInHandler)
+{
+  SignalOwner0 so;
+
+  int hit1 = 0, hit2 = 0; bool hasHandler2 = false;
+  so.sig.connect([&] {
+    ++hit1;
+    if (!hasHandler2) {
+      so.sig.connect([&] { ++hit2; });
+      hasHandler2 = true;
+    }
+  });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 1); // handler1 called
+  BOOST_CHECK_EQUAL(hit2, 0); // handler2 not called
+
+  // new subscription takes effect
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit1, 2); // handler1 called
+  BOOST_CHECK_EQUAL(hit2, 1); // handler2 called
+}
+
+BOOST_AUTO_TEST_CASE(DisconnectSelfInHandler)
+{
+  SignalOwner0 so;
+
+  int hit = 0;
+  Connection connection;
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
+  connection = so.sig.connect([&so, &connection, &hit] {
+    ++hit;
+    BOOST_CHECK_EQUAL(connection.isConnected(), true);
+    connection.disconnect();
+    BOOST_CHECK_EQUAL(connection.isConnected(), false);
+    BOOST_CHECK_EQUAL(so.isSigEmpty(), false); // disconnecting hasn't taken effect
+  });
+
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+  BOOST_CHECK_EQUAL(connection.isConnected(), false);
+
+  // disconnecting takes effect
+  BOOST_CHECK_EQUAL(so.isSigEmpty(), true);
+  so.emitSignal(sig);
+  BOOST_CHECK_EQUAL(hit, 1); // handler not called
+}
+
+BOOST_AUTO_TEST_CASE(ThrowInHandler)
+{
+  SignalOwner0 so;
+
+  struct HandlerError : public std::exception
+  {
+  };
+
+  int hit = 0;
+  so.sig.connect([&] {
+    ++hit;
+    BOOST_THROW_EXCEPTION(HandlerError());
+  });
+
+  BOOST_CHECK_THROW(so.emitSignal(sig), HandlerError);
+  BOOST_CHECK_EQUAL(hit, 1); // handler called
+
+  BOOST_CHECK_THROW(so.emitSignal(sig), HandlerError);
+  BOOST_CHECK_EQUAL(hit, 2); // handler called
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSignal
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace signal
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/simple-notification.hpp b/tests/unit/util/simple-notification.hpp
new file mode 100644
index 0000000..9e0d4ed
--- /dev/null
+++ b/tests/unit/util/simple-notification.hpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018 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).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_TESTS_UNIT_TESTS_UTIL_SIMPLE_NOTIFICATION_HPP
+#define NDN_TESTS_UNIT_TESTS_UTIL_SIMPLE_NOTIFICATION_HPP
+
+#include "common.hpp"
+
+#include "encoding/encoding-buffer.hpp"
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+class SimpleNotification
+{
+public:
+  SimpleNotification() = default;
+
+  explicit
+  SimpleNotification(const Block& block)
+  {
+    wireDecode(block);
+  }
+
+  SimpleNotification(const std::string& message)
+    : m_message(message)
+  {
+  }
+
+  Block
+  wireEncode() const
+  {
+    ndn::EncodingBuffer buffer;
+    buffer.prependByteArrayBlock(0x8888,
+                                 reinterpret_cast<const uint8_t*>(m_message.c_str()),
+                                 m_message.size());
+    return buffer.block();
+  }
+
+  void
+  wireDecode(const Block& block)
+  {
+    m_message.assign(reinterpret_cast<const char*>(block.value()),
+                     block.value_size());
+
+    // error for testing
+    if (!m_message.empty() && m_message[0] == '\x07')
+      BOOST_THROW_EXCEPTION(tlv::Error("0x07 error"));
+  }
+
+  const std::string&
+  getMessage() const
+  {
+    return m_message;
+  }
+
+  void
+  setMessage(const std::string& message)
+  {
+    m_message = message;
+  }
+
+private:
+  std::string m_message;
+};
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_TESTS_UTIL_SIMPLE_NOTIFICATION_HPP
diff --git a/tests/unit/util/sqlite3-statement.t.cpp b/tests/unit/util/sqlite3-statement.t.cpp
new file mode 100644
index 0000000..13171c5
--- /dev/null
+++ b/tests/unit/util/sqlite3-statement.t.cpp
@@ -0,0 +1,158 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/sqlite3-statement.hpp"
+
+#include "boost-test.hpp"
+
+#include <boost/filesystem.hpp>
+#include <cstring>
+#include <sqlite3.h>
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+class Sqlite3StatementTestFixture
+{
+public:
+  Sqlite3StatementTestFixture()
+    : m_path(boost::filesystem::path(UNIT_TEST_CONFIG_PATH))
+  {
+    boost::filesystem::create_directories(m_path);
+    int result = sqlite3_open_v2((m_path / "sqlite3-statement.db").string().c_str(), &db,
+                                 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+#ifdef NDN_CXX_DISABLE_SQLITE3_FS_LOCKING
+                                 "unix-dotfile"
+#else
+                                 nullptr
+#endif
+                                 );
+
+    if (result != SQLITE_OK) {
+      BOOST_FAIL("Sqlite3 database cannot be opened/created: " + m_path.string());
+    }
+  }
+
+  ~Sqlite3StatementTestFixture()
+  {
+    sqlite3_close(db);
+    boost::filesystem::remove_all(m_path);
+  }
+
+private:
+  boost::filesystem::path m_path;
+
+public:
+  sqlite3* db;
+};
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestSqlite3Statement, Sqlite3StatementTestFixture)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  // create table
+  BOOST_CHECK_NO_THROW(Sqlite3Statement(db, "CREATE TABLE test (t1 int, t2 text)").step());
+
+  // insert data into table
+  BOOST_CHECK_NO_THROW(Sqlite3Statement(db, "INSERT INTO test VALUES (1, 'test1')").step());
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (2, ?)");
+    stmt.bind(1, "test2", std::strlen("test2"), SQLITE_STATIC);
+    stmt.step();
+  }
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (3, ?)");
+    stmt.bind(1, "test3", SQLITE_TRANSIENT);
+    stmt.step();
+  }
+
+  Block block(100);
+  block.encode();
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (4, ?)");
+    stmt.bind(1, block, SQLITE_STATIC);
+    stmt.step();
+  }
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (5, ?)");
+    stmt.bind(1, reinterpret_cast<const void*>(block.wire()), block.size(), SQLITE_STATIC);
+    stmt.step();
+  }
+
+  {
+    Sqlite3Statement stmt(db, "INSERT INTO test VALUES (?, ?)");
+    stmt.bind(1, 6);
+    stmt.bind(2, "test", SQLITE_TRANSIENT);
+    stmt.step();
+  }
+
+  // check content of the table
+
+  {
+    Sqlite3Statement stmt(db, "SELECT count(*) FROM test");
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 6);
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_DONE);
+  }
+
+  {
+    Sqlite3Statement stmt(db, "SELECT t1, t2 FROM test ORDER BY t1");
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 1);
+    BOOST_CHECK_EQUAL(stmt.getString(1), "test1");
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 2);
+    BOOST_CHECK_EQUAL(stmt.getString(1), "test2");
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 3);
+    BOOST_CHECK_EQUAL(stmt.getString(1), "test3");
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 4);
+
+    Block newBlock = stmt.getBlock(1);
+    BOOST_CHECK_EQUAL(newBlock.type(), 100);
+    BOOST_CHECK_EQUAL(newBlock, block);
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.getInt(0), 5);
+    BOOST_CHECK_EQUAL(stmt.getSize(1), block.size());
+    BOOST_CHECK_EQUAL_COLLECTIONS(block.begin(), block.end(),
+                                  stmt.getBlob(1), stmt.getBlob(1) + stmt.getSize(1));
+
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_ROW);
+    BOOST_CHECK_EQUAL(stmt.step(), SQLITE_DONE);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestSqlite3Statement
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/string-helper.t.cpp b/tests/unit/util/string-helper.t.cpp
new file mode 100644
index 0000000..0456a35
--- /dev/null
+++ b/tests/unit/util/string-helper.t.cpp
@@ -0,0 +1,208 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/string-helper.hpp"
+#include "encoding/buffer.hpp"
+
+#include "boost-test.hpp"
+
+#include <cctype>
+#include <cstring>
+
+namespace ndn {
+namespace util {
+namespace test {
+
+using boost::test_tools::output_test_stream;
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestStringHelper)
+
+BOOST_AUTO_TEST_CASE(PrintHex)
+{
+  output_test_stream os;
+
+  printHex(os, 0);
+  BOOST_CHECK(os.is_equal("0x0"));
+
+  printHex(os, 42);
+  BOOST_CHECK(os.is_equal("0x2a"));
+
+  printHex(os, 2748, true);
+  BOOST_CHECK(os.is_equal("0xABC"));
+
+  printHex(os, static_cast<uint64_t>(-1));
+  BOOST_CHECK(os.is_equal("0xffffffffffffffff"));
+
+  printHex(os, ~0U, true);
+  BOOST_CHECK(os.is_equal("0xFFFFFFFF"));
+
+  printHex(os, ~0ULL, true);
+  BOOST_CHECK(os.is_equal("0xFFFFFFFFFFFFFFFF"));
+}
+
+BOOST_AUTO_TEST_CASE(AsHex)
+{
+  using ndn::AsHex;
+  output_test_stream os;
+
+  os << AsHex{0};
+  BOOST_CHECK(os.is_equal("0x0"));
+
+  os << AsHex{42};
+  BOOST_CHECK(os.is_equal("0x2a"));
+
+  os << std::uppercase << AsHex{~0U};
+  BOOST_CHECK(os.is_equal("0xFFFFFFFF"));
+
+  os << std::nouppercase << AsHex{~0U};
+  BOOST_CHECK(os.is_equal("0xffffffff"));
+}
+
+BOOST_AUTO_TEST_CASE(ToHex)
+{
+  std::string test = "Hello, world!";
+  BOOST_CHECK_EQUAL(toHex(reinterpret_cast<const uint8_t*>(test.data()), test.size()),
+                    "48656C6C6F2C20776F726C6421");
+  BOOST_CHECK_EQUAL(toHex(reinterpret_cast<const uint8_t*>(test.data()), test.size(), false),
+                    "48656c6c6f2c20776f726c6421");
+  BOOST_CHECK_EQUAL(toHex(nullptr, 0), "");
+
+  Buffer buffer(test.data(), test.size());
+  BOOST_CHECK_EQUAL(toHex(buffer, false),  "48656c6c6f2c20776f726c6421");
+  BOOST_CHECK_EQUAL(toHex(Buffer{}), "");
+}
+
+BOOST_AUTO_TEST_CASE(FromHex)
+{
+  BOOST_CHECK(*fromHex("") == Buffer{});
+  BOOST_CHECK(*fromHex("48656c6c6f2c20776f726c6421") ==
+              (std::vector<uint8_t>{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20,
+                                    0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21}));
+  BOOST_CHECK(*fromHex("012a3Bc4defAB5CdEF") ==
+              (std::vector<uint8_t>{0x01, 0x2a, 0x3b, 0xc4, 0xde,
+                                    0xfa, 0xb5, 0xcd, 0xef}));
+
+  BOOST_CHECK_THROW(fromHex("1"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("zz"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("00az"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("1234z"), StringHelperError);
+}
+
+BOOST_AUTO_TEST_CASE(ToHexChar)
+{
+  static const std::vector<std::pair<unsigned int, char>> hexMap{
+    {0, '0'}, {1, '1'},  {2, '2'},  {3, '3'},  {4, '4'},  {5, '5'},  {6, '6'},  {7, '7'},
+    {8, '8'}, {9, '9'}, {10, 'A'}, {11, 'B'}, {12, 'C'}, {13, 'D'}, {14, 'E'}, {15, 'F'}
+  };
+
+  for (const auto& i : hexMap) {
+    BOOST_CHECK_EQUAL(toHexChar(i.first), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first + 16), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first + 32), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first + 240), i.second);
+    BOOST_CHECK_EQUAL(toHexChar(i.first, false), std::tolower(static_cast<unsigned char>(i.second)));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(FromHexChar)
+{
+  // for (int ch = 0; ch <= std::numeric_limits<uint8_t>::max(); ++ch) {
+  //   std::cout << "{0x" << std::hex << ch << ", "
+  //             << std::dec << fromHexChar(static_cast<char>(ch)) << "}, ";
+  //   if (ch % 8 == 7)
+  //     std::cout << std::endl;
+  // }
+  static const std::vector<std::pair<char, int>> hexMap{
+    {0x0, -1}, {0x1, -1}, {0x2, -1}, {0x3, -1}, {0x4, -1}, {0x5, -1}, {0x6, -1}, {0x7, -1},
+    {0x8, -1}, {0x9, -1}, {0xa, -1}, {0xb, -1}, {0xc, -1}, {0xd, -1}, {0xe, -1}, {0xf, -1},
+    {0x10, -1}, {0x11, -1}, {0x12, -1}, {0x13, -1}, {0x14, -1}, {0x15, -1}, {0x16, -1}, {0x17, -1},
+    {0x18, -1}, {0x19, -1}, {0x1a, -1}, {0x1b, -1}, {0x1c, -1}, {0x1d, -1}, {0x1e, -1}, {0x1f, -1},
+    {0x20, -1}, {0x21, -1}, {0x22, -1}, {0x23, -1}, {0x24, -1}, {0x25, -1}, {0x26, -1}, {0x27, -1},
+    {0x28, -1}, {0x29, -1}, {0x2a, -1}, {0x2b, -1}, {0x2c, -1}, {0x2d, -1}, {0x2e, -1}, {0x2f, -1},
+    {0x30, 0}, {0x31, 1}, {0x32, 2}, {0x33, 3}, {0x34, 4}, {0x35, 5}, {0x36, 6}, {0x37, 7},
+    {0x38, 8}, {0x39, 9}, {0x3a, -1}, {0x3b, -1}, {0x3c, -1}, {0x3d, -1}, {0x3e, -1}, {0x3f, -1},
+    {0x40, -1}, {0x41, 10}, {0x42, 11}, {0x43, 12}, {0x44, 13}, {0x45, 14}, {0x46, 15}, {0x47, -1},
+    {0x48, -1}, {0x49, -1}, {0x4a, -1}, {0x4b, -1}, {0x4c, -1}, {0x4d, -1}, {0x4e, -1}, {0x4f, -1},
+    {0x50, -1}, {0x51, -1}, {0x52, -1}, {0x53, -1}, {0x54, -1}, {0x55, -1}, {0x56, -1}, {0x57, -1},
+    {0x58, -1}, {0x59, -1}, {0x5a, -1}, {0x5b, -1}, {0x5c, -1}, {0x5d, -1}, {0x5e, -1}, {0x5f, -1},
+    {0x60, -1}, {0x61, 10}, {0x62, 11}, {0x63, 12}, {0x64, 13}, {0x65, 14}, {0x66, 15}, {0x67, -1},
+    {0x68, -1}, {0x69, -1}, {0x6a, -1}, {0x6b, -1}, {0x6c, -1}, {0x6d, -1}, {0x6e, -1}, {0x6f, -1},
+    {0x70, -1}, {0x71, -1}, {0x72, -1}, {0x73, -1}, {0x74, -1}, {0x75, -1}, {0x76, -1}, {0x77, -1},
+    {0x78, -1}, {0x79, -1}, {0x7a, -1}, {0x7b, -1}, {0x7c, -1}, {0x7d, -1}, {0x7e, -1}, {0x7f, -1},
+    {0x80, -1}, {0x81, -1}, {0x82, -1}, {0x83, -1}, {0x84, -1}, {0x85, -1}, {0x86, -1}, {0x87, -1},
+    {0x88, -1}, {0x89, -1}, {0x8a, -1}, {0x8b, -1}, {0x8c, -1}, {0x8d, -1}, {0x8e, -1}, {0x8f, -1},
+    {0x90, -1}, {0x91, -1}, {0x92, -1}, {0x93, -1}, {0x94, -1}, {0x95, -1}, {0x96, -1}, {0x97, -1},
+    {0x98, -1}, {0x99, -1}, {0x9a, -1}, {0x9b, -1}, {0x9c, -1}, {0x9d, -1}, {0x9e, -1}, {0x9f, -1},
+    {0xa0, -1}, {0xa1, -1}, {0xa2, -1}, {0xa3, -1}, {0xa4, -1}, {0xa5, -1}, {0xa6, -1}, {0xa7, -1},
+    {0xa8, -1}, {0xa9, -1}, {0xaa, -1}, {0xab, -1}, {0xac, -1}, {0xad, -1}, {0xae, -1}, {0xaf, -1},
+    {0xb0, -1}, {0xb1, -1}, {0xb2, -1}, {0xb3, -1}, {0xb4, -1}, {0xb5, -1}, {0xb6, -1}, {0xb7, -1},
+    {0xb8, -1}, {0xb9, -1}, {0xba, -1}, {0xbb, -1}, {0xbc, -1}, {0xbd, -1}, {0xbe, -1}, {0xbf, -1},
+    {0xc0, -1}, {0xc1, -1}, {0xc2, -1}, {0xc3, -1}, {0xc4, -1}, {0xc5, -1}, {0xc6, -1}, {0xc7, -1},
+    {0xc8, -1}, {0xc9, -1}, {0xca, -1}, {0xcb, -1}, {0xcc, -1}, {0xcd, -1}, {0xce, -1}, {0xcf, -1},
+    {0xd0, -1}, {0xd1, -1}, {0xd2, -1}, {0xd3, -1}, {0xd4, -1}, {0xd5, -1}, {0xd6, -1}, {0xd7, -1},
+    {0xd8, -1}, {0xd9, -1}, {0xda, -1}, {0xdb, -1}, {0xdc, -1}, {0xdd, -1}, {0xde, -1}, {0xdf, -1},
+    {0xe0, -1}, {0xe1, -1}, {0xe2, -1}, {0xe3, -1}, {0xe4, -1}, {0xe5, -1}, {0xe6, -1}, {0xe7, -1},
+    {0xe8, -1}, {0xe9, -1}, {0xea, -1}, {0xeb, -1}, {0xec, -1}, {0xed, -1}, {0xee, -1}, {0xef, -1},
+    {0xf0, -1}, {0xf1, -1}, {0xf2, -1}, {0xf3, -1}, {0xf4, -1}, {0xf5, -1}, {0xf6, -1}, {0xf7, -1},
+    {0xf8, -1}, {0xf9, -1}, {0xfa, -1}, {0xfb, -1}, {0xfc, -1}, {0xfd, -1}, {0xfe, -1}, {0xff, -1}
+  };
+
+  for (const auto& item : hexMap) {
+    BOOST_CHECK_EQUAL(fromHexChar(item.first), item.second);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(Escape)
+{
+  BOOST_CHECK_EQUAL(escape(""), "");
+  BOOST_CHECK_EQUAL(escape("foo42"), "foo42");
+  BOOST_CHECK_EQUAL(escape("foo%bar"), "foo%25bar");
+  BOOST_CHECK_EQUAL(escape("lower UPPER"), "lower%20UPPER");
+  BOOST_CHECK_EQUAL(escape("-._~"), "-._~");
+  BOOST_CHECK_EQUAL(escape(":/?#[]@"), "%3A%2F%3F%23%5B%5D%40");
+
+  output_test_stream os;
+  const char str[] = "\x01\x2a\x3b\xc4\xde\xfa\xb5\xcd\xef";
+  escape(os, str, std::strlen(str));
+  BOOST_CHECK(os.is_equal("%01%2A%3B%C4%DE%FA%B5%CD%EF"));
+}
+
+BOOST_AUTO_TEST_CASE(Unescape)
+{
+  BOOST_CHECK_EQUAL(unescape(""), "");
+  BOOST_CHECK_EQUAL(unescape("Hello%01, world!%AA  "), "Hello\x01, world!\xAA  ");
+  BOOST_CHECK_EQUAL(unescape("Bad %ZZ (not a hex value)"), "Bad %ZZ (not a hex value)");
+  BOOST_CHECK_EQUAL(unescape("Bad %a (should be two hex chars)"), "Bad %a (should be two hex chars)");
+  BOOST_CHECK_EQUAL(unescape("Bad %a"), "Bad %a");
+
+  output_test_stream os;
+  const char str[] = "%01%2a%3B%c4%de%fA%B5%Cd%EF";
+  unescape(os, str, std::strlen(str));
+  BOOST_CHECK(os.is_equal("\x01\x2a\x3b\xc4\xde\xfa\xb5\xcd\xef"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestStringHelper
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace test
+} // namespace util
+} // namespace ndn
diff --git a/tests/unit/util/time-unit-test-clock.t.cpp b/tests/unit/util/time-unit-test-clock.t.cpp
new file mode 100644
index 0000000..dd8346a
--- /dev/null
+++ b/tests/unit/util/time-unit-test-clock.t.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/time-unit-test-clock.hpp"
+#include "util/scheduler.hpp"
+
+#include "boost-test.hpp"
+#include "../unit-test-time-fixture.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <thread>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_FIXTURE_TEST_SUITE(TestTimeUnitTestClock, UnitTestTimeFixture)
+
+BOOST_AUTO_TEST_CASE(SystemClock)
+{
+  BOOST_CHECK_EQUAL(time::system_clock::now().time_since_epoch(),
+                    time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+  BOOST_CHECK_EQUAL(time::system_clock::now().time_since_epoch(),
+                    time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+
+  steadyClock->advance(1_day);
+  BOOST_CHECK_EQUAL(time::system_clock::now().time_since_epoch(),
+                    time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+
+  systemClock->advance(1_day);
+  BOOST_CHECK_GT(time::system_clock::now().time_since_epoch(),
+                 time::UnitTestClockTraits<time::system_clock>::getDefaultStartTime());
+
+  time::system_clock::TimePoint referenceTime =
+    time::fromUnixTimestamp(time::milliseconds(1390966967032LL));
+  BOOST_CHECK_GT(time::system_clock::now(), referenceTime);
+
+  systemClock->setNow(referenceTime.time_since_epoch());
+  BOOST_CHECK_EQUAL(time::system_clock::now(), referenceTime);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(time::system_clock::now()),
+                    "1390966967032000000 nanoseconds since unit test beginning");
+
+  BOOST_CHECK_EQUAL(time::toIsoString(referenceTime), "20140129T034247.032000");
+  BOOST_CHECK_EQUAL(time::toString(referenceTime), "2014-01-29 03:42:47");
+  BOOST_CHECK_EQUAL(time::toString(referenceTime), "2014-01-29 03:42:47");
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(time::toString(referenceTime, "%Y. gada %d. %B",
+  //                                  std::locale("lv_LV.UTF-8")),
+  //                   "2014. gada 29. Janvāris");
+
+  BOOST_CHECK_EQUAL(time::toString(referenceTime, "%Y -- %d -- %B",
+                                   std::locale("C")),
+                    "2014 -- 29 -- January");
+
+  BOOST_CHECK_EQUAL(time::fromIsoString("20140129T034247.032000"), referenceTime);
+  BOOST_CHECK_EQUAL(time::fromIsoString("20140129T034247.032000Z"), referenceTime);
+  BOOST_CHECK_EQUAL(time::fromString("2014-01-29 03:42:47"),
+                    time::fromUnixTimestamp(1390966967_s));
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(time::fromString("2014. gada 29. Janvāris", "%Y. gada %d. %B",
+  //                                    std::locale("lv_LV.UTF-8")),
+  //                   time::fromUnixTimestamp(1390953600_s));
+
+  BOOST_CHECK_EQUAL(time::fromString("2014 -- 29 -- January", "%Y -- %d -- %B",
+                                     std::locale("C")),
+                    time::fromUnixTimestamp(1390953600_s));
+}
+
+BOOST_AUTO_TEST_CASE(SteadyClock)
+{
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(),
+                    time::steady_clock::duration::zero());
+
+  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(),
+                    time::steady_clock::duration::zero());
+
+  systemClock->advance(36500_days);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(),
+                    time::steady_clock::duration::zero());
+
+  steadyClock->advance(100_ns);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(), 100_ns);
+
+  steadyClock->advance(100_us);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(), 100100_ns);
+
+  steadyClock->setNow(1_ms);
+  BOOST_CHECK_EQUAL(time::steady_clock::now().time_since_epoch(), 1000000_ns);
+
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(time::steady_clock::now()),
+                    "1000000 nanoseconds since unit test beginning");
+}
+
+BOOST_AUTO_TEST_CASE(Scheduler)
+{
+  ndn::Scheduler scheduler(io);
+
+  bool hasFired = false;
+  scheduler.scheduleEvent(100_s, [&] { hasFired = true; });
+
+  io.poll();
+  BOOST_CHECK_EQUAL(hasFired, false);
+
+  steadyClock->advance(100_s);
+
+  io.poll();
+  BOOST_CHECK_EQUAL(hasFired, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTimeUnitTestClock
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit/util/time.t.cpp b/tests/unit/util/time.t.cpp
new file mode 100644
index 0000000..a60e2d9
--- /dev/null
+++ b/tests/unit/util/time.t.cpp
@@ -0,0 +1,138 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/time.hpp"
+
+#include "boost-test.hpp"
+
+#include <thread>
+
+namespace ndn {
+namespace time {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestTime)
+
+BOOST_AUTO_TEST_CASE(SystemClock)
+{
+  system_clock::TimePoint value = system_clock::now();
+  system_clock::TimePoint referenceTime = fromUnixTimestamp(milliseconds(1390966967032LL));
+
+  BOOST_CHECK_GT(value, referenceTime);
+
+  BOOST_CHECK_EQUAL(toIsoString(referenceTime), "20140129T034247.032000");
+  BOOST_CHECK_EQUAL(toString(referenceTime), "2014-01-29 03:42:47");
+  BOOST_CHECK_EQUAL(toString(referenceTime), "2014-01-29 03:42:47");
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(toString(referenceTime, "%Y. gada %d. %B", std::locale("lv_LV.UTF-8")),
+  //                   "2014. gada 29. Janvāris");
+
+  BOOST_CHECK_EQUAL(toString(referenceTime, "%Y -- %d -- %B", std::locale("C")),
+                    "2014 -- 29 -- January");
+
+  BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000"), referenceTime);
+  BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000Z"), referenceTime);
+  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47"), fromUnixTimestamp(1390966967_s));
+
+  // Unfortunately, not all systems has lv_LV locale installed :(
+  // BOOST_CHECK_EQUAL(fromString("2014. gada 29. Janvāris", "%Y. gada %d. %B", std::locale("lv_LV.UTF-8")),
+  //                   fromUnixTimestamp(1390953600_s));
+
+  BOOST_CHECK_EQUAL(fromString("2014 -- 29 -- January", "%Y -- %d -- %B", std::locale("C")),
+                    fromUnixTimestamp(1390953600_s));
+}
+
+BOOST_AUTO_TEST_CASE(SteadyClock)
+{
+  steady_clock::TimePoint oldValue = steady_clock::now();
+  std::this_thread::sleep_for(std::chrono::milliseconds(1));
+  steady_clock::TimePoint newValue = steady_clock::now();
+  BOOST_CHECK_GT(newValue, oldValue);
+}
+
+BOOST_AUTO_TEST_CASE(Abs)
+{
+  BOOST_CHECK_EQUAL(abs(nanoseconds(24422)), nanoseconds(24422));
+  BOOST_CHECK_EQUAL(abs(microseconds(0)), microseconds(0));
+  BOOST_CHECK_EQUAL(abs(milliseconds(-15583)), milliseconds(15583));
+}
+
+BOOST_AUTO_TEST_CASE(LargeDates)
+{
+  auto value = fromUnixTimestamp(1390966967032_ms);
+  BOOST_CHECK_EQUAL(toIsoString(value), "20140129T034247.032000");
+  BOOST_CHECK_EQUAL(fromIsoString("20140129T034247.032000"), value);
+  BOOST_CHECK_EQUAL(toString(value, "%Y-%m-%d %H:%M:%S%F"), "2014-01-29 03:42:47.032000");
+  BOOST_CHECK_EQUAL(fromString("2014-01-29 03:42:47.032000", "%Y-%m-%d %H:%M:%S%F"), value);
+
+  value += 36524_days;
+  BOOST_CHECK_EQUAL(toIsoString(value), "21140129T034247.032000");
+  BOOST_CHECK_EQUAL(fromIsoString("21140129T034247.032000"), value);
+  BOOST_CHECK_EQUAL(toString(value, "%Y-%m-%d %H:%M:%S%F"), "2114-01-29 03:42:47.032000");
+  BOOST_CHECK_EQUAL(fromString("2114-01-29 03:42:47.03200", "%Y-%m-%d %H:%M:%S%F"), value);
+}
+
+BOOST_AUTO_TEST_CASE(Literals)
+{
+  BOOST_CHECK_EQUAL(42_s, seconds(42));
+
+  BOOST_CHECK_EQUAL(1_day, 24_h);
+  BOOST_CHECK_EQUAL(2_days, 48_h);
+  BOOST_CHECK_EQUAL(0.5_day, 12_h);
+  BOOST_CHECK_EQUAL(.5_days, 12_h);
+
+  BOOST_CHECK_EQUAL(1_h, 60_min);
+  BOOST_CHECK_EQUAL(0.5_h, 30_min);
+
+  BOOST_CHECK_EQUAL(1_min, 60_s);
+  BOOST_CHECK_EQUAL(0.5_min, 30_s);
+
+  BOOST_CHECK_EQUAL(1_s, 1000_ms);
+  BOOST_CHECK_EQUAL(0.5_s, 500_ms);
+
+  BOOST_CHECK_EQUAL(1_ms, 1000_us);
+  BOOST_CHECK_EQUAL(0.5_ms, 500_us);
+
+  BOOST_CHECK_EQUAL(1_us, 1000_ns);
+  BOOST_CHECK_EQUAL(0.5_us, 500_ns);
+
+  BOOST_CHECK_EQUAL(1_ns, nanoseconds(1));
+  BOOST_CHECK_EQUAL(5.5_ns, 0.0055_us);
+}
+
+BOOST_AUTO_TEST_CASE(Year2038)
+{
+  auto year2042 = fromIsoString("20420101T000001.042000");
+  auto year2010 = fromIsoString("20100101T000001.042000");
+
+  BOOST_CHECK_EQUAL(to_string(year2010), "1262304001042000000 nanoseconds since Jan 1, 1970");
+  BOOST_CHECK_EQUAL(to_string(year2042), "2272147201042000000 nanoseconds since Jan 1, 1970");
+  BOOST_CHECK_GT(year2042, year2010);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTime
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace time
+} // namespace ndn
diff --git a/tests/unit/version.t.cpp b/tests/unit/version.t.cpp
new file mode 100644
index 0000000..7fdb50d
--- /dev/null
+++ b/tests/unit/version.t.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "version.hpp"
+#include "common.hpp"
+
+#include "boost-test.hpp"
+
+#include <stdio.h>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestVersion)
+
+BOOST_AUTO_TEST_CASE(VersionNumber)
+{
+  BOOST_TEST_MESSAGE("NDN_CXX_VERSION = " + to_string(NDN_CXX_VERSION));
+
+  BOOST_CHECK_EQUAL(NDN_CXX_VERSION, NDN_CXX_VERSION_MAJOR * 1000000 +
+                                     NDN_CXX_VERSION_MINOR * 1000 +
+                                     NDN_CXX_VERSION_PATCH);
+}
+
+BOOST_AUTO_TEST_CASE(VersionString)
+{
+  BOOST_TEST_MESSAGE("NDN_CXX_VERSION_STRING = " NDN_CXX_VERSION_STRING);
+
+  BOOST_STATIC_ASSERT(NDN_CXX_VERSION_MAJOR < 1000);
+  BOOST_STATIC_ASSERT(NDN_CXX_VERSION_MINOR < 1000);
+  BOOST_STATIC_ASSERT(NDN_CXX_VERSION_PATCH < 1000);
+  char buf[12];
+  ::snprintf(buf, sizeof(buf), "%d.%d.%d",
+             NDN_CXX_VERSION_MAJOR, NDN_CXX_VERSION_MINOR, NDN_CXX_VERSION_PATCH);
+
+  BOOST_CHECK_EQUAL(NDN_CXX_VERSION_STRING, buf);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestVersion
+
+} // namespace tests
+} // namespace ndn