[XFRM_MSG_NEWSA] Support xfrm_stats and xfrm_usersa_info

Bug: 308011229
Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.netlink
      (new tests added)
Change-Id: I8641cf36a790d7b15fbe41b4da328c1d91fcec1e
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmStats.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmStats.java
new file mode 100644
index 0000000..be13293
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmStats.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.xfrm;
+
+import com.android.net.module.util.Struct;
+
+/**
+ * Struct xfrm_lifetime_cur
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_stats {
+ *      __u32 replay_window;
+ *      __u32 replay;
+ *      __u32 integrity_failed;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmStats extends Struct {
+    public static final int STRUCT_SIZE = 12;
+
+    /** Number of packets that fall out of the replay window */
+    @Field(order = 0, type = Type.U32)
+    public final long replayWindow;
+
+    /** Number of replayed packets */
+    @Field(order = 1, type = Type.U32)
+    public final long replay;
+
+    /** Number of packets that failed authentication */
+    @Field(order = 2, type = Type.U32)
+    public final long integrityFailed;
+
+    // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+    public StructXfrmStats(long replayWindow, long replay, long integrityFailed) {
+        this.replayWindow = replayWindow;
+        this.replay = replay;
+        this.integrityFailed = integrityFailed;
+    }
+
+    // Constructor to build a new message
+    public StructXfrmStats() {
+        this(0, 0, 0);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfo.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfo.java
new file mode 100644
index 0000000..740a801
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfo.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2023 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.xfrm;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Computed;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Struct xfrm_usersa_info
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_usersa_info {
+ *      struct xfrm_selector sel;
+ *      struct xfrm_id id;
+ *      xfrm_address_t saddr;
+ *      struct xfrm_lifetime_cfg lft;
+ *      struct xfrm_lifetime_cur curlft;
+ *      struct xfrm_stats stats;
+ *      __u32 seq;
+ *      __u32 reqid;
+ *      __u16 family;
+ *      __u8 mode;
+ *      __u8 replay_window;
+ *      __u8 flags;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmUsersaInfo extends Struct {
+    private static final int NESTED_STRUCTS_SIZE =
+            StructXfrmSelector.STRUCT_SIZE
+                    + StructXfrmId.STRUCT_SIZE
+                    + StructXfrmAddressT.STRUCT_SIZE
+                    + StructXfrmLifetimeCfg.STRUCT_SIZE
+                    + StructXfrmLifetimeCur.STRUCT_SIZE
+                    + StructXfrmStats.STRUCT_SIZE;
+
+    public static final int STRUCT_SIZE = NESTED_STRUCTS_SIZE + 20;
+
+    @Computed private final StructXfrmSelector mXfrmSelector;
+    @Computed private final StructXfrmId mXfrmId;
+    @Computed private final StructXfrmAddressT mSourceXfrmAddressT;
+    @Computed private final StructXfrmLifetimeCfg mXfrmLifetime;
+    @Computed private final StructXfrmLifetimeCur mXfrmCurrentLifetime;
+    @Computed private final StructXfrmStats mXfrmStats;
+
+    @Field(order = 0, type = Type.ByteArray, arraysize = NESTED_STRUCTS_SIZE)
+    public final byte[] nestedStructs;
+
+    @Field(order = 1, type = Type.U32)
+    public final long seq;
+
+    @Field(order = 2, type = Type.U32)
+    public final long reqId;
+
+    @Field(order = 3, type = Type.U16)
+    public final int family;
+
+    @Field(order = 4, type = Type.U8)
+    public final short mode;
+
+    @Field(order = 5, type = Type.U8)
+    public final short replayWindowLegacy;
+
+    @Field(order = 6, type = Type.U8, padding = 7)
+    public final short flags;
+
+    // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+    public StructXfrmUsersaInfo(
+            @NonNull final byte[] nestedStructs,
+            long seq,
+            long reqId,
+            int family,
+            short mode,
+            short replayWindowLegacy,
+            short flags) {
+        this.nestedStructs = nestedStructs.clone();
+        this.seq = seq;
+        this.reqId = reqId;
+        this.family = family;
+        this.mode = mode;
+        this.replayWindowLegacy = replayWindowLegacy;
+        this.flags = flags;
+
+        final ByteBuffer nestedStructsBuff = ByteBuffer.wrap(nestedStructs);
+        nestedStructsBuff.order(ByteOrder.nativeOrder());
+
+        // The parsing order matters
+        mXfrmSelector = Struct.parse(StructXfrmSelector.class, nestedStructsBuff);
+        mXfrmId = Struct.parse(StructXfrmId.class, nestedStructsBuff);
+        mSourceXfrmAddressT = Struct.parse(StructXfrmAddressT.class, nestedStructsBuff);
+        mXfrmLifetime = Struct.parse(StructXfrmLifetimeCfg.class, nestedStructsBuff);
+        mXfrmCurrentLifetime = Struct.parse(StructXfrmLifetimeCur.class, nestedStructsBuff);
+        mXfrmStats = Struct.parse(StructXfrmStats.class, nestedStructsBuff);
+    }
+
+    // Constructor to build a new message for TESTING
+    StructXfrmUsersaInfo(
+            @NonNull final InetAddress dAddr,
+            @NonNull final InetAddress sAddr,
+            @NonNull final BigInteger addTime,
+            int selectorFamily,
+            long spi,
+            long seq,
+            long reqId,
+            short proto,
+            short mode,
+            short replayWindowLegacy,
+            short flags) {
+        this.seq = seq;
+        this.reqId = reqId;
+        this.family = dAddr instanceof Inet4Address ? OsConstants.AF_INET : OsConstants.AF_INET6;
+        this.mode = mode;
+        this.replayWindowLegacy = replayWindowLegacy;
+        this.flags = flags;
+
+        mXfrmSelector = new StructXfrmSelector(selectorFamily);
+        mXfrmId = new StructXfrmId(dAddr, spi, proto);
+        mSourceXfrmAddressT = new StructXfrmAddressT(sAddr);
+        mXfrmLifetime = new StructXfrmLifetimeCfg();
+        mXfrmCurrentLifetime =
+                new StructXfrmLifetimeCur(
+                        BigInteger.ZERO, BigInteger.ZERO, addTime, BigInteger.ZERO);
+        mXfrmStats = new StructXfrmStats();
+
+        final ByteBuffer nestedStructsBuff = ByteBuffer.allocate(NESTED_STRUCTS_SIZE);
+        nestedStructsBuff.order(ByteOrder.nativeOrder());
+
+        mXfrmSelector.writeToByteBuffer(nestedStructsBuff);
+        mXfrmId.writeToByteBuffer(nestedStructsBuff);
+        mSourceXfrmAddressT.writeToByteBuffer(nestedStructsBuff);
+        mXfrmLifetime.writeToByteBuffer(nestedStructsBuff);
+        mXfrmCurrentLifetime.writeToByteBuffer(nestedStructsBuff);
+        mXfrmStats.writeToByteBuffer(nestedStructsBuff);
+
+        this.nestedStructs = nestedStructsBuff.array();
+    }
+
+    // Constructor to build a new message
+    public StructXfrmUsersaInfo(
+            @NonNull final InetAddress dAddr,
+            @NonNull final InetAddress sAddr,
+            long spi,
+            long seq,
+            long reqId,
+            short proto,
+            short mode,
+            short replayWindowLegacy,
+            short flags) {
+        // Use AF_UNSPEC for all SAs selectors. In transport mode, kernel picks selector family
+        // based on usersa->family, while in tunnel mode, the XFRM_STATE_AF_UNSPEC flag allows
+        // dual-stack SAs.
+        this(
+                dAddr,
+                sAddr,
+                BigInteger.ZERO,
+                OsConstants.AF_UNSPEC,
+                spi,
+                seq,
+                reqId,
+                proto,
+                mode,
+                replayWindowLegacy,
+                flags);
+    }
+
+    /** Return the destination address */
+    public InetAddress getDestAddress() {
+        return mXfrmId.getDestAddress(family);
+    }
+
+    /** Return the source address */
+    public InetAddress getSrcAddress() {
+        return mSourceXfrmAddressT.getAddress(family);
+    }
+
+    /** Return the SPI */
+    public long getSpi() {
+        return mXfrmId.spi;
+    }
+
+    /** Return the current lifetime */
+    public StructXfrmLifetimeCur getCurrentLifetime() {
+        return mXfrmCurrentLifetime;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
index 9773cd6..14a3a0f 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
@@ -41,6 +41,9 @@
     public static final short XFRM_MSG_NEWSA = 16;
     public static final short XFRM_MSG_GETSA = 18;
 
+    public static final int XFRM_MODE_TRANSPORT = 0;
+    public static final int XFRM_MODE_TUNNEL = 1;
+
     public static final BigInteger XFRM_INF = new BigInteger("FFFFFFFFFFFFFFFF", 16);
 
     public XfrmNetlinkMessage(@NonNull final StructNlMsgHdr header) {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfoTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfoTest.java
new file mode 100644
index 0000000..94161ff
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaInfoTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 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.xfrm;
+
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MODE_TRANSPORT;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+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.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructXfrmUsersaInfoTest {
+    private static final String EXPECTED_HEX_STRING =
+            "00000000000000000000000000000000"
+                    + "00000000000000000000000000000000"
+                    + "00000000000000000A00000000000000"
+                    + "000000000000000020010DB800000000"
+                    + "0000000000000111AABBCCDD32000000"
+                    + "20010DB8000000000000000000000222"
+                    + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+                    + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+                    + "00000000000000000000000000000000"
+                    + "00000000000000000000000000000000"
+                    + "00000000000000000000000000000000"
+                    + "FD464C65000000000000000000000000"
+                    + "00000000000000000000000000000000"
+                    + "024000000A0000000000000000000000";
+    private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
+
+    private static final InetAddress DEST_ADDRESS =
+            InetAddresses.parseNumericAddress("2001:db8::111");
+    private static final InetAddress SOURCE_ADDRESS =
+            InetAddresses.parseNumericAddress("2001:db8::222");
+    private static final BigInteger ADD_TIME;
+    private static final int SELECTOR_FAMILY = OsConstants.AF_INET6;
+    private static final int FAMILY = OsConstants.AF_INET6;
+    private static final long SPI = 0xaabbccddL;
+    private static final long SEQ = 0L;
+    private static final long REQ_ID = 16386L;
+    private static final short PROTO = IPPROTO_ESP;
+    private static final short MODE = XFRM_MODE_TRANSPORT;
+    private static final short REPLAY_WINDOW_LEGACY = 0;
+    private static final short FLAGS = 0;
+
+    static {
+        final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+        cal.set(2023, Calendar.NOVEMBER, 9, 2, 42, 05);
+        final long timestampSeconds = TimeUnit.MILLISECONDS.toSeconds(cal.getTimeInMillis());
+        ADD_TIME = BigInteger.valueOf(timestampSeconds);
+    }
+
+    @Test
+    public void testEncode() throws Exception {
+        final StructXfrmUsersaInfo struct =
+                new StructXfrmUsersaInfo(
+                        DEST_ADDRESS,
+                        SOURCE_ADDRESS,
+                        ADD_TIME,
+                        SELECTOR_FAMILY,
+                        SPI,
+                        SEQ,
+                        REQ_ID,
+                        PROTO,
+                        MODE,
+                        REPLAY_WINDOW_LEGACY,
+                        FLAGS);
+
+        final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+        buffer.order(ByteOrder.nativeOrder());
+        struct.writeToByteBuffer(buffer);
+
+        assertArrayEquals(EXPECTED_HEX, buffer.array());
+    }
+
+    @Test
+    public void testDecode() throws Exception {
+        final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
+        buffer.order(ByteOrder.nativeOrder());
+
+        final StructXfrmUsersaInfo struct =
+                StructXfrmUsersaInfo.parse(StructXfrmUsersaInfo.class, buffer);
+
+        assertEquals(DEST_ADDRESS, struct.getDestAddress());
+        assertEquals(SOURCE_ADDRESS, struct.getSrcAddress());
+        assertEquals(SPI, struct.getSpi());
+        assertEquals(SEQ, struct.seq);
+        assertEquals(REQ_ID, struct.reqId);
+        assertEquals(FAMILY, struct.family);
+        assertEquals(MODE, struct.mode);
+        assertEquals(REPLAY_WINDOW_LEGACY, struct.replayWindowLegacy);
+        assertEquals(FLAGS, struct.flags);
+    }
+}