core: extend FaceUri with fd, ether, dev schemes

refs #1396

Change-Id: I6680eb2013c335a14c9fa76b965c2d1e63202334
diff --git a/daemon/core/face-uri.cpp b/daemon/core/face-uri.cpp
index cc6c8c8..14751d6 100644
--- a/daemon/core/face-uri.cpp
+++ b/daemon/core/face-uri.cpp
@@ -6,6 +6,10 @@
 
 #include "face-uri.hpp"
 #include "core/logger.hpp"
+#ifdef HAVE_PCAP
+#include "face/ethernet.hpp"
+#endif // HAVE_PCAP
+
 #include <boost/regex.hpp>
 
 NFD_LOG_INIT("FaceUri");
@@ -35,30 +39,35 @@
   m_port.clear();
   m_path.clear();
 
-  boost::regex protocolExp("(\\w+\\d?)://([^/]*)(\\/[^?]*)?");
+  static const boost::regex protocolExp("(\\w+\\d?)://([^/]*)(\\/[^?]*)?");
   boost::smatch protocolMatch;
   if (!boost::regex_match(uri, protocolMatch, protocolExp)) {
     return false;
   }
   m_scheme = protocolMatch[1];
+  const std::string& authority = protocolMatch[2];
   m_path = protocolMatch[3];
 
-  const std::string& authority = protocolMatch[2];
+  // pattern for IPv6 address enclosed in [ ], with optional port number
+  static const boost::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
+  // pattern for Ethernet address in standard hex-digits-and-colons notation
+  static const boost::regex etherExp("^((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))$");
+  // pattern for IPv4/hostname/fd/ifname, with optional port number
+  static const boost::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
 
-  boost::regex v6Exp("^\\[(([a-fA-F0-9:]+))\\](:(\\d+))?$"); // [host]:port
-  boost::regex v4Exp("^((\\d+\\.){3}\\d+)(:(\\d+))?$");
-  boost::regex hostExp("^(([^:]*))(:(\\d+))?$"); // host:port
-
-  boost::smatch match;
-  m_isV6 = boost::regex_match(authority, match, v6Exp);
-  if (m_isV6 ||
-      boost::regex_match(authority, match, v4Exp) ||
-      boost::regex_match(authority, match, hostExp)) {
-    m_host = match[1];
-    m_port = match[4];
+  if (authority.empty()) {
+    // UNIX, internal
   }
   else {
-    if (m_path.empty()) {
+    boost::smatch match;
+    m_isV6 = boost::regex_match(authority, match, v6Exp);
+    if (m_isV6 ||
+        boost::regex_match(authority, match, etherExp) ||
+        boost::regex_match(authority, match, v4HostExp)) {
+      m_host = match[1];
+      m_port = match[2];
+    }
+    else {
       return false;
     }
   }
@@ -84,11 +93,40 @@
   m_port = boost::lexical_cast<std::string>(endpoint.port());
 }
 
+#ifdef HAVE_UNIX_SOCKETS
 FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint)
   : m_isV6(false)
 {
   m_scheme = "unix";
   m_path = endpoint.path();
 }
+#endif // HAVE_UNIX_SOCKETS
+
+FaceUri
+FaceUri::fromFd(int fd)
+{
+  FaceUri uri;
+  uri.m_scheme = "fd";
+  uri.m_host = boost::lexical_cast<std::string>(fd);
+  return uri;
+}
+
+#ifdef HAVE_PCAP
+FaceUri::FaceUri(const ethernet::Address& address)
+  : m_isV6(false)
+{
+  m_scheme = "ether";
+  m_host = address.toString(':');
+}
+#endif // HAVE_PCAP
+
+FaceUri
+FaceUri::fromDev(const std::string& ifname)
+{
+  FaceUri uri;
+  uri.m_scheme = "dev";
+  uri.m_host = ifname;
+  return uri;
+}
 
 } // namespace nfd
