ndns-update: load Response from file directly

- also fix the bug that ndns-update segfaults when running without
parameters

Change-Id: I65a741e845d44d14aa29b2814c6616d27dc0a12a
Refs: #2255, #2273
diff --git a/tools/ndns-update.cpp b/tools/ndns-update.cpp
index 6caf991..25fda11 100644
--- a/tools/ndns-update.cpp
+++ b/tools/ndns-update.cpp
@@ -47,50 +47,34 @@
 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)
+  NdnsUpdate(const Name& hint, const Name& zone, const shared_ptr<Data>& update, Face& face)
+    : m_hint(hint)
     , m_zone(zone)
-    , m_certName(certName)
     , m_interestLifetime(DEFAULT_INTEREST_LIFETIME)
     , m_face(face)
     , m_validator(face)
+    , m_update(update)
     , 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()
+  start()
   {
-    NDNS_LOG_INFO(" =================================== "
+    NDNS_LOG_INFO(" ================ "
                   << "start to update RR at Zone = " << this->m_zone
-                  << " with rrLabel = " << this->m_update.getRrLabel()
-                  << " and rrType = " << this->m_rrType
-                  << " =================================== ");
-    NDNS_LOG_INFO("certificate to sign the data is: " << m_certName);
+                  << " new RR is: " << m_update->getName()
+                  <<" =================== ");
+
+    NDNS_LOG_INFO("new RR is signed by: "
+                  << m_update->getSignature().getKeyLocator().getName());
 
     Interest interest = this->makeUpdateInterest();
-    NDNS_LOG_TRACE("[* <- *] send Update: " << m_update);
+    NDNS_LOG_TRACE("[* <- *] send Update: " << m_update->getName().toUri());
     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
@@ -151,11 +135,8 @@
   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.setRrLabel(Name().append(m_update->wireEncode()));
     q.setRrType(label::NDNS_UPDATE_LABEL);
     q.setInterestLifetime(m_interestLifetime);
 
@@ -187,17 +168,6 @@
   }
 
 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)
@@ -211,26 +181,17 @@
     return m_hasError;
   }
 
-  const Response&
-  getUpdate() const
-  {
-    return m_update;
-  }
-
 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;
+  shared_ptr<Data> m_update;
   bool m_hasError;
 };
 
@@ -254,8 +215,8 @@
   Name certName;
   std::vector<string> contents;
   string contentFile;
-  string dstFile;
-  ndn::Block block;
+  shared_ptr<Data> update;
+
   try {
     namespace po = boost::program_options;
     po::variables_map vm;
@@ -274,7 +235,7 @@
       ("content,o", po::value<std::vector<string>>(&contents)->multitoken(),
        "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")
+       " Response packet in base64 format")
       ;
 
     po::options_description hidden("Hidden Options");
@@ -292,7 +253,11 @@
     po::options_description config_file_options;
     config_file_options.add(config).add(hidden);
 
-    po::options_description visible("Allowed options");
+    po::options_description visible("Usage: ndns-update zone rrLabel [-t rrType] [-T TTL] "
+                                    "[-H hint] [-n NdnsType] [-c cert] "
+                                    "[-f contentFile]|[-o content]\n"
+                                    "Allowed options");
+
     visible.add(generic).add(config);
 
     po::parsed_options parsed =
@@ -302,92 +267,165 @@
     po::notify(vm);
 
     if (vm.count("help")) {
-      std::cout << "Usage: ndns-update zone rrLabel [-t rrType] [-T TTL] "
-        "[-H hint] [-n NdnsType] [-c cert] [-f contentFile]|[-o content]" << std::endl;
       std::cout << visible << std::endl;
       return 0;
     }
 
-    KeyChain keyChain;
-    if (certName.empty()) {
-      Name name = Name().append(zone).append(rrLabel);
-      // choosing the longest match of the identity who also have default certificate
-      for (size_t i = name.size() + 1; i > 0; --i) { // i >=0 will present warnning
-        Name tmp = name.getPrefix(i - 1);
-        if (keyChain.doesIdentityExist(tmp)) {
-          try {
-            certName = keyChain.getDefaultCertificateNameForIdentity(tmp);
-            break;
-          }
-          catch (std::exception&) {
-            // If it cannot get a default certificate from one identity,
-            // just ignore this one try next identity.
-            ;
-          }
-        }
-      }
-    }
-    else {
-      if (!keyChain.doesCertificateExist(certName)) {
-        std::cerr << "certificate: " << certName << " does not exist" << std::endl;
-        return 0;
-      }
-    }
-
-    if (certName.empty()) {
-      std::cerr << "cannot figure out the certificate automatically. "
-                << "please set it with -c CERT_NAEME" << std::endl;
-    }
 
     if (vm.count("content") && vm.count("contentFile")) {
-      std::cerr << "both content and contentFile are set. Only one is allowed" << std::endl;
-      return 0;
+      std::cerr << "both -o content and -f contentFile are set. Only one is allowed" << std::endl;
+      return 1;
     }
 
