face: Implementing InterestFilter abstraction to be used with setInterestFilter

This commit minimally changes the API, primarily altering the internal
structures preparing for separation of `registerPrefix` (=send a command
to local forwarder to register FIB/RIB entry) and `setInterestFilter`
(=update library's InterestFilter->Callback dispatch table).

The existing setInterestFilter methods preserve all previous functionality
(any string URI or ndn::Name can be supplied as a first parameter),
but also allow InterestFilter as the filtering parameter.
InterestFilter, provides a way to select Interest either based on prefix,
as before, or based on prefix and regular expression.

Change-Id: Id71404f2163f82c261018d21db172111c4b0da69
Refs: #1275
diff --git a/src/detail/registered-prefix.hpp b/src/detail/registered-prefix.hpp
index 3a17785..8957d5e 100644
--- a/src/detail/registered-prefix.hpp
+++ b/src/detail/registered-prefix.hpp
@@ -19,47 +19,91 @@
 
 namespace ndn {
 
+class InterestFilterRecord
+{
+public:
+  typedef function<void (const InterestFilter&, const Interest&)> OnInterest;
+
+  InterestFilterRecord(const InterestFilter& filter, const OnInterest& onInterest)
+    : m_filter(filter)
+    , m_onInterest(onInterest)
+  {
+  }
+
+  /**
+   * @brief Check if Interest name matches the filter
+   * @param name Interest Name
+   */
+  bool
+  doesMatch(const Name& name) const
+  {
+    return m_filter.doesMatch(name);
+  }
+
+  void
+  operator()(const Interest& interest) const
+  {
+    m_onInterest(m_filter, interest);
+  }
+
+  const InterestFilter&
+  getFilter() const
+  {
+    return m_filter;
+  }
+
+private:
+  InterestFilter m_filter;
+  OnInterest m_onInterest;
+};
+
+
 class RegisteredPrefix
 {
 public:
-  typedef function<void (const Name&, const Interest&)> OnInterest;
+  explicit
+  RegisteredPrefix(const Name& prefix)
+    : m_prefix(prefix)
+  {
+  }
 
-  /**
-   * Create a new PrefixEntry.
-   * @param prefix A shared_ptr for the prefix.
-   * @param onInterest A function object to call when a matching data packet is received.
-   */
-  RegisteredPrefix(const Name& prefix, const OnInterest& onInterest)
-    : m_prefix(new Name(prefix))
-    , m_onInterest(onInterest)
+  RegisteredPrefix(const Name& prefix, shared_ptr<InterestFilterRecord> filter)
+    : m_prefix(prefix)
+    , m_filter(filter)
   {
   }
 
   const Name&
   getPrefix() const
   {
-    return* m_prefix;
+    return m_prefix;
   }
 
-  const OnInterest&
-  getOnInterest() const
+  const shared_ptr<InterestFilterRecord>&
+  getFilter() const
   {
-    return m_onInterest;
+    return m_filter;
   }
 
 private:
-  shared_ptr<Name> m_prefix;
-  const OnInterest m_onInterest;
+  Name m_prefix;
+
+  // to support old interface of combined (un)setInterestFilter
+  shared_ptr<InterestFilterRecord> m_filter;
 };
 
-
-struct RegisteredPrefixId;
+/**
+ * @brief Opaque class representing ID of the registered prefix
+ */
+class RegisteredPrefixId;
 
 /**
- * @brief Functor to match pending interests against PendingInterestId
+ * @brief Functor to match RegisteredPrefixId
  */
-struct MatchRegisteredPrefixId
+class MatchRegisteredPrefixId
 {
+public:
+  explicit
   MatchRegisteredPrefixId(const RegisteredPrefixId* registeredPrefixId)
     : m_id(registeredPrefixId)
   {
@@ -74,6 +118,33 @@
   const RegisteredPrefixId* m_id;
 };
 
+
+/**
+ * @brief Opaque class representing ID of the Interest filter
+ */
+class InterestFilterId;
+
+/**
+ * @brief Functor to match InterestFilterId
+ */
+class MatchInterestFilterId
+{
+public:
+  explicit
+  MatchInterestFilterId(const InterestFilterId* interestFilterId)
+    : m_id(interestFilterId)
+  {
+  }
+
+  bool
+  operator()(const shared_ptr<InterestFilter>& interestFilterId) const
+  {
+    return (reinterpret_cast<const InterestFilterId*>(interestFilterId.get()) == m_id);
+  }
+private:
+  const InterestFilterId* m_id;
+};
+
 } // namespace ndn
 
 #endif // NDN_DETAIL_REGISTERED_PREFIX_HPP
diff --git a/src/face.cpp b/src/face.cpp
index 44de6cc..2041a7b 100644
--- a/src/face.cpp
+++ b/src/face.cpp
@@ -219,21 +219,18 @@
   m_pendingInterestTable.remove_if(MatchPendingInterestId(pendingInterestId));
 }
 
-void
-Face::finalizeSetInterestFilter(const shared_ptr<RegisteredPrefix>& registeredPrefix)
-{
-  m_registeredPrefixTable.push_back(registeredPrefix);
-}
-
 const RegisteredPrefixId*
-Face::setInterestFilter(const Name& prefix,
+Face::setInterestFilter(const InterestFilter& interestFilter,
                         const OnInterest& onInterest,
                         const OnSetInterestFilterFailed& onSetInterestFilterFailed)
 {
-  shared_ptr<RegisteredPrefix> prefixToRegister(new RegisteredPrefix(prefix, onInterest));
+  shared_ptr<InterestFilterRecord> filter =
+    make_shared<InterestFilterRecord>(cref(interestFilter), onInterest);
+  shared_ptr<RegisteredPrefix> prefixToRegister =
+    make_shared<RegisteredPrefix>(cref(interestFilter.getPrefix()), filter);
 
   m_fwController->selfRegisterPrefix(prefixToRegister->getPrefix(),
-                                     bind(&Face::finalizeSetInterestFilter, this, prefixToRegister),
+                                     bind(&Face::afterPrefixRegistered, this, prefixToRegister),
                                      bind(onSetInterestFilterFailed,
                                           prefixToRegister->getPrefix(), _1));
 
@@ -241,15 +238,18 @@
 }
 
 const RegisteredPrefixId*
-Face::setInterestFilter(const Name& prefix,
+Face::setInterestFilter(const InterestFilter& interestFilter,
                         const OnInterest& onInterest,
                         const OnSetInterestFilterFailed& onSetInterestFilterFailed,
                         const IdentityCertificate& certificate)
 {
-  shared_ptr<RegisteredPrefix> prefixToRegister(new RegisteredPrefix(prefix, onInterest));
+  shared_ptr<InterestFilterRecord> filter =
+    make_shared<InterestFilterRecord>(cref(interestFilter), onInterest);
+  shared_ptr<RegisteredPrefix> prefixToRegister =
+    make_shared<RegisteredPrefix>(cref(interestFilter.getPrefix()), filter);
 
   m_fwController->selfRegisterPrefix(prefixToRegister->getPrefix(),
-                                     bind(&Face::finalizeSetInterestFilter, this, prefixToRegister),
+                                     bind(&Face::afterPrefixRegistered, this, prefixToRegister),
                                      bind(onSetInterestFilterFailed,
                                           prefixToRegister->getPrefix(), _1),
                                      certificate);
@@ -258,15 +258,19 @@
 }
 
 const RegisteredPrefixId*
-Face::setInterestFilter(const Name& prefix,
+Face::setInterestFilter(const InterestFilter& interestFilter,
                         const OnInterest& onInterest,
                         const OnSetInterestFilterFailed& onSetInterestFilterFailed,
                         const Name& identity)
 {
-  shared_ptr<RegisteredPrefix> prefixToRegister(new RegisteredPrefix(prefix, onInterest));
+  // without ptr_lib:: here, reference to cref becomes ambiguous on OSX 10.9
+  shared_ptr<InterestFilterRecord> filter =
+    make_shared<InterestFilterRecord>(cref(interestFilter), onInterest);
+  shared_ptr<RegisteredPrefix> prefixToRegister =
+    make_shared<RegisteredPrefix>(cref(interestFilter.getPrefix()), filter);
 
   m_fwController->selfRegisterPrefix(prefixToRegister->getPrefix(),
-                                     bind(&Face::finalizeSetInterestFilter, this, prefixToRegister),
+                                     bind(&Face::afterPrefixRegistered, this, prefixToRegister),
                                      bind(onSetInterestFilterFailed,
                                           prefixToRegister->getPrefix(), _1),
                                      identity);
@@ -275,6 +279,18 @@
 }
 
 void
+Face::afterPrefixRegistered(const shared_ptr<RegisteredPrefix>& registeredPrefix)
+{
+  m_registeredPrefixTable.push_back(registeredPrefix);
+
+  if (static_cast<bool>(registeredPrefix->getFilter()))
+    {
+      // it was a combined operation
+      m_interestFilterTable.push_back(registeredPrefix->getFilter());
+    }
+}
+
+void
 Face::unsetInterestFilter(const RegisteredPrefixId* registeredPrefixId)
 {
   m_ioService->post(bind(&Face::asyncUnsetInterestFilter, this, registeredPrefixId));
@@ -304,8 +320,15 @@
                                                    MatchRegisteredPrefixId(registeredPrefixId));
   if (i != m_registeredPrefixTable.end())
     {
+      const shared_ptr<InterestFilterRecord>& filter = (*i)->getFilter();
+      if (static_cast<bool>(filter))
+        {
+          // it was a combined operation
+          m_interestFilterTable.remove(filter);
+        }
+
       m_fwController->selfDeregisterPrefix((*i)->getPrefix(),
-                                           bind(&Face::finalizeUnsetInterestFilter, this, i),
+                                           bind(&Face::finalizeUnregisterPrefix, this, i),
                                            Controller::FailCallback());
     }
 
@@ -321,8 +344,15 @@
                                                    MatchRegisteredPrefixId(registeredPrefixId));
   if (i != m_registeredPrefixTable.end())
     {
+      const shared_ptr<InterestFilterRecord>& filter = (*i)->getFilter();
+      if (static_cast<bool>(filter))
+        {
+          // it was a combined operation
+          m_interestFilterTable.remove(filter);
+        }
+
       m_fwController->selfDeregisterPrefix((*i)->getPrefix(),
-                                           bind(&Face::finalizeUnsetInterestFilter, this, i),
+                                           bind(&Face::finalizeUnregisterPrefix, this, i),
                                            Controller::FailCallback(),
                                            certificate);
     }
@@ -339,8 +369,15 @@
                                                    MatchRegisteredPrefixId(registeredPrefixId));
   if (i != m_registeredPrefixTable.end())
     {
+      const shared_ptr<InterestFilterRecord>& filter = (*i)->getFilter();
+      if (static_cast<bool>(filter))
+        {
+          // it was a combined operation
+          m_interestFilterTable.remove(filter);
+        }
+
       m_fwController->selfDeregisterPrefix((*i)->getPrefix(),
-                                           bind(&Face::finalizeUnsetInterestFilter, this, i),
+                                           bind(&Face::finalizeUnregisterPrefix, this, i),
                                            Controller::FailCallback(),
                                            identity);
     }
@@ -349,7 +386,7 @@
 }
 
 void
-Face::finalizeUnsetInterestFilter(RegisteredPrefixTable::iterator item)
+Face::finalizeUnregisterPrefix(RegisteredPrefixTable::iterator item)
 {
   m_registeredPrefixTable.erase(item);
 
@@ -529,13 +566,13 @@
 void
 Face::processInterestFilters(Interest& interest)
 {
-  for (RegisteredPrefixTable::iterator i = m_registeredPrefixTable.begin();
-       i != m_registeredPrefixTable.end();
+  for (InterestFilterTable::iterator i = m_interestFilterTable.begin();
+       i != m_interestFilterTable.end();
        ++i)
     {
-      if ((*i)->getPrefix().isPrefixOf(interest.getName()))
+      if ((*i)->doesMatch(interest.getName()))
         {
-          (*i)->getOnInterest()((*i)->getPrefix(), interest);
+          (**i)(interest);
         }
     }
 }
diff --git a/src/face.hpp b/src/face.hpp
index d0efc03..1758e27 100644
--- a/src/face.hpp
+++ b/src/face.hpp
@@ -31,9 +31,6 @@
 
 namespace ndn {
 
-struct PendingInterestId;
-struct RegisteredPrefixId;
-
 /**
  * An OnData function object is used to pass a callback to expressInterest.
  */
@@ -47,12 +44,12 @@
 /**
  * An OnInterest function object is used to pass a callback to registerPrefix.
  */
-typedef function<void (const Name&, const Interest&)> OnInterest;
+typedef function<void (const InterestFilter&, const Interest&)> OnInterest;
 
 /**
  * An OnRegisterFailed function object is used to report when registerPrefix fails.
  */
-typedef function<void(const Name&, const std::string&)> OnSetInterestFilterFailed;
+typedef function<void(const InterestFilter&, const std::string&)> OnSetInterestFilterFailed;
 
 
 
@@ -203,7 +200,7 @@
    * @brief Register prefix with the connected NDN hub and call onInterest when a matching
    *        interest is received.
    *
-   * @param prefix     A reference to a Name for the prefix to register
+   * @param interestFilter Interest filter (prefix part will be registered with the forwarder)
    * @param onInterest A function object to call when a matching interest is received
    *
    * @param onRegisterFailed A function object to call if failed to retrieve the connected
@@ -214,7 +211,7 @@
    * @return The registered prefix ID which can be used with removeRegisteredPrefix.
    */
   const RegisteredPrefixId*
-  setInterestFilter(const Name& prefix,
+  setInterestFilter(const InterestFilter& interestFilter,
                     const OnInterest& onInterest,
                     const OnSetInterestFilterFailed& onSetInterestFilterFailed);
 
@@ -222,7 +219,7 @@
    * @brief Register prefix with the connected NDN hub and call onInterest when a matching
    *        interest is received.
    *
-   * @param prefix     A reference to a Name for the prefix to register
+   * @param interestFilter Interest filter (prefix part will be registered with the forwarder)
    * @param onInterest A function object to call when a matching interest is received
    *
    * @param onRegisterFailed A function object to call if failed to retrieve the connected
@@ -236,7 +233,7 @@
    * @return The registered prefix ID which can be used with removeRegisteredPrefix.
    */
   const RegisteredPrefixId*
-  setInterestFilter(const Name& prefix,
+  setInterestFilter(const InterestFilter& interestFilter,
                     const OnInterest& onInterest,
                     const OnSetInterestFilterFailed& onSetInterestFilterFailed,
                     const IdentityCertificate& certificate);
@@ -245,7 +242,7 @@
    * @brief Register prefix with the connected NDN hub and call onInterest when a matching
    *        interest is received.
    *
-   * @param prefix     A reference to a Name for the prefix to register
+   * @param interestFilter Interest filter (prefix part will be registered with the forwarder)
    * @param onInterest A function object to call when a matching interest is received
    *
    * @param onRegisterFailed A function object to call if failed to retrieve the connected
@@ -259,7 +256,7 @@
    * @return The registered prefix ID which can be used with removeRegisteredPrefix.
    */
   const RegisteredPrefixId*
-  setInterestFilter(const Name& prefix,
+  setInterestFilter(const InterestFilter& interestFilter,
                     const OnInterest& onInterest,
                     const OnSetInterestFilterFailed& onSetInterestFilterFailed,
                     const Name& identity);
@@ -373,6 +370,7 @@
   };
 
   typedef std::list<shared_ptr<PendingInterest> > PendingInterestTable;
+  typedef std::list<shared_ptr<InterestFilterRecord> > InterestFilterTable;
   typedef std::list<shared_ptr<RegisteredPrefix> > RegisteredPrefixTable;
 
   void
@@ -383,6 +381,9 @@
   asyncRemovePendingInterest(const PendingInterestId* pendingInterestId);
 
   void
+  afterPrefixRegistered(const shared_ptr<RegisteredPrefix>& registeredPrefix);
+
+  void
   asyncUnsetInterestFilter(const RegisteredPrefixId* registeredPrefixId);
 
   void
@@ -394,10 +395,7 @@
                                        const Name& identity);
 
   void
-  finalizeSetInterestFilter(const shared_ptr<RegisteredPrefix>& registeredPrefix);
-
-  void
-  finalizeUnsetInterestFilter(RegisteredPrefixTable::iterator item);
+  finalizeUnregisterPrefix(RegisteredPrefixTable::iterator item);
 
   void
   onReceiveElement(const Block& wire);
@@ -427,6 +425,7 @@
   shared_ptr<Transport> m_transport;
 
   PendingInterestTable m_pendingInterestTable;
+  InterestFilterTable m_interestFilterTable;
   RegisteredPrefixTable m_registeredPrefixTable;
 
   shared_ptr<Controller> m_fwController;
diff --git a/src/interest-filter.hpp b/src/interest-filter.hpp
new file mode 100644
index 0000000..4cb77af
--- /dev/null
+++ b/src/interest-filter.hpp
@@ -0,0 +1,181 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (c) 2013-2014,  Regents of the University of California.
+ * All rights reserved.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * This file licensed under New BSD License.  See COPYING for detailed information about
+ * ndn-cxx library copyright, permissions, and redistribution restrictions.
+ */
+
+#ifndef NDN_INTEREST_FILTER_HPP
+#define NDN_INTEREST_FILTER_HPP
+
+#include "util/regex/regex-pattern-list-matcher.hpp"
+#include "name.hpp"
+
+namespace ndn {
+
+class InterestFilter
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+  /**
+   * @brief Create an Interest filter to match Interests by prefix
+   *
+   * This filter will match all Interests, whose name start with the given prefix
+   *
+   * Any Name can be implicitly converted to the InterestFilter.
+   */
+  InterestFilter(const Name& prefix);
+
+  /**
+   * @brief Create an Interest filter to match Interests by prefix URI
+   *
+   * This filter will match all Interests, whose name start with the given prefix
+   *
+   * Any const char* can be implicitly converted to the InterestFilter.
+   */
+  InterestFilter(const char* prefixUri);
+
+  /**
+   * @brief Create an Interest filter to match Interests by prefix URI
+   *
+   * This filter will match all Interests, whose name start with the given prefix
+   *
+   * Any std::string can be implicitly converted to the InterestFilter.
+   */
+  InterestFilter(const std::string& prefixUri);
+
+  /**
+   * @brief Create an Interest filter to match Interest by prefix and regular expression
+   *
+   * This filter will match all Interests, whose name start with the given prefix and
+   * other components of the Interest name match the given regular expression.
+   * For example, the following InterestFilter:
+   *
+   *    InterestFilter("/hello", "<world><>+")
+   *
+   * will match all Interests, whose name has prefix `/hello`, which is followed by
+   * component `world` and has at least one more component after it.  Examples:
+   *
+   *    /hello/world/!
+   *    /hello/world/x/y/z
+   *
+   * Note that regular expression will need to match all components (e.g., there is
+   * an implicit heading `^` and trailing `$` symbols in the regular expression).
+   */
+  InterestFilter(const Name& prefix, const std::string& regexFilter);
+
+  /**
+   * @brief Implicit conversion to Name (to provide backwards compatibility for onInterest callback)
+   */
+  operator const Name&() const
+  {
+    if (static_cast<bool>(m_regexFilter)) {
+      throw Error("Please update OnInterest callback to accept const InterestFilter& "
+                  "(non-trivial Interest filter is being used)");
+    }
+    return m_prefix;
+  }
+
+  /**
+   * @brief Check if specified name matches the filter
+   */
+  bool
+  doesMatch(const Name& name) const;
+
+  const Name&
+  getPrefix() const
+  {
+    return m_prefix;
+  }
+
+  bool
+  hasRegexFilter() const
+  {
+    return static_cast<bool>(m_regexFilter);
+  }
+
+  const RegexPatternListMatcher&
+  getRegexFilter() const
+  {
+    return *m_regexFilter;
+  }
+
+private:
+  Name m_prefix;
+  shared_ptr<RegexPatternListMatcher> m_regexFilter;
+};
+
+inline
+InterestFilter::InterestFilter(const Name& prefix)
+  : m_prefix(prefix)
+{
+}
+
+inline
+InterestFilter::InterestFilter(const char* prefixUri)
+  : m_prefix(prefixUri)
+{
+}
+
+inline
+InterestFilter::InterestFilter(const std::string& prefixUri)
+  : m_prefix(prefixUri)
+{
+}
+
+inline
+InterestFilter::InterestFilter(const Name& prefix, const std::string& regexFilter)
+  : m_prefix(prefix)
+  , m_regexFilter(new RegexPatternListMatcher(regexFilter, shared_ptr<RegexBackrefManager>()))
+{
+}
+
+inline bool
+InterestFilter::doesMatch(const Name& name) const
+{
+  if (name.size() < m_prefix.size())
+    return false;
+
+  if (hasRegexFilter()) {
+    // perform prefix match and regular expression match for the remaining components
+    bool isMatch = m_prefix.isPrefixOf(name);
+
+    if (!isMatch)
+      return false;
+
+    return m_regexFilter->match(name, m_prefix.size(), name.size() - m_prefix.size());
+  }
+  else {
+    // perform just prefix match
+
+    return m_prefix.isPrefixOf(name);
+  }
+}
+
+inline std::ostream&
+operator<<(std::ostream& os, const InterestFilter& filter)
+{
+  os << filter.getPrefix();
+  if (filter.hasRegexFilter()) {
+    os << "?regex=" << filter.getRegexFilter();
+  }
+  return os;
+}
+
+} // namespace ndn
+
+#endif // NDN_INTEREST_FILTER_HPP
diff --git a/src/interest.hpp b/src/interest.hpp
index 0e07f81..d67579e 100644
--- a/src/interest.hpp
+++ b/src/interest.hpp
@@ -18,6 +18,7 @@
 #include "common.hpp"
 #include "name.hpp"
 #include "selectors.hpp"
+#include "interest-filter.hpp"
 #include "management/nfd-local-control-header.hpp"
 
 namespace ndn {
diff --git a/src/util/regex/regex-matcher.hpp b/src/util/regex/regex-matcher.hpp
index 88cefe0..efe906f 100644
--- a/src/util/regex/regex-matcher.hpp
+++ b/src/util/regex/regex-matcher.hpp
@@ -92,6 +92,13 @@
   std::vector<name::Component> m_matchResult;
 };
 
+inline std::ostream&
+operator<<(std::ostream& os, const RegexMatcher& regex)
+{
+  os << regex.getExpr();
+  return os;
+}
+
 } // namespace ndn
 
 #include "regex-backref-manager.hpp"
diff --git a/tests-integrated/test-faces.cpp b/tests-integrated/test-faces.cpp
index c98df6f..8d37d01 100644
--- a/tests-integrated/test-faces.cpp
+++ b/tests-integrated/test-faces.cpp
@@ -17,52 +17,68 @@
 
 namespace ndn {
 
-BOOST_AUTO_TEST_SUITE(TestFaces)
 
-struct FacesFixture
+class FacesFixture
 {
+public:
   FacesFixture()
-    : dataCount(0)
-    , timeoutCount(0)
+    : nData(0)
+    , nTimeouts(0)
     , regPrefixId(0)
-    , inInterestCount(0)
-    , inInterestCount2(0)
-    , regFailedCount(0)
+    , nInInterests(0)
+    , nInInterests2(0)
+    , nRegFailures(0)
   {
   }
 
   void
   onData()
   {
-    ++dataCount;
+    ++nData;
   }
 
   void
   onTimeout()
   {
-    ++timeoutCount;
+    ++nTimeouts;
   }
 
   void
-  onInterest(Face& face)
+  onInterest(Face& face,
+             const Name&, const Interest&)
   {
-    ++inInterestCount;
+    ++nInInterests;
 
     face.unsetInterestFilter(regPrefixId);
   }
 
   void
-  onInterest2(Face& face)
+  onInterest2(Face& face,
+              const Name&, const Interest&)
   {
-    ++inInterestCount2;
+    ++nInInterests2;
 
     face.unsetInterestFilter(regPrefixId2);
   }
 
   void
+  onInterestRegex(Face& face,
+                  const InterestFilter&, const Interest&)
+  {
+    ++nInInterests;
+  }
+
+  void
+  onInterestRegexError(Face& face,
+                       const Name&, const Interest&)
+  {
+    BOOST_FAIL("InterestFilter::Error should have been triggered");
+  }
+
+  void
   onRegFailed()
   {
-    ++regFailedCount;
+    ++nRegFailures;
   }
 
   void
@@ -81,17 +97,19 @@
     face.shutdown();
   }
 
-  uint32_t dataCount;
-  uint32_t timeoutCount;
+  uint32_t nData;
+  uint32_t nTimeouts;
 
   const RegisteredPrefixId* regPrefixId;
   const RegisteredPrefixId* regPrefixId2;
-  uint32_t inInterestCount;
-  uint32_t inInterestCount2;
-  uint32_t regFailedCount;
+  uint32_t nInInterests;
+  uint32_t nInInterests2;
+  uint32_t nRegFailures;
 };
 
-BOOST_FIXTURE_TEST_CASE(Unix, FacesFixture)
+BOOST_FIXTURE_TEST_SUITE(TestFaces, FacesFixture)
+
+BOOST_AUTO_TEST_CASE(Unix)
 {
   Face face;
 
@@ -101,8 +119,8 @@
 
   BOOST_REQUIRE_NO_THROW(face.processEvents());
 
-  BOOST_CHECK_EQUAL(dataCount, 1);
-  BOOST_CHECK_EQUAL(timeoutCount, 0);
+  BOOST_CHECK_EQUAL(nData, 1);
+  BOOST_CHECK_EQUAL(nTimeouts, 0);
 
   face.expressInterest(Interest("/localhost/non-existing/data/should/not/exist/anywhere",
                                 time::milliseconds(50)),
@@ -111,11 +129,11 @@
 
   BOOST_REQUIRE_NO_THROW(face.processEvents());
 
-  BOOST_CHECK_EQUAL(dataCount, 1);
-  BOOST_CHECK_EQUAL(timeoutCount, 1);
+  BOOST_CHECK_EQUAL(nData, 1);
+  BOOST_CHECK_EQUAL(nTimeouts, 1);
 }
 
-BOOST_FIXTURE_TEST_CASE(Tcp, FacesFixture)
+BOOST_AUTO_TEST_CASE(Tcp)
 {
   Face face("localhost");
 
@@ -125,8 +143,8 @@
 
   BOOST_REQUIRE_NO_THROW(face.processEvents());
 
-  BOOST_CHECK_EQUAL(dataCount, 1);
-  BOOST_CHECK_EQUAL(timeoutCount, 0);
+  BOOST_CHECK_EQUAL(nData, 1);
+  BOOST_CHECK_EQUAL(nTimeouts, 0);
 
   face.expressInterest(Interest("/localhost/non-existing/data/should/not/exist/anywhere",
                                 time::milliseconds(50)),
@@ -135,12 +153,12 @@
 
   BOOST_REQUIRE_NO_THROW(face.processEvents());
 
-  BOOST_CHECK_EQUAL(dataCount, 1);
-  BOOST_CHECK_EQUAL(timeoutCount, 1);
+  BOOST_CHECK_EQUAL(nData, 1);
+  BOOST_CHECK_EQUAL(nTimeouts, 1);
 }
 
 
-BOOST_FIXTURE_TEST_CASE(SetFilter, FacesFixture)
+BOOST_AUTO_TEST_CASE(SetFilter)
 {
   Face face;
   Face face2(face.ioService());
@@ -149,7 +167,7 @@
                           bind(&FacesFixture::terminate, this, ref(face)));
 
   regPrefixId = face.setInterestFilter("/Hello/World",
-                                       bind(&FacesFixture::onInterest, this, ref(face)),
+                                       bind(&FacesFixture::onInterest, this, ref(face), _1, _2),
                                        bind(&FacesFixture::onRegFailed, this));
 
   scheduler.scheduleEvent(time::milliseconds(200),
@@ -158,13 +176,13 @@
 
   BOOST_REQUIRE_NO_THROW(face.processEvents());
 
-  BOOST_CHECK_EQUAL(regFailedCount, 0);
-  BOOST_CHECK_EQUAL(inInterestCount, 1);
-  BOOST_CHECK_EQUAL(timeoutCount, 1);
-  BOOST_CHECK_EQUAL(dataCount, 0);
+  BOOST_CHECK_EQUAL(nRegFailures, 0);
+  BOOST_CHECK_EQUAL(nInInterests, 1);
+  BOOST_CHECK_EQUAL(nTimeouts, 1);
+  BOOST_CHECK_EQUAL(nData, 0);
 }
 
-BOOST_FIXTURE_TEST_CASE(SetTwoFilters, FacesFixture)
+BOOST_AUTO_TEST_CASE(SetTwoFilters)
 {
   Face face;
   Face face2(face.ioService());
@@ -173,12 +191,12 @@
                           bind(&FacesFixture::terminate, this, ref(face)));
 
   regPrefixId = face.setInterestFilter("/Hello/World",
-                                       bind(&FacesFixture::onInterest, this, ref(face)),
+                                       bind(&FacesFixture::onInterest, this, ref(face), _1, _2),
                                        bind(&FacesFixture::onRegFailed, this));
 
   regPrefixId2 = face.setInterestFilter("/Los/Angeles/Lakers",
-                                       bind(&FacesFixture::onInterest2, this, ref(face)),
-                                       bind(&FacesFixture::onRegFailed, this));
+                                        bind(&FacesFixture::onInterest2, this, ref(face), _1, _2),
+                                        bind(&FacesFixture::onRegFailed, this));
 
 
   scheduler.scheduleEvent(time::milliseconds(200),
@@ -187,13 +205,75 @@
 
   BOOST_REQUIRE_NO_THROW(face.processEvents());
 
-  BOOST_CHECK_EQUAL(regFailedCount, 0);
-  BOOST_CHECK_EQUAL(inInterestCount, 1);
-  BOOST_CHECK_EQUAL(inInterestCount2, 0);
-  BOOST_CHECK_EQUAL(timeoutCount, 1);
-  BOOST_CHECK_EQUAL(dataCount, 0);
+  BOOST_CHECK_EQUAL(nRegFailures, 0);
+  BOOST_CHECK_EQUAL(nInInterests, 1);
+  BOOST_CHECK_EQUAL(nInInterests2, 0);
+  BOOST_CHECK_EQUAL(nTimeouts, 1);
+  BOOST_CHECK_EQUAL(nData, 0);
 }
 
+BOOST_AUTO_TEST_CASE(SetRegexFilterError)
+{
+  Face face;
+  Face face2(face.getIoService());
+  Scheduler scheduler(*face.ioService());
+  scheduler.scheduleEvent(time::seconds(1),
+                          bind(&FacesFixture::terminate, this, ref(face)));
+
+  regPrefixId = face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
+                                       bind(&FacesFixture::onInterestRegexError, this,
+                                            ref(face), _1, _2),
+                                       bind(&FacesFixture::onRegFailed, this));
+
+  scheduler.scheduleEvent(time::milliseconds(300),
+                          bind(&FacesFixture::expressInterest, this,
+                               ref(face2), Name("/Hello/World/XXX/b/c"))); // should match
+
+  BOOST_REQUIRE_THROW(face.processEvents(), InterestFilter::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SetRegexFilter)
+{
+  Face face;
+  Face face2(face.getIoService());
+  Scheduler scheduler(*face.ioService());
+  scheduler.scheduleEvent(time::seconds(1),
+                          bind(&FacesFixture::terminate, this, ref(face)));
+
+  scheduler.scheduleEvent(time::seconds(2),
+                          bind(&FacesFixture::terminate, this, ref(face)));
+
+  regPrefixId = face.setInterestFilter(InterestFilter("/Hello/World", "<><b><c>?"),
+                                       bind(&FacesFixture::onInterestRegex, this,
+                                            ref(face), _1, _2),
+                                       bind(&FacesFixture::onRegFailed, this));
+
+  scheduler.scheduleEvent(time::milliseconds(200),
+                          bind(&FacesFixture::expressInterest, this,
+                               ref(face2), Name("/Hello/World/a"))); // shouldn't match
+
+  scheduler.scheduleEvent(time::milliseconds(300),
+                          bind(&FacesFixture::expressInterest, this,
+                               ref(face2), Name("/Hello/World/a/b"))); // should match
+
+  scheduler.scheduleEvent(time::milliseconds(400),
+                          bind(&FacesFixture::expressInterest, this,
+                               ref(face2), Name("/Hello/World/a/b/c"))); // should match
+
+  scheduler.scheduleEvent(time::milliseconds(500),
+                          bind(&FacesFixture::expressInterest, this,
+                               ref(face2), Name("/Hello/World/a/b/d"))); // should not match
+
+  face.processEvents();
+  // BOOST_REQUIRE_NO_THROW(face.processEvents());
+
+  BOOST_CHECK_EQUAL(nRegFailures, 0);
+  BOOST_CHECK_EQUAL(nInInterests, 2);
+  BOOST_CHECK_EQUAL(nTimeouts, 2);
+  BOOST_CHECK_EQUAL(nData, 0);
+}
+
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace ndn
diff --git a/tests/test-interest.cpp b/tests/test-interest.cpp
index b1c3e69..10f88d6 100644
--- a/tests/test-interest.cpp
+++ b/tests/test-interest.cpp
@@ -17,8 +17,8 @@
 
 #include "boost-test.hpp"
 
-using namespace std;
 namespace ndn {
+namespace tests {
 
 BOOST_AUTO_TEST_SUITE(TestInterest)
 
@@ -430,6 +430,26 @@
   BOOST_CHECK_EQUAL(interest.matchesData(data6), false);
 }
 
+BOOST_AUTO_TEST_CASE(InterestFilterMatching)
+{
+  BOOST_CHECK_EQUAL(InterestFilter("/a").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b/c").doesMatch("/a/b"), false);
+
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b>").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b", "<b>").doesMatch("/a/b"), false);
+
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b", "<b>").doesMatch("/a/b/c/b"), false);
+  BOOST_CHECK_EQUAL(InterestFilter("/a/b", "<>*<b>").doesMatch("/a/b/c/b"), true);
+
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b>").doesMatch("/a/b/c/d"), false);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>*").doesMatch("/a/b/c/d"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>*").doesMatch("/a/b"), true);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>+").doesMatch("/a/b"), false);
+  BOOST_CHECK_EQUAL(InterestFilter("/a", "<b><>+").doesMatch("/a/b/c"), true);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
+} // namespace tests
 } // namespace ndn