face: scoped Interest filter

refs #3919

Change-Id: I4e1aed63b2fabe463e9572b99bb3d97b278cabd6
diff --git a/ndn-cxx/face.cpp b/ndn-cxx/face.cpp
index 4e09421..bf832d6 100644
--- a/ndn-cxx/face.cpp
+++ b/ndn-cxx/face.cpp
@@ -227,7 +227,7 @@
   } IO_CAPTURE_WEAK_IMPL_END
 }
 
-const RegisteredPrefixId*
+RegisteredPrefixHandle
 Face::setInterestFilter(const InterestFilter& interestFilter,
                         const InterestCallback& onInterest,
                         const RegisterPrefixFailureCallback& onFailure,
@@ -237,7 +237,7 @@
   return setInterestFilter(interestFilter, onInterest, nullptr, onFailure, signingInfo, flags);
 }
 
-const RegisteredPrefixId*
+RegisteredPrefixHandle
 Face::setInterestFilter(const InterestFilter& interestFilter,
                         const InterestCallback& onInterest,
                         const RegisterPrefixSuccessCallback& onSuccess,
@@ -250,11 +250,12 @@
   nfd::CommandOptions options;
   options.setSigningInfo(signingInfo);
 
-  return m_impl->registerPrefix(interestFilter.getPrefix(), filter,
-                                onSuccess, onFailure, flags, options);
+  auto id = m_impl->registerPrefix(interestFilter.getPrefix(), filter,
+                                   onSuccess, onFailure, flags, options);
+  return RegisteredPrefixHandle(*this, id);
 }
 
-const InterestFilterId*
+InterestFilterHandle
 Face::setInterestFilter(const InterestFilter& interestFilter,
                         const InterestCallback& onInterest)
 {
@@ -264,7 +265,8 @@
     impl->asyncSetInterestFilter(filter);
   } IO_CAPTURE_WEAK_IMPL_END
 
-  return reinterpret_cast<const InterestFilterId*>(filter.get());
+  auto id = reinterpret_cast<const InterestFilterId*>(filter.get());
+  return InterestFilterHandle(*this, id);
 }
 
 RegisteredPrefixHandle
@@ -436,4 +438,10 @@
   m_id = nullptr;
 }
 
+InterestFilterHandle::InterestFilterHandle(Face& face, const InterestFilterId* id)
+  : CancelHandle([&face, id] { face.unsetInterestFilter(id); })
+  , m_id(id)
+{
+}
+
 } // namespace ndn
diff --git a/ndn-cxx/face.hpp b/ndn-cxx/face.hpp
index b33abd4..05454ca 100644
--- a/ndn-cxx/face.hpp
+++ b/ndn-cxx/face.hpp
@@ -40,6 +40,7 @@
 class RegisteredPrefixId;
 class RegisteredPrefixHandle;
 class InterestFilterId;
+class InterestFilterHandle;
 
 namespace nfd {
 class Controller;
@@ -275,10 +276,9 @@
    * @param signingInfo    (optional) Signing parameters.  When omitted, a default parameters
    *                       used in the signature will be used.
    *
-   * @return Opaque registered prefix ID which can be used with unsetInterestFilter or
-   *         removeRegisteredPrefix
+   * @return A handle for unregistering the prefix.
    */
-  const RegisteredPrefixId*
+  RegisteredPrefixHandle
   setInterestFilter(const InterestFilter& interestFilter,
                     const InterestCallback& onInterest,
                     const RegisterPrefixFailureCallback& onFailure,
@@ -303,10 +303,9 @@
    * @param signingInfo    (optional) Signing parameters.  When omitted, a default parameters
    *                       used in the signature will be used.
    *
-   * @return Opaque registered prefix ID which can be used with unsetInterestFilter or
-   *         removeRegisteredPrefix
+   * @return A handle for unregistering the prefix.
    */
-  const RegisteredPrefixId*
+  RegisteredPrefixHandle
   setInterestFilter(const InterestFilter& interestFilter,
                     const InterestCallback& onInterest,
                     const RegisterPrefixSuccessCallback& onSuccess,
@@ -324,9 +323,9 @@
    * forwarder.  It will always succeed.  To register prefix with the forwarder, use
    * registerPrefix, or use the setInterestFilter overload taking two callbacks.
    *
-   * @return Opaque interest filter ID which can be used with unsetInterestFilter
+   * @return A handle for unsetting the Interest filter.
    */
-  const InterestFilterId*
+  InterestFilterHandle
   setInterestFilter(const InterestFilter& interestFilter,
                     const InterestCallback& onInterest);
 
@@ -344,7 +343,7 @@
    *                    used in the signature will be used.
    * @param flags       Prefix registration flags
    *
-   * @return The registered prefix ID which can be used with unregisterPrefix
+   * @return A handle for unregistering the prefix.
    * @see nfd::RouteFlags
    */
   RegisteredPrefixHandle
@@ -364,7 +363,7 @@
    * unsetInterestFilter will use the same credentials as original
    * setInterestFilter/registerPrefix command
    *
-   * @param registeredPrefixId The ID returned from registerPrefix
+   * @param registeredPrefixId a handle returned from registerPrefix
    */
   void
   unsetInterestFilter(const RegisteredPrefixId* registeredPrefixId);
@@ -375,7 +374,7 @@
    * This method always succeeds and will **NOT** send any request to the connected
    * forwarder.
    *
-   * @param interestFilterId The ID returned from setInterestFilter.
+   * @param interestFilterId a handle returned from setInterestFilter.
    */
   void
   unsetInterestFilter(const InterestFilterId* interestFilterId);
@@ -389,7 +388,7 @@
    * If registeredPrefixId was obtained using setInterestFilter, the corresponding
    * InterestFilter will be unset too.
    *
-   * @param registeredPrefixId The ID returned from registerPrefix
+   * @param registeredPrefixId a handle returned from registerPrefix
    * @param onSuccess          Callback to be called when operation succeeds
    * @param onFailure          Callback to be called when operation fails
    */
@@ -586,6 +585,53 @@
  */
 using ScopedRegisteredPrefixHandle = detail::ScopedCancelHandle;
 
+/** \brief A handle of registered Interest filter.
+ *
+ *  \code
+ *  InterestFilterHandle hdl = face.setInterestFilter(prefix, onInterest);
+ *  hdl.cancel(); // unset the Interest filter
+ *  \endcode
+ *
+ *  \warning Unsetting the same Interest filter more than once, using same or different
+ *           InterestFilterHandle or ScopedInterestFilterHandle, may trigger undefined behavior.
+ *  \warning Unsetting an Interest filter after the face has been destructed may trigger
+ *           undefined behavior.
+ */
+class InterestFilterHandle : public detail::CancelHandle
+{
+public:
+  InterestFilterHandle() = default;
+
+  InterestFilterHandle(Face& face, const InterestFilterId* id);
+
+  operator const InterestFilterId*() const
+  {
+    return m_id;
+  }
+
+private:
+  const InterestFilterId* m_id = nullptr;
+};
+
+/** \brief A scoped handle of registered Interest filter.
+ *
+ *  Upon destruction of this handle, the Interest filter is unset automatically.
+ *  Most commonly, the application keeps a ScopedInterestFilterHandle as a class member field,
+ *  so that it can cleanup its Interest filter when the class instance is destructed.
+ *
+ *  \code
+ *  {
+ *    ScopedInterestFilterHandle hdl = face.setInterestFilter(prefix, onInterest);
+ *  } // hdl goes out of scope, unsetting the Interest filter
+ *  \endcode
+ *
+ *  \warning Unsetting the same Interest filter more than once, using same or different
+ *           InterestFilterHandle or ScopedInterestFilterHandle, may trigger undefined behavior.
+ *  \warning Unsetting an Interest filter after the face has been destructed may trigger
+ *           undefined behavior.
+ */
+using ScopedInterestFilterHandle = detail::ScopedCancelHandle;
+
 } // namespace ndn
 
 #endif // NDN_FACE_HPP
diff --git a/ndn-cxx/mgmt/dispatcher.cpp b/ndn-cxx/mgmt/dispatcher.cpp
index 2c33ef9..6cc80c5 100644
--- a/ndn-cxx/mgmt/dispatcher.cpp
+++ b/ndn-cxx/mgmt/dispatcher.cpp
@@ -56,7 +56,7 @@
 {
   std::vector<Name> topPrefixNames;
   std::transform(m_topLevelPrefixes.begin(), m_topLevelPrefixes.end(), std::back_inserter(topPrefixNames),
-                 [] (const auto& entry) { return entry.second.topPrefix; });
+                 [] (const auto& entry) { return entry.first; });
 
   for (const auto& name : topPrefixNames) {
     removeTopPrefix(name);
@@ -76,10 +76,9 @@
   }
 
   TopPrefixEntry& topPrefixEntry = m_topLevelPrefixes[prefix];
-  topPrefixEntry.topPrefix = prefix;
 
   if (wantRegister) {
-    topPrefixEntry.registeredPrefixId = m_face.registerPrefix(prefix,
+    topPrefixEntry.registeredPrefix = m_face.registerPrefix(prefix,
       nullptr,
       [] (const Name&, const std::string& reason) {
         BOOST_THROW_EXCEPTION(std::runtime_error("prefix registration failed: " + reason));
@@ -89,28 +88,15 @@
 
   for (const auto& entry : m_handlers) {
     Name fullPrefix = Name(prefix).append(entry.first);
-    const auto* filterId = m_face.setInterestFilter(fullPrefix, bind(entry.second, prefix, _2));
-    topPrefixEntry.interestFilters.push_back(filterId);
+    auto filterHdl = m_face.setInterestFilter(fullPrefix, bind(entry.second, prefix, _2));
+    topPrefixEntry.interestFilters.push_back(filterHdl);
   }
 }
 
 void
 Dispatcher::removeTopPrefix(const Name& prefix)
 {
-  auto it = m_topLevelPrefixes.find(prefix);
-  if (it == m_topLevelPrefixes.end()) {
-    return;
-  }
-
-  const TopPrefixEntry& topPrefixEntry = it->second;
-  if (topPrefixEntry.registeredPrefixId != nullptr) {
-    m_face.unregisterPrefix(topPrefixEntry.registeredPrefixId, nullptr, nullptr);
-  }
-  for (const auto& filter : topPrefixEntry.interestFilters) {
-    m_face.unsetInterestFilter(filter);
-  }
-
-  m_topLevelPrefixes.erase(it);
+  m_topLevelPrefixes.erase(prefix);
 }
 
 bool
@@ -348,7 +334,7 @@
     return;
   }
 
-  Name streamName(m_topLevelPrefixes.begin()->second.topPrefix);
+  Name streamName(m_topLevelPrefixes.begin()->first);
   streamName.append(relPrefix);
   streamName.appendSequenceNumber(m_streams[streamName]++);
 
diff --git a/ndn-cxx/mgmt/dispatcher.hpp b/ndn-cxx/mgmt/dispatcher.hpp
index ab96c00..a3a2325 100644
--- a/ndn-cxx/mgmt/dispatcher.hpp
+++ b/ndn-cxx/mgmt/dispatcher.hpp
@@ -439,9 +439,8 @@
 private:
   struct TopPrefixEntry
   {
-    Name topPrefix;
-    const RegisteredPrefixId* registeredPrefixId = nullptr;
-    std::vector<const InterestFilterId*> interestFilters;
+    ScopedRegisteredPrefixHandle registeredPrefix;
+    std::vector<ScopedInterestFilterHandle> interestFilters;
   };
   std::unordered_map<Name, TopPrefixEntry> m_topLevelPrefixes;
 
diff --git a/tests/unit/face.t.cpp b/tests/unit/face.t.cpp
index c9b8205..7f4ba07 100644
--- a/tests/unit/face.t.cpp
+++ b/tests/unit/face.t.cpp
@@ -651,6 +651,7 @@
     this->advanceClocks(5_s, 20); // wait for command timeout
   }));
 }
+
 BOOST_AUTO_TEST_CASE(RegisterUnregisterPrefixHandle)
 {
   RegisteredPrefixHandle hdl;
@@ -777,6 +778,24 @@
   BOOST_CHECK_EQUAL(hit, 1);
 }
 
+BOOST_AUTO_TEST_CASE(SetInterestFilterHandle)
+{
+  int hit = 0;
+  auto hdl = face.setInterestFilter(Name("/"), bind([&hit] { ++hit; }));
+  face.processEvents(-1_ms);
+
+  face.receive(*makeInterest("/A"));
+  face.processEvents(-1_ms);
+  BOOST_CHECK_EQUAL(hit, 1);
+
+  hdl.cancel();
+  face.processEvents(-1_ms);
+
+  face.receive(*makeInterest("/B"));
+  face.processEvents(-1_ms);
+  BOOST_CHECK_EQUAL(hit, 1);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // Producer
 
 BOOST_AUTO_TEST_SUITE(IoRoutines)