Add NextHopFaceId test scenario

Add libssl-dev to install_dependencies.py

refs #1942

Change-Id: I39270df30f5946e9acc18e1c008a30b6184bca47
diff --git a/install_helpers/install_dependencies.py b/install_helpers/install_dependencies.py
index d08de8b..6c5d1f3 100644
--- a/install_helpers/install_dependencies.py
+++ b/install_helpers/install_dependencies.py
@@ -6,4 +6,4 @@
     print "\nINSTALLING DEPENDENCIES"
     print "***********************"
     os.system("sudo apt-get -qq update")
-    os.system("sudo apt-get -qq install build-essential git libboost-all-dev libcrypto++-dev libpcap-dev pkg-config libsqlite3-dev socat bind9")
+    os.system("sudo apt-get -qq install build-essential git libboost-all-dev libcrypto++-dev libpcap-dev pkg-config libsqlite3-dev socat bind9 libssl-dev")
diff --git a/install_helpers/tools/Makefile b/install_helpers/tools/Makefile
index fa5c8cb..0d802a4 100644
--- a/install_helpers/tools/Makefile
+++ b/install_helpers/tools/Makefile
@@ -3,7 +3,7 @@
 LIBS = `pkg-config --libs libndn-cxx`
 DESTDIR ?= /usr/local
 
-PROGRAMS = test-nack-consumer
+PROGRAMS = test-nack-consumer test-nexthopfaceid-consumer
 
 all: $(PROGRAMS)
 
