text: /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
  /*
   * Copyright (c) 2013-2018 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 "certificate-bundle-fetcher.hpp"
  #include "face.hpp"
  #include "util/logger.hpp"
  
  namespace ndn {
  namespace security {
  namespace v2 {
  
  NDN_LOG_INIT(ndn.security.v2.CertificateBundleFetcher);
  
  #define NDN_LOG_DEBUG_DEPTH(x) NDN_LOG_DEBUG(std::string(state->getDepth() + 1, '>') << " " << x)
  #define NDN_LOG_TRACE_DEPTH(x) NDN_LOG_TRACE(std::string(state->getDepth() + 1, '>') << " " << x)
  
  CertificateBundleFetcher::CertificateBundleFetcher(unique_ptr<CertificateFetcher> inner,
                                                     Face& face)
    : m_inner(std::move(inner))
    , m_face(face)
    , m_bundleInterestLifetime(1000)
  {
    BOOST_ASSERT(m_inner != nullptr);
  }
  
  void
  CertificateBundleFetcher::setBundleInterestLifetime(time::milliseconds time)
  {
    m_bundleInterestLifetime = time;
  }
  
  time::milliseconds
  CertificateBundleFetcher::getBundleInterestLifetime() const
  {
    return m_bundleInterestLifetime;
  }
  
  void
  CertificateBundleFetcher::setCertificateStorage(CertificateStorage& certStorage)
  {
    m_certStorage = &certStorage;
    m_inner->setCertificateStorage(certStorage);
  }
  
  void
  CertificateBundleFetcher::doFetch(const shared_ptr<CertificateRequest>& certRequest,
                                    const shared_ptr<ValidationState>& state,
                                    const ValidationContinuation& continueValidation)
  {
    auto dataValidationState = dynamic_pointer_cast<DataValidationState>(state);
    if (dataValidationState == nullptr) {
      return m_inner->fetch(certRequest, state, continueValidation);
    }
  
    // check if a bundle segment was fetched before
    shared_ptr<BundleNameTag> bundleNameTag = state->getTag<BundleNameTag>();
    if (bundleNameTag == nullptr) {
      const Name& originalDataName = dataValidationState->getOriginalData().getName();
      if (originalDataName.empty()) {
        return m_inner->fetch(certRequest, state, continueValidation);
      }
      // derive certificate bundle name from original data name
      Name bundleNamePrefix = deriveBundleName(originalDataName);
      fetchFirstBundleSegment(bundleNamePrefix, certRequest, state, continueValidation);
    }
    else {
      Name fullBundleName = bundleNameTag->get();
      fetchNextBundleSegment(fullBundleName, fullBundleName.get(-1).getSuccessor(),
                             certRequest, state, continueValidation);
    }
  }
  
  void
  CertificateBundleFetcher::fetchFirstBundleSegment(const Name& bundleNamePrefix,
                                                    const shared_ptr<CertificateRequest>& certRequest,
                                                    const shared_ptr<ValidationState>& state,
                                                    const ValidationContinuation& continueValidation)
  {
    Interest bundleInterest = Interest(bundleNamePrefix);
    bundleInterest.setCanBePrefix(true);
    bundleInterest.setMustBeFresh(true);
    bundleInterest.setChildSelector(1);
    bundleInterest.setInterestLifetime(m_bundleInterestLifetime);
  
    m_face.expressInterest(bundleInterest,
                           [=] (const Interest& interest, const Data& data) {
                             dataCallback(data, true, certRequest, state, continueValidation);
                           },
                           [=] (const Interest& interest, const lp::Nack& nack) {
                             nackCallback(nack, certRequest, state, continueValidation, bundleNamePrefix);
                           },
                           [=] (const Interest& interest) {
                             timeoutCallback(certRequest, state, continueValidation, bundleNamePrefix);
                           });
  }
  
  void
  CertificateBundleFetcher::fetchNextBundleSegment(const Name& fullBundleName, const name::Component& segmentNo,
                                                   const shared_ptr<CertificateRequest>& certRequest,
                                                   const shared_ptr<ValidationState>& state,
                                                   const ValidationContinuation& continueValidation)
  {
    shared_ptr<FinalBlockIdTag> finalBlockId = state->getTag<FinalBlockIdTag>();
    if (finalBlockId != nullptr && segmentNo > finalBlockId->get()) {
      return m_inner->fetch(certRequest, state, continueValidation);
    }
  
    Interest bundleInterest(fullBundleName.getPrefix(-1).append(segmentNo));
    bundleInterest.setCanBePrefix(false);
    bundleInterest.setMustBeFresh(false);
    bundleInterest.setInterestLifetime(m_bundleInterestLifetime);
  
    m_face.expressInterest(bundleInterest,
                           [=] (const Interest& interest, const Data& data) {
                             dataCallback(data, false, certRequest, state, continueValidation);
                           },
                           [=] (const Interest& interest, const lp::Nack& nack) {
                             nackCallback(nack, certRequest, state, continueValidation, fullBundleName);
                           },
                           [=] (const Interest& interest) {
                             timeoutCallback(certRequest, state, continueValidation, fullBundleName);
                           });
  }
  
  void
  CertificateBundleFetcher::dataCallback(const Data& bundleData,
                                         bool isSegmentZeroExpected,
                                         const shared_ptr<CertificateRequest>& certRequest,
                                         const shared_ptr<ValidationState>& state,
                                         const ValidationContinuation& continueValidation)
  {
    NDN_LOG_DEBUG_DEPTH("Fetched certificate bundle from network " << bundleData.getName());
  
    name::Component currentSegment = bundleData.getName().get(-1);
    if (!currentSegment.isSegment()) {
      return m_inner->fetch(certRequest, state, continueValidation);
    }
  
    if (isSegmentZeroExpected && currentSegment.toSegment() != 0) {
      // fetch segment zero
      fetchNextBundleSegment(bundleData.getName(), name::Component::fromSegment(0),
                             certRequest, state, continueValidation);
    }
    else {
      state->setTag(make_shared<BundleNameTag>(bundleData.getName()));
  
      const auto& finalBlockId = bundleData.getFinalBlock();
      if (!finalBlockId) {
        state->setTag(make_shared<FinalBlockIdTag>(*finalBlockId));
      }
  
      Block bundleContent = bundleData.getContent();
      bundleContent.parse();
  
      // store all the certificates in unverified cache
      for (const auto& block : bundleContent.elements()) {
        m_certStorage->cacheUnverifiedCert(Certificate(block));
      }
  
      auto cert = m_certStorage->getUnverifiedCertCache().find(certRequest->interest);
      continueValidation(*cert, state);
    }
  }
  
  void
  CertificateBundleFetcher::nackCallback(const lp::Nack& nack,
                                         const shared_ptr<CertificateRequest>& certRequest,
                                         const shared_ptr<ValidationState>& state,
                                         const ValidationContinuation& continueValidation,
                                         const Name& bundleName)
  {
    NDN_LOG_DEBUG_DEPTH("NACK (" << nack.getReason() <<  ") while fetching certificate bundle"
                        << bundleName);
  
    m_inner->fetch(certRequest, state, continueValidation);
  }
  
  void
  CertificateBundleFetcher::timeoutCallback(const shared_ptr<CertificateRequest>& certRequest,
                                            const shared_ptr<ValidationState>& state,
                                            const ValidationContinuation& continueValidation,
                                            const Name& bundleName)
  {
    NDN_LOG_DEBUG_DEPTH("Timeout while fetching certificate bundle" << bundleName);
  
    m_inner->fetch(certRequest, state, continueValidation);
  }
  
  Name
  CertificateBundleFetcher::deriveBundleName(const Name& name)
  {
    name::Component lastComponent = name.at(-1);
  
    Name bundleName = name;
    if (lastComponent.isImplicitSha256Digest()) {
      if (name.size() >= 2 && name.get(-2).isSegment()) {
        bundleName = name.getPrefix(-2);
      }
      else {
        bundleName = name.getPrefix(-1);
      }
    }
    else if (lastComponent.isSegment()) {
      bundleName = name.getPrefix(-1);
    }
    bundleName.append("_BUNDLE");
    bundleName.appendNumber(00);
  
    return bundleName;
  }
  
  } // namespace v2
  } // namespace security
  } // namespace ndn
  