diff --git a/daemon/core/face-uri.hpp b/daemon/core/face-uri.hpp
index 29647bb..fa0a62a 100644
--- a/daemon/core/face-uri.hpp
+++ b/daemon/core/face-uri.hpp
@@ -11,22 +11,34 @@
 
 namespace nfd {
 
-/// represents a URI in Face Management protocol
+#ifdef HAVE_PCAP
+namespace ethernet {
+class Address;
+} // namespace ethernet
+#endif // HAVE_PCAP
+
+/** \brief represents the underlying protocol and address used by a Face
+ *  \sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#FaceUri
+ */
 class FaceUri
 {
 public:
-  class Error : public std::runtime_error
+  class Error : public std::invalid_argument
   {
   public:
-    Error(const std::string& what) : std::runtime_error(what) {}
+    explicit
+    Error(const std::string& what)
+      : std::invalid_argument(what)
+    {
+    }
   };
 
   FaceUri();
 
   /** \brief construct by parsing
    *
-   * \param uri scheme://domain[:port]/path
-   * \throw FaceUri::Error if URI cannot be parsed
+   *  \param uri scheme://host[:port]/path
+   *  \throw FaceUri::Error if URI cannot be parsed
    */
   explicit
   FaceUri(const std::string& uri);
@@ -36,19 +48,40 @@
   explicit
   FaceUri(const char* uri);
 
-  explicit
-  FaceUri(const boost::asio::ip::tcp::endpoint& endpoint);
-
-  explicit
-  FaceUri(const boost::asio::ip::udp::endpoint& endpoint);
-
-  explicit
-  FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint);
-
   /// exception-safe parsing
   bool
   parse(const std::string& uri);
 
+public: // scheme-specific construction
+  /// construct tcp4 or tcp6 canonical FaceUri
+  explicit
+  FaceUri(const boost::asio::ip::tcp::endpoint& endpoint);
+
+  /// construct udp4 or udp6 canonical FaceUri
+  explicit
+  FaceUri(const boost::asio::ip::udp::endpoint& endpoint);
+
+#ifdef HAVE_UNIX_SOCKETS
+  /// construct unix canonical FaceUri
+  explicit
+  FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint);
+#endif // HAVE_UNIX_SOCKETS
+
+  /// create fd FaceUri from file descriptor
+  static FaceUri
+  fromFd(int fd);
+
+#ifdef HAVE_PCAP
+  /// construct ether canonical FaceUri
+  explicit
+  FaceUri(const ethernet::Address& address);
+#endif // HAVE_PCAP
+
+  /// create dev FaceUri from network device name
+  static FaceUri
+  fromDev(const std::string& ifname);
+
+public: // getters
   /// get scheme (protocol)
   const std::string&
   getScheme() const;
@@ -76,12 +109,13 @@
   bool m_isV6;
   std::string m_port;
   std::string m_path;
-  
+
   friend std::ostream& operator<<(std::ostream& os, const FaceUri& uri);
 };
 
 inline
 FaceUri::FaceUri()
+  : m_isV6(false)
 {
 }
 
diff --git a/tests/core/face-uri.cpp b/tests/core/face-uri.cpp
index 7eb9b62..80d5a89 100644
--- a/tests/core/face-uri.cpp
+++ b/tests/core/face-uri.cpp
@@ -5,6 +5,9 @@
  */
 
 #include "core/face-uri.hpp"
+#ifdef HAVE_PCAP
+#include "face/ethernet.hpp"
+#endif // HAVE_PCAP
 
 #include "tests/test-common.hpp"
 
@@ -13,7 +16,21 @@
 
 BOOST_FIXTURE_TEST_SUITE(CoreFaceUri, BaseFixture)
 
-BOOST_AUTO_TEST_CASE(Basic)
+BOOST_AUTO_TEST_CASE(Internal)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("internal://"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "internal");
+  BOOST_CHECK_EQUAL(uri.getHost(), "");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("internal:"), false);
+  BOOST_CHECK_EQUAL(uri.parse("internal:/"), false);
+}
+
+BOOST_AUTO_TEST_CASE(Udp)
 {
   BOOST_CHECK_NO_THROW(FaceUri("udp://hostname:6363"));
   BOOST_CHECK_THROW(FaceUri("udp//hostname:6363"), FaceUri::Error);
@@ -21,7 +38,7 @@
 
   FaceUri uri;
   BOOST_CHECK_EQUAL(uri.parse("udp//hostname:6363"), false);
-  
+
   BOOST_CHECK(uri.parse("udp://hostname:80"));
   BOOST_CHECK_EQUAL(uri.getScheme(), "udp");
   BOOST_CHECK_EQUAL(uri.getHost(), "hostname");
@@ -39,29 +56,116 @@
   BOOST_CHECK_EQUAL(uri.getHost(), "2001:db8:3f9:0::1");
   BOOST_CHECK_EQUAL(uri.getPort(), "6363");
   BOOST_CHECK_EQUAL(uri.getPath(), "");
-  
+
   BOOST_CHECK(uri.parse("udp6://[2001:db8:3f9:0:3025:ccc5:eeeb:86d3]:6363"));
   BOOST_CHECK_EQUAL(uri.getScheme(), "udp6");
   BOOST_CHECK_EQUAL(uri.getHost(), "2001:db8:3f9:0:3025:ccc5:eeeb:86d3");
   BOOST_CHECK_EQUAL(uri.getPort(), "6363");
   BOOST_CHECK_EQUAL(uri.getPath(), "");
 
+  BOOST_CHECK_EQUAL(uri.parse("udp6://[2001:db8:3f9:0:3025:ccc5:eeeb:86dg]:6363"), false);
+
+  using namespace boost::asio;
+  ip::udp::endpoint endpoint4(ip::address_v4::from_string("192.0.2.1"), 7777);
+  BOOST_REQUIRE_NO_THROW(FaceUri(endpoint4));
+  BOOST_CHECK_EQUAL(FaceUri(endpoint4).toString(), "udp4://192.0.2.1:7777");
+
+  ip::udp::endpoint endpoint6(ip::address_v6::from_string("2001:DB8::1"), 7777);
+  BOOST_REQUIRE_NO_THROW(FaceUri(endpoint6));
+  BOOST_CHECK_EQUAL(FaceUri(endpoint6).toString(), "udp6://[2001:db8::1]:7777");
+}
+
+BOOST_AUTO_TEST_CASE(Tcp)
+{
+  FaceUri uri;
+
   BOOST_CHECK(uri.parse("tcp://random.host.name"));
   BOOST_CHECK_EQUAL(uri.getScheme(), "tcp");
   BOOST_CHECK_EQUAL(uri.getHost(), "random.host.name");
   BOOST_CHECK_EQUAL(uri.getPort(), "");
   BOOST_CHECK_EQUAL(uri.getPath(), "");
 
+  BOOST_CHECK_EQUAL(uri.parse("tcp://192.0.2.1:"), false);
+  BOOST_CHECK_EQUAL(uri.parse("tcp://[::zzzz]"), false);
+
+  using namespace boost::asio;
+  ip::tcp::endpoint endpoint4(ip::address_v4::from_string("192.0.2.1"), 7777);
+  BOOST_REQUIRE_NO_THROW(FaceUri(endpoint4));
+  BOOST_CHECK_EQUAL(FaceUri(endpoint4).toString(), "tcp4://192.0.2.1:7777");
+
+  ip::tcp::endpoint endpoint6(ip::address_v6::from_string("2001:DB8::1"), 7777);
+  BOOST_REQUIRE_NO_THROW(FaceUri(endpoint6));
+  BOOST_CHECK_EQUAL(FaceUri(endpoint6).toString(), "tcp6://[2001:db8::1]:7777");
+}
+
+BOOST_AUTO_TEST_CASE(Unix)
+{
+  FaceUri uri;
+
   BOOST_CHECK(uri.parse("unix:///var/run/example.sock"));
   BOOST_CHECK_EQUAL(uri.getScheme(), "unix");
   BOOST_CHECK_EQUAL(uri.getHost(), "");
   BOOST_CHECK_EQUAL(uri.getPort(), "");
   BOOST_CHECK_EQUAL(uri.getPath(), "/var/run/example.sock");
 
-  BOOST_CHECK_EQUAL(uri.parse("tcp://192.0.2.1:"), false);
-  BOOST_CHECK_EQUAL(uri.parse("tcp://[::zzzz]"), false);
-  BOOST_CHECK_EQUAL(uri.parse("udp6://[2001:db8:3f9:0:3025:ccc5:eeeb:86dg]:6363"), false);  
+  //BOOST_CHECK_EQUAL(uri.parse("unix://var/run/example.sock"), false);
+  // This is not a valid unix:/ URI, but the parse would treat "var" as host
+
+#ifdef HAVE_UNIX_SOCKETS
+  using namespace boost::asio;
+  local::stream_protocol::endpoint endpoint("/var/run/example.sock");
+  BOOST_REQUIRE_NO_THROW(FaceUri(endpoint));
+  BOOST_CHECK_EQUAL(FaceUri(endpoint).toString(), "unix:///var/run/example.sock");
+#endif // HAVE_UNIX_SOCKETS
+}
+
+BOOST_AUTO_TEST_CASE(Fd)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("fd://6"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "fd");
+  BOOST_CHECK_EQUAL(uri.getHost(), "6");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
   BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  int fd = 21;
+  BOOST_REQUIRE_NO_THROW(FaceUri::fromFd(fd));
+  BOOST_CHECK_EQUAL(FaceUri::fromFd(fd).toString(), "fd://21");
+}
+
+BOOST_AUTO_TEST_CASE(Ether)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("ether://08:00:27:01:dd:01"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "ether");
+  BOOST_CHECK_EQUAL(uri.getHost(), "08:00:27:01:dd:01");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  BOOST_CHECK_EQUAL(uri.parse("ether://08:00:27:zz:dd:01"), false);
+
+#ifdef HAVE_PCAP
+  ethernet::Address address = ethernet::Address::fromString("33:33:01:01:01:01");
+  BOOST_REQUIRE_NO_THROW(FaceUri(address));
+  BOOST_CHECK_EQUAL(FaceUri(address).toString(), "ether://33:33:01:01:01:01");
+#endif // HAVE_PCAP
+}
+
+BOOST_AUTO_TEST_CASE(Dev)
+{
+  FaceUri uri;
+
+  BOOST_CHECK(uri.parse("dev://eth0"));
+  BOOST_CHECK_EQUAL(uri.getScheme(), "dev");
+  BOOST_CHECK_EQUAL(uri.getHost(), "eth0");
+  BOOST_CHECK_EQUAL(uri.getPort(), "");
+  BOOST_CHECK_EQUAL(uri.getPath(), "");
+
+  std::string ifname = "en1";
+  BOOST_REQUIRE_NO_THROW(FaceUri::fromDev(ifname));
+  BOOST_CHECK_EQUAL(FaceUri::fromDev(ifname).toString(), "dev://en1");
 }
 
 BOOST_AUTO_TEST_SUITE_END()