Merge "Make RtNetlinkLinkMessage class immutable after creation" into main am: e7f5ac9d2f

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

Change-Id: I420d02d2edc9ac53d2021e03b76495c04ba69c88
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/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index 0c49edc..72c770a 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -16,6 +16,8 @@
 
 package com.android.net.module.util.netlink;
 
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+
 import android.net.MacAddress;
 import android.system.OsConstants;
 
@@ -24,6 +26,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
 /**
  * A NetlinkMessage subclass for rtnetlink link messages.
@@ -46,29 +49,55 @@
 
     public static final short IN6_ADDR_GEN_MODE_NONE = 1;
 
-    private int mMtu;
-    @NonNull
-    private StructIfinfoMsg mIfinfomsg;
-    @Nullable
-    private MacAddress mHardwareAddress;
-    @Nullable
-    private String mInterfaceName;
+    // The maximum buffer size to hold an interface name including the null-terminator '\0'.
+    private static final int IFNAMSIZ = 16;
+    // The default value of MTU, which means the MTU is unspecified.
+    private static final int DEFAULT_MTU = 0;
 
-    private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr header) {
-        super(header);
-        mIfinfomsg = null;
-        mMtu = 0;
-        mHardwareAddress = null;
-        mInterfaceName = null;
+    @NonNull
+    private final StructIfinfoMsg mIfinfomsg;
+    private final int mMtu;
+    @Nullable
+    private final MacAddress mHardwareAddress;
+    @Nullable
+    private final String mInterfaceName;
+
+    /**
+     * Creates an {@link RtNetlinkLinkMessage} instance.
+     *
+     * <p>This method validates the arguments and returns {@code null} if any of them are invalid.
+     * nlmsghdr's nlmsg_len will be updated to the correct length before creation.
+     *
+     * @param nlmsghdr The Netlink message header. Must not be {@code null}.
+     * @param ifinfomsg The interface information message. Must not be {@code null}.
+     * @param mtu The Maximum Transmission Unit (MTU) value for the link.
+     * @param hardwareAddress The hardware address (MAC address) of the link. May be {@code null}.
+     * @param interfaceName The name of the interface. May be {@code null}.
+     * @return A new {@link RtNetlinkLinkMessage} instance, or {@code null} if the input arguments
+     *         are invalid.
+     */
+    @Nullable
+    public static RtNetlinkLinkMessage build(@NonNull StructNlMsgHdr nlmsghdr,
+            @NonNull StructIfinfoMsg ifinfomsg, int mtu, @Nullable MacAddress hardwareAddress,
+            @Nullable String interfaceName) {
+        if (mtu < 0) {
+            return null;
+        }
+        if (interfaceName != null
+                && (interfaceName.isEmpty() || interfaceName.length() + 1 > IFNAMSIZ)) {
+            return null;
+        }
+
+        nlmsghdr.nlmsg_len = calculateMessageLength(mtu, hardwareAddress, interfaceName);
+        return new RtNetlinkLinkMessage(nlmsghdr, ifinfomsg, mtu, hardwareAddress, interfaceName);
     }
 
-    @VisibleForTesting
-    public RtNetlinkLinkMessage(@NonNull StructNlMsgHdr nlmsghdr,
-            int mtu, @NonNull StructIfinfoMsg ifinfomsg, @NonNull MacAddress hardwareAddress,
-            @NonNull String interfaceName) {
+    private RtNetlinkLinkMessage(@NonNull StructNlMsgHdr nlmsghdr,
+            @NonNull StructIfinfoMsg ifinfomsg, int mtu, @Nullable MacAddress hardwareAddress,
+            @Nullable String interfaceName) {
         super(nlmsghdr);
-        mMtu = mtu;
         mIfinfomsg = ifinfomsg;
+        mMtu = mtu;
         mHardwareAddress = hardwareAddress;
         mInterfaceName = interfaceName;
     }
@@ -102,33 +131,46 @@
     @Nullable
     public static RtNetlinkLinkMessage parse(@NonNull final StructNlMsgHdr header,
             @NonNull final ByteBuffer byteBuffer) {
-        final RtNetlinkLinkMessage linkMsg = new RtNetlinkLinkMessage(header);
-
-        linkMsg.mIfinfomsg = StructIfinfoMsg.parse(byteBuffer);
-        if (linkMsg.mIfinfomsg == null) return null;
+        final StructIfinfoMsg ifinfoMsg = StructIfinfoMsg.parse(byteBuffer);
+        if (ifinfoMsg == null) {
+            return null;
+        }
 
         // IFLA_MTU
+        int mtu = DEFAULT_MTU;
         final int baseOffset = byteBuffer.position();
         StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(IFLA_MTU, byteBuffer);
         if (nlAttr != null) {
-            linkMsg.mMtu = nlAttr.getValueAsInt(0 /* default value */);
+            mtu = nlAttr.getValueAsInt(DEFAULT_MTU);
         }
 
         // IFLA_ADDRESS
+        MacAddress hardwareAddress = null;
         byteBuffer.position(baseOffset);
         nlAttr = StructNlAttr.findNextAttrOfType(IFLA_ADDRESS, byteBuffer);
         if (nlAttr != null) {
-            linkMsg.mHardwareAddress = nlAttr.getValueAsMacAddress();
+            hardwareAddress = nlAttr.getValueAsMacAddress();
         }
 
         // IFLA_IFNAME
+        String interfaceName = null;
         byteBuffer.position(baseOffset);
         nlAttr = StructNlAttr.findNextAttrOfType(IFLA_IFNAME, byteBuffer);
         if (nlAttr != null) {
-            linkMsg.mInterfaceName = nlAttr.getValueAsString();
+            interfaceName = nlAttr.getValueAsString();
         }
 
-        return linkMsg;
+        return new RtNetlinkLinkMessage(header, ifinfoMsg, mtu, hardwareAddress, interfaceName);
+    }
+
+    /**
+     *  Write a rtnetlink link message to {@link byte} array.
+     */
+    public byte[] pack(ByteOrder order) {
+        byte[] bytes = new byte[mHeader.nlmsg_len];
+        ByteBuffer buffer = ByteBuffer.wrap(bytes).order(order);
+        pack(buffer);
+        return bytes;
     }
 
     /**
@@ -136,10 +178,10 @@
      */
     @VisibleForTesting
     protected void pack(ByteBuffer byteBuffer) {
-        getHeader().pack(byteBuffer);
+        mHeader.pack(byteBuffer);
         mIfinfomsg.pack(byteBuffer);
 
-        if (mMtu != 0) {
+        if (mMtu != DEFAULT_MTU) {
             final StructNlAttr mtu = new StructNlAttr(IFLA_MTU, mMtu);
             mtu.pack(byteBuffer);
         }
@@ -153,11 +195,34 @@
         }
     }
 
+    /**
+     *  Calculate the byte length of the packed buffer.
+     */
+    private static int calculateMessageLength(int mtu, MacAddress hardwareAddress,
+            String interfaceName) {
+        int length = StructNlMsgHdr.STRUCT_SIZE + StructIfinfoMsg.STRUCT_SIZE;
+
+        if (mtu != DEFAULT_MTU) {
+            length += NetlinkConstants.alignedLengthOf(StructNlAttr.NLA_HEADERLEN + Integer.BYTES);
+        }
+        if (hardwareAddress != null) {
+            length += NetlinkConstants.alignedLengthOf(
+                    StructNlAttr.NLA_HEADERLEN + ETHER_ADDR_LEN);
+        }
+        if (interfaceName != null) {
+            length += NetlinkConstants.alignedLengthOf(
+                    // The string should be end with '\0', so the length should plus 1.
+                    StructNlAttr.NLA_HEADERLEN + interfaceName.length() + 1);
+        }
+
+        return length;
+    }
+
     @Override
     public String toString() {
         return "RtNetlinkLinkMessage{ "
                 + "nlmsghdr{" + mHeader.toString(OsConstants.NETLINK_ROUTE) + "}, "
-                + "Ifinfomsg{" + mIfinfomsg.toString() + "}, "
+                + "Ifinfomsg{" + mIfinfomsg + "}, "
                 + "Hardware Address{" + mHardwareAddress + "}, "
                 + "MTU{" + mMtu + "}, "
                 + "Ifname{" + mInterfaceName + "} "
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 9db63db..afe220f 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
@@ -124,14 +124,14 @@
     }
 
     private static final String RTM_NEWLINK_PACK_HEX =
-            "34000000100000000000000000000000"   // struct nlmsghr
+            "40000000100000000000000000000000"   // struct nlmsghr
             + "000001001E0000000210000000000000" // struct ifinfo
             + "08000400DC050000"                 // IFLA_MTU
             + "0A00010092C3E3C9374E0000"         // IFLA_ADDRESS
             + "0A000300776C616E30000000";        // IFLA_IFNAME(wlan0)
 
     @Test
-    public void testPackRtmNewLink() {
+    public void testParseAndPackRtmNewLink() {
         final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_PACK_HEX);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
         final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
@@ -145,6 +145,21 @@
         assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBuffer.array()));
     }
 
+    @Test
+    public void testPackRtmNewLink() {
+        final RtNetlinkLinkMessage linkMsg = RtNetlinkLinkMessage.build(
+                // nlmsg_len will be updated inside create() method, so it's ok to set 0 here.
+                new StructNlMsgHdr(0 /*nlmsg_len*/, (short) 0x10, (short) 0, 0),
+                new StructIfinfoMsg((byte) 0, (short) 1, 0x1e, 0x1002, 0),
+                1500,
+                MacAddress.fromString("92:c3:e3:c9:37:4e"),
+                "wlan0");
+        assertNotNull(linkMsg);
+
+        final byte[] packBytes = linkMsg.pack(ByteOrder.LITTLE_ENDIAN);
+        assertEquals(RTM_NEWLINK_PACK_HEX, HexDump.toHexString(packBytes));
+    }
+
     private static final String RTM_NEWLINK_TRUNCATED_HEX =
             "54000000100000000000000000000000"   // struct nlmsghr
             + "000001001E0000000210000000000000" // struct ifinfo