Implement struct printing

Also:
- fix potential use-after-free in nlbuf iterator
- simplify getFirst usage by migrating from std::optional to std::pair

Bug: 161898189
Test: print all messages from RTMGRP_LINK group

Change-Id: I4aa785ccef60e397ba87088f37e35d9873fc3c82
diff --git a/automotive/can/1.0/default/libnetdevice/Android.bp b/automotive/can/1.0/default/libnetdevice/Android.bp
index 928ad13..d49b9ab 100644
--- a/automotive/can/1.0/default/libnetdevice/Android.bp
+++ b/automotive/can/1.0/default/libnetdevice/Android.bp
@@ -28,6 +28,7 @@
         "protocols/generic/Unknown.cpp",
         "protocols/route/Link.cpp",
         "protocols/route/Route.cpp",
+        "protocols/route/structs.cpp",
         "protocols/MessageDefinition.cpp",
         "protocols/NetlinkProtocol.cpp",
         "protocols/all.cpp",
diff --git a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/nlbuf.h b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/nlbuf.h
index 17815f9..601ab94 100644
--- a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/nlbuf.h
+++ b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/nlbuf.h
@@ -66,9 +66,12 @@
         return mData;
     }
 
-    std::optional<std::reference_wrapper<const T>> getFirst() const {
-        if (!ok()) return std::nullopt;
-        return *mData;
+    std::pair<bool, const T&> getFirst() const {
+        if (!ok()) {
+            static const T dummy = {};
+            return {false, dummy};
+        }
+        return {true, *mData};
     }
 
     /**
@@ -141,7 +144,7 @@
         size_t len() const { return mBuffer.remainingLength(); }
 
       private:
-        const nlbuf<T>& mBuffer;
+        const nlbuf<T> mBuffer;
     };
 
     raw_view getRaw() const { return {*this}; }
@@ -160,8 +163,12 @@
     size_t declaredLength() const {
         // We can't even fit a header, so let's return some absurd high value to trip off
         // buffer overflow checks.
-        if (sizeof(T) > remainingLength()) return std::numeric_limits<size_t>::max() / 2;
-        return declaredLengthImpl();
+        static constexpr size_t badHeaderLength = std::numeric_limits<size_t>::max() / 2;
+
+        if (sizeof(T) > remainingLength()) return badHeaderLength;
+        const auto len = declaredLengthImpl();
+        if (sizeof(T) > len) return badHeaderLength;
+        return len;
     }
 
     size_t remainingLength() const {
diff --git a/automotive/can/1.0/default/libnetdevice/printer.cpp b/automotive/can/1.0/default/libnetdevice/printer.cpp
index 0076dd6..f6c9c60 100644
--- a/automotive/can/1.0/default/libnetdevice/printer.cpp
+++ b/automotive/can/1.0/default/libnetdevice/printer.cpp
@@ -85,17 +85,16 @@
 
     ss << attrtype.name << ": ";
     switch (attrtype.dataType) {
-        case DataType::Raw: {
+        case DataType::Raw:
             toStream(ss, attr.data<uint8_t>());
             break;
-        }
         case DataType::Nested: {
             ss << '{';
             bool first = true;
-            for (auto childattr : attr.data<nlattr>()) {
+            for (const auto childattr : attr.data<nlattr>()) {
                 if (!first) ss << ", ";
                 first = false;
-                toStream(ss, childattr, attrtype.subTypes);
+                toStream(ss, childattr, std::get<protocols::AttributeMap>(attrtype.ops));
             }
             ss << '}';
             break;
@@ -105,9 +104,14 @@
             ss << '"' << sanitize({str.ptr(), str.len()}) << '"';
             break;
         }
-        case DataType::Uint: {
+        case DataType::Uint:
             ss << attr.data<uint32_t>().copyFirst();
             break;
+        case DataType::Struct: {
+            const auto structToStream =
+                    std::get<protocols::AttributeDefinition::ToStream>(attrtype.ops);
+            structToStream(ss, attr);
+            break;
         }
     }
 }
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h b/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h
index a25e885..3a8b2b5 100644
--- a/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h
+++ b/automotive/can/1.0/default/libnetdevice/protocols/MessageDefinition.h
@@ -21,6 +21,7 @@
 
 #include <map>
 #include <sstream>
+#include <variant>
 
 namespace android::netdevice::protocols {
 
@@ -57,11 +58,13 @@
         Nested,
         String,
         Uint,
+        Struct,
     };
+    using ToStream = std::function<void(std::stringstream& ss, const nlbuf<nlattr> attr)>;
 
     std::string name;
     DataType dataType = DataType::Raw;
-    AttributeMap subTypes = {};
+    std::variant<AttributeMap, ToStream> ops = AttributeMap{};
 };
 
 /**
@@ -107,13 +110,13 @@
         : MessageDescriptor(name, messageTypes, attrTypes, sizeof(T)) {}
 
     void dataToStream(std::stringstream& ss, const nlbuf<nlmsghdr> hdr) const override {
-        const auto msg = hdr.data<T>().getFirst();
-        if (!msg.has_value()) {
+        const auto& [ok, msg] = hdr.data<T>().getFirst();
+        if (!ok) {
             ss << "{incomplete payload}";
             return;
         }
 
-        toStream(ss, *msg);
+        toStream(ss, msg);
     }
 
   protected:
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp b/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp
index 08b2be7..4120008 100644
--- a/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp
+++ b/automotive/can/1.0/default/libnetdevice/protocols/generic/Ctrl.cpp
@@ -37,14 +37,14 @@
     {CTRL_ATTR_VERSION, {"VERSION", DataType::Uint}},
     {CTRL_ATTR_HDRSIZE, {"HDRSIZE", DataType::Uint}},
     {CTRL_ATTR_MAXATTR, {"MAXATTR", DataType::Uint}},
-    {CTRL_ATTR_OPS, {"OPS", DataType::Nested, {
-        {std::nullopt, {"OP", DataType::Nested, {
+    {CTRL_ATTR_OPS, {"OPS", DataType::Nested, AttributeMap{
+        {std::nullopt, {"OP", DataType::Nested, AttributeMap{
             {CTRL_ATTR_OP_ID, {"ID", DataType::Uint}},
             {CTRL_ATTR_OP_FLAGS, {"FLAGS", DataType::Uint}},
         }}},
     }}},
-    {CTRL_ATTR_MCAST_GROUPS, {"MCAST_GROUPS", DataType::Nested, {
-        {std::nullopt, {"GRP", DataType::Nested, {
+    {CTRL_ATTR_MCAST_GROUPS, {"MCAST_GROUPS", DataType::Nested, AttributeMap{
+        {std::nullopt, {"GRP", DataType::Nested, AttributeMap{
             {CTRL_ATTR_MCAST_GRP_NAME, {"NAME", DataType::String}},
             {CTRL_ATTR_MCAST_GRP_ID, {"ID", DataType::Uint}},
         }}},
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp b/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp
index 4617d92..53e1700 100644
--- a/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/Link.cpp
@@ -16,6 +16,10 @@
 
 #include "Link.h"
 
+#include "structs.h"
+
+#include <net/if.h>
+
 namespace android::netdevice::protocols::route {
 
 using DataType = AttributeDefinition::DataType;
@@ -29,62 +33,76 @@
     {IFLA_ADDRESS, {"ADDRESS"}},
     {IFLA_BROADCAST, {"BROADCAST"}},
     {IFLA_IFNAME, {"IFNAME", DataType::String}},
-    {IFLA_MTU, {"MTU"}},
+    {IFLA_MTU, {"MTU", DataType::Uint}},
     {IFLA_LINK, {"LINK", DataType::Uint}},
-    {IFLA_QDISC, {"QDISC"}},
-    {IFLA_STATS, {"STATS"}},
+    {IFLA_QDISC, {"QDISC", DataType::String}},
+    {IFLA_STATS, {"STATS", DataType::Struct, statsToStream<rtnl_link_stats>}},
     {IFLA_COST, {"COST"}},
     {IFLA_PRIORITY, {"PRIORITY"}},
-    {IFLA_MASTER, {"MASTER"}},
+    {IFLA_MASTER, {"MASTER", DataType::Uint}},
     {IFLA_WIRELESS, {"WIRELESS"}},
     {IFLA_PROTINFO, {"PROTINFO"}},
-    {IFLA_TXQLEN, {"TXQLEN"}},
-    {IFLA_MAP, {"MAP"}},
-    {IFLA_WEIGHT, {"WEIGHT"}},
-    {IFLA_OPERSTATE, {"OPERSTATE"}},
-    {IFLA_LINKMODE, {"LINKMODE"}},
-    {IFLA_LINKINFO, {"LINKINFO", DataType::Nested, {
+    {IFLA_TXQLEN, {"TXQLEN", DataType::Uint}},
+    {IFLA_MAP, {"MAP", DataType::Struct, mapToStream}},
+    {IFLA_WEIGHT, {"WEIGHT", DataType::Uint}},
+    {IFLA_OPERSTATE, {"OPERSTATE", DataType::Uint}},
+    {IFLA_LINKMODE, {"LINKMODE", DataType::Uint}},
+    {IFLA_LINKINFO, {"LINKINFO", DataType::Nested, AttributeMap{
         {IFLA_INFO_KIND, {"INFO_KIND", DataType::String}},
         {IFLA_INFO_DATA, {"INFO_DATA", DataType::Nested}},
         {IFLA_INFO_XSTATS, {"INFO_XSTATS"}},
-        {IFLA_INFO_SLAVE_KIND, {"INFO_SLAVE_KIND"}},
+        {IFLA_INFO_SLAVE_KIND, {"INFO_SLAVE_KIND", DataType::String}},
         {IFLA_INFO_SLAVE_DATA, {"INFO_SLAVE_DATA"}},
     }}},
-    {IFLA_NET_NS_PID, {"NET_NS_PID"}},
-    {IFLA_IFALIAS, {"IFALIAS"}},
-    {IFLA_NUM_VF, {"NUM_VF"}},
+    {IFLA_NET_NS_PID, {"NET_NS_PID", DataType::Uint}},
+    {IFLA_IFALIAS, {"IFALIAS", DataType::String}},
+    {IFLA_NUM_VF, {"NUM_VF", DataType::Uint}},
     {IFLA_VFINFO_LIST, {"VFINFO_LIST"}},
-    {IFLA_STATS64, {"STATS64"}},
+    {IFLA_STATS64, {"STATS64", DataType::Struct, statsToStream<rtnl_link_stats64>}},
     {IFLA_VF_PORTS, {"VF_PORTS"}},
     {IFLA_PORT_SELF, {"PORT_SELF"}},
-    {IFLA_AF_SPEC, {"AF_SPEC"}},
-    {IFLA_GROUP, {"GROUP"}},
-    {IFLA_NET_NS_FD, {"NET_NS_FD"}},
-    {IFLA_EXT_MASK, {"EXT_MASK"}},
-    {IFLA_PROMISCUITY, {"PROMISCUITY"}},
-    {IFLA_NUM_TX_QUEUES, {"NUM_TX_QUEUES"}},
-    {IFLA_NUM_RX_QUEUES, {"NUM_RX_QUEUES"}},
-    {IFLA_CARRIER, {"CARRIER"}},
+    {IFLA_AF_SPEC, {"AF_SPEC", DataType::Nested, AttributeMap{
+        {AF_INET, {"AF_INET", DataType::Nested, AttributeMap{
+            {IFLA_INET_CONF, {"INET_CONF", DataType::Struct, arrayToStream<int32_t>}},
+        }}},
+        {AF_INET6, {"AF_INET6", DataType::Nested, AttributeMap{
+            {IFLA_INET6_FLAGS, {"INET6_FLAGS", DataType::Uint}},
+            {IFLA_INET6_CONF, {"INET6_CONF", DataType::Struct, arrayToStream<int32_t>}},
+            {IFLA_INET6_STATS, {"INET6_STATS", DataType::Struct, arrayToStream<uint64_t>}},
+            {IFLA_INET6_MCAST, {"INET6_MCAST"}},
+            {IFLA_INET6_CACHEINFO, {"INET6_CACHEINFO", DataType::Struct, ifla_cacheinfoToStream}},
+            {IFLA_INET6_ICMP6STATS, {"INET6_ICMP6STATS", DataType::Struct, arrayToStream<uint64_t>}},
+            {IFLA_INET6_TOKEN, {"INET6_TOKEN"}},
+            {IFLA_INET6_ADDR_GEN_MODE, {"INET6_ADDR_GEN_MODE", DataType::Uint}},
+        }}},
+    }}},
+    {IFLA_GROUP, {"GROUP", DataType::Uint}},
+    {IFLA_NET_NS_FD, {"NET_NS_FD", DataType::Uint}},
+    {IFLA_EXT_MASK, {"EXT_MASK", DataType::Uint}},
+    {IFLA_PROMISCUITY, {"PROMISCUITY", DataType::Uint}},
+    {IFLA_NUM_TX_QUEUES, {"NUM_TX_QUEUES", DataType::Uint}},
+    {IFLA_NUM_RX_QUEUES, {"NUM_RX_QUEUES", DataType::Uint}},
+    {IFLA_CARRIER, {"CARRIER", DataType::Uint}},
     {IFLA_PHYS_PORT_ID, {"PHYS_PORT_ID"}},
-    {IFLA_CARRIER_CHANGES, {"CARRIER_CHANGES"}},
+    {IFLA_CARRIER_CHANGES, {"CARRIER_CHANGES", DataType::Uint}},
     {IFLA_PHYS_SWITCH_ID, {"PHYS_SWITCH_ID"}},
-    {IFLA_LINK_NETNSID, {"LINK_NETNSID"}},
-    {IFLA_PHYS_PORT_NAME, {"PHYS_PORT_NAME"}},
-    {IFLA_PROTO_DOWN, {"PROTO_DOWN"}},
-    {IFLA_GSO_MAX_SEGS, {"GSO_MAX_SEGS"}},
-    {IFLA_GSO_MAX_SIZE, {"GSO_MAX_SIZE"}},
+    {IFLA_LINK_NETNSID, {"LINK_NETNSID"}},  // NLA_S32
+    {IFLA_PHYS_PORT_NAME, {"PHYS_PORT_NAME", DataType::String}},
+    {IFLA_PROTO_DOWN, {"PROTO_DOWN", DataType::Uint}},
+    {IFLA_GSO_MAX_SEGS, {"GSO_MAX_SEGS", DataType::Uint}},
+    {IFLA_GSO_MAX_SIZE, {"GSO_MAX_SIZE", DataType::Uint}},
     {IFLA_PAD, {"PAD"}},
     {IFLA_XDP, {"XDP"}},
-    {IFLA_EVENT, {"EVENT"}},
-    {IFLA_NEW_NETNSID, {"NEW_NETNSID"}},
-    {IFLA_TARGET_NETNSID, {"TARGET_NETNSID"}},
-    {IFLA_CARRIER_UP_COUNT, {"CARRIER_UP_COUNT"}},
-    {IFLA_CARRIER_DOWN_COUNT, {"CARRIER_DOWN_COUNT"}},
-    {IFLA_NEW_IFINDEX, {"NEW_IFINDEX"}},
-    {IFLA_MIN_MTU, {"MIN_MTU"}},
-    {IFLA_MAX_MTU, {"MAX_MTU"}},
+    {IFLA_EVENT, {"EVENT", DataType::Uint}},
+    {IFLA_NEW_NETNSID, {"NEW_NETNSID"}},  // NLA_S32
+    {IFLA_TARGET_NETNSID, {"TARGET_NETNSID"}},  // NLA_S32
+    {IFLA_CARRIER_UP_COUNT, {"CARRIER_UP_COUNT", DataType::Uint}},
+    {IFLA_CARRIER_DOWN_COUNT, {"CARRIER_DOWN_COUNT", DataType::Uint}},
+    {IFLA_NEW_IFINDEX, {"NEW_IFINDEX"}},  // NLA_S32
+    {IFLA_MIN_MTU, {"MIN_MTU", DataType::Uint}},
+    {IFLA_MAX_MTU, {"MAX_MTU", DataType::Uint}},
     {IFLA_PROP_LIST, {"PROP_LIST"}},
-    {IFLA_ALT_IFNAME, {"ALT_IFNAME"}},
+    {IFLA_ALT_IFNAME, {"ALT_IFNAME", DataType::String}},
     {IFLA_PERM_ADDRESS, {"PERM_ADDRESS"}},
 }) {}
 // clang-format off
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/structs.cpp b/automotive/can/1.0/default/libnetdevice/protocols/route/structs.cpp
new file mode 100644
index 0000000..48d64f0
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/structs.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "structs.h"
+
+namespace android::netdevice::protocols::route {
+
+void mapToStream(std::stringstream& ss, const nlbuf<nlattr> attr) {
+    const auto& [ok, data] = attr.data<rtnl_link_ifmap>().getFirst();
+    if (!ok) {
+        ss << "invalid structure";
+        return;
+    }
+    ss << '{'                        //
+       << data.mem_start << ','      //
+       << data.mem_end << ','        //
+       << data.base_addr << ','      //
+       << data.irq << ','            //
+       << unsigned(data.dma) << ','  //
+       << unsigned(data.port) << '}';
+}
+
+void ifla_cacheinfoToStream(std::stringstream& ss, const nlbuf<nlattr> attr) {
+    const auto& [ok, data] = attr.data<ifla_cacheinfo>().getFirst();
+    if (!ok) {
+        ss << "invalid structure";
+        return;
+    }
+    ss << '{'                         //
+       << data.max_reasm_len << ','   //
+       << data.tstamp << ','          //
+       << data.reachable_time << ','  //
+       << data.retrans_time << '}';
+}
+
+}  // namespace android::netdevice::protocols::route
diff --git a/automotive/can/1.0/default/libnetdevice/protocols/route/structs.h b/automotive/can/1.0/default/libnetdevice/protocols/route/structs.h
new file mode 100644
index 0000000..e532704
--- /dev/null
+++ b/automotive/can/1.0/default/libnetdevice/protocols/route/structs.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <libnetdevice/nlbuf.h>
+
+#include <linux/rtnetlink.h>
+
+#include <sstream>
+
+namespace android::netdevice::protocols::route {
+
+// rtnl_link_ifmap
+void mapToStream(std::stringstream& ss, const nlbuf<nlattr> attr);
+
+// ifla_cacheinfo
+void ifla_cacheinfoToStream(std::stringstream& ss, const nlbuf<nlattr> attr);
+
+template <typename T>
+void arrayToStream(std::stringstream& ss, const nlbuf<nlattr> attr) {
+    ss << '{';
+    for (const auto it : attr.data<T>().getRaw()) {
+        ss << it << ',';
+    }
+    ss.seekp(-1, std::ios_base::cur);
+    ss << '}';
+}
+
+// rtnl_link_stats or rtnl_link_stats64
+template <typename T>
+void statsToStream(std::stringstream& ss, const nlbuf<nlattr> attr) {
+    const auto& [ok, data] = attr.data<T>().getFirst();
+    if (!ok) {
+        ss << "invalid structure";
+        return;
+    }
+    ss << '{'                              //
+       << data.rx_packets << ','           //
+       << data.tx_packets << ','           //
+       << data.rx_bytes << ','             //
+       << data.tx_bytes << ','             //
+       << data.rx_errors << ','            //
+       << data.tx_errors << ','            //
+       << data.rx_dropped << ','           //
+       << data.tx_dropped << ','           //
+       << data.multicast << ','            //
+       << data.collisions << ','           //
+       << data.rx_length_errors << ','     //
+       << data.rx_over_errors << ','       //
+       << data.rx_crc_errors << ','        //
+       << data.rx_frame_errors << ','      //
+       << data.rx_fifo_errors << ','       //
+       << data.rx_missed_errors << ','     //
+       << data.tx_aborted_errors << ','    //
+       << data.tx_carrier_errors << ','    //
+       << data.tx_fifo_errors << ','       //
+       << data.tx_heartbeat_errors << ','  //
+       << data.tx_window_errors << ','     //
+       << data.rx_compressed << ','        //
+       << data.tx_compressed << ','        //
+       << data.rx_nohandler << '}';
+}
+
+}  // namespace android::netdevice::protocols::route