text: /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
  /*
   * Copyright (c) 2013-2020 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 "ndn-cxx/security/validation-policy-config.hpp"
  #include "ndn-cxx/security/validator.hpp"
  #include "ndn-cxx/util/io.hpp"
  
  #include <boost/algorithm/string/predicate.hpp>
  #include <boost/filesystem.hpp>
  #include <boost/lexical_cast.hpp>
  #include <boost/property_tree/info_parser.hpp>
  
  #include <fstream>
  
  namespace ndn {
  namespace security {
  inline namespace v2 {
  namespace validator_config {
  
  void
  ValidationPolicyConfig::load(const std::string& filename)
  {
    std::ifstream inputFile(filename);
    if (!inputFile) {
      NDN_THROW(Error("Failed to read configuration file: " + filename));
    }
    load(inputFile, filename);
  }
  
  void
  ValidationPolicyConfig::load(const std::string& input, const std::string& filename)
  {
    std::istringstream inputStream(input);
    load(inputStream, filename);
  }
  
  void
  ValidationPolicyConfig::load(std::istream& input, const std::string& filename)
  {
    ConfigSection tree;
    try {
      boost::property_tree::read_info(input, tree);
    }
    catch (const boost::property_tree::info_parser_error& e) {
      NDN_THROW(Error("Failed to parse configuration file " + filename +
                      " line " + to_string(e.line()) + ": " + e.message()));
    }
    load(tree, filename);
  }
  
  void
  ValidationPolicyConfig::load(const ConfigSection& configSection, const std::string& filename)
  {
    BOOST_ASSERT(!filename.empty());
  
    if (m_validator == nullptr) {
      NDN_THROW(Error("Validator instance not assigned on the policy"));
    }
    if (m_isConfigured) {
      m_shouldBypass = false;
      m_dataRules.clear();
      m_interestRules.clear();
      m_validator->resetAnchors();
      m_validator->resetVerifiedCertificates();
    }
    m_isConfigured = true;
  
    for (const auto& subSection : configSection) {
      const std::string& sectionName = subSection.first;
      const ConfigSection& section = subSection.second;
  
      if (boost::iequals(sectionName, "rule")) {
        auto rule = Rule::create(section, filename);
        if (rule->getPktType() == tlv::Data) {
          m_dataRules.push_back(std::move(rule));
        }
        else if (rule->getPktType() == tlv::Interest) {
          m_interestRules.push_back(std::move(rule));
        }
      }
      else if (boost::iequals(sectionName, "trust-anchor")) {
        processConfigTrustAnchor(section, filename);
      }
      else {
        NDN_THROW(Error("Error processing configuration file " + filename +
                        ": unrecognized section " + sectionName));
      }
    }
  }
  
  void
  ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection,
                                                   const std::string& filename)
  {
    using namespace boost::filesystem;
  
    auto propertyIt = configSection.begin();
  
    // Get trust-anchor.type
    if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type")) {
      NDN_THROW(Error("Expecting <trust-anchor.type>"));
    }
  
    std::string type = propertyIt->second.data();
    propertyIt++;
  
    if (boost::iequals(type, "file")) {
      // Get trust-anchor.file
      if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "file-name")) {
        NDN_THROW(Error("Expecting <trust-anchor.file-name>"));
      }
  
      std::string file = propertyIt->second.data();
      propertyIt++;
  
      time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
      if (propertyIt != configSection.end())
        NDN_THROW(Error("Expecting end of <trust-anchor>"));
  
      m_validator->loadAnchor(file, absolute(file, path(filename).parent_path()).string(),
                              refresh, false);
    }
    else if (boost::iequals(type, "base64")) {
      // Get trust-anchor.base64-string
      if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
        NDN_THROW(Error("Expecting <trust-anchor.base64-string>"));
  
      std::stringstream ss(propertyIt->second.data());
      propertyIt++;
  
      if (propertyIt != configSection.end())
        NDN_THROW(Error("Expecting end of <trust-anchor>"));
  
      auto idCert = io::load<Certificate>(ss);
      if (idCert != nullptr) {
        m_validator->loadAnchor("", std::move(*idCert));
      }
      else {
        NDN_THROW(Error("Cannot decode certificate from base64-string"));
      }
    }
    else if (boost::iequals(type, "dir")) {
      if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
        NDN_THROW(Error("Expecting <trust-anchor.dir>"));
  
      std::string dirString(propertyIt->second.data());
      propertyIt++;
  
      time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
      if (propertyIt != configSection.end())
        NDN_THROW(Error("Expecting end of <trust-anchor>"));
  
      path dirPath = absolute(dirString, path(filename).parent_path());
      m_validator->loadAnchor(dirString, dirPath.string(), refresh, true);
    }
    else if (boost::iequals(type, "any")) {
      m_shouldBypass = true;
    }
    else {
      NDN_THROW(Error("Unrecognized <trust-anchor.type>: " + type));
    }
  }
  
  time::nanoseconds
  ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
                                           const ConfigSection::const_iterator& end)
  {
    auto refresh = time::nanoseconds::max();
    if (it == end) {
      return refresh;
    }
  
    if (!boost::iequals(it->first, "refresh")) {
      NDN_THROW(Error("Expecting <trust-anchor.refresh>"));
    }
  
    std::string inputString = it->second.data();
    ++it;
    char unit = inputString[inputString.size() - 1];
    std::string refreshString = inputString.substr(0, inputString.size() - 1);
  
    int32_t refreshPeriod = -1;
    try {
      refreshPeriod = boost::lexical_cast<int32_t>(refreshString);
    }
    catch (const boost::bad_lexical_cast&) {
      // pass
    }
    if (refreshPeriod < 0) {
      NDN_THROW(Error("Bad refresh value: " + refreshString));
    }
  
    if (refreshPeriod == 0) {
      return getDefaultRefreshPeriod();
    }
  
    switch (unit) {
      case 'h':
        return time::hours(refreshPeriod);
      case 'm':
        return time::minutes(refreshPeriod);
      case 's':
        return time::seconds(refreshPeriod);
      default:
        NDN_THROW(Error("Bad refresh time unit: "s + unit));
    }
  }
  
  time::nanoseconds
  ValidationPolicyConfig::getDefaultRefreshPeriod()
  {
    return 1_h;
  }
  
  void
  ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
                                      const ValidationContinuation& continueValidation)
  {
    BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
  
    if (m_shouldBypass) {
      return continueValidation(nullptr, state);
    }
  
    Name klName = getKeyLocatorName(data, *state);
    if (!state->getOutcome()) { // already failed
      return;
    }
  
    for (const auto& rule : m_dataRules) {
      if (rule->match(tlv::Data, data.getName(), state)) {
        if (rule->check(tlv::Data, data.getName(), klName, state)) {
          return continueValidation(make_shared<CertificateRequest>(klName), state);
        }
        // rule->check calls state->fail(...) if the check fails
        return;
      }
    }
  
    return state->fail({ValidationError::POLICY_ERROR,
                        "No rule matched for data `" + data.getName().toUri() + "`"});
  }
  
  void
  ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
                                      const ValidationContinuation& continueValidation)
  {
    BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
  
    if (m_shouldBypass) {
      return continueValidation(nullptr, state);
    }
  
    Name klName = getKeyLocatorName(interest, *state);
    if (!state->getOutcome()) { // already failed
      return;
    }
  
    for (const auto& rule : m_interestRules) {
      if (rule->match(tlv::Interest, interest.getName(), state)) {
        if (rule->check(tlv::Interest, interest.getName(), klName, state)) {
          return continueValidation(make_shared<CertificateRequest>(klName), state);
        }
        // rule->check calls state->fail(...) if the check fails
        return;
      }
    }
  
    return state->fail({ValidationError::POLICY_ERROR,
                        "No rule matched for interest `" + interest.getName().toUri() + "`"});
  }
  
  } // namespace validator_config
  } // inline namespace v2
  } // namespace security
  } // namespace ndn
  