encryptor: Continuosly retry fetching KEK forever

With this change, Encryptor will continue to retry fetching KEK until
success.  Each failed attempt will be notified to application via
onFailure callback supplied during Encryptor construction.

Change-Id: I48017b5d25bb77d9f7cfb45b21a9816fb99c33dd
diff --git a/src/common.hpp b/src/common.hpp
index b8523e9..a84c66c 100644
--- a/src/common.hpp
+++ b/src/common.hpp
@@ -109,6 +109,9 @@
 const time::seconds DEFAULT_KDK_FRESHNESS_PERIOD = 1_h;
 const time::seconds DEFAULT_CK_FRESHNESS_PERIOD = 1_h;
 
+const time::seconds RETRY_DELAY_AFTER_NACK = 1_s;
+const time::seconds RETRY_DELAY_KEK_RETRIEVAL = 60_s;
+
 enum class ErrorCode {
   KekRetrievalFailure = 1,
   KekRetrievalTimeout = 2,
diff --git a/src/encryptor.cpp b/src/encryptor.cpp
index 26b9a73..0472e1e 100644
--- a/src/encryptor.cpp
+++ b/src/encryptor.cpp
@@ -38,11 +38,13 @@
   , m_ckBits{AES_KEY_SIZE}
   , m_ckDataSigningInfo{std::move(ckDataSigningInfo)}
   , m_isKekRetrievalInProgress(false)
+  , m_onFailure(onFailure)
   , m_ckRegId{nullptr}
   , m_keyChain{keyChain}
   , m_face{face}
+  , m_scheduler{face.getIoService()}
 {
-  regenerateCk(onFailure);
+  regenerateCk();
 
   auto serveFromIms = [this] (const Name& prefix, const Interest& interest) {
     auto data = m_ims.find(interest);
@@ -72,31 +74,42 @@
 }
 
 void
-Encryptor::regenerateCk(const ErrorCallback& onFailure)
+Encryptor::retryFetchingKek()
+{
+  if (m_isKekRetrievalInProgress) {
+    return;
+  }
+
+  NDN_LOG_DEBUG("Retrying fetching of KEK");
+  m_isKekRetrievalInProgress = true;
+  fetchKekAndPublishCkData([&] {
+      NDN_LOG_DEBUG("KEK retrieved and published");
+      m_isKekRetrievalInProgress = false;
+    },
+    [=] (const ErrorCode& code, const std::string& msg) {
+      NDN_LOG_ERROR("Failed to retrieved KEK: " + msg);
+      m_isKekRetrievalInProgress = false;
+      m_onFailure(code, msg);
+    },
+    N_RETRIES);
+}
+
+void
+Encryptor::regenerateCk()
 {
   m_ckName = m_ckPrefix;
   m_ckName
     .append(CK)
     .appendVersion(); // version = ID of CK
-  random::generateSecureBytes(m_ckBits.data(), m_ckBits.size());
-
   NDN_LOG_DEBUG("Generating new CK: " << m_ckName);
+  random::generateSecureBytes(m_ckBits.data(), m_ckBits.size());
 
   // one implication: if CK updated before KEK fetched, KDK for the old CK will not be published
   if (!m_kek) {
-    m_isKekRetrievalInProgress = true;
-    fetchKekAndPublishCkData([&] {
-        NDN_LOG_DEBUG("KEK retrieved and published");
-        m_isKekRetrievalInProgress = false;
-      },
-      [&] (const ErrorCode&, const std::string& msg) {
-        NDN_LOG_ERROR("Failed to retrieved KEK: " + msg);
-        m_isKekRetrievalInProgress = false;
-      },
-      N_RETRIES);
+    retryFetchingKek();
   }
   else {
-    makeAndPublishCkData(onFailure);
+    makeAndPublishCkData(m_onFailure);
   }
 }
 
@@ -145,9 +158,20 @@
                            },
                            [=] (const Interest& i, const lp::Nack& nack) {
                              m_kekPendingInterest = nullptr;
-                             onFailure(ErrorCode::KekRetrievalFailure,
-                                       "Retrieval of KEK [" + i.getName().toUri() + "] failed. "
-                                       "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
+                             if (nTriesLeft > 1) {
+                               m_scheduler.scheduleEvent(RETRY_DELAY_AFTER_NACK, [=] {
+                                   fetchKekAndPublishCkData(onReady, onFailure, nTriesLeft - 1);
+                                 });
+                             }
+                             else {
+                               onFailure(ErrorCode::KekRetrievalFailure,
+                                         "Retrieval of KEK [" + i.getName().toUri() + "] failed. "
+                                         "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
+                               NDN_LOG_DEBUG("Scheduling retry from NACK");
+                               m_scheduler.scheduleEvent(RETRY_DELAY_KEK_RETRIEVAL, [=] {
+                                   retryFetchingKek();
+                                 });
+                             }
                            },
                            [=] (const Interest& i) {
                              m_kekPendingInterest = nullptr;
@@ -157,6 +181,10 @@
                              else {
                                onFailure(ErrorCode::KekRetrievalTimeout,
                                          "Retrieval of KEK [" + i.getName().toUri() + "] timed out");
+                               NDN_LOG_DEBUG("Scheduling retry after all timeouts");
+                               m_scheduler.scheduleEvent(RETRY_DELAY_KEK_RETRIEVAL, [=] {
+                                   retryFetchingKek();
+                                 });
                              }
                            });
 }
diff --git a/src/encryptor.hpp b/src/encryptor.hpp
index 679f6d4..34d69bb 100644
--- a/src/encryptor.hpp
+++ b/src/encryptor.hpp
@@ -40,7 +40,10 @@
    *                      (each will have unique version appended)
    * @param ckDataSigningInfo  SigningInfo parameters to sign CK Data
    * @param onFailure     Callback to notify application of a failure to create CK data
-   *                      (failed to fetch KEK, failed to encrypt with KEK, etc.)
+   *                      (failed to fetch KEK, failed to encrypt with KEK, etc.).
+   *                      Note that Encryptor will continue trying to retrieve KEK until success
+   *                      (each attempt separated by `RETRY_DELAY_KEK_RETRIEVAL`) and @p onFailure
+   *                      may be called multiple times.
    * @param validator     Validation policy to ensure correctness of KEK
    * @param keyChain      KeyChain
    * @param face          Face that will be used to fetch KEK and publish CK data
@@ -78,7 +81,7 @@
    *       before KEK fetched
    */
   void
-  regenerateCk(const ErrorCallback& onFailure);
+  regenerateCk();
 
 public: // accessor interface for published data packets
 
@@ -114,6 +117,9 @@
 
 private:
   void
+  retryFetchingKek();
+
+  void
   fetchKekAndPublishCkData(const std::function<void()>& onReady,
                            const ErrorCallback& onFailure,
                            size_t nTriesLeft);
@@ -130,6 +136,7 @@
 
   bool m_isKekRetrievalInProgress;
   optional<Data> m_kek;
+  ErrorCallback m_onFailure;
 
   InMemoryStoragePersistent m_ims; // for encrypted CKs
   const RegisteredPrefixId* m_ckRegId = nullptr;
@@ -137,6 +144,7 @@
 
   KeyChain& m_keyChain;
   Face& m_face;
+  Scheduler m_scheduler;
 };
 
 } // namespace nac
