face: Implementation of ethernet::Address::fromString()

Change-Id: I18279bee23f8e80ee667099c1207e50556fbd1a1
diff --git a/daemon/face/ethernet.cpp b/daemon/face/ethernet.cpp
new file mode 100644
index 0000000..af11a3a
--- /dev/null
+++ b/daemon/face/ethernet.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "ethernet.hpp"
+
+#include <stdio.h>
+
+namespace nfd {
+namespace ethernet {
+
+std::string
+Address::toString(char sep) const
+{
+  char s[18]; // 12 digits + 5 separators + null terminator
+  ::snprintf(s, sizeof(s), "%02x%c%02x%c%02x%c%02x%c%02x%c%02x",
+             elems[0], sep, elems[1], sep, elems[2], sep,
+             elems[3], sep, elems[4], sep, elems[5]);
+  return std::string(s);
+}
+
+Address
+Address::fromString(const std::string& str)
+{
+  Address a;
+  int n, ret;
+
+  // colon-separated
+  ret = ::sscanf(str.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx%n",
+                 &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &n);
+  if (ret >= 6 && str.c_str()[n] == '\0')
+    return a;
+
+  // dash-separated
+  ret = ::sscanf(str.c_str(), "%hhx-%hhx-%hhx-%hhx-%hhx-%hhx%n",
+                 &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &n);
+  if (ret >= 6 && str.c_str()[n] == '\0')
+    return a;
+
+  return Address();
+}
+
+std::ostream&
+operator<<(std::ostream& o, const Address& a)
+{
+  return o << a.toString();
+}
+
+} // namespace ethernet
+} // namespace nfd
diff --git a/daemon/face/ethernet.hpp b/daemon/face/ethernet.hpp
index f12b3fb..573ea56 100644
--- a/daemon/face/ethernet.hpp
+++ b/daemon/face/ethernet.hpp
@@ -56,8 +56,25 @@
   bool
   isNull() const;
 
+  /**
+   * @brief Converts the address to a human-readable string
+   *
+   * @param sep A character used to visually separate the octets,
+   *            usually a dash (the default value) or a colon
+   */
   std::string
   toString(char sep = '-') const;
+
+  /**
+   * @brief Creates an Address from a string containing an Ethernet address
+   *        in hexadecimal notation, with colons or dashes as separators
+   *
+   * @param str The string to be parsed
+   * @return Always an instance of Address, which will be null
+   *         if the parsing fails
+   */
+  static Address
+  fromString(const std::string& str);
 };
 
 /// Returns the Ethernet broadcast address (FF-FF-FF-FF-FF-FF)
@@ -125,21 +142,8 @@
          elems[3] == 0x0 && elems[4] == 0x0 && elems[5] == 0x0;
 }
 
-inline std::string
-Address::toString(char sep) const
-{
-  char s[18]; // 12 digits + 5 separators + null terminator
-  ::snprintf(s, sizeof(s), "%02x%c%02x%c%02x%c%02x%c%02x%c%02x",
-             elems[0], sep, elems[1], sep, elems[2], sep,
-             elems[3], sep, elems[4], sep, elems[5]);
-  return std::string(s);
-}
-
-static std::ostream&
-operator<<(std::ostream& o, const Address& a)
-{
-  return o << a.toString();
-}
+std::ostream&
+operator<<(std::ostream& o, const Address& a);
 
 } // namespace ethernet
 } // namespace nfd
diff --git a/tests/face/ethernet-address.cpp b/tests/face/ethernet-address.cpp
new file mode 100644
index 0000000..0f726e1
--- /dev/null
+++ b/tests/face/ethernet-address.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "face/ethernet.hpp"
+#include "tests/test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(FaceEthernetAddress, BaseFixture)
+
+BOOST_AUTO_TEST_CASE(Checks)
+{
+  BOOST_CHECK(ethernet::Address().isNull());
+  BOOST_CHECK(ethernet::getBroadcastAddress().isBroadcast());
+  BOOST_CHECK(ethernet::getDefaultMulticastAddress().isMulticast());
+}
+
+BOOST_AUTO_TEST_CASE(ToString)
+{
+  BOOST_CHECK_EQUAL(ethernet::Address().toString(),
+                    "00-00-00-00-00-00");
+  BOOST_CHECK_EQUAL(ethernet::getBroadcastAddress().toString(':'),
+                    "ff:ff:ff:ff:ff:ff");
+  BOOST_CHECK_EQUAL(ethernet::Address(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB).toString(),
+                    "01-23-45-67-89-ab");
+  BOOST_CHECK_EQUAL(ethernet::Address(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB).toString(':'),
+                    "01:23:45:67:89:ab");
+}
+
+BOOST_AUTO_TEST_CASE(FromString)
+{
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("0:0:0:0:0:0"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("ff-ff-ff-ff-ff-ff"),
+                    ethernet::getBroadcastAddress());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("de:ad:be:ef:1:2"),
+                    ethernet::Address(0xde, 0xad, 0xbe, 0xef, 0x01, 0x02));
+
+  // malformed inputs
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01.23.45.67.89.ab"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45 :67:89:ab"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45:67:89::1"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01-23-45-67-89"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("01:23:45:67:89:ab:cd"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("qw-er-ty-12-34-56"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("this-is-not-an-ethernet-address"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString("foobar"),
+                    ethernet::Address());
+  BOOST_CHECK_EQUAL(ethernet::Address::fromString(""),
+                    ethernet::Address());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nfd