Merge "health 2.1 impl: log values properly." am: 709382fcc5 am: 5fef7e1de0 am: 98a3f25f5c am: 6deecc5ff9

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1581063

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I7aea61d82c4655685b6513163c70db252224981c
diff --git a/automotive/OWNERS b/automotive/OWNERS
index fb3e3d6..43c5f3e 100644
--- a/automotive/OWNERS
+++ b/automotive/OWNERS
@@ -1,6 +1,6 @@
 pirozzoj@google.com
 twasilczyk@google.com
-pfg@google.com
+krachuri@google.com
 gurunagarajan@google.com
 keunyoung@google.com
 felipeal@google.com
diff --git a/automotive/can/1.0/default/libnl++/Android.bp b/automotive/can/1.0/default/libnl++/Android.bp
index a69e302..9e18ba0 100644
--- a/automotive/can/1.0/default/libnl++/Android.bp
+++ b/automotive/can/1.0/default/libnl++/Android.bp
@@ -26,6 +26,7 @@
         "protocols/generic/Generic.cpp",
         "protocols/generic/GenericMessageBase.cpp",
         "protocols/generic/Unknown.cpp",
+        "protocols/generic/families/Mac80211hwsim.cpp",
         "protocols/generic/families/Nl80211.cpp",
         "protocols/route/Link.cpp",
         "protocols/route/Route.cpp",
@@ -33,6 +34,7 @@
         "protocols/MessageDefinition.cpp",
         "protocols/NetlinkProtocol.cpp",
         "protocols/all.cpp",
+        "protocols/structs.cpp",
         "Attributes.cpp",
         "MessageFactory.cpp",
         "MessageMutator.cpp",
