encoding: RouteOrigin stream extraction operator

refs #3987

Change-Id: I073984d6e2f158ff200a8bb8478896028993897f
diff --git a/src/encoding/nfd-constants.cpp b/src/encoding/nfd-constants.cpp
index 9cda265..0de5fe2 100644
--- a/src/encoding/nfd-constants.cpp
+++ b/src/encoding/nfd-constants.cpp
@@ -22,6 +22,9 @@
 #include "nfd-constants.hpp"
 #include "util/string-helper.hpp"
 
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <istream>
 #include <map>
 #include <ostream>
 
@@ -92,6 +95,53 @@
   return os << static_cast<unsigned>(faceEventKind);
 }
 
+std::istream&
+operator>>(std::istream& is, RouteOrigin& routeOrigin)
+{
+  using boost::algorithm::iequals;
+
+  std::string s;
+  is >> s;
+
+  if (iequals(s, "none"))
+    routeOrigin = ROUTE_ORIGIN_NONE;
+  else if (iequals(s, "app"))
+    routeOrigin = ROUTE_ORIGIN_APP;
+  else if (iequals(s, "autoreg"))
+    routeOrigin = ROUTE_ORIGIN_AUTOREG;
+  else if (iequals(s, "client"))
+    routeOrigin = ROUTE_ORIGIN_CLIENT;
+  else if (iequals(s, "autoconf"))
+    routeOrigin = ROUTE_ORIGIN_AUTOCONF;
+  else if (iequals(s, "nlsr"))
+    routeOrigin = ROUTE_ORIGIN_NLSR;
+  else if (iequals(s, "static"))
+    routeOrigin = ROUTE_ORIGIN_STATIC;
+  else {
+    // To reject negative numbers, we parse as a wider signed type, and compare with the range.
+    static_assert(std::numeric_limits<std::underlying_type<RouteOrigin>::type>::max() <=
+                  std::numeric_limits<int>::max(), "");
+
+    int v = -1;
+    try {
+      v = boost::lexical_cast<int>(s);
+    }
+    catch (const boost::bad_lexical_cast&) {
+    }
+
+    if (v >= std::numeric_limits<std::underlying_type<RouteOrigin>::type>::min() &&
+        v <= std::numeric_limits<std::underlying_type<RouteOrigin>::type>::max()) {
+      routeOrigin = static_cast<RouteOrigin>(v);
+    }
+    else {
+      routeOrigin = ROUTE_ORIGIN_NONE;
+      is.setstate(std::ios::failbit);
+    }
+  }
+
+  return is;
+}
+
 std::ostream&
 operator<<(std::ostream& os, RouteOrigin routeOrigin)
 {
diff --git a/src/encoding/nfd-constants.hpp b/src/encoding/nfd-constants.hpp
index de19776..1212eb9 100644
--- a/src/encoding/nfd-constants.hpp
+++ b/src/encoding/nfd-constants.hpp
@@ -95,6 +95,14 @@
   ROUTE_ORIGIN_STATIC   = 255,
 };
 
+/** \brief extract RouteOrigin from stream
+ *  \post if the first token in \p contains a valid RouteOrigin as string or numeric value, it is
+ *        extracted into \p routeOrigin ; otherwise, \p routeOrigin is set to \p ROUTE_ORIGIN_NONE ,
+ *        and failbit is set on \p is
+ */
+std::istream&
+operator>>(std::istream& is, RouteOrigin& routeOrigin);
+
 std::ostream&
 operator<<(std::ostream& os, RouteOrigin routeOrigin);
 
diff --git a/tests/unit-tests/encoding/nfd-constants.t.cpp b/tests/unit-tests/encoding/nfd-constants.t.cpp
index e185093..c3fe54a 100644
--- a/tests/unit-tests/encoding/nfd-constants.t.cpp
+++ b/tests/unit-tests/encoding/nfd-constants.t.cpp
@@ -67,6 +67,47 @@
   BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(static_cast<FaceEventKind>(175)), "175");
 }
 
+BOOST_AUTO_TEST_CASE(ParseRouteOrigin)
+{
+  auto expectSuccess = [] (const std::string& input, RouteOrigin expected) {
+    std::istringstream is(input);
+    RouteOrigin routeOrigin;
+    is >> routeOrigin;
+
+    BOOST_TEST_MESSAGE("parsing " << input);
+    BOOST_CHECK_EQUAL(routeOrigin, expected);
+  };
+
+  auto expectFail = [] (const std::string& input) {
+    std::istringstream is(input);
+    RouteOrigin routeOrigin;
+    is >> routeOrigin;
+
+    BOOST_TEST_MESSAGE("parsing " << input);
+    BOOST_CHECK(is.fail());
+    BOOST_CHECK_EQUAL(routeOrigin, ROUTE_ORIGIN_NONE);
+  };
+
+  expectSuccess("none", ROUTE_ORIGIN_NONE);
+  expectSuccess("App", ROUTE_ORIGIN_APP);
+  expectSuccess("AutoReg", ROUTE_ORIGIN_AUTOREG);
+  expectSuccess("Client", ROUTE_ORIGIN_CLIENT);
+  expectSuccess("AutoConf", ROUTE_ORIGIN_AUTOCONF);
+  expectSuccess("NLSR", ROUTE_ORIGIN_NLSR);
+  expectSuccess("static", ROUTE_ORIGIN_STATIC);
+  expectSuccess("27", static_cast<RouteOrigin>(27));
+
+  expectSuccess(" app", ROUTE_ORIGIN_APP);
+  expectSuccess("app ", ROUTE_ORIGIN_APP);
+  expectSuccess(" app ", ROUTE_ORIGIN_APP);
+
+  expectFail("unrecognized");
+  expectFail("-1");
+  expectFail("0.1");
+  expectFail("65537");
+  expectFail("");
+}
+
 BOOST_AUTO_TEST_CASE(PrintRouteOrigin)
 {
   BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(ROUTE_ORIGIN_NONE), "none");