util, transport: add configuration file support and make default unix socket configurable

add sample library configuration file

refs: #1364

Change-Id: I3cb36d078aa3f0b0a50d9a83a521e95448df0a93
diff --git a/src/face.cpp b/src/face.cpp
index fa0f24d..2773f07 100644
--- a/src/face.cpp
+++ b/src/face.cpp
@@ -13,6 +13,7 @@
 
 #include "util/time.hpp"
 #include "util/random.hpp"
+#include "util/config-file.hpp"
 #include <cstdlib>
 
 #include "management/ndnd-controller.hpp"
@@ -23,13 +24,15 @@
 
 Face::Face()
 {
-  construct(shared_ptr<Transport>(new UnixTransport()),
+  const std::string socketName = UnixTransport::getDefaultSocketName(m_config);
+  construct(shared_ptr<Transport>(new UnixTransport(socketName)),
             make_shared<boost::asio::io_service>());
 }
 
 Face::Face(const shared_ptr<boost::asio::io_service>& ioService)
 {
-  construct(shared_ptr<Transport>(new UnixTransport()),
+  const std::string socketName = UnixTransport::getDefaultSocketName(m_config);
+  construct(shared_ptr<Transport>(new UnixTransport(socketName)),
             ioService);
 }
 
@@ -68,18 +71,39 @@
   m_pitTimeoutCheckTimer      = make_shared<monotonic_deadline_timer>(boost::ref(*m_ioService));
   m_processEventsTimeoutTimer = make_shared<monotonic_deadline_timer>(boost::ref(*m_ioService));
 
-  if (std::getenv("NFD") != 0)
+  std::string protocol = "nrd-0.1";
+
+  try
     {
-      if (std::getenv("NRD") != 0)
-        m_fwController = make_shared<nrd::Controller>(boost::ref(*this));
-      else
-        m_fwController = make_shared<nfd::Controller>(boost::ref(*this));
+      protocol = m_config.getParsedConfiguration().get<std::string>("protocol");
+    }
+  catch (const boost::property_tree::ptree_bad_path& error)
+    {
+      // protocol not specified
+    }
+  catch (const boost::property_tree::ptree_bad_data& error)
+    {
+      throw ConfigFile::Error(error.what());
+    }
+
+  if (isSupportedNrdProtocol(protocol))
+    {
+      m_fwController = make_shared<nrd::Controller>(boost::ref(*this));
+    }
+  if (isSupportedNfdProtocol(protocol))
+    {
+      m_fwController = make_shared<nfd::Controller>(boost::ref(*this));
+    }
+  else if (isSupportedNdndProtocol(protocol))
+    {
+      m_fwController = make_shared<ndnd::Controller>(boost::ref(*this));
     }
   else
-    m_fwController = make_shared<ndnd::Controller>(boost::ref(*this));
+    {
+      throw Face::Error("Cannot create controller for unsupported protocol \"" + protocol + "\"");
+    }
 }
 
-
 const PendingInterestId*
 Face::expressInterest(const Interest& interest, const OnData& onData, const OnTimeout& onTimeout)
 {
diff --git a/src/face.hpp b/src/face.hpp
index be1ca06..d168e73 100644
--- a/src/face.hpp
+++ b/src/face.hpp
@@ -58,12 +58,16 @@
 
   /**
    * @brief Create a new Face for communication with an NDN Forwarder using the default UnixTransport.
+   * @throws ConfigFile::Error on configuration file parse failure
+   * @throws Face::Error on unsupported protocol
    */
   Face();
 
   /**
    * @brief Create a new Face for communication with an NDN Forwarder using the default UnixTransport.
    * @param ioService A shared pointer to boost::io_service object that should control all IO operations
+   * @throws ConfigFile::Error on configuration file parse failure
+   * @throws Face::Error on unsupported protocol
    */
   explicit
   Face(const shared_ptr<boost::asio::io_service>& ioService);
@@ -72,6 +76,7 @@
    * Create a new Face for communication with an NDN hub at host:port using the default TcpTransport.
    * @param host The host of the NDN hub.
    * @param port The port or service name of the NDN hub. If omitted. use 6363.
+   * @throws Face::Error on unsupported protocol
    */
   Face(const std::string& host, const std::string& port = "6363");
 
@@ -79,6 +84,7 @@
    * Create a new Face for communication with an NDN hub with the given Transport object and connectionInfo.
    * @param transport A shared_ptr to a Transport object used for communication.
    * @param transport A shared_ptr to a Transport::ConnectionInfo to be used to connect to the transport.
+   * @throws Face::Error on unsupported protocol
    */
   explicit
   Face(const shared_ptr<Transport>& transport);
@@ -94,6 +100,7 @@
    *     // Now the following ensures that events on both faces are processed
    *     face1.processEvents();
    * </code>
+   * @throws Face::Error on unsupported protocol
    */
   Face(const shared_ptr<Transport>& transport,
        const shared_ptr<boost::asio::io_service>& ioService);
@@ -203,9 +210,22 @@
   ioService() { return m_ioService; }
 
 private:
+
+  /**
+   * @throws Face::Error on unsupported protocol
+   */
   void
   construct(const shared_ptr<Transport>& transport,
             const shared_ptr<boost::asio::io_service>& ioService);
+
+  bool
+  isSupportedNfdProtocol(const std::string& protocol);
+
+  bool
+  isSupportedNrdProtocol(const std::string& protocol);
+
+  bool
+  isSupportedNdndProtocol(const std::string& protocol);
   
   struct ProcessEventsTimeout {};
   typedef std::list<shared_ptr<PendingInterest> > PendingInterestTable;
@@ -255,8 +275,28 @@
   RegisteredPrefixTable m_registeredPrefixTable;
 
   shared_ptr<Controller> m_fwController;
+
+  ConfigFile m_config;
 };
 
+inline bool
+Face::isSupportedNfdProtocol(const std::string& protocol)
+{
+  return protocol == "nfd-0.1";
+}
+
+inline bool
+Face::isSupportedNrdProtocol(const std::string& protocol)
+{
+  return protocol == "nrd-0.1";
+}
+
+inline bool
+Face::isSupportedNdndProtocol(const std::string& protocol)
+{
+  return protocol == "ndnd-tlv-0.7";
+}
+
 } // namespace ndn
 
 #endif // NDN_FACE_HPP
diff --git a/src/transport/transport.hpp b/src/transport/transport.hpp
index da06da4..e678491 100644
--- a/src/transport/transport.hpp
+++ b/src/transport/transport.hpp
@@ -14,7 +14,12 @@
 
 class Transport {
 public:
-  struct Error : public std::runtime_error { inline Error(const boost::system::error_code &code, const std::string &msg); };
+  class Error : public std::runtime_error
+  {
+  public:
+    inline Error(const boost::system::error_code &code, const std::string &msg);
+    inline Error(const std::string& msg);
+  };
   
   typedef ptr_lib::function<void (const Block &wire)> ReceiveCallback;
   typedef ptr_lib::function<void ()> ErrorCallback;
@@ -89,12 +94,19 @@
 {
 }
 
-inline Transport::Error::Error(const boost::system::error_code& code, const std::string& msg)
+inline
+Transport::Error::Error(const boost::system::error_code& code, const std::string& msg)
   : std::runtime_error(msg + (code.value() ? " (" + code.category().message(code.value()) + ")" : ""))
 {
 }
 
 inline
+Transport::Error::Error(const std::string& msg)
+  : std::runtime_error(msg)
+{
+}
+
+inline
 Transport::~Transport()
 {
 }
diff --git a/src/transport/unix-transport.cpp b/src/transport/unix-transport.cpp
index 33fad1c..ad6db2c 100644
--- a/src/transport/unix-transport.cpp
+++ b/src/transport/unix-transport.cpp
@@ -13,14 +13,6 @@
 
 namespace ndn {
 
-UnixTransport::UnixTransport()
-{
-  if (std::getenv("NFD") != 0)
-      m_unixSocket = "/var/run/nfd.sock";
-  else
-      m_unixSocket = "/tmp/.ndnd.sock";
-}
-
 UnixTransport::UnixTransport(const std::string& unixSocket)
   : m_unixSocket(unixSocket)
 {
@@ -30,6 +22,48 @@
 {
 }
 
+std::string
+UnixTransport::getDefaultSocketName(const ConfigFile& config)
+{
+  const ConfigFile::Parsed& parsed = config.getParsedConfiguration();
+  try
+    {
+      return parsed.get<std::string>("unix_socket");
+    }
+  catch (const boost::property_tree::ptree_bad_path& error)
+    {
+      // unix_socket not present, continue
+    }
+  catch (const boost::property_tree::ptree_bad_data& error)
+    {
+      throw ConfigFile::Error(error.what());
+    }
+
+  // no unix_socket specified so the default socket name
+  // depends on the protocol we're using
+  try
+    {
+      const std::string protocol = parsed.get<std::string>("protocol");
+      if (protocol == "ndnd-tlv-0.7")
+        {
+          return "/tmp/.ndnd.sock";
+        }
+    }
+  catch (boost::property_tree::ptree_bad_path& error)
+    {
+      return "/var/run/nfd.sock";
+    }
+  catch (boost::property_tree::ptree_bad_data& error)
+    {
+      throw ConfigFile::Error(error.what());
+    }
+
+  // A we made here, then there's no unix_socket specified in the configuration
+  // file. A protocol is present, but it's not ndnd.
+  // Assume the default nfd.sock location.
+  return "/var/run/nfd.sock";
+}
+
 void
 UnixTransport::connect(boost::asio::io_service& ioService,
                        const ReceiveCallback& receiveCallback)
diff --git a/src/transport/unix-transport.hpp b/src/transport/unix-transport.hpp
index f8114f5..47636fa 100644
--- a/src/transport/unix-transport.hpp
+++ b/src/transport/unix-transport.hpp
@@ -9,6 +9,7 @@
 
 #include "../common.hpp"
 #include "transport.hpp"
+#include "../util/config-file.hpp"
 
 // forward declaration
 namespace boost { namespace asio { namespace local { class stream_protocol; } } }
@@ -22,9 +23,15 @@
 class UnixTransport : public Transport
 {
 public:
-  UnixTransport();
 
+  /**
+   * Create Unix transport based on the socket specified
+   * in a well-known configuration file or fallback to /var/run/nfd.sock
+   *
+   * @throws Throws UnixTransport::Error on failure to parse a discovered configuration file
+   */
   UnixTransport(const std::string& unixSocket);
+
   ~UnixTransport();
 
   // from Transport
@@ -46,7 +53,16 @@
 
   virtual void
   send(const Block& header, const Block& payload);
-  
+
+  /**
+   * Determine the default NFD unix socket
+   *
+   * @returns unix_socket value if present in config, else /var/run/nfd.sock
+   * @throws ConfigFile::Error if fail to parse value of a present "unix_socket" field
+   */
+  static std::string
+  getDefaultSocketName(const ConfigFile& config);
+
 private:
   std::string m_unixSocket;
 
diff --git a/src/util/config-file.cpp b/src/util/config-file.cpp
new file mode 100644
index 0000000..031df25
--- /dev/null
+++ b/src/util/config-file.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+
+#include "config-file.hpp"
+
+#include <boost/property_tree/ini_parser.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+
+ConfigFile::ConfigFile()
+  : m_path(findConfigFile())
+{
+  if (open())
+    {
+      parse();
+      close();
+    }
+}
+
+ConfigFile::~ConfigFile()
+{
+  if (m_input.is_open())
+    {
+      m_input.close();
+    }
+}
+
+boost::filesystem::path
+ConfigFile::findConfigFile()
+{
+  using namespace boost::filesystem;
+
+  path home(std::getenv("HOME"));
+  if (!home.empty())
+    {
+      home /= ".ndn/client.conf";
+      if (exists(home))
+        {
+          return absolute(home);
+        }
+    }
+
+#ifdef NDN_CPP_SYSCONFDIR
+  path sysconfdir(NDN_CPP_SYSCONFDIR);
+  sysconfdir /= "ndn/client.conf";
+
+  if (exists(sysconfdir))
+    {
+      return absolute(sysconfdir);
+    }
+#endif // NDN_CPP_SYSCONFDIR
+
+  path etc("/etc/ndn/client.conf");
+  if (exists(etc))
+    {
+      return absolute(etc);
+    }
+
+  return path();
+}
+
+
+
+bool
+ConfigFile::open()
+{
+  if (m_path.empty())
+    {
+      return false;
+    }
+
+  m_input.open(m_path.c_str());
+  if (!m_input.good() || !m_input.is_open())
+    {
+      return false;
+    }
+  return true;
+}
+
+void
+ConfigFile::close()
+{
+  if (m_input.is_open())
+    {
+      m_input.close();
+    }
+}
+
+
+const ConfigFile::Parsed&
+ConfigFile::parse()
+{
+  if (m_path.empty())
+    {
+      throw Error("Failed to locate configuration file for parsing");
+    }
+  else if (!m_input.is_open() && !open())
+    {
+      throw Error("Failed to open configuration file for parsing");
+    }
+
+  try
+    {
+      boost::property_tree::read_ini(m_input, m_config);
+    }
+  catch (const boost::property_tree::ini_parser_error& error)
+    {
+      std::stringstream msg;
+      msg << "Failed to parse configuration file";
+      msg << " " << m_path;
+      msg << " " << error.message() << " line " << error.line();
+      throw Error(msg.str());
+    }
+  return m_config;
+}
+
+}
+
diff --git a/src/util/config-file.hpp b/src/util/config-file.hpp
new file mode 100644
index 0000000..cecceba
--- /dev/null
+++ b/src/util/config-file.hpp
@@ -0,0 +1,103 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NDN_MANAGEMENT_CONFIG_FILE_HPP
+#define NDN_MANAGEMENT_CONFIG_FILE_HPP
+
+#include "../common.hpp"
+
+#include <boost/property_tree/ptree.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+
+class ConfigFile : noncopyable
+{
+public:
+
+  class Error : public std::runtime_error
+  {
+  public:
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+
+    }
+  };
+
+  typedef boost::property_tree::ptree Parsed;
+
+  /**
+   * Locate, open, and parse a library configuration file.
+   *
+   * @throws ConfigFile::Error on parse error
+   */
+  ConfigFile();
+
+  ~ConfigFile();
+
+  const boost::filesystem::path&
+  getPath() const;
+
+  const Parsed&
+  getParsedConfiguration() const;
+
+private:
+
+  bool
+  open();
+
+  void
+  close();
+
+  /**
+   * Parse a previously discovered and opened configuration file.
+   * For convenience this method will attempt to open the file
+   * if it has previously been located, but open() has not been called.
+   *
+   * @throws ConfigFile::Error on parse error
+   * @throws ConfigFile::Error on failure to open previously un-open configuration file
+   * @throws ConfigFile::Error if no configuration file was previously located
+   */
+  const Parsed&
+  parse();
+
+  /**
+   * Looking for the configuration file in these well-known locations:
+   *
+   * 1. $HOME/.ndn/client.conf
+   * 2. @SYSCONFDIR@/ndn/client.conf
+   * 3. /etc/ndn/client.conf
+   *
+   * @return path to preferred configuration (according to above order) or empty path on failure
+   */
+
+  boost::filesystem::path
+  findConfigFile();
+
+private:
+  boost::filesystem::path m_path; // absolute path to active configuration file (if any)
+  std::ifstream m_input;
+  Parsed m_config;
+};
+
+inline const boost::filesystem::path&
+ConfigFile::getPath() const
+{
+  return m_path;
+}
+
+inline const ConfigFile::Parsed&
+ConfigFile::getParsedConfiguration() const
+{
+  return m_config;
+}
+
+} // namespace ndn
+
+
+#endif // NDN_MANAGEMENT_CONFIG_FILE_HPP
+