tools: create face during nfdc route add

refs #4332

Change-Id: I1a30bf87abd57bab08a2c027870e779392de0104
diff --git a/tools/nfdc/canonizer.cpp b/tools/nfdc/canonizer.cpp
new file mode 100644
index 0000000..3e526cd
--- /dev/null
+++ b/tools/nfdc/canonizer.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2020,  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 "canonizer.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+std::pair<optional<FaceUri>, std::string>
+canonize(ExecuteContext& ctx, const FaceUri& uri)
+{
+  optional<FaceUri> result;
+  std::string error;
+  uri.canonize(
+    [&result] (const FaceUri& canonicalUri) { result = canonicalUri; },
+    [&error] (const std::string& errorReason) { error = errorReason; },
+    ctx.face.getIoService(), ctx.getTimeout());
+  ctx.face.processEvents();
+
+  return {result, error};
+}
+
+std::pair<FindFace::Code, std::string>
+canonizeErrorHelper(const FaceUri& uri,
+                    const std::string& error,
+                    const std::string& field)
+{
+  std::string msg = "Error during canonization of ";
+  if (!field.empty()) {
+    msg += field + " ";
+  }
+  msg += "'" + uri.toString() + "': " + error;
+  return {FindFace::Code::CANONIZE_ERROR, msg};
+}
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/canonizer.hpp b/tools/nfdc/canonizer.hpp
new file mode 100644
index 0000000..b58372d
--- /dev/null
+++ b/tools/nfdc/canonizer.hpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2020,  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/>.
+ */
+
+#ifndef NFD_TOOLS_NFDC_CANONIZER_HPP
+#define NFD_TOOLS_NFDC_CANONIZER_HPP
+
+#include "core/common.hpp"
+#include "execute-command.hpp"
+#include "find-face.hpp"
+
+#include <ndn-cxx/net/face-uri.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+/** \brief canonize FaceUri
+ *  \return pair of canonical FaceUri (nullopt if failure) and error string
+ */
+std::pair<optional<FaceUri>, std::string>
+canonize(ExecuteContext& ctx, const FaceUri& uri);
+
+/** \brief helper to generate exit code and error message for face canonization failures
+ *  \param uri FaceUri
+ *  \param error error string returned by canonization process
+ *  \param field optional field identifier to include with message
+ *  \return pair of exit code and error message
+ */
+std::pair<FindFace::Code, std::string>
+canonizeErrorHelper(const FaceUri& uri,
+                    const std::string& error,
+                    const std::string& field = "");
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_CANONIZER_HPP
diff --git a/tools/nfdc/face-module.cpp b/tools/nfdc/face-module.cpp
index 37f5bd9..e46279d2 100644
--- a/tools/nfdc/face-module.cpp
+++ b/tools/nfdc/face-module.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "face-module.hpp"
+#include "canonizer.hpp"
 #include "find-face.hpp"
 
 namespace nfd {
@@ -179,34 +180,19 @@
     mtu = static_cast<uint64_t>(v);
   }
 
-  FaceUri canonicalRemote;
+  optional<FaceUri> canonicalRemote;
   optional<FaceUri> canonicalLocal;
 
-  auto handleCanonizeError = [&] (const FaceUri& faceUri, const std::string& error) {
-    ctx.exitCode = static_cast<int>(FindFace::Code::CANONIZE_ERROR);
-    ctx.err << "Error when canonizing '" << faceUri << "': " << error << '\n';
-  };
-
-  auto printPositiveResult = [&] (const std::string& actionSummary, const ControlParameters& resp) {
-    text::ItemAttributes ia;
-    ctx.out << actionSummary << ' '
-            << ia("id") << resp.getFaceId()
-            << ia("local") << resp.getLocalUri()
-            << ia("remote") << resp.getUri()
-            << ia("persistency") << resp.getFacePersistency();
-    printFaceParams(ctx.out, ia, resp);
-  };
-
-  auto updateFace = [&printPositiveResult] (ControlParameters respParams, ControlParameters resp) {
+  auto updateFace = [&] (ControlParameters respParams, ControlParameters resp) {
     // faces/update response does not have FaceUris, copy from faces/create response
     resp.setLocalUri(respParams.getLocalUri())
         .setUri(respParams.getUri());
-    printPositiveResult("face-updated", resp);
+    printSuccess(ctx.out, "face-updated", resp);
   };
 
   auto handle409 = [&] (const ControlResponse& resp) {
     ControlParameters respParams(resp.getBody());
-    if (respParams.getUri() != canonicalRemote.toString()) {
+    if (respParams.getUri() != canonicalRemote->toString()) {
       // we are conflicting with a different face, which is a general error
       return false;
     }
@@ -256,7 +242,7 @@
     }
     else {
       // don't do anything
-      printPositiveResult("face-exists", respParams);
+      printSuccess(ctx.out, "face-exists", respParams);
     }
 
     return true;
@@ -264,7 +250,7 @@
 
   auto doCreateFace = [&] {
     ControlParameters params;
-    params.setUri(canonicalRemote.toString());
+    params.setUri(canonicalRemote->toString());
     if (canonicalLocal) {
       params.setLocalUri(canonicalLocal->toString());
     }
@@ -287,7 +273,9 @@
 
     ctx.controller.start<ndn::nfd::FaceCreateCommand>(
       params,
-      bind(printPositiveResult, "face-created", _1),
+      [&] (const ControlParameters& resp) {
+        printSuccess(ctx.out, "face-created", resp);
+      },
       [&] (const ControlResponse& resp) {
         if (resp.getCode() == 409 && handle409(resp)) {
           return;
@@ -297,24 +285,33 @@
       ctx.makeCommandOptions());
   };
 
-  remoteUri.canonize(
-    [&] (const FaceUri& canonicalUri) {
-      canonicalRemote = canonicalUri;
-      if (localUri) {
-        localUri->canonize(
-          [&] (const FaceUri& canonicalUri) {
-            canonicalLocal = canonicalUri;
-            doCreateFace();
-          },
-          bind(handleCanonizeError, *localUri, _1),
-          ctx.face.getIoService(), ctx.getTimeout());
-      }
-      else {
+  std::string error;
+  std::tie(canonicalRemote, error) = canonize(ctx, remoteUri);
+  if (canonicalRemote) {
+    // RemoteUri canonization successful
+    if (localUri) {
+      std::tie(canonicalLocal, error) = canonize(ctx, *localUri);
+      if (canonicalLocal) {
+        // LocalUri canonization successful
         doCreateFace();
       }
-    },
-    bind(handleCanonizeError, remoteUri, _1),
-    ctx.face.getIoService(), ctx.getTimeout());
+      else {
+        // LocalUri canonization failure
+        auto canonizationError = canonizeErrorHelper(*localUri, error, "local FaceUri");
+        ctx.exitCode = static_cast<int>(canonizationError.first);
+        ctx.err << canonizationError.second << '\n';
+      }
+    }
+    else {
+      doCreateFace();
+    }
+  }
+  else {
+    // RemoteUri canonization failure
+    auto canonizationError = canonizeErrorHelper(remoteUri, error, "remote FaceUri");
+    ctx.exitCode = static_cast<int>(canonizationError.first);
+    ctx.err << canonizationError.second << '\n';
+  }
 
   ctx.face.processEvents();
 }
@@ -349,6 +346,7 @@
   ctx.controller.start<ndn::nfd::FaceDestroyCommand>(
     ControlParameters().setFaceId(face.getFaceId()),
     [&] (const ControlParameters& resp) {
+      // We can't use printSuccess because some face attributes come from FaceStatus not ControlResponse
       ctx.out << "face-destroyed ";
       text::ItemAttributes ia;
       ctx.out << ia("id") << face.getFaceId()
@@ -528,6 +526,20 @@
 }
 
 void
+FaceModule::printSuccess(std::ostream& os,
+                         const std::string& actionSummary,
+                         const ControlParameters& resp)
+{
+  text::ItemAttributes ia;
+  os << actionSummary << ' '
+     << ia("id") << resp.getFaceId()
+     << ia("local") << resp.getLocalUri()
+     << ia("remote") << resp.getUri()
+     << ia("persistency") << resp.getFacePersistency();
+  printFaceParams(os, ia, resp);
+}
+
+void
 FaceModule::printFaceParams(std::ostream& os, text::ItemAttributes& ia, const ControlParameters& resp)
 {
   os << ia("reliability") << text::OnOff{resp.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED)}
diff --git a/tools/nfdc/face-module.hpp b/tools/nfdc/face-module.hpp
index 8083e74..cffd98b 100644
--- a/tools/nfdc/face-module.hpp
+++ b/tools/nfdc/face-module.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -94,6 +94,14 @@
   static void
   formatItemText(std::ostream& os, const FaceStatus& item, bool wantMultiLine);
 
+  /** \brief print face action success message to specified ostream
+   *  \param os output stream
+   *  \param actionSummary description of action taken
+   *  \param resp response control parameters to print
+   */
+  static void
+  printSuccess(std::ostream& os, const std::string& actionSummary, const ControlParameters& resp);
+
   /** \brief print face response parameters to specified ostream
    *  \param os output stream
    *  \param ia ItemAttributes used to format output
diff --git a/tools/nfdc/find-face.cpp b/tools/nfdc/find-face.cpp
index bb6183d..de0fd8e 100644
--- a/tools/nfdc/find-face.cpp
+++ b/tools/nfdc/find-face.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -24,6 +24,7 @@
  */
 
 #include "find-face.hpp"
+#include "canonizer.hpp"
 #include "format-helpers.hpp"
 
 #include <ndn-cxx/util/logger.hpp>
@@ -75,18 +76,16 @@
   m_filter = filter;
 
   if (m_filter.hasRemoteUri()) {
-    auto remoteUri = this->canonize("remote", FaceUri(m_filter.getRemoteUri()));
+    auto remoteUri = canonize("remote FaceUri", FaceUri(m_filter.getRemoteUri()));
     if (!remoteUri) {
-      m_res = Code::CANONIZE_ERROR;
       return m_res;
     }
     m_filter.setRemoteUri(remoteUri->toString());
   }
 
   if (m_filter.hasLocalUri()) {
-    auto localUri = this->canonize("local", FaceUri(m_filter.getLocalUri()));
+    auto localUri = canonize("local FaceUri", FaceUri(m_filter.getLocalUri()));
     if (!localUri) {
-      m_res = Code::CANONIZE_ERROR;
       return m_res;
     }
     m_filter.setLocalUri(localUri->toString());
@@ -107,23 +106,27 @@
 }
 
 optional<FaceUri>
-FindFace::canonize(const std::string& fieldName, const FaceUri& input)
+FindFace::canonize(const std::string& fieldName, const FaceUri& uri)
 {
-  if (!FaceUri::canCanonize(input.getScheme())) {
-    NDN_LOG_DEBUG("Using " << fieldName << '=' << input << " without canonization");
-    return input;
+  // We use a wrapper because we want to accept FaceUris that cannot be canonized
+  if (!FaceUri::canCanonize(uri.getScheme())) {
+    NDN_LOG_DEBUG("Using " << fieldName << "=" << uri << " without canonization");
+    return uri;
   }
 
   optional<FaceUri> result;
-  input.canonize(
-    [&result] (const FaceUri& canonicalUri) { result = canonicalUri; },
-    [this, fieldName] (const std::string& errorReason) {
-      m_errorReason = "Error during " + fieldName + " FaceUri canonization: " + errorReason;
-    },
-    m_ctx.face.getIoService(), m_ctx.getTimeout());
-  m_ctx.face.processEvents();
+  std::string error;
+  std::tie(result, error) = nfdc::canonize(m_ctx, uri);
 
-  return result;
+  if (result) {
+    // Canonization succeeded
+    return result;
+  }
+  else {
+    // Canonization failed
+    std::tie(m_res, m_errorReason) = canonizeErrorHelper(uri, error);
+    return nullopt;
+  }
 }
 
 void
diff --git a/tools/nfdc/find-face.hpp b/tools/nfdc/find-face.hpp
index ea1245b..3a6bba5 100644
--- a/tools/nfdc/find-face.hpp
+++ b/tools/nfdc/find-face.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -121,12 +121,8 @@
   printDisambiguation(std::ostream& os, DisambiguationStyle style) const;
 
 private:
-  /** \brief canonize FaceUri
-   *  \return canonical FaceUri if canonization succeeds, input if canonization is unsupported
-   *  \retval nullopt canonization fails; m_errorReason describes the failure
-   */
   optional<FaceUri>
-  canonize(const std::string& fieldName, const FaceUri& input);
+  canonize(const std::string& fieldName, const FaceUri& uri);
 
   /** \brief retrieve FaceStatus from filter
    *  \post m_res == Code::OK and m_results is populated if retrieval succeeds
diff --git a/tools/nfdc/rib-module.cpp b/tools/nfdc/rib-module.cpp
index b41d33a..544da6e 100644
--- a/tools/nfdc/rib-module.cpp
+++ b/tools/nfdc/rib-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -24,6 +24,8 @@
  */
 
 #include "rib-module.hpp"
+#include "canonizer.hpp"
+#include "face-module.hpp"
 #include "find-face.hpp"
 #include "format-helpers.hpp"
 
@@ -151,18 +153,96 @@
   bool wantCapture = ctx.args.get<bool>("capture", false);
   auto expiresMillis = ctx.args.getOptional<uint64_t>("expires");
 
+  auto registerRoute = [&] (uint64_t faceId) {
+    ControlParameters registerParams;
+    registerParams
+      .setName(prefix)
+      .setFaceId(faceId)
+      .setOrigin(origin)
+      .setCost(cost)
+      .setFlags((wantChildInherit ? ndn::nfd::ROUTE_FLAG_CHILD_INHERIT : ndn::nfd::ROUTE_FLAGS_NONE) |
+                (wantCapture ? ndn::nfd::ROUTE_FLAG_CAPTURE : ndn::nfd::ROUTE_FLAGS_NONE));
+    if (expiresMillis) {
+      registerParams.setExpirationPeriod(time::milliseconds(*expiresMillis));
+    }
+
+    ctx.controller.start<ndn::nfd::RibRegisterCommand>(
+      registerParams,
+      [&] (const ControlParameters& resp) {
+        ctx.exitCode = static_cast<int>(FindFace::Code::OK);
+        ctx.out << "route-add-accepted ";
+        text::ItemAttributes ia;
+        ctx.out << ia("prefix") << resp.getName()
+                << ia("nexthop") << resp.getFaceId()
+                << ia("origin") << resp.getOrigin()
+                << ia("cost") << resp.getCost()
+                << ia("flags") << static_cast<ndn::nfd::RouteFlags>(resp.getFlags());
+        if (resp.hasExpirationPeriod()) {
+          ctx.out << ia("expires") << text::formatDuration<time::milliseconds>(resp.getExpirationPeriod()) << "\n";
+        }
+        else {
+          ctx.out<< ia("expires") << "never\n";
+        }
+      },
+      ctx.makeCommandFailureHandler("adding route"),
+      ctx.makeCommandOptions());
+  };
+
+  auto handleFaceNotFound = [&] {
+    const FaceUri* faceUri = ndn::any_cast<FaceUri>(&nexthop);
+    if (faceUri == nullptr) {
+      ctx.err << "Face not found\n";
+      return;
+    }
+
+    if (faceUri->getScheme() == "ether") {
+      // Unicast Ethernet faces require a LocalUri, which hasn't been provided
+      // Multicast Ethernet faces cannot be created via management (already exist on each interface)
+      ctx.err << "Unable to implicitly create Ethernet faces\n";
+      ctx.err << "Please create the face with 'nfdc face create' before adding the route\n";
+      return;
+    }
+
+    optional<FaceUri> canonized;
+    std::string error;
+    std::tie(canonized, error) = canonize(ctx, *faceUri);
+    if (!canonized) {
+      // Canonization failed
+      auto canonizationError = canonizeErrorHelper(*faceUri, error);
+      ctx.exitCode = static_cast<int>(canonizationError.first);
+      ctx.err << canonizationError.second << '\n';
+      return;
+    }
+
+    ControlParameters faceCreateParams;
+    faceCreateParams.setUri(canonized->toString());
+
+    ctx.controller.start<ndn::nfd::FaceCreateCommand>(
+      faceCreateParams,
+      [&] (const ControlParameters& resp) {
+        FaceModule::printSuccess(ctx.out, "face-created", resp);
+        registerRoute(resp.getFaceId());
+      },
+      ctx.makeCommandFailureHandler("implicitly creating face"),
+      ctx.makeCommandOptions());
+  };
+
   FindFace findFace(ctx);
   FindFace::Code res = findFace.execute(nexthop);
 
   ctx.exitCode = static_cast<int>(res);
   switch (res) {
     case FindFace::Code::OK:
+      registerRoute(findFace.getFaceId());
       break;
     case FindFace::Code::ERROR:
     case FindFace::Code::CANONIZE_ERROR:
-    case FindFace::Code::NOT_FOUND:
       ctx.err << findFace.getErrorReason() << '\n';
       return;
+    case FindFace::Code::NOT_FOUND:
+      // Attempt to create face if it doesn't exist
+      handleFaceNotFound();
+      break;
     case FindFace::Code::AMBIGUOUS:
       ctx.err << "Multiple faces match specified remote FaceUri. Re-run the command with a FaceId:";
       findFace.printDisambiguation(ctx.err, FindFace::DisambiguationStyle::LOCAL_URI);
@@ -173,38 +253,6 @@
       return;
   }
 
-  ControlParameters registerParams;
-  registerParams
-    .setName(prefix)
-    .setFaceId(findFace.getFaceId())
-    .setOrigin(origin)
-    .setCost(cost)
-    .setFlags((wantChildInherit ? ndn::nfd::ROUTE_FLAG_CHILD_INHERIT : ndn::nfd::ROUTE_FLAGS_NONE) |
-              (wantCapture ? ndn::nfd::ROUTE_FLAG_CAPTURE : ndn::nfd::ROUTE_FLAGS_NONE));
-  if (expiresMillis) {
-    registerParams.setExpirationPeriod(time::milliseconds(*expiresMillis));
-  }
-
-  ctx.controller.start<ndn::nfd::RibRegisterCommand>(
-    registerParams,
-    [&] (const ControlParameters& resp) {
-      ctx.out << "route-add-accepted ";
-      text::ItemAttributes ia;
-      ctx.out << ia("prefix") << resp.getName()
-              << ia("nexthop") << resp.getFaceId()
-              << ia("origin") << resp.getOrigin()
-              << ia("cost") << resp.getCost()
-              << ia("flags") << static_cast<ndn::nfd::RouteFlags>(resp.getFlags());
-      if (resp.hasExpirationPeriod()) {
-        ctx.out << ia("expires") << text::formatDuration<time::milliseconds>(resp.getExpirationPeriod()) << "\n";
-      }
-      else {
-        ctx.out<< ia("expires") << "never\n";
-      }
-    },
-    ctx.makeCommandFailureHandler("adding route"),
-    ctx.makeCommandOptions());
-
   ctx.face.processEvents();
 }