exclude: Allow excluding empty name component

Change-Id: I6eb835aec30db7dc0cd36a5b17f0a34693d34c42
Fixes: #2660
diff --git a/src/exclude.cpp b/src/exclude.cpp
index 89d052d..76a6260 100644
--- a/src/exclude.cpp
+++ b/src/exclude.cpp
@@ -24,6 +24,8 @@
 #include "exclude.hpp"
 #include "util/concepts.hpp"
 
+#include <boost/range/adaptors.hpp>
+
 namespace ndn {
 
 BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Exclude>));
@@ -54,17 +56,14 @@
   // Exclude ::= EXCLUDE-TYPE TLV-LENGTH Any? (NameComponent (Any)?)+
   // Any     ::= ANY-TYPE TLV-LENGTH(=0)
 
-  for (Exclude::const_iterator i = m_exclude.begin(); i != m_exclude.end(); i++)
-    {
-      if (i->second)
-        {
-          totalLength += prependBooleanBlock(block, tlv::Any);
-        }
-      if (!i->first.empty())
-        {
-          totalLength += i->first.wireEncode(block);
-        }
+  for (const auto& item : m_exclude) {
+    if (item.second) {
+      totalLength += prependBooleanBlock(block, tlv::Any);
     }
+    if (!item.first.empty() || !item.second) {
+      totalLength += item.first.wireEncode(block);
+    }
+  }
 
   totalLength += block.prependVarNumber(totalLength);
   totalLength += block.prependVarNumber(tlv::Exclude);
@@ -112,41 +111,35 @@
   // Any     ::= ANY-TYPE TLV-LENGTH(=0)
 
   Block::element_const_iterator i = m_wire.elements_begin();
-  if (i->type() == tlv::Any)
-    {
-      appendExclude(name::Component(), true);
-      ++i;
+  if (i->type() == tlv::Any) {
+    appendExclude(name::Component(), true);
+    ++i;
+  }
+
+  while (i != m_wire.elements_end()) {
+    name::Component excludedComponent;
+    try {
+      excludedComponent = std::move(name::Component(*i));
+    }
+    catch (const name::Component::Error&) {
+      throw Error("Incorrect format of Exclude filter");
     }
 
-  while (i != m_wire.elements_end())
-    {
-      name::Component excludedComponent;
-      try {
-        excludedComponent = std::move(name::Component(*i));
-      }
-      catch (const name::Component::Error&) {
-        throw Error("Incorrect format of Exclude filter");
-      }
+    ++i;
 
-      ++i;
-
-      if (i != m_wire.elements_end())
-        {
-          if (i->type() == tlv::Any)
-            {
-              appendExclude(excludedComponent, true);
-              ++i;
-            }
-          else
-            {
-              appendExclude(excludedComponent, false);
-            }
-        }
-      else
-        {
-          appendExclude(excludedComponent, false);
-        }
+    if (i != m_wire.elements_end()) {
+      if (i->type() == tlv::Any) {
+        appendExclude(excludedComponent, true);
+        ++i;
+      }
+      else {
+        appendExclude(excludedComponent, false);
+      }
     }
+    else {
+      appendExclude(excludedComponent, false);
+    }
+  }
 }
 
 // example: ANY /b /d ANY /f
@@ -172,18 +165,15 @@
     return true;
   else
     return lowerBound->first == comp;
-
-  return false;
 }
 
 Exclude&
 Exclude::excludeOne(const name::Component& comp)
 {
-  if (!isExcluded(comp))
-    {
-      m_exclude.insert(std::make_pair(comp, false));
-      m_wire.reset();
-    }
+  if (!isExcluded(comp)) {
+    m_exclude.insert(std::make_pair(comp, false));
+    m_wire.reset();
+  }
   return *this;
 }
 
@@ -236,9 +226,9 @@
 
   iterator newTo = m_exclude.lower_bound(to); // !newTo cannot be end()
   if (newTo == newFrom || !newTo->second) {
-      std::pair<iterator, bool> toResult = m_exclude.insert(std::make_pair(to, false));
-      newTo = toResult.first;
-      ++ newTo;
+    std::pair<iterator, bool> toResult = m_exclude.insert(std::make_pair(to, false));
+    newTo = toResult.first;
+    ++ newTo;
   }
   // else
   // nothing to do really
@@ -276,17 +266,19 @@
 std::ostream&
 operator<<(std::ostream& os, const Exclude& exclude)
 {
-  bool empty = true;
-  for (Exclude::const_reverse_iterator i = exclude.rbegin(); i != exclude.rend(); i++) {
-    if (!i->first.empty()) {
-      if (!empty) os << ",";
-      os << i->first.toUri();
-      empty = false;
+  bool isFirst = true;
+  for (const auto& item : exclude | boost::adaptors::reversed) {
+    if (!item.first.empty() || !item.second) {
+      if (!isFirst)
+        os << ",";
+      os << item.first.toUri();
+      isFirst = false;
     }
-    if (i->second) {
-      if (!empty) os << ",";
+    if (item.second) {
+      if (!isFirst)
+        os << ",";
       os << "*";
-      empty = false;
+      isFirst = false;
     }
   }
   return os;
diff --git a/src/name-component.hpp b/src/name-component.hpp
index 4e6119b..0ed3759 100644
--- a/src/name-component.hpp
+++ b/src/name-component.hpp
@@ -494,7 +494,7 @@
   bool
   empty() const
   {
-    return !hasValue();
+    return !hasValue() || value_size() == 0;
   }
 
   Component
diff --git a/tests/unit-tests/exclude.t.cpp b/tests/unit-tests/exclude.t.cpp
index 88b692a..ce2892e 100644
--- a/tests/unit-tests/exclude.t.cpp
+++ b/tests/unit-tests/exclude.t.cpp
@@ -219,6 +219,28 @@
   BOOST_CHECK_NO_THROW(exclude.wireDecode(block));
 }
 
+BOOST_AUTO_TEST_CASE(ExcludeEmptyComponent) // Bug #2660
+{
+  Exclude e1, e2;
+
+  e1.excludeOne(name::Component());
+  e2.excludeOne(name::Component(""));
+
+  BOOST_CHECK_EQUAL(e1, e2);
+  BOOST_CHECK_EQUAL(e1.toUri(), e2.toUri());
+  BOOST_CHECK(e1.wireEncode() == e2.wireEncode());
+
+  BOOST_CHECK_EQUAL("...", e1.toUri());
+
+  uint8_t WIRE[] {0x10, 0x02, 0x08, 0x00};
+  BOOST_CHECK_EQUAL_COLLECTIONS(e1.wireEncode().begin(), e1.wireEncode().end(),
+                                WIRE, WIRE + sizeof(WIRE));
+
+  Exclude e3(Block(WIRE, sizeof(WIRE)));
+  BOOST_CHECK_EQUAL(e1, e3);
+  BOOST_CHECK_EQUAL(e1.toUri(), e3.toUri());
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests