util: handle NACK in NotificationSubscriber

refs #3662

Change-Id: Ia8023668c912e8ba827922f5c31880f903c362f6
diff --git a/tests/unit-tests/util/notification-subscriber.t.cpp b/tests/unit-tests/util/notification-subscriber.t.cpp
index 5037c0a..309afad 100644
--- a/tests/unit-tests/util/notification-subscriber.t.cpp
+++ b/tests/unit-tests/util/notification-subscriber.t.cpp
@@ -29,8 +29,9 @@
 #include "simple-notification.hpp"
 #include "util/dummy-client-face.hpp"
 
-#include "boost-test.hpp"
 #include "../identity-management-time-fixture.hpp"
+#include "../make-interest-data.hpp"
+#include "boost-test.hpp"
 
 namespace ndn {
 namespace util {
@@ -69,6 +70,15 @@
     subscriberFace.receive(data);
   }
 
+  /** \brief deliver a Nack to subscriber
+   */
+  void
+  deliverNack(const Interest& interest, const lp::NackReason& reason)
+  {
+    lp::Nack nack = makeNack(interest, reason);
+    subscriberFace.receive(nack);
+  }
+
   void
   afterNotification(const SimpleNotification& notification)
   {
@@ -76,6 +86,12 @@
   }
 
   void
+  afterNack(const lp::Nack& nack)
+  {
+    lastNack = nack;
+  }
+
+  void
   afterTimeout()
   {
     hasTimeout = true;
@@ -92,6 +108,8 @@
   {
     notificationConn = subscriber.onNotification.connect(
       bind(&NotificationSubscriberFixture::afterNotification, this, _1));
+    nackConn = subscriber.onNack.connect(
+      bind(&NotificationSubscriberFixture::afterNack, this, _1));
     subscriber.onTimeout.connect(
       bind(&NotificationSubscriberFixture::afterTimeout, this));
     subscriber.onDecodeError.connect(
@@ -102,6 +120,7 @@
   disconnectHandlers()
   {
     notificationConn.disconnect();
+    nackConn.disconnect();
   }
 
   /** \return true if subscriberFace has an initial request (first sent Interest)
@@ -143,9 +162,11 @@
   DummyClientFace subscriberFace;
   util::NotificationSubscriber<SimpleNotification> subscriber;
   util::signal::Connection notificationConn;
+  util::signal::Connection nackConn;
   uint64_t nextSendNotificationNo;
   uint64_t lastDeliveredSeqNo;
   SimpleNotification lastNotification;
+  lp::Nack lastNack;
   bool hasTimeout;
   Data lastDecodeErrorData;
 };
@@ -194,6 +215,50 @@
   BOOST_CHECK_EQUAL(this->getRequestSeqNo(), lastDeliveredSeqNo + 1);
 }
 
+BOOST_AUTO_TEST_CASE(Nack)
+{
+  this->connectHandlers();
+  subscriber.start();
+  advanceClocks(time::milliseconds(1));
+
+  // send the first Nack to initial request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  Interest interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(lastNack.getReason(), lp::NackReason::CONGESTION);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(time::milliseconds(300));
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+
+  // send the second Nack to initial request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(time::milliseconds(301));
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(time::milliseconds(200));
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+
+  // send a notification to initial request
+  subscriberFace.sentInterests.clear();
+  this->deliverNotification("n1");
+  advanceClocks(time::milliseconds(1));
+
+  // send a Nack to subsequent request
+  BOOST_REQUIRE_EQUAL(subscriberFace.sentInterests.size(), 1);
+  interest = subscriberFace.sentInterests[0];
+  subscriberFace.sentInterests.clear();
+  this->deliverNack(interest, lp::NackReason::CONGESTION);
+  advanceClocks(time::milliseconds(1));
+  BOOST_CHECK_EQUAL(lastNack.getReason(), lp::NackReason::CONGESTION);
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), false);
+  advanceClocks(time::milliseconds(300));
+  BOOST_REQUIRE_EQUAL(this->hasInitialRequest(), true);
+}
+
 BOOST_AUTO_TEST_CASE(Timeout)
 {
   this->connectHandlers();