-    if (!contentFile.empty()) {
-      shared_ptr<ndn::Data> data = ndn::io::load<ndn::Data>(contentFile);
-      block = data->wireEncode();
+    if (!vm.count("contentFile")) {
+      NDNS_LOG_TRACE("content option is set. try to figure out the certificate");
+      if (!vm.count("zone") || !vm.count("rrlabel")) {
+        std::cerr << "-o option must be set together with -z zone and -r rrLabel" << std::endl;
+        return 1;
+      }
+
+      KeyChain keyChain;
+      if (certName.empty()) {
+        Name name = Name().append(zone).append(rrLabel);
+        // choosing the longest match of the identity who also have default certificate
+        for (size_t i = name.size() + 1; i > 0; --i) { // i >=0 will present warnning
+          Name tmp = name.getPrefix(i - 1);
+          if (keyChain.doesIdentityExist(tmp)) {
+            try {
+              certName = keyChain.getDefaultCertificateNameForIdentity(tmp);
+              break;
+            }
+            catch (std::exception&) {
+              // If it cannot get a default certificate from one identity,
+              // just ignore this one try next identity.
+              ;
+            }
+          }
+        } // for
+
+        if (certName.empty()) {
+          std::cerr << "cannot figure out the certificate automatically. "
+                    << "please set it with -c CERT_NAEME" << std::endl;
+          return 1;
+        }
+      }
+      else {
+        if (!keyChain.doesCertificateExist(certName)) {
+          std::cerr << "certificate: " << certName << " does not exist" << std::endl;
+          return 1;
+        }
+      }
+
+      NdnsType ndnsType = toNdnsType(ndnsTypeStr);
+
+      if (ndnsType == ndns::NDNS_UNKNOWN) {
+        std::cerr << "unknown NdnsType: " << ndnsTypeStr << std::endl;
+        return 1;
+      }
+
+      Response re;
+      re.setZone(zone);
+      re.setRrLabel(rrLabel);
+      name::Component qType = (rrType == "ID-CERT" ?
+                               ndns::label::NDNS_CERT_QUERY : ndns::label::NDNS_ITERATIVE_QUERY);
+
+      re.setQueryType(qType);
+      re.setRrType(name::Component(rrType));
+      re.setNdnsType(ndnsType);
+
+      for (const auto& content : contents) {
+        re.addRr(ndn::dataBlock(ndn::ndns::tlv::RrData, content.c_str(), content.size()));
+
+        // re.addRr(content);
+      }
+
+      update = re.toData();
+      keyChain.sign(*update, certName);
+    }
+    else {
+      try {
+        update = ndn::io::load<ndn::Data>(contentFile);
+        NDNS_LOG_TRACE("load data " << update->getName() << " from content file: " << contentFile);
+      }
+      catch (const std::exception& e) {
+        std::cerr << "Error: load Data packet from file: " << contentFile
+                  << ". Due to: " << e.what() << std::endl;
+        return 1;
+      }
+
+      try {
+        // must check the Data is a legal Response with right name
+        shared_ptr<Regex> regex = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT><>*");
+        shared_ptr<Regex> regex2 = make_shared<Regex>("(<>*)<NDNS>(<>+)");
+
+        Name zone2;
+        if (regex->match(update->getName())) {
+          zone2 = regex->expand("\\1");
+        }
+        else if (regex2->match(update->getName())) {
+          zone2 = regex2->expand("\\1");
+        }
+        else {
+          std::cerr << "The loaded Data packet cannot be stored in NDNS "
+            "since its does not have a proper name" << std::endl;
+          return 1;
+        }
+
+        if (vm.count("zone") && zone != zone2) {
+          std::cerr << "The loaded Data packet is supposed to be stored at zone: " << zone2
+                    << " instead of zone: " << zone << std::endl;
+          return 1;
+        }
+        else {
+          zone = zone2;
+        }
+
+        Response re;
+        re.fromData(hint, zone, *update);
+
+        if (vm.count("rrlabel") && rrLabel != re.getRrLabel()) {
+          std::cerr << "The loaded Data packet is supposed to have rrLabel: " << re.getRrLabel()
+                    << " instead of label: " << rrLabel << std::endl;
+          return 1;
+        }
+
+        if (vm.count("rrtype") && name::Component(rrType) != re.getRrType()) {
+          std::cerr << "The loaded Data packet is supposed to have rrType: " << re.getRrType()
+                    << " instead of label: " << rrType << std::endl;
+          return 1;
+        }
+      }
+      catch (const std::exception& e) {
+        std::cerr << "Error: the loaded Data packet cannot parse to a Response stored at zone: "
+                  << zone << std::endl;
+        return 1;
+      }
+
     }
   }
   catch (const std::exception& ex) {
     std::cerr << "Parameter Error: " << ex.what() << std::endl;
-    return 0;
+    return 1;
   }
 
   Face face;
-  NdnsType ndnsType = toNdnsType(ndnsTypeStr);
+  try {
+    NdnsUpdate updater(hint, zone, update, face);
+    updater.setInterestLifetime(ndn::time::seconds(ttl));
 
-  NdnsUpdate update(hint, zone, rrLabel, ndn::name::Component(rrType),
-                    ndnsType, certName, face);
-  update.setInterestLifetime(ndn::time::seconds(ttl));
-
-  if (!contentFile.empty()) {
-    if (!block.empty()) {
-      if (ndnsType == ndn::ndns::NDNS_RAW)
-        update.setUpdateAppContent(block);
-      else {
-        update.addUpdateRr(block);
-      }
-    }
-  }
-  else {
-    if (ndnsType == ndn::ndns::NDNS_RAW) {
-      // since NDNS_RAW's message tlv type cannot be decided, here we stop this option
-      std::cerr << "--content (-o) does not support NDNS-RAW Response" << std::endl;
+    updater.start();
+    face.processEvents();
+    if (updater.hasError())
+      return 1;
+    else
       return 0;
-    }
-    else {
-      for (const auto& content : contents) {
-        block = ndn::dataBlock(ndn::ndns::tlv::RrData, content.c_str(), content.size());
-        update.addUpdateRr(block);
-      }
-    }
   }
-
-  update.run();
-
-  if (update.hasError())
+  catch (const ndn::ValidatorConfig::Error& e) {
+    std::cerr << "Fail to create the validator: " << e.what() << std::endl;
     return 1;
-  else
-    return 0;
+  }
+  catch (const std::exception& e) {
+    std::cerr << "Error: " << e.what() << std::endl;
+    return 1;
+  }
 }