Merge "Add RtmNetlinkPrefixMessage class to represent RTM_NEWPREFIX message." into main am: 7dd2525646

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3039360

Change-Id: I70ead9b124281f4173d25bea61db06ecec345672
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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 781a04e..dfb2053 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -150,6 +150,8 @@
                 return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer);
             case NetlinkConstants.RTM_NEWNDUSEROPT:
                 return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer);
+            case NetlinkConstants.RTM_NEWPREFIX:
+                return (NetlinkMessage) RtNetlinkPrefixMessage.parse(nlmsghdr, byteBuffer);
             default: return null;
         }
     }
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java
new file mode 100644
index 0000000..30c63fb
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkPrefixMessage.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 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.net.IpPrefix;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * A NetlinkMessage subclass for rtnetlink address messages.
+ *
+ * RtNetlinkPrefixMessage.parse() must be called with a ByteBuffer that contains exactly one
+ * netlink message.
+ *
+ * see also:
+ *
+ *     include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class RtNetlinkPrefixMessage extends NetlinkMessage {
+    public static final short PREFIX_ADDRESS       = 1;
+    public static final short PREFIX_CACHEINFO     = 2;
+
+    @NonNull
+    private StructPrefixMsg mPrefixmsg;
+    @NonNull
+    private IpPrefix mPrefix;
+    private long mPreferredLifetime;
+    private long mValidLifetime;
+
+    @VisibleForTesting
+    public RtNetlinkPrefixMessage(@NonNull final StructNlMsgHdr header,
+            @NonNull final StructPrefixMsg prefixmsg,
+            @NonNull final IpPrefix prefix,
+            long preferred, long valid) {
+        super(header);
+        mPrefixmsg = prefixmsg;
+        mPrefix = prefix;
+        mPreferredLifetime = preferred;
+        mValidLifetime = valid;
+    }
+
+    private RtNetlinkPrefixMessage(@NonNull StructNlMsgHdr header) {
+        this(header, null, null, 0 /* preferredLifetime */, 0 /* validLifetime */);
+    }
+
+    @NonNull
+    public StructPrefixMsg getPrefixMsg() {
+        return mPrefixmsg;
+    }
+
+    @NonNull
+    public IpPrefix getPrefix() {
+        return mPrefix;
+    }
+
+    public long getPreferredLifetime() {
+        return mPreferredLifetime;
+    }
+
+    public long getValidLifetime() {
+        return mValidLifetime;
+    }
+
+    /**
+     * Parse rtnetlink prefix message from {@link ByteBuffer}. This method must be called with a
+     * ByteBuffer that contains exactly one netlink message.
+     *
+     * RTM_NEWPREFIX Message Format:
+     *  +----------+- - -+-------------+- - -+---------------------+-----------------------+
+     *  | nlmsghdr | Pad |  prefixmsg  | Pad | PREFIX_ADDRESS attr | PREFIX_CACHEINFO attr |
+     *  +----------+- - -+-------------+- - -+---------------------+-----------------------+
+     *
+     * @param header netlink message header.
+     * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
+     */
+    @Nullable
+    public static RtNetlinkPrefixMessage parse(@NonNull final StructNlMsgHdr header,
+            @NonNull final ByteBuffer byteBuffer) {
+        try {
+            final RtNetlinkPrefixMessage msg = new RtNetlinkPrefixMessage(header);
+            msg.mPrefixmsg = StructPrefixMsg.parse(byteBuffer);
+
+            // PREFIX_ADDRESS
+            final int baseOffset = byteBuffer.position();
+            StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(PREFIX_ADDRESS, byteBuffer);
+            if (nlAttr == null) return null;
+            final Inet6Address addr = (Inet6Address) nlAttr.getValueAsInetAddress();
+            if (addr == null) return null;
+            msg.mPrefix = new IpPrefix(addr, msg.mPrefixmsg.prefix_len);
+
+            // PREFIX_CACHEINFO
+            byteBuffer.position(baseOffset);
+            nlAttr = StructNlAttr.findNextAttrOfType(PREFIX_CACHEINFO, byteBuffer);
+            if (nlAttr == null) return null;
+            final ByteBuffer buffer = nlAttr.getValueAsByteBuffer();
+            if (buffer == null) return null;
+            final StructPrefixCacheInfo cacheinfo = StructPrefixCacheInfo.parse(buffer);
+            msg.mPreferredLifetime = cacheinfo.preferred_time;
+            msg.mValidLifetime = cacheinfo.valid_time;
+
+            return msg;
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Write a rtnetlink prefix message to {@link ByteBuffer}.
+     */
+    @VisibleForTesting
+    protected void pack(ByteBuffer byteBuffer) {
+        getHeader().pack(byteBuffer);
+        mPrefixmsg.pack(byteBuffer);
+
+        // PREFIX_ADDRESS attribute
+        final StructNlAttr prefixAddress =
+                new StructNlAttr(PREFIX_ADDRESS, mPrefix.getRawAddress());
+        prefixAddress.pack(byteBuffer);
+
+        // PREFIX_CACHEINFO attribute
+        final StructPrefixCacheInfo cacheinfo =
+                new StructPrefixCacheInfo(mPreferredLifetime, mValidLifetime);
+        final StructNlAttr prefixCacheinfo =
+                new StructNlAttr(PREFIX_CACHEINFO, cacheinfo.writeToBytes());
+        prefixCacheinfo.pack(byteBuffer);
+    }
+
+    @Override
+    public String toString() {
+        return "RtNetlinkPrefixMessage{ "
+                + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
+                + "prefixmsg{" + mPrefixmsg.toString() + "}, "
+                + "IP Prefix{" + mPrefix + "}, "
+                + "preferred lifetime{" + mPreferredLifetime + "}, "
+                + "valid lifetime{" + mValidLifetime + "} "
+                + "}";
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java
new file mode 100644
index 0000000..b1779cb
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkPrefixMessageTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 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.IpPrefix;
+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.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkPrefixMessageTest {
+    private static final IpPrefix TEST_PREFIX = new IpPrefix("2001:db8:1:1::/64");
+
+    // An example of the full RTM_NEWPREFIX message.
+    private static final String RTM_NEWPREFIX_HEX =
+            "3C000000340000000000000000000000"            // struct nlmsghr
+            + "0A0000002F00000003400300"                  // struct prefixmsg
+            + "1400010020010DB8000100010000000000000000"  // PREFIX_ADDRESS
+            + "0C000200803A0900008D2700";                 // PREFIX_CACHEINFO
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+
+    @Test
+    public void testParseRtmNewPrefix() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkPrefixMessage);
+        final RtNetlinkPrefixMessage prefixmsg = (RtNetlinkPrefixMessage) msg;
+
+        final StructNlMsgHdr hdr = prefixmsg.getHeader();
+        assertNotNull(hdr);
+        assertEquals(60, hdr.nlmsg_len);
+        assertEquals(NetlinkConstants.RTM_NEWPREFIX, hdr.nlmsg_type);
+        assertEquals(0, hdr.nlmsg_flags);
+        assertEquals(0, hdr.nlmsg_seq);
+        assertEquals(0, hdr.nlmsg_pid);
+
+        final StructPrefixMsg prefixmsgHdr = prefixmsg.getPrefixMsg();
+        assertNotNull(prefixmsgHdr);
+        assertEquals((byte) OsConstants.AF_INET6, prefixmsgHdr.prefix_family);
+        assertEquals(3, prefixmsgHdr.prefix_type);
+        assertEquals(64, prefixmsgHdr.prefix_len);
+        assertEquals(0x03, prefixmsgHdr.prefix_flags);
+        assertEquals(0x2F, prefixmsgHdr.prefix_ifindex);
+
+        assertEquals(prefixmsg.getPrefix(), TEST_PREFIX);
+        assertEquals(604800L, prefixmsg.getPreferredLifetime());
+        assertEquals(2592000L, prefixmsg.getValidLifetime());
+    }
+
+    @Test
+    public void testPackRtmNewPrefix() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkPrefixMessage);
+        final RtNetlinkPrefixMessage prefixmsg = (RtNetlinkPrefixMessage) msg;
+
+        final ByteBuffer packBuffer = ByteBuffer.allocate(60);
+        packBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        prefixmsg.pack(packBuffer);
+        assertEquals(RTM_NEWPREFIX_HEX, HexDump.toHexString(packBuffer.array()));
+    }
+
+    private static final String RTM_NEWPREFIX_WITHOUT_PREFIX_ADDRESS_HEX =
+            "24000000340000000000000000000000"            // struct nlmsghr
+            + "0A0000002F00000003400300"                  // struct prefixmsg
+            + "0C000200803A0900008D2700";                 // PREFIX_CACHEINFO
+
+    @Test
+    public void testParseRtmNewPrefix_withoutPrefixAddressAttribute() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_WITHOUT_PREFIX_ADDRESS_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNull(msg);
+    }
+
+    private static final String RTM_NEWPREFIX_WITHOUT_PREFIX_CACHEINFO_HEX =
+            "30000000340000000000000000000000"             // struct nlmsghr
+            + "0A0000002F00000003400300"                   // struct prefixmsg
+            + "140001002A0079E10ABCF6050000000000000000";  // PREFIX_ADDRESS
+
+    @Test
+    public void testParseRtmNewPrefix_withoutPrefixCacheinfoAttribute() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_WITHOUT_PREFIX_CACHEINFO_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNull(msg);
+    }
+
+    private static final String RTM_NEWPREFIX_TRUNCATED_PREFIX_ADDRESS_HEX =
+            "3C000000340000000000000000000000"            // struct nlmsghr
+            + "0A0000002F00000003400300"                  // struct prefixmsg
+            + "140001002A0079E10ABCF605000000000000"      // PREFIX_ADDRESS (truncated)
+            + "0C000200803A0900008D2700";                 // PREFIX_CACHEINFO
+
+    @Test
+    public void testParseRtmNewPrefix_truncatedPrefixAddressAttribute() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_TRUNCATED_PREFIX_ADDRESS_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNull(msg);
+    }
+
+    private static final String RTM_NEWPREFIX_TRUNCATED_PREFIX_CACHEINFO_HEX =
+            "3C000000340000000000000000000000"            // struct nlmsghr
+            + "0A0000002F00000003400300"                  // struct prefixmsg
+            + "140001002A0079E10ABCF6050000000000000000"  // PREFIX_ADDRESS
+            + "0C000200803A0900008D";                     // PREFIX_CACHEINFO (truncated)
+
+    @Test
+    public void testParseRtmNewPrefix_truncatedPrefixCacheinfoAttribute() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_TRUNCATED_PREFIX_CACHEINFO_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNull(msg);
+    }
+
+    @Test
+    public void testToString() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWPREFIX_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkPrefixMessage);
+        final RtNetlinkPrefixMessage prefixmsg = (RtNetlinkPrefixMessage) msg;
+        final String expected = "RtNetlinkPrefixMessage{ "
+                + "nlmsghdr{StructNlMsgHdr{ nlmsg_len{60}, nlmsg_type{52(RTM_NEWPREFIX)}, "
+                + "nlmsg_flags{0()}, nlmsg_seq{0}, nlmsg_pid{0} }}, "
+                + "prefixmsg{prefix_family: 10, prefix_ifindex: 47, prefix_type: 3, "
+                + "prefix_len: 64, prefix_flags: 3}, "
+                + "IP Prefix{2001:db8:1:1::/64}, "
+                + "preferred lifetime{604800}, valid lifetime{2592000} }";
+        assertEquals(expected, prefixmsg.toString());
+    }
+}