util: FaceUri canonize dev scheme

refs #4027

Change-Id: Iee36b72c237824e8e2aed4a93786accc1464f62f
diff --git a/src/util/face-uri.cpp b/src/util/face-uri.cpp
index e58dd1d..83ad2eb 100644
--- a/src/util/face-uri.cpp
+++ b/src/util/face-uri.cpp
@@ -1,12 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, 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.
+ * Copyright (c) 2013-2017 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 ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -233,7 +233,7 @@
   canonize(const FaceUri& faceUri,
            const FaceUri::CanonizeSuccessCallback& onSuccess,
            const FaceUri::CanonizeFailureCallback& onFailure,
-           boost::asio::io_service& io, const time::nanoseconds& timeout) const = 0;
+           boost::asio::io_service& io, time::nanoseconds timeout) const = 0;
 };
 
 template<typename Protocol>
@@ -275,7 +275,7 @@
   canonize(const FaceUri& faceUri,
            const FaceUri::CanonizeSuccessCallback& onSuccess,
            const FaceUri::CanonizeFailureCallback& onFailure,
-           boost::asio::io_service& io, const time::nanoseconds& timeout) const override
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
   {
     if (this->isCanonical(faceUri)) {
       onSuccess(faceUri);
@@ -433,7 +433,7 @@
   canonize(const FaceUri& faceUri,
            const FaceUri::CanonizeSuccessCallback& onSuccess,
            const FaceUri::CanonizeFailureCallback& onFailure,
-           boost::asio::io_service& io, const time::nanoseconds& timeout) const override
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
   {
     auto addr = ethernet::Address::fromString(faceUri.getHost());
     if (addr.isNull()) {
@@ -446,6 +446,46 @@
   }
 };
 
+class DevCanonizeProvider : public CanonizeProvider
+{
+public:
+  std::set<std::string>
+  getSchemes() const override
+  {
+    return {"dev"};
+  }
+
+  bool
+  isCanonical(const FaceUri& faceUri) const override
+  {
+    return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
+  }
+
+  void
+  canonize(const FaceUri& faceUri,
+           const FaceUri::CanonizeSuccessCallback& onSuccess,
+           const FaceUri::CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
+  {
+    if (faceUri.getHost().empty()) {
+      onFailure("network interface name is missing");
+      return;
+    }
+    if (!faceUri.getPort().empty()) {
+      onFailure("port number is not allowed");
+      return;
+    }
+    if (!faceUri.getPath().empty() && faceUri.getPath() != "/") { // permit trailing slash only
+      onFailure("path is not allowed");
+      return;
+    }
+
+    FaceUri canonicalUri = FaceUri::fromDev(faceUri.getHost());
+    BOOST_ASSERT(canonicalUri.isCanonical());
+    onSuccess(canonicalUri);
+  }
+};
+
 class UdpDevCanonizeProvider : public CanonizeProvider
 {
 public:
@@ -471,7 +511,7 @@
   canonize(const FaceUri& faceUri,
            const FaceUri::CanonizeSuccessCallback& onSuccess,
            const FaceUri::CanonizeFailureCallback& onFailure,
-           boost::asio::io_service& io, const time::nanoseconds& timeout) const override
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
   {
     if (this->isCanonical(faceUri)) {
       onSuccess(faceUri);
@@ -485,6 +525,7 @@
 using CanonizeProviders = boost::mpl::vector<UdpCanonizeProvider*,
                                              TcpCanonizeProvider*,
                                              EtherCanonizeProvider*,
+                                             DevCanonizeProvider*,
                                              UdpDevCanonizeProvider*>;
 using CanonizeProviderTable = std::map<std::string, shared_ptr<CanonizeProvider>>;
 
@@ -549,7 +590,7 @@
 void
 FaceUri::canonize(const CanonizeSuccessCallback& onSuccess,
                   const CanonizeFailureCallback& onFailure,
-                  boost::asio::io_service& io, const time::nanoseconds& timeout) const
+                  boost::asio::io_service& io, time::nanoseconds timeout) const
 {
   const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
   if (cp == nullptr) {
diff --git a/src/util/face-uri.hpp b/src/util/face-uri.hpp
index f726388..09c8aaf 100644
--- a/src/util/face-uri.hpp
+++ b/src/util/face-uri.hpp
@@ -1,12 +1,12 @@
 /* -*- 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.
+ * Copyright (c) 2013-2017 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 ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -43,7 +43,7 @@
 } // namespace ethernet
 
 /** \brief represents the underlying protocol and address used by a Face
- *  \sa http://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#FaceUri
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#FaceUri
  */
 class FaceUri
 {
@@ -181,7 +181,7 @@
   canonize(const CanonizeSuccessCallback& onSuccess,
            const CanonizeFailureCallback& onFailure,
            boost::asio::io_service& io,
-           const time::nanoseconds& timeout) const;
+           time::nanoseconds timeout) const;
 
 private:
   std::string m_scheme;
diff --git a/tests/unit-tests/util/face-uri.t.cpp b/tests/unit-tests/util/face-uri.t.cpp
index 74c6784..0b9a61a 100644
--- a/tests/unit-tests/util/face-uri.t.cpp
+++ b/tests/unit-tests/util/face-uri.t.cpp
@@ -1,12 +1,12 @@
 /* -*- 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.
+ * Copyright (c) 2013-2017 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 ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -401,6 +401,7 @@
   runTests();
 }
 
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(ParseDev, 1)
 BOOST_AUTO_TEST_CASE(ParseDev)
 {
   FaceUri uri;
@@ -411,9 +412,33 @@
   BOOST_CHECK_EQUAL(uri.getPort(), "");
   BOOST_CHECK_EQUAL(uri.getPath(), "");
 
+  BOOST_CHECK_EQUAL(uri.parse("dev://eth0:8888"), false); // Bug #3896
+
   std::string ifname = "en1";
-  BOOST_REQUIRE_NO_THROW(FaceUri::fromDev(ifname));
-  BOOST_CHECK_EQUAL(FaceUri::fromDev(ifname).toString(), "dev://en1");
+  BOOST_REQUIRE_NO_THROW(uri = FaceUri::fromDev(ifname));
+  BOOST_CHECK_EQUAL(uri.toString(), "dev://en1");
+}
+
+BOOST_AUTO_TEST_CASE(IsCanonicalDev)
+{
+  BOOST_CHECK_EQUAL(FaceUri::canCanonize("dev"), true);
+
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0").isCanonical(), true);
+  BOOST_CHECK_EQUAL(FaceUri("dev://").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0:8888").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0/").isCanonical(), false);
+  BOOST_CHECK_EQUAL(FaceUri("dev://eth0/A").isCanonical(), false);
+}
+
+BOOST_FIXTURE_TEST_CASE(CanonizeDev, CanonizeFixture)
+{
+  addTest("dev://eth0", true, "dev://eth0");
+  addTest("dev://", false, "");
+  addTest("dev://eth0:8888", false, "");
+  addTest("dev://eth0/", true, "dev://eth0");
+  addTest("dev://eth0/A", false, "");
+
+  runTests();
 }
 
 BOOST_AUTO_TEST_CASE(ParseUdpDev)
@@ -496,19 +521,16 @@
   BOOST_CHECK_EQUAL(FaceUri::canCanonize("null"), false);
   BOOST_CHECK_EQUAL(FaceUri::canCanonize("unix"), false);
   BOOST_CHECK_EQUAL(FaceUri::canCanonize("fd"), false);
-  BOOST_CHECK_EQUAL(FaceUri::canCanonize("dev"), false);
 
   BOOST_CHECK_EQUAL(FaceUri("internal://").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("null://").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("unix:///var/run/nfd.sock").isCanonical(), false);
   BOOST_CHECK_EQUAL(FaceUri("fd://0").isCanonical(), false);
-  BOOST_CHECK_EQUAL(FaceUri("dev://eth1").isCanonical(), false);
 
   addTest("internal://", false, "");
   addTest("null://", false, "");
   addTest("unix:///var/run/nfd.sock", false, "");
   addTest("fd://0", false, "");
-  addTest("dev://eth1", false, "");
 
   runTests();
 }