tools: Add NDN-FCH stage (service to get "closest" NDN hub) to ndn-autoconfig

Change-Id: Ia2b0bf93d3d25b45713eeba58924e64871715bdb
Refs: #3800
diff --git a/tools/ndn-autoconfig/ndn-fch-discovery.cpp b/tools/ndn-autoconfig/ndn-fch-discovery.cpp
new file mode 100644
index 0000000..56b7fd1
--- /dev/null
+++ b/tools/ndn-autoconfig/ndn-fch-discovery.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ndn-fch-discovery.hpp"
+#include <boost/regex.hpp>
+#include <boost/algorithm/string.hpp>
+
+namespace ndn {
+namespace tools {
+namespace autoconfig {
+
+/**
+ * A partial and specialized copy of ndn::FaceUri implementation
+ *
+ * Consider removing in favor of a library-provided URL parsing, if project
+ * includes such a library.
+ */
+class Url
+{
+public:
+  Url(const std::string& url)
+    : m_isValid(false)
+  {
+    static const boost::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
+    boost::smatch protocolMatch;
+    if (!boost::regex_match(url, protocolMatch, protocolExp)) {
+      return;
+    }
+    m_scheme = protocolMatch[1];
+    const std::string& authority = protocolMatch[3];
+    m_path = protocolMatch[4];
+
+    // pattern for IPv6 address enclosed in [ ], with optional port number
+    static const boost::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
+    // pattern for IPv4-mapped IPv6 address, with optional port number
+    static const boost::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
+    // pattern for IPv4/hostname/fd/ifname, with optional port number
+    static const boost::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
+
+    if (authority.empty()) {
+      // UNIX, internal
+    }
+    else {
+      boost::smatch match;
+      bool isV6 = boost::regex_match(authority, match, v6Exp);
+      if (isV6 ||
+          boost::regex_match(authority, match, v4MappedV6Exp) ||
+          boost::regex_match(authority, match, v4HostExp)) {
+        m_host = match[1];
+        m_port = match[2];
+      }
+      else {
+        return;
+      }
+    }
+    if (m_port.empty()) {
+      m_port = "80";
+    }
+    if (m_path.empty()) {
+      m_path = "/";
+    }
+    m_isValid = true;
+  }
+
+  bool
+  isValid() const
+  {
+    return m_isValid;
+  }
+
+  const std::string&
+  getScheme() const
+  {
+    return m_scheme;
+  }
+
+  const std::string&
+  getHost() const
+  {
+    return m_host;
+  }
+
+  const std::string&
+  getPort() const
+  {
+    return m_port;
+  }
+
+  const std::string&
+  getPath() const
+  {
+    return m_path;
+  }
+
+private:
+  bool m_isValid;
+  std::string m_scheme;
+  std::string m_host;
+  std::string m_port;
+  std::string m_path;
+};
+
+class HttpException : public std::runtime_error
+{
+public:
+  explicit
+  HttpException(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+NdnFchDiscovery::NdnFchDiscovery(Face& face, KeyChain& keyChain,
+                                 const std::string& url,
+                                 const NextStageCallback& nextStageOnFailure)
+  : Base(face, keyChain, nextStageOnFailure)
+  , m_url(url)
+{
+}
+
+void
+NdnFchDiscovery::start()
+{
+  try {
+    using namespace boost::asio::ip;
+    tcp::iostream requestStream;
+
+    requestStream.expires_from_now(boost::posix_time::milliseconds(3000));
+
+    Url url(m_url);
+    if (!url.isValid()) {
+      BOOST_THROW_EXCEPTION(HttpException("Invalid NDN-FCH URL: " + m_url));
+    }
+
+    if (!boost::iequals(url.getScheme(), "http")) {
+      BOOST_THROW_EXCEPTION(HttpException("Only http:// NDN-FCH URLs are supported"));
+    }
+
+    requestStream.connect(url.getHost(), url.getPort());
+
+    if (!requestStream) {
+      BOOST_THROW_EXCEPTION(HttpException("HTTP connection error to " + m_url));
+    }
+
+    requestStream << "GET " << url.getPath() << " HTTP/1.0\r\n";
+    requestStream << "Host: " << url.getHost() << ":" << url.getPort() << "\r\n";
+    requestStream << "Accept: */*\r\n";
+    requestStream << "Cache-Control: no-cache\r\n";
+    requestStream << "Connection: close\r\n\r\n";
+    requestStream.flush();
+
+    std::string statusLine;
+    std::getline(requestStream, statusLine);
+    if (!requestStream) {
+      BOOST_THROW_EXCEPTION(HttpException("HTTP communication error"));
+    }
+
+    std::stringstream responseStream(statusLine);
+    std::string httpVersion;
+    responseStream >> httpVersion;
+    unsigned int statusCode;
+    responseStream >> statusCode;
+    std::string statusMessage;
+
+    std::getline(responseStream, statusMessage);
+    if (!static_cast<bool>(requestStream) || httpVersion.substr(0, 5) != "HTTP/") {
+      throw HttpException("HTTP communication error");
+    }
+    if (statusCode != 200) {
+      boost::trim(statusMessage);
+      throw HttpException("HTTP request failed: " +
+                          std::to_string(statusCode) + " " + statusMessage);
+    }
+    std::string header;
+    while (std::getline(requestStream, header) && header != "\r")
+      ;
+
+    std::string hubHost;
+    requestStream >> hubHost;
+
+    if (hubHost.empty()) {
+      throw HttpException("NDN-FCH did not return hub host");
+    }
+
+    this->connectToHub("udp://" + hubHost);
+  }
+  catch (const std::runtime_error& e) {
+    m_nextStageOnFailure(std::string("Failed to find NDN router using NDN-FCH service (") + e.what() + ")");
+  }
+}
+
+} // namespace autoconfig
+} // namespace tools
+} // namespace ndn