rtnetlink: Add public interface parser

This will be used by the rtnetlink server to parse new links.

Change-Id: Ibc0abc12ca58b78ffc22ac6f75822853d4b7bdc8
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/rtnetlink.cpp b/src/rtnetlink.cpp
index febc305..fd68671 100644
--- a/src/rtnetlink.cpp
+++ b/src/rtnetlink.cpp
@@ -8,6 +8,83 @@
 namespace phosphor::network::netlink
 {
 
+using std::literals::string_view_literals::operator""sv;
+
+static void parseVlanInfo(InterfaceInfo& info, std::string_view msg)
+{
+    if (msg.data() == nullptr)
+    {
+        throw std::runtime_error("Missing VLAN data");
+    }
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        switch (hdr.rta_type)
+        {
+            case IFLA_VLAN_ID:
+                info.vlan_id.emplace(stdplus::raw::copyFrom<uint16_t>(data));
+                break;
+        }
+    }
+}
+
+static void parseLinkInfo(InterfaceInfo& info, std::string_view msg)
+{
+    std::string_view submsg;
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        switch (hdr.rta_type)
+        {
+            case IFLA_INFO_KIND:
+                data.remove_suffix(1);
+                info.kind.emplace(data);
+                break;
+            case IFLA_INFO_DATA:
+                submsg = data;
+                break;
+        }
+    }
+    if (info.kind == "vlan"sv)
+    {
+        parseVlanInfo(info, submsg);
+    }
+}
+
+InterfaceInfo intfFromRtm(std::string_view msg)
+{
+    const auto& ifinfo = netlink::extractRtData<ifinfomsg>(msg);
+    InterfaceInfo ret;
+    ret.flags = ifinfo.ifi_flags;
+    ret.idx = ifinfo.ifi_index;
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        switch (hdr.rta_type)
+        {
+            case IFLA_IFNAME:
+                ret.name.emplace(data.begin(), data.end() - 1);
+                break;
+            case IFLA_ADDRESS:
+                if (data.size() == sizeof(ether_addr))
+                {
+                    ret.mac.emplace(stdplus::raw::copyFrom<ether_addr>(data));
+                }
+                break;
+            case IFLA_MTU:
+                ret.mtu.emplace(stdplus::raw::copyFrom<unsigned>(data));
+                break;
+            case IFLA_LINK:
+                ret.parent_idx.emplace(stdplus::raw::copyFrom<unsigned>(data));
+                break;
+            case IFLA_LINKINFO:
+                parseLinkInfo(ret, data);
+                break;
+        }
+    }
+    return ret;
+}
+
 template <typename Addr>
 static std::optional<std::tuple<unsigned, InAddrAny>>
     parse(std::string_view msg)
diff --git a/src/rtnetlink.hpp b/src/rtnetlink.hpp
index 2862143..5436e9d 100644
--- a/src/rtnetlink.hpp
+++ b/src/rtnetlink.hpp
@@ -8,6 +8,8 @@
 namespace phosphor::network::netlink
 {
 
+InterfaceInfo intfFromRtm(std::string_view msg);
+
 std::optional<std::tuple<unsigned, InAddrAny>>
     gatewayFromRtm(std::string_view msg);
 
diff --git a/src/system_queries.cpp b/src/system_queries.cpp
index 19c37ba..894fc09 100644
--- a/src/system_queries.cpp
+++ b/src/system_queries.cpp
@@ -125,87 +125,6 @@
     getIFSock().ioctl(SIOCSIFFLAGS, &ifr);
 }
 
