Changing logic in GroupManger.getGroupKey() to avoid regenerate group key pairs every time.

Change-Id: I4c6eb5246bce04289d7ba097a66d0f11745ce44c
Refs: #3812
diff --git a/src/group-manager-db.cpp b/src/group-manager-db.cpp
index 5c9cd23..0f8640b 100644
--- a/src/group-manager-db.cpp
+++ b/src/group-manager-db.cpp
@@ -55,7 +55,17 @@
   "      ON UPDATE CASCADE                            \n"
   "  );                                               \n"
   "CREATE UNIQUE INDEX IF NOT EXISTS                  \n"
-  "   memNameIndex ON members(member_name);           \n";
+  "   memNameIndex ON members(member_name);           \n"
+  "                                                   \n"
+  "CREATE TABLE IF NOT EXISTS                         \n"
+  "  ekeys(                                           \n"
+  "    ekey_id             INTEGER PRIMARY KEY,       \n"
+  "    ekey_name           BLOB NOT NULL,             \n"
+  "    pub_key             BLOB NOT NULL,             \n"
+  "    pri_key             BLOB NOT NULL              \n"
+  "  );                                               \n"
+  "CREATE UNIQUE INDEX IF NOT EXISTS                  \n"
+  "   ekeyNameIndex ON ekeys(ekey_name);              \n";
 
 class GroupManagerDB::Impl
 {
@@ -319,5 +329,60 @@
   statement.step();
 }
 
+bool
+GroupManagerDB::hasEKey(const Name& eKeyName)
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "SELECT ekey_id FROM ekeys where ekey_name=?");
+  statement.bind(1, eKeyName.wireEncode(), SQLITE_TRANSIENT);
+  return (statement.step() == SQLITE_ROW);
+}
+
+void
+GroupManagerDB::addEKey(const Name& eKeyName, const Buffer& pubKey, const Buffer& priKey)
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "INSERT INTO ekeys(ekey_name, pub_key, pri_key) values (?, ?, ?)");
+  statement.bind(1, eKeyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(2, pubKey.buf(), pubKey.size(), SQLITE_TRANSIENT);
+  statement.bind(3, priKey.buf(), priKey.size(), SQLITE_TRANSIENT);
+  if (statement.step() != SQLITE_DONE)
+    BOOST_THROW_EXCEPTION(Error("Cannot add the EKey to database"));
+}
+
+std::tuple<Buffer, Buffer>
+GroupManagerDB::getEKey(const Name& eKeyName)
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "SELECT * FROM ekeys where ekey_name=?");
+  statement.bind(1, eKeyName.wireEncode(), SQLITE_TRANSIENT);
+
+  Buffer pubKey, priKey;
+  if (statement.step() == SQLITE_ROW) {
+    pubKey = Buffer(statement.getBlob(2), statement.getSize(2));
+    priKey = Buffer(statement.getBlob(3), statement.getSize(3));
+  }
+  else {
+    BOOST_THROW_EXCEPTION(Error("Cannot get the result from database"));
+  }
+  return std::make_tuple(pubKey, priKey);
+}
+
+void
+GroupManagerDB::cleanEKeys()
+{
+  Sqlite3Statement statement(m_impl->m_database, "DELETE FROM ekeys");
+  statement.step();
+}
+
+void
+GroupManagerDB::deleteEKey(const Name& eKeyName)
+{
+  Sqlite3Statement statement(m_impl->m_database,
+                             "DELETE FROM ekeys WHERE ekey_name=?");
+  statement.bind(1, eKeyName.wireEncode(), SQLITE_TRANSIENT);
+  statement.step();
+}
+
 } // namespace gep
 } // namespace ndn
diff --git a/src/group-manager-db.hpp b/src/group-manager-db.hpp
index 9f737ae..cbf4ecd 100644
--- a/src/group-manager-db.hpp
+++ b/src/group-manager-db.hpp
@@ -164,6 +164,42 @@
   void
   deleteMember(const Name& identity);
 
+  /**
+   * @brief Check if there is a EKey with name @p eKeyName in database
+   */
+  bool
+  hasEKey(const Name& eKeyName);
+
+  /**
+   * @brief Add a EKey with name @p eKeyName to database
+   *
+   * @p pubKey The public Key of the group key pair
+   * @p priKey The private Key of the group key pair
+   */
+  void
+  addEKey(const Name& eKeyName, const Buffer& pubKey, const Buffer& priKey);
+
+  /**
+   * @brief Get the group key pair from database
+   */
+  std::tuple<Buffer, Buffer>
+  getEKey(const Name& eKeyName);
+
+  /**
+   * @brief Delete all the EKeys in the database
+   *
+   * The database will keep growing because EKeys will keep being added. The method
+   * should be called periodically
+   */
+  void
+  cleanEKeys();
+
+  /**
+   * @brief Delete a EKey with name @p eKeyName from database
+   */
+  void
+  deleteEKey(const Name& eKeyName);
+
 private:
   class Impl;
   unique_ptr<Impl> m_impl;
diff --git a/src/group-manager.cpp b/src/group-manager.cpp
index 4bbf19e..bd5feb0 100644
--- a/src/group-manager.cpp
+++ b/src/group-manager.cpp
@@ -39,7 +39,7 @@
 }
 
 std::list<Data>
-GroupManager::getGroupKey(const TimeStamp& timeslot)
+GroupManager::getGroupKey(const TimeStamp& timeslot, bool needRegenerate)
 {
   std::map<Name, Buffer> memberKeys;
   std::list<Data> result;
@@ -54,7 +54,19 @@
 
   // generate the pri key and pub key
   Buffer priKeyBuf, pubKeyBuf;
-  generateKeyPairs(priKeyBuf, pubKeyBuf);
+  Name eKeyName(m_namespace);
+  eKeyName.append(NAME_COMPONENT_E_KEY).append(startTs).append(endTs);
+
+  if (!needRegenerate && m_db.hasEKey(eKeyName)) {
+    std::tie(pubKeyBuf, priKeyBuf) = getEKey(eKeyName);
+  }
+  else {
+    generateKeyPairs(priKeyBuf, pubKeyBuf);
+    if (m_db.hasEKey(eKeyName)) {
+      deleteEKey(eKeyName);
+    }
+    addEKey(eKeyName, pubKeyBuf, priKeyBuf);
+  }
 
   // add the first element to the result
   // E-KEY (public key) data packet name convention:
@@ -200,5 +212,29 @@
   return data;
 }
 
+void
+GroupManager::addEKey(const Name& eKeyName, const Buffer& pubKey, const Buffer& priKey)
+{
+  m_db.addEKey(eKeyName, pubKey, priKey);
+}
+
+std::tuple<Buffer, Buffer>
+GroupManager::getEKey(const Name& eKeyName)
+{
+  return m_db.getEKey(eKeyName);
+}
+
+void
+GroupManager::deleteEKey(const Name& eKeyName)
+{
+  m_db.deleteEKey(eKeyName);
+}
+
+void
+GroupManager::cleanEKeys()
+{
+  m_db.cleanEKeys();
+}
+
 } // namespace ndn
 } // namespace ndn
diff --git a/src/group-manager.hpp b/src/group-manager.hpp
index e4674df..2e19ab1 100644
--- a/src/group-manager.hpp
+++ b/src/group-manager.hpp
@@ -62,14 +62,17 @@
    *
    * This method creates a group key if it does not
    * exist, and encrypts the key using public key of
-   * all eligible members
+   * all eligible members.
+   *
+   * @p needRegenerate should be true if 1.first time to call 2.a member was removed
+   *                   and it can be false if 1.not the first time to call 2.a member was added
    *
    * @returns The group key (the first one is the
    *          public key, and the rest are encrypted
    *          private key.
    */
   std::list<Data>
-  getGroupKey(const TimeStamp& timeslot);
+  getGroupKey(const TimeStamp& timeslot, bool needRegenerate = true);
 
   /// @brief Add @p schedule with @p scheduleName
   void
@@ -95,6 +98,7 @@
   void
   updateMemberSchedule(const Name& identity, const std::string& scheduleName);
 
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   /**
    * @brief Calculate interval that covers @p timeslot
@@ -121,6 +125,22 @@
   createDKeyData(const std::string& startTs, const std::string& endTs, const Name& keyName,
                  const Buffer& priKeyBuf, const Buffer& certKey);
 
+  /// @brief Add a EKey to the database
+  void
+  addEKey(const Name& eKeyName, const Buffer& pubKey, const Buffer& priKey);
+
+  /// @brief Get the key pair from the database
+  std::tuple<Buffer, Buffer>
+  getEKey(const Name& eKeyName);
+
+  /// @brief Delete a EKey to the database
+  void
+  deleteEKey(const Name& eKeyName);
+
+  /// @brief The method should be called periodically because the table size will keep growing
+  void
+  cleanEKeys();
+
 private:
   Name m_namespace;
   GroupManagerDB m_db;
diff --git a/tests/unit-tests/group-manager.t.cpp b/tests/unit-tests/group-manager.t.cpp
index 30bfd6c..dc64eda 100644
--- a/tests/unit-tests/group-manager.t.cpp
+++ b/tests/unit-tests/group-manager.t.cpp
@@ -358,6 +358,58 @@
   BOOST_CHECK_EQUAL(manager.getGroupKey(tp3).size(), 0);
 }
 
+BOOST_AUTO_TEST_CASE(GetGroupKeyWithoutRegeneration)
+{
+  // create the group manager database
+  std::string dbDir = tmpPath.c_str();
+  dbDir += "/manager-group-key-test.db";
+
+  // create group manager
+  GroupManager manager(Name("Alice"), Name("data_type"), dbDir, 1024, 1);
+  setManager(manager);
+
+  // get data list from group manager
+  TimeStamp tp1(from_iso_string("20150825T093000"));
+  std::list<Data> result1 = manager.getGroupKey(tp1);
+
+  BOOST_CHECK_EQUAL(result1.size(), 4);
+
+  // first data contain the group encrypt key(public key)
+  std::list<Data>::iterator dataIterator1 = result1.begin();
+  BOOST_CHECK_EQUAL(dataIterator1->getName().toUri(),
+                    "/Alice/READ/data_type/E-KEY/20150825T090000/20150825T100000");
+  EncryptKey<algo::Rsa> groupEKey1(Buffer(dataIterator1->getContent().value(),
+                                         dataIterator1->getContent().value_size()));
+
+  // second data
+  dataIterator1++;
+  BOOST_CHECK_EQUAL(dataIterator1->getName().toUri(),
+                    "/Alice/READ/data_type/D-KEY/20150825T090000/20150825T100000/FOR/ndn/memberA/ksk-123");
+
+  // add new members to the database
+  Block dataBlock = cert.wireEncode();
+  Data memberD(dataBlock);
+  memberD.setName(Name("/ndn/memberD/KEY/ksk-123/ID-CERT/123"));
+  manager.addMember("schedule1", memberD);
+
+  std::list<Data> result2 = manager.getGroupKey(tp1, false);
+  BOOST_CHECK_EQUAL(result2.size(), 5);
+
+  // check the new EKey is the same with the previous one
+  std::list<Data>::iterator dataIterator2 = result2.begin();
+  BOOST_CHECK_EQUAL(dataIterator2->getName().toUri(),
+                    "/Alice/READ/data_type/E-KEY/20150825T090000/20150825T100000");
+  EncryptKey<algo::Rsa> groupEKey2(Buffer(dataIterator2->getContent().value(),
+                                         dataIterator2->getContent().value_size()));
+  BOOST_CHECK_EQUAL_COLLECTIONS(groupEKey1.getKeyBits().begin(), groupEKey1.getKeyBits().end(),
+                                groupEKey2.getKeyBits().begin(), groupEKey2.getKeyBits().end());
+
+  // second data
+  dataIterator2++;
+  BOOST_CHECK_EQUAL(dataIterator2->getName().toUri(),
+                    "/Alice/READ/data_type/D-KEY/20150825T090000/20150825T100000/FOR/ndn/memberA/ksk-123");
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace test