dump: Capture and print network NACK packets

refs: #3463

Change-Id: I3aed0d3668378305404f9713cc110e13eec434e5
diff --git a/.waf-tools/default-compiler-flags.py b/.waf-tools/default-compiler-flags.py
index 9f15fcc..91e9a7b 100644
--- a/.waf-tools/default-compiler-flags.py
+++ b/.waf-tools/default-compiler-flags.py
@@ -167,6 +167,9 @@
 
     def getDebugFlags(self, conf):
         flags = super(ClangFlags, self).getDebugFlags(conf)
+        version = tuple(int(i) for i in conf.env['CC_VERSION'])
+        if Utils.unversioned_sys_platform() == 'darwin' and version < (7, 0, 0):
+            flags['CXXFLAGS'] += ['-Wno-missing-field-initializers']
         flags['CXXFLAGS'] += ['-fcolor-diagnostics',
                               '-Wno-unused-local-typedef', # Bugs #2657 and #3209
                               '-Wno-error=unneeded-internal-declaration', # Bug #1588
@@ -177,6 +180,9 @@
 
     def getOptimizedFlags(self, conf):
         flags = super(ClangFlags, self).getOptimizedFlags(conf)
+        version = tuple(int(i) for i in conf.env['CC_VERSION'])
+        if Utils.unversioned_sys_platform() == 'darwin' and version < (7, 0, 0):
+            flags['CXXFLAGS'] += ['-Wno-missing-field-initializers']
         flags['CXXFLAGS'] += ['-fcolor-diagnostics',
                               '-Wno-unused-local-typedef', # Bugs #2657 and #3209
                               ]
diff --git a/AUTHORS.md b/AUTHORS.md
index 93c29e2..7647fe3 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -19,3 +19,4 @@
 * Wentao Shang          <http://irl.cs.ucla.edu/~wentao/>
 * Steve DiBenedetto     <https://dibenede.github.io>
 * Andrea Tosatto        <https://linkedin.com/in/tosattoandrea>
+* Vince Lehman          <http://vslehman.com>
diff --git a/tests/dump/README.md b/tests/dump/README.md
new file mode 100644
index 0000000..47fbf7f
--- /dev/null
+++ b/tests/dump/README.md
@@ -0,0 +1,29 @@
+Testing Instructions
+====================
+
+This folder contains a tcpdump trace that can be used to manually check
+the correctness of ndndump.
+
+## Test Cases / Trace File Description
+
+### 1. NDNLPv2 NACK
+
+Trace file: `nack.pcap`
+
+Trace summary: Six IPv4 UDP packets, carrying NDN interests and NACK packets.
+Twelve IPv4 TCP packets, carrying NDN interests and NACK packets.
+
+Expected result of the capture:
+
+- 3 NDN interests are captured with UDP tunnel type.
+- 3 NACKs are captured with UDP tunnel type and the NACK reasons:
+  - Congestion
+  - Duplicate
+  - None
+
+- 3 NDN interests are captured with TCP tunnel type.
+- 4 NACKs are captured with TCP tunnel type and the NACK reasons:
+  - Congestion
+  - Duplicate
+  - None
+  - None
diff --git a/tests/dump/nack.pcap b/tests/dump/nack.pcap
new file mode 100644
index 0000000..ab177f3
--- /dev/null
+++ b/tests/dump/nack.pcap
Binary files differ
diff --git a/tests/dump/ndndump.t.cpp b/tests/dump/ndndump.t.cpp
new file mode 100644
index 0000000..d4b8396
--- /dev/null
+++ b/tests/dump/ndndump.t.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  University of Memphis.
+ *
+ * This file is part of ndn-tools (Named Data Networking Essential Tools).
+ * See AUTHORS.md for complete list of ndn-tools authors and contributors.
+ *
+ * ndn-tools is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * ndn-tools, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tools/dump/ndndump.hpp"
+
+#include "tests/test-common.hpp"
+
+#include <boost/test/output_test_stream.hpp>
+
+#include <ndn-cxx/lp/packet.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/util/ethernet.hpp>
+
+namespace ndn {
+namespace dump {
+namespace tests {
+
+using namespace ndn::tests;
+
+class StdCoutRedirector
+{
+public:
+  StdCoutRedirector(std::ostream& os)
+  {
+    // Redirect std::cout to the specified output stream
+    originalBuffer = std::cout.rdbuf(os.rdbuf());
+  }
+
+  ~StdCoutRedirector()
+  {
+    // Revert state for std::cout
+    std::cout.rdbuf(originalBuffer);
+  }
+
+private:
+  std::streambuf* originalBuffer;
+};
+
+class NdnDumpFixture
+{
+protected:
+  NdnDumpFixture()
+  {
+    dump.m_dataLinkType = DLT_EN10MB;
+  }
+
+  template <typename Packet>
+  void
+  receive(const Packet& packet)
+  {
+    ndn::EncodingBuffer buffer(packet.wireEncode());
+    receive(buffer);
+  }
+
+  void
+  receive(ndn::EncodingBuffer& buffer)
+  {
+    ndn::util::ethernet::Address host;
+
+    // Ethernet header
+    uint16_t frameType = htons(ndn::util::ethernet::ETHERTYPE_NDN);
+    buffer.prependByteArray(reinterpret_cast<const uint8_t*>(&frameType),
+                            ndn::util::ethernet::TYPE_LEN);
+    buffer.prependByteArray(host.data(), host.size());
+    buffer.prependByteArray(host.data(), host.size());
+
+    pcap_pkthdr header{};
+    header.len = buffer.size();
+
+    {
+      StdCoutRedirector redirect(output);
+      dump.onCapturedPacket(&header, buffer.buf());
+    }
+  }
+
+protected:
+  Ndndump dump;
+  boost::test_tools::output_test_stream output;
+};
+
+BOOST_FIXTURE_TEST_SUITE(NdnDump, NdnDumpFixture)
+
+BOOST_AUTO_TEST_CASE(CaptureInterest)
+{
+  ndn::Interest interest("/test");
+  interest.setNonce(0);
+
+  this->receive(interest);
+
+  const std::string expectedOutput =
+    "0.000000 Tunnel Type: EthernetFrame, INTEREST: /test?ndn.Nonce=0\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureData)
+{
+  ndn::security::KeyChain keyChain;
+
+  ndn::Data data("/test");
+  keyChain.sign(data);
+
+  this->receive(data);
+
+  const std::string expectedOutput = "0.000000 Tunnel Type: EthernetFrame, DATA: /test\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureNack)
+{
+  ndn::Interest interest("/test");
+  interest.setNonce(0);
+
+  ndn::lp::Nack nack(interest);
+  nack.setReason(ndn::lp::NackReason::DUPLICATE);
+
+  lp::Packet lpPacket(interest.wireEncode());
+  lpPacket.add<lp::NackField>(nack.getHeader());
+
+  this->receive(lpPacket);
+
+  const std::string expectedOutput =
+    "0.000000 Tunnel Type: EthernetFrame, NACK: Duplicate, /test?ndn.Nonce=0\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureLpFragment)
+{
+  const uint8_t data[10] = {
+    0x06, 0x08, // Data packet
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+  };
+
+  ndn::Buffer buffer(data, 4);
+
+  lp::Packet lpPacket;
+  lpPacket.add<lp::FragmentField>(std::make_pair(buffer.begin(), buffer.end()));
+  lpPacket.add<lp::FragIndexField>(0);
+  lpPacket.add<lp::FragCountField>(2);
+  lpPacket.add<lp::SequenceField>(1000);
+
+  this->receive(lpPacket);
+
+  const std::string expectedOutput =
+    "0.000000 Tunnel Type: EthernetFrame, NDNLPv2-FRAGMENT\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureIdlePacket)
+{
+  lp::Packet lpPacket;
+
+  this->receive(lpPacket);
+
+  const std::string expectedOutput =
+    "0.000000 Tunnel Type: EthernetFrame, NDNLPv2-IDLE\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureIncompletePacket)
+{
+  const uint8_t interest[] = {
+  0x05, 0x0E, // Interest
+    0x07, 0x06, // Name
+      0x08, 0x04, // NameComponent
+        0x74, 0x65, 0x73, 0x74,
+    0x0a, 0x04, // Nonce
+      0x00, 0x00, 0x00, 0x01
+  };
+
+  EncodingBuffer buffer;
+  buffer.prependByteArray(interest, 4);
+
+  this->receive(buffer);
+
+  const std::string expectedOutput =
+    "0.000000 Tunnel Type: EthernetFrame, INCOMPLETE-PACKET, size: 4\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(CaptureUnknownNetworkPacket)
+{
+  EncodingBuffer buffer(ndn::encoding::makeEmptyBlock(tlv::Name));
+
+  this->receive(buffer);
+
+  const std::string expectedOutput =
+    "0.000000 Tunnel Type: EthernetFrame, UNKNOWN-NETWORK-PACKET\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+BOOST_AUTO_TEST_CASE(DumpPcapTrace)
+{
+  dump.inputFile = "tests/dump/nack.pcap";
+  dump.pcapProgram = "";
+
+  {
+    StdCoutRedirector redirect(output);
+    dump.run();
+  }
+
+  const std::string expectedOutput =
+    "1456768916.467099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: UDP, INTEREST: /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=2581361680\n"
+    "1456768916.567099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: UDP, INTEREST: /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=4138343109\n"
+    "1456768916.667099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: UDP, INTEREST: /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=4034910304\n"
+    "1456768916.767099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: UDP, NACK: Congestion, /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=2581361680\n"
+    "1456768916.867099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: UDP, NACK: Duplicate, /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=4138343109\n"
+    "1456768916.967099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: UDP, NACK: None, /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=4034910304\n"
+    "1456768917.067099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: TCP, INTEREST: /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=3192497423\n"
+    "1456768917.267099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: Congestion, /producer/nack/congestion?ndn.MustBeFresh=1&ndn.Nonce=3192497423\n"
+    "1456768917.367099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: TCP, INTEREST: /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=522390724\n"
+    "1456768917.567099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: Duplicate, /producer/nack/duplicate?ndn.MustBeFresh=1&ndn.Nonce=522390724\n"
+    "1456768917.767099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: None, /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=2002441365\n"
+    "1456768917.967099 From: 1.0.0.1, To: 1.0.0.2, Tunnel Type: TCP, INTEREST: /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=3776824408\n"
+    "1456768918.067099 From: 1.0.0.2, To: 1.0.0.1, Tunnel Type: TCP, NACK: None, /producer/nack/no-reason?ndn.MustBeFresh=1&ndn.Nonce=3776824408\n";
+
+  BOOST_CHECK(output.is_equal(expectedOutput));
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace dump
+} // namespace ndn
diff --git a/tools/dump/ndndump.cpp b/tools/dump/ndndump.cpp
index cf8f839..e964c58 100644
--- a/tools/dump/ndndump.cpp
+++ b/tools/dump/ndndump.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2015,  Regents of the University of California.
+ * Copyright (c) 2014-2016,  Regents of the University of California.
  *
  * This file is part of ndn-tools (Named Data Networking Essential Tools).
  * See AUTHORS.md for complete list of ndn-tools authors and contributors.
@@ -47,8 +47,8 @@
 #include "tcpdump/udp.h"
 #include "tcpdump/tcp.h"
 
-} // namespace ndn
 } // namespace dump
+} // namespace ndn
 
 #include <boost/lexical_cast.hpp>
 
@@ -56,12 +56,12 @@
 
 #include <ndn-cxx/interest.hpp>
 #include <ndn-cxx/data.hpp>
+#include <ndn-cxx/lp/nack.hpp>
+#include <ndn-cxx/lp/packet.hpp>
 
 namespace ndn {
 namespace dump {
 
-// const uint8_t NDNLP_HEADER[] = {'N', 'd', 'n', 'l', 'p'};
-
 const size_t MAX_SNAPLEN = 65535;
 
 void
@@ -139,7 +139,7 @@
 
 
 void
-Ndndump::onCapturedPacket(const struct pcap_pkthdr* header, const uint8_t* packet)
+Ndndump::onCapturedPacket(const pcap_pkthdr* header, const uint8_t* packet)
 {
   std::ostringstream os;
   printInterceptTime(os, header);
@@ -162,25 +162,66 @@
   Block block;
   std::tie(isOk, block) = Block::fromBuffer(payload, payloadSize);
   if (!isOk) {
-    // if packet is fragmented, we will not be able to process it
+    // if packet is incomplete, we will not be able to process it
+    if (payloadSize > 0) {
+      std::cout << os.str() << ", " << "INCOMPLETE-PACKET" << ", size: " << payloadSize << std::endl;
+    }
     return;
   }
 
-  /// \todo Detect various header (LocalControlHeader, NDNLP, etc.)
+  lp::Packet lpPacket;
+  Block netPacket;
+
+  if (block.type() == lp::tlv::LpPacket) {
+    lpPacket = lp::Packet(block);
+
+    Buffer::const_iterator begin, end;
+
+    if (lpPacket.has<lp::FragmentField>()) {
+      std::tie(begin, end) = lpPacket.get<lp::FragmentField>();
+    }
+    else {
+      std::cout << os.str() << ", " << "NDNLPv2-IDLE" << std::endl;
+      return;
+    }
+
+    bool isOk = false;
+    std::tie(isOk, netPacket) = Block::fromBuffer(&*begin, std::distance(begin, end));
+    if (!isOk) {
+      // if network packet is fragmented, we will not be able to process it
+      std::cout << os.str() << ", " << "NDNLPv2-FRAGMENT" << std::endl;
+      return;
+    }
+  }
+  else {
+    netPacket = block;
+  }
 
   try {
-    if (block.type() == tlv::Interest) {
-      Interest interest(block);
+    if (netPacket.type() == tlv::Interest) {
+      Interest interest(netPacket);
       if (matchesFilter(interest.getName())) {
-        std::cout << os.str() << ", " << "INTEREST: " << interest << std::endl;
+
+        if (lpPacket.has<lp::NackField>()) {
+          lp::Nack nack(interest);
+          nack.setHeader(lpPacket.get<lp::NackField>());
+
+          std::cout << os.str() << ", " << "NACK: " << nack.getReason() << ", " << interest << std::endl;
+        }
+        else {
+          std::cout << os.str() << ", " << "INTEREST: " << interest << std::endl;
+        }
       }
     }
-    else if (block.type() == tlv::Data) {
-      Data data(block);
+    else if (netPacket.type() == tlv::Data) {
+      Data data(netPacket);
       if (matchesFilter(data.getName())) {
         std::cout << os.str() << ", " << "DATA: " << data.getName() << std::endl;
       }
     }
+    else {
+      std::cout << os.str() << ", " << "UNKNOWN-NETWORK-PACKET" << std::endl;
+    }
   }
   catch (tlv::Error& e) {
     std::cerr << e.what() << std::endl;
@@ -188,7 +229,7 @@
 }
 
 void
-Ndndump::printInterceptTime(std::ostream& os, const struct pcap_pkthdr* header)
+Ndndump::printInterceptTime(std::ostream& os, const pcap_pkthdr* header)
 {
   os << header->ts.tv_sec
      << "."
diff --git a/tools/dump/ndndump.hpp b/tools/dump/ndndump.hpp
index b46c109..594406d 100644
--- a/tools/dump/ndndump.hpp
+++ b/tools/dump/ndndump.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2015,  Regents of the University of California.
+ * Copyright (c) 2014-2016,  Regents of the University of California.
  *
  * This file is part of ndn-tools (Named Data Networking Essential Tools).
  * See AUTHORS.md for complete list of ndn-tools authors and contributors.
@@ -37,6 +37,8 @@
 #ifndef NDN_TOOLS_DUMP_NDNDUMP_HPP
 #define NDN_TOOLS_DUMP_NDNDUMP_HPP
 
+#include "core/common.hpp"
+
 #include <pcap.h>
 
 #include <ndn-cxx/name.hpp>
@@ -72,18 +74,19 @@
   void
   run();
 
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  void
+  onCapturedPacket(const pcap_pkthdr* header, const uint8_t* packet);
+
 private:
   static void
-  onCapturedPacket(uint8_t* userData, const struct pcap_pkthdr* header, const uint8_t* packet)
+  onCapturedPacket(uint8_t* userData, const pcap_pkthdr* header, const uint8_t* packet)
   {
     reinterpret_cast<Ndndump*>(userData)->onCapturedPacket(header, packet);
   }
 
   void
-  onCapturedPacket(const struct pcap_pkthdr* header, const uint8_t* packet);
-
-  void
-  printInterceptTime(std::ostream& os, const struct pcap_pkthdr* header);
+  printInterceptTime(std::ostream& os, const pcap_pkthdr* header);
 
   int
   skipDataLinkHeaderAndGetFrameType(const uint8_t*& payload, ssize_t& payloadSize);
@@ -120,6 +123,8 @@
 
 private:
   pcap_t* m_pcap;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   int m_dataLinkType;
 };