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