Add Consumer

Change-Id: Ic94cde3c24c86074c509b77608403aec54b95803
Refs: #3192
diff --git a/src/consumer-db.cpp b/src/consumer-db.cpp
new file mode 100644
index 0000000..6baa696
--- /dev/null
+++ b/src/consumer-db.cpp
@@ -0,0 +1,130 @@
+/* -*- 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 Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ */
+
+#include "consumer-db.hpp"
+
+#include <sqlite3.h>
+#include <boost/filesystem.hpp>
+#include <ndn-cxx/util/sqlite3-statement.hpp>
+
+namespace ndn {
+namespace gep {
+
+using util::Sqlite3Statement;
+
+static const std::string INITIALIZATION =
+  "CREATE TABLE IF NOT EXISTS                         \n"
+  "  decryptionkeys(                                  \n"
+  "    key_id              INTEGER PRIMARY KEY,       \n"
+  "    key_name            BLOB NOT NULL,             \n"
+  "    key_buf             BLOB NOT NULL              \n"
+  "  );                                               \n"
+  "CREATE UNIQUE INDEX IF NOT EXISTS                  \n"
+  "   KeyNameIndex ON decryptionkeys(key_name);       \n"
+  "CREATE TABLE IF NOT EXISTS                         \n"
+  "  consumer(                                        \n"
+  "    prefix              BLOB PRIMARY KEY           \n"
+  "  );                                               \n";
+
+class ConsumerDB::Impl
+{
+public:
+  Impl(const std::string& dbDir)
+  {
+    // open Database
+
+    int result = sqlite3_open_v2(dbDir.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("GroupManager DB cannot be opened/created: " + dbDir));
+
+    // 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("GroupManager DB cannot be initialized"));
+    }
+  }
+
+  ~Impl()
+  {
+    sqlite3_close(m_database);
+  }
+
+public:
+  sqlite3* m_database;
+};
+
+
+ConsumerDB::ConsumerDB(const std::string& dbDir)
+  : m_impl(new Impl(dbDir))
+{
+}
+
+ConsumerDB::~ConsumerDB() = default;
+
+const Buffer
+ConsumerDB::getKey(const Name& keyName) const
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "SELECT key_buf FROM decryptionkeys\
+                              WHERE key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+
+  Buffer result;
+  if (statement.step() == SQLITE_ROW) {
+    result = Buffer(statement.getBlob(0), statement.getSize(0));
+  }
+  return result;
+}
+
+void
+ConsumerDB::addKey(const Name& keyName, const Buffer& keyBuf)
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "INSERT INTO decryptionkeys(key_name, key_buf)\
+                              values (?, ?)");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(2, keyBuf.buf(), keyBuf.size(), SQLITE_TRANSIENT);
+
+  if (statement.step() != SQLITE_DONE)
+    BOOST_THROW_EXCEPTION(Error("Cannot add the key to database"));
+}
+
+void
+ConsumerDB::deleteKey(const Name& keyName)
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "DELETE FROM decryptionkeys WHERE key_name=?");
+  statement.bind(1, keyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
+} // namespace gep
+} // namespace ndn
diff --git a/src/consumer-db.hpp b/src/consumer-db.hpp
new file mode 100644
index 0000000..4b1f971
--- /dev/null
+++ b/src/consumer-db.hpp
@@ -0,0 +1,83 @@
+/* -*- 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 Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ */
+
+#ifndef NDN_GEP_CONSUMER_DB_HPP
+#define NDN_GEP_CONSUMER_DB_HPP
+
+#include "common.hpp"
+
+namespace ndn {
+namespace gep {
+
+/**
+ * @brief ConsumerDB is a class to manage decryption keys for consumer.
+ */
+class ConsumerDB
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
+  explicit
+  ConsumerDB(const std::string& dbDir);
+
+  ~ConsumerDB();
+
+public:
+  /**
+   * @brief Get the key with @p keyName from database.
+   *
+   * @return Empty buffer when there is no key with @p keyName in database
+   */
+  const Buffer
+  getKey(const Name& keyName) const;
+
+  /**
+   * @brief Add the key with @p keyName and @p keyBuf to database.
+   *
+   * @throw Error when a key with the same name already exists in database.
+   */
+  void
+  addKey(const Name& keyName, const Buffer& keyBuf);
+
+  /**
+   * @brief Remove the key with @p keyName from the database.
+   */
+  void
+  deleteKey(const Name& keyName);
+
+private:
+  class Impl;
+  unique_ptr<Impl> m_impl;
+};
+
+} // namespace gep
+} // namespace ndn
+
+#endif // NDN_GEP_CONSUMER_DB_HPP
diff --git a/src/consumer.cpp b/src/consumer.cpp
new file mode 100644
index 0000000..f515652
--- /dev/null
+++ b/src/consumer.cpp
@@ -0,0 +1,284 @@
+/* -*- 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 Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ * @author Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#include "consumer.hpp"
+#include "encrypted-content.hpp"
+
+namespace ndn {
+namespace gep {
+
+// public
+Consumer::Consumer(Face& face, const Name& groupName, const Name& consumerName, const std::string& dbDir)
+  : m_db(dbDir)
+  , m_validator(new ValidatorNull)
+  , m_face(face)
+  , m_groupName(groupName)
+  , m_consumerName(consumerName)
+{
+}
+
+void
+Consumer::setGroup(const Name& groupName)
+{
+  m_groupName = groupName;
+}
+
+void
+Consumer::addDecryptionKey(const Name& keyName, const Buffer& keyBuf)
+{
+  BOOST_ASSERT(m_consumerName.isPrefixOf(keyName));
+
+  m_db.addKey(keyName, keyBuf);
+}
+
+void
+Consumer::consume(const Name& contentName,
+                  const ConsumptionCallBack& consumptionCallBack,
+                  const ErrorCallBack& errorCallBack)
+{
+  shared_ptr<Interest> interest = make_shared<Interest>(contentName);
+
+  // prepare callback functions
+  auto onData = [=] (const Interest& contentInterest, const Data& contentData) {
+    if (!contentInterest.matchesData(contentData))
+      return;
+
+    this->m_validator->validate(contentData,
+      [=] (const shared_ptr<const Data>& validData) {
+        // decrypt content
+        decryptContent(*validData,
+                       [=] (const Buffer& plainText) {consumptionCallBack(contentData, plainText);},
+                       errorCallBack);
+      },
+      [=] (const shared_ptr<const Data>& d, const std::string& e) {
+        errorCallBack(ErrorCode::Validation, e);
+      });
+  };
+
+  auto onTimeout = [=] (const Interest& contentInterest) {
+    // we should re-try at least once.
+    this->m_face.expressInterest(*interest, onData,
+      [=] (const Interest& contentInterest) {
+        errorCallBack(ErrorCode::Timeout, interest->getName().toUri());
+      });
+  };
+
+  // express Interest packet
+  m_face.expressInterest(*interest, onData, onTimeout);
+}
+
+// private
+
+void
+Consumer::decrypt(const Block& encryptedBlock,
+                  const Buffer& keyBits,
+                  const PlainTextCallBack& plainTextCallBack,
+                  const ErrorCallBack& errorCallBack)
+{
+  EncryptedContent encryptedContent(encryptedBlock);
+  const Buffer& payload = encryptedContent.getPayload();
+
+  switch (encryptedContent.getAlgorithmType()) {
+    case tlv::AlgorithmAesCbc: {
+      // prepare parameter
+      algo::EncryptParams decryptParams(tlv::AlgorithmAesCbc);
+      decryptParams.setIV(encryptedContent.getInitialVector().buf(),
+                          encryptedContent.getInitialVector().size());
+
+      // decrypt content
+      Buffer content = algo::Aes::decrypt(keyBits.buf(), keyBits.size(),
+                                          payload.buf(), payload.size(),
+                                          decryptParams);
+      plainTextCallBack(content);
+      break;
+    }
+    case tlv::AlgorithmRsaOaep: {
+      // prepare parameter
+      algo::EncryptParams decryptParams(tlv::AlgorithmRsaOaep);
+
+      // decrypt content
+      Buffer content = algo::Rsa::decrypt(keyBits.buf(), keyBits.size(),
+                                          payload.buf(), payload.size(),
+                                          decryptParams);
+      plainTextCallBack(content);
+      break;
+    }
+    default: {
+      errorCallBack(ErrorCode::UnsupportedEncryptionScheme,
+                    std::to_string(encryptedContent.getAlgorithmType()));
+    }
+  }
+}
+
+void
+Consumer::decryptContent(const Data& data,
+                         const PlainTextCallBack& plainTextCallBack,
+                         const ErrorCallBack& errorCallBack)
+{
+  // get encrypted content
+  Block encryptedContent = data.getContent().blockFromValue();
+  Name cKeyName = EncryptedContent(encryptedContent).getKeyLocator().getName();
+
+  // check if content key already in store
+  auto it = m_cKeyMap.find(cKeyName);
+
+  if (it != m_cKeyMap.end()) { // decrypt content directly
+    decrypt(encryptedContent, it->second, plainTextCallBack, errorCallBack);
+  }
+  else {
+    // retrieve the C-Key Data from network
+    Name interestName = cKeyName;
+    interestName.append(NAME_COMPONENT_FOR).append(m_groupName);
+    shared_ptr<Interest> interest = make_shared<Interest>(interestName);
+
+    // prepare callback functions
+    auto onData = [=] (const Interest& cKeyInterest, const Data& cKeyData) {
+      if (!cKeyInterest.matchesData(cKeyData))
+        return;
+
+      this->m_validator->validate(cKeyData,
+        [=] (const shared_ptr<const Data>& validCKeyData) {
+          decryptCKey(*validCKeyData,
+                      [=] (const Buffer& cKeyBits) {
+                        decrypt(encryptedContent, cKeyBits, plainTextCallBack, errorCallBack);
+                        this->m_cKeyMap.insert(std::make_pair(cKeyName, cKeyBits));
+                      },
+                      errorCallBack);},
+        [=] (const shared_ptr<const Data>& d, const std::string& e) {
+          errorCallBack(ErrorCode::Validation, e);
+        });
+    };
+
+    auto onTimeout = [=] (const Interest& cKeyInterest) {
+      // we should re-try at least once.
+      this->m_face.expressInterest(*interest, onData,
+        [=] (const Interest& contentInterest) {
+          errorCallBack(ErrorCode::Timeout, interest->getName().toUri());
+        });
+    };
+
+    // express Interest packet
+    m_face.expressInterest(*interest, onData, onTimeout);
+  }
+}
+
+void
+Consumer::decryptCKey(const Data& cKeyData,
+                      const PlainTextCallBack& plainTextCallBack,
+                      const ErrorCallBack& errorCallBack)
+{
+  // get encrypted content
+  Block cKeyContent = cKeyData.getContent().blockFromValue();
+  Name eKeyName = EncryptedContent(cKeyContent).getKeyLocator().getName();
+  Name dKeyName = eKeyName.getPrefix(-3);
+  dKeyName.append(NAME_COMPONENT_D_KEY).append(eKeyName.getSubName(-2));
+
+  // check if decryption key already in store
+  auto it = m_dKeyMap.find(dKeyName);
+
+  if (it != m_dKeyMap.end()) { // decrypt C-Key directly
+    decrypt(cKeyContent, it->second, plainTextCallBack, errorCallBack);
+  }
+  else {
+    // get the D-Key Data
+    Name interestName = dKeyName;
+    interestName.append(NAME_COMPONENT_FOR).append(m_consumerName);
+    shared_ptr<Interest> interest = make_shared<Interest>(dKeyName);
+
+    // prepare callback functions
+    auto onData = [=] (const Interest& dKeyInterest, const Data& dKeyData) {
+      if (!dKeyInterest.matchesData(dKeyData))
+        return;
+
+      this->m_validator->validate(dKeyData,
+        [=] (const shared_ptr<const Data>& validDKeyData) {
+          decryptDKey(*validDKeyData,
+                      [=] (const Buffer& dKeyBits) {
+                        decrypt(cKeyContent, dKeyBits, plainTextCallBack, errorCallBack);
+                        this->m_dKeyMap.insert(std::make_pair(dKeyName, dKeyBits));
+                      },
+                      errorCallBack);},
+        [=] (const shared_ptr<const Data>& d, const std::string& e) {
+          errorCallBack(ErrorCode::Validation, e);
+        });
+    };
+
+    auto onTimeout = [=] (const Interest& dKeyInterest) {
+      // we should re-try at least once.
+      this->m_face.expressInterest(*interest, onData,
+        [=] (const Interest& contentInterest) {
+          errorCallBack(ErrorCode::Timeout, interest->getName().toUri());
+        });
+    };
+
+    // express Interest packet
+    m_face.expressInterest(*interest, onData, onTimeout);
+  }
+}
+
+void
+Consumer::decryptDKey(const Data& dKeyData,
+                      const PlainTextCallBack& plainTextCallBack,
+                      const ErrorCallBack& errorCallBack)
+{
+  // get encrypted content
+  Block dataContent = dKeyData.getContent();
+  dataContent.parse();
+
+  if (dataContent.elements_size() != 2)
+    errorCallBack(ErrorCode::InvalidEncryptedFormat,
+                  "Data packet does not satisfy D-KEY packet format");
+
+  // process nonce;
+  auto it = dataContent.elements_begin();
+  Block encryptedNonceBlock = *it;
+  EncryptedContent encryptedNonce(encryptedNonceBlock);
+  Name consumerKeyName = encryptedNonce.getKeyLocator().getName();
+
+  // get consumer decryption key
+  Buffer consumerKeyBuf = getDecryptionKey(consumerKeyName);
+  if (consumerKeyBuf.empty()) {
+    errorCallBack(ErrorCode::NoDecryptKey,
+                  "No desired consumer decryption key in database");
+    return;
+  }
+
+  // process d-key
+  it++;
+  Block encryptedPayloadBlock = *it;
+
+  // decrypt d-key
+  decrypt(encryptedNonceBlock, consumerKeyBuf,
+          [&] (const Buffer& nonceKeyBits) {
+            decrypt(encryptedPayloadBlock, nonceKeyBits, plainTextCallBack, errorCallBack);
+          },
+          errorCallBack);
+}
+
+const Buffer
+Consumer::getDecryptionKey(const Name& decryptionKeyName)
+{
+  return m_db.getKey(decryptionKeyName);
+}
+
+} // namespace gep
+} // namespace ndn
diff --git a/src/consumer.hpp b/src/consumer.hpp
new file mode 100644
index 0000000..5da849b
--- /dev/null
+++ b/src/consumer.hpp
@@ -0,0 +1,147 @@
+/* -*- 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 Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ * @author Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#ifndef NDN_GEP_CONSUMER_HPP
+#define NDN_GEP_CONSUMER_HPP
+
+#include "algo/rsa.hpp"
+#include "algo/aes.hpp"
+#include "consumer-db.hpp"
+#include "error-code.hpp"
+
+#include <ndn-cxx/security/validator-null.hpp>
+#include <ndn-cxx/face.hpp>
+
+namespace ndn {
+namespace gep {
+
+typedef function<void (const Data&, const Buffer&)> ConsumptionCallBack;
+
+/**
+ * @brief Consumer in group-based encryption protocol
+ */
+class Consumer
+{
+private:
+  typedef function<void (const Buffer&)> PlainTextCallBack;
+
+public:
+  /**
+   * @brief Create a consumer instance
+   *
+   * @param face The face used for key fetching
+   * @param groupName The reading group name that the consumer belongs to
+   * @param consumerName The identity of the consumer
+   * @param dbDir The path to database storing decryption key
+   */
+  Consumer(Face& face, const Name& groupName, const Name& consumerName, const std::string& dbDir);
+
+  /**
+   * @brief Send out the Interest packet to fetch content packet with @p dataName.
+   *
+   * @param consumptionCallBack The callback when requested data is decrypted
+   * @param errorCallBack The callback when error happens in consumption
+   */
+  void
+  consume(const Name& dataName,
+          const ConsumptionCallBack& consumptionCallBack,
+          const ErrorCallBack& errorCallBack);
+
+  /**
+   * @brief Set the group name to @p groupName.
+   */
+  void
+  setGroup(const Name& groupName);
+
+  /**
+   * @brief Add new decryption key with @p keyName and @p keyBuf.
+   */
+  void
+  addDecryptionKey(const Name& keyName, const Buffer& keyBuf);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+
+  /**
+   * @brief Decrypt @p encryptedBlock using @p keyBits
+   *
+   * Invoke @p plainTextCallBack when block is decrypted, otherwise @p errorCallBack.
+   */
+  void
+  decrypt(const Block& encryptedBlock,
+          const Buffer& keyBits,
+          const PlainTextCallBack& plainTextCallBack,
+          const ErrorCallBack& errorCallBack);
+
+  /**
+   * @brief Decrypt @p data.
+   *
+   * Invoke @p plainTextCallBack when block is decrypted, otherwise @p errorCallBack.
+   */
+  void
+  decryptContent(const Data& data,
+                 const PlainTextCallBack& plainTextCallBack,
+                 const ErrorCallBack& errorCallBack);
+
+  /**
+   * @brief Decrypt @p cKeyData.
+   *
+   * Invoke @p plainTextCallBack when block is decrypted, otherwise @p errorCallBack.
+   */
+  void
+  decryptCKey(const Data& cKeyData,
+              const PlainTextCallBack& plainTextCallBack,
+              const ErrorCallBack& errorCallBack);
+
+  /**
+   * @brief Decrypt @p dKeyData.
+   *
+   * Invoke @p plainTextCallBack when block is decrypted, otherwise @p errorCallBack.
+   */
+  void
+  decryptDKey(const Data& dKeyData,
+              const PlainTextCallBack& plainTextCallBack,
+              const ErrorCallBack& errorCallBack);
+
+
+  /**
+   * @brief Get the buffer of decryption key with @p decryptionKeyName from database.
+   *
+   * @return Null buffer when there is no decryption key with @p decryptionKeyName.
+   */
+  const Buffer
+  getDecryptionKey(const Name& decryptionKeyName);
+
+private:
+  ConsumerDB m_db;
+  unique_ptr<Validator> m_validator;
+  Face& m_face;
+  Name m_groupName;
+  Name m_consumerName;
+
+  std::map<Name, Buffer> m_cKeyMap;
+  std::map<Name, Buffer> m_dKeyMap;
+};
+
+} // namespace gep
+} // namespace ndn
+
+#endif // NDN_GEP_CONSUMER_HPP
diff --git a/src/error-code.hpp b/src/error-code.hpp
new file mode 100644
index 0000000..3e5d2a6
--- /dev/null
+++ b/src/error-code.hpp
@@ -0,0 +1,43 @@
+/* -*- 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 Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#ifndef NDN_GEP_ERROR_CODE_HPP
+#define NDN_GEP_ERROR_CODE_HPP
+
+#include "common.hpp"
+
+namespace ndn {
+namespace gep {
+
+enum class ErrorCode {
+  Timeout = 1,
+  Validation = 2,
+  UnsupportedEncryptionScheme = 32,
+  InvalidEncryptedFormat = 33,
+  NoDecryptKey = 34
+};
+
+typedef function<void (const ErrorCode&, const std::string&)> ErrorCallBack;
+
+} // namespace gep
+} // namespace ndn
+
+#endif // NDN_GEP_ERROR_CODE_HPP
diff --git a/tests/unit-tests/consumer-db.t.cpp b/tests/unit-tests/consumer-db.t.cpp
new file mode 100644
index 0000000..bad6213
--- /dev/null
+++ b/tests/unit-tests/consumer-db.t.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ */
+
+#include "consumer-db.hpp"
+#include "algo/rsa.hpp"
+#include "algo/aes.hpp"
+#include "boost-test.hpp"
+
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace gep {
+namespace tests {
+
+class ConsumerDBFixture
+{
+public:
+  ConsumerDBFixture()
+    : tmpPath(boost::filesystem::path(TMP_TESTS_PATH))
+  {
+    boost::filesystem::create_directories(tmpPath);
+  }
+
+  ~ConsumerDBFixture()
+  {
+    boost::filesystem::remove_all(tmpPath);
+  }
+
+  void
+  generateRsaKey(Buffer& encryptionKeyBuf, Buffer& decryptionKeyBuf)
+  {
+    RandomNumberGenerator rng;
+    RsaKeyParams params;
+    DecryptKey<algo::Rsa> dKey = algo::Rsa::generateKey(rng, params);
+    decryptionKeyBuf = dKey.getKeyBits();
+    EncryptKey<algo::Rsa> eKey = algo::Rsa::deriveEncryptKey(decryptionKeyBuf);
+    encryptionKeyBuf = eKey.getKeyBits();
+  }
+
+  void
+  generateAesKey(Buffer& encryptionKeyBuf, Buffer& decryptionKeyBuf)
+  {
+    RandomNumberGenerator rng;
+    AesKeyParams params;
+    DecryptKey<algo::Aes> memberDecryptKey = algo::Aes::generateKey(rng, params);
+    decryptionKeyBuf = memberDecryptKey.getKeyBits();
+    EncryptKey<algo::Aes> memberEncryptKey = algo::Aes::deriveEncryptKey(decryptionKeyBuf);
+    encryptionKeyBuf = memberEncryptKey.getKeyBits();
+  }
+
+public:
+  boost::filesystem::path tmpPath;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestConsumerDB, ConsumerDBFixture)
+
+BOOST_AUTO_TEST_CASE(OperateAesDecryptionKey)
+{
+  // construction
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+  ConsumerDB db(dbDir);
+
+  // generate key buffer
+  Buffer eKeyBuf;
+  Buffer dKeyBuf;
+  generateAesKey(eKeyBuf, dKeyBuf);
+
+  Name keyName("/alice/health/samples/activity/steps/C-KEY/20150928080000/20150928090000!");
+  keyName.append("FOR/alice/health/read/activity!");
+  db.addKey(keyName, dKeyBuf);
+  Buffer resultBuf = db.getKey(keyName);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(dKeyBuf.begin(), dKeyBuf.end(),
+                                resultBuf.begin(), resultBuf.end());
+
+  db.deleteKey(keyName);
+  resultBuf = db.getKey(keyName);
+
+  BOOST_CHECK_EQUAL(resultBuf.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(OperateRsaDecryptionKey)
+{
+  // construction
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+  ConsumerDB db(dbDir);
+
+  // generate key buffer
+  Buffer eKeyBuf;
+  Buffer dKeyBuf;
+  generateRsaKey(eKeyBuf, dKeyBuf);
+
+  Name keyName("/alice/health/samples/activity/steps/D-KEY/20150928080000/20150928090000!");
+  keyName.append("FOR/test/member/KEY/123!");
+  db.addKey(keyName, dKeyBuf);
+  Buffer resultBuf = db.getKey(keyName);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(dKeyBuf.begin(), dKeyBuf.end(),
+                                resultBuf.begin(), resultBuf.end());
+
+  db.deleteKey(keyName);
+  resultBuf = db.getKey(keyName);
+
+  BOOST_CHECK_EQUAL(resultBuf.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace gep
+} // namespace ndn
diff --git a/tests/unit-tests/consumer.t.cpp b/tests/unit-tests/consumer.t.cpp
new file mode 100644
index 0000000..4db7e5f
--- /dev/null
+++ b/tests/unit-tests/consumer.t.cpp
@@ -0,0 +1,297 @@
+/* -*- 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 Zhiyi Zhang <dreamerbarrychang@gmail.com>
+ * @author Yingdi Yu <yingdi@cs.ucla.edu>
+ */
+
+#include "consumer.hpp"
+#include "boost-test.hpp"
+#include "algo/encryptor.hpp"
+#include "unit-test-time-fixture.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/asio.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
+};
+
+static const uint8_t AES_KEY[] = {
+  0xdd, 0x60, 0x77, 0xec, 0xa9, 0x6b, 0x23, 0x1b,
+  0x40, 0x6b, 0x5a, 0xf8, 0x7d, 0x3d, 0x55, 0x32
+};
+
+static const uint8_t IV[] = {
+  0x73, 0x6f, 0x6d, 0x65, 0x72, 0x61, 0x6e, 0x64,
+  0x6f, 0x6d, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72
+};
+
+class ConsumerFixture : public UnitTestTimeFixture
+{
+public:
+  ConsumerFixture()
+    : 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)
+    , groupName("/Prefix/READ")
+    , contentName("/Prefix/SAMPLE/Content")
+    , cKeyName("/Prefix/SAMPLE/Content/C-KEY/1")
+    , eKeyName("/Prefix/READ/E-KEY/1/2")
+    , dKeyName("/Prefix/READ/D-KEY/1/2")
+    , uKeyName("/U/Key")
+    , uName("/U")
+  {
+    boost::filesystem::create_directories(tmpPath);
+
+    // generate e/d key
+    RandomNumberGenerator rng;
+    RsaKeyParams params;
+    fixtureDKeyBuf = algo::Rsa::generateKey(rng, params).getKeyBits();
+    fixtureEKeyBuf = algo::Rsa::deriveEncryptKey(fixtureDKeyBuf).getKeyBits();
+
+    // generate user key
+    fixtureUDKeyBuf = algo::Rsa::generateKey(rng, params).getKeyBits();
+    fixtureUEKeyBuf = algo::Rsa::deriveEncryptKey(fixtureUDKeyBuf).getKeyBits();
+
+    // load C-KEY
+    fixtureCKeyBuf = Buffer(AES_KEY, sizeof(AES_KEY));
+  }
+
+  ~ConsumerFixture()
+  {
+    boost::filesystem::remove_all(tmpPath);
+  }
+
+  shared_ptr<Data>
+  createEncryptedContent()
+  {
+    shared_ptr<Data> contentData = make_shared<Data>(contentName);
+    algo::EncryptParams eparams(tlv::AlgorithmAesCbc);
+    eparams.setIV(IV, sizeof(IV));
+    algo::encryptData(*contentData, DATA_CONTEN, sizeof(DATA_CONTEN), cKeyName,
+                      fixtureCKeyBuf.buf(), fixtureCKeyBuf.size(), eparams);
+    keyChain.sign(*contentData);
+    return contentData;
+  }
+
+  shared_ptr<Data>
+  createEncryptedCKey()
+  {
+    shared_ptr<Data> cKeyData = make_shared<Data>(cKeyName);
+    algo::EncryptParams eparams(tlv::AlgorithmRsaOaep);
+    algo::encryptData(*cKeyData, fixtureCKeyBuf.buf(), fixtureCKeyBuf.size(), dKeyName,
+                      fixtureEKeyBuf.buf(), fixtureEKeyBuf.size(), eparams);
+    keyChain.sign(*cKeyData);
+    return cKeyData;
+  }
+
+  shared_ptr<Data>
+  createEncryptedDKey()
+  {
+    shared_ptr<Data> dKeyData = make_shared<Data>(dKeyName);
+    algo::EncryptParams eparams(tlv::AlgorithmRsaOaep);
+    algo::encryptData(*dKeyData, fixtureDKeyBuf.buf(), fixtureDKeyBuf.size(), uKeyName,
+                      fixtureUEKeyBuf.buf(), fixtureUEKeyBuf.size(), eparams);
+    keyChain.sign(*dKeyData);
+    return dKeyData;
+  }
+
+  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;
+
+  Buffer fixtureCKeyBuf;
+  Buffer fixtureEKeyBuf;
+  Buffer fixtureDKeyBuf;
+  Buffer fixtureUEKeyBuf;
+  Buffer fixtureUDKeyBuf;
+
+  Name groupName;
+  Name contentName;
+  Name cKeyName;
+  Name eKeyName;
+  Name dKeyName;
+  Name uKeyName;
+  Name uName;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestConsumer, ConsumerFixture)
+
+BOOST_AUTO_TEST_CASE(DecryptContent)
+{
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+
+  // generate AES key pairs
+  Buffer aesKeyBuf = Buffer(AES_KEY, sizeof(AES_KEY));
+
+  // generate C-KEY packet
+  auto cKeyData = createEncryptedCKey();
+  // generate Content packet
+  auto contentData = createEncryptedContent();
+
+  // create consumer
+  Consumer consumer(*face1, Name("/Group"), Name("/U"), dbDir);
+
+  // decrypt
+  consumer.decrypt(cKeyData->getContent().blockFromValue(),
+                   fixtureDKeyBuf,
+                   [=](const Buffer& result){
+                     BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
+                                                   aesKeyBuf.begin(),
+                                                   aesKeyBuf.end());
+                   },
+                   [=](const ErrorCode&, const std::string&){
+                     BOOST_CHECK(false);
+                   });
+
+  // decrypt
+  consumer.decrypt(contentData->getContent().blockFromValue(),
+                   fixtureCKeyBuf,
+                   [=](const Buffer& result){
+                     BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
+                                                   DATA_CONTEN,
+                                                   DATA_CONTEN + sizeof(DATA_CONTEN));
+                   },
+                   [=](const ErrorCode&, const std::string&){
+                     BOOST_CHECK(false);
+                   });
+}
+
+BOOST_AUTO_TEST_CASE(Consume)
+{
+  auto contentData = createEncryptedContent();
+  auto cKeyData = createEncryptedCKey();
+  auto dKeyData = createEncryptedDKey();
+
+  int contentCount = 0;
+  int cKeyCount = 0;
+  int dKeyCount = 0;
+
+  Name prefix("/Prefix");
+  // prepare face1
+  face1->setInterestFilter(prefix,
+                           [&] (const InterestFilter&, const Interest& i) {
+                             if (i.matchesData(*contentData)) {
+                               contentCount = 1;
+                               face1->put(*contentData);
+                               return;
+                             }
+                             if (i.matchesData(*cKeyData)) {
+                               cKeyCount = 1;
+                               face1->put(*cKeyData);
+                               return;
+                             }
+                             if (i.matchesData(*dKeyData)) {
+                               dKeyCount = 1;
+                               face1->put(*dKeyData);
+                               return;
+                             }
+                             return;
+                           },
+                           RegisterPrefixSuccessCallback(),
+                           [] (const Name&, const std::string& e) { });
+
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+
+  // create consumer
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/test.db";
+  Consumer consumer(*face2, groupName, uName, dbDir);
+  consumer.addDecryptionKey(uKeyName, fixtureUDKeyBuf);
+
+  int finalCount = 0;
+  consumer.consume(contentName,
+                   [&](const Data& data, const Buffer& result){
+                     finalCount = 1;
+                     BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(),
+                                                   DATA_CONTEN,
+                                                   DATA_CONTEN + sizeof(DATA_CONTEN));
+                   },
+                   [&](const ErrorCode& code, const std::string& str){
+                     BOOST_CHECK(false);
+                   });
+
+  do {
+    advanceClocks(time::milliseconds(10), 20);
+  } while (passPacket());
+
+  BOOST_CHECK_EQUAL(contentCount, 1);
+  BOOST_CHECK_EQUAL(cKeyCount, 1);
+  BOOST_CHECK_EQUAL(dKeyCount, 1);
+  BOOST_CHECK_EQUAL(finalCount, 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace test
+} // namespace gep
+} // namespace ndn
diff --git a/tests/unit-tests/unit-test-time-fixture.hpp b/tests/unit-tests/unit-test-time-fixture.hpp
new file mode 100644
index 0000000..62db2d7
--- /dev/null
+++ b/tests/unit-tests/unit-test-time-fixture.hpp
@@ -0,0 +1,70 @@
+/* -*- 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/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_GEP_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+#define NDN_GEP_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+#include <boost/asio.hpp>
+
+namespace ndn {
+namespace gep {
+namespace tests {
+
+class UnitTestTimeFixture
+{
+public:
+  UnitTestTimeFixture()
+    : steadyClock(make_shared<time::UnitTestSteadyClock>())
+    , systemClock(make_shared<time::UnitTestSystemClock>())
+  {
+    time::setCustomClocks(steadyClock, systemClock);
+  }
+
+  ~UnitTestTimeFixture()
+  {
+    time::setCustomClocks(nullptr, nullptr);
+  }
+
+  void
+  advanceClocks(const time::nanoseconds& tick, size_t nTicks = 1)
+  {
+    for (size_t i = 0; i < nTicks; ++i) {
+      steadyClock->advance(tick);
+      systemClock->advance(tick);
+
+      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 gep
+} // namespace ndn
+
+#endif // NDN_GEP_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP