daemon: Add a new class RrsetFactory for generating RR sets

Change-Id: I9569ef4f57a07d909be27e36974029856e34798b
diff --git a/src/daemon/rrset-factory.cpp b/src/daemon/rrset-factory.cpp
new file mode 100644
index 0000000..b1bbf90
--- /dev/null
+++ b/src/daemon/rrset-factory.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016, Regents of the University of California.
+ *
+ * This file is part of NDNS (Named Data Networking Domain Name Service).
+ * See AUTHORS.md for complete list of NDNS authors and contributors.
+ *
+ * NDNS 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.
+ *
+ * NDNS 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
+ * NDNS, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rrset-factory.hpp"
+#include "mgmt/management-tool.hpp"
+
+#include <boost/algorithm/string/join.hpp>
+
+namespace ndn {
+namespace ndns {
+
+NDNS_LOG_INIT("RrsetFactory")
+
+RrsetFactory::RrsetFactory(const std::string& dbFile,
+                           const Name& zoneName,
+                           KeyChain& keyChain,
+                           const Name& inputDskCertName)
+  : m_keyChain(keyChain)
+  , m_dbFile(dbFile)
+  , m_zone(zoneName)
+  , m_dskCertName(inputDskCertName)
+  , m_checked(false)
+{
+  if (m_dskCertName == DEFAULT_CERT) {
+    m_dskName = m_keyChain.getDefaultKeyNameForIdentity(zoneName);
+    m_dskCertName = m_keyChain.getDefaultCertificateNameForKey(m_dskName);
+  }
+}
+
+void
+RrsetFactory::checkZoneKey()
+{
+  onlyCheckZone();
+  if (m_dskCertName != DEFAULT_CERT &&
+      !matchCertificate(m_dskCertName, m_zone.getName())) {
+    BOOST_THROW_EXCEPTION(Error("Cannot verify certificate"));
+  }
+}
+
+void
+RrsetFactory::onlyCheckZone()
+{
+  if (m_checked) {
+    return ;
+  }
+  m_checked = true;
+
+  DbMgr dbMgr(m_dbFile);
+  const Name& zoneName = m_zone.getName();
+  if (!dbMgr.find(m_zone)) {
+    BOOST_THROW_EXCEPTION(Error(zoneName.toUri() + " is not presented in the NDNS db"));
+  }
+}
+
+
+std::pair<Rrset, Name>
+RrsetFactory::generateBaseRrset(const Name& label,
+                                const name::Component& type,
+                                const uint64_t version,
+                                const time::seconds& ttl)
+{
+  Rrset rrset(&m_zone);
+
+  rrset.setLabel(label);
+  rrset.setType(type);
+  rrset.setTtl(ttl);
+
+  name::Component qType;
+  if (type == label::CERT_RR_TYPE) {
+    qType = label::NDNS_CERT_QUERY;
+  } else {
+    qType = label::NDNS_ITERATIVE_QUERY;
+  }
+
+  Name name;
+  name.append(m_zone.getName())
+      .append(qType)
+      .append(label)
+      .append(type);
+
+  if (version != VERSION_USE_UNIX_TIMESTAMP) {
+    name.append(name::Component::fromVersion(version));
+  } else {
+    name.appendVersion();
+  }
+
+  rrset.setVersion(name.get(-1));
+
+  return std::make_pair(rrset, name);
+}
+
+bool
+RrsetFactory::matchCertificate(const Name& certName, const Name& identity)
+{
+  if (!m_keyChain.doesCertificateExist(certName)) {
+    NDNS_LOG_WARN(certName.toUri() << " is not presented in KeyChain");
+    return false;
+  }
+
+  // Check its public key information
+  shared_ptr<IdentityCertificate> cert = m_keyChain.getCertificate(certName);
+  Name keyName = cert->getPublicKeyName();
+
+  if (!identity.isPrefixOf(keyName) || identity.size() != keyName.size() - 1) {
+    NDNS_LOG_WARN(keyName.toUri() << " is not a key of " << identity.toUri());
+    return false;
+  }
+
+  if (!m_keyChain.doesKeyExistInTpm(keyName, KeyClass::PRIVATE)) {
+    NDNS_LOG_WARN("Private key: " << keyName.toUri() << " is not present in KeyChain");
+    return false;
+  }
+
+  return true;
+}
+
+Rrset
+RrsetFactory::generateNsRrset(const Name& label,
+                              const name::Component& type,
+                              const uint64_t version,
+                              time::seconds ttl,
+                              const ndn::Link::DelegationSet& delegations)
+{
+  if (!m_checked) {
+    BOOST_THROW_EXCEPTION(Error("You have to call checkZoneKey before call generate functions"));
+  }
+
+  if (ttl == DEFAULT_RR_TTL)
+    ttl = m_zone.getTtl();
+
+  std::pair<Rrset, Name> rrsetAndName = generateBaseRrset(label, type, version, ttl);
+  const Name& name = rrsetAndName.second;
+  Rrset& rrset = rrsetAndName.first;
+
+  Link link(name);
+  for (const auto& i : delegations) {
+    link.addDelegation(i.first, i.second);
+  }
+
+  setMetaInfo(link);
+  sign(link);
+  rrset.setData(link.wireEncode());
+
+  return rrset;
+}
+
+Rrset
+RrsetFactory::generateTxtRrset(const Name& label,
+                               const name::Component& type,
+                               const uint64_t version,
+                               time::seconds ttl,
+                               const std::vector<std::string>& strings)
+{
+  if (!m_checked) {
+    BOOST_THROW_EXCEPTION(Error("You have to call checkZoneKey before call generate functions"));
+  }
+
+  if (ttl == DEFAULT_RR_TTL)
+    ttl = m_zone.getTtl();
+
+  Name name;
+  Rrset rrset;
+  std::tie(rrset, name) = generateBaseRrset(label, type, version, ttl);
+
+  std::vector<Block> rrs;
+  for (const auto& item : strings) {
+    rrs.push_back(makeBinaryBlock(ndns::tlv::RrData,
+                                  item.c_str(),
+                                  item.size()));
+  }
+
+  Data data(name);
+  data.setContent(wireEncode(rrs));
+
+  setMetaInfo(data);
+  sign(data);
+  rrset.setData(data.wireEncode());
+
+  return rrset;
+}
+
+Rrset
+RrsetFactory::generateCertRrset(const Name& label,
+                                const name::Component& type,
+                                const uint64_t version,
+                                time::seconds ttl,
+                                const IdentityCertificate& cert)
+{
+  if (!m_checked) {
+    BOOST_THROW_EXCEPTION(Error("You have to call checkZoneKey before call generate functions"));
+  }
+
+  if (ttl == DEFAULT_RR_TTL)
+    ttl = m_zone.getTtl();
+
+  Name name;
+  Rrset rrset;
+  std::tie(rrset, name) = generateBaseRrset(label, type, version, ttl);
+
+  Data data(name);
+  data.setContent(cert.wireEncode());
+
+  setMetaInfo(data);
+  sign(data);
+  rrset.setData(data.wireEncode());
+
+  return rrset;
+}
+
+
+void
+RrsetFactory::sign(Data& data)
+{
+  m_keyChain.sign(data, m_dskCertName);
+}
+
+void
+RrsetFactory::setMetaInfo(Data& data)
+{
+  MetaInfo metaInfo = data.getMetaInfo();
+  metaInfo.addAppMetaInfo(makeNonNegativeIntegerBlock(ndns::tlv::NdnsType,
+                                                      NDNS_RESP));
+  data.setMetaInfo(metaInfo);
+}
+
+template<encoding::Tag TAG>
+inline size_t
+RrsetFactory::wireEncode(EncodingImpl<TAG>& block, const std::vector<Block>& rrs) const
+{
+  // Content :: = CONTENT-TYPE TLV-LENGTH
+  //              Block*
+
+  size_t totalLength = 0;
+  for (auto iter = rrs.rbegin(); iter != rrs.rend(); ++iter) {
+    totalLength += block.prependBlock(*iter);
+  }
+
+  totalLength += block.prependVarNumber(totalLength);
+  totalLength += block.prependVarNumber(::ndn::tlv::Content);
+
+  return totalLength;
+}
+
+const Block
+RrsetFactory::wireEncode(const std::vector<Block>& rrs) const
+{
+  EncodingEstimator estimator;
+  size_t estimatedSize = wireEncode(estimator, rrs);
+  EncodingBuffer buffer(estimatedSize, 0);
+  wireEncode(buffer, rrs);
+  return buffer.block();
+}
+
+std::vector<std::string>
+RrsetFactory::wireDecodeTxt(const Block& wire)
+{
+  std::vector<std::string> txts;
+  wire.parse();
+
+  for (const auto& e: wire.elements()) {
+    txts.push_back(std::string(reinterpret_cast<const char*>(e.value()),
+                               e.value_size()));
+  }
+
+  return txts;
+}
+
+
+} // namespace ndns
+} // namespace ndn
diff --git a/src/daemon/rrset-factory.hpp b/src/daemon/rrset-factory.hpp
new file mode 100644
index 0000000..732f0a7
--- /dev/null
+++ b/src/daemon/rrset-factory.hpp
@@ -0,0 +1,125 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016, Regents of the University of California.
+ *
+ * This file is part of NDNS (Named Data Networking Domain Name Service).
+ * See AUTHORS.md for complete list of NDNS authors and contributors.
+ *
+ * NDNS 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.
+ *
+ * NDNS 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
+ * NDNS, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NDNS_DAEMON_RRSET_FACTORY_HPP
+#define NDNS_DAEMON_RRSET_FACTORY_HPP
+
+#include "common.hpp"
+#include "rrset.hpp"
+#include "logger.hpp"
+#include "daemon/db-mgr.hpp"
+
+#include <ndn-cxx/link.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+
+#include <vector>
+#include <string>
+
+namespace ndn {
+namespace ndns {
+
+class RrsetFactory
+{
+public:
+  /** @brief Represents an error might be thrown during runtime
+   */
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what) : std::runtime_error(what)
+    {
+    }
+  };
+
+public:
+  RrsetFactory(const std::string& dbFile,
+               const Name& zoneName,
+               KeyChain& keyChain,
+               const Name& inputDskCertName);
+
+  void
+  checkZoneKey();
+
+  Rrset
+  generateNsRrset(const Name& label,
+                  const name::Component& type,
+                  const uint64_t version,
+                  time::seconds ttl,
+                  const ndn::Link::DelegationSet& delegations);
+
+  Rrset
+  generateTxtRrset(const Name& label,
+                   const name::Component& type,
+                   const uint64_t version,
+                   time::seconds ttl,
+                   const std::vector<std::string>& contents);
+
+  Rrset
+  generateCertRrset(const Name& label,
+                    const name::Component& type,
+                    const uint64_t version,
+                    time::seconds ttl,
+                    const IdentityCertificate& cert);
+
+  static std::vector<std::string>
+  wireDecodeTxt(const Block& wire);
+
+NDNS_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  // only used for database-test-data unit test
+  void
+  onlyCheckZone();
+
+private:
+  std::pair<Rrset, Name>
+  generateBaseRrset(const Name& label,
+                    const name::Component& type,
+                    const uint64_t version,
+                    const time::seconds& ttl);
+
+  bool
+  matchCertificate(const Name& certName, const Name& identity);
+
+  template<encoding::Tag TAG>
+  inline size_t
+  wireEncode(EncodingImpl<TAG>& block, const std::vector<Block>& rrs) const;
+
+  const Block
+  wireEncode(const std::vector<Block>& rrs) const;
+
+  void
+  sign(Data& data);
+
+  void
+  setMetaInfo(Data& data);
+
+private:
+  KeyChain& m_keyChain;
+  std::string m_dbFile;
+  Zone m_zone;
+  Name m_dskCertName;
+  Name m_dskName;
+  bool m_checked;
+};
+
+} // namespace ndns
+} // namespace ndn
+
+#endif // NDNS_DAEMON_RRSET_FACTORY_HPP
+