tools: nfdc face create command

refs #3864

Change-Id: Icca589eceae0b78f68cda61e761dd4721ce54f9c
diff --git a/tools/nfdc/execute-command.cpp b/tools/nfdc/execute-command.cpp
index 639fd19..4dd4f24 100644
--- a/tools/nfdc/execute-command.cpp
+++ b/tools/nfdc/execute-command.cpp
@@ -29,6 +29,28 @@
 namespace tools {
 namespace nfdc {
 
+time::nanoseconds
+ExecuteContext::getTimeout() const
+{
+  return time::seconds(4);
+}
+
+ndn::nfd::CommandOptions
+ExecuteContext::makeCommandOptions() const
+{
+  return ndn::nfd::CommandOptions()
+           .setTimeout(time::duration_cast<time::milliseconds>(this->getTimeout()));
+}
+
+Controller::CommandFailCallback
+ExecuteContext::makeCommandFailureHandler(const std::string& commandName)
+{
+  return [=] (const ndn::nfd::ControlResponse& resp) {
+    this->exitCode = 1;
+    this->err << "Error " << resp.getCode() << " when " << commandName << ": " << resp.getText() << '\n';
+  };
+}
+
 Controller::DatasetFailCallback
 ExecuteContext::makeDatasetFailureHandler(const std::string& datasetName)
 {
diff --git a/tools/nfdc/execute-command.hpp b/tools/nfdc/execute-command.hpp
index 644c70b..09f28d5 100644
--- a/tools/nfdc/execute-command.hpp
+++ b/tools/nfdc/execute-command.hpp
@@ -28,7 +28,12 @@
 
 #include "command-arguments.hpp"
 #include <ndn-cxx/face.hpp>
+#include <ndn-cxx/mgmt/nfd/command-options.hpp>
 #include <ndn-cxx/mgmt/nfd/controller.hpp>
+#include <ndn-cxx/mgmt/nfd/control-command.hpp>
+#include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+#include <ndn-cxx/mgmt/nfd/control-response.hpp>
+#include <ndn-cxx/mgmt/nfd/status-dataset.hpp>
 #include <ndn-cxx/security/key-chain.hpp>
 
 namespace nfd {
@@ -37,6 +42,7 @@
 
 using ndn::Face;
 using ndn::KeyChain;
+using ndn::nfd::ControlParameters;
 using ndn::nfd::Controller;
 
 /** \brief context for command execution
@@ -44,8 +50,22 @@
 class ExecuteContext
 {
 public:
-  /** \brief handler for dataset retrieval failure
-   *  \param datasetName dataset name used in error message
+  /** \return timeout for each step
+   */
+  time::nanoseconds
+  getTimeout() const;
+
+  ndn::nfd::CommandOptions
+  makeCommandOptions() const;
+
+  /** \return handler for command execution failure
+   *  \param commandName command name used in error message (present continuous tense)
+   */
+  Controller::CommandFailCallback
+  makeCommandFailureHandler(const std::string& commandName);
+
+  /** \return handler for dataset retrieval failure
+   *  \param datasetName dataset name used in error message (noun phrase)
    */
   Controller::DatasetFailCallback
   makeDatasetFailureHandler(const std::string& datasetName);
diff --git a/tools/nfdc/face-module.cpp b/tools/nfdc/face-module.cpp
index 721a56f..3c087f4 100644
--- a/tools/nfdc/face-module.cpp
+++ b/tools/nfdc/face-module.cpp
@@ -38,6 +38,13 @@
     .setTitle("show face information")
     .addArg("id", ArgValueType::UNSIGNED, Required::YES, Positional::YES);
   parser.addCommand(defFaceShow, &FaceModule::show);
+
+  CommandDefinition defFaceCreate("face", "create");
+  defFaceCreate
+    .setTitle("create a face")
+    .addArg("remote", ArgValueType::FACE_URI, Required::YES, Positional::YES)
+    .addArg("persistency", ArgValueType::FACE_PERSISTENCY, Required::NO, Positional::YES);
+  parser.addCommand(defFaceCreate, &FaceModule::create);
 }
 
 void
@@ -57,7 +64,38 @@
       }
       formatItemText(ctx.out, result.front(), true);
     },
-    ctx.makeDatasetFailureHandler("face information"));
+    ctx.makeDatasetFailureHandler("face information"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
+FaceModule::create(ExecuteContext& ctx)
+{
+  auto faceUri = ctx.args.get<FaceUri>("remote");
+  auto persistency = ctx.args.get<FacePersistency>("persistency", FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+
+  faceUri.canonize(
+    [&] (const FaceUri& canonicalUri) {
+      ctx.controller.start<ndn::nfd::FaceCreateCommand>(
+        ControlParameters().setUri(canonicalUri.toString()).setFacePersistency(persistency),
+        [&] (const ControlParameters& resp) {
+          ctx.out << "face-created ";
+          text::ItemAttributes ia;
+          ctx.out << ia("id") << resp.getFaceId()
+                  << ia("remote") << resp.getUri()
+                  << ia("persistency") << resp.getFacePersistency() << '\n';
+          ///\todo #3864 display localUri
+        },
+        ctx.makeCommandFailureHandler("creating face"), ///\todo #3232 update persistency upon 409
+        ctx.makeCommandOptions());
+    },
+    [&] (const std::string& canonizeError) {
+      ctx.exitCode = 4;
+      ctx.err << "Error when canonizing FaceUri: " << canonizeError << '\n';
+    },
+    ctx.face.getIoService(), ctx.getTimeout());
 
   ctx.face.processEvents();
 }
diff --git a/tools/nfdc/face-module.hpp b/tools/nfdc/face-module.hpp
index 92c29b4..1bbc31b 100644
--- a/tools/nfdc/face-module.hpp
+++ b/tools/nfdc/face-module.hpp
@@ -51,6 +51,11 @@
   static void
   show(ExecuteContext& ctx);
 
+  /** \brief the 'face create' command
+   */
+  static void
+  create(ExecuteContext& ctx);
+
   void
   fetchStatus(Controller& controller,
               const function<void()>& onSuccess,