diff --git a/tests/tests/decryptor.t.cpp b/tests/tests/decryptor.t.cpp
index 258d135..88bda75 100644
--- a/tests/tests/decryptor.t.cpp
+++ b/tests/tests/decryptor.t.cpp
@@ -34,10 +34,10 @@
 namespace nac {
 namespace tests {
 
-class StaticDataEnvironment : public UnitTestTimeFixture
+class DecryptorStaticDataEnvironment : public UnitTestTimeFixture
 {
 public:
-  StaticDataEnvironment()
+  DecryptorStaticDataEnvironment()
     : fw(m_io, m_keyChain)
     , imsFace(static_cast<util::DummyClientFace&>(fw.addFace()))
   {
@@ -75,7 +75,7 @@
 };
 
 template<class T>
-class DecryptorFixture : public StaticDataEnvironment
+class DecryptorFixture : public DecryptorStaticDataEnvironment
 {
 public:
   DecryptorFixture()
diff --git a/tests/tests/encryptor.t.cpp b/tests/tests/encryptor.t.cpp
index 0a5a198..57e8b21 100644
--- a/tests/tests/encryptor.t.cpp
+++ b/tests/tests/encryptor.t.cpp
@@ -30,17 +30,15 @@
 namespace nac {
 namespace tests {
 
-class StaticDataEnvironment : public UnitTestTimeFixture
+class EncryptorStaticDataEnvironment : public UnitTestTimeFixture
 {
 public:
-  StaticDataEnvironment()
+  EncryptorStaticDataEnvironment(bool shouldPublishData)
     : fw(m_io, m_keyChain)
     , imsFace(static_cast<util::DummyClientFace&>(fw.addFace()))
   {
-    StaticData data;
-    for (const auto& block : data.managerPackets) {
-      auto data = make_shared<Data>(block);
-      m_ims.insert(*data);
+    if (shouldPublishData) {
+      publishData();
     }
 
     auto serveFromIms = [this] (const Name& prefix, const Interest& interest) {
@@ -51,6 +49,20 @@
     };
     imsFace.setInterestFilter("/", serveFromIms, [] (auto...) {});
     advanceClocks(1_ms, 10);
+
+    imsFace.sentData.clear();
+    imsFace.sentInterests.clear();
+  }
+
+  void
+  publishData()
+  {
+    StaticData data;
+    for (const auto& block : data.managerPackets) {
+      auto data = make_shared<Data>(block);
+      m_ims.insert(*data);
+    }
+    advanceClocks(1_ms, 10);
   }
 
 public:
@@ -59,13 +71,17 @@
   InMemoryStoragePersistent m_ims;
 };
 
-class EncryptorFixture : public StaticDataEnvironment
+template<bool shouldPublishData = true>
+class EncryptorFixture : public EncryptorStaticDataEnvironment
 {
 public:
   EncryptorFixture()
-    : face(static_cast<util::DummyClientFace&>(fw.addFace()))
+    : EncryptorStaticDataEnvironment(shouldPublishData)
+    , face(static_cast<util::DummyClientFace&>(fw.addFace()))
     , encryptor("/access/policy/identity/NAC/dataset", "/some/ck/prefix", signingWithSha256(),
-                [] (auto&&...) {},
+                [=] (const ErrorCode& code, const std::string& error) {
+                  onFailure(code, error);
+                },
                 validator, m_keyChain, face)
   {
     advanceClocks(1_ms, 10);
@@ -75,9 +91,10 @@
   util::DummyClientFace& face;
   ValidatorNull validator;
   Encryptor encryptor;
+  util::Signal<EncryptorFixture, ErrorCode, std::string> onFailure;
 };
 
-BOOST_FIXTURE_TEST_SUITE(TestEncryptor, EncryptorFixture)
+BOOST_FIXTURE_TEST_SUITE(TestEncryptor, EncryptorFixture<>)
 
 BOOST_AUTO_TEST_CASE(EncryptAndPublishedCk)
 {
@@ -118,12 +135,48 @@
   BOOST_CHECK_EQUAL(extractedKek, kek.getName());
 }
 
-BOOST_AUTO_TEST_CASE(EnumerateDataFromIms)
+BOOST_FIXTURE_TEST_CASE(KekRetrievalFailure, EncryptorFixture<false>)
 {
-  encryptor.regenerateCk([] (auto&&...) {});
+  size_t nErrors = 0;
+  onFailure.connect([&] (const ErrorCode& code, const std::string& error) {
+      ++nErrors;
+    });
+
+  std::string plaintext = "Data to encrypt";
+  auto block = encryptor.encrypt(reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size());
   advanceClocks(1_ms, 10);
 
-  encryptor.regenerateCk([] (auto&&...) {});
+  // check that KEK interests has been sent
+  BOOST_CHECK_EQUAL(face.sentInterests.at(0).getName().getPrefix(6), Name("/access/policy/identity/NAC/dataset/KEK"));
+
+  // and failed
+  BOOST_CHECK_EQUAL(imsFace.sentData.size(), 0);
+
+  advanceClocks(1_s, 13); // 4_s default interest lifetime x 3
+  BOOST_CHECK_EQUAL(nErrors, 1);
+  BOOST_CHECK_EQUAL(imsFace.sentData.size(), 0);
+
+  advanceClocks(1_s, 730); // 60 seconds between attempts + ~12 seconds for each attempt
+  BOOST_CHECK_EQUAL(nErrors, 11);
+  BOOST_CHECK_EQUAL(imsFace.sentData.size(), 0);
+
+  // check recovery
+
+  publishData();
+
+  advanceClocks(1_s, 73);
+
+  auto kek = imsFace.sentData.at(0);
+  BOOST_CHECK_EQUAL(kek.getName().getPrefix(6), Name("/access/policy/identity/NAC/dataset/KEK"));
+  BOOST_CHECK_EQUAL(kek.getName().size(), 7);
+}
+
+BOOST_AUTO_TEST_CASE(EnumerateDataFromIms)
+{
+  encryptor.regenerateCk();
+  advanceClocks(1_ms, 10);
+
+  encryptor.regenerateCk();
   advanceClocks(1_ms, 10);
 
   BOOST_CHECK_EQUAL(encryptor.size(), 3);
@@ -157,7 +210,7 @@
       std::cerr << "\"_block\n";
     }
 
-    encryptor.regenerateCk([] (auto&&...) {});
+    encryptor.regenerateCk();
     advanceClocks(1_ms, 10);
   }
   std::cerr  << "  };\n\n";