nfd-status: Define format and implement --xml option

The ether URI format "ether://01:00:5e:00:17:aa" violates
the URI specification, so it has been changed to
ether://[01:00:5e:00:17:aa] in this commit.

refs: #1438

Change-Id: I1218374b75f919a5e1ab158bbab2e217e64aced3
diff --git a/core/face-uri.cpp b/core/face-uri.cpp
index 7eaaddd..1b2092c 100644
--- a/core/face-uri.cpp
+++ b/core/face-uri.cpp
@@ -70,7 +70,7 @@
   // pattern for IPv6 address enclosed in [ ], with optional port number
   static const boost::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
   // pattern for Ethernet address in standard hex-digits-and-colons notation
-  static const boost::regex etherExp("^((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))$");
+  static const boost::regex etherExp("^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
   // pattern for IPv4/hostname/fd/ifname, with optional port number
   static const boost::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
 
@@ -132,7 +132,7 @@
 
 #ifdef HAVE_LIBPCAP
 FaceUri::FaceUri(const ethernet::Address& address)
-  : m_isV6(false)
+  : m_isV6(true)
 {
   m_scheme = "ether";
   m_host = address.toString();
diff --git a/docs/_static/nfd-status.xsd b/docs/_static/nfd-status.xsd
new file mode 100644
index 0000000..44b5e93
--- /dev/null
+++ b/docs/_static/nfd-status.xsd
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+targetNamespace="ndn:/localhost/nfd/status/1" xmlns:nfd="ndn:/localhost/nfd/status/1"
+elementFormDefault="qualified">
+
+<xs:complexType name="unidirectionalPacketCountersType">
+  <xs:sequence>
+    <xs:element type="xs:nonNegativeInteger" name="nInterests"/>
+    <xs:element type="xs:nonNegativeInteger" name="nDatas"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="bidirectionalPacketCountersType">
+  <xs:sequence>
+    <xs:element type="nfd:unidirectionalPacketCountersType" name="incomingPackets"/>
+    <xs:element type="nfd:unidirectionalPacketCountersType" name="outgoingPackets"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="generalStatusType">
+  <xs:sequence>
+    <xs:element type="xs:string" name="version"/>
+    <xs:element type="xs:dateTime" name="startTime"/>
+    <xs:element type="xs:dateTime" name="currentTime"/>
+    <xs:element type="xs:duration" name="uptime"/>
+    <xs:element type="xs:nonNegativeInteger" name="nNameTreeEntries"/>
+    <xs:element type="xs:nonNegativeInteger" name="nFibEntries"/>
+    <xs:element type="xs:nonNegativeInteger" name="nPitEntries"/>
+    <xs:element type="xs:nonNegativeInteger" name="nMeasurementsEntries"/>
+    <xs:element type="xs:nonNegativeInteger" name="nCsEntries"/>
+    <xs:element type="nfd:bidirectionalPacketCountersType" name="packetCounters"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="faceType">
+  <xs:sequence>
+    <xs:element type="xs:nonNegativeInteger" name="faceId"/>
+    <xs:element type="xs:anyURI" name="remoteUri"/>
+    <xs:element type="xs:anyURI" name="localUri"/>
+    <xs:element type="nfd:bidirectionalPacketCountersType" name="packetCounters"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="facesType">
+  <xs:sequence>
+    <xs:element type="nfd:faceType" name="face" maxOccurs="unbounded" minOccurs="0"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="nextHopType">
+  <xs:sequence>
+    <xs:element type="xs:nonNegativeInteger" name="faceId"/>
+    <xs:element type="xs:nonNegativeInteger" name="cost"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="fibEntryType">
+  <xs:sequence>
+    <xs:element type="xs:anyURI" name="prefix"/>
+    <xs:element name="nextHops">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element type="nfd:nextHopType" name="nextHop" maxOccurs="unbounded"/>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="fibType">
+  <xs:sequence>
+    <xs:element type="nfd:fibEntryType" name="fibEntry" maxOccurs="unbounded" minOccurs="0"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:element name="nfdStatus">
+  <xs:complexType>
+    <xs:sequence>
+      <xs:element type="nfd:generalStatusType" name="generalStatus"/>
+      <xs:element type="nfd:facesType" name="faces"/>
+      <xs:element type="nfd:fibType" name="fib"/>
+    </xs:sequence>
+  </xs:complexType>
+</xs:element>
+
+</xs:schema>
diff --git a/docs/manpages.rst b/docs/manpages.rst
index 3b8a496..6a52897 100644
--- a/docs/manpages.rst
+++ b/docs/manpages.rst
@@ -8,6 +8,7 @@
    manpages/nrd
    manpages/nfdc
    manpages/nfd-status
+   schema
    manpages/nfd-status-http-server
    manpages/ndn-autoconfig
    manpages/ndn-autoconfig-server
diff --git a/docs/schema.rst b/docs/schema.rst
new file mode 100644
index 0000000..08e40b7
--- /dev/null
+++ b/docs/schema.rst
@@ -0,0 +1,10 @@
+NFD Status XML Schema
+=====================
+
+The NFD status XML schema describes the structure of the NFD status XML
+document generated by command ``nfd-status``.
+
+Users can use this schema to validate the generated XML document.
+
+.. literalinclude:: _static/nfd-status.xsd
+   :language: xml
diff --git a/tests/core/face-uri.cpp b/tests/core/face-uri.cpp
index 0e54f04..e1e07a9 100644
--- a/tests/core/face-uri.cpp
+++ b/tests/core/face-uri.cpp
@@ -156,7 +156,7 @@
 {
   FaceUri uri;
 
-  BOOST_CHECK(uri.parse("ether://08:00:27:01:dd:01"));
+  BOOST_CHECK(uri.parse("ether://[08:00:27:01:dd:01]"));
   BOOST_CHECK_EQUAL(uri.getScheme(), "ether");
   BOOST_CHECK_EQUAL(uri.getHost(), "08:00:27:01:dd:01");
   BOOST_CHECK_EQUAL(uri.getPort(), "");
@@ -167,7 +167,7 @@
 #ifdef HAVE_LIBPCAP
   ethernet::Address address = ethernet::Address::fromString("33:33:01:01:01:01");
   BOOST_REQUIRE_NO_THROW(FaceUri(address));
-  BOOST_CHECK_EQUAL(FaceUri(address).toString(), "ether://33:33:01:01:01:01");
+  BOOST_CHECK_EQUAL(FaceUri(address).toString(), "ether://[33:33:01:01:01:01]");
 #endif // HAVE_LIBPCAP
 }
 
diff --git a/tests/daemon/face/ethernet.cpp b/tests/daemon/face/ethernet.cpp
index 270abb0..936406b 100644
--- a/tests/daemon/face/ethernet.cpp
+++ b/tests/daemon/face/ethernet.cpp
@@ -118,7 +118,7 @@
   BOOST_CHECK(!face->isOnDemand());
   BOOST_CHECK_EQUAL(face->isLocal(), false);
   BOOST_CHECK_EQUAL(face->getRemoteUri().toString(),
-                    "ether://" + ethernet::getDefaultMulticastAddress().toString());
+                    "ether://[" + ethernet::getDefaultMulticastAddress().toString()+"]");
   BOOST_CHECK_EQUAL(face->getLocalUri().toString(),
                     "dev://" + m_interfaces.front()->name);
 
diff --git a/tools/nfd-status.cpp b/tools/nfd-status.cpp
index 9e8fa28..1792961 100644
--- a/tools/nfd-status.cpp
+++ b/tools/nfd-status.cpp
@@ -46,6 +46,7 @@
     , m_needVersionRetrieval(false)
     , m_needFaceStatusRetrieval(false)
     , m_needFibEnumerationRetrieval(false)
+    , m_needXmlDataRetrieval(false)
   {
   }
 
@@ -59,10 +60,12 @@
       "  [-v] - retrieve version information\n"
       "  [-f] - retrieve face status information\n"
       "  [-b] - retrieve FIB information\n"
+      "  [-x] - retrieve NFD status information in XML format\n"
       "\n"
       "  [-V] - show version information of nfd-status and exit\n"
       "\n"
       "If no options are provided, all information is retrieved.\n"
+      "If -x is provided, other options(v, f and b) are ignored, and all information is retrieved in XML format.\n"
       ;
   }
 
