mgmt: add faces/update command

refs #3731

Change-Id: I79777a10feecb2de83276371100cc86a43d0e76d
diff --git a/tests/daemon/mgmt/face-manager-create-face.t.cpp b/tests/daemon/mgmt/face-manager-create-face.t.cpp
index 052b89c..c691491 100644
--- a/tests/daemon/mgmt/face-manager-create-face.t.cpp
+++ b/tests/daemon/mgmt/face-manager-create-face.t.cpp
@@ -84,19 +84,6 @@
   }
 };
 
-class UdpFaceConnectToSelf // face that will cause afterCreateFaceFailure to be invoked
-                           // fails because remote endpoint is prohibited
-{
-public:
-  ControlParameters
-  getParameters()
-  {
-    return ControlParameters()
-      .setUri("udp4://0.0.0.0:16363"); // cannot connect to self
-  }
-};
-
-
 class UdpFacePersistent
 {
 public:
@@ -121,41 +108,28 @@
   }
 };
 
-class Success
+class UdpFaceConnectToSelf // face that will cause afterCreateFaceFailure to be invoked
+                           // fails because remote endpoint is prohibited
 {
 public:
-  ControlResponse
-  getExpected()
+  ControlParameters
+  getParameters()
   {
-    return ControlResponse()
-      .setCode(200)
-      .setText("OK");
-  }
-};
-
-template<int CODE>
-class Failure
-{
-public:
-  ControlResponse
-  getExpected()
-  {
-    return ControlResponse()
-      .setCode(CODE)
-      .setText("Error"); // error description should not be checked
+    return ControlParameters()
+      .setUri("udp4://0.0.0.0:16363"); // cannot connect to self
   }
 };
 
 namespace mpl = boost::mpl;
 
 // pairs of CreateCommand and Success/Failure status
-typedef mpl::vector<mpl::pair<TcpFaceOnDemand, Failure<406>>,
-                    mpl::pair<TcpFacePersistent, Success>,
-                    mpl::pair<TcpFacePermanent, Failure<406>>,
-                    mpl::pair<UdpFaceOnDemand, Failure<406>>,
-                    mpl::pair<UdpFacePersistent, Success>,
-                    mpl::pair<UdpFacePermanent, Success>,
-                    mpl::pair<UdpFaceConnectToSelf, Failure<406>>> Faces;
+typedef mpl::vector<mpl::pair<TcpFaceOnDemand, CommandFailure<406>>,
+                    mpl::pair<TcpFacePersistent, CommandSuccess>,
+                    mpl::pair<TcpFacePermanent, CommandFailure<406>>,
+                    mpl::pair<UdpFaceOnDemand, CommandFailure<406>>,
+                    mpl::pair<UdpFacePersistent, CommandSuccess>,
+                    mpl::pair<UdpFacePermanent, CommandSuccess>,
+                    mpl::pair<UdpFaceConnectToSelf, CommandFailure<406>>> Faces;
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(NewFace, T, Faces, FaceManagerCommandFixture)
 {
@@ -186,10 +160,28 @@
       BOOST_CHECK(actualParams.hasFaceId());
       BOOST_CHECK_EQUAL(expectedParams.getFacePersistency(), actualParams.getFacePersistency());
 
-      if (actual.getCode() != 200) {
+      if (actual.getCode() == 200) {
+        if (expectedParams.hasFlags()) {
+          // TODO: #3731 check if Flags match
+        }
+        else {
+          // TODO: #3731 check if Flags at defaults
+        }
+      }
+      else {
         BOOST_CHECK_EQUAL(expectedParams.getUri(), actualParams.getUri());
       }
     }
+
+    if (actual.getCode() != 200) {
+      // ensure face not created
+      FaceUri uri(FaceType().getParameters().getUri());
+      auto& faceTable = this->node1.manager.m_faceTable;
+      BOOST_CHECK(std::none_of(faceTable.begin(), faceTable.end(), [uri] (Face& face) {
+        return face.getRemoteUri() == uri;
+      }));
+    }
+
     hasCallbackFired = true;
   });
 
