neighbor: Refactor out netlink code
This will allow us to re-use the generic netlink bits for other netlink
request operations.
Adds test coverage to the netlink message parsing routines for sanity
and regression prevention.
Tested:
Neighbor population still works when static neighbors are created on
the BMC.
Change-Id: I755e86eb76a8f6f825616c13279328134de87da9
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/Makefile.am b/test/Makefile.am
index ffa4655..388f679 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -7,6 +7,8 @@
test_SOURCES = \
test_util.cpp \
mock_syscall.cpp \
+ test_neighbor.cpp \
+ test_netlink.cpp \
test_network_manager.cpp \
test_ethernet_interface.cpp \
test_rtnetlink.cpp \
@@ -50,6 +52,7 @@
$(top_builddir)/network_config.o \
$(top_builddir)/ipaddress.o \
$(top_builddir)/neighbor.o \
+ $(top_builddir)/netlink.o \
$(top_builddir)/routing_table.o \
$(top_builddir)/util.o \
$(top_builddir)/rtnetlink_server.o \
diff --git a/test/test_neighbor.cpp b/test/test_neighbor.cpp
new file mode 100644
index 0000000..a76a7d5
--- /dev/null
+++ b/test/test_neighbor.cpp
@@ -0,0 +1,173 @@
+#include "neighbor.hpp"
+#include "util.hpp"
+
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+
+#include <cstring>
+#include <stdexcept>
+#include <string>
+#include <system_error>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace phosphor
+{
+namespace network
+{
+namespace detail
+{
+
+TEST(ParseNeighbor, NotNeighborType)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWLINK;
+
+ std::vector<NeighborInfo> neighbors;
+ EXPECT_THROW(parseNeighbor(hdr, "", neighbors), std::runtime_error);
+ EXPECT_EQ(0, neighbors.size());
+}
+
+TEST(ParseNeighbor, SmallMsg)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWNEIGH;
+ std::string data = "1";
+
+ std::vector<NeighborInfo> neighbors;
+ EXPECT_THROW(parseNeighbor(hdr, data, neighbors), std::runtime_error);
+ EXPECT_EQ(0, neighbors.size());
+}
+
+TEST(ParseNeighbor, BadIf)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWNEIGH;
+ ndmsg msg{};
+ std::string data;
+ data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
+
+ std::vector<NeighborInfo> neighbors;
+ EXPECT_THROW(parseNeighbor(hdr, data, neighbors), std::system_error);
+ EXPECT_EQ(0, neighbors.size());
+}
+
+TEST(ParseNeighbor, NoAttrs)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWNEIGH;
+ ndmsg msg{};
+ msg.ndm_ifindex = if_nametoindex("lo");
+ ASSERT_NE(0, msg.ndm_ifindex);
+ std::string data;
+ data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
+
+ std::vector<NeighborInfo> neighbors;
+ EXPECT_THROW(parseNeighbor(hdr, data, neighbors), std::runtime_error);
+ EXPECT_EQ(0, neighbors.size());
+}
+
+TEST(ParseNeighbor, NoAddress)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWNEIGH;
+ ndmsg msg{};
+ msg.ndm_ifindex = if_nametoindex("lo");
+ ASSERT_NE(0, msg.ndm_ifindex);
+ ether_addr mac = {{0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}};
+ rtattr lladdr{};
+ lladdr.rta_len = RTA_LENGTH(sizeof(mac));
+ lladdr.rta_type = NDA_LLADDR;
+ char lladdrbuf[RTA_ALIGN(lladdr.rta_len)];
+ std::memset(lladdrbuf, '\0', sizeof(lladdrbuf));
+ std::memcpy(lladdrbuf, &lladdr, sizeof(lladdr));
+ std::memcpy(RTA_DATA(lladdrbuf), &mac, sizeof(mac));
+ std::string data;
+ data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
+ data.append(reinterpret_cast<char*>(&lladdrbuf), sizeof(lladdrbuf));
+
+ std::vector<NeighborInfo> neighbors;
+ EXPECT_THROW(parseNeighbor(hdr, data, neighbors), std::runtime_error);
+ EXPECT_EQ(0, neighbors.size());
+}
+
+TEST(ParseNeighbor, NoMAC)
+{
+ constexpr auto ifstr = "lo";
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWNEIGH;
+ ndmsg msg{};
+ msg.ndm_family = AF_INET;
+ msg.ndm_state = NUD_PERMANENT;
+ msg.ndm_ifindex = if_nametoindex(ifstr);
+ ASSERT_NE(0, msg.ndm_ifindex);
+ in_addr addr;
+ ASSERT_EQ(1, inet_pton(msg.ndm_family, "192.168.10.1", &addr));
+ rtattr dst{};
+ dst.rta_len = RTA_LENGTH(sizeof(addr));
+ dst.rta_type = NDA_DST;
+ char dstbuf[RTA_ALIGN(dst.rta_len)];
+ std::memset(dstbuf, '\0', sizeof(dstbuf));
+ std::memcpy(dstbuf, &dst, sizeof(dst));
+ std::memcpy(RTA_DATA(dstbuf), &addr, sizeof(addr));
+ std::string data;
+ data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
+ data.append(reinterpret_cast<char*>(&dstbuf), sizeof(dstbuf));
+
+ std::vector<NeighborInfo> neighbors;
+ parseNeighbor(hdr, data, neighbors);
+ EXPECT_EQ(1, neighbors.size());
+ EXPECT_EQ(ifstr, neighbors[0].interface);
+ EXPECT_TRUE(neighbors[0].permanent);
+ EXPECT_FALSE(neighbors[0].mac);
+ EXPECT_TRUE(equal(addr, std::get<in_addr>(neighbors[0].address)));
+}
+
+TEST(ParseNeighbor, Full)
+{
+ constexpr auto ifstr = "lo";
+ nlmsghdr hdr{};
+ hdr.nlmsg_type = RTM_NEWNEIGH;
+ ndmsg msg{};
+ msg.ndm_family = AF_INET6;
+ msg.ndm_state = NUD_NOARP;
+ msg.ndm_ifindex = if_nametoindex(ifstr);
+ ether_addr mac = {{0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}};
+ rtattr lladdr{};
+ lladdr.rta_len = RTA_LENGTH(sizeof(mac));
+ lladdr.rta_type = NDA_LLADDR;
+ char lladdrbuf[RTA_ALIGN(lladdr.rta_len)];
+ std::memset(lladdrbuf, '\0', sizeof(lladdrbuf));
+ std::memcpy(lladdrbuf, &lladdr, sizeof(lladdr));
+ std::memcpy(RTA_DATA(lladdrbuf), &mac, sizeof(mac));
+ in6_addr addr;
+ ASSERT_EQ(1, inet_pton(msg.ndm_family, "fd00::1", &addr));
+ rtattr dst{};
+ dst.rta_len = RTA_LENGTH(sizeof(addr));
+ dst.rta_type = NDA_DST;
+ char dstbuf[RTA_ALIGN(dst.rta_len)];
+ std::memset(dstbuf, '\0', sizeof(dstbuf));
+ std::memcpy(dstbuf, &dst, sizeof(dst));
+ std::memcpy(RTA_DATA(dstbuf), &addr, sizeof(addr));
+ std::string data;
+ data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
+ data.append(reinterpret_cast<char*>(&lladdrbuf), sizeof(lladdrbuf));
+ data.append(reinterpret_cast<char*>(&dstbuf), sizeof(dstbuf));
+
+ std::vector<NeighborInfo> neighbors;
+ parseNeighbor(hdr, data, neighbors);
+ EXPECT_EQ(1, neighbors.size());
+ EXPECT_EQ(ifstr, neighbors[0].interface);
+ EXPECT_FALSE(neighbors[0].permanent);
+ EXPECT_TRUE(neighbors[0].mac);
+ EXPECT_EQ(0, std::memcmp(&mac, neighbors[0].mac->data(), sizeof(mac)));
+ EXPECT_TRUE(equal(addr, std::get<in6_addr>(neighbors[0].address)));
+}
+
+} // namespace detail
+} // namespace network
+} // namespace phosphor
diff --git a/test/test_netlink.cpp b/test/test_netlink.cpp
new file mode 100644
index 0000000..b788dd1
--- /dev/null
+++ b/test/test_netlink.cpp
@@ -0,0 +1,290 @@
+#include "netlink.hpp"
+#include "util.hpp"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <cstring>
+#include <stdexcept>
+#include <string_view>
+
+#include <gtest/gtest.h>
+
+namespace phosphor
+{
+namespace network
+{
+namespace netlink
+{
+namespace detail
+{
+
+TEST(ExtractMsgs, TooSmall)
+{
+ const char buf[] = {'1'};
+ static_assert(sizeof(buf) < sizeof(nlmsghdr));
+ std::string_view data(buf, sizeof(buf));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ EXPECT_THROW(processMsg(data, done, cb), std::runtime_error);
+ EXPECT_EQ(1, data.size());
+ EXPECT_EQ(0, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, SmallAttrLen)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0) - 1;
+ std::string_view data(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ EXPECT_THROW(processMsg(data, done, cb), std::runtime_error);
+ EXPECT_EQ(NLMSG_SPACE(0), data.size());
+ EXPECT_EQ(0, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, LargeAttrLen)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0) + 1;
+ std::string_view data(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ EXPECT_THROW(processMsg(data, done, cb), std::runtime_error);
+ EXPECT_EQ(NLMSG_SPACE(0), data.size());
+ EXPECT_EQ(0, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, NoopMsg)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0);
+ hdr.nlmsg_type = NLMSG_NOOP;
+ std::string_view data(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(0, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, AckMsg)
+{
+ nlmsgerr ack{};
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(sizeof(ack));
+ hdr.nlmsg_type = NLMSG_ERROR;
+ char buf[NLMSG_ALIGN(hdr.nlmsg_len)];
+ std::memcpy(buf, &hdr, sizeof(hdr));
+ std::memcpy(NLMSG_DATA(buf), &ack, sizeof(ack));
+ std::string_view data(reinterpret_cast<char*>(&buf), sizeof(buf));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(0, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, ErrMsg)
+{
+ nlmsgerr err{};
+ err.error = EINVAL;
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(sizeof(err));
+ hdr.nlmsg_type = NLMSG_ERROR;
+ char buf[NLMSG_ALIGN(hdr.nlmsg_len)];
+ std::memcpy(buf, &hdr, sizeof(hdr));
+ std::memcpy(NLMSG_DATA(buf), &err, sizeof(err));
+ std::string_view data(reinterpret_cast<char*>(&buf), sizeof(buf));
+
+ size_t cbCalls = 0;
+ nlmsghdr hdrOut;
+ std::string_view dataOut;
+ auto cb = [&](const nlmsghdr& hdr, std::string_view data) {
+ hdrOut = hdr;
+ dataOut = data;
+ cbCalls++;
+ };
+ bool done = true;
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(1, cbCalls);
+ EXPECT_TRUE(equal(hdr, hdrOut));
+ EXPECT_TRUE(equal(err, extract<nlmsgerr>(dataOut)));
+ EXPECT_EQ(0, dataOut.size());
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, DoneNoMulti)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0);
+ hdr.nlmsg_type = NLMSG_DONE;
+ std::string_view data(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ EXPECT_THROW(processMsg(data, done, cb), std::runtime_error);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(0, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsg, TwoMultiMsgs)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0);
+ hdr.nlmsg_type = RTM_NEWLINK;
+ hdr.nlmsg_flags = NLM_F_MULTI;
+ std::string buf;
+ buf.append(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+ buf.append(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ std::string_view data = buf;
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ processMsg(data, done, cb);
+ EXPECT_EQ(NLMSG_SPACE(0), data.size());
+ EXPECT_EQ(1, cbCalls);
+ EXPECT_FALSE(done);
+
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(2, cbCalls);
+ EXPECT_FALSE(done);
+}
+
+TEST(ExtractMsgs, MultiMsgValid)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0);
+ hdr.nlmsg_type = RTM_NEWLINK;
+ hdr.nlmsg_flags = NLM_F_MULTI;
+ std::string_view data(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(1, cbCalls);
+ EXPECT_FALSE(done);
+
+ hdr.nlmsg_type = NLMSG_DONE;
+ hdr.nlmsg_flags = 0;
+ data = std::string_view(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(1, cbCalls);
+ EXPECT_TRUE(done);
+}
+
+TEST(ExtractMsgs, MultiMsgInvalid)
+{
+ nlmsghdr hdr{};
+ hdr.nlmsg_len = NLMSG_LENGTH(0);
+ hdr.nlmsg_type = RTM_NEWLINK;
+ hdr.nlmsg_flags = NLM_F_MULTI;
+ std::string_view data(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+
+ size_t cbCalls = 0;
+ auto cb = [&](const nlmsghdr&, std::string_view) { cbCalls++; };
+ bool done = true;
+ processMsg(data, done, cb);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(1, cbCalls);
+ EXPECT_FALSE(done);
+
+ hdr.nlmsg_flags = 0;
+ data = std::string_view(reinterpret_cast<char*>(&hdr), NLMSG_SPACE(0));
+ EXPECT_THROW(processMsg(data, done, cb), std::runtime_error);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(1, cbCalls);
+ EXPECT_FALSE(done);
+}
+
+} // namespace detail
+
+TEST(ExtractRtAttr, TooSmall)
+{
+ const char buf[] = {'1'};
+ static_assert(sizeof(buf) < sizeof(rtattr));
+ std::string_view data(buf, sizeof(buf));
+
+ EXPECT_THROW(extractRtAttr(data), std::runtime_error);
+ EXPECT_EQ(1, data.size());
+}
+
+TEST(ExtractRtAttr, SmallAttrLen)
+{
+ rtattr rta{};
+ rta.rta_len = RTA_LENGTH(0) - 1;
+ std::string_view data(reinterpret_cast<char*>(&rta), RTA_SPACE(0));
+
+ EXPECT_THROW(extractRtAttr(data), std::runtime_error);
+ EXPECT_EQ(RTA_SPACE(0), data.size());
+}
+
+TEST(ExtractRtAttr, LargeAttrLen)
+{
+ rtattr rta{};
+ rta.rta_len = RTA_LENGTH(0) + 1;
+ std::string_view data(reinterpret_cast<char*>(&rta), RTA_SPACE(0));
+
+ EXPECT_THROW(extractRtAttr(data), std::runtime_error);
+ EXPECT_EQ(RTA_SPACE(0), data.size());
+}
+
+TEST(ExtractRtAttr, NoData)
+{
+ rtattr rta{};
+ rta.rta_len = RTA_LENGTH(0);
+ std::string_view data(reinterpret_cast<char*>(&rta), RTA_SPACE(0));
+
+ auto [hdr, attr] = extractRtAttr(data);
+ EXPECT_EQ(0, data.size());
+ EXPECT_EQ(0, attr.size());
+ EXPECT_EQ(0, std::memcmp(&rta, &hdr, sizeof(rta)));
+}
+
+TEST(ExtractRtAttr, SomeData)
+{
+ const char attrbuf[] = "abcd";
+ const char nextbuf[] = "efgh";
+ rtattr rta{};
+ rta.rta_len = RTA_LENGTH(sizeof(attrbuf));
+
+ char buf[RTA_SPACE(sizeof(attrbuf)) + sizeof(nextbuf)];
+ memcpy(buf, &rta, sizeof(rta));
+ memcpy(RTA_DATA(buf), &attrbuf, sizeof(attrbuf));
+ memcpy(buf + RTA_SPACE(sizeof(attrbuf)), &nextbuf, sizeof(nextbuf));
+ std::string_view data(buf, sizeof(buf));
+
+ auto [hdr, attr] = extractRtAttr(data);
+ EXPECT_EQ(0, memcmp(&rta, &hdr, sizeof(rta)));
+ EXPECT_EQ(sizeof(attrbuf), attr.size());
+ EXPECT_EQ(0, memcmp(&attrbuf, attr.data(), sizeof(attrbuf)));
+ EXPECT_EQ(sizeof(nextbuf), data.size());
+ EXPECT_EQ(0, memcmp(&nextbuf, data.data(), sizeof(nextbuf)));
+}
+
+} // namespace netlink
+} // namespace network
+} // namespace phosphor
diff --git a/test/test_util.cpp b/test/test_util.cpp
index 4e79394..3cf28e3 100644
--- a/test/test_util.cpp
+++ b/test/test_util.cpp
@@ -4,6 +4,9 @@
#include <netinet/in.h>
#include <cstddef>
+#include <cstring>
+#include <string>
+#include <string_view>
#include <xyz/openbmc_project/Common/error.hpp>
#include <gtest/gtest.h>
@@ -13,6 +16,7 @@
namespace network
{
+using namespace std::literals;
using InternalFailure =
sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
class TestUtil : public testing::Test
@@ -268,5 +272,93 @@
EXPECT_EQ("fe80::", address);
}
+TEST_F(TestUtil, CopyFromTooSmall)
+{
+ constexpr auto expected = "abcde"sv;
+ struct
+ {
+ uint8_t data[10];
+ } data;
+ static_assert(sizeof(data) > expected.size());
+ EXPECT_THROW(copyFrom<decltype(data)>(expected), std::runtime_error);
+}
+
+TEST_F(TestUtil, CopyFromSome)
+{
+ constexpr auto expected = "abcde"sv;
+ struct
+ {
+ uint8_t data[2];
+ } data;
+ static_assert(sizeof(data) < expected.size());
+ data = copyFrom<decltype(data)>(expected);
+ EXPECT_EQ(0, memcmp(&data, expected.data(), sizeof(data)));
+}
+
+TEST_F(TestUtil, CopyFromAll)
+{
+ constexpr auto expected = "abcde"sv;
+ struct
+ {
+ uint8_t data[5];
+ } data;
+ static_assert(sizeof(data) == expected.size());
+ data = copyFrom<decltype(data)>(expected);
+ EXPECT_EQ(0, memcmp(&data, expected.data(), sizeof(data)));
+}
+
+TEST_F(TestUtil, ExtractSome)
+{
+ constexpr auto expected = "abcde"sv;
+ auto buf = expected;
+ struct
+ {
+ uint8_t data[2];
+ } data;
+ static_assert(sizeof(data) < expected.size());
+ data = extract<decltype(data)>(buf);
+ EXPECT_EQ(0, memcmp(&data, expected.data(), sizeof(data)));
+ EXPECT_EQ(3, buf.size());
+ EXPECT_EQ(expected.substr(2), buf);
+}
+
+TEST_F(TestUtil, ExtractAll)
+{
+ constexpr auto expected = "abcde"sv;
+ auto buf = expected;
+ struct
+ {
+ uint8_t data[5];
+ } data;
+ static_assert(sizeof(data) == expected.size());
+ data = extract<decltype(data)>(buf);
+ EXPECT_EQ(0, memcmp(&data, expected.data(), sizeof(data)));
+ EXPECT_EQ(0, buf.size());
+}
+
+TEST_F(TestUtil, Equal)
+{
+ struct
+ {
+ int i;
+ } a, b{};
+ a.i = 4;
+ b.i = 4;
+
+ EXPECT_TRUE(equal(a, b));
+}
+
+TEST_F(TestUtil, NotEqual)
+{
+ struct
+ {
+ int i;
+ } a, b{};
+ a.i = 2;
+ b.i = 4;
+
+ EXPECT_FALSE(equal(a, b));
+}
+
} // namespace network
} // namespace phosphor