diff --git a/install_helpers/tools/test-nexthopfaceid-consumer.cpp b/install_helpers/tools/test-nexthopfaceid-consumer.cpp
new file mode 100644
index 0000000..90ba6b6
--- /dev/null
+++ b/install_helpers/tools/test-nexthopfaceid-consumer.cpp
@@ -0,0 +1,102 @@
+/**
+ * Consumer for the NextHopFaceId test (test_nexthopfaceid)
+ *
+ * Author: Eric Newberry <enewberry@email.arizona.edu>
+ *
+ * Based on ndn-cxx example consumer
+ */
+
+#include <ndn-cxx/lp/tags.hpp>
+#include <ndn-cxx/mgmt/nfd/controller.hpp>
+
+#include <cstring>
+#include <cstdlib>
+
+using namespace ndn;
+
+class TestNextHopFaceIdConsumer : noncopyable
+{
+public:
+  void
+  run(Name name, bool enableLocalFields, int nextHopFaceId)
+  {
+    nfd::ControlParameters params;
+    params.setFlagBit(nfd::BIT_LOCAL_FIELDS_ENABLED, enableLocalFields);
+
+    ndn::nfd::Controller controller(m_face, m_keyChain);
+    controller.start<ndn::nfd::FaceUpdateCommand>(params,
+                                                  nullptr,
+                                                  [] (const ndn::nfd::ControlResponse& resp) {
+                                                    throw std::runtime_error("Unable to toggle local fields");
+                                                  });
+    m_face.processEvents();
+
+    // Now, send test case Interest
+    Interest interest(name);
+
+    if (nextHopFaceId != -1) {
+      interest.setTag(make_shared<lp::NextHopFaceIdTag>(nextHopFaceId));
+    }
+
+    m_face.expressInterest(interest,
+                           [] (const Interest& interest, const Data& data) {
+                             std::cout.write(reinterpret_cast<const char*>(data.getContent().value()),
+                                             data.getContent().value_size());
+                             std::cout << std::endl;
+                           },
+                           [] (const Interest& interest, const lp::Nack& nack) {
+                             std::cout << "Nack" << std::endl;
+                           },
+                           [] (const Interest& interest) {
+                             std::cout << "Timeout" << std::endl;
+                           });
+    m_face.processEvents();
+  }
+
+private:
+  Face m_face;
+  KeyChain m_keyChain;
+};
+
+static void
+usage(const char* name)
+{
+  std::cerr << "Usage: " << name
+            << " [prefix] [enableLocalFields - t/f] [NextHopFaceId (-1 if not set)]" << std::endl;
+}
+
+int
+main(int argc, char** argv)
+{
+  if (argc != 4) {
+    usage(argv[0]);
+    return -1;
+  }
+
+  Name name(argv[1]);
+
+  bool enableLocalFields = false;
+  if (std::strcmp(argv[2], "t") == 0) {
+    enableLocalFields = true;
+  }
+  else if (std::strcmp(argv[2], "f") == 0) {
+    enableLocalFields = false;
+  }
+  else {
+    usage(argv[0]);
+    return -1;
+  }
+
+  int nextHopFaceId = std::atoi(argv[3]);
+
+  try {
+    TestNextHopFaceIdConsumer consumer;
+    consumer.run(name, enableLocalFields, nextHopFaceId);
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return -2;
+  }
+
+  return 0;
+}
diff --git a/test_nexthopfaceid/NDNTrafficServer-B.conf b/test_nexthopfaceid/NDNTrafficServer-B.conf
new file mode 100644
index 0000000..41c905d
--- /dev/null
+++ b/test_nexthopfaceid/NDNTrafficServer-B.conf
@@ -0,0 +1,4 @@
+##########
+Name=/P
+Content=BBBBBBBB
+##########
diff --git a/test_nexthopfaceid/NDNTrafficServer-C.conf b/test_nexthopfaceid/NDNTrafficServer-C.conf
new file mode 100644
index 0000000..fa88f4c
--- /dev/null
+++ b/test_nexthopfaceid/NDNTrafficServer-C.conf
@@ -0,0 +1,4 @@
+##########
+Name=/P
+Content=CCCCCCCC
+##########
diff --git a/test_nexthopfaceid/README.md b/test_nexthopfaceid/README.md
new file mode 100644
index 0000000..aff116b
--- /dev/null
+++ b/test_nexthopfaceid/README.md
@@ -0,0 +1,17 @@
+# Test NextHopFaceId tag and LP field
+
+## Topology
+
+B--A--C
+
+## Steps
+
+1.  Start NFD on A, B, and C.
+2.  On A, create routes for prefix ndn:/P toward B and C. The cost toward B is lower than the cost toward C.
+3.  On B, start ndn-traffic-server on ndn:/P, serving payload "BBBBBBBB".
+4.  On C, start ndn-traffic-server on ndn:/P, serving payload "CCCCCCCC".
+5.  On A, execute a consumer to enable local fields and then express an Interest for ndn:/P/1, without NextHopFaceId tag. Expect Data with payload "BBBBBBBB".
+6.  On A, execute a consumer to enable local fields and then express an Interest for ndn:/P/2, with NextHopFaceId=faceB. Expect Data with payload "BBBBBBBB".
+7.  On A, execute a consumer to enable local fields and then express an Interest for ndn:/P/3, with NextHopFaceId=faceC. Expect Data with payload "CCCCCCCC".
+8.  On A, execute a consumer to enable local fields and then express an Interest for ndn:/P/4, with NextHopFaceId=null-face. Expect either timeout or Nack.
+9.  On A, execute a consumer to disable local fields and then express an Interest for ndn:/P/5, with NextHopFaceId=faceC. Expect either timeout or Data with payload "BBBBBBBB".
diff --git a/test_nexthopfaceid/nexthopfaceid-test.sh b/test_nexthopfaceid/nexthopfaceid-test.sh
new file mode 100755
index 0000000..d4b22b7
--- /dev/null
+++ b/test_nexthopfaceid/nexthopfaceid-test.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+source ../multi-host.conf
+echo "host B IP address $IP4_B1"
+echo "host C IP address $IP4_C1"
+
+clean_up() {
+  r=$(ssh $CTRL_B "sudo killall ndn-traffic-server" 2>&1)
+  r=$(ssh $CTRL_B "sudo killall nfd" 2>&1)
+  r=$(ssh $CTRL_C "sudo killall ndn-traffic-server" 2>&1)
+  r=$(ssh $CTRL_C "sudo killall nfd" 2>&1)
+  r=$(sudo killall nfd 2>&1)
+}
+
+mkdir -p logs
+
+# Start NFD on hosts A, B, and C
+workdir=$(pwd)
+echo "starting nfd on host A..."
+sudo nfd > $workdir/logs/nfd.log 2>&1 &
+sleep 1
+
+echo "starting nfd on host B..."
+ssh $CTRL_B "mkdir -p $workdir/logs;\
+    sudo nfd &> $workdir/logs/nfd.log &"
+sleep 1
+
+echo "starting nfd on host C..."
+ssh $CTRL_C "mkdir -p $workdir/logs;\
+    sudo nfd &> $workdir/logs/nfd.log &"
+sleep 1
+
+# A: Create route for prefix ndn:/P toward B with cost 10
+nfdc register -c 10 ndn:/P udp4://$IP4_B1
+if [[ $? -ne 0 ]]
+then
+    echo "Failed to create route for ndn:/P toward host B"
+    clean_up
+    exit 1
+fi
+
+# A: Create route for prefix ndn:/P toward C with cost 20
+nfdc register -c 20 ndn:/P udp4://$IP4_C1
+if [[ $? -ne 0 ]]
+then
+    echo "Failed to create route for ndn:/P toward host C"
+    clean_up
+    exit 1
+fi
+
+# Get face IDs on A
+faceB=$(nfdc face list | grep "udp4://$IP4_B1" | cut -d" " -f3 | cut -d"=" -f2)
+faceC=$(nfdc face list | grep "udp4://$IP4_C1" | cut -d" " -f3 | cut -d"=" -f2)
+
+# B: Start ndn-traffic-server serving "BBBBBBBB" on ndn:/P
+echo "starting ndn-traffic-server on host B..."
+ssh $CTRL_B "ndn-traffic-server $workdir/NDNTrafficServer-B.conf > $workdir/logs/server.log 2>&1 &"
+
+# C: Start ndn-traffic-server serving "CCCCCCCC" on ndn:/P
+echo "starting ndn-traffic-server on host C..."
+ssh $CTRL_C "ndn-traffic-server $workdir/NDNTrafficServer-C.conf > $workdir/logs/server.log 2>&1 &"
+
+# A: Start consumer to enable local fields and express Interest for ndn:/P/1 w/o NextHopFaceId
+#    Expect Data w/ payload "BBBBBBBB"
+echo "From A, sending Interest for ndn:/P/1 (LocalFields=enabled)..."
+output=$(test-nexthopfaceid-consumer /P/1 t -1)
+if [[ $output != "BBBBBBBB" ]]; then
+  echo "Interest ndn:/P/1 not answered with Data containing payload 'BBBBBBBB'"
+  clean_up
+  exit 2
+fi
+
+# A: Start consumer to enable local fields and express Interest for ndn:/P/2 w/ NextHopFaceId=faceB
+#    Expect Data w/ payload "BBBBBBBB"
+echo "From A, sending Interest for ndn:/P/2 (LocalFields=enabled, NextHopFaceId=faceB)..."
+output=$(test-nexthopfaceid-consumer /P/2 t $faceB)
+if [[ $output != "BBBBBBBB" ]]; then
+  echo "Interest ndn:/P/2 not answered with Data containing payload 'BBBBBBBB'"
+  clean_up
+  exit 3
+fi
+
+# A: Start consumer to enable local fields and express Interest for ndn:/P/3 w/ NextHopFaceId=faceC
+#    Expect Data w/ payload "CCCCCCCC"
+echo "From A, sending Interest for ndn:/P/3 (LocalFields=enabled, NextHopFaceId=faceC)..."
+output=$(test-nexthopfaceid-consumer /P/3 t $faceC)
+if [[ $output != "CCCCCCCC" ]]; then
+  echo "Interest ndn:/P/3 not answered with Data containing payload 'CCCCCCCC'"
+  clean_up
+  exit 4
+fi
+
+# A: Start consumer to enable local fields and express Interest for ndn:/P/4 w/ NextHopFaceId=null-face
+#    Expect either timeout or Nack
+echo "From A, sending Interest for ndn:/P/4 (LocalFields=enabled, NextHopFaceId=null-face)..."
+output=$(test-nexthopfaceid-consumer /P/4 t 0)
+if [[ $output != "Timeout" && $output != "Nack" ]]; then
+  echo "Interest ndn:/P/4 was not answered with a Nack and did not time out"
+  clean_up
+  exit 5
+fi
+
+# A: Start consumer to disable local fields and express Interest for ndn:/P/5w/ NextHopFaceId=faceC
+#    Expect either timeout or Data w/ payload "BBBBBBBB"
+echo "From A, sending Interest for ndn:/P/5 (LocalFields=disabled, NextHopFaceId=faceC)..."
+output=$(test-nexthopfaceid-consumer /P/5 f $faceC)
+if [[ $output != "Timeout" && $output != "BBBBBBBB" ]]; then
+  echo "Interest ndn:/P/5 was not answered with Data containing payload 'BBBBBBBB' and did not time out"
+  clean_up
+  exit 6
+fi
+
+clean_up
+echo "NextHopFaceId Test PASSED"
diff --git a/test_nexthopfaceid/test_nexthopfaceid.py b/test_nexthopfaceid/test_nexthopfaceid.py
new file mode 100644
index 0000000..5dc2b8a
--- /dev/null
+++ b/test_nexthopfaceid/test_nexthopfaceid.py
@@ -0,0 +1,37 @@
+#!/usr/bin/python2
+# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+#
+# Copyright (C) 2016 University of Arizona
+# Author: Eric Newberry <enewberry@cs.arizona.edu>
+# See COPYING for copyright and distribution information.
+#
+
+import os
+import unittest
+import subprocess
+
+class test_nexthopfaceid(unittest.TestCase):
+    """Test case for NextHopFaceId field"""
+
+    def setUp(self):
+        print "\nTesting NextHopFaceId"
+        print "*****************"
+        os.chdir("test_nexthopfaceid")
+
+    def tearDown(self):
+        print "*****************"
+        os.chdir("..")
+
+    def test_nexthopfaceid(self):
+        ret = subprocess.call(["./nexthopfaceid-test.sh"], shell=True)
+        print "Test script return value:", ret
+        errormsg = {
+            1 : "Failed to create required routes",
+            2 : "Interest ndn:/P/1 not answered with Data containing payload 'BBBBBBBB'",
+            3 : "Interest ndn:/P/2 not answered with Data containing payload 'BBBBBBBB'",
+            4 : "Interest ndn:/P/3 not answered with Data containing payload 'CCCCCCCC'",
+            5 : "Interest ndn:/P/4 was not answered with a Nack and did not time out",
+            6 : "Interest ndn:/P/5 was not answered with Data containing payload 'BBBBBBBB' and did not time out",
+        }
+        if (ret != 0):
+            self.fail(errormsg[ret])