security: Add ValidationPolicyCommandInterest

Refs: #3920

Change-Id: I978e68979e4f9cfb58561054b9f87f8d65083d5d
diff --git a/src/security/v2/validation-policy-command-interest.cpp b/src/security/v2/validation-policy-command-interest.cpp
new file mode 100644
index 0000000..5753ef3
--- /dev/null
+++ b/src/security/v2/validation-policy-command-interest.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library 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 Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, 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.
+ */
+
+#include "validation-policy-command-interest.hpp"
+#include "../pib/key.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+ValidationPolicyCommandInterest::ValidationPolicyCommandInterest(unique_ptr<ValidationPolicy> inner,
+                                                                 const Options& options)
+  : m_options(options)
+  , m_index(m_container.get<0>())
+  , m_queue(m_container.get<1>())
+{
+  if (inner == nullptr) {
+    BOOST_THROW_EXCEPTION(std::invalid_argument("inner policy is missing"));
+  }
+  setInnerPolicy(std::move(inner));
+
+  m_options.gracePeriod = std::max(m_options.gracePeriod, time::nanoseconds::zero());
+}
+
+void
+ValidationPolicyCommandInterest::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
+                                             const ValidationContinuation& continueValidation)
+{
+  getInnerPolicy().checkPolicy(data, state, continueValidation);
+}
+
+void
+ValidationPolicyCommandInterest::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
+                                             const ValidationContinuation& continueValidation)
+{
+  bool isOk = false;
+  Name keyName;
+  uint64_t timestamp = 0;
+  std::tie(isOk, keyName, timestamp) = parseCommandInterest(interest, state);
+  if (!isOk) {
+    return;
+  }
+
+  if (!checkTimestamp(state, keyName, timestamp)) {
+    return;
+  }
+  getInnerPolicy().checkPolicy(interest, state, std::bind(continueValidation, _1, _2));
+}
+
+void
+ValidationPolicyCommandInterest::cleanup()
+{
+  time::steady_clock::TimePoint expiring = time::steady_clock::now() - m_options.recordLifetime;
+
+  while ((!m_queue.empty() && m_queue.front().lastRefreshed <= expiring) ||
+         (m_options.maxRecords >= 0 &&
+          m_queue.size() > static_cast<size_t>(m_options.maxRecords))) {
+    m_queue.pop_front();
+  }
+}
+
+std::tuple<bool, Name, uint64_t>
+ValidationPolicyCommandInterest::parseCommandInterest(const Interest& interest,
+                                                      const shared_ptr<ValidationState>& state) const
+{
+  const Name& name = interest.getName();
+  if (name.size() < command_interest::MIN_SIZE) {
+    state->fail({ValidationError::POLICY_ERROR, "Command interest name `" +
+                 interest.getName().toUri() + "` is too short"});
+    return std::make_tuple(false, Name(), 0);
+  }
+
+  const name::Component& timestampComp = name.at(command_interest::POS_TIMESTAMP);
+  if (!timestampComp.isNumber()) {
+    state->fail({ValidationError::POLICY_ERROR, "Command interest `" +
+                 interest.getName().toUri() + "` doesn't include timestamp component"});
+    return std::make_tuple(false, Name(), 0);
+  }
+
+  SignatureInfo sig;
+  try {
+    sig.wireDecode(name[signed_interest::POS_SIG_INFO].blockFromValue());
+  }
+  catch (const tlv::Error&) {
+    state->fail({ValidationError::POLICY_ERROR, "Command interest `" +
+                 interest.getName().toUri() + "` does not include SignatureInfo component"});
+    return std::make_tuple(false, Name(), 0);
+  }
+
+  if (!sig.hasKeyLocator()) {
+    state->fail({ValidationError::INVALID_KEY_LOCATOR, "Command interest `" +
+                 interest.getName().toUri() + "` does not include KeyLocator"});
+    return std::make_tuple(false, Name(), 0);
+  }
+
+  const KeyLocator& keyLocator = sig.getKeyLocator();
+  if (keyLocator.getType() != KeyLocator::KeyLocator_Name) {
+    state->fail({ValidationError::INVALID_KEY_LOCATOR, "Command interest `" +
+                 interest.getName().toUri() + "` KeyLocator type is not Name"});
+    return std::make_tuple(false, Name(), 0);
+  }
+
+  return std::make_tuple(true, keyLocator.getName(), timestampComp.toNumber());
+}
+
+bool
+ValidationPolicyCommandInterest::checkTimestamp(const shared_ptr<ValidationState>& state,
+                                                const Name& keyName, uint64_t timestamp)
+{
+  this->cleanup();
+
+  time::system_clock::TimePoint now = time::system_clock::now();
+  time::system_clock::TimePoint timestampPoint = time::fromUnixTimestamp(time::milliseconds(timestamp));
+  if (timestampPoint < now - m_options.gracePeriod || timestampPoint > now + m_options.gracePeriod) {
+    state->fail({ValidationError::POLICY_ERROR, "Timestamp is outside the grace period for key " + keyName.toUri()});
+    return false;
+  }
+  auto it = m_index.find(keyName);
+  if (it != m_index.end()) {
+    if (timestamp <= it->timestamp) {
+      state->fail({ValidationError::POLICY_ERROR, "Timestamp is reordered for key " + keyName.toUri()});
+      return false;
+    }
+  }
+
+  shared_ptr<InterestValidationState> interestState = std::dynamic_pointer_cast<InterestValidationState>(state);
+  interestState->afterSuccess.connect(bind(&ValidationPolicyCommandInterest::insertNewRecord,
+                                           this, _1, keyName, timestamp));
+  return true;
+}
+
+void
+ValidationPolicyCommandInterest::insertNewRecord(const Interest& interest, const Name& keyName,
+                                                 uint64_t timestamp)
+{
+  // try to insert new record
+  time::steady_clock::TimePoint now = time::steady_clock::now();
+  Queue::iterator i = m_queue.end();
+  bool isNew = false;
+  LastTimestampRecord newRecord{keyName, timestamp, now};
+  std::tie(i, isNew) = m_queue.push_back(newRecord);
+
+  if (!isNew) {
+    BOOST_ASSERT(i->keyName == keyName);
+
+    // set lastRefreshed field, and move to queue tail
+    m_queue.erase(i);
+    isNew = m_queue.push_back(newRecord).second;
+    BOOST_VERIFY(isNew);
+  }
+}
+
+} // namespace v2
+} // namespace security
+} // namespace ndn