Add data structures to parse netlink IP address messages.

Bug: 163492391
Test: atest NetworkStaticLibsTests
Change-Id: I85f1b80925fa4a9397df931902d6de189902da72
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
index 11aee84..07b52d8 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -144,6 +144,8 @@
 
     // Netlink groups.
     public static final int RTMGRP_LINK = 1;
+    public static final int RTMGRP_IPV4_IFADDR = 0x10;
+    public static final int RTMGRP_IPV6_IFADDR = 0x100;
     public static final int RTNLGRP_ND_USEROPT = 20;
     public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
 
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
index d83c12f..708736e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -123,6 +123,9 @@
             case NetlinkConstants.RTM_NEWLINK:
             case NetlinkConstants.RTM_DELLINK:
                 return (NetlinkMessage) RtNetlinkLinkMessage.parse(nlmsghdr, byteBuffer);
+            case NetlinkConstants.RTM_NEWADDR:
+            case NetlinkConstants.RTM_DELADDR:
+                return (NetlinkMessage) RtNetlinkAddressMessage.parse(nlmsghdr, byteBuffer);
             case NetlinkConstants.RTM_NEWNEIGH:
             case NetlinkConstants.RTM_DELNEIGH:
             case NetlinkConstants.RTM_GETNEIGH:
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
new file mode 100644
index 0000000..a518c76
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util.netlink;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.HexDump;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink address messages.
+ *
+ * RtNetlinkAddressMessage.parse() must be called with a ByteBuffer that contains exactly one
+ * netlink message.
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class RtNetlinkAddressMessage extends NetlinkMessage {
+    public static final short IFA_ADDRESS        = 1;
+    public static final short IFA_CACHEINFO      = 6;
+    public static final short IFA_FLAGS          = 8;
+
+    private int mFlags;
+    @NonNull
+    private StructIfaddrMsg mIfaddrmsg;
+    @NonNull
+    private InetAddress mIpAddress;
+    @Nullable
+    private StructIfacacheInfo mIfacacheInfo;
+
+    private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) {
+        super(header);
+        mIfaddrmsg = null;
+        mIpAddress = null;
+        mIfacacheInfo = null;
+        mFlags = 0;
+    }
+
+    public int getFlags() {
+        return mFlags;
+    }
+
+    @NonNull
+    public StructIfaddrMsg getIfaddrHeader() {
+        return mIfaddrmsg;
+    }
+
+    @NonNull
+    public InetAddress getIpAddress() {
+        return mIpAddress;
+    }
+
+    @Nullable
+    public StructIfacacheInfo getIfacacheInfo() {
+        return mIfacacheInfo;
+    }
+
+    /**
+     * Parse rtnetlink address message from {@link ByteBuffer}. This method must be called with a
+     * ByteBuffer that contains exactly one netlink message.
+     *
+     * @param header netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+     */
+    @Nullable
+    public static RtNetlinkAddressMessage parse(@NonNull final StructNlMsgHdr header,
+            @NonNull final ByteBuffer byteBuffer) {
+        final RtNetlinkAddressMessage addrMsg = new RtNetlinkAddressMessage(header);
+
+        addrMsg.mIfaddrmsg = StructIfaddrMsg.parse(byteBuffer);
+        if (addrMsg.mIfaddrmsg == null) return null;
+
+        // IFA_ADDRESS
+        final int baseOffset = byteBuffer.position();
+        StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFA_ADDRESS, byteBuffer);
+        if (nlAttr == null) return null;
+        addrMsg.mIpAddress = nlAttr.getValueAsInetAddress();
+        if (addrMsg.mIpAddress == null) return null;
+
+        // IFA_CACHEINFO
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(IFA_CACHEINFO, byteBuffer);
+        if (nlAttr != null) {
+            addrMsg.mIfacacheInfo = StructIfacacheInfo.parse(nlAttr.getValueAsByteBuffer());
+        }
+
+        // The first 8 bits of flags are in the ifaddrmsg.
+        addrMsg.mFlags = addrMsg.mIfaddrmsg.flags;
+        // IFA_FLAGS. All the flags are in the IF_FLAGS attribute. This should always be present,
+        // and will overwrite the flags set above.
+        byteBuffer.position(baseOffset);
+        nlAttr = StructNlAttr.findNextAttrOfType(IFA_FLAGS, byteBuffer);
+        if (nlAttr != null) {
+            addrMsg.mFlags = nlAttr.getValueAsInt(0 /* default value */);
+        }
+
+        return addrMsg;
+    }
+
+    /**
+     * Write a rtnetlink address message to {@link ByteBuffer}.
+     */
+    @VisibleForTesting
+    protected void pack(ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        mIfaddrmsg.pack(byteBuffer);
+
+        final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, mIpAddress);
+        address.pack(byteBuffer);
+
+        if (mIfacacheInfo != null) {
+            final StructNlAttr cacheInfo = new StructNlAttr(IFA_CACHEINFO,
+                    mIfacacheInfo.writeToBytes());
+            cacheInfo.pack(byteBuffer);
+        }
+
+        // If IFA_FLAGS attribute isn't present on the wire at parsing netlink message, it will
+        // still be packed to ByteBuffer even if the flag is 0.
+        final StructNlAttr flags = new StructNlAttr(IFA_FLAGS, mFlags);
+        flags.pack(byteBuffer);
+    }
+
+    @Override
+    public String toString() {
+        return "RtNetlinkAddressMessage{ "
+                + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+                + "Ifaddrmsg{" + mIfaddrmsg.toString() + "}, "
+                + "IP Address{" + mIpAddress.getHostAddress() + "}, "
+                + "IfacacheInfo{" + (mIfacacheInfo == null ? "" : mIfacacheInfo.toString()) + "}, "
+                + "Address Flags{" + HexDump.toHexString(mFlags) + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java
new file mode 100644
index 0000000..360f56d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfacacheInfo.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct ifa_cacheinfo
+ *
+ * see also:
+ *
+ *     include/uapi/linux/if_addr.h
+ *
+ * @hide
+ */
+public class StructIfacacheInfo extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 16;
+
+    @Field(order = 0, type = Type.U32)
+    public final long preferred;
+    @Field(order = 1, type = Type.U32)
+    public final long valid;
+    @Field(order = 2, type = Type.U32)
+    public final long cstamp; // created timestamp, hundredths of seconds.
+    @Field(order = 3, type = Type.U32)
+    public final long tstamp; // updated timestamp, hundredths of seconds.
+
+    StructIfacacheInfo(long preferred, long valid, long cstamp, long tstamp) {
+        this.preferred = preferred;
+        this.valid = valid;
+        this.cstamp = cstamp;
+        this.tstamp = tstamp;
+    }
+
+    /**
+     * Parse an ifa_cacheinfo struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the ifa_cacheinfo.
+     * @return the parsed ifa_cacheinfo struct, or {@code null} if the ifa_cacheinfo struct
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructIfacacheInfo parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructIfacacheInfo.class, byteBuffer);
+    }
+
+    /**
+     * Write an ifa_cacheinfo struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
new file mode 100644
index 0000000..9196feb
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct ifaddrmsg
+ *
+ * see also:
+ *
+ *     include/uapi/linux/if_addr.h
+ *
+ * @hide
+ */
+public class StructIfaddrMsg extends Struct {
+    // Already aligned.
+    public static final int STRUCT_SIZE = 8;
+
+    @Field(order = 0, type = Type.U8)
+    public final short family;
+    @Field(order = 1, type = Type.U8)
+    public final short prefixLen;
+    @Field(order = 2, type = Type.U8)
+    public final short flags;
+    @Field(order = 3, type = Type.U8)
+    public final short scope;
+    @Field(order = 4, type = Type.S32)
+    public final int index;
+
+    StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) {
+        this.family = family;
+        this.prefixLen = prefixLen;
+        this.flags = flags;
+        this.scope = scope;
+        this.index = index;
+    }
+
+    /**
+     * Parse an ifaddrmsg struct from a {@link ByteBuffer}.
+     *
+     * @param byteBuffer The buffer from which to parse the ifaddrmsg.
+     * @return the parsed ifaddrmsg struct, or {@code null} if the ifaddrmsg struct
+     *         could not be parsed successfully (for example, if it was truncated).
+     */
+    @Nullable
+    public static StructIfaddrMsg parse(@NonNull final ByteBuffer byteBuffer) {
+        if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+        // The ByteOrder must already have been set to native order.
+        return Struct.parse(StructIfaddrMsg.class, byteBuffer);
+    }
+
+    /**
+     * Write an ifaddrmsg struct to {@link ByteBuffer}.
+     */
+    public void pack(@NonNull final ByteBuffer byteBuffer) {
+        // The ByteOrder must already have been set to native order.
+        this.writeToByteBuffer(byteBuffer);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
index 881dc18..02d1574 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -58,22 +58,22 @@
     }
 
     /**
-     * Parse a rtnetlink link message header from a {@link ByteBuffer}.
+     * Parse an ifinfomsg struct from a {@link ByteBuffer}.
      *
-     * @param byteBuffer The buffer from which to parse the rtnetlink link message header.
-     * @return the parsed rtnetlink link message header, or {@code null} if the rtnetlink message
-     *         header could not be parsed successfully (for example, if it was truncated).
+     * @param byteBuffer The buffer from which to parse the ifinfomsg.
+     * @return the parsed ifinfomsg struct, or {@code null} if the ifinfomsg struct
+     *         could not be parsed successfully (for example, if it was truncated).
      */
     @Nullable
     public static StructIfinfoMsg parse(@NonNull final ByteBuffer byteBuffer) {
         if (byteBuffer.remaining() < STRUCT_SIZE) return null;
 
         // The ByteOrder must already have been set to native order.
-        return StructIfinfoMsg.parse(StructIfinfoMsg.class, byteBuffer);
+        return Struct.parse(StructIfinfoMsg.class, byteBuffer);
     }
 
     /**
-     * Write ifinfomsg to {@link ByteBuffer}.
+     * Write an ifinfomsg struct to {@link ByteBuffer}.
      */
     public void pack(@NonNull final ByteBuffer byteBuffer) {
         // The ByteOrder must already have been set to native order.
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
new file mode 100644
index 0000000..7d8dbd2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+package com.android.net.module.util.netlink;
+
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkAddressMessageTest {
+    private static final Inet6Address TEST_LINK_LOCAL =
+            (Inet6Address) InetAddresses.parseNumericAddress("fe80::2C41:5CFF:FE09:6665");
+
+    // An example of the full RTM_NEWADDR message.
+    private static final String RTM_NEWADDR_HEX =
+            "48000000140000000000000000000000"            // struct nlmsghr
+            + "0A4080FD1E000000"                          // struct ifaddrmsg
+            + "14000100FE800000000000002C415CFFFE096665"  // IFA_ADDRESS
+            + "14000600100E0000201C00002A70000045700000"  // IFA_CACHEINFO
+            + "0800080080000000";                         // IFA_FLAGS
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+
+    @Test
+    public void testParseRtmNewAddress() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+        final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg;
+
+        final StructNlMsgHdr hdr = addrMsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(72, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWADDR, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructIfaddrMsg ifaddrMsgHdr = addrMsg.getIfaddrHeader();
+        assertNotNull(ifaddrMsgHdr);
+        assertEquals((byte) OsConstants.AF_INET6, ifaddrMsgHdr.family);
+        assertEquals(64, ifaddrMsgHdr.prefixLen);
+        assertEquals(0x80, ifaddrMsgHdr.flags);
+        assertEquals(0xFD, ifaddrMsgHdr.scope);
+        assertEquals(30, ifaddrMsgHdr.index);
+
+        assertEquals((Inet6Address) addrMsg.getIpAddress(), TEST_LINK_LOCAL);
+        assertEquals(3600L, addrMsg.getIfacacheInfo().preferred);
+        assertEquals(7200L, addrMsg.getIfacacheInfo().valid);
+        assertEquals(28714, addrMsg.getIfacacheInfo().cstamp);
+        assertEquals(28741, addrMsg.getIfacacheInfo().tstamp);
+        assertEquals(0x80, addrMsg.getFlags());
+    }
+
+    private static final String RTM_NEWADDR_PACK_HEX =
+            "48000000140000000000000000000000"             // struct nlmsghr
+            + "0A4080FD1E000000"                           // struct ifaddrmsg
+            + "14000100FE800000000000002C415CFFFE096665"   // IFA_ADDRESS
+            + "14000600FFFFFFFFFFFFFFFF2A7000002A700000"   // IFA_CACHEINFO
+            + "0800080081000000";                          // IFA_FLAGS(override ifa_flags)
+
+    @Test
+    public void testPackRtmNewAddr() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_PACK_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+        final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg;
+
+        final ByteBuffer packBuffer = ByteBuffer.allocate(72);
+        packBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        addrMsg.pack(packBuffer);
+        assertEquals(RTM_NEWADDR_PACK_HEX, HexDump.toHexString(packBuffer.array()));
+    }
+
+    private static final String RTM_NEWADDR_TRUNCATED_HEX =
+            "44000000140000000000000000000000"            // struct nlmsghr
+            + "0A4080FD1E000000"                          // struct ifaddrmsg
+            + "10000100FE800000000000002C415CFF"          // IFA_ADDRESS(truncated)
+            + "14000600FFFFFFFFFFFFFFFF2A7000002A700000"  // IFA_CACHEINFO
+            + "0800080080000000";                         // IFA_FLAGS
+
+    @Test
+    public void testTruncatedRtmNewAddr() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_TRUNCATED_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        // Parsing RTM_NEWADDR with truncated IFA_ADDRESS attribute returns null.
+        assertNull(msg);
+    }
+
+    @Test
+    public void testToString() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWADDR_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkAddressMessage);
+        final RtNetlinkAddressMessage addrMsg = (RtNetlinkAddressMessage) msg;
+        final String expected = "RtNetlinkAddressMessage{ "
+                + "nlmsghdr{"
+                + "StructNlMsgHdr{ nlmsg_len{72}, nlmsg_type{20(RTM_NEWADDR)}, nlmsg_flags{0()}, "
+                + "nlmsg_seq{0}, nlmsg_pid{0} }}, "
+                + "Ifaddrmsg{"
+                + "family: 10, prefixLen: 64, flags: 128, scope: 253, index: 30}, "
+                + "IP Address{fe80::2c41:5cff:fe09:6665}, "
+                + "IfacacheInfo{"
+                + "preferred: 3600, valid: 7200, cstamp: 28714, tstamp: 28741}, "
+                + "Address Flags{00000080} "
+                + "}";
+        assertEquals(expected, addrMsg.toString());
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 58a7478..5d446b8 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -42,7 +42,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class RtNetlinkLinkMessageTest {
-    private static final String TAG = "RtNetlinkLinkMessageTest";
 
     // An example of the full RTM_NEWLINK message.
     private static final String RTM_NEWLINK_HEX =