diff --git a/automotive/can/1.0/default/libnl++/MessageMutator.cpp b/automotive/can/1.0/default/libnl++/MessageMutator.cpp
index 00b48a6..de2a2b1 100644
--- a/automotive/can/1.0/default/libnl++/MessageMutator.cpp
+++ b/automotive/can/1.0/default/libnl++/MessageMutator.cpp
@@ -19,7 +19,7 @@
 namespace android::nl {
 
 MessageMutator::MessageMutator(nlmsghdr* buffer, size_t totalLen)
-    : mConstBuffer(buffer, totalLen), mMutableBuffer(buffer) {
+    : mMutableBuffer(buffer), mTotalLen(totalLen) {
     CHECK(totalLen >= sizeof(nlmsghdr));
 }
 
@@ -27,8 +27,12 @@
     return mMutableBuffer;
 }
 
+Buffer<nlmsghdr> MessageMutator::constBuffer() const {
+    return {mMutableBuffer, mTotalLen};
+}
+
 MessageMutator::operator Buffer<nlmsghdr>() const {
-    return mConstBuffer;
+    return constBuffer();
 }
 
 uint64_t MessageMutator::read(Buffer<nlattr> attr) const {
@@ -37,7 +41,8 @@
 
 void MessageMutator::write(Buffer<nlattr> attr, uint64_t val) const {
     const auto attrData = attr.data<uint64_t>();
-    const auto offset = mConstBuffer.getOffset(attrData);
+    // TODO(b/177251183): deduplicate this code against fragment()
+    const auto offset = constBuffer().getOffset(attrData);
     CHECK(offset.has_value()) << "Trying to write attribute that's not a member of this message";
 
     const auto writeableBuffer = reinterpret_cast<uint8_t*>(mMutableBuffer) + *offset;
@@ -47,4 +52,40 @@
     memcpy(writeableBuffer, &val, std::min(sizeof(val), attrSize));
 }
 
+MessageMutator MessageMutator::fragment(Buffer<nlmsghdr> buf) const {
+    const auto offset = constBuffer().getOffset(buf);
+    CHECK(offset.has_value()) << "Trying to modify a fragment outside of buffer range";
+
+    const auto writeableBuffer = reinterpret_cast<nlmsghdr*>(uintptr_t(mMutableBuffer) + *offset);
+    const auto len = buf.getRaw().len();
+    CHECK(len <= mTotalLen - *offset);
+
+    return {writeableBuffer, len};
+}
+
+MessageMutator::iterator MessageMutator::begin() const {
+    return {*this, constBuffer().begin()};
+}
+
+MessageMutator::iterator MessageMutator::end() const {
+    return {*this, constBuffer().end()};
+}
+
+MessageMutator::iterator::iterator(const MessageMutator& container,
+                                   Buffer<nlmsghdr>::iterator current)
+    : mContainer(container), mCurrent(current) {}
+
+MessageMutator::iterator MessageMutator::iterator::operator++() {
+    ++mCurrent;
+    return *this;
+}
+
+bool MessageMutator::iterator::operator==(const iterator& other) const {
+    return other.mCurrent == mCurrent;
+}
+
+const MessageMutator MessageMutator::iterator::operator*() const {
+    return mContainer.fragment(*mCurrent);
+}
+
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h b/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
index d759a0a..4cabb9a 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
@@ -138,7 +138,7 @@
     class raw_iterator : public iterator {
       public:
         iterator operator++() {
-            this->mCurrent.mData++;  // ignore alignment
+            ++this->mCurrent.mData;  // ignore alignment
             return *this;
         }
         const T& operator*() const { return *this->mCurrent.mData; }
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Message.h b/automotive/can/1.0/default/libnl++/include/libnl++/Message.h
index 50b3c4b..29f397d 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Message.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Message.h
@@ -34,7 +34,7 @@
  * a single instance can only be used by a single thread - the one owning the underlying buffer).
  */
 template <typename T>
-class Message {
+class Message : public Buffer<nlmsghdr> {
   public:
     /**
      * Validate buffer contents as a message carrying T data and create instance of parsed message.
@@ -51,7 +51,7 @@
 
         const auto attributes = buf.data<nlattr>(sizeof(T));
 
-        return Message<T>(nlHeader, dataHeader, attributes);
+        return Message<T>(buf, nlHeader, dataHeader, attributes);
     }
 
     /**
@@ -94,8 +94,9 @@
     const T* operator->() const { return &data; }
 
   private:
-    Message(const nlmsghdr& nlHeader, const T& dataHeader, Attributes attributes)
-        : header(nlHeader), data(dataHeader), attributes(attributes) {}
+    Message(Buffer<nlmsghdr> buffer, const nlmsghdr& nlHeader, const T& dataHeader,
+            Attributes attributes)
+        : Buffer<nlmsghdr>(buffer), header(nlHeader), data(dataHeader), attributes(attributes) {}
 };
 
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h b/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h
index 7d495e9..baadc44 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/MessageMutator.h
@@ -53,9 +53,27 @@
      */
     void write(Buffer<nlattr> attr, uint64_t val) const;
 
+    class iterator {
+      public:
+        iterator(const MessageMutator& container, Buffer<nlmsghdr>::iterator current);
+
+        iterator operator++();
+        bool operator==(const iterator& other) const;
+        const MessageMutator operator*() const;
+
+      protected:
+        const MessageMutator& mContainer;
+        Buffer<nlmsghdr>::iterator mCurrent;
+    };
+    iterator begin() const;
+    iterator end() const;
+
   private:
-    const Buffer<nlmsghdr> mConstBuffer;
     nlmsghdr* mMutableBuffer;
+    size_t mTotalLen;
+
+    Buffer<nlmsghdr> constBuffer() const;
+    MessageMutator fragment(Buffer<nlmsghdr> buf) const;
 };
 
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/generic/families/mac80211_hwsim.h b/automotive/can/1.0/default/libnl++/include/libnl++/generic/families/mac80211_hwsim.h
new file mode 100644
index 0000000..9b811f8
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/generic/families/mac80211_hwsim.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// API definitions from kernel drivers/net/wireless/mac80211_hwsim.h
+
+#define BIT(n) (1 << (n))
+
+enum hwsim_tx_control_flags {
+    HWSIM_TX_CTL_REQ_TX_STATUS = BIT(0),
+    HWSIM_TX_CTL_NO_ACK = BIT(1),
+    HWSIM_TX_STAT_ACK = BIT(2),
+};
+
+enum {
+    HWSIM_CMD_UNSPEC,
+    HWSIM_CMD_REGISTER,
+    HWSIM_CMD_FRAME,
+    HWSIM_CMD_TX_INFO_FRAME,
+    HWSIM_CMD_NEW_RADIO,
+    HWSIM_CMD_DEL_RADIO,
+    HWSIM_CMD_GET_RADIO,
+    HWSIM_CMD_ADD_MAC_ADDR,
+    HWSIM_CMD_DEL_MAC_ADDR,
+};
+
+enum {
+    HWSIM_ATTR_UNSPEC,
+    HWSIM_ATTR_ADDR_RECEIVER,
+    HWSIM_ATTR_ADDR_TRANSMITTER,
+    HWSIM_ATTR_FRAME,
+    HWSIM_ATTR_FLAGS,
+    HWSIM_ATTR_RX_RATE,
+    HWSIM_ATTR_SIGNAL,
+    HWSIM_ATTR_TX_INFO,
+    HWSIM_ATTR_COOKIE,
+    HWSIM_ATTR_CHANNELS,
+    HWSIM_ATTR_RADIO_ID,
+    HWSIM_ATTR_REG_HINT_ALPHA2,
+    HWSIM_ATTR_REG_CUSTOM_REG,
+    HWSIM_ATTR_REG_STRICT_REG,
+    HWSIM_ATTR_SUPPORT_P2P_DEVICE,
+    HWSIM_ATTR_USE_CHANCTX,
+    HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
+    HWSIM_ATTR_RADIO_NAME,
+    HWSIM_ATTR_NO_VIF,
+    HWSIM_ATTR_FREQ,
+    HWSIM_ATTR_PAD,
+    HWSIM_ATTR_TX_INFO_FLAGS,
+    HWSIM_ATTR_PERM_ADDR,
+    HWSIM_ATTR_IFTYPE_SUPPORT,
+    HWSIM_ATTR_CIPHER_SUPPORT,
+};
+
+struct hwsim_tx_rate {
+    int8_t idx;
+    uint8_t count;
+} __packed;
+static_assert(sizeof(hwsim_tx_rate) == 2);
+
+#undef BIT
diff --git a/automotive/can/1.0/default/libnl++/printer.cpp b/automotive/can/1.0/default/libnl++/printer.cpp
index f08897e..d540482 100644
--- a/automotive/can/1.0/default/libnl++/printer.cpp
+++ b/automotive/can/1.0/default/libnl++/printer.cpp
@@ -154,16 +154,19 @@
     }
 }
 
-std::string toString(const Buffer<nlmsghdr> hdr, int protocol, bool printPayload) {
-    if (!hdr.firstOk()) return "nlmsg{buffer overflow}";
+static void toStream(std::stringstream& ss, const Buffer<nlmsghdr> hdr, int protocol,
+                     bool printPayload) {
+    if (!hdr.firstOk()) {
+        ss << "nlmsg{buffer overflow}";
+        return;
+    }
 
-    std::stringstream ss;
     ss << std::setfill('0');
 
     auto protocolMaybe = protocols::get(protocol);
     if (!protocolMaybe.has_value()) {
         ss << "nlmsg{protocol=" << protocol << "}";
-        return ss.str();
+        return;
     }
     protocols::NetlinkProtocol& protocolDescr = *protocolMaybe;
 
@@ -187,7 +190,7 @@
     ss << ", crc=" << std::hex << std::setw(4) << crc16(hdr.data<uint8_t>()) << std::dec;
     ss << '}';
 
-    if (!printPayload) return ss.str();
+    if (!printPayload) return;
     ss << ' ';
 
     if (!msgDescMaybe.has_value()) {
@@ -210,6 +213,17 @@
     }
 
     ss << "}";
+}
+
+std::string toString(const Buffer<nlmsghdr> hdrs, int protocol, bool printPayload) {
+    std::stringstream ss;
+    bool first = true;
+    for (const auto hdr : hdrs) {
+        if (!first) ss << std::endl;
+        first = false;
+
+        toStream(ss, hdr, protocol, printPayload);
+    }
 
     return ss.str();
 }
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp
index 1e1ad12..478c383 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/Ctrl.cpp
@@ -16,6 +16,7 @@
 
 #include "Ctrl.h"
 
+#include "families/Mac80211hwsim.h"
 #include "families/Nl80211.h"
 
 #include <libnl++/Message.h>
@@ -68,12 +69,15 @@
     const auto familyId = msg.attributes.get<uint16_t>(CTRL_ATTR_FAMILY_ID);
     const auto familyName = msg.attributes.get<std::string>(CTRL_ATTR_FAMILY_NAME);
 
-    /* For now, we support just a single family. But if you add more, please define proper
+    /* For now, we support just two families. But if you add more, please define proper
      * abstraction and not hardcode every name and class here.
      */
     if (familyName == "nl80211") {
         mFamilyRegister[familyId] = std::make_shared<families::Nl80211>(familyId);
     }
+    if (familyName == "MAC80211_HWSIM") {
+        mFamilyRegister[familyId] = std::make_shared<families::Mac80211hwsim>(familyId);
+    }
 }
 
 }  // namespace android::nl::protocols::generic
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
index b7b811b..f92d6c0 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
@@ -40,9 +40,9 @@
 
     ss << "genlmsghdr{";
     if (commandName.has_value()) {
-        ss << "cmd=" << unsigned(data.cmd);
-    } else {
         ss << "cmd=" << *commandName;
+    } else {
+        ss << "cmd=" << unsigned(data.cmd);
     }
     ss << ", version=" << unsigned(data.version);
     if (data.reserved != 0) ss << ", reserved=" << data.reserved;
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.cpp b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.cpp
new file mode 100644
index 0000000..f85309e
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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 "Mac80211hwsim.h"
+
+#include "../../structs.h"
+#include "common.h"
+
+#include <libnl++/generic/families/mac80211_hwsim.h>
+
+namespace android::nl::protocols::generic::families {
+
+using DataType = AttributeDefinition::DataType;
+using Flags = AttributeDefinition::Flags;
+
+static void hwsim_tx_rateToStream(std::stringstream& ss, const Buffer<nlattr> attr);
+
+static const FlagsMap txControlFlags{
+        {HWSIM_TX_CTL_REQ_TX_STATUS, "REQ_TX"},
+        {HWSIM_TX_CTL_NO_ACK, "NO_ACK"},
+        {HWSIM_TX_STAT_ACK, "ACK"},
+};
+
+// clang-format off
+Mac80211hwsim::Mac80211hwsim(nlmsgtype_t familyId) : GenericMessageBase(familyId, "hwsim", {
+    {HWSIM_CMD_UNSPEC, "UNSPEC"},
+    {HWSIM_CMD_REGISTER, "REGISTER"},
+    {HWSIM_CMD_FRAME, "FRAME"},
+    {HWSIM_CMD_TX_INFO_FRAME, "TX_INFO_FRAME"},
+    {HWSIM_CMD_NEW_RADIO, "NEW_RADIO"},
+    {HWSIM_CMD_DEL_RADIO, "DEL_RADIO"},
+    {HWSIM_CMD_GET_RADIO, "GET_RADIO"},
+    {HWSIM_CMD_ADD_MAC_ADDR, "ADD_MAC_ADDR"},
+    {HWSIM_CMD_DEL_MAC_ADDR, "DEL_MAC_ADDR"},
+}, {
+    {HWSIM_ATTR_UNSPEC, {"UNSPEC"}},
+    {HWSIM_ATTR_ADDR_RECEIVER, {"ADDR_RECEIVER", DataType::Struct, hwaddrToStream}},
+    {HWSIM_ATTR_ADDR_TRANSMITTER, {"ADDR_TRANSMITTER", DataType::Struct, hwaddrToStream}},
+    {HWSIM_ATTR_FRAME, {"FRAME", DataType::Raw, AttributeMap{}, Flags::Verbose}},
+    {HWSIM_ATTR_FLAGS, {"FLAGS", DataType::Struct, flagsToStream(txControlFlags)}},
+    {HWSIM_ATTR_RX_RATE, {"RX_RATE", DataType::Uint}},
+    {HWSIM_ATTR_SIGNAL, {"SIGNAL", DataType::Uint}},
+    {HWSIM_ATTR_TX_INFO, {"TX_INFO", DataType::Struct, hwsim_tx_rateToStream}},
+    {HWSIM_ATTR_COOKIE, {"COOKIE", DataType::Uint}},
+    {HWSIM_ATTR_CHANNELS, {"CHANNELS", DataType::Uint}},
+    {HWSIM_ATTR_RADIO_ID, {"RADIO_ID", DataType::Uint}},
+    {HWSIM_ATTR_REG_HINT_ALPHA2, {"REG_HINT_ALPHA2", DataType::String}},
+    {HWSIM_ATTR_REG_CUSTOM_REG, {"REG_CUSTOM_REG", DataType::Uint}},
+    {HWSIM_ATTR_REG_STRICT_REG, {"REG_STRICT_REG", DataType::Flag}},
+    {HWSIM_ATTR_SUPPORT_P2P_DEVICE, {"SUPPORT_P2P_DEVICE", DataType::Flag}},
+    {HWSIM_ATTR_USE_CHANCTX, {"USE_CHANCTX", DataType::Flag}},
+    {HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE, {"DESTROY_RADIO_ON_CLOSE", DataType::Flag}},
+    {HWSIM_ATTR_RADIO_NAME, {"RADIO_NAME", DataType::String}},
+    {HWSIM_ATTR_NO_VIF, {"NO_VIF", DataType::Flag}},
+    {HWSIM_ATTR_FREQ, {"FREQ", DataType::Uint}},
+    {HWSIM_ATTR_PAD, {"PAD", DataType::Uint}},
+    {HWSIM_ATTR_TX_INFO_FLAGS, {"TX_INFO_FLAGS"}},  // hwsim_tx_rate_flag
+    {HWSIM_ATTR_PERM_ADDR, {"PERM_ADDR"}},
+    {HWSIM_ATTR_IFTYPE_SUPPORT, {"IFTYPE_SUPPORT", DataType::Uint}},  // NL80211_IFTYPE_STATION etc
+    {HWSIM_ATTR_CIPHER_SUPPORT, {"CIPHER_SUPPORT", DataType::Struct, arrayToStream<int32_t>}},
+}) {}
+// clang-format on
+
+static void hwsim_tx_rateToStream(std::stringstream& ss, const Buffer<nlattr> attr) {
+    ss << '{';
+    bool first = true;
+    for (const auto rate : attr.data<hwsim_tx_rate>().getRaw()) {
+        if (rate.idx == -1) continue;
+
+        ss << (int)rate.idx << ": " << (unsigned)rate.count;
+
+        if (!first) ss << ", ";
+        first = false;
+    }
+    ss << '}';
+}
+
+}  // namespace android::nl::protocols::generic::families
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.h b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.h
new file mode 100644
index 0000000..c01eb93
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/families/Mac80211hwsim.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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 "../GenericMessageBase.h"
+
+namespace android::nl::protocols::generic::families {
+
+class Mac80211hwsim : public GenericMessageBase {
+  public:
+    Mac80211hwsim(nlmsgtype_t familyId);
+};
+
+}  // namespace android::nl::protocols::generic::families
diff --git a/automotive/can/1.0/default/libnl++/protocols/structs.cpp b/automotive/can/1.0/default/libnl++/protocols/structs.cpp
new file mode 100644
index 0000000..8ff71f0
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/protocols/structs.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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"
+
+#include <iomanip>
+
+namespace android::nl::protocols {
+
+AttributeDefinition::ToStream flagsToStream(FlagsMap flags) {
+    return [flags](std::stringstream& ss, const Buffer<nlattr> attr) {
+        auto val = attr.data<uint64_t>().copyFirst();
+
+        bool first = true;
+        for (const auto& [flag, name] : flags) {
+            if ((val & flag) != flag) continue;
+            val &= ~flag;
+
+            if (!first) ss << '|';
+            first = false;
+
+            ss << name;
+        }
+
+        if (val == 0) return;
+
+        if (!first) ss << '|';
+        ss << std::hex << val << std::dec;
+    };
+}
+
+void hwaddrToStream(std::stringstream& ss, const Buffer<nlattr> attr) {
+    ss << std::hex;
+    bool first = true;
+    for (const auto byte : attr.data<uint8_t>().getRaw()) {
+        if (!first) ss << ':';
+        first = false;
+
+        ss << std::setw(2) << unsigned(byte);
+    }
+    ss << std::dec;
+}
+
+}  // namespace android::nl::protocols
diff --git a/automotive/can/1.0/default/libnl++/protocols/structs.h b/automotive/can/1.0/default/libnl++/protocols/structs.h
index 44c17b8..f3a8c44 100644
--- a/automotive/can/1.0/default/libnl++/protocols/structs.h
+++ b/automotive/can/1.0/default/libnl++/protocols/structs.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "MessageDefinition.h"
+
 #include <sstream>
 
 namespace android::nl::protocols {
@@ -30,4 +32,9 @@
     ss << '}';
 }
 
+typedef std::map<uint64_t, std::string> FlagsMap;
+AttributeDefinition::ToStream flagsToStream(FlagsMap flags);
+
+void hwaddrToStream(std::stringstream& ss, const Buffer<nlattr> attr);
+
 }  // namespace android::nl::protocols