Adding DataProducer class to produce and encrypt data packets.
Change-Id: I147164a1517f1f6f24536959bc572a6d777c15b0
refs: #3016
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