tools: nfdc face destroy command

refs #3864

Change-Id: I1d070570c14364529c566273eba44b87413942b1
diff --git a/tools/nfdc/face-module.cpp b/tools/nfdc/face-module.cpp
index 3c087f4..4dc732e 100644
--- a/tools/nfdc/face-module.cpp
+++ b/tools/nfdc/face-module.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "face-module.hpp"
+#include "find-face.hpp"
 #include "format-helpers.hpp"
 
 namespace nfd {
@@ -45,6 +46,12 @@
     .addArg("remote", ArgValueType::FACE_URI, Required::YES, Positional::YES)
     .addArg("persistency", ArgValueType::FACE_PERSISTENCY, Required::NO, Positional::YES);
   parser.addCommand(defFaceCreate, &FaceModule::create);
+
+  CommandDefinition defFaceDestroy("face", "destroy");
+  defFaceDestroy
+    .setTitle("destroy a face")
+    .addArg("face", ArgValueType::FACE_ID_OR_URI, Required::YES, Positional::YES);
+  parser.addCommand(defFaceDestroy, &FaceModule::destroy);
 }
 
 void
@@ -101,6 +108,58 @@
 }
 
 void
+FaceModule::destroy(ExecuteContext& ctx)
+{
+  const boost::any faceIdOrUri = ctx.args.at("face");
+
+  FindFace findFace(ctx);
+  FindFace::Code res = FindFace::Code::ERROR;
+  const uint64_t* faceId = boost::any_cast<uint64_t>(&faceIdOrUri);
+  if (faceId != nullptr) {
+    res = findFace.execute(*faceId);
+  }
+  else {
+    res = findFace.execute(boost::any_cast<FaceUri>(faceIdOrUri));
+  }
+
+  ctx.exitCode = static_cast<int>(res);
+  switch (res) {
+    case FindFace::Code::OK:
+      break;
+    case FindFace::Code::ERROR:
+    case FindFace::Code::CANONIZE_ERROR:
+    case FindFace::Code::NOT_FOUND:
+      ctx.err << findFace.getErrorReason() << '\n';
+      return;
+    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);
+      ctx.err << '\n';
+      return;
+    default:
+      BOOST_ASSERT_MSG(false, "unexpected FindFace result");
+      return;
+  }
+
+  const FaceStatus& face = findFace.getFaceStatus();
+
+  ctx.controller.start<ndn::nfd::FaceDestroyCommand>(
+    ControlParameters().setFaceId(face.getFaceId()),
+    [&] (const ControlParameters& resp) {
+      ctx.out << "face-destroyed ";
+      text::ItemAttributes ia;
+      ctx.out << ia("id") << face.getFaceId()
+              << ia("local") << face.getLocalUri()
+              << ia("remote") << face.getRemoteUri()
+              << ia("persistency") << face.getFacePersistency() << '\n';
+    },
+    ctx.makeCommandFailureHandler("destroying face"),
+    ctx.makeCommandOptions());
+
+  ctx.face.processEvents();
+}
+
+void
 FaceModule::fetchStatus(Controller& controller,
                         const function<void()>& onSuccess,
                         const Controller::DatasetFailCallback& onFailure,
diff --git a/tools/nfdc/face-module.hpp b/tools/nfdc/face-module.hpp
index 1bbc31b..3f70d48 100644
--- a/tools/nfdc/face-module.hpp
+++ b/tools/nfdc/face-module.hpp
@@ -56,6 +56,11 @@
   static void
   create(ExecuteContext& ctx);
 
+  /** \brief the 'face destroy' command
+   */
+  static void
+  destroy(ExecuteContext& ctx);
+
   void
   fetchStatus(Controller& controller,
               const function<void()>& onSuccess,
diff --git a/tools/nfdc/find-face.cpp b/tools/nfdc/find-face.cpp
new file mode 100644
index 0000000..c03e6dc
--- /dev/null
+++ b/tools/nfdc/find-face.cpp
@@ -0,0 +1,149 @@
+/* -*- 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.
+ *
+ * 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 "find-face.hpp"
+#include "format-helpers.hpp"
+#include <ndn-cxx/util/logger.hpp>
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+NDN_LOG_INIT(nfdc.FindFace);
+
+FindFace::FindFace(ExecuteContext& ctx)
+  : m_ctx(ctx)
+{
+}
+
+FindFace::Code
+FindFace::execute(const FaceUri& faceUri, bool allowMulti)
+{
+  FaceQueryFilter filter;
+  filter.setRemoteUri(faceUri.toString());
+  return this->execute(filter);
+}
+
+FindFace::Code
+FindFace::execute(uint64_t faceId)
+{
+  FaceQueryFilter filter;
+  filter.setFaceId(faceId);
+  return this->execute(filter);
+}
+
+FindFace::Code
+FindFace::execute(const FaceQueryFilter& filter, bool allowMulti)
+{
+  BOOST_ASSERT(m_res == Code::NOT_STARTED);
+  m_res = Code::IN_PROGRESS;
+  m_filter = filter;
+
+  if (m_filter.hasRemoteUri()) {
+    auto remoteUri = this->canonize("remote", FaceUri(m_filter.getRemoteUri()));
+    if (!remoteUri) {
+      m_res = Code::CANONIZE_ERROR;
+      return m_res;
+    }
+    m_filter.setRemoteUri(remoteUri->toString());
+  }
+
+  ///\todo #3864 canonize localUri
+
+  this->query();
+  if (m_res == Code::OK) {
+    if (m_results.size() == 0) {
+      m_res = Code::NOT_FOUND;
+      m_errorReason = "Face not found";
+    }
+    else if (m_results.size() > 1 && !allowMulti) {
+      m_res = Code::AMBIGUOUS;
+      m_errorReason = "Multiple faces match the query";
+    }
+  }
+  return m_res;
+}
+
+ndn::optional<FaceUri>
+FindFace::canonize(const std::string& fieldName, const FaceUri& input)
+{
+  if (!FaceUri::canCanonize(input.getScheme())) {
+    NDN_LOG_DEBUG("Using " << fieldName << '=' << input << " without canonization");
+    return input;
+  }
+
+  ndn::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();
+
+  return result;
+}
+
+void
+FindFace::query()
+{
+  m_ctx.controller.fetch<ndn::nfd::FaceQueryDataset>(
+    m_filter,
+    [this] (const std::vector<ndn::nfd::FaceStatus>& result) {
+      m_res = Code::OK;
+      m_results = result;
+    },
+    [this] (uint32_t code, const std::string& reason) {
+      m_res = Code::ERROR;
+      m_errorReason = "Error " + std::to_string(code) + " when querying face: " + reason;
+    },
+    m_ctx.makeCommandOptions());
+  m_ctx.face.processEvents();
+}
+
+const FaceStatus&
+FindFace::getFaceStatus() const
+{
+  BOOST_ASSERT(m_results.size() == 1);
+  return m_results.front();
+}
+
+void
+FindFace::printDisambiguation(std::ostream& os, DisambiguationStyle style) const
+{
+  text::Separator sep(" ", ", ");
+  for (const auto& item : m_results) {
+    os << sep;
+    switch (style) {
+      case DisambiguationStyle::LOCAL_URI:
+        os << item.getFaceId() << " (local=" << item.getLocalUri() << ')';
+        break;
+    }
+  }
+}
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
diff --git a/tools/nfdc/find-face.hpp b/tools/nfdc/find-face.hpp
new file mode 100644
index 0000000..57eb0f0
--- /dev/null
+++ b/tools/nfdc/find-face.hpp
@@ -0,0 +1,137 @@
+/* -*- 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.
+ *
+ * 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_FIND_FACE_HPP
+#define NFD_TOOLS_NFDC_FIND_FACE_HPP
+
+#include "execute-command.hpp"
+
+namespace nfd {
+namespace tools {
+namespace nfdc {
+
+using ndn::nfd::FaceQueryFilter;
+using ndn::nfd::FaceStatus;
+
+/** \brief procedure to find a face
+ */
+class FindFace : noncopyable
+{
+public:
+  enum class Code {
+    OK = 0, ///< found exactly one face, or found multiple faces when allowMulti is true
+    ERROR = 1, ///< unspecified error
+    NOT_FOUND = 3, ///< found zero face
+    CANONIZE_ERROR = 4, ///< error during FaceUri canonization
+    AMBIGUOUS = 5, ///< found multiple faces and allowMulti is false
+    NOT_STARTED = -1, ///< for internal use
+    IN_PROGRESS = -2, ///< for internal use
+  };
+
+  enum class DisambiguationStyle
+  {
+    LOCAL_URI = 1 ///< print FaceId and LocalUri
+  };
+
+  explicit
+  FindFace(ExecuteContext& ctx);
+
+  /** \brief find face by FaceUri
+   *  \pre execute has not been invoked
+   */
+  Code
+  execute(const FaceUri& faceUri, bool allowMulti = false);
+
+  /** \brief find face by FaceId
+   *  \pre execute has not been invoked
+   */
+  Code
+  execute(uint64_t faceId);
+
+  /** \brief find face by FaceQueryFilter
+   *  \pre execute has not been invoked
+   */
+  Code
+  execute(const FaceQueryFilter& filter, bool allowMulti = false);
+
+  /** \return face status for all results
+   */
+  const std::vector<FaceStatus>&
+  getResults() const
+  {
+    return m_results;
+  }
+
+  /** \return a single face status
+   *  \pre getResults().size() == 1
+   */
+  const FaceStatus&
+  getFaceStatus() const;
+
+  uint64_t
+  getFaceId() const
+  {
+    return this->getFaceStatus().getFaceId();
+  }
+
+  const std::string&
+  getErrorReason() const
+  {
+    return m_errorReason;
+  }
+
+  /** \brief print results for disambiguation
+   */
+  void
+  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
+   */
+  ndn::optional<FaceUri>
+  canonize(const std::string& fieldName, const FaceUri& input);
+
+  /** \brief retrieve FaceStatus from filter
+   *  \post m_res == Code::OK and m_results is populated if retrieval succeeds
+   *  \post m_res == Code::ERROR and m_errorReason is set if retrieval fails
+   */
+  void
+  query();
+
+private:
+  ExecuteContext& m_ctx;
+  FaceQueryFilter m_filter;
+  Code m_res = Code::NOT_STARTED;
+  std::vector<FaceStatus> m_results;
+  std::string m_errorReason;
+};
+
+} // namespace nfdc
+} // namespace tools
+} // namespace nfd
+
+#endif // NFD_TOOLS_NFDC_FIND_FACE_HPP