add NdnsUpdate

Change-Id: Ia34e0228e6e9164239f3412e5c419f965e35daec
diff --git a/tools/ndns-update.cpp b/tools/ndns-update.cpp
new file mode 100644
index 0000000..bb08adf
--- /dev/null
+++ b/tools/ndns-update.cpp
@@ -0,0 +1,352 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014, 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 "clients/response.hpp"
+#include "clients/query.hpp"
+#include "ndns-label.hpp"
+#include "validator.hpp"
+#include "ndns-enum.hpp"
+#include "ndns-tlv.hpp"
+#include "logger.hpp"
+#include "daemon/db-mgr.hpp"
+#include "util/util.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/data.hpp>
+#include <ndn-cxx/util/io.hpp>
+#include <ndn-cxx/encoding/block.hpp>
+#include <ndn-cxx/encoding/block-helpers.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/program_options.hpp>
+#include <boost/asio.hpp>
+#include <boost/filesystem.hpp>
+
+#include <string>
+#include <tuple>
+
+namespace ndn {
+namespace ndns {
+NDNS_LOG_INIT("NdnsUpdate");
+
+class NdnsUpdate : noncopyable
+{
+public:
+  NdnsUpdate(const Name& hint, const Name& zone, const Name& rrLabel,
+             const name::Component& rrType, NdnsType ndnsType, const Name& certName,
+             Face& face)
+    : m_queryType(rrType == label::CERT_RR_TYPE ?
+                  label::NDNS_CERT_QUERY : label::NDNS_ITERATIVE_QUERY)
+    , m_rrType(rrType)
+    , m_hint(hint)
+    , m_zone(zone)
+    , m_certName(certName)
+    , m_interestLifetime(DEFAULT_INTEREST_LIFETIME)
+    , m_face(face)
+    , m_validator(face)
+    , m_hasError(false)
+  {
+    m_update.setZone(m_zone);
+    m_update.setRrLabel(rrLabel);
+    m_update.setQueryType(m_queryType);
+    m_update.setRrType(name::Component(rrType));
+    m_update.setNdnsType(ndnsType);
+  }
+
+  void
+  run()
+  {
+    NDNS_LOG_INFO(" =================================== "
+                  << "start to update RR at Zone = " << this->m_zone
+                  << " with rrLabel = " << this->m_update.getRrLabel()
+                  << " and rrType = " << this->m_rrType
+                  << " =================================== ");
+
+    Interest interest = this->makeUpdateInterest();
+    NDNS_LOG_TRACE("[* <- *] send Update: " << m_update);
+    m_face.expressInterest(interest,
+                           bind(&NdnsUpdate::onData, this, _1, _2),
+                           bind(&NdnsUpdate::onTimeout, this, _1) //dynamic binding
+                           );
+    try {
+      m_face.processEvents();
+    }
+    catch (std::exception& e) {
+      NDNS_LOG_FATAL("Face fails to process events: " << e.what());
+      m_hasError = true;
+    }
+  }
+
+  void
+  stop()
+  {
+    m_face.getIoService().stop();
+  }
+
+private:
+  void
+  onData(const Interest& interest, const Data& data)
+  {
+    NDNS_LOG_INFO("get response of Update");
+    int ret = -1;
+    std::string msg;
+    std::tie(ret, msg) = this->parseResponse(data);
+    NDNS_LOG_INFO("Return Code: " << ret << ", and Update "
+                  << (ret == UPDATE_OK ? "succeeds" : "fails"));
+    if (ret != UPDATE_OK)
+      m_hasError = true;
+
+    if (!msg.empty()) {
+      NDNS_LOG_INFO("Return Msg: " << msg);
+    }
+
+    NDNS_LOG_INFO("to verify the response");
+    m_validator.validate(data,
+                         bind(&NdnsUpdate::onDataValidated, this, _1),
+                         bind(&NdnsUpdate::onDataValidationFailed, this, _1, _2)
+                         );
+  }
+
+  std::tuple<int, std::string>
+  parseResponse(const Data& data)
+  {
+    int ret = -1;
+    std::string msg;
+    Block blk = data.getContent();
+    blk.parse();
+    Block block = blk.blockFromValue();
+    block.parse();
+    Block::element_const_iterator val = block.elements_begin();
+    for (; val != block.elements_end(); ++val) {
+      if (val->type() == ndns::tlv::UpdateReturnCode) { // the first must be return code
+        ret = readNonNegativeInteger(*val);
+      }
+      else if (val->type() == ndns::tlv::UpdateReturnMsg) {
+        msg =  std::string(reinterpret_cast<const char*>(val->value()), val->value_size());
+      }
+    }
+
+    return std::make_tuple(ret, msg);
+  }
+
+  /**
+   * @brief construct a query (interest) which contains the update information
+   */
+  Interest
+  makeUpdateInterest()
+  {
+    shared_ptr<Data> data = m_update.toData();
+    m_keyChain.sign(*data, m_certName);
+
+    Query q(m_hint, m_zone, label::NDNS_ITERATIVE_QUERY);
+    q.setRrLabel(Name().append(data->wireEncode()));
+    q.setRrType(label::NDNS_UPDATE_LABEL);
+    q.setInterestLifetime(m_interestLifetime);
+
+    return q.toInterest();
+  }
+
+private:
+  void
+  onTimeout(const ndn::Interest& interest)
+  {
+    NDNS_LOG_TRACE("Update timeouts");
+    m_hasError = true;
+    this->stop();
+  }
+
+  void
+  onDataValidated(const shared_ptr<const Data>& data)
+  {
+    NDNS_LOG_INFO("data pass verification");
+    this->stop();
+  }
+
+  void
+  onDataValidationFailed(const shared_ptr<const Data>& data, const std::string& str)
+  {
+    NDNS_LOG_INFO("data does not pass verification");
+    m_hasError = true;
+    this->stop();
+  }
+
+public:
+  void
+  setUpdateAppContent(const Block& block)
+  {
+    m_update.setAppContent(block);
+  }
+
+  void
+  addUpdateRr(const Block& block)
+  {
+    m_update.addRr(block);
+  }
+
+  void
+  setInterestLifetime(const time::milliseconds& interestLifetime)
+  {
+    m_interestLifetime = interestLifetime;
+  }
+
+  const bool
+  hasError() const
+  {
+    return m_hasError;
+  }
+
+private:
+  name::Component m_queryType; ///< NDNS or KEY
+  name::Component m_rrType;
+
+  Name m_hint;
+  Name m_zone;
+  Name m_certName;
+  time::milliseconds m_interestLifetime;
+
+  Face& m_face;
+  Validator m_validator;
+  KeyChain m_keyChain;
+
+  Response m_update;
+  bool m_hasError;
+};
+
+} // namespace ndns
+} // namespace ndn
+
+int
+main(int argc, char* argv[])
+{
+  ndn::ndns::log::init();
+  using std::string;
+  using namespace ndn;
+  using namespace ndn::ndns;
+
+  Name hint;
+  Name zone;
+  int ttl = 4;
+  Name rrLabel;
+  string rrType = "TXT";
+  string ndnsTypeStr = "resp";
+  Name certName;
+  string content;
+  string contentFile;
+  ndn::Block block;
+  try {
+    namespace po = boost::program_options;
+    po::variables_map vm;
+
+    po::options_description generic("Generic Options");
+    generic.add_options()("help,h", "print help message");
+
+    po::options_description config("Configuration");
+    config.add_options()
+      ("hint,H", po::value<Name>(&hint), "forwarding hint")
+      ("ttl,T", po::value<int>(&ttl), "TTL of query. default: 4 sec")
+      ("rrtype,t", po::value<string>(&rrType), "set request RR Type. default: TXT")
+      ("ndnsType,n", po::value<string>(&ndnsTypeStr), "Set the ndnsType of the resource record. "
+       "Potential values are [resp|nack|auth|raw]. Default: resp")
+      ("cert,c", po::value<Name>(&certName), "set the name of certificate to sign the update")
+      ("content,o", po::value<string>(&content), "set the content of the RR")
+      ("contentFile,f", po::value<string>(&contentFile), "set the path of file which contain"
+       " content of the RR in base64 format")
+      ;
+
+    po::options_description hidden("Hidden Options");
+    hidden.add_options()
+      ("zone,z", po::value<Name>(&zone), "zone the record is delegated")
+      ("rrlabel,l", po::value<Name>(&rrLabel), "set request RR Label")
+      ;
+    po::positional_options_description postion;
+    postion.add("zone", 1);
+    postion.add("rrlabel", 1);
+
+    po::options_description cmdline_options;
+    cmdline_options.add(generic).add(config).add(hidden);
+
+    po::options_description config_file_options;
+    config_file_options.add(config).add(hidden);
+
+    po::options_description visible("Allowed options");
+    visible.add(generic).add(config);
+
+    po::parsed_options parsed =
+      po::command_line_parser(argc, argv).options(cmdline_options).positional(postion).run();
+
+    po::store(parsed, vm);
+    po::notify(vm);
+
+    if (vm.count("help")) {
+      std::cout << "Usage: ndns-update zone rrLabel [-t rrType] [-w waitingSec] "
+        "[-H hint] [-n NdnsType] [-c cert] [-f contentFile]|[-o content]" << std::endl;
+      std::cout << visible << std::endl;
+      return 0;
+    }
+
+    KeyChain keyChain;
+    if (certName.empty()) {
+      certName = keyChain.getDefaultCertificateName().toUri();
+    }
+    else {
+      if (!keyChain.doesCertificateExist(certName)) {
+        std::cerr << "certificate: " << certName << " does not exist" << std::endl;
+        return 0;
+      }
+    }
+
+    if (vm.count("content") && vm.count("contentFile")) {
+      std::cerr <<"both content and contentFile are set. Only one is allowed" << std::endl;
+      return 0;
+    }
+
+    if (!contentFile.empty()) {
+      shared_ptr<ndn::Data> data = ndn::io::load<ndn::Data>(contentFile);
+      block = data->wireEncode();
+    }
+    else {
+      if (!content.empty())
+        block = ndn::dataBlock(ndn::ndns::tlv::RrData, content.c_str(), content.size());
+    }
+  }
+  catch (const std::exception& ex) {
+    std::cerr << "Parameter Error: " << ex.what() << std::endl;
+    return 0;
+  }
+
+  Face face;
+  NdnsType ndnsType = toNdnsType(ndnsTypeStr);
+
+  NdnsUpdate update(hint, zone, rrLabel, ndn::name::Component(rrType),
+                    ndnsType, certName, face);
+  update.setInterestLifetime(ndn::time::seconds(ttl));
+
+  if (!block.empty()) {
+    if (ndnsType == ndn::ndns::NDNS_RAW)
+      update.setUpdateAppContent(block);
+    else
+      update.addUpdateRr(block);
+  }
+
+  update.run();
+
+  if (update.hasError())
+    return 1;
+  else
+    return 0;
+}