Merge changes I8fe05731,Ib71c9288,Ib629cad3

* changes:
  Trim trailing NIL character when fetching string attribute
  Implement message kinds for flag printing
  Refactor MessageFactory
diff --git a/automotive/can/1.0/default/libnetdevice/can.cpp b/automotive/can/1.0/default/libnetdevice/can.cpp
index 5a1105c..083f4f0 100644
--- a/automotive/can/1.0/default/libnetdevice/can.cpp
+++ b/automotive/can/1.0/default/libnetdevice/can.cpp
@@ -34,7 +34,7 @@
 static constexpr can_err_mask_t kErrMask = CAN_ERR_MASK;
 
 base::unique_fd socket(const std::string& ifname) {
-    struct sockaddr_can addr = {};
+    sockaddr_can addr = {};
     addr.can_family = AF_CAN;
     addr.can_ifindex = nametoindex(ifname);
     if (addr.can_ifindex == 0) {
@@ -58,7 +58,7 @@
         return {};
     }
 
-    if (0 != bind(sock.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))) {
+    if (0 != bind(sock.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr))) {
         LOG(ERROR) << "Can't bind to CAN interface " << ifname;
         return {};
     }
@@ -67,26 +67,25 @@
 }
 
 bool setBitrate(std::string ifname, uint32_t bitrate) {
-    struct can_bittiming bt = {};
+    can_bittiming bt = {};
     bt.bitrate = bitrate;
 
-    nl::MessageFactory<struct ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
+    nl::MessageFactory<ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK);
 
-    const auto ifidx = nametoindex(ifname);
-    if (ifidx == 0) {
+    req->ifi_index = nametoindex(ifname);
+    if (req->ifi_index == 0) {
         LOG(ERROR) << "Can't find interface " << ifname;
         return false;
     }
-    req.data().ifi_index = ifidx;
 
     {
-        auto linkinfo = req.nest(IFLA_LINKINFO);
-        req.addattr(IFLA_INFO_KIND, "can");
+        auto linkinfo = req.addNested(IFLA_LINKINFO);
+        req.add(IFLA_INFO_KIND, "can");
         {
-            auto infodata = req.nest(IFLA_INFO_DATA);
+            auto infodata = req.addNested(IFLA_INFO_DATA);
             /* For CAN FD, it would require to add IFLA_CAN_DATA_BITTIMING
              * and IFLA_CAN_CTRLMODE as well. */
-            req.addattr(IFLA_CAN_BITTIMING, bt);
+            req.add(IFLA_CAN_BITTIMING, bt);
         }
     }
 
diff --git a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
index e2ba2cb..9447c0c 100644
--- a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
+++ b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
@@ -62,13 +62,13 @@
 }
 
 bool add(std::string dev, std::string type) {
-    nl::MessageFactory<struct ifinfomsg> req(RTM_NEWLINK,
-                                             NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
-    req.addattr(IFLA_IFNAME, dev);
+    nl::MessageFactory<ifinfomsg> req(RTM_NEWLINK,
+                                      NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
+    req.add(IFLA_IFNAME, dev);
 
     {
-        auto linkinfo = req.nest(IFLA_LINKINFO);
-        req.addattr(IFLA_INFO_KIND, type);
+        auto linkinfo = req.addNested(IFLA_LINKINFO);
+        req.add(IFLA_INFO_KIND, type);
     }
 
     nl::Socket sock(NETLINK_ROUTE);
@@ -76,8 +76,8 @@
 }
 
 bool del(std::string dev) {
-    nl::MessageFactory<struct ifinfomsg> req(RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
-    req.addattr(IFLA_IFNAME, dev);
+    nl::MessageFactory<ifinfomsg> req(RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK);
+    req.add(IFLA_IFNAME, dev);
 
     nl::Socket sock(NETLINK_ROUTE);
     return sock.send(req) && sock.receiveAck(req);
diff --git a/automotive/can/1.0/default/libnetdevice/vlan.cpp b/automotive/can/1.0/default/libnetdevice/vlan.cpp
index 33dc029..ee02f7b 100644
--- a/automotive/can/1.0/default/libnetdevice/vlan.cpp
+++ b/automotive/can/1.0/default/libnetdevice/vlan.cpp
@@ -33,18 +33,18 @@
         return false;
     }
 
-    nl::MessageFactory<struct ifinfomsg> req(RTM_NEWLINK,
-                                             NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
-    req.addattr(IFLA_IFNAME, vlan);
-    req.addattr<uint32_t>(IFLA_LINK, ethidx);
+    nl::MessageFactory<ifinfomsg> req(RTM_NEWLINK,
+                                      NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK);
+    req.add(IFLA_IFNAME, vlan);
+    req.add<uint32_t>(IFLA_LINK, ethidx);
 
     {
-        auto linkinfo = req.nest(IFLA_LINKINFO);
-        req.addattr(IFLA_INFO_KIND, "vlan");
+        auto linkinfo = req.addNested(IFLA_LINKINFO);
+        req.add(IFLA_INFO_KIND, "vlan");
 
         {
-            auto linkinfo = req.nest(IFLA_INFO_DATA);
-            req.addattr(IFLA_VLAN_ID, id);
+            auto linkinfo = req.addNested(IFLA_INFO_DATA);
+            req.add(IFLA_VLAN_ID, id);
         }
     }
 
diff --git a/automotive/can/1.0/default/libnl++/Attributes.cpp b/automotive/can/1.0/default/libnl++/Attributes.cpp
index c101647..620f57b 100644
--- a/automotive/can/1.0/default/libnl++/Attributes.cpp
+++ b/automotive/can/1.0/default/libnl++/Attributes.cpp
@@ -49,7 +49,11 @@
 template <>
 std::string Attributes::parse(Buffer<nlattr> buf) {
     const auto rawString = buf.data<char>().getRaw();
-    return std::string(rawString.ptr(), rawString.len());
+    std::string str(rawString.ptr(), rawString.len());
+
+    str.erase(std::find(str.begin(), str.end(), '\0'), str.end());
+
+    return str;
 }
 
 template <typename T>
diff --git a/automotive/can/1.0/default/libnl++/MessageFactory.cpp b/automotive/can/1.0/default/libnl++/MessageFactory.cpp
index 0c6a331..6f35897 100644
--- a/automotive/can/1.0/default/libnl++/MessageFactory.cpp
+++ b/automotive/can/1.0/default/libnl++/MessageFactory.cpp
@@ -17,41 +17,36 @@
 #include <libnl++/MessageFactory.h>
 
 #include <android-base/logging.h>
+#include <libnl++/bits.h>
 
-// for RTA_ macros missing from NLA_ definitions
-#include <linux/rtnetlink.h>
+namespace android::nl {
 
-namespace android::nl::impl {
-
-static struct nlattr* nlmsg_tail(struct nlmsghdr* n) {
-    return reinterpret_cast<struct nlattr*>(  //
-            reinterpret_cast<uintptr_t>(n) + NLMSG_ALIGN(n->nlmsg_len));
+static nlattr* tail(nlmsghdr* msg) {
+    return reinterpret_cast<nlattr*>(uintptr_t(msg) + impl::align(msg->nlmsg_len));
 }
 
-struct nlattr* addattr_l(struct nlmsghdr* n, size_t maxLen, nlattrtype_t type, const void* data,
-                         size_t dataLen) {
-    size_t newLen = NLMSG_ALIGN(n->nlmsg_len) + RTA_SPACE(dataLen);
+nlattr* MessageFactoryBase::add(nlmsghdr* msg, size_t maxLen, nlattrtype_t type, const void* data,
+                                size_t dataLen) {
+    const auto totalAttrLen = impl::space<nlattr>(dataLen);
+    const auto newLen = impl::align(msg->nlmsg_len) + totalAttrLen;
     if (newLen > maxLen) {
-        LOG(ERROR) << "addattr_l failed - exceeded maxLen: " << newLen << " > " << maxLen;
+        LOG(ERROR) << "Can't add attribute of size " << dataLen  //
+                   << " - exceeded maxLen: " << newLen << " > " << maxLen;
         return nullptr;
     }
 
-    auto attr = nlmsg_tail(n);
-    attr->nla_len = RTA_SPACE(dataLen);
+    auto attr = tail(msg);
+    attr->nla_len = totalAttrLen;
     attr->nla_type = type;
-    if (dataLen > 0) memcpy(RTA_DATA(attr), data, dataLen);
+    if (dataLen > 0) memcpy(impl::data<nlattr, void>(attr), data, dataLen);
 
-    n->nlmsg_len = newLen;
+    msg->nlmsg_len = newLen;
     return attr;
 }
 
-struct nlattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, nlattrtype_t type) {
-    return addattr_l(n, maxLen, type, nullptr, 0);
+void MessageFactoryBase::closeNested(nlmsghdr* msg, nlattr* nested) {
+    if (nested == nullptr) return;
+    nested->nla_len = uintptr_t(tail(msg)) - uintptr_t(nested);
 }
 
-void addattr_nest_end(struct nlmsghdr* n, struct nlattr* nest) {
-    size_t nestLen = reinterpret_cast<uintptr_t>(nlmsg_tail(n)) - reinterpret_cast<uintptr_t>(nest);
-    nest->nla_len = nestLen;
-}
-
-}  // namespace android::nl::impl
+}  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/Socket.cpp b/automotive/can/1.0/default/libnl++/Socket.cpp
index 1a34df8..9e96ea9 100644
--- a/automotive/can/1.0/default/libnl++/Socket.cpp
+++ b/automotive/can/1.0/default/libnl++/Socket.cpp
@@ -49,8 +49,8 @@
 
 bool Socket::send(const Buffer<nlmsghdr>& msg, const sockaddr_nl& sa) {
     if constexpr (kSuperVerbose) {
-        LOG(VERBOSE) << (mFailed ? "(not) " : "") << "sending Netlink message ("  //
-                     << msg->nlmsg_pid << " -> " << sa.nl_pid << "): " << toString(msg, mProtocol);
+        LOG(VERBOSE) << (mFailed ? "(not) " : "") << "sending to " << sa.nl_pid << ": "
+                     << toString(msg, mProtocol);
     }
     if (mFailed) return false;
 
@@ -97,8 +97,7 @@
 
     Buffer<nlmsghdr> msg(reinterpret_cast<nlmsghdr*>(mReceiveBuffer.data()), bytesReceived);
     if constexpr (kSuperVerbose) {
-        LOG(VERBOSE) << "received (" << sa.nl_pid << " -> " << msg->nlmsg_pid << "):"  //
-                     << toString(msg, mProtocol);
+        LOG(VERBOSE) << "received from " << sa.nl_pid << ": " << toString(msg, mProtocol);
     }
     return {msg, sa};
 }
diff --git a/automotive/can/1.0/default/libnl++/common.cpp b/automotive/can/1.0/default/libnl++/common.cpp
index 74eee24..23c2d94 100644
--- a/automotive/can/1.0/default/libnl++/common.cpp
+++ b/automotive/can/1.0/default/libnl++/common.cpp
@@ -32,9 +32,7 @@
     return 0;
 }
 
-std::string sanitize(std::string str) {
-    str.erase(std::find(str.begin(), str.end(), '\0'), str.end());
-
+std::string printableOnly(std::string str) {
     const auto isInvalid = [](char c) { return !isprint(c); };
     std::replace_if(str.begin(), str.end(), isInvalid, '?');
 
diff --git a/automotive/can/1.0/default/libnl++/common.h b/automotive/can/1.0/default/libnl++/common.h
index 1d9dbab..38263c5 100644
--- a/automotive/can/1.0/default/libnl++/common.h
+++ b/automotive/can/1.0/default/libnl++/common.h
@@ -37,11 +37,14 @@
 unsigned int nametoindex(const std::string& ifname);
 
 /**
- * Sanitize a string of unknown contents.
+ * Filter a string against non-printable characters.
  *
- * Trims the string to the first '\0' character and replaces all non-printable characters with '?'.
+ * Replaces all non-printable characters with '?'.
+ *
+ * \param str String to filter.
+ * \return Filtered string.
  */
-std::string sanitize(std::string str);
+std::string printableOnly(std::string str);
 
 /**
  * Calculates a (optionally running) CRC16 checksum.
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 a6f8f7a..c3137c8 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Buffer.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <android-base/logging.h>
+#include <libnl++/bits.h>
 
 #include <linux/netlink.h>
 
@@ -25,7 +26,7 @@
 namespace android::nl {
 
 /**
- * Buffer wrapper containing netlink structure (e.g. struct nlmsghdr, struct nlattr).
+ * Buffer wrapper containing netlink structure (e.g. nlmsghdr, nlattr).
  *
  * This is a C++-style, memory safe(r) and generic implementation of linux/netlink.h macros.
  *
@@ -43,15 +44,6 @@
  */
 template <typename T>
 class Buffer {
-    // The following definitions are C++ equivalents of NLMSG_* macros from linux/netlink.h
-
-    static constexpr size_t alignto = NLMSG_ALIGNTO;
-    static_assert(NLMSG_ALIGNTO == NLA_ALIGNTO);
-
-    static constexpr size_t align(size_t ptr) { return (ptr + alignto - 1) & ~(alignto - 1); }
-
-    static constexpr size_t hdrlen = align(sizeof(T));
-
   public:
     /**
      * Constructs empty buffer of size 0.
@@ -96,9 +88,7 @@
 
     template <typename D>
     const Buffer<D> data(size_t offset = 0) const {
-        // Equivalent to NLMSG_DATA(hdr) + NLMSG_ALIGN(offset)
-        const D* dptr = reinterpret_cast<const D*>(uintptr_t(mData) + hdrlen + align(offset));
-        return {dptr, dataEnd()};
+        return {impl::data<const T, const D>(mData, offset), dataEnd()};
     }
 
     class iterator {
@@ -111,7 +101,7 @@
         iterator operator++() {
             // mBufferEnd stays the same
             mCurrent.mData = reinterpret_cast<const T*>(  //
-                    uintptr_t(mCurrent.mData) + align(mCurrent.declaredLength()));
+                    uintptr_t(mCurrent.mData) + impl::align(mCurrent.declaredLength()));
 
             return *this;
         }
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/MessageFactory.h b/automotive/can/1.0/default/libnl++/include/libnl++/MessageFactory.h
index 5272577..c3d72c5 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/MessageFactory.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/MessageFactory.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <android-base/macros.h>
+#include <libnl++/Buffer.h>
 #include <libnl++/types.h>
 
 #include <linux/netlink.h>
@@ -25,131 +26,140 @@
 
 namespace android::nl {
 
-/** Implementation details, do not use outside MessageFactory template. */
-namespace impl {
-
-struct nlattr* addattr_l(struct nlmsghdr* n, size_t maxLen, nlattrtype_t type, const void* data,
-                         size_t dataLen);
-struct nlattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, nlattrtype_t type);
-void addattr_nest_end(struct nlmsghdr* n, struct nlattr* nest);
-
-}  // namespace impl
+class MessageFactoryBase {
+  protected:
+    static nlattr* add(nlmsghdr* msg, size_t maxLen, nlattrtype_t type, const void* data,
+                       size_t dataLen);
+    static void closeNested(nlmsghdr* msg, nlattr* nested);
+};
 
 /**
  * Wrapper around NETLINK_ROUTE messages, to build them in C++ style.
  *
- * \param T specific message header (such as struct ifinfomsg)
- * \param BUFSIZE how much space to reserve for payload (not counting the header size)
+ * \param T Message payload type (such as ifinfomsg).
+ * \param BUFSIZE how much space to reserve for attributes.
  */
 template <class T, unsigned int BUFSIZE = 128>
-struct MessageFactory {
-    struct RequestData {
-        struct nlmsghdr nlmsg;
+class MessageFactory : private MessageFactoryBase {
+    struct alignas(NLMSG_ALIGNTO) Message {
+        nlmsghdr header;
         T data;
-        char buf[BUFSIZE];
+        uint8_t attributesBuffer[BUFSIZE];
     };
 
-    static constexpr size_t totalLength = sizeof(RequestData);
-
+  public:
     /**
      * Create empty message.
      *
-     * \param type Message type (such as RTM_NEWLINK)
-     * \param flags Message flags (such as NLM_F_REQUEST)
+     * \param type Message type (such as RTM_NEWLINK).
+     * \param flags Message flags (such as NLM_F_REQUEST).
      */
-    MessageFactory(nlmsgtype_t type, uint16_t flags) {
-        mRequest.nlmsg.nlmsg_len = NLMSG_LENGTH(sizeof(mRequest.data));
-        mRequest.nlmsg.nlmsg_type = type;
-        mRequest.nlmsg.nlmsg_flags = flags;
+    MessageFactory(nlmsgtype_t type, uint16_t flags)
+        : header(mMessage.header), data(mMessage.data) {
+        mMessage.header.nlmsg_len = offsetof(Message, attributesBuffer);
+        mMessage.header.nlmsg_type = type;
+        mMessage.header.nlmsg_flags = flags;
     }
 
-    /** \return pointer to raw netlink message header. */
-    struct nlmsghdr* header() {
-        return &mRequest.nlmsg;
-    }
-    /** Reference to message-specific header. */
-    T& data() { return mRequest.data; }
-
     /**
-     * Adds an attribute of a simple type.
+     * Netlink message header.
      *
-     * If this method fails (i.e. due to insufficient space), the message will be marked
-     * as bad (\see isGood).
+     * This is a generic Netlink header containing information such as message flags.
+     */
+    nlmsghdr& header;
+
+    /**
+     * Netlink message data.
+     *
+     * This is a payload specific to a given message type.
+     */
+    T& data;
+
+    T* operator->() { return &mMessage.data; }
+
+    /**
+     * Build netlink message.
+     *
+     * In fact, this operation is almost a no-op, since the factory builds the message in a single
+     * buffer, using native data structures.
+     *
+     * A likely failure case is when the BUFSIZE template parameter is too small to acommodate
+     * added attributes. In such a case, please increase this parameter.
+     *
+     * \return Netlink message or std::nullopt in case of failure.
+     */
+    std::optional<Buffer<nlmsghdr>> build() const {
+        if (!mIsGood) return std::nullopt;
+        return {{&mMessage.header, mMessage.header.nlmsg_len}};
+    }
+
+    /**
+     * Adds an attribute of a trivially copyable type.
+     *
+     * Template specializations may extend this function for other types, such as std::string.
+     *
+     * If this method fails (i.e. due to insufficient space), a warning will be printed to the log
+     * and the message will be marked as bad, causing later \see build call to fail.
      *
      * \param type attribute type (such as IFLA_IFNAME)
      * \param attr attribute data
      */
     template <class A>
-    void addattr(nlattrtype_t type, const A& attr) {
-        if (!mIsGood) return;
-        auto ap = impl::addattr_l(&mRequest.nlmsg, sizeof(mRequest), type, &attr, sizeof(attr));
-        if (ap == nullptr) mIsGood = false;
+    void add(nlattrtype_t type, const A& attr) {
+        add(type, &attr, sizeof(attr));
     }
 
     template <>
-    void addattr(nlattrtype_t type, const std::string& s) {
-        if (!mIsGood) return;
-        auto ap = impl::addattr_l(&mRequest.nlmsg, sizeof(mRequest), type, s.c_str(), s.size() + 1);
-        if (ap == nullptr) mIsGood = false;
+    void add(nlattrtype_t type, const std::string& s) {
+        add(type, s.c_str(), s.size() + 1);
     }
 
-    /** Guard class to frame nested attributes. See nest(int). */
-    struct Nest {
-        Nest(MessageFactory& req, nlattrtype_t type) : mReq(req), mAttr(req.nestStart(type)) {}
-        ~Nest() { mReq.nestEnd(mAttr); }
+    /** Guard class to frame nested attributes. \see addNested(nlattrtype_t). */
+    class [[nodiscard]] NestedGuard {
+      public:
+        NestedGuard(MessageFactory & req, nlattrtype_t type) : mReq(req), mAttr(req.add(type)) {}
+        ~NestedGuard() { closeNested(&mReq.mMessage.header, mAttr); }
 
       private:
         MessageFactory& mReq;
-        struct nlattr* mAttr;
+        nlattr* mAttr;
 
-        DISALLOW_COPY_AND_ASSIGN(Nest);
+        DISALLOW_COPY_AND_ASSIGN(NestedGuard);
     };
 
     /**
      * Add nested attribute.
      *
      * The returned object is a guard for auto-nesting children inside the argument attribute.
-     * When the Nest object goes out of scope, the nesting attribute is closed.
+     * When the guard object goes out of scope, the nesting attribute is closed.
      *
      * Example usage nesting IFLA_CAN_BITTIMING inside IFLA_INFO_DATA, which is nested
      * inside IFLA_LINKINFO:
-     *    MessageFactory<struct ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST);
+     *    MessageFactory<ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST);
      *    {
-     *        auto linkinfo = req.nest(IFLA_LINKINFO);
-     *        req.addattr(IFLA_INFO_KIND, "can");
+     *        auto linkinfo = req.addNested(IFLA_LINKINFO);
+     *        req.add(IFLA_INFO_KIND, "can");
      *        {
-     *            auto infodata = req.nest(IFLA_INFO_DATA);
-     *            req.addattr(IFLA_CAN_BITTIMING, bitTimingStruct);
+     *            auto infodata = req.addNested(IFLA_INFO_DATA);
+     *            req.add(IFLA_CAN_BITTIMING, bitTimingStruct);
      *        }
      *    }
      *    // use req
      *
      * \param type attribute type (such as IFLA_LINKINFO)
      */
-    Nest nest(int type) { return Nest(*this, type); }
-
-    /**
-     * Indicates, whether the message is in a good state.
-     *
-     * The bad state is usually a result of payload buffer being too small.
-     * You can modify BUFSIZE template parameter to fix this.
-     */
-    bool isGood() const { return mIsGood; }
+    NestedGuard addNested(nlattrtype_t type) { return {*this, type}; }
 
   private:
+    Message mMessage = {};
     bool mIsGood = true;
-    RequestData mRequest = {};
 
-    struct nlattr* nestStart(nlattrtype_t type) {
+    nlattr* add(nlattrtype_t type, const void* data = nullptr, size_t len = 0) {
         if (!mIsGood) return nullptr;
-        auto attr = impl::addattr_nest(&mRequest.nlmsg, sizeof(mRequest), type);
+        auto attr = MessageFactoryBase::add(&mMessage.header, sizeof(mMessage), type, data, len);
         if (attr == nullptr) mIsGood = false;
         return attr;
     }
-
-    void nestEnd(struct nlattr* nest) {
-        if (mIsGood && nest != nullptr) impl::addattr_nest_end(&mRequest.nlmsg, nest);
-    }
 };
 
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h b/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
index 16b63f5..6a4e82e 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/Socket.h
@@ -76,13 +76,12 @@
      */
     template <typename T, unsigned BUFSIZE>
     bool send(MessageFactory<T, BUFSIZE>& req, const sockaddr_nl& sa) {
-        if (!req.isGood()) return false;
+        req.header.nlmsg_seq = mSeq + 1;
 
-        const auto nlmsg = req.header();
-        nlmsg->nlmsg_seq = mSeq + 1;
+        const auto msg = req.build();
+        if (!msg.has_value()) return false;
 
-        // With MessageFactory<>, we trust nlmsg_len to be correct.
-        return send({nlmsg, nlmsg->nlmsg_len}, sa);
+        return send(*msg, sa);
     }
 
     /**
@@ -156,7 +155,7 @@
      */
     template <typename T, unsigned BUFSIZE>
     bool receiveAck(MessageFactory<T, BUFSIZE>& req) {
-        return receiveAck(req.header()->nlmsg_seq);
+        return receiveAck(req.header.nlmsg_seq);
     }
 
     /**
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/bits.h b/automotive/can/1.0/default/libnl++/include/libnl++/bits.h
new file mode 100644
index 0000000..4c8f1aa
--- /dev/null
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/bits.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <linux/netlink.h>
+
+namespace android::nl::impl {
+
+// The following definitions are C++ equivalents of NLMSG_* macros from linux/netlink.h.
+
+/**
+ * Equivalent to NLMSG_ALIGNTO.
+ */
+constexpr size_t alignto = NLMSG_ALIGNTO;
+static_assert(NLMSG_ALIGNTO == NLA_ALIGNTO);
+
+/**
+ * Equivalent to NLMSG_ALIGN(len).
+ */
+constexpr size_t align(size_t len) {
+    return (len + alignto - 1) & ~(alignto - 1);
+}
+
+/**
+ * Equivalent to NLMSG_SPACE(len).
+ */
+template <typename H>
+constexpr size_t space(size_t len) {
+    return align(align(sizeof(H)) + len);
+}
+
+/**
+ * Equivalent to NLMSG_DATA(hdr) + NLMSG_ALIGN(offset).
+ */
+template <typename H, typename D>
+constexpr D* data(H* header, size_t offset = 0) {
+    return reinterpret_cast<D*>(uintptr_t(header) + space<H>(offset));
+}
+
+}  // namespace android::nl::impl
diff --git a/automotive/can/1.0/default/libnl++/include/libnl++/printer.h b/automotive/can/1.0/default/libnl++/include/libnl++/printer.h
index 53a06d8..3570918 100644
--- a/automotive/can/1.0/default/libnl++/include/libnl++/printer.h
+++ b/automotive/can/1.0/default/libnl++/include/libnl++/printer.h
@@ -32,6 +32,6 @@
  * \param printPayload True will stringify message data, false will only stringify the header(s).
  * \return Stringified message.
  */
-std::string toString(const Buffer<nlmsghdr> hdr, int protocol, bool printPayload = false);
+std::string toString(const Buffer<nlmsghdr> hdr, int protocol, bool printPayload = true);
 
 }  // namespace android::nl
diff --git a/automotive/can/1.0/default/libnl++/printer.cpp b/automotive/can/1.0/default/libnl++/printer.cpp
index 9735db1..e6cada2 100644
--- a/automotive/can/1.0/default/libnl++/printer.cpp
+++ b/automotive/can/1.0/default/libnl++/printer.cpp
@@ -28,10 +28,10 @@
 
 namespace android::nl {
 
-static void flagsToStream(std::stringstream& ss, __u16 nlmsg_flags) {
+static void flagsToStream(std::stringstream& ss, __u16 nlmsg_flags, protocols::MessageGenre genre) {
     bool first = true;
     auto printFlag = [&ss, &first, &nlmsg_flags](__u16 flag, const std::string& name) {
-        if (!(nlmsg_flags & flag)) return;
+        if ((nlmsg_flags & flag) != flag) return;
         nlmsg_flags &= ~flag;
 
         if (first) {
@@ -42,6 +42,7 @@
 
         ss << name;
     };
+
     printFlag(NLM_F_REQUEST, "REQUEST");
     printFlag(NLM_F_MULTI, "MULTI");
     printFlag(NLM_F_ACK, "ACK");
@@ -49,11 +50,29 @@
     printFlag(NLM_F_DUMP_INTR, "DUMP_INTR");
     printFlag(NLM_F_DUMP_FILTERED, "DUMP_FILTERED");
 
-    // TODO(twasilczyk): print flags depending on request type
-    printFlag(NLM_F_ROOT, "ROOT-REPLACE");
-    printFlag(NLM_F_MATCH, "MATCH-EXCL");
-    printFlag(NLM_F_ATOMIC, "ATOMIC-CREATE");
-    printFlag(NLM_F_APPEND, "APPEND");
+    switch (genre) {
+        case protocols::MessageGenre::UNKNOWN:
+            break;
+        case protocols::MessageGenre::GET:
+            printFlag(NLM_F_DUMP, "DUMP");  // ROOT | MATCH
+            printFlag(NLM_F_ROOT, "ROOT");
+            printFlag(NLM_F_MATCH, "MATCH");
+            printFlag(NLM_F_ATOMIC, "ATOMIC");
+            break;
+        case protocols::MessageGenre::NEW:
+            printFlag(NLM_F_REPLACE, "REPLACE");
+            printFlag(NLM_F_EXCL, "EXCL");
+            printFlag(NLM_F_CREATE, "CREATE");
+            printFlag(NLM_F_APPEND, "APPEND");
+            break;
+        case protocols::MessageGenre::DELETE:
+            printFlag(NLM_F_NONREC, "NONREC");
+            break;
+        case protocols::MessageGenre::ACK:
+            printFlag(NLM_F_CAPPED, "CAPPED");
+            printFlag(NLM_F_ACK_TLVS, "ACK_TLVS");
+            break;
+    }
 
     if (nlmsg_flags != 0) {
         if (!first) ss << '|';
@@ -100,7 +119,7 @@
         }
         case DataType::String: {
             const auto str = attr.data<char>().getRaw();
-            ss << '"' << sanitize({str.ptr(), str.len()}) << '"';
+            ss << '"' << printableOnly({str.ptr(), str.len()}) << '"';
             break;
         }
         case DataType::Uint:
@@ -128,27 +147,26 @@
     }
     protocols::NetlinkProtocol& protocolDescr = *protocolMaybe;
 
-    auto msgDescMaybe = protocolDescr.getMessageDescriptor(hdr->nlmsg_type);
-    const auto msgTypeName = msgDescMaybe.has_value()
-                                     ? msgDescMaybe->get().getMessageName(hdr->nlmsg_type)
-                                     : std::to_string(hdr->nlmsg_type);
+    const auto msgDescMaybe = protocolDescr.getMessageDescriptor(hdr->nlmsg_type);
+    const auto msgDetails =
+            protocols::MessageDescriptor::getMessageDetails(msgDescMaybe, hdr->nlmsg_type);
 
     ss << "nlmsg{" << protocolDescr.getName() << " ";
 
     ss << "hdr={";
-    ss << "type=" << msgTypeName;
+    ss << "type=" << msgDetails.name;
     if (hdr->nlmsg_flags != 0) {
         ss << ", flags=";
-        flagsToStream(ss, hdr->nlmsg_flags);
+        flagsToStream(ss, hdr->nlmsg_flags, msgDetails.genre);
     }
     if (hdr->nlmsg_seq != 0) ss << ", seq=" << hdr->nlmsg_seq;
     if (hdr->nlmsg_pid != 0) ss << ", pid=" << hdr->nlmsg_pid;
     ss << ", len=" << hdr->nlmsg_len;
-
     ss << ", crc=" << std::hex << std::setw(4) << crc16(hdr.data<uint8_t>()) << std::dec;
-    ss << "} ";
+    ss << '}';
 
     if (!printPayload) return ss.str();
+    ss << ' ';
 
     if (!msgDescMaybe.has_value()) {
         toStream(ss, hdr.data<uint8_t>());
diff --git a/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.cpp b/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.cpp
index dc56643..c93d865 100644
--- a/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.cpp
@@ -32,11 +32,12 @@
     return find(nla_type)->second;
 }
 
-MessageDescriptor::MessageDescriptor(const std::string& name, const MessageTypeMap&& messageTypes,
+MessageDescriptor::MessageDescriptor(const std::string& name,
+                                     const MessageDetailsMap&& messageDetails,
                                      const AttributeMap&& attrTypes, size_t contentsSize)
     : mName(name),
       mContentsSize(contentsSize),
-      mMessageTypes(messageTypes),
+      mMessageDetails(messageDetails),
       mAttributeMap(attrTypes) {}
 
 MessageDescriptor::~MessageDescriptor() {}
@@ -45,18 +46,25 @@
     return mContentsSize;
 }
 
-const MessageDescriptor::MessageTypeMap& MessageDescriptor::getMessageTypeMap() const {
-    return mMessageTypes;
+const MessageDescriptor::MessageDetailsMap& MessageDescriptor::getMessageDetailsMap() const {
+    return mMessageDetails;
 }
 
 const AttributeMap& MessageDescriptor::getAttributeMap() const {
     return mAttributeMap;
 }
 
-const std::string MessageDescriptor::getMessageName(nlmsgtype_t msgtype) const {
-    const auto it = mMessageTypes.find(msgtype);
-    if (it == mMessageTypes.end()) return "?";
+MessageDescriptor::MessageDetails MessageDescriptor::getMessageDetails(nlmsgtype_t msgtype) const {
+    const auto it = mMessageDetails.find(msgtype);
+    if (it == mMessageDetails.end()) return {std::to_string(msgtype), MessageGenre::UNKNOWN};
     return it->second;
 }
 
+MessageDescriptor::MessageDetails MessageDescriptor::getMessageDetails(
+        const std::optional<std::reference_wrapper<const MessageDescriptor>>& msgDescMaybe,
+        nlmsgtype_t msgtype) {
+    if (msgDescMaybe.has_value()) return msgDescMaybe->get().getMessageDetails(msgtype);
+    return {std::to_string(msgtype), protocols::MessageGenre::UNKNOWN};
+}
+
 }  // namespace android::nl::protocols
diff --git a/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.h b/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.h
index ef73d09..bd0e60f 100644
--- a/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.h
+++ b/automotive/can/1.0/default/libnl++/protocols/MessageDefinition.h
@@ -68,30 +68,53 @@
 };
 
 /**
+ * General message type's kind.
+ *
+ * For example, RTM_NEWLINK is a NEW kind. For details, please see "Flags values"
+ * section in linux/netlink.h.
+ */
+enum class MessageGenre {
+    UNKNOWN,
+    GET,
+    NEW,
+    DELETE,
+    ACK,
+};
+
+/**
  * Message family descriptor.
  *
  * Describes the structure of all message types with the same header and attributes.
  */
 class MessageDescriptor {
-  protected:
-    typedef std::map<nlmsgtype_t, std::string> MessageTypeMap;
-
-    MessageDescriptor(const std::string& name, const MessageTypeMap&& messageTypes,
-                      const AttributeMap&& attrTypes, size_t contentsSize);
+  public:
+    struct MessageDetails {
+        std::string name;
+        MessageGenre genre;
+    };
+    typedef std::map<nlmsgtype_t, MessageDetails> MessageDetailsMap;
 
   public:
     virtual ~MessageDescriptor();
 
     size_t getContentsSize() const;
-    const MessageTypeMap& getMessageTypeMap() const;
+    const MessageDetailsMap& getMessageDetailsMap() const;
     const AttributeMap& getAttributeMap() const;
-    const std::string getMessageName(nlmsgtype_t msgtype) const;
+    MessageDetails getMessageDetails(nlmsgtype_t msgtype) const;
     virtual void dataToStream(std::stringstream& ss, const Buffer<nlmsghdr> hdr) const = 0;
 
+    static MessageDetails getMessageDetails(
+            const std::optional<std::reference_wrapper<const MessageDescriptor>>& msgDescMaybe,
+            nlmsgtype_t msgtype);
+
+  protected:
+    MessageDescriptor(const std::string& name, const MessageDetailsMap&& messageDetails,
+                      const AttributeMap&& attrTypes, size_t contentsSize);
+
   private:
     const std::string mName;
     const size_t mContentsSize;
-    const MessageTypeMap mMessageTypes;
+    const MessageDetailsMap mMessageDetails;
     const AttributeMap mAttributeMap;
 };
 
@@ -103,11 +126,11 @@
 template <typename T>
 class MessageDefinition : public MessageDescriptor {
   public:
-    MessageDefinition(
+    MessageDefinition(  //
             const std::string& name,
-            const std::initializer_list<MessageDescriptor::MessageTypeMap::value_type> messageTypes,
+            const std::initializer_list<MessageDescriptor::MessageDetailsMap::value_type> msgDet,
             const std::initializer_list<AttributeMap::value_type> attrTypes = {})
-        : MessageDescriptor(name, messageTypes, attrTypes, sizeof(T)) {}
+        : MessageDescriptor(name, msgDet, attrTypes, sizeof(T)) {}
 
     void dataToStream(std::stringstream& ss, const Buffer<nlmsghdr> hdr) const override {
         const auto& [ok, msg] = hdr.data<T>().getFirst();
diff --git a/automotive/can/1.0/default/libnl++/protocols/NetlinkProtocol.cpp b/automotive/can/1.0/default/libnl++/protocols/NetlinkProtocol.cpp
index 4b795d9..159b6d6 100644
--- a/automotive/can/1.0/default/libnl++/protocols/NetlinkProtocol.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/NetlinkProtocol.cpp
@@ -42,7 +42,7 @@
         const NetlinkProtocol::MessageDescriptorList& descrs, int protocol) {
     MessageDescriptorMap map;
     for (const auto& descr : descrs) {
-        for (const auto& [mtype, mname] : descr->getMessageTypeMap()) {
+        for (const auto& [mtype, mdet] : descr->getMessageDetailsMap()) {
             map.emplace(mtype, descr);
         }
     }
@@ -53,7 +53,7 @@
     };
 
     for (const auto& descr : baseDescriptors) {
-        for (const auto& [mtype, mname] : descr->getMessageTypeMap()) {
+        for (const auto& [mtype, mdet] : descr->getMessageDetailsMap()) {
             map.emplace(mtype, descr);
         }
     }
diff --git a/automotive/can/1.0/default/libnl++/protocols/common/Empty.cpp b/automotive/can/1.0/default/libnl++/protocols/common/Empty.cpp
index dbf10d4..8a672d3 100644
--- a/automotive/can/1.0/default/libnl++/protocols/common/Empty.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/common/Empty.cpp
@@ -20,9 +20,9 @@
 
 // clang-format off
 Empty::Empty() : MessageDefinition<char>("nlmsg", {
-    {NLMSG_NOOP, "NOOP"},
-    {NLMSG_DONE, "DONE"},
-    {NLMSG_OVERRUN, "OVERRUN"},
+    {NLMSG_NOOP, {"NOOP", MessageGenre::UNKNOWN}},
+    {NLMSG_DONE, {"DONE", MessageGenre::UNKNOWN}},
+    {NLMSG_OVERRUN, {"OVERRUN", MessageGenre::UNKNOWN}},
 }) {}
 // clang-format on
 
diff --git a/automotive/can/1.0/default/libnl++/protocols/common/Error.cpp b/automotive/can/1.0/default/libnl++/protocols/common/Error.cpp
index 1d6bd1c..44708a3 100644
--- a/automotive/can/1.0/default/libnl++/protocols/common/Error.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/common/Error.cpp
@@ -22,15 +22,21 @@
 
 namespace android::nl::protocols::base {
 
+using DataType = AttributeDefinition::DataType;
+
 // clang-format off
 Error::Error(int protocol) : MessageDefinition<nlmsgerr>("nlmsg", {
-    {NLMSG_ERROR, "ERROR"},
+    {NLMSG_ERROR, {"ERROR", MessageGenre::ACK}},
+}, {
+    {NLMSGERR_ATTR_MSG, {"MSG", DataType::String}},
+    {NLMSGERR_ATTR_OFFS, {"OFFS", DataType::Uint}},
+    {NLMSGERR_ATTR_COOKIE, {"COOKIE", DataType::Raw}},
 }), mProtocol(protocol) {}
 // clang-format on
 
 void Error::toStream(std::stringstream& ss, const nlmsgerr& data) const {
     ss << "nlmsgerr{error=" << data.error
-       << ", msg=" << toString({&data.msg, sizeof(data.msg)}, mProtocol) << "}";
+       << ", msg=" << toString({&data.msg, sizeof(data.msg)}, mProtocol, false) << "}";
 }
 
 }  // namespace android::nl::protocols::base
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 5203368..5b6bd97 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.cpp
@@ -22,10 +22,11 @@
         nlmsgtype_t msgtype, std::string msgname,
         const std::initializer_list<GenericCommandNameMap::value_type> commandNames,
         const std::initializer_list<AttributeMap::value_type> attrTypes)
-    : MessageDefinition<struct genlmsghdr>(msgname, {{msgtype, msgname}}, attrTypes),
+    : MessageDefinition<genlmsghdr>(msgname, {{msgtype, {msgname, MessageGenre::UNKNOWN}}},
+                                    attrTypes),
       mCommandNames(commandNames) {}
 
-void GenericMessageBase::toStream(std::stringstream& ss, const struct genlmsghdr& data) const {
+void GenericMessageBase::toStream(std::stringstream& ss, const genlmsghdr& data) const {
     ss << "genlmsghdr{";
     if (mCommandNames.count(data.cmd) == 0) {
         ss << "cmd=" << unsigned(data.cmd);
diff --git a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.h b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.h
index f3b0b31..f0ee5b0 100644
--- a/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.h
+++ b/automotive/can/1.0/default/libnl++/protocols/generic/GenericMessageBase.h
@@ -22,7 +22,7 @@
 
 namespace android::nl::protocols::generic {
 
-class GenericMessageBase : public MessageDefinition<struct genlmsghdr> {
+class GenericMessageBase : public MessageDefinition<genlmsghdr> {
   public:
     typedef std::map<uint8_t, std::string> GenericCommandNameMap;
 
@@ -31,7 +31,7 @@
             const std::initializer_list<GenericCommandNameMap::value_type> commandNames = {},
             const std::initializer_list<AttributeMap::value_type> attrTypes = {});
 
-    void toStream(std::stringstream& ss, const struct genlmsghdr& data) const override;
+    void toStream(std::stringstream& ss, const genlmsghdr& data) const override;
 
   private:
     const GenericCommandNameMap mCommandNames;
diff --git a/automotive/can/1.0/default/libnl++/protocols/route/Link.cpp b/automotive/can/1.0/default/libnl++/protocols/route/Link.cpp
index 26d3e3e..7db487a 100644
--- a/automotive/can/1.0/default/libnl++/protocols/route/Link.cpp
+++ b/automotive/can/1.0/default/libnl++/protocols/route/Link.cpp
@@ -25,10 +25,10 @@
 using DataType = AttributeDefinition::DataType;
 
 // clang-format off
-Link::Link() : MessageDefinition<struct ifinfomsg>("link", {
-    {RTM_NEWLINK, "NEWLINK"},
-    {RTM_DELLINK, "DELLINK"},
-    {RTM_GETLINK, "GETLINK"},
+Link::Link() : MessageDefinition<ifinfomsg>("link", {
+    {RTM_NEWLINK, {"NEWLINK", MessageGenre::NEW}},
+    {RTM_DELLINK, {"DELLINK", MessageGenre::DELETE}},
+    {RTM_GETLINK, {"GETLINK", MessageGenre::GET}},
 }, {
     {IFLA_ADDRESS, {"ADDRESS"}},
     {IFLA_BROADCAST, {"BROADCAST"}},
@@ -107,7 +107,7 @@
 }) {}
 // clang-format off
 
-void Link::toStream(std::stringstream& ss, const struct ifinfomsg& data) const {
+void Link::toStream(std::stringstream& ss, const ifinfomsg& data) const {
     ss << "ifinfomsg{"
        << "family=" << unsigned(data.ifi_family) << ", type=" << data.ifi_type
        << ", index=" << data.ifi_index << ", flags=" << data.ifi_flags
diff --git a/automotive/can/1.0/default/libnl++/protocols/route/Link.h b/automotive/can/1.0/default/libnl++/protocols/route/Link.h
index 4ea3eba..ecfefc9 100644
--- a/automotive/can/1.0/default/libnl++/protocols/route/Link.h
+++ b/automotive/can/1.0/default/libnl++/protocols/route/Link.h
@@ -22,10 +22,10 @@
 
 namespace android::nl::protocols::route {
 
-class Link : public MessageDefinition<struct ifinfomsg> {
+class Link : public MessageDefinition<ifinfomsg> {
   public:
     Link();
-    void toStream(std::stringstream& ss, const struct ifinfomsg& data) const override;
+    void toStream(std::stringstream& ss, const ifinfomsg& data) const override;
 };
 
 }  // namespace android::nl::protocols::route