face: congestion detection in TCP, UDP, and Unix socket transports

refs #4362

Change-Id: Idaa5d65e1f33663d95bad56de42640183b2cda6d
diff --git a/tests/daemon/face/datagram-transport.t.cpp b/tests/daemon/face/datagram-transport.t.cpp
index 6121152..8cfb622 100644
--- a/tests/daemon/face/datagram-transport.t.cpp
+++ b/tests/daemon/face/datagram-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -150,6 +150,13 @@
   BOOST_REQUIRE_EQUAL(this->limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
 }
 
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(SendQueueLength, T, DatagramTransportFixtures, T)
+{
+  TRANSPORT_TEST_INIT();
+
+  BOOST_CHECK_EQUAL(this->transport->getSendQueueLength(), 0);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestDatagramTransport
 BOOST_AUTO_TEST_SUITE_END() // Face
 
diff --git a/tests/daemon/face/dummy-transport.hpp b/tests/daemon/face/dummy-transport.hpp
index 07444aa..08a7141 100644
--- a/tests/daemon/face/dummy-transport.hpp
+++ b/tests/daemon/face/dummy-transport.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -44,8 +44,10 @@
                  ndn::nfd::FaceScope scope = ndn::nfd::FACE_SCOPE_NON_LOCAL,
                  ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
                  ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_POINT_TO_POINT,
-                 ssize_t mtu = MTU_UNLIMITED)
+                 ssize_t mtu = MTU_UNLIMITED,
+                 ssize_t sendQueueCapacity = QUEUE_UNSUPPORTED)
     : isClosed(false)
+    , m_sendQueueLength(0)
   {
     this->setLocalUri(FaceUri(localUri));
     this->setRemoteUri(FaceUri(remoteUri));
@@ -53,6 +55,7 @@
     this->setPersistency(persistency);
     this->setLinkType(linkType);
     this->setMtu(mtu);
+    this->setSendQueueCapacity(sendQueueCapacity);
   }
 
   void
@@ -67,6 +70,18 @@
     this->Transport::setState(state);
   }
 
+  ssize_t
+  getSendQueueLength() override
+  {
+    return m_sendQueueLength;
+  }
+
+  void
+  setSendQueueLength(ssize_t sendQueueLength)
+  {
+    m_sendQueueLength = sendQueueLength;
+  }
+
   void
   receivePacket(Packet&& packet)
   {
@@ -87,14 +102,14 @@
   }
 
 private:
-  virtual void
+  void
   doClose() override
   {
     isClosed = true;
     this->setState(TransportState::CLOSED);
   }
 
-  virtual void
+  void
   doSend(Packet&& packet) override
   {
     sentPackets.push_back(std::move(packet));
@@ -103,6 +118,9 @@
 public:
   bool isClosed;
   std::vector<Packet> sentPackets;
+
+private:
+  ssize_t m_sendQueueLength;
 };
 
 } // namespace tests
diff --git a/tests/daemon/face/generic-link-service.t.cpp b/tests/daemon/face/generic-link-service.t.cpp
index e4e82f7..98801c6 100644
--- a/tests/daemon/face/generic-link-service.t.cpp
+++ b/tests/daemon/face/generic-link-service.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -25,10 +25,11 @@
 
 #include "face/generic-link-service.hpp"
 #include "face/face.hpp"
-#include "dummy-transport.hpp"
-#include <ndn-cxx/lp/tags.hpp>
 
-#include "tests/test-common.hpp"
+#include "test-common.hpp"
+#include "dummy-transport.hpp"
+
+#include <ndn-cxx/lp/tags.hpp>
 
 namespace nfd {
 namespace face {
@@ -40,7 +41,7 @@
 
 using nfd::Face;
 
-class GenericLinkServiceFixture : public BaseFixture
+class GenericLinkServiceFixture : public UnitTestTimeFixture
 {
 protected:
   GenericLinkServiceFixture()
@@ -53,7 +54,9 @@
   }
 
   void
-  initialize(const GenericLinkService::Options& options, ssize_t mtu = MTU_UNLIMITED)
+  initialize(const GenericLinkService::Options& options,
+             ssize_t mtu = MTU_UNLIMITED,
+             ssize_t sendQueueCapacity = QUEUE_UNSUPPORTED)
   {
     face.reset(new Face(make_unique<GenericLinkService>(options),
                         make_unique<DummyTransport>("dummy://",
@@ -61,7 +64,8 @@
                                                     ndn::nfd::FACE_SCOPE_NON_LOCAL,
                                                     ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
                                                     ndn::nfd::LINK_TYPE_POINT_TO_POINT,
-                                                    mtu)));
+                                                    mtu,
+                                                    sendQueueCapacity)));
     service = static_cast<GenericLinkService*>(face->getLinkService());
     transport = static_cast<DummyTransport*>(face->getTransport());
 
@@ -462,6 +466,357 @@
 
 BOOST_AUTO_TEST_SUITE_END() // Reliability
 
+// congestion detection and marking
+BOOST_AUTO_TEST_SUITE(CongestionMark)
+
+BOOST_AUTO_TEST_CASE(NoCongestion)
+{
+  GenericLinkService::Options options;
+  options.allowCongestionMarking = true;
+  options.baseCongestionMarkingInterval = time::milliseconds(100);
+  initialize(options, MTU_UNLIMITED, 65536);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+
+  // congestion threshold will be 32768 bytes, since min(65536, 65536 / 2) = 32768 bytes
+
+  // no congestion
+  transport->setSendQueueLength(0);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
+  lp::Packet pkt1;
+  BOOST_REQUIRE_NO_THROW(pkt1.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt1.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+
+  // no congestion
+  transport->setSendQueueLength(32768);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 2);
+  lp::Packet pkt2;
+  BOOST_REQUIRE_NO_THROW(pkt2.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt2.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+}
+
+BOOST_AUTO_TEST_CASE(CongestionCoDel)
+{
+  GenericLinkService::Options options;
+  options.allowCongestionMarking = true;
+  options.baseCongestionMarkingInterval = time::milliseconds(100);
+  initialize(options, MTU_UNLIMITED, 65536);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, time::steady_clock::TimePoint::min());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+
+  // congestion threshold will be 32768 bytes, since min(65536, 65536 / 2) = 32768 bytes
+
+  // first congested packet, will be marked
+  transport->setSendQueueLength(32769);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
+  lp::Packet pkt1;
+  BOOST_REQUIRE_NO_THROW(pkt1.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt1.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt1.get<lp::CongestionMarkField>(), 1);
+  time::steady_clock::TimePoint nextMarkTime = time::steady_clock::now() + time::milliseconds(100);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  time::steady_clock::TimePoint lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 1);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 1);
+
+  // advance clock to half of marking interval cycle
+  advanceClocks(time::milliseconds(50));
+
+  // second congested packet, but within marking interval, will not be marked
+  transport->setSendQueueLength(33000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 2);
+  lp::Packet pkt2;
+  BOOST_REQUIRE_NO_THROW(pkt2.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt2.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 1);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 1);
+
+  // advance clocks past end of initial interval cycle
+  this->advanceClocks(time::milliseconds(51));
+
+  // first congested packet after waiting marking interval, will be marked
+  transport->setSendQueueLength(40000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 3);
+  lp::Packet pkt3;
+  BOOST_REQUIRE_NO_THROW(pkt3.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt3.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt3.get<lp::CongestionMarkField>(), 1);
+  time::nanoseconds markingInterval(
+    static_cast<time::nanoseconds::rep>(options.baseCongestionMarkingInterval.count() /
+                                        std::sqrt(service->m_nMarkedSinceInMarkingState)));
+  nextMarkTime += markingInterval;
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 2);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 2);
+
+  // advance clock partway through current marking interval
+  this->advanceClocks(markingInterval - time::milliseconds(10));
+
+  // still congested, but within marking interval cycle
+  transport->setSendQueueLength(38000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 4);
+  lp::Packet pkt4;
+  BOOST_REQUIRE_NO_THROW(pkt4.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt4.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 2);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 2);
+
+  // advance clocks past end of current marking interval cycle
+  this->advanceClocks(time::milliseconds(11));
+
+  // still congested, after marking interval cycle
+  transport->setSendQueueLength(39000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 5);
+  lp::Packet pkt5;
+  BOOST_REQUIRE_NO_THROW(pkt5.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt5.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt5.get<lp::CongestionMarkField>(), 1);
+  markingInterval = time::nanoseconds(
+    static_cast<time::nanoseconds::rep>(options.baseCongestionMarkingInterval.count() /
+                                        std::sqrt(service->m_nMarkedSinceInMarkingState)));
+  nextMarkTime += markingInterval;
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 3);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 3);
+
+  this->advanceClocks(time::milliseconds(1));
+
+  // still congested, but within marking interval cycle
+  transport->setSendQueueLength(38000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 6);
+  lp::Packet pkt6;
+  BOOST_REQUIRE_NO_THROW(pkt6.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt6.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 3);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 3);
+
+  this->advanceClocks(markingInterval);
+
+  // still congested, after marking interval cycle
+  transport->setSendQueueLength(34000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 7);
+  lp::Packet pkt7;
+  BOOST_REQUIRE_NO_THROW(pkt7.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt7.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt7.get<lp::CongestionMarkField>(), 1);
+  markingInterval = time::nanoseconds(
+    static_cast<time::nanoseconds::rep>(options.baseCongestionMarkingInterval.count() /
+                                        std::sqrt(service->m_nMarkedSinceInMarkingState)));
+  nextMarkTime += markingInterval;
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 4);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 4);
+
+  // no more congestion
+  transport->setSendQueueLength(30000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 8);
+  lp::Packet pkt8;
+  BOOST_REQUIRE_NO_THROW(pkt8.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt8.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 4);
+
+  this->advanceClocks(time::milliseconds(50));
+
+  // send queue congested again, but can't mark packet because within one full interval of last mark
+  transport->setSendQueueLength(50000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 9);
+  lp::Packet pkt9;
+  BOOST_REQUIRE_NO_THROW(pkt9.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt9.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 4);
+
+  // advance clock past full 100ms interval since last mark
+  this->advanceClocks(time::milliseconds(51));
+
+  transport->setSendQueueLength(40000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 10);
+  lp::Packet pkt10;
+  BOOST_REQUIRE_NO_THROW(pkt10.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt10.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt10.get<lp::CongestionMarkField>(), 1);
+  nextMarkTime = time::steady_clock::now() + time::milliseconds(100);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 1);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 5);
+
+  // advance clock partway through 100ms marking interval
+  this->advanceClocks(time::milliseconds(50));
+
+  // not marked since within 100ms window before can mark again
+  transport->setSendQueueLength(50000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 11);
+  lp::Packet pkt11;
+  BOOST_REQUIRE_NO_THROW(pkt11.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt11.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 1);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 5);
+
+  // advance clocks past m_nextMarkTime
+  this->advanceClocks(time::milliseconds(51));
+
+  // markable packet, queue length still above threshold
+  transport->setSendQueueLength(33000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 12);
+  lp::Packet pkt12;
+  BOOST_REQUIRE_NO_THROW(pkt12.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt12.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt12.get<lp::CongestionMarkField>(), 1);
+  markingInterval = time::nanoseconds(
+    static_cast<time::nanoseconds::rep>(options.baseCongestionMarkingInterval.count() /
+                                        std::sqrt(service->m_nMarkedSinceInMarkingState)));
+  nextMarkTime += markingInterval;
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 2);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 6);
+
+  // no more congestion
+  transport->setSendQueueLength(10000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 13);
+  lp::Packet pkt13;
+  BOOST_REQUIRE_NO_THROW(pkt13.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt13.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 6);
+
+  // advance clocks past one full interval since last mark
+  this->advanceClocks(time::milliseconds(101));
+
+  // start congestion again
+  transport->setSendQueueLength(50000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 14);
+  lp::Packet pkt14;
+  BOOST_REQUIRE_NO_THROW(pkt14.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt14.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt14.get<lp::CongestionMarkField>(), 1);
+  nextMarkTime = time::steady_clock::now() + time::milliseconds(100);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  lastMarkTime = time::steady_clock::now();
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 1);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 7);
+
+  // no more congestion, cancel marking interval
+  transport->setSendQueueLength(5000);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 15);
+  lp::Packet pkt15;
+  BOOST_REQUIRE_NO_THROW(pkt15.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt15.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_lastMarkTime, lastMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 7);
+}
+
+BOOST_AUTO_TEST_CASE(DefaultThreshold)
+{
+  GenericLinkService::Options options;
+  options.allowCongestionMarking = true;
+  options.baseCongestionMarkingInterval = time::milliseconds(100);
+  initialize(options, MTU_UNLIMITED, QUEUE_UNSUPPORTED);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+
+  shared_ptr<Interest> interest = makeInterest("/12345678");
+
+  // congestion threshold will be 65536 bytes, since the transport reports that it cannot measure
+  // the queue capacity
+
+  // no congestion
+  transport->setSendQueueLength(0);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 1);
+  lp::Packet pkt1;
+  BOOST_REQUIRE_NO_THROW(pkt1.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt1.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+
+  // no congestion
+  transport->setSendQueueLength(65536);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 2);
+  lp::Packet pkt2;
+  BOOST_REQUIRE_NO_THROW(pkt2.wireDecode(transport->sentPackets.back().packet));
+  BOOST_CHECK_EQUAL(pkt2.count<lp::CongestionMarkField>(), 0);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, time::steady_clock::TimePoint::max());
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 0);
+
+  // first congested (and marked) packet
+  transport->setSendQueueLength(65537);
+  face->sendInterest(*interest);
+  BOOST_REQUIRE_EQUAL(transport->sentPackets.size(), 3);
+  lp::Packet pkt3;
+  BOOST_REQUIRE_NO_THROW(pkt3.wireDecode(transport->sentPackets.back().packet));
+  BOOST_REQUIRE_EQUAL(pkt3.count<lp::CongestionMarkField>(), 1);
+  BOOST_CHECK_EQUAL(pkt3.get<lp::CongestionMarkField>(), 1);
+  time::steady_clock::TimePoint nextMarkTime = time::steady_clock::now() + time::milliseconds(100);
+  BOOST_CHECK_EQUAL(service->m_nextMarkTime, nextMarkTime);
+  BOOST_CHECK_EQUAL(service->m_nMarkedSinceInMarkingState, 1);
+  BOOST_CHECK_EQUAL(service->getCounters().nCongestionMarked, 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // CongestionMark
+
 BOOST_AUTO_TEST_SUITE(LpFields)
 
 BOOST_AUTO_TEST_CASE(ReceiveNextHopFaceId)
@@ -772,7 +1127,6 @@
 
 BOOST_AUTO_TEST_SUITE_END() // LpFields
 
-
 BOOST_AUTO_TEST_SUITE(Malformed) // receive malformed packets
 
 BOOST_AUTO_TEST_CASE(WrongTlvType)
diff --git a/tests/daemon/face/multicast-ethernet-transport.t.cpp b/tests/daemon/face/multicast-ethernet-transport.t.cpp
index 7544adc..511f57a 100644
--- a/tests/daemon/face/multicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/multicast-ethernet-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -46,6 +46,7 @@
   BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
   BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
   BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(transport->getSendQueueCapacity(), QUEUE_UNSUPPORTED);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
@@ -79,6 +80,14 @@
   BOOST_REQUIRE_EQUAL(limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
 }
 
+BOOST_AUTO_TEST_CASE(SendQueueLength)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+  initializeMulticast();
+
+  BOOST_CHECK_EQUAL(transport->getSendQueueLength(), QUEUE_UNSUPPORTED);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestMulticastEthernetTransport
 BOOST_AUTO_TEST_SUITE_END() // Face
 
diff --git a/tests/daemon/face/multicast-udp-transport.t.cpp b/tests/daemon/face/multicast-udp-transport.t.cpp
index 51acb08..de6d30f 100644
--- a/tests/daemon/face/multicast-udp-transport.t.cpp
+++ b/tests/daemon/face/multicast-udp-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -62,6 +62,7 @@
   BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
   BOOST_CHECK_EQUAL(this->transport->getMtu(),
                     this->addressFamily == AddressFamily::V4 ? (65535 - 60 - 8) : (65535 - 8));
+  BOOST_CHECK_GT(this->transport->getSendQueueCapacity(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
diff --git a/tests/daemon/face/stream-transport.t.cpp b/tests/daemon/face/stream-transport.t.cpp
index 33baec1..6628b92 100644
--- a/tests/daemon/face/stream-transport.t.cpp
+++ b/tests/daemon/face/stream-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -214,6 +214,13 @@
   BOOST_REQUIRE_EQUAL(this->limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
 }
 
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(SendQueueLength, T, StreamTransportFixtures, T)
+{
+  TRANSPORT_TEST_INIT();
+
+  BOOST_CHECK_EQUAL(this->transport->getSendQueueLength(), 0);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestStreamTransport
 BOOST_AUTO_TEST_SUITE_END() // Face
 
diff --git a/tests/daemon/face/tcp-transport.t.cpp b/tests/daemon/face/tcp-transport.t.cpp
index 2f4171b..ef23ca6 100644
--- a/tests/daemon/face/tcp-transport.t.cpp
+++ b/tests/daemon/face/tcp-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -54,6 +54,7 @@
   BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
   BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
   BOOST_CHECK_EQUAL(this->transport->getMtu(), MTU_UNLIMITED);
+  BOOST_CHECK_EQUAL(this->transport->getSendQueueCapacity(), QUEUE_UNSUPPORTED);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
diff --git a/tests/daemon/face/unicast-ethernet-transport.t.cpp b/tests/daemon/face/unicast-ethernet-transport.t.cpp
index 49a0fbb..a95c3fb 100644
--- a/tests/daemon/face/unicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/unicast-ethernet-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -120,6 +120,14 @@
   BOOST_CHECK_EQUAL(nStateChanges, 2);
 }
 
+BOOST_AUTO_TEST_CASE(SendQueueLength)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+  initializeUnicast();
+
+  BOOST_CHECK_EQUAL(transport->getSendQueueLength(), QUEUE_UNSUPPORTED);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestUnicastEthernetTransport
 BOOST_AUTO_TEST_SUITE_END() // Face
 
diff --git a/tests/daemon/face/unicast-udp-transport.t.cpp b/tests/daemon/face/unicast-udp-transport.t.cpp
index 075194b..a2a3e79 100644
--- a/tests/daemon/face/unicast-udp-transport.t.cpp
+++ b/tests/daemon/face/unicast-udp-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -54,6 +54,7 @@
   BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
   BOOST_CHECK_EQUAL(this->transport->getMtu(),
                     this->addressFamily == AddressFamily::V4 ? (65535 - 60 - 8) : (65535 - 8));
+  BOOST_CHECK_GT(this->transport->getSendQueueCapacity(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
diff --git a/tests/daemon/face/unix-stream-transport.t.cpp b/tests/daemon/face/unix-stream-transport.t.cpp
index de2e03a..8eebb60 100644
--- a/tests/daemon/face/unix-stream-transport.t.cpp
+++ b/tests/daemon/face/unix-stream-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -48,6 +48,7 @@
   BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
   BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
   BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
+  BOOST_CHECK_EQUAL(transport->getSendQueueCapacity(), QUEUE_UNSUPPORTED);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
diff --git a/tests/daemon/face/websocket-transport.t.cpp b/tests/daemon/face/websocket-transport.t.cpp
index f13e065..66a3d43 100644
--- a/tests/daemon/face/websocket-transport.t.cpp
+++ b/tests/daemon/face/websocket-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -54,6 +54,7 @@
   BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
   BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
   BOOST_CHECK_EQUAL(this->transport->getMtu(), MTU_UNLIMITED);
+  BOOST_CHECK_EQUAL(this->transport->getSendQueueCapacity(), QUEUE_UNSUPPORTED);
 }
 
 using StaticPropertiesV4MappedFixtures = boost::mpl::vector<
@@ -246,6 +247,13 @@
   BOOST_CHECK_EQUAL(nStateChanges, 2);
 }
 
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(SendQueueLength, T, WebSocketTransportFixtures, T)
+{
+  TRANSPORT_TEST_INIT();
+
+  BOOST_CHECK_EQUAL(this->transport->getSendQueueLength(), QUEUE_UNSUPPORTED);
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestWebSocketTransport
 BOOST_AUTO_TEST_SUITE_END() // Face