@@ -85,6 +88,12 @@
   }
 
   void
+  enableXmlDataRetrieval()
+  {
+    m_needXmlDataRetrieval = true;
+  }
+
+  void
   onTimeout()
   {
     std::cerr << "Request timed out" << std::endl;
@@ -112,32 +121,86 @@
       }
   }
 
+  void escapeSpecialCharacters(std::string *data)
+  {
+    using boost::algorithm::replace_all;
+    replace_all(*data, "&",  "&amp;");
+    replace_all(*data, "\"", "&quot;");
+    replace_all(*data, "\'", "&apos;");
+    replace_all(*data, "<",  "&lt;");
+    replace_all(*data, ">",  "&gt;");
+  }
+
   void
   afterFetchedVersionInformation(const Data& data)
   {
-    std::cout << "General NFD status:" << std::endl;
-
     nfd::ForwarderStatus status(data.getContent());
-    std::cout << "               version="
-              << status.getNfdVersion() << std::endl;
-    std::cout << "             startTime="
-              << time::toIsoString(status.getStartTimestamp()) << std::endl;
-    std::cout << "           currentTime="
-              << time::toIsoString(status.getCurrentTimestamp()) << std::endl;
-    std::cout << "                uptime="
-              << time::duration_cast<time::seconds>(status.getCurrentTimestamp()
-                                                    - status.getStartTimestamp()) << std::endl;
+    if (m_needXmlDataRetrieval)
+      {
+        std::cout << "<?xml version=\"1.0\"?>";
+        std::cout << "<nfdStatus xmlns=\"ndn:/localhost/nfd/status/1\">";
+        std::cout << "<generalStatus>";
+        std::cout << "<version>"
+                  << status.getNfdVersion() << "</version>";
+        std::cout << "<startTime>"
+                  << time::toString(status.getStartTimestamp(), "%Y-%m-%dT%H:%M:%S%F")
+                  << "</startTime>";
+        std::cout << "<currentTime>"
+                  << time::toString(status.getCurrentTimestamp(), "%Y-%m-%dT%H:%M:%S%F")
+                  << "</currentTime>";
+        std::cout << "<uptime>PT"
+                  << time::duration_cast<time::seconds>(status.getCurrentTimestamp()
+                                                        - status.getStartTimestamp()).count()
+                  << "S</uptime>";
+        std::cout << "<nNameTreeEntries>"     << status.getNNameTreeEntries()
+                  << "</nNameTreeEntries>";
+        std::cout << "<nFibEntries>"          << status.getNFibEntries()
+                  << "</nFibEntries>";
+        std::cout << "<nPitEntries>"          << status.getNPitEntries()
+                  << "</nPitEntries>";
+        std::cout << "<nMeasurementsEntries>" << status.getNMeasurementsEntries()
+                  << "</nMeasurementsEntries>";
+        std::cout << "<nCsEntries>"           << status.getNCsEntries()
+                  << "</nCsEntries>";
+        std::cout << "<packetCounters>";
+        std::cout << "<incomingPackets>";
+        std::cout << "<nInterests>"           << status.getNInInterests()
+                  << "</nInterests>";
+        std::cout << "<nDatas>"               << status.getNInDatas()
+                  << "</nDatas>";
+        std::cout << "</incomingPackets>";
+        std::cout << "<outgoingPackets>";
+        std::cout << "<nInterests>"           << status.getNOutInterests()
+                  << "</nInterests>";
+        std::cout << "<nDatas>"               << status.getNOutDatas()
+                  << "</nDatas>";
+        std::cout << "</outgoingPackets>";
+        std::cout << "</packetCounters>";
+        std::cout << "</generalStatus>";
+      }
+    else
+      {
+        std::cout << "General NFD status:" << std::endl;
+        std::cout << "               version="
+                  << status.getNfdVersion() << std::endl;
+        std::cout << "             startTime="
+                  << time::toIsoString(status.getStartTimestamp()) << std::endl;
+        std::cout << "           currentTime="
+                  << time::toIsoString(status.getCurrentTimestamp()) << std::endl;
+        std::cout << "                uptime="
+                  << time::duration_cast<time::seconds>(status.getCurrentTimestamp()
+                                                        - status.getStartTimestamp()) << std::endl;
 
-    std::cout << "      nNameTreeEntries=" << status.getNNameTreeEntries()     << std::endl;
-    std::cout << "           nFibEntries=" << status.getNFibEntries()          << std::endl;
-    std::cout << "           nPitEntries=" << status.getNPitEntries()          << std::endl;
-    std::cout << "  nMeasurementsEntries=" << status.getNMeasurementsEntries() << std::endl;
-    std::cout << "            nCsEntries=" << status.getNCsEntries()           << std::endl;
-    std::cout << "          nInInterests=" << status.getNInInterests()         << std::endl;
-    std::cout << "         nOutInterests=" << status.getNOutInterests()        << std::endl;
-    std::cout << "              nInDatas=" << status.getNInDatas()             << std::endl;
-    std::cout << "             nOutDatas=" << status.getNOutDatas()            << std::endl;
-
+        std::cout << "      nNameTreeEntries=" << status.getNNameTreeEntries()     << std::endl;
+        std::cout << "           nFibEntries=" << status.getNFibEntries()          << std::endl;
+        std::cout << "           nPitEntries=" << status.getNPitEntries()          << std::endl;
+        std::cout << "  nMeasurementsEntries=" << status.getNMeasurementsEntries() << std::endl;
+        std::cout << "            nCsEntries=" << status.getNCsEntries()           << std::endl;
+        std::cout << "          nInInterests=" << status.getNInInterests()         << std::endl;
+        std::cout << "         nOutInterests=" << status.getNOutInterests()        << std::endl;
+        std::cout << "              nInDatas=" << status.getNInDatas()             << std::endl;
+        std::cout << "             nOutDatas=" << status.getNOutDatas()            << std::endl;
+      }
     if (m_needFaceStatusRetrieval)
       {
         fetchFaceStatusInformation();
@@ -163,35 +226,84 @@
   void
   afterFetchedFaceStatusInformation()
   {
-    std::cout << "Faces:" << std::endl;
-
     ConstBufferPtr buf = m_buffer->buf();
-
-    Block block;
-    size_t offset = 0;
-    while (offset < buf->size())
+    if (m_needXmlDataRetrieval)
       {
-        bool ok = Block::fromBuffer(buf, offset, block);
-        if (!ok)
+        std::cout << "<faces>";
+
+        Block block;
+        size_t offset = 0;
+        while (offset < buf->size())
           {
-            std::cerr << "ERROR: cannot decode FaceStatus TLV" << std::endl;
-            break;
+            bool ok = Block::fromBuffer(buf, offset, block);
+            if (!ok)
+              {
+                std::cerr << "ERROR: cannot decode FaceStatus TLV" << std::endl;
+                break;
+              }
+
+            offset += block.size();
+
+            nfd::FaceStatus faceStatus(block);
+
+            std::cout << "<face>";
+            std::cout << "<faceId>" << faceStatus.getFaceId() << "</faceId>";
+
+            std::string remoteUri(faceStatus.getRemoteUri());
+            escapeSpecialCharacters(&remoteUri);
+            std::cout << "<remoteUri>" << remoteUri << "</remoteUri>";
+
+            std::string localUri(faceStatus.getLocalUri());
+            escapeSpecialCharacters(&localUri);
+            std::cout << "<localUri>" << localUri << "</localUri>";
+            std::cout << "<packetCounters>";
+            std::cout << "<incomingPackets>";
+            std::cout << "<nInterests>"       << faceStatus.getNInInterests()
+                      << "</nInterests>";
+            std::cout << "<nDatas>"           << faceStatus.getNInInterests()
+                      << "</nDatas>";
+            std::cout << "</incomingPackets>";
+            std::cout << "<outgoingPackets>";
+            std::cout << "<nInterests>"       << faceStatus.getNOutInterests()
+                      << "</nInterests>";
+            std::cout << "<nDatas>"           << faceStatus.getNOutInterests()
+                      << "</nDatas>";
+            std::cout << "</outgoingPackets>";
+            std::cout << "</packetCounters>";
+            std::cout << "</face>";
           }
-
-        offset += block.size();
-
-        nfd::FaceStatus faceStatus(block);
-
-        std::cout << "  faceid=" << faceStatus.getFaceId()
-                  << " remote=" << faceStatus.getRemoteUri()
-                  << " local=" << faceStatus.getLocalUri()
-                  << " counters={"
-                  << "in={" << faceStatus.getNInInterests() << "i "
-                  << faceStatus.getNInDatas() << "d}"
-                  << " out={" << faceStatus.getNOutInterests() << "i "
-                  << faceStatus.getNOutDatas() << "d}"
-                  << "}" << std::endl;
+        std::cout << "</faces>";
       }
+    else
+      {
+        std::cout << "Faces:" << std::endl;
+
+        Block block;
+        size_t offset = 0;
+        while (offset < buf->size())
+          {
+            bool ok = Block::fromBuffer(buf, offset, block);
+            if (!ok)
+              {
+                std::cerr << "ERROR: cannot decode FaceStatus TLV" << std::endl;
+                break;
+              }
+
+            offset += block.size();
+
+            nfd::FaceStatus faceStatus(block);
+
+            std::cout << "  faceid=" << faceStatus.getFaceId()
+                      << " remote=" << faceStatus.getRemoteUri()
+                      << " local=" << faceStatus.getLocalUri()
+                      << " counters={"
+                      << "in={" << faceStatus.getNInInterests() << "i "
+                      << faceStatus.getNInDatas() << "d}"
+                      << " out={" << faceStatus.getNOutInterests() << "i "
+                      << faceStatus.getNOutDatas() << "d}"
+                      << "}" << std::endl;
+          }
+       }
 
     if (m_needFibEnumerationRetrieval)
       {
@@ -217,36 +329,78 @@
   void
   afterFetchedFibEnumerationInformation()
   {
-    std::cout << "FIB:" << std::endl;
-
     ConstBufferPtr buf = m_buffer->buf();
-
-    Block block;
-    size_t offset = 0;
-    while (offset < buf->size())
+    if (m_needXmlDataRetrieval)
       {
-        bool ok = Block::fromBuffer(buf, offset, block);
-        if (!ok)
-          {
-            std::cerr << "ERROR: cannot decode FibEntry TLV" << std::endl;
-            break;
-          }
-        offset += block.size();
+        std::cout << "<fib>";
 
-        nfd::FibEntry fibEntry(block);
-
-        std::cout << "  " << fibEntry.getPrefix() << " nexthops={";
-        for (std::list<nfd::NextHopRecord>::const_iterator
-               nextHop = fibEntry.getNextHopRecords().begin();
-             nextHop != fibEntry.getNextHopRecords().end();
-             ++nextHop)
+        Block block;
+        size_t offset = 0;
+        while (offset < buf->size())
           {
-            if (nextHop != fibEntry.getNextHopRecords().begin())
-              std::cout << ", ";
-            std::cout << "faceid=" << nextHop->getFaceId()
-                      << " (cost=" << nextHop->getCost() << ")";
+            bool ok = Block::fromBuffer(buf, offset, block);
+            if (!ok)
+              {
+                std::cerr << "ERROR: cannot decode FibEntry TLV";
+                break;
+              }
+            offset += block.size();
+
+            nfd::FibEntry fibEntry(block);
+
+            std::cout << "<fibEntry>";
+            std::string prefix(fibEntry.getPrefix().toUri());
+            escapeSpecialCharacters(&prefix);
+            std::cout << "<prefix>" << prefix << "</prefix>";
+            std::cout << "<nextHops>";
+            for (std::list<nfd::NextHopRecord>::const_iterator
+                   nextHop = fibEntry.getNextHopRecords().begin();
+                 nextHop != fibEntry.getNextHopRecords().end();
+                 ++nextHop)
+              {
+                std::cout << "<nextHop>" ;
+                std::cout << "<faceId>"  << nextHop->getFaceId() << "</faceId>";
+                std::cout << "<cost>"    << nextHop->getCost()   << "</cost>";
+                std::cout << "</nextHop>";
+              }
+            std::cout << "</nextHops>";
+            std::cout << "</fibEntry>";
           }
-        std::cout << "}" << std::endl;
+
+        std::cout << "</fib>";
+        std::cout << "</nfdStatus>";
+      }
+    else
+      {
+        std::cout << "FIB:" << std::endl;
+
+        Block block;
+        size_t offset = 0;
+        while (offset < buf->size())
+          {
+            bool ok = Block::fromBuffer(buf, offset, block);
+            if (!ok)
+              {
+                std::cerr << "ERROR: cannot decode FibEntry TLV" << std::endl;
+                break;
+              }
+            offset += block.size();
+
+            nfd::FibEntry fibEntry(block);
+
+            std::cout << "  " << fibEntry.getPrefix() << " nexthops={";
+            for (std::list<nfd::NextHopRecord>::const_iterator
+                   nextHop = fibEntry.getNextHopRecords().begin();
+                 nextHop != fibEntry.getNextHopRecords().end();
+                 ++nextHop)
+              {
+                if (nextHop != fibEntry.getNextHopRecords().begin())
+                  std::cout << ", ";
+                std::cout << "faceid=" << nextHop->getFaceId()
+                          << " (cost=" << nextHop->getCost() << ")";
+              }
+            std::cout << "}" << std::endl;
+          }
       }
   }
 
@@ -267,9 +421,10 @@
   void
   fetchInformation()
   {
-    if (!m_needVersionRetrieval &&
+    if (m_needXmlDataRetrieval ||
+        (!m_needVersionRetrieval &&
         !m_needFaceStatusRetrieval &&
-        !m_needFibEnumerationRetrieval)
+        !m_needFibEnumerationRetrieval))
       {
         enableVersionRetrieval();
         enableFaceStatusRetrieval();
@@ -297,6 +452,7 @@
   bool m_needVersionRetrieval;
   bool m_needFaceStatusRetrieval;
   bool m_needFibEnumerationRetrieval;
+  bool m_needXmlDataRetrieval;
   Face m_face;
 
   shared_ptr<OBufferStream> m_buffer;
@@ -309,7 +465,7 @@
   int option;
   ndn::NfdStatus nfdStatus(argv[0]);
 
-  while ((option = getopt(argc, argv, "hvfbV")) != -1) {
+  while ((option = getopt(argc, argv, "hvfbxV")) != -1) {
     switch (option) {
     case 'h':
       nfdStatus.usage();
@@ -323,6 +479,9 @@
     case 'b':
       nfdStatus.enableFibEnumerationRetrieval();
       break;
+    case 'x':
+      nfdStatus.enableXmlDataRetrieval();
+      break;
     case 'V':
       std::cout << NFD_VERSION_BUILD_STRING << std::endl;
       return 0;