mgmt: add faces/update command

refs #3731

Change-Id: I79777a10feecb2de83276371100cc86a43d0e76d
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 9cd0e07..f41f4de 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -59,6 +59,9 @@
   registerCommandHandler<ndn::nfd::FaceCreateCommand>("create",
     bind(&FaceManager::createFace, this, _2, _3, _4, _5));
 
+  registerCommandHandler<ndn::nfd::FaceUpdateCommand>("update",
+    bind(&FaceManager::updateFace, this, _2, _3, _4, _5));
+
   registerCommandHandler<ndn::nfd::FaceDestroyCommand>("destroy",
     bind(&FaceManager::destroyFace, this, _2, _3, _4, _5));
 
@@ -143,6 +146,8 @@
 
   m_faceTable.add(newFace);
 
+  // TODO: #3731: Verify and add Flags
+
   // Set ControlResponse parameters
   response.setFaceId(newFace->getId());
   response.setFacePersistency(newFace->getPersistency());
@@ -161,6 +166,69 @@
 }
 
 void
+FaceManager::updateFace(const Name& topPrefix, const Interest& interest,
+                        const ControlParameters& parameters,
+                        const ndn::mgmt::CommandContinuation& done)
+{
+  FaceId faceId = parameters.getFaceId();
+  if (faceId == 0) {
+    // Self-updating
+    shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = interest.getTag<lp::IncomingFaceIdTag>();
+    if (incomingFaceIdTag == nullptr) {
+      NFD_LOG_TRACE("unable to determine face for self-update");
+      done(ControlResponse(404, "No FaceId specified and IncomingFaceId not available"));
+      return;
+    }
+    faceId = *incomingFaceIdTag;
+  }
+
+  Face* face = m_faceTable.get(faceId);
+
+  if (face == nullptr) {
+    NFD_LOG_TRACE("invalid face specified");
+    done(ControlResponse(404, "Specified face does not exist"));
+    return;
+  }
+
+  // Verify validity of requested changes
+  ControlParameters response;
+  bool areParamsValid = true;
+
+  if (parameters.hasFacePersistency()) {
+    // TODO #3232: Add FacePersistency updating
+    NFD_LOG_TRACE("received unsupported face persistency change");
+    areParamsValid = false;
+    response.setFacePersistency(parameters.getFacePersistency());
+  }
+
+  if (parameters.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
+      parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
+      face->getScope() != ndn::nfd::FACE_SCOPE_LOCAL) {
+    NFD_LOG_TRACE("received request to enable local fields on non-local face");
+    areParamsValid = false;
+    response.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED,
+                        parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED));
+  }
+
+  if (!areParamsValid) {
+    done(ControlResponse(409, "Invalid properties specified").setBody(response.wireEncode()));
+    return;
+  }
+
+  // All specified properties are valid, so make changes
+
+  // TODO #3232: Add FacePersistency updating
+
+  setLinkServiceOptions(*face, parameters, response);
+
+  // Set remaining ControlResponse fields
+  response.setFaceId(faceId);
+  response.setFacePersistency(face->getPersistency());
+
+  done(ControlResponse(200, "OK").setBody(response.wireEncode()));
+}
+
+void
 FaceManager::destroyFace(const Name& topPrefix, const Interest& interest,
                          const ControlParameters& parameters,
                          const ndn::mgmt::CommandContinuation& done)
@@ -183,8 +251,7 @@
     return;
   }
 
-  // TODO#3226 redesign enable-local-control
-  // For now, enable-local-control will enable all local fields in GenericLinkService.
+  // enable-local-control will enable all local fields in GenericLinkService
   auto service = dynamic_cast<face::GenericLinkService*>(face->getLinkService());
   if (service == nullptr) {
     return done(ControlResponse(503, "LinkService type not supported"));
@@ -208,8 +275,7 @@
     return;
   }
 
-  // TODO#3226 redesign disable-local-control
-  // For now, disable-local-control will disable all local fields in GenericLinkService.
+  // disable-local-control will disable all local fields in GenericLinkService
   auto service = dynamic_cast<face::GenericLinkService*>(face->getLinkService());
   if (service == nullptr) {
     return done(ControlResponse(503, "LinkService type not supported"));
@@ -251,6 +317,25 @@
 }
 
 void
+FaceManager::setLinkServiceOptions(Face& face,
+                                   const ControlParameters& parameters,
+                                   ControlParameters& response)
+{
+  auto linkService = dynamic_cast<face::GenericLinkService*>(face.getLinkService());
+  BOOST_ASSERT(linkService != nullptr);
+
+  auto options = linkService->getOptions();
+  if (parameters.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
+      face.getScope() == ndn::nfd::FACE_SCOPE_LOCAL) {
+    options.allowLocalFields = parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED);
+  }
+  linkService->setOptions(options);
+
+  // Set Flags for ControlResponse
+  response.setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, options.allowLocalFields, false);
+}
+
+void
 FaceManager::listFaces(const Name& topPrefix, const Interest& interest,
                        ndn::mgmt::StatusDatasetContext& context)
 {
diff --git a/daemon/mgmt/face-manager.hpp b/daemon/mgmt/face-manager.hpp
index 094f288..b168da4 100644
--- a/daemon/mgmt/face-manager.hpp
+++ b/daemon/mgmt/face-manager.hpp
@@ -61,15 +61,26 @@
              const ndn::mgmt::CommandContinuation& done);
 
   void
+  updateFace(const Name& topPrefix, const Interest& interest,
+             const ControlParameters& parameters,
+             const ndn::mgmt::CommandContinuation& done);
+
+  void
   destroyFace(const Name& topPrefix, const Interest& interest,
               const ControlParameters& parameters,
               const ndn::mgmt::CommandContinuation& done);
 
+  /**
+   * \deprecated use Flags+Mask in faces/update instead
+   */
   void
   enableLocalControl(const Name& topPrefix, const Interest& interest,
                      const ControlParameters& parameters,
                      const ndn::mgmt::CommandContinuation& done);
 
+  /**
+   * \deprecated use Flags+Mask in faces/update instead
+   */
   void
   disableLocalControl(const Name& topPrefix, const Interest& interest,
                       const ControlParameters& parameters,
@@ -91,6 +102,11 @@
                           const ControlParameters& parameters,
                           const ndn::mgmt::CommandContinuation& done);
 
+  static void
+  setLinkServiceOptions(Face& face,
+                        const ControlParameters& parameters,
+                        ControlParameters& response);
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE: // StatusDataset
   void
   listFaces(const Name& topPrefix, const Interest& interest,
@@ -151,9 +167,9 @@
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   std::map<std::string /*protocol*/, shared_ptr<ProtocolFactory>> m_factories;
+  FaceTable& m_faceTable;
 
 private:
-  FaceTable& m_faceTable;
   ndn::mgmt::PostNotification m_postNotification;
   signal::ScopedConnection m_faceAddConn;
   signal::ScopedConnection m_faceRemoveConn;
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