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"