-static void parseVlanInfo(InterfaceInfo& info, std::string_view msg)
-{
-    if (msg.data() == nullptr)
-    {
-        throw std::runtime_error("Missing VLAN data");
-    }
-    while (!msg.empty())
-    {
-        auto [hdr, data] = netlink::extractRtAttr(msg);
-        switch (hdr.rta_type)
-        {
-            case IFLA_VLAN_ID:
-                info.vlan_id.emplace(stdplus::raw::copyFrom<uint16_t>(data));
-                break;
-        }
-    }
-}
-
-static void parseLinkInfo(InterfaceInfo& info, std::string_view msg)
-{
-    std::string_view submsg;
-    while (!msg.empty())
-    {
-        auto [hdr, data] = netlink::extractRtAttr(msg);
-        switch (hdr.rta_type)
-        {
-            case IFLA_INFO_KIND:
-                data.remove_suffix(1);
-                info.kind.emplace(data);
-                break;
-            case IFLA_INFO_DATA:
-                submsg = data;
-                break;
-        }
-    }
-    if (info.kind == "vlan"sv)
-    {
-        parseVlanInfo(info, submsg);
-    }
-}
-
-InterfaceInfo detail::parseInterface(const nlmsghdr& hdr, std::string_view msg)
-{
-    if (hdr.nlmsg_type != RTM_NEWLINK)
-    {
-        throw std::runtime_error("Not an interface msg");
-    }
-    const auto& ifinfo = netlink::extractRtData<ifinfomsg>(msg);
-    InterfaceInfo ret;
-    ret.flags = ifinfo.ifi_flags;
-    ret.idx = ifinfo.ifi_index;
-    while (!msg.empty())
-    {
-        auto [hdr, data] = netlink::extractRtAttr(msg);
-        switch (hdr.rta_type)
-        {
-            case IFLA_IFNAME:
-                ret.name.emplace(data.begin(), data.end() - 1);
-                break;
-            case IFLA_ADDRESS:
-                if (data.size() != sizeof(ether_addr))
-                {
-                    // Some interfaces have IP addresses for their LLADDR
-                    break;
-                }
-                ret.mac.emplace(stdplus::raw::copyFrom<ether_addr>(data));
-                break;
-            case IFLA_MTU:
-                ret.mtu.emplace(stdplus::raw::copyFrom<unsigned>(data));
-                break;
-            case IFLA_LINK:
-                ret.parent_idx.emplace(stdplus::raw::copyFrom<unsigned>(data));
-                break;
-            case IFLA_LINKINFO:
-                parseLinkInfo(ret, data);
-                break;
-        }
-    }
-    return ret;
-}
-
 bool detail::validateNewAddr(const AddressInfo& info,
                              const AddressFilter& filter) noexcept
 {
@@ -229,10 +148,10 @@
 std::vector<InterfaceInfo> getInterfaces()
 {
     std::vector<InterfaceInfo> ret;
-    auto cb = [&](const nlmsghdr& hdr, std::string_view msg) {
+    auto cb = [&](const nlmsghdr&, std::string_view msg) {
         try
         {
-            ret.emplace_back(detail::parseInterface(hdr, msg));
+            ret.emplace_back(netlink::intfFromRtm(msg));
         }
         catch (const std::exception& e)
         {
diff --git a/src/system_queries.hpp b/src/system_queries.hpp
index 9ec478d..7785c39 100644
--- a/src/system_queries.hpp
+++ b/src/system_queries.hpp
@@ -42,7 +42,6 @@
 
 namespace detail
 {
-InterfaceInfo parseInterface(const nlmsghdr& hdr, std::string_view msg);
 bool validateNewAddr(const AddressInfo& info,
                      const AddressFilter& filter) noexcept;
 bool validateNewNeigh(const NeighborInfo& info,
diff --git a/test/test_rtnetlink.cpp b/test/test_rtnetlink.cpp
index 7b60072..73a0f00 100644
--- a/test/test_rtnetlink.cpp
+++ b/test/test_rtnetlink.cpp
@@ -10,6 +10,54 @@
 namespace phosphor::network::netlink
 {
 
+TEST(IntfFromRtm, SmallMsg)
+{
+    EXPECT_THROW(intfFromRtm("1"), std::runtime_error);
+}
+
+TEST(IntfFromRtm, NoAttrs)
+{
+    struct
+    {
+        ifinfomsg hdr __attribute__((aligned(NLMSG_ALIGNTO)));
+    } msg;
+    msg.hdr.ifi_index = 1;
+    msg.hdr.ifi_flags = 2;
+    EXPECT_EQ(intfFromRtm(stdplus::raw::asView<char>(msg)),
+              (InterfaceInfo{.idx = 1, .flags = 2}));
+}
+
+TEST(IntfFromRtm, AllAttrs)
+{
+    struct
+    {
+        ifinfomsg hdr __attribute__((aligned(NLMSG_ALIGNTO)));
+        rtattr addr_hdr __attribute__((aligned((RTA_ALIGNTO))));
+        char addr[6]
+            __attribute__((aligned((RTA_ALIGNTO)))) = {0, 1, 2, 3, 4, 5};
+        rtattr name_hdr __attribute__((aligned((RTA_ALIGNTO))));
+        char name[5] __attribute__((aligned((RTA_ALIGNTO)))) = "eth0";
+        rtattr mtu_hdr __attribute__((aligned((RTA_ALIGNTO))));
+        unsigned mtu __attribute__((aligned((RTA_ALIGNTO)))) = 50;
+    } msg;
+    msg.hdr.ifi_index = 1;
+    msg.hdr.ifi_flags = 2;
+    msg.addr_hdr.rta_type = IFLA_ADDRESS;
+    msg.addr_hdr.rta_len = RTA_LENGTH(sizeof(msg.addr));
+    msg.name_hdr.rta_type = IFLA_IFNAME;
+    msg.name_hdr.rta_len = RTA_LENGTH(sizeof(msg.name));
+    msg.mtu_hdr.rta_type = IFLA_MTU;
+    msg.mtu_hdr.rta_len = RTA_LENGTH(sizeof(msg.mtu));
+
+    auto info = intfFromRtm(stdplus::raw::asView<char>(msg));
+    auto expected = InterfaceInfo{.idx = 1,
+                                  .flags = 2,
+                                  .name = "eth0",
+                                  .mac = ether_addr{0, 1, 2, 3, 4, 5},
+                                  .mtu = 50};
+    EXPECT_EQ(info, expected);
+}
+
 TEST(AddrFromRtm, MissingAddr)
 {
     struct
diff --git a/test/test_system_queries.cpp b/test/test_system_queries.cpp
index 5042cc6..e88c64b 100644
--- a/test/test_system_queries.cpp
+++ b/test/test_system_queries.cpp
@@ -1,87 +1,12 @@
 #include "system_queries.hpp"
 
-#include <linux/rtnetlink.h>
-#include <net/if.h>
-
-#include <stdplus/raw.hpp>
-
 #include <gtest/gtest.h>
 
-using std::literals::string_view_literals::operator""sv;
-
 namespace phosphor::network::system
 {
 namespace detail
 {
 
-TEST(ParseInterface, NotLinkType)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWADDR;
-
-    EXPECT_THROW(parseInterface(hdr, ""), std::runtime_error);
-}
-
-TEST(ParseInterface, SmallMsg)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWLINK;
-    auto data = "1"sv;
-
-    EXPECT_THROW(parseInterface(hdr, data), std::runtime_error);
-}
-
-TEST(ParseInterface, NoAttrs)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWLINK;
-    struct
-    {
-        ifinfomsg hdr __attribute__((aligned(NLMSG_ALIGNTO)));
-    } msg;
-    msg.hdr.ifi_index = 1;
-    msg.hdr.ifi_flags = 2;
-    auto data = stdplus::raw::asView<char>(msg);
-
-    auto info = parseInterface(hdr, data);
-    auto expected = InterfaceInfo{.idx = 1, .flags = 2};
-    EXPECT_EQ(info, expected);
-}
-
-TEST(ParseInterface, AllAttrs)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWLINK;
-    struct
-    {
-        ifinfomsg hdr __attribute__((aligned(NLMSG_ALIGNTO)));
-        rtattr addr_hdr __attribute__((aligned((RTA_ALIGNTO))));
-        char addr[6]
-            __attribute__((aligned((RTA_ALIGNTO)))) = {0, 1, 2, 3, 4, 5};
-        rtattr name_hdr __attribute__((aligned((RTA_ALIGNTO))));
-        char name[5] __attribute__((aligned((RTA_ALIGNTO)))) = "eth0";
-        rtattr mtu_hdr __attribute__((aligned((RTA_ALIGNTO))));
-        unsigned mtu __attribute__((aligned((RTA_ALIGNTO)))) = 50;
-    } msg;
-    msg.hdr.ifi_index = 1;
-    msg.hdr.ifi_flags = 2;
-    msg.addr_hdr.rta_type = IFLA_ADDRESS;
-    msg.addr_hdr.rta_len = RTA_LENGTH(sizeof(msg.addr));
-    msg.name_hdr.rta_type = IFLA_IFNAME;
-    msg.name_hdr.rta_len = RTA_LENGTH(sizeof(msg.name));
-    msg.mtu_hdr.rta_type = IFLA_MTU;
-    msg.mtu_hdr.rta_len = RTA_LENGTH(sizeof(msg.mtu));
-    auto data = stdplus::raw::asView<char>(msg);
-
-    auto info = parseInterface(hdr, data);
-    auto expected = InterfaceInfo{.idx = 1,
-                                  .flags = 2,
-                                  .name = "eth0",
-                                  .mac = ether_addr{0, 1, 2, 3, 4, 5},
-                                  .mtu = 50};
-    EXPECT_EQ(info, expected);
-}
-
 TEST(ValidateNewAddr, Filtering)
 {
     AddressInfo info = {};