Add Consumer

Change-Id: Ic94cde3c24c86074c509b77608403aec54b95803
Refs: #3192
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