Testing environment

Change-Id: I431214b62e7cc8793db80a05fe854849b4c6df5b
diff --git a/tests/block-literal.hpp b/tests/block-literal.hpp
new file mode 100644
index 0000000..d36675e
--- /dev/null
+++ b/tests/block-literal.hpp
@@ -0,0 +1,68 @@
+/* -*- 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 NAC_TESTS_BLOCK_LITERAL_HPP
+#define NAC_TESTS_BLOCK_LITERAL_HPP
+
+#include <ndn-cxx/encoding/block.hpp>
+#include <ndn-cxx/encoding/buffer-stream.hpp>
+#include <ndn-cxx/security/transform.hpp>
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+/** \brief Construct a \c Block from hexadecimal \p input.
+ *  \param input a string containing hexadecimal bytes and comments.
+ *               0-9 and upper-case A-F are input; all other characters are comments.
+ *  \param len length of \p input.
+ *  \throw security::transform::Error input has odd number of hexadecimal digits.
+ *  \throw tlv::Error input cannot be parsed into valid \c Block.
+ *
+ *  Example
+ *  \code
+ *  Block nameBlock = "0706 080141 080142"_block;
+ *  Block nackBlock = "FD032005 reason(no-route)=FD03210196"_block;
+ *  \endcode
+ */
+inline Block
+operator "" _block(const char* input, std::size_t len)
+{
+  namespace t = ndn::security::transform;
+  t::StepSource ss;
+  OBufferStream os;
+  ss >> t::hexDecode() >> t::streamSink(os);
+
+  for (const char* end = input + len; input != end; ++input) {
+    if (std::strchr("0123456789ABCDEF", *input) != nullptr) {
+      ss.write(reinterpret_cast<const uint8_t*>(input), 1);
+    }
+  }
+  ss.end();
+
+  return Block(os.buf());
+}
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
+
+#endif // NAC_TESTS_BLOCK_LITERAL_HPP
diff --git a/tests/boost-multi-log-formatter.hpp b/tests/boost-multi-log-formatter.hpp
new file mode 100644
index 0000000..ae37416
--- /dev/null
+++ b/tests/boost-multi-log-formatter.hpp
@@ -0,0 +1,214 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2015 Regents of the University of California.
+ *
+ * Based on work by Martin Ba (http://stackoverflow.com/a/26718189)
+ *
+ * This file is distributed under the Boost Software License, Version 1.0.
+ * (See http://www.boost.org/LICENSE_1_0.txt)
+ */
+
+#ifndef NDN_TESTS_BOOST_MULTI_LOG_FORMATTER_HPP
+#define NDN_TESTS_BOOST_MULTI_LOG_FORMATTER_HPP
+
+#include <boost/version.hpp>
+
+#if BOOST_VERSION >= 105900
+#include <boost/test/unit_test_parameters.hpp>
+#else
+#include <boost/test/detail/unit_test_parameters.hpp>
+#endif // BOOST_VERSION >= 105900
+
+#include <boost/test/unit_test_log_formatter.hpp>
+#include <boost/test/output/compiler_log_formatter.hpp>
+#include <boost/test/output/xml_log_formatter.hpp>
+
+namespace boost {
+namespace unit_test {
+namespace output {
+
+/**
+ * @brief Log formatter for Boost.Test that outputs the logging to multiple formatters
+ *
+ * The log formatter is designed to output to one or multiple formatters at the same time.  For
+ * example, one HRF formatter can output to the standard output, while XML formatter output to
+ * the file.
+ *
+ * Usage:
+ *
+ *     // Call in init_unit_test_suite: (this will override the --log_format parameter)
+ *     auto formatter = new boost::unit_test::output::multi_log_formatter; // same as already configured logger
+ *
+ *     // Prepare and add additional logger(s)
+ *     formatter.add(std::make_shared<boost::unit_test::output::xml_log_formatter>(),
+ *                   std::make_shared<std::ofstream>("out.xml"));
+ *
+ *      boost::unit_test::unit_test_log.set_formatter(formatter);
+ *
+ * @note Calling `boost::unit_test::unit_test_log.set_stream(...)` will change the stream for
+ *       the original logger.
+ */
+class multi_log_formatter : public unit_test_log_formatter
+{
+public:
+  /**
+   * @brief Create instance of the logger, based on the configured logger instance
+   */
+  multi_log_formatter()
+  {
+    auto format =
+#if BOOST_VERSION > 105900
+      runtime_config::get<output_format>(runtime_config::LOG_FORMAT);
+#else
+      runtime_config::log_format();
+#endif // BOOST_VERSION > 105900
+
+    switch (format) {
+      default:
+#if BOOST_VERSION >= 105900
+      case OF_CLF:
+#else
+      case CLF:
+#endif // BOOST_VERSION >= 105900
+        m_loggers.push_back({std::make_shared<compiler_log_formatter>(), nullptr});
+        break;
+#if BOOST_VERSION >= 105900
+      case OF_XML:
+#else
+      case XML:
+#endif // BOOST_VERSION >= 105900
+        m_loggers.push_back({std::make_shared<xml_log_formatter>(), nullptr});
+        break;
+    }
+  }
+
+  void
+  add(std::shared_ptr<unit_test_log_formatter> formatter, std::shared_ptr<std::ostream> os)
+  {
+    m_loggers.push_back({formatter, os});
+  }
+
+  // Formatter interface
+  void
+  log_start(std::ostream& os, counter_t test_cases_amount)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_start(l.os == nullptr ? os : *l.os, test_cases_amount);
+  }
+
+  void
+  log_finish(std::ostream& os)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_finish(l.os == nullptr ? os : *l.os);
+  }
+
+  void
+  log_build_info(std::ostream& os)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_build_info(l.os == nullptr ? os : *l.os);
+  }
+
+  void
+  test_unit_start(std::ostream& os, const test_unit& tu)
+  {
+    for (auto& l : m_loggers)
+      l.logger->test_unit_start(l.os == nullptr ? os : *l.os, tu);
+  }
+
+  void
+  test_unit_finish(std::ostream& os, const test_unit& tu, unsigned long elapsed)
+  {
+    for (auto& l : m_loggers)
+      l.logger->test_unit_finish(l.os == nullptr ? os : *l.os, tu, elapsed);
+  }
+
+  void
+  test_unit_skipped(std::ostream& os, const test_unit& tu)
+  {
+    for (auto& l : m_loggers)
+      l.logger->test_unit_skipped(l.os == nullptr ? os : *l.os, tu);
+  }
+
+#if BOOST_VERSION >= 105900
+  void
+  log_exception_start(std::ostream& os, const log_checkpoint_data& lcd, const execution_exception& ex)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_exception_start(l.os == nullptr ? os : *l.os, lcd, ex);
+  }
+
+  void
+  log_exception_finish(std::ostream& os)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_exception_finish(l.os == nullptr ? os : *l.os);
+  }
+#else
+  void
+  log_exception(std::ostream& os, const log_checkpoint_data& lcd, const execution_exception& ex)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_exception(l.os == nullptr ? os : *l.os, lcd, ex);
+  }
+#endif // BOOST_VERSION >= 105900
+
+  void
+  log_entry_start(std::ostream& os, const log_entry_data& entry_data, log_entry_types let)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_entry_start(l.os == nullptr ? os : *l.os, entry_data, let);
+  }
+
+  void
+  log_entry_value(std::ostream& os, const_string value)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_entry_value(l.os == nullptr ? os : *l.os, value);
+  }
+
+  void
+  log_entry_finish(std::ostream& os)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_entry_finish(l.os == nullptr ? os : *l.os);
+  }
+
+#if BOOST_VERSION >= 105900
+  void
+  entry_context_start(std::ostream& os, log_level level)
+  {
+    for (auto& l : m_loggers)
+      l.logger->entry_context_start(l.os == nullptr ? os : *l.os, level);
+  }
+
+  void
+  log_entry_context(std::ostream& os, const_string value)
+  {
+    for (auto& l : m_loggers)
+      l.logger->log_entry_context(l.os == nullptr ? os : *l.os, value);
+  }
+
+  void
+  entry_context_finish(std::ostream& os)
+  {
+    for (auto& l : m_loggers)
+      l.logger->entry_context_finish(l.os == nullptr ? os : *l.os);
+  }
+#endif // BOOST_VERSION >= 105900
+
+private:
+  struct LoggerInfo
+  {
+    std::shared_ptr<unit_test_log_formatter> logger;
+    std::shared_ptr<std::ostream> os;
+  };
+  std::vector<LoggerInfo> m_loggers;
+};
+
+} // namespace output
+} // namespace unit_test
+} // namespace boost
+
+#endif // NDN_TESTS_BOOST_MULTI_LOG_FORMATTER_HPP
diff --git a/tests/boost-test.hpp b/tests/boost-test.hpp
new file mode 100644
index 0000000..a19590e
--- /dev/null
+++ b/tests/boost-test.hpp
@@ -0,0 +1,31 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#ifndef NDN_NAC_TESTS_BOOST_TEST_HPP
+#define NDN_NAC_TESTS_BOOST_TEST_HPP
+
+// suppress warnings from Boost.Test
+#pragma GCC system_header
+#pragma clang system_header
+
+#include <boost/concept_check.hpp>
+#include <boost/test/output_test_stream.hpp>
+#include <boost/test/unit_test.hpp>
+
+#endif // NDN_NAC_TESTS_BOOST_TEST_HPP
diff --git a/tests/dummy-forwarder.cpp b/tests/dummy-forwarder.cpp
new file mode 100644
index 0000000..ff34fb2
--- /dev/null
+++ b/tests/dummy-forwarder.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#include "dummy-forwarder.hpp"
+
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+DummyForwarder::DummyForwarder(boost::asio::io_service& io, KeyChain& keyChain)
+  : m_io(io)
+  , m_keyChain(keyChain)
+{
+}
+
+Face&
+DummyForwarder::addFace()
+{
+  auto face = std::make_shared<util::DummyClientFace>(m_io, m_keyChain, util::
+                                                      DummyClientFace::Options{true, true});
+  face->onSendInterest.connect([this, face] (const Interest& interest) {
+      for (auto& otherFace : m_faces) {
+        if (&*face == &*otherFace) {
+          continue;
+        }
+        otherFace->receive(interest);
+      }
+    });
+
+  face->onSendData.connect([this, face] (const Data& data) {
+      for (auto& otherFace : m_faces) {
+        if (&*face == &*otherFace) {
+          continue;
+        }
+        otherFace->receive(data);
+      }
+    });
+
+  face->onSendNack.connect([this, face] (const lp::Nack& nack) {
+      for (auto& otherFace : m_faces) {
+        if (&*face == &*otherFace) {
+          continue;
+        }
+        otherFace->receive(nack);
+      }
+    });
+
+  m_faces.push_back(face);
+  return *face;
+}
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
diff --git a/tests/dummy-forwarder.hpp b/tests/dummy-forwarder.hpp
new file mode 100644
index 0000000..e4f5fd8
--- /dev/null
+++ b/tests/dummy-forwarder.hpp
@@ -0,0 +1,63 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/data.hpp>
+#include <ndn-cxx/lp/nack.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+
+#ifndef NAC_TESTS_TEST_DUMMY_FORWARDER_HPP
+#define NAC_TESTS_TEST_DUMMY_FORWARDER_HPP
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+/**
+ * @brief Very basic implementation of the dummy forwarder
+ *
+ * Interests expressed by any added face, will be forwarded to all other faces.
+ * Similarly, any pushed data, will be pushed to all other faces.
+ */
+class DummyForwarder
+{
+public:
+  DummyForwarder(boost::asio::io_service& io, KeyChain& keyChain);
+
+  Face&
+  addFace();
+
+  Face&
+  getFace(size_t nFace)
+  {
+    return *m_faces.at(nFace);
+  }
+
+private:
+  boost::asio::io_service& m_io;
+  KeyChain& m_keyChain;
+  std::vector<shared_ptr<util::DummyClientFace>> m_faces;
+};
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
+
+#endif // NAC_TESTS_TEST_DUMMY_FORWARDER_HPP
diff --git a/tests/identity-management-fixture.cpp b/tests/identity-management-fixture.cpp
new file mode 100644
index 0000000..f8b9c35
--- /dev/null
+++ b/tests/identity-management-fixture.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#include "identity-management-fixture.hpp"
+
+#include <ndn-cxx/util/io.hpp>
+#include <ndn-cxx/security/v2/additional-description.hpp>
+
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+namespace v2 = security::v2;
+
+IdentityManagementFixture::IdentityManagementFixture()
+  : m_keyChain("pib-memory:", "tpm-memory:")
+{
+}
+
+IdentityManagementFixture::~IdentityManagementFixture()
+{
+  boost::system::error_code ec;
+  for (const auto& certFile : m_certFiles) {
+    boost::filesystem::remove(certFile, ec); // ignore error
+  }
+}
+
+security::Identity
+IdentityManagementFixture::addIdentity(const Name& identityName, const KeyParams& params)
+{
+  auto identity = m_keyChain.createIdentity(identityName, params);
+  m_identities.insert(identityName);
+  return identity;
+}
+
+bool
+IdentityManagementFixture::saveIdentityCertificate(const security::Identity& identity,
+                                                   const std::string& filename)
+{
+  try {
+    auto cert = identity.getDefaultKey().getDefaultCertificate();
+    return saveCertToFile(cert, filename);
+  }
+  catch (const security::Pib::Error&) {
+    return false;
+  }
+}
+
+security::Identity
+IdentityManagementFixture::addSubCertificate(const Name& subIdentityName,
+                                             const security::Identity& issuer, const KeyParams& params)
+{
+  auto subIdentity = addIdentity(subIdentityName, params);
+
+  v2::Certificate request = subIdentity.getDefaultKey().getDefaultCertificate();
+
+  request.setName(request.getKeyName().append("parent").appendVersion());
+
+  SignatureInfo info;
+  info.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
+                                                  time::system_clock::now() + time::days(7300)));
+
+  v2::AdditionalDescription description;
+  description.set("type", "sub-certificate");
+  info.appendTypeSpecificTlv(description.wireEncode());
+
+  m_keyChain.sign(request, signingByIdentity(issuer).setSignatureInfo(info));
+  m_keyChain.setDefaultCertificate(subIdentity.getDefaultKey(), request);
+
+  return subIdentity;
+}
+
+v2::Certificate
+IdentityManagementFixture::addCertificate(const security::Key& key, const std::string& issuer)
+{
+  Name certificateName = key.getName();
+  certificateName
+    .append(issuer)
+    .appendVersion();
+  v2::Certificate certificate;
+  certificate.setName(certificateName);
+
+  // set metainfo
+  certificate.setContentType(tlv::ContentType_Key);
+  certificate.setFreshnessPeriod(time::hours(1));
+
+  // set content
+  certificate.setContent(key.getPublicKey().data(), key.getPublicKey().size());
+
+  // set signature-info
+  SignatureInfo info;
+  info.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
+                                                  time::system_clock::now() + time::days(10)));
+
+  m_keyChain.sign(certificate, signingByKey(key).setSignatureInfo(info));
+  return certificate;
+}
+
+bool
+IdentityManagementFixture::saveCertToFile(const Data& obj, const std::string& filename)
+{
+  m_certFiles.insert(filename);
+  try {
+    io::save(obj, filename);
+    return true;
+  }
+  catch (const io::Error&) {
+    return false;
+  }
+}
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
new file mode 100644
index 0000000..96cbc7f
--- /dev/null
+++ b/tests/identity-management-fixture.hpp
@@ -0,0 +1,95 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#ifndef NAC_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
+#define NAC_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
+
+#include "boost-test.hpp"
+
+#include <ndn-cxx/security/v2/key-chain.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+
+#include <vector>
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+/**
+ * @brief A test suite level fixture to help with identity management
+ *
+ * Test cases in the suite can use this fixture to create identities.  Identities,
+ * certificates, and saved certificates are automatically removed during test teardown.
+ */
+class IdentityManagementFixture
+{
+public:
+  IdentityManagementFixture();
+
+  ~IdentityManagementFixture();
+
+  /**
+   * @brief Add identity @p identityName
+   * @return name of the created self-signed certificate
+   */
+  security::Identity
+  addIdentity(const Name& identityName, const KeyParams& params = security::v2::KeyChain::getDefaultKeyParams());
+
+  /**
+   *  @brief Save identity certificate to a file
+   *  @param identity identity
+   *  @param filename file name, should be writable
+   *  @return whether successful
+   */
+  bool
+  saveIdentityCertificate(const security::Identity& identity, const std::string& filename);
+
+  /**
+   * @brief Issue a certificate for \p subIdentityName signed by \p issuer
+   *
+   *  If identity does not exist, it is created.
+   *  A new key is generated as the default key for identity.
+   *  A default certificate for the key is signed by the issuer using its default certificate.
+   *
+   *  @return the sub identity
+   */
+  security::Identity
+  addSubCertificate(const Name& subIdentityName, const security::Identity& issuer,
+                    const KeyParams& params = security::v2::KeyChain::getDefaultKeyParams());
+
+  /**
+   * @brief Add a self-signed certificate to @p key with issuer ID @p issuer
+   */
+  security::v2::Certificate
+  addCertificate(const security::Key& key, const std::string& issuer);
+
+  bool
+  saveCertToFile(const Data& obj, const std::string& filename);
+
+protected:
+  security::v2::KeyChain m_keyChain;
+  std::set<Name> m_identities;
+  std::set<std::string> m_certFiles;
+};
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
+
+#endif // NAC_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
diff --git a/tests/main.cpp b/tests/main.cpp
new file mode 100644
index 0000000..cef5c60
--- /dev/null
+++ b/tests/main.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#define BOOST_TEST_MODULE NAC Tests
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_ALTERNATIVE_INIT_API
+
+#include <boost/version.hpp>
+
+#if BOOST_VERSION >= 106200
+// Boost.Test v3.3 (Boost 1.62) natively supports multi-logger output
+#include "boost-test.hpp"
+#else
+#define BOOST_TEST_NO_MAIN
+#include "boost-test.hpp"
+
+#include "boost-multi-log-formatter.hpp"
+
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options/parsers.hpp>
+
+#include <fstream>
+#include <iostream>
+
+static bool
+init_tests()
+{
+  init_unit_test();
+
+  namespace po = boost::program_options;
+  namespace ut = boost::unit_test;
+
+  po::options_description extraOptions;
+  std::string logger;
+  std::string outputFile = "-";
+  extraOptions.add_options()
+    ("log_format2", po::value<std::string>(&logger), "Type of second log formatter: HRF or XML")
+    ("log_sink2", po::value<std::string>(&outputFile)->default_value(outputFile), "Second log sink, - for stdout")
+    ;
+  po::variables_map vm;
+  try {
+    po::store(po::command_line_parser(ut::framework::master_test_suite().argc,
+                                      ut::framework::master_test_suite().argv)
+                .options(extraOptions)
+                .run(),
+              vm);
+    po::notify(vm);
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << "\n"
+              << extraOptions << std::endl;
+    return false;
+  }
+
+  if (vm.count("log_format2") == 0) {
+    // second logger is not configured
+    return true;
+  }
+
+  std::shared_ptr<ut::unit_test_log_formatter> formatter;
+  if (logger == "XML") {
+    formatter = std::make_shared<ut::output::xml_log_formatter>();
+  }
+  else if (logger == "HRF") {
+    formatter = std::make_shared<ut::output::compiler_log_formatter>();
+  }
+  else {
+    std::cerr << "ERROR: only HRF or XML log formatter can be specified" << std::endl;
+    return false;
+  }
+
+  std::shared_ptr<std::ostream> output;
+  if (outputFile == "-") {
+    output = std::shared_ptr<std::ostream>(&std::cout, std::bind([]{}));
+  }
+  else {
+    output = std::make_shared<std::ofstream>(outputFile.c_str());
+  }
+
+  auto multiFormatter = new ut::output::multi_log_formatter;
+  multiFormatter->add(formatter, output);
+  ut::unit_test_log.set_formatter(multiFormatter);
+
+  return true;
+}
+
+int
+main(int argc, char* argv[])
+{
+  return ::boost::unit_test::unit_test_main(&init_tests, argc, argv);
+}
+
+#endif // BOOST_VERSION >= 106200
diff --git a/tests/tests-common.hpp b/tests/tests-common.hpp
new file mode 100644
index 0000000..40130c8
--- /dev/null
+++ b/tests/tests-common.hpp
@@ -0,0 +1,36 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#ifndef NAC_TESTS_TEST_COMMON_HPP
+#define NAC_TESTS_TEST_COMMON_HPP
+
+#include "boost-test.hpp"
+#include "unit-test-common-fixtures.hpp"
+#include "identity-management-fixture.hpp"
+#include "block-literal.hpp"
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
+
+#endif // NAC_TESTS_TEST_COMMON_HPP
diff --git a/tests/unit-test-common-fixtures.cpp b/tests/unit-test-common-fixtures.cpp
new file mode 100644
index 0000000..f001ef7
--- /dev/null
+++ b/tests/unit-test-common-fixtures.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#include "tests-common.hpp"
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+BaseFixture::BaseFixture()
+{
+}
+
+UnitTestTimeFixture::UnitTestTimeFixture()
+  : steadyClock(make_shared<time::UnitTestSteadyClock>())
+  , systemClock(make_shared<time::UnitTestSystemClock>())
+{
+  time::setCustomClocks(steadyClock, systemClock);
+}
+
+UnitTestTimeFixture::~UnitTestTimeFixture()
+{
+  time::setCustomClocks(nullptr, nullptr);
+}
+
+void
+UnitTestTimeFixture::advanceClocks(const time::nanoseconds& tick, size_t nTicks)
+{
+  this->advanceClocks(tick, tick * nTicks);
+}
+
+void
+UnitTestTimeFixture::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 (m_io.stopped())
+      m_io.reset();
+    m_io.poll();
+  }
+}
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
diff --git a/tests/unit-test-common-fixtures.hpp b/tests/unit-test-common-fixtures.hpp
new file mode 100644
index 0000000..03ba0f5
--- /dev/null
+++ b/tests/unit-test-common-fixtures.hpp
@@ -0,0 +1,92 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC 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.
+ *
+ * NAC 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 NAC library authors and contributors.
+ */
+
+#ifndef NAC_TESTS_UNIT_TEST_COMMON_FIXTURES_HPP
+#define NAC_TESTS_UNIT_TEST_COMMON_FIXTURES_HPP
+
+#include "boost-test.hpp"
+#include "identity-management-fixture.hpp"
+
+#include <boost/asio.hpp>
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+/** \brief base test fixture
+ *
+ *  Every test case should be based on this fixture,
+ *  to have per test case io_service initialization.
+ */
+class BaseFixture : public IdentityManagementFixture
+{
+protected:
+  BaseFixture();
+
+protected:
+  /** \brief reference to global io_service
+   */
+  boost::asio::io_service m_io;
+};
+
+/** \brief a base test fixture that overrides steady clock and system clock
+ */
+class UnitTestTimeFixture : public BaseFixture
+{
+protected:
+  UnitTestTimeFixture();
+
+  ~UnitTestTimeFixture();
+
+  /** \brief advance steady and system clocks
+   *
+   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
+   *  After each tick, global 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);
+
+  /** \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, global 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);
+
+protected:
+  shared_ptr<time::UnitTestSteadyClock> steadyClock;
+  shared_ptr<time::UnitTestSystemClock> systemClock;
+
+  friend class LimitedIo;
+};
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn
+
+#endif // NAC_TESTS_UNIT_TEST_COMMON_FIXTURES_HPP
diff --git a/tests/wscript b/tests/wscript
new file mode 100644
index 0000000..a32e9d8
--- /dev/null
+++ b/tests/wscript
@@ -0,0 +1,21 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+from waflib import Utils
+
+top = '..'
+
+def build(bld):
+    bld.objects(
+        target='tests-main',
+        source='main.cpp',
+        use='libndn-nac')
+
+    config_path = 'TMP_TESTS_PATH="%s"' % bld.bldnode.make_node('tmp-files')
+
+    bld.program(
+        target='../unit-tests',
+        name='unit-tests',
+        source=bld.path.ant_glob('**/*.cpp', excl='main.cpp'),
+        use='libndn-nac tests-main',
+        includes=['.'],
+        install_path=None,
+        defines=[config_path])
diff --git a/wscript b/wscript
index d0c3075..8931c7a 100644
--- a/wscript
+++ b/wscript
@@ -62,9 +62,9 @@
         includes = ['src', '.'],
         export_includes=['src', '.'])
 
-    # # Unit tests
-    # if bld.env['WITH_TESTS']:
-    #     bld.recurse('tests')
+    # Unit tests
+    if bld.env['WITH_TESTS']:
+        bld.recurse('tests')
 
     bld.install_files(
         dest = "%s/ndn-nac" % bld.env['INCLUDEDIR'],