diff --git a/tests/daemon/mgmt/face-manager-update-face.t.cpp b/tests/daemon/mgmt/face-manager-update-face.t.cpp
new file mode 100644
index 0000000..68ac36a
--- /dev/null
+++ b/tests/daemon/mgmt/face-manager-update-face.t.cpp
@@ -0,0 +1,523 @@
+/* -*- 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.
+ *
+ * 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 "face-manager-command-fixture.hpp"
+#include "nfd-manager-common-fixture.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_AUTO_TEST_SUITE(TestFaceManager)
+
+class FaceManagerUpdateFixture : public FaceManagerCommandFixture
+{
+public:
+  FaceManagerUpdateFixture()
+    : faceId(0)
+  {
+  }
+
+  ~FaceManagerUpdateFixture()
+  {
+    destroyFace();
+  }
+
+  void
+  createFace(const std::string& uri = "tcp4://127.0.0.1:26363",
+             ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             bool enableLocalFields = false)
+  {
+    ControlParameters params;
+    params.setUri(uri);
+    params.setFacePersistency(persistency);
+    params.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, enableLocalFields);
+
+    Name commandName("/localhost/nfd/faces/create");
+    commandName.append(params.wireEncode());
+    auto command = makeInterest(commandName);
+    m_keyChain.sign(*command);
+
+    bool hasCallbackFired = false;
+    signal::ScopedConnection connection = this->node1.face.onSendData.connect(
+      [this, command, &hasCallbackFired] (const Data& response) {
+        if (!command->getName().isPrefixOf(response.getName())) {
+          return;
+        }
+
+        ControlResponse create(response.getContent().blockFromValue());
+        BOOST_REQUIRE_EQUAL(create.getCode(), 200);
+
+        if (create.getBody().hasWire()) {
+          ControlParameters faceParams(create.getBody());
+          BOOST_REQUIRE(faceParams.hasFaceId());
+          this->faceId = faceParams.getFaceId();
+        }
+        else {
+          BOOST_FAIL("Face creation failed");
+        }
+
+        hasCallbackFired = true;
+      });
+
+    this->node1.face.receive(*command);
+    this->advanceClocks(time::milliseconds(1), 5);
+
+    BOOST_REQUIRE(hasCallbackFired);
+  }
+
+  void
+  updateFace(const ControlParameters& requestParams,
+             bool isSelfUpdating,
+             const function<void(const ControlResponse& resp)>& checkResp)
+  {
+    Name commandName("/localhost/nfd/faces/update");
+    commandName.append(requestParams.wireEncode());
+    auto command = makeInterest(commandName);
+    if (isSelfUpdating) {
+      // Attach IncomingFaceIdTag to interest
+      command->setTag(make_shared<lp::IncomingFaceIdTag>(faceId));
+    }
+    m_keyChain.sign(*command);
+
+    bool hasCallbackFired = false;
+    signal::ScopedConnection connection = this->node1.face.onSendData.connect(
+      [this, command, &hasCallbackFired, &checkResp] (const Data& response) {
+        if (!command->getName().isPrefixOf(response.getName())) {
+          return;
+        }
+
+        ControlResponse actual(response.getContent().blockFromValue());
+        checkResp(actual);
+
+        hasCallbackFired = true;
+      });
+
+    this->node1.face.receive(*command);
+    this->advanceClocks(time::milliseconds(1), 5);
+
+    BOOST_CHECK(hasCallbackFired);
+  }
+
+private:
+  void
+  destroyFace()
+  {
+    if (faceId == 0) {
+      return;
+    }
+
+    ControlParameters params;
+    params.setFaceId(faceId);
+
+    Name commandName("/localhost/nfd/faces/destroy");
+    commandName.append(params.wireEncode());
+    auto command = makeInterest(commandName);
+    m_keyChain.sign(*command);
+
+    bool hasCallbackFired = false;
+    signal::ScopedConnection connection = this->node1.face.onSendData.connect(
+      [this, command, &hasCallbackFired] (const Data& response) {
+        if (!command->getName().isPrefixOf(response.getName())) {
+          return;
+        }
+
+        ControlResponse destroy(response.getContent().blockFromValue());
+        BOOST_CHECK_EQUAL(destroy.getCode(), 200);
+
+        faceId = 0;
+        hasCallbackFired = true;
+      });
+
+    this->node1.face.receive(*command);
+    this->advanceClocks(time::milliseconds(1), 5);
+
+    BOOST_CHECK(hasCallbackFired);
+  }
+
+public:
+  FaceId faceId;
+};
+
+BOOST_FIXTURE_TEST_SUITE(UpdateFace, FaceManagerUpdateFixture)
+
+BOOST_AUTO_TEST_CASE(FaceDoesNotExist)
+{
+  ControlParameters requestParams;
+  requestParams.setFaceId(65535);
+
+  updateFace(requestParams, false, [] (const ControlResponse& actual) {
+    ControlResponse expected(404, "Specified face does not exist");
+    BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
+    BOOST_TEST_MESSAGE(actual.getText());
+  });
+}
+
+// TODO #3232: Expected failure until FacePersistency updating implemented
+BOOST_AUTO_TEST_CASE(UpdatePersistency)
+{
+  createFace();
+
+  ControlParameters requestParams;
+  requestParams.setFaceId(faceId);
+  requestParams.setFacePersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+
+  updateFace(requestParams, false, [] (const ControlResponse& actual) {
+    ControlResponse expected(409, "Invalid fields specified");
+    BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
+    BOOST_TEST_MESSAGE(actual.getText());
+
+    if (actual.getBody().hasWire()) {
+      ControlParameters actualParams(actual.getBody());
+
+      BOOST_REQUIRE(actualParams.hasFacePersistency());
+      BOOST_CHECK_EQUAL(actualParams.getFacePersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+    }
+    else {
+      BOOST_ERROR("Response does not contain ControlParameters");
+    }
+  });
+}
+
+class TcpLocalFieldsEnable
+{
+public:
+  std::string
+  getUri()
+  {
+    return "tcp4://127.0.0.1:26363";
+  }
+
+  boost::asio::ip::address_v4
+  getIpAddress()
+  {
+    return boost::asio::ip::address_v4::from_string("127.0.0.1");
+  }
+
+  ndn::nfd::FacePersistency
+  getPersistency()
+  {
+    return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
+  }
+
+  bool
+  getInitLocalFieldsEnabled()
+  {
+    return false;
+  }
+
+  bool
+  getLocalFieldsEnabled()
+  {
+    return true;
+  }
+
+  bool
+  getLocalFieldsEnabledMask()
+  {
+    return true;
+  }
+
+  bool
+  shouldHaveWire()
+  {
+    return false;
+  }
+};
+
+class TcpLocalFieldsDisable
+{
+public:
+  std::string
+  getUri()
+  {
+    return "tcp4://127.0.0.1:26363";
+  }
+
+  ndn::nfd::FacePersistency
+  getPersistency()
+  {
+    return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
+  }
+
+  bool
+  getInitLocalFieldsEnabled()
+  {
+    return true;
+  }
+
+  bool
+  getLocalFieldsEnabled()
+  {
+    return false;
+  }
+
+  bool
+  getLocalFieldsEnabledMask()
+  {
+    return true;
+  }
+
+  bool
+  shouldHaveWire()
+  {
+    return false;
+  }
+};
+
+// UDP faces are non-local by definition
+class UdpLocalFieldsEnable
+{
+public:
+  std::string
+  getUri()
+  {
+    return "udp4://127.0.0.1:26363";
+  }
+
+  ndn::nfd::FacePersistency
+  getPersistency()
+  {
+    return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
+  }
+
+  bool
+  getInitLocalFieldsEnabled()
+  {
+    return false;
+  }
+
+  bool
+  getLocalFieldsEnabled()
+  {
+    return true;
+  }
+
+  bool
+  getLocalFieldsEnabledMask()
+  {
+    return true;
+  }
+
+  bool
+  shouldHaveWire()
+  {
+    return true;
+  }
+};
+
+// UDP faces are non-local by definition
+// In this test case, attempt to disable local fields on face with local fields already disabled
+class UdpLocalFieldsDisable
+{
+public:
+  std::string
+  getUri()
+  {
+    return "udp4://127.0.0.1:26363";
+  }
+
+  ndn::nfd::FacePersistency
+  getPersistency()
+  {
+    return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
+  }
+
+  bool
+  getInitLocalFieldsEnabled()
+  {
+    return false;
+  }
+
+  bool
+  getLocalFieldsEnabled()
+  {
+    return false;
+  }
+
+  bool
+  getLocalFieldsEnabledMask()
+  {
+    return true;
+  }
+
+  bool
+  shouldHaveWire()
+  {
+    return false;
+  }
+};
+
+// In this test case, set Flags to enable local fields on non-local face, but exclude local fields
+// from Mask. This test case will pass as no action is taken due to the missing Mask bit.
+class UdpLocalFieldsEnableNoMaskBit
+{
+public:
+  std::string
+  getUri()
+  {
+    return "udp4://127.0.0.1:26363";
+  }
+
+  ndn::nfd::FacePersistency
+  getPersistency()
+  {
+    return ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
+  }
+
+  bool
+  getInitLocalFieldsEnabled()
+  {
+    return false;
+  }
+
+  bool
+  getLocalFieldsEnabled()
+  {
+    return true;
+  }
+
+  bool
+  getLocalFieldsEnabledMask()
+  {
+    return false;
+  }
+
+  bool
+  shouldHaveWire()
+  {
+    return false;
+  }
+};
+
+namespace mpl = boost::mpl;
+
+typedef mpl::vector<mpl::pair<TcpLocalFieldsEnable, CommandSuccess>,
+                    mpl::pair<TcpLocalFieldsDisable, CommandSuccess>,
+                    mpl::pair<UdpLocalFieldsEnable, CommandFailure<409>>,
+                    mpl::pair<UdpLocalFieldsDisable, CommandSuccess>,
+                    mpl::pair<UdpLocalFieldsEnableNoMaskBit, CommandSuccess>> LocalFieldFaces;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(UpdateLocalFields, T, LocalFieldFaces)
+{
+  typedef typename T::first TestType;
+  typedef typename T::second ResultType;
+
+  createFace(TestType().getUri(), TestType().getPersistency(), TestType().getInitLocalFieldsEnabled());
+
+  ControlParameters requestParams;
+  requestParams.setFaceId(faceId);
+  requestParams.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, TestType().getLocalFieldsEnabled());
+  if (!TestType().getLocalFieldsEnabledMask()) {
+    requestParams.unsetFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED);
+  }
+
+  updateFace(requestParams, false, [] (const ControlResponse& actual) {
+    ControlResponse expected(ResultType().getExpected().getCode(), "");
+    BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
+    BOOST_TEST_MESSAGE(actual.getText());
+
+    if (TestType().shouldHaveWire() && actual.getBody().hasWire()) {
+      ControlParameters actualParams(actual.getBody());
+
+      BOOST_CHECK(!actualParams.hasFacePersistency());
+      BOOST_CHECK(actualParams.hasFlags());
+      BOOST_CHECK(actualParams.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
+      BOOST_CHECK(actualParams.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
+    }
+  });
+}
+
+BOOST_AUTO_TEST_CASE(UpdateLocalFieldsEnableDisable)
+{
+  createFace();
+
+  ControlParameters enableParams;
+  enableParams.setFaceId(faceId);
+  enableParams.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, true);
+
+  ControlParameters disableParams;
+  disableParams.setFaceId(faceId);
+  disableParams.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, false);
+
+  updateFace(enableParams, false, [] (const ControlResponse& actual) {
+    ControlResponse expected(200, "OK");
+    BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
+    BOOST_TEST_MESSAGE(actual.getText());
+
+    if (actual.getBody().hasWire()) {
+      ControlParameters actualParams(actual.getBody());
+
+      BOOST_CHECK(actualParams.hasFaceId());
+      BOOST_CHECK(actualParams.hasFacePersistency());
+      BOOST_REQUIRE(actualParams.hasFlags());
+      // Check if flags indicate local fields enabled
+      BOOST_CHECK(actualParams.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
+    }
+    else {
+      BOOST_ERROR("Enable: Response does not contain ControlParameters");
+    }
+  });
+
+  updateFace(disableParams, false, [] (const ControlResponse& actual) {
+    ControlResponse expected(200, "OK");
+    BOOST_CHECK_EQUAL(actual.getCode(), expected.getCode());
+    BOOST_TEST_MESSAGE(actual.getText());
+
+    if (actual.getBody().hasWire()) {
+      ControlParameters actualParams(actual.getBody());
+
+      BOOST_CHECK(actualParams.hasFaceId());
+      BOOST_CHECK(actualParams.hasFacePersistency());
+      BOOST_REQUIRE(actualParams.hasFlags());
+      // Check if flags indicate local fields disabled
+      BOOST_CHECK(!actualParams.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
+    }
+    else {
+      BOOST_ERROR("Disable: Response does not contain ControlParameters");
+    }
+  });
+}
+
+BOOST_AUTO_TEST_CASE(SelfUpdating)
+{
+  createFace();
+
+  // Send a command that does nothing (will return 200) and does not contain a FaceId
+  ControlParameters sentParams;
+
+  updateFace(sentParams, true, [] (const ControlResponse& actual) {
+    ControlResponse expected(200, "OK");
+    BOOST_REQUIRE_EQUAL(actual.getCode(), expected.getCode());
+    BOOST_TEST_MESSAGE(actual.getText());
+  });
+}
+
+BOOST_AUTO_TEST_SUITE_END() // UpdateFace
+BOOST_AUTO_TEST_SUITE_END() // TestFaceManager
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/daemon/mgmt/nfd-manager-common-fixture.hpp b/tests/daemon/mgmt/nfd-manager-common-fixture.hpp
index 32650ce..65c5dcc 100644
--- a/tests/daemon/mgmt/nfd-manager-common-fixture.hpp
+++ b/tests/daemon/mgmt/nfd-manager-common-fixture.hpp
@@ -55,6 +55,31 @@
   shared_ptr<CommandAuthenticator> m_authenticator;
 };
 
+class CommandSuccess
+{
+public:
+  ControlResponse
+  getExpected()
+  {
+    return ControlResponse()
+      .setCode(200)
+      .setText("OK");
+  }
+};
+
+template<int CODE>
+class CommandFailure
+{
+public:
+  ControlResponse
+  getExpected()
+  {
+    return ControlResponse()
+      .setCode(CODE);
+    // error description should not be checked
+  }
+};
+
 } // namespace tests
 } // namespace nfd