face: Invoke NackCallback on all matching Interests

Change-Id: I7fd2b6456f9d3b83dafde4348c90bad9acd500f6
Refs: #3908
diff --git a/src/detail/face-impl.hpp b/src/detail/face-impl.hpp
index 9ce4a69..bb454d1 100644
--- a/src/detail/face-impl.hpp
+++ b/src/detail/face-impl.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -136,7 +136,7 @@
   {
     for (auto entry = m_pendingInterestTable.begin(); entry != m_pendingInterestTable.end(); ) {
       const Interest& pendingInterest = *(*entry)->getInterest();
-      if (pendingInterest == nack.getInterest()) {
+      if (nack.getInterest().matchesInterest(pendingInterest)) {
         shared_ptr<PendingInterest> matchedEntry = *entry;
         entry = m_pendingInterestTable.erase(entry);
         matchedEntry->invokeNackCallback(nack);
diff --git a/src/interest.cpp b/src/interest.cpp
index 7f4579f..fa58a33 100644
--- a/src/interest.cpp
+++ b/src/interest.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -212,6 +212,14 @@
   return true;
 }
 
+bool
+Interest::matchesInterest(const Interest& other) const
+{
+  /// @todo #3162 match Link field
+  return (this->getName() == other.getName() &&
+          this->getSelectors() == other.getSelectors());
+}
+
 template<encoding::Tag TAG>
 size_t
 Interest::wireEncode(EncodingImpl<TAG>& encoder) const
diff --git a/src/interest.hpp b/src/interest.hpp
index 14ba33e..8d3f29b 100644
--- a/src/interest.hpp
+++ b/src/interest.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -119,8 +119,7 @@
   toUri() const;
 
 public: // Link and forwarding hint
-
-   /**
+  /**
    * @brief Check whether the Interest contains a Link object
    * @return True if there is a link object, otherwise false
    */
@@ -210,6 +209,18 @@
   bool
   matchesData(const Data& data) const;
 
+  /**
+   * @brief Check if Interest matches @p other interest
+   *
+   * Interest matches @p other if both have the same name, selectors, and link.  Other fields
+   * (e.g., Nonce) may be different.
+   *
+   * @todo Implement distinguishing interests by link. The current implementation checks only
+   *       name+selectors (Issue #3162).
+   */
+  bool
+  matchesInterest(const Interest& other) const;
+
 public: // Name and guiders
   const Name&
   getName() const
diff --git a/tests/unit-tests/face.t.cpp b/tests/unit-tests/face.t.cpp
index d547e33..0b1c479 100644
--- a/tests/unit-tests/face.t.cpp
+++ b/tests/unit-tests/face.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -95,6 +95,35 @@
   BOOST_CHECK_EQUAL(nTimeouts, 1);
 }
 
+BOOST_AUTO_TEST_CASE(ExpressMultipleInterestData)
+{
+  size_t nData = 0;
+
+  face.expressInterest(Interest("/Hello/World", time::milliseconds(50)),
+                       [&] (const Interest& i, const Data& d) {
+                         ++nData;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  face.expressInterest(Interest("/Hello/World/a", time::milliseconds(50)),
+                       [&] (const Interest& i, const Data& d) {
+                         ++nData;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected Nack"); }),
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(time::milliseconds(40));
+
+  face.receive(*makeData("/Hello/World/a/b"));
+
+  advanceClocks(time::milliseconds(50), 2);
+
+  BOOST_CHECK_EQUAL(nData, 2);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+  BOOST_CHECK_EQUAL(face.sentData.size(), 0);
+}
+
 BOOST_AUTO_TEST_CASE(ExpressInterestEmptyDataCallback)
 {
   face.expressInterest(Interest("/Hello/World"),
@@ -218,6 +247,38 @@
   BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
 }
 
+BOOST_AUTO_TEST_CASE(ExpressMultipleInterestNack)
+{
+  size_t nNacks = 0;
+
+  Interest interest("/Hello/World", time::milliseconds(50));
+  interest.setNonce(1);
+
+  face.expressInterest(interest,
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       [&] (const Interest& i, const lp::Nack& n) {
+                         ++nNacks;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  interest.setNonce(2);
+  face.expressInterest(interest,
+                       bind([] { BOOST_FAIL("Unexpected Data"); }),
+                       [&] (const Interest& i, const lp::Nack& n) {
+                         ++nNacks;
+                       },
+                       bind([] { BOOST_FAIL("Unexpected timeout"); }));
+
+  advanceClocks(time::milliseconds(40));
+
+  face.receive(makeNack(face.sentInterests.at(1), lp::NackReason::DUPLICATE));
+
+  advanceClocks(time::milliseconds(50), 2);
+
+  BOOST_CHECK_EQUAL(nNacks, 2);
+  BOOST_CHECK_EQUAL(face.sentInterests.size(), 2);
+}
+
 BOOST_AUTO_TEST_CASE(ExpressInterestEmptyNackCallback)
 {
   face.expressInterest(Interest("/Hello/World"),
diff --git a/tests/unit-tests/interest.t.cpp b/tests/unit-tests/interest.t.cpp
index 240cc80..969bdbd 100644
--- a/tests/unit-tests/interest.t.cpp
+++ b/tests/unit-tests/interest.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -1098,6 +1098,46 @@
   BOOST_CHECK_EQUAL(interest7b.matchesData(data7), false); // violates implicit digest
 }
 
+BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(MatchesInterest, 1)
+BOOST_AUTO_TEST_CASE(MatchesInterest)
+{
+  Interest interest;
+  interest
+    .setName("ndn:/A")
+    .setMinSuffixComponents(2)
+    .setMaxSuffixComponents(2)
+    .setPublisherPublicKeyLocator(KeyLocator("ndn:/B"))
+    .setExclude(Exclude().excludeAfter(name::Component("J")))
+    .setNonce(10)
+    .setInterestLifetime(time::seconds(5));
+
+  Link link("/A/LINK", {{10, "/test1"}, {20, "/test2"}, {100, "/test3"}});
+  m_keyChain.sign(link);
+  interest.setLink(link.wireEncode());
+
+  Interest other;
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), false);
+
+  other.setName(interest.getName());
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), false);
+
+  // TODO: will match until #3162 implemented
+  other.setSelectors(interest.getSelectors());
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), false);
+
+  other.setLink(interest.getLink().wireEncode());
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+
+  other.setNonce(200);
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+
+  other.setInterestLifetime(time::hours(5));
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+
+  other.setSelectedDelegation(0);
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+}
+
 BOOST_AUTO_TEST_CASE(InterestFilterMatching)
 {
   BOOST_CHECK_EQUAL(InterestFilter("/a").doesMatch("/a/b"), true);