Adding DataProducer class to produce and encrypt data packets.

Change-Id: I147164a1517f1f6f24536959bc572a6d777c15b0
refs: #3016
diff --git a/src/producer-db.cpp b/src/producer-db.cpp
new file mode 100644
index 0000000..c748953
--- /dev/null
+++ b/src/producer-db.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California
+ *
+ * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
+ * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
+ *
+ * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-group-encrypt, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Prashanth Swaminathan <prashanthsw@gmail.com>
+ */
+
+#include "producer-db.hpp"
+
+#include <sqlite3.h>
+#include <boost/filesystem.hpp>
+#include <ndn-cxx/util/sqlite3-statement.hpp>
+#include <ndn-cxx/security/identity-certificate.hpp>
+
+namespace ndn {
+namespace gep {
+
+using util::Sqlite3Statement;
+using time::system_clock;
+
+static const std::string INITIALIZATION =
+  "CREATE TABLE IF NOT EXISTS                         \n"
+  "  contentkeys(                                     \n"
+  "    rowId            INTEGER PRIMARY KEY,          \n"
+  "    timeslot         INTEGER,                      \n"
+  "    key              BLOB NOT NULL                 \n"
+  "  );                                               \n"
+  "CREATE UNIQUE INDEX IF NOT EXISTS                  \n"
+  "   timeslotIndex ON contentkeys(timeslot);         \n";
+
+class ProducerDB::Impl
+{
+public:
+  Impl(const std::string& dbPath)
+  {
+    // open Database
+
+    int result = sqlite3_open_v2(dbPath.c_str(), &m_database,
+                                 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+#ifdef NDN_CXX_DISABLE_SQLITE3_FS_LOCKING
+                                 "unix-dotfile"
+#else
+                                 nullptr
+#endif
+                                 );
+
+    if (result != SQLITE_OK)
+      BOOST_THROW_EXCEPTION(Error("Producer DB cannot be opened/created: " + dbPath));
+
+    // enable foreign key
+    sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", nullptr, nullptr, nullptr);
+
+    // initialize database specific tables
+    char* errorMessage = nullptr;
+    result = sqlite3_exec(m_database, INITIALIZATION.c_str(), nullptr, nullptr, &errorMessage);
+    if (result != SQLITE_OK && errorMessage != nullptr) {
+      sqlite3_free(errorMessage);
+      BOOST_THROW_EXCEPTION(Error("Producer DB cannot be initialized"));
+    }
+  }
+
+  ~Impl()
+  {
+    sqlite3_close(m_database);
+  }
+
+public:
+  sqlite3* m_database;
+};
+
+ProducerDB::ProducerDB(const std::string& dbPath)
+  : m_impl(new Impl(dbPath))
+{
+}
+
+ProducerDB::~ProducerDB() = default;
+
+static int32_t
+getFixedTimeslot(const system_clock::TimePoint& timeslot) {
+  return (time::toUnixTimestamp(timeslot)).count() / 3600000;
+}
+
+bool
+ProducerDB::hasContentKey(const system_clock::TimePoint& timeslot) const
+{
+  int32_t fixedTimeslot = getFixedTimeslot(timeslot);
+  Sqlite3Statement statement(m_impl->m_database,
+                             "SELECT key FROM contentkeys where timeslot=?");
+  statement.bind(1, fixedTimeslot);
+  return (statement.step() == SQLITE_ROW);
+}
+
+
+Buffer
+ProducerDB::getContentKey(const system_clock::TimePoint& timeslot) const
+{
+  int32_t fixedTimeslot = getFixedTimeslot(timeslot);
+  Sqlite3Statement statement(m_impl->m_database,
+                             "SELECT key FROM contentkeys where timeslot=?");
+  statement.bind(1, fixedTimeslot);
+
+  Buffer result;
+  if (statement.step() == SQLITE_ROW) {
+    result = Buffer(statement.getBlob(0), statement.getSize(0));
+  }
+  else {
+    BOOST_THROW_EXCEPTION(Error("Cannot get the key from database"));
+  }
+  return result;
+}
+
+void
+ProducerDB::addContentKey(const system_clock::TimePoint& timeslot, const Buffer& key)
+{
+  // BOOST_ASSERT(key.length() != 0);
+  int32_t fixedTimeslot = getFixedTimeslot(timeslot);
+  Sqlite3Statement statement(m_impl->m_database,
+                             "INSERT INTO contentkeys (timeslot, key)\
+                              values (?, ?)");
+  statement.bind(1, fixedTimeslot);
+  statement.bind(2, key.buf(), key.size(), SQLITE_TRANSIENT);
+  if (statement.step() != SQLITE_DONE)
+    BOOST_THROW_EXCEPTION(Error("Cannot add the key to database"));
+}
+
+void
+ProducerDB::deleteContentKey(const system_clock::TimePoint& timeslot)
+{
+  int32_t fixedTimeslot = getFixedTimeslot(timeslot);
+  Sqlite3Statement statement(m_impl->m_database,
+                             "DELETE FROM contentkeys WHERE timeslot=?");
+  statement.bind(1, fixedTimeslot);
+  statement.step();
+}
+
+} // namespace gep
+} // namespace ndn
diff --git a/src/producer-db.hpp b/src/producer-db.hpp
new file mode 100644
index 0000000..4a10d3f
--- /dev/null
+++ b/src/producer-db.hpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California
+ *
+ * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
+ * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
+ *
+ * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-group-encrypt, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Prashanth Swaminathan <prashanthsw@gmail.com>
+ */
+
+#ifndef NDN_GEP_PRODUCER_DB_HPP
+#define NDN_GEP_PRODUCER_DB_HPP
+
+#include "common.hpp"
+
+namespace ndn {
+namespace gep {
+
+/**
+ * @brief ProducerDB is a class to manage the database of data producer.
+ * It contains one table that maps timeslots (to the nearest hour) to the
+ * content key created for that timeslot.
+ */
+class ProducerDB
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
+  explicit
+  ProducerDB(const std::string& dbPath);
+
+  ~ProducerDB();
+
+public:
+  /**
+   * @brief Check if content key exists for the hour covering @p timeslot
+   */
+  bool
+  hasContentKey(const time::system_clock::TimePoint& timeslot) const;
+
+  /**
+   * @brief Get content key for the hour covering @p timeslot
+   * @throws Error if the key does not exist
+   */
+  Buffer
+  getContentKey(const time::system_clock::TimePoint& timeslot) const;
+
+  /**
+   * @brief Add @p key as the content key for the hour covering @p timeslot
+   * @throws Error if a key for the same hour already exists
+   */
+  void
+  addContentKey(const time::system_clock::TimePoint& timeslot, const Buffer& key);
+
+  /**
+   * @brief Delete content key for the hour covering @p timeslot
+   */
+  void
+  deleteContentKey(const time::system_clock::TimePoint& timeslot);
+
+private:
+  class Impl;
+  unique_ptr<Impl> m_impl;
+};
+
+} // namespace gep
+} // namespace ndn
+
+#endif // NDN_GEP_PRODUCER_DB_HPP
diff --git a/src/producer.cpp b/src/producer.cpp
new file mode 100644
index 0000000..f112fa6
--- /dev/null
+++ b/src/producer.cpp
@@ -0,0 +1,241 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California
+ *
+ * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
+ * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
+ *
+ * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-group-encrypt, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Prashanth Swaminathan <prashanthsw@gmail.com>
+ */
+
+#include "producer.hpp"
+#include "random-number-generator.hpp"
+#include "algo/encryptor.hpp"
+#include "algo/aes.hpp"
+
+namespace ndn {
+namespace gep {
+
+using time::system_clock;
+
+static const int startTs = -2;
+static const int endTs = -1;
+
+/**
+  @brief Method to round the provided @p timeslot to the nearest whole
+  hour, so that we can store content keys uniformly (by start of the hour).
+*/
+static const system_clock::TimePoint
+getRoundedTimeslot(const system_clock::TimePoint& timeslot) {
+  return time::fromUnixTimestamp(
+    (time::toUnixTimestamp(timeslot) / 3600000) * 3600000);
+}
+
+Producer::Producer(const Name& prefix, const Name& dataType,
+                   Face& face, const std::string& dbPath, uint8_t repeatAttempts)
+  : m_face(face),
+    m_db(dbPath),
+    m_maxRepeatAttempts(repeatAttempts)
+{
+  Name fixedPrefix = prefix;
+  Name fixedDataType = dataType;
+  KeyInfo keyInfo;
+  /**
+    Fill m_ekeyInfo vector with all permutations of dataType, including the 'E-KEY'
+    component of the name. This will be used in DataProducer::createContentKey to
+    send interests without reconstructing names every time.
+  */
+  fixedPrefix.append(NAME_COMPONENT_READ);
+  while (!fixedDataType.empty()) {
+    Name nodeName = fixedPrefix;
+    nodeName.append(fixedDataType);
+    nodeName.append(NAME_COMPONENT_E_KEY);
+
+    m_ekeyInfo[nodeName] = keyInfo;
+    fixedDataType = fixedDataType.getPrefix(-1);
+  }
+  fixedPrefix.append(dataType);
+  m_namespace = prefix;
+  m_namespace.append(NAME_COMPONENT_SAMPLE);
+  m_namespace.append(dataType);
+}
+
+Name
+Producer::createContentKey(const system_clock::TimePoint& timeslot,
+                           const ProducerEKeyCallback& callback)
+{
+  const system_clock::TimePoint hourSlot = getRoundedTimeslot(timeslot);
+
+  // Create content key name.
+  Name contentKeyName = m_namespace;
+  contentKeyName.append(NAME_COMPONENT_C_KEY);
+  contentKeyName.append(time::toIsoString(hourSlot));
+
+  Buffer contentKeyBits;
+  if (m_db.hasContentKey(timeslot)) {
+    contentKeyBits = m_db.getContentKey(timeslot);
+    return contentKeyName;
+  }
+
+  RandomNumberGenerator rng;
+  AesKeyParams aesParams(128);
+  contentKeyBits = algo::Aes::generateKey(rng, aesParams).getKeyBits();
+  m_db.addContentKey(timeslot, contentKeyBits);
+
+  uint64_t timeCount = toUnixTimestamp(timeslot).count();
+  m_keyRequests.insert({timeCount, KeyRequest(m_ekeyInfo.size())});
+  KeyRequest& keyRequest = m_keyRequests.at(timeCount);
+
+  Exclude timeRange;
+  timeRange.excludeAfter(name::Component(time::toIsoString(timeslot)));
+  // Send interests for all nodes in tree.
+  std::unordered_map<Name, KeyInfo>::iterator it;
+  for (it = m_ekeyInfo.begin(); it != m_ekeyInfo.end(); ++it) {
+    const KeyInfo& keyInfo = it->second;
+    keyRequest.repeatAttempts.insert({it->first, 0});
+    if (timeslot < keyInfo.beginTimeslot || timeslot >= keyInfo.endTimeslot) {
+      sendKeyInterest(it->first, timeslot, keyRequest, callback, timeRange);
+    }
+    else {
+      Name eKeyName(it->first);
+      eKeyName.append(time::toIsoString(keyInfo.beginTimeslot));
+      eKeyName.append(time::toIsoString(keyInfo.endTimeslot));
+      encryptContentKey(keyRequest, keyInfo.keyBits, eKeyName, timeslot, callback);
+    }
+  }
+
+  return contentKeyName;
+}
+
+void
+Producer::produce(Data& data, const system_clock::TimePoint& timeslot,
+                  const uint8_t* content, size_t contentLen)
+{
+  Buffer contentKey;
+
+  Name contentKeyName = createContentKey(timeslot, nullptr);
+  contentKey = m_db.getContentKey(timeslot);
+
+  Name dataName = m_namespace;
+  dataName.append(time::toIsoString(getRoundedTimeslot(timeslot)));
+
+  data.setName(dataName);
+  algo::EncryptParams params(tlv::AlgorithmAesCbc, 16);
+  algo::encryptData(data, content, contentLen, contentKeyName,
+                    contentKey.buf(), contentKey.size(), params);
+  m_keychain.sign(data);
+}
+
+void
+Producer::sendKeyInterest(const Name& name, const system_clock::TimePoint& timeslot,
+                          KeyRequest& keyRequest,
+                          const ProducerEKeyCallback& callback,
+                          const Exclude& timeRange)
+{
+  auto onkey = std::bind(&Producer::handleCoveringKey, this, _1, _2,
+                         std::cref(timeslot), std::ref(keyRequest), callback);
+  auto timeout = std::bind(&Producer::handleTimeout, this, _1,
+                           std::cref(timeslot), std::ref(keyRequest), callback);
+
+  Selectors selector;
+  selector.setExclude(timeRange);
+  selector.setChildSelector(1);
+
+  Interest keyInterest(name);
+  keyInterest.setSelectors(selector);
+
+  m_face.expressInterest(keyInterest, onkey, timeout);
+}
+
+void
+Producer::encryptContentKey(KeyRequest& keyRequest, const Buffer& encryptionKey,
+                            const Name& eKeyName,
+                            const system_clock::TimePoint& timeslot,
+                            const ProducerEKeyCallback& callback)
+{
+  Name keyName = m_namespace;
+  keyName.append(NAME_COMPONENT_C_KEY);
+  keyName.append(time::toIsoString(getRoundedTimeslot(timeslot)));
+
+  Buffer contentKey = m_db.getContentKey(timeslot);
+
+  Data cKeyData;
+  cKeyData.setName(keyName);
+  algo::EncryptParams params(tlv::AlgorithmRsaOaep);
+  algo::encryptData(cKeyData, contentKey.buf(), contentKey.size(), eKeyName,
+                    encryptionKey.buf(), encryptionKey.size(), params);
+  m_keychain.sign(cKeyData);
+  keyRequest.encryptedKeys.push_back(cKeyData);
+
+  keyRequest.interestCount--;
+  if (keyRequest.interestCount == 0 && callback) {
+    callback(keyRequest.encryptedKeys);
+    m_keyRequests.erase(toUnixTimestamp(timeslot).count());
+  }
+}
+
+void
+Producer::handleCoveringKey(const Interest& interest, Data& data,
+                            const system_clock::TimePoint& timeslot,
+                            KeyRequest& keyRequest,
+                            const ProducerEKeyCallback& callback)
+{
+  Name interestName = interest.getName();
+  Name keyName = data.getName();
+
+  system_clock::TimePoint begin = time::fromIsoString(keyName.get(startTs).toUri());
+  system_clock::TimePoint end = time::fromIsoString(keyName.get(endTs).toUri());
+
+  if (timeslot >= end) {
+    Exclude timeRange = interest.getSelectors().getExclude();
+    timeRange.excludeBefore(keyName.get(startTs));
+    keyRequest.repeatAttempts[interestName] = 0;
+    sendKeyInterest(interestName, timeslot, keyRequest, callback, timeRange);
+    return;
+  }
+
+  const Block keyBlock = data.getContent();
+  Buffer encryptionKey(keyBlock.value(), keyBlock.value_size());
+  m_ekeyInfo[interestName].beginTimeslot = begin;
+  m_ekeyInfo[interestName].endTimeslot = end;
+  m_ekeyInfo[interestName].keyBits = encryptionKey;
+
+  encryptContentKey(keyRequest, encryptionKey, keyName, timeslot, callback);
+}
+
+void
+Producer::handleTimeout(const Interest& interest,
+                        const system_clock::TimePoint& timeslot,
+                        KeyRequest& keyRequest,
+                        const ProducerEKeyCallback& callback)
+{
+  Name interestName = interest.getName();
+
+  if (keyRequest.repeatAttempts[interestName] < m_maxRepeatAttempts) {
+    keyRequest.repeatAttempts[interestName]++;
+    sendKeyInterest(interestName, timeslot, keyRequest, callback,
+                    interest.getSelectors().getExclude());
+  }
+  else {
+    keyRequest.interestCount--;
+  }
+
+  if (keyRequest.interestCount == 0 && callback) {
+    callback(keyRequest.encryptedKeys);
+    m_keyRequests.erase(toUnixTimestamp(timeslot).count());
+  }
+}
+
+} // namespace gep
+} // namespace ndn
\ No newline at end of file
diff --git a/src/producer.hpp b/src/producer.hpp
new file mode 100644
index 0000000..c3cd14e
--- /dev/null
+++ b/src/producer.hpp
@@ -0,0 +1,150 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California
+ *
+ * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
+ * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
+ *
+ * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-group-encrypt, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Prashanth Swaminathan <prashanthsw@gmail.com>
+ */
+
+#ifndef NDN_GEP_PRODUCER_HPP
+#define NDN_GEP_PRODUCER_HPP
+
+#include "producer-db.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/face.hpp>
+
+namespace ndn {
+namespace gep {
+
+// @brief Callback returns vector of Data contains content keys encrypted by E-KEYS
+typedef function<void(const std::vector<Data>&)> ProducerEKeyCallback;
+
+/**
+ * @brief Manage content key and data encryption
+ */
+class Producer
+{
+public:
+  struct KeyInfo {
+    time::system_clock::TimePoint beginTimeslot;
+    time::system_clock::TimePoint endTimeslot;
+    Buffer keyBits;
+  };
+
+  struct KeyRequest {
+    KeyRequest(size_t interests)
+    : interestCount(interests)
+    {}
+    size_t interestCount;
+    std::unordered_map<Name, size_t> repeatAttempts;
+    std::vector<Data> encryptedKeys;
+  };
+
+public:
+  /**
+   * @brief Construct a producer
+   *
+   * A producer can produce data with a naming convention:
+   *   /<@p prefix>/SAMPLES/<@p dataType>/[timestamp]
+   *
+   * The produced data packet is encrypted with a content key,
+   * which is stored in a database at @p dbPath.
+   *
+   * A producer also need to produce data containing content key
+   * encrypted with E-KEYs. A producer can retrieve E-KEYs through
+   * @p face, and will re-try for at most @p repeatAttemps times when
+   * E-KEY retrieval fails.
+   */
+  Producer(const Name& prefix, const Name& dataType,
+           Face& face, const std::string& dbPath, uint8_t repeatAttempts = 3);
+
+  /**
+   * @brief Create content key
+   *
+   * This method will first check if the content key exists. For existing
+   * content key, the method will return content key name directly.
+   * If the key does not exist, the method will create one and encrypt
+   * it using corresponding E-KEY. The encrypted content keys will be
+   * passed back through @p callback.
+   */
+  Name
+  createContentKey(const time::system_clock::TimePoint& timeslot,
+                   const ProducerEKeyCallback& callback);
+
+  /**
+   * @brief Produce an data packet encrypted using corresponding content key
+   *
+   * This method encrypts @p content with a content key covering
+   * @p timeslot, and set @p data with the encrypted content and
+   * appropriate data name.
+   */
+  void
+  produce(Data& data, const time::system_clock::TimePoint& timeslot,
+          const uint8_t* content, size_t contentLen);
+
+private:
+
+  /**
+   * @brief Sends interest through face with necessary callbacks
+   *        Uses @p exclude to limit interest if specified
+   */
+  void
+  sendKeyInterest(const Name& name, const time::system_clock::TimePoint& timeslot,
+                  KeyRequest& keyRequest, const ProducerEKeyCallback& callback,
+                  const Exclude& timeRange = Exclude());
+
+  /**
+   * @brief Updates state in @p keyRequest on timeout
+   */
+  void
+  handleTimeout(const Interest& interest,
+                const time::system_clock::TimePoint& timeslot,
+                KeyRequest& keyRequest, const ProducerEKeyCallback& callback);
+
+  /**
+   * @brief Checks that encryption key contained in @p data fits @p timeslot
+   *        Sends refined interest if required
+   */
+  void
+  handleCoveringKey(const Interest& interest, Data& data,
+                    const time::system_clock::TimePoint& timeslot,
+                    KeyRequest& keyRequest, const ProducerEKeyCallback& callback);
+
+  /**
+   * @brief Encrypts content key for @p timeslot with @p encryptionKey
+   *        Fires @p callback if no more interests to process
+   */
+  void
+  encryptContentKey(KeyRequest& keyRequest, const Buffer& encryptionKey,
+                    const Name& eKeyName,
+                    const time::system_clock::TimePoint& timeslot,
+                    const ProducerEKeyCallback& callback);
+
+private:
+  Face& m_face;
+  Name m_namespace;
+  KeyChain m_keychain;
+  std::unordered_map<Name, KeyInfo> m_ekeyInfo;
+  std::unordered_map<uint64_t, KeyRequest> m_keyRequests;
+  ProducerDB m_db;
+  uint8_t m_maxRepeatAttempts;
+};
+
+} // namespace gep
+} // namespace ndn
+
+#endif // NDN_GEP_PRODUCER_HPP
diff --git a/tests/unit-tests/producer-db.t.cpp b/tests/unit-tests/producer-db.t.cpp
new file mode 100644
index 0000000..bcf09e0
--- /dev/null
+++ b/tests/unit-tests/producer-db.t.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California
+ *
+ * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
+ * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
+ *
+ * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-group-encrypt, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Prashanth Swaminathan <prashanthsw@gmail.com>
+ */
+
+#include "producer-db.hpp"
+#include "algo/aes.hpp"
+#include "boost-test.hpp"
+
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace gep {
+namespace tests {
+
+using time::system_clock;
+
+class ProducerDBFixture
+{
+public:
+  ProducerDBFixture()
+    : tmpPath(boost::filesystem::path(TMP_TESTS_PATH))
+  {
+    boost::filesystem::create_directories(tmpPath);
+  }
+
+  ~ProducerDBFixture()
+  {
+    boost::filesystem::remove_all(tmpPath);
+  }
+
+public:
+  boost::filesystem::path tmpPath;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestProducerDB, ProducerDBFixture)
+
+BOOST_AUTO_TEST_CASE(DatabaseFunctions)
+{
+  // construction
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+  ProducerDB db(dbDir);
+
+  // create member
+  RandomNumberGenerator rng;
+  AesKeyParams params(128);
+  Buffer keyBuf1 = algo::Aes::generateKey(rng, params).getKeyBits();
+  Buffer keyBuf2 = algo::Aes::generateKey(rng, params).getKeyBits();
+
+  system_clock::TimePoint point1(time::fromIsoString("20150101T100000"));
+  system_clock::TimePoint point2(time::fromIsoString("20150102T100000"));
+  system_clock::TimePoint point3(time::fromIsoString("20150103T100000"));
+  system_clock::TimePoint point4(time::fromIsoString("20150104T100000"));
+
+  // add keys into the database
+  BOOST_CHECK_NO_THROW(db.addContentKey(point1, keyBuf1));
+  BOOST_CHECK_NO_THROW(db.addContentKey(point2, keyBuf1));
+  BOOST_CHECK_NO_THROW(db.addContentKey(point3, keyBuf2));
+
+  // throw exception when adding a key to an existing timeslot
+  BOOST_CHECK_THROW(db.addContentKey(point1, keyBuf1), ProducerDB::Error);
+
+  // has function
+  BOOST_CHECK_EQUAL(db.hasContentKey(point1), true);
+  BOOST_CHECK_EQUAL(db.hasContentKey(point2), true);
+  BOOST_CHECK_EQUAL(db.hasContentKey(point3), true);
+  BOOST_CHECK_EQUAL(db.hasContentKey(point4), false);
+
+  // get content key
+  Buffer keyResult = db.getContentKey(point1);
+  BOOST_CHECK_EQUAL_COLLECTIONS(keyResult.begin(),
+                                keyResult.end(),
+                                keyBuf1.begin(),
+                                keyBuf1.end());
+
+  keyResult = db.getContentKey(point3);
+  BOOST_CHECK_EQUAL_COLLECTIONS(keyResult.begin(),
+                                keyResult.end(),
+                                keyBuf2.begin(),
+                                keyBuf2.end());
+
+  // throw exception when there is no such timeslot in database
+  BOOST_CHECK_THROW(db.getContentKey(point4), ProducerDB::Error);
+
+  // delete content key
+  BOOST_CHECK_EQUAL(db.hasContentKey(point1), true);
+  db.deleteContentKey(point1);
+  BOOST_CHECK_EQUAL(db.hasContentKey(point1), false);
+
+  // delete at a non-existing timeslot
+  BOOST_CHECK_NO_THROW(db.deleteContentKey(point4));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace gep
+} // namespace ndn
diff --git a/tests/unit-tests/producer.t.cpp b/tests/unit-tests/producer.t.cpp
new file mode 100755
index 0000000..84ed8c3
--- /dev/null
+++ b/tests/unit-tests/producer.t.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California
+ *
+ * This file is part of ndn-group-encrypt (Group-based Encryption Protocol for NDN).
+ * See AUTHORS.md for complete list of ndn-group-encrypt authors and contributors.
+ *
+ * ndn-group-encrypt is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-group-encrypt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-group-encrypt, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "producer.hpp"
+#include "algo/encryptor.hpp"
+#include "algo/rsa.hpp"
+#include "algo/aes.hpp"
+#include "encrypted-content.hpp"
+#include "unit-test-time-fixture.hpp"
+#include "random-number-generator.hpp"
+
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#include "boost-test.hpp"
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace gep {
+namespace tests {
+
+static const uint8_t DATA_CONTEN[] = {
+  0xcb, 0xe5, 0x6a, 0x80, 0x41, 0x24, 0x58, 0x23,
+  0x84, 0x14, 0x15, 0x61, 0x80, 0xb9, 0x5e, 0xbd,
+  0xce, 0x32, 0xb4, 0xbe, 0xbc, 0x91, 0x31, 0xd6,
+  0x19, 0x00, 0x80, 0x8b, 0xfa, 0x00, 0x05, 0x9c
+};
+
+class ProducerFixture : public UnitTestTimeFixture
+{
+public:
+  ProducerFixture()
+    : tmpPath(boost::filesystem::path(TMP_TESTS_PATH))
+    , face1(util::makeDummyClientFace(io, {true, true}))
+    , face2(util::makeDummyClientFace(io, {true, true}))
+    , readInterestOffset1(0)
+    , readDataOffset1(0)
+    , readInterestOffset2(0)
+    , readDataOffset2(0)
+  {
+    boost::filesystem::create_directories(tmpPath);
+  }
+
+  ~ProducerFixture()
+  {
+    boost::filesystem::remove_all(tmpPath);
+  }
+
+  void
+  createEncryptionKey(Name eKeyName, const Name& timeMarker)
+  {
+    RandomNumberGenerator rng;
+    RsaKeyParams params;
+    eKeyName.append(timeMarker);
+
+    Buffer dKeyBuf = algo::Rsa::generateKey(rng, params).getKeyBits();
+    Buffer eKeyBuf = algo::Rsa::deriveEncryptKey(dKeyBuf).getKeyBits();
+    decryptionKeys[eKeyName] = dKeyBuf;
+
+    shared_ptr<Data> keyData = make_shared<Data>(eKeyName);
+    keyData->setContent(eKeyBuf.buf(), eKeyBuf.size());
+    keyChain.sign(*keyData);
+    encryptionKeys[eKeyName] = keyData;
+  }
+
+  bool
+  passPacket()
+  {
+    bool hasPassed = false;
+
+    checkFace(face1->sentInterests, readInterestOffset1, *face2, hasPassed);
+    checkFace(face1->sentDatas, readDataOffset1, *face2, hasPassed);
+    checkFace(face2->sentInterests, readInterestOffset2, *face1, hasPassed);
+    checkFace(face2->sentDatas, readDataOffset2, *face1, hasPassed);
+
+    return hasPassed;
+  }
+
+  template<typename Packet>
+  void
+  checkFace(std::vector<Packet>& receivedPackets,
+            size_t& readPacketOffset,
+            util::DummyClientFace& receiver,
+            bool& hasPassed)
+  {
+    while (receivedPackets.size() > readPacketOffset) {
+      receiver.receive(receivedPackets[readPacketOffset]);
+      readPacketOffset++;
+      hasPassed = true;
+    }
+  }
+
+public:
+  boost::filesystem::path tmpPath;
+
+  shared_ptr<util::DummyClientFace> face1;
+  shared_ptr<util::DummyClientFace> face2;
+
+  size_t readInterestOffset1;
+  size_t readDataOffset1;
+  size_t readInterestOffset2;
+  size_t readDataOffset2;
+
+  KeyChain keyChain;
+
+  std::unordered_map<Name, Buffer> decryptionKeys;
+  std::unordered_map<Name, shared_ptr<Data>> encryptionKeys;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestProducer, ProducerFixture)
+
+BOOST_AUTO_TEST_CASE(ContentKeyRequest)
+{
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+
+  Name prefix("/prefix");
+  Name suffix("/a/b/c");
+  Name expectedInterest = prefix;
+  expectedInterest.append(NAME_COMPONENT_READ);
+  expectedInterest.append(suffix);
+  expectedInterest.append(NAME_COMPONENT_E_KEY);
+
+  Name cKeyName = prefix;
+  cKeyName.append(NAME_COMPONENT_SAMPLE);
+  cKeyName.append(suffix);
+  cKeyName.append(NAME_COMPONENT_C_KEY);
+
+  Name timeMarker("20150101T100000/20150101T120000");
+  time::system_clock::TimePoint testTime1 = time::fromIsoString("20150101T100001");
+  time::system_clock::TimePoint testTime2 = time::fromIsoString("20150101T110001");
+  name::Component testTimeRounded1("20150101T100000");
+  name::Component testTimeRounded2("20150101T110000");
+
+  // Create content keys required for this test case:
+  for (size_t i = 0; i < suffix.size(); i++) {
+    createEncryptionKey(expectedInterest, timeMarker);
+    expectedInterest = expectedInterest.getPrefix(-2).append(NAME_COMPONENT_E_KEY);
+  }
+
+  face2->setInterestFilter(prefix,
+         [&] (const InterestFilter&, const Interest& i) {
+            Name interestName = i.getName();
+            interestName.append(timeMarker);
+            BOOST_REQUIRE_EQUAL(encryptionKeys.find(interestName) !=
+                                encryptionKeys.end(), true);
+            face2->put(*(encryptionKeys[interestName]));
+            return;
+         },
+         RegisterPrefixSuccessCallback(),
+         [] (const Name&, const std::string& e) { });
+
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+
+  /*
+  Verify that content key is correctly encrypted for each domain, and the
+  produce method encrypts provided data with the same content key.
+  */
+  Producer producer(prefix, suffix, *face1, dbDir);
+  ProducerDB testDb(dbDir);
+  Buffer contentKey;
+
+  auto checkEncryptionKeys =
+          [&](const std::vector<Data>& result,
+              const time::system_clock::TimePoint& testTime,
+              const name::Component& roundedTime) {
+            BOOST_CHECK_EQUAL(testDb.hasContentKey(testTime), true);
+            contentKey = testDb.getContentKey(testTime);
+
+            algo::EncryptParams params(tlv::AlgorithmRsaOaep);
+            std::vector<Data>::const_iterator it;
+            for (it = result.begin(); it != result.end(); ++it) {
+              Name keyName = it->getName();
+              BOOST_CHECK_EQUAL(keyName.getSubName(0,6), cKeyName);
+              BOOST_CHECK_EQUAL(keyName.get(6), roundedTime);
+              BOOST_CHECK_EQUAL(keyName.get(7), NAME_COMPONENT_FOR);
+              BOOST_CHECK_EQUAL(decryptionKeys.find(keyName.getSubName(8)) !=
+                                decryptionKeys.end(), true);
+              Name testName = it->getName().getSubName(-8);
+              Buffer decryptionKey;
+
+              decryptionKey = decryptionKeys.at(keyName.getSubName(8));
+              BOOST_CHECK_EQUAL(decryptionKey.size() != 0, true);
+              Block encryptedKeyBlock = it->getContent();
+              encryptedKeyBlock.parse();
+
+              EncryptedContent content(*(encryptedKeyBlock.elements_begin()));
+              const Buffer& encryptedKey = content.getPayload();
+              Buffer retrievedKey = algo::Rsa::decrypt(decryptionKey.buf(),
+                                                       decryptionKey.size(),
+                                                       encryptedKey.buf(),
+                                                       encryptedKey.size(),
+                                                       params);
+
+              BOOST_CHECK_EQUAL_COLLECTIONS(contentKey.begin(),
+                                            contentKey.end(),
+                                            retrievedKey.begin(),
+                                            retrievedKey.end());
+            }
+            BOOST_CHECK_EQUAL(result.size(), 3);
+          };
+
+  // Initial test to confirm that keys are created for this timeslot
+  Name contentKeyName1 =
+      producer.createContentKey(testTime1,
+      std::bind(checkEncryptionKeys, _1, testTime1, testTimeRounded1));
+
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+
+  // Verify that we do not repeat the search for e-keys, don't advance clock
+  Name contentKeyName2 =
+      producer.createContentKey(testTime2,
+      std::bind(checkEncryptionKeys, _1, testTime2, testTimeRounded2));
+
+  // Confirm content key names are correct
+  BOOST_CHECK_EQUAL(contentKeyName1.getPrefix(-1), cKeyName);
+  BOOST_CHECK_EQUAL(contentKeyName1.get(6), testTimeRounded1);
+  BOOST_CHECK_EQUAL(contentKeyName2.getPrefix(-1), cKeyName);
+  BOOST_CHECK_EQUAL(contentKeyName2.get(6), testTimeRounded2);
+
+  // Confirm produce encrypts with correct key and has right name
+  Data testData;
+  producer.produce(testData, testTime2, DATA_CONTEN, sizeof(DATA_CONTEN));
+
+  Name producedName = testData.getName();
+  BOOST_CHECK_EQUAL(producedName.getSubName(0,5), cKeyName.getPrefix(-1));
+  BOOST_CHECK_EQUAL(producedName.get(5), testTimeRounded2);
+  BOOST_CHECK_EQUAL(producedName.get(6), NAME_COMPONENT_FOR);
+  BOOST_CHECK_EQUAL(producedName.getSubName(7,6), cKeyName);
+  BOOST_CHECK_EQUAL(producedName.get(13), testTimeRounded2);
+
+  Block dataBlock = testData.getContent();
+  dataBlock.parse();
+
+  EncryptedContent dataContent(*(dataBlock).elements_begin());
+  const Buffer& encData = dataContent.getPayload();
+  const Buffer& iv = dataContent.getInitialVector();
+
+  algo::EncryptParams params(tlv::AlgorithmAesCbc, 16);
+  params.setIV(iv.buf(), iv.size());
+  Buffer decryptTest = algo::Aes::decrypt(contentKey.buf(), contentKey.size(),
+                                          encData.buf(), encData.size(), params);
+  BOOST_CHECK_EQUAL_COLLECTIONS(decryptTest.begin(),
+                                decryptTest.end(),
+                                DATA_CONTEN,
+                                DATA_CONTEN + sizeof(DATA_CONTEN));
+}
+
+BOOST_AUTO_TEST_CASE(ContentKeySearch)
+{
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+
+  Name timeMarkerFirstHop("20150101T070000/20150101T080000");
+  Name timeMarkerSecondHop("20150101T080000/20150101T090000");
+  Name timeMarkerThirdHop("20150101T100000/20150101T110000");
+
+  Name prefix("/prefix");
+  Name suffix("/suffix");
+  Name expectedInterest = prefix;
+  expectedInterest.append(NAME_COMPONENT_READ);
+  expectedInterest.append(suffix);
+  expectedInterest.append(NAME_COMPONENT_E_KEY);
+
+  Name cKeyName = prefix;
+  cKeyName.append(NAME_COMPONENT_SAMPLE);
+  cKeyName.append(suffix);
+  cKeyName.append(NAME_COMPONENT_C_KEY);
+
+  time::system_clock::TimePoint testTime = time::fromIsoString("20150101T100001");
+
+  // Create content keys required for this test case:
+  createEncryptionKey(expectedInterest, timeMarkerFirstHop);
+  createEncryptionKey(expectedInterest, timeMarkerSecondHop);
+  createEncryptionKey(expectedInterest, timeMarkerThirdHop);
+
+  size_t requestCount = 0;
+  face2->setInterestFilter(prefix,
+         [&] (const InterestFilter&, const Interest& i) {
+            BOOST_REQUIRE_EQUAL(i.getName(), expectedInterest);
+            Name interestName = i.getName();
+            switch(requestCount) {
+              case 0:
+                interestName.append(timeMarkerFirstHop);
+                break;
+
+              case 1:
+                interestName.append(timeMarkerSecondHop);
+                break;
+
+              case 2:
+                interestName.append(timeMarkerThirdHop);
+                break;
+
+              default:
+                break;
+            }
+            face2->put(*(encryptionKeys[interestName]));
+            requestCount++;
+            return;
+         },
+         RegisterPrefixSuccessCallback(),
+         [] (const Name&, const std::string& e) { });
+
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+
+  /*
+  Verify that if a key is found, but not within the right timeslot, the search
+  is refined until a valid timeslot is found.
+  */
+  Producer producer(prefix, suffix, *face1, dbDir);
+  producer.createContentKey(testTime,
+          [&](const std::vector<Data>& result){
+            BOOST_CHECK_EQUAL(requestCount, 3);
+            BOOST_CHECK_EQUAL(result.size(), 1);
+
+            Data keyData = result[0];
+            Name keyName = keyData.getName();
+            BOOST_CHECK_EQUAL(keyName.getSubName(0,4), cKeyName);
+            BOOST_CHECK_EQUAL(keyName.get(4), timeMarkerThirdHop[0]);
+            BOOST_CHECK_EQUAL(keyName.get(5), NAME_COMPONENT_FOR);
+            BOOST_CHECK_EQUAL(keyName.getSubName(6),
+                              expectedInterest.append(timeMarkerThirdHop));
+          });
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+}
+
+BOOST_AUTO_TEST_CASE(ContentKeyTimeout)
+{
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+
+  Name prefix("/prefix");
+  Name suffix("/suffix");
+  Name expectedInterest = prefix;
+  expectedInterest.append(NAME_COMPONENT_READ);
+  expectedInterest.append(suffix);
+  expectedInterest.append(NAME_COMPONENT_E_KEY);
+
+  time::system_clock::TimePoint testTime = time::fromIsoString("20150101T100001");
+
+  size_t timeoutCount = 0;
+  face2->setInterestFilter(prefix,
+         [&] (const InterestFilter&, const Interest& i) {
+            BOOST_CHECK_EQUAL(i.getName(), expectedInterest);
+            timeoutCount++;
+            return;
+         },
+         RegisterPrefixSuccessCallback(),
+         [] (const Name&, const std::string& e) { });
+
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+
+  /*
+  Verify that if no response is received, the producer appropriately times out.
+  The result vector should not contain elements that have timed out.
+  */
+  Producer producer(prefix, suffix, *face1, dbDir);
+  producer.createContentKey(testTime,
+          [&](const std::vector<Data>& result){
+            BOOST_CHECK_EQUAL(timeoutCount, 4);
+            BOOST_CHECK_EQUAL(result.size(), 0);
+          });
+
+  do {
+    advanceClocks(time::milliseconds(10), 500);
+  } while (passPacket());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace gep
+} // namespace ndn