Merge "Make sure the test module uninstall the apks at the end"
diff --git a/src/com/android/networkstack/netlink/TcpInfo.java b/src/com/android/networkstack/netlink/TcpInfo.java
new file mode 100644
index 0000000..7a2adc7
--- /dev/null
+++ b/src/com/android/networkstack/netlink/TcpInfo.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2019 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.networkstack.netlink;
+
+import android.util.Log;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+/**
+ * Class for tcp_info.
+ *
+ * Corresponds to {@code struct tcp_info} from bionic/libc/kernel/uapi/linux/tcp.h
+ */
+public class TcpInfo {
+    public enum Field {
+        STATE(Byte.BYTES),
+        CASTATE(Byte.BYTES),
+        RETRANSMITS(Byte.BYTES),
+        PROBES(Byte.BYTES),
+        BACKOFF(Byte.BYTES),
+        OPTIONS(Byte.BYTES),
+        WSCALE(Byte.BYTES),
+        DELIVERY_RATE_APP_LIMITED(Byte.BYTES),
+        RTO(Integer.BYTES),
+        ATO(Integer.BYTES),
+        SND_MSS(Integer.BYTES),
+        RCV_MSS(Integer.BYTES),
+        UNACKED(Integer.BYTES),
+        SACKED(Integer.BYTES),
+        LOST(Integer.BYTES),
+        RETRANS(Integer.BYTES),
+        FACKETS(Integer.BYTES),
+        LAST_DATA_SENT(Integer.BYTES),
+        LAST_ACK_SENT(Integer.BYTES),
+        LAST_DATA_RECV(Integer.BYTES),
+        LAST_ACK_RECV(Integer.BYTES),
+        PMTU(Integer.BYTES),
+        RCV_SSTHRESH(Integer.BYTES),
+        RTT(Integer.BYTES),
+        RTTVAR(Integer.BYTES),
+        SND_SSTHRESH(Integer.BYTES),
+        SND_CWND(Integer.BYTES),
+        ADVMSS(Integer.BYTES),
+        REORDERING(Integer.BYTES),
+        RCV_RTT(Integer.BYTES),
+        RCV_SPACE(Integer.BYTES),
+        TOTAL_RETRANS(Integer.BYTES),
+        PACING_RATE(Long.BYTES),
+        MAX_PACING_RATE(Long.BYTES),
+        BYTES_ACKED(Long.BYTES),
+        BYTES_RECEIVED(Long.BYTES),
+        SEGS_OUT(Integer.BYTES),
+        SEGS_IN(Integer.BYTES),
+        NOTSENT_BYTES(Integer.BYTES),
+        MIN_RTT(Integer.BYTES),
+        DATA_SEGS_IN(Integer.BYTES),
+        DATA_SEGS_OUT(Integer.BYTES),
+        DELIVERY_RATE(Long.BYTES),
+        BUSY_TIME(Long.BYTES),
+        RWND_LIMITED(Long.BYTES),
+        SNDBUF_LIMITED(Long.BYTES);
+
+        public final int size;
+
+        Field(int s) {
+            size = s;
+        }
+    }
+
+    private static final String TAG = "TcpInfo";
+    private final LinkedHashMap<Field, Number> mFieldsValues = new LinkedHashMap<Field, Number>();
+
+    private TcpInfo(@NonNull ByteBuffer bytes, int infolen) {
+        final int start = bytes.position();
+        for (final Field field : Field.values()) {
+            switch (field.size) {
+                case Byte.BYTES:
+                    mFieldsValues.put(field, getByte(bytes, start, infolen));
+                    break;
+                case Integer.BYTES:
+                    mFieldsValues.put(field, getInt(bytes, start, infolen));
+                    break;
+                case Long.BYTES:
+                    mFieldsValues.put(field, getLong(bytes, start, infolen));
+                    break;
+                default:
+                    Log.e(TAG, "Unexpected size:" + field.size);
+            }
+        }
+
+    }
+
+    @VisibleForTesting
+    TcpInfo(@NonNull HashMap<Field, Number> info) {
+        for (final Field field : Field.values()) {
+            mFieldsValues.put(field, info.get(field));
+        }
+    }
+
+    /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */
+    @Nullable
+    public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) {
+        try {
+            TcpInfo info = new TcpInfo(bytes, infolen);
+            return info;
+        } catch (BufferUnderflowException e) {
+            Log.e(TAG, "parsing error.", e);
+            return null;
+        }
+    }
+
+    /**
+     * Helper function for handling different struct tcp_info versions in the kernel.
+     */
+    private static boolean isValidOffset(int start, int len, int pos, int targetBytes) {
+        final Range a = new Range(start, start + len);
+        final Range b = new Range(pos, pos + targetBytes);
+        return a.contains(b);
+    }
+
+    /** Get value for specific key. */
+    @Nullable
+    public Number getValue(@NonNull Field key) {
+        return mFieldsValues.get(key);
+    }
+
+    @Nullable
+    private static Byte getByte(@NonNull ByteBuffer buffer, int start, int len) {
+        if (!isValidOffset(start, len, buffer.position(), Byte.BYTES)) return null;
+
+        return buffer.get();
+    }
+
+    @Nullable
+    private static Integer getInt(@NonNull ByteBuffer buffer, int start, int len) {
+        if (!isValidOffset(start, len, buffer.position(), Integer.BYTES)) return null;
+
+        return buffer.getInt();
+    }
+
+    @Nullable
+    private static Long getLong(@NonNull ByteBuffer buffer, int start, int len) {
+        if (!isValidOffset(start, len, buffer.position(), Long.BYTES)) return null;
+
+        return buffer.getLong();
+    }
+
+    private static String decodeWscale(byte num) {
+        return String.valueOf((num >> 4) & 0x0f)  + ":" + String.valueOf(num & 0x0f);
+    }
+
+    /**
+     *  Returns a string representing a given tcp state.
+     *  Map to enum in bionic/libc/include/netinet/tcp.h
+     */
+    @VisibleForTesting
+    static String getTcpStateName(int state) {
+        switch (state) {
+            case 1: return "TCP_ESTABLISHED";
+            case 2: return "TCP_SYN_SENT";
+            case 3: return "TCP_SYN_RECV";
+            case 4: return "TCP_FIN_WAIT1";
+            case 5: return "TCP_FIN_WAIT2";
+            case 6: return "TCP_TIME_WAIT";
+            case 7: return "TCP_CLOSE";
+            case 8: return "TCP_CLOSE_WAIT";
+            case 9: return "TCP_LAST_ACK";
+            case 10: return "TCP_LISTEN";
+            case 11: return "TCP_CLOSING";
+            default: return "UNKNOWN:" + Integer.toString(state);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof TcpInfo)) return false;
+        TcpInfo other = (TcpInfo) obj;
+
+        for (final Field key : mFieldsValues.keySet()) {
+            if (!Objects.equals(mFieldsValues.get(key), other.mFieldsValues.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFieldsValues.values().toArray());
+    }
+
+    @Override
+    public String toString() {
+        String str = "TcpInfo{ ";
+        for (final Field key : mFieldsValues.keySet()) {
+            str += key.name().toLowerCase() + "=";
+            if (key == Field.STATE) {
+                str += getTcpStateName(mFieldsValues.get(key).intValue()) + " ";
+            } else if (key == Field.WSCALE) {
+                str += decodeWscale(mFieldsValues.get(key).byteValue()) + " ";
+            } else {
+                str += mFieldsValues.get(key) + " ";
+            }
+        }
+        str += "}";
+        return str;
+    }
+}
diff --git a/tests/lib/src/android/networkstack/util/FakeDns.kt b/tests/lib/src/com/android/testutils/FakeDns.kt
similarity index 98%
rename from tests/lib/src/android/networkstack/util/FakeDns.kt
rename to tests/lib/src/com/android/testutils/FakeDns.kt
index 157b6ce..825d748 100644
--- a/tests/lib/src/android/networkstack/util/FakeDns.kt
+++ b/tests/lib/src/com/android/testutils/FakeDns.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.networkstack.util
+package com.android.testutils
 
 import android.net.DnsResolver
 import android.net.InetAddresses
diff --git a/tests/unit/src/android/networkstack/metrics/DataStallStatsUtilsTest.kt b/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
similarity index 94%
rename from tests/unit/src/android/networkstack/metrics/DataStallStatsUtilsTest.kt
rename to tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
index 3b53b5f..3820165 100644
--- a/tests/unit/src/android/networkstack/metrics/DataStallStatsUtilsTest.kt
+++ b/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package android.networkstack.metrics
+package com.android.networkstack.metrics
 
 import android.net.captiveportal.CaptivePortalProbeResult
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
-import com.android.networkstack.metrics.DataStallStatsUtils
 import com.android.server.connectivity.nano.DataStallEventProto
 import org.junit.Assert.assertEquals
 import org.junit.Test
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
new file mode 100644
index 0000000..415bbe4
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2019 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.networkstack.netlink;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TcpInfoTest {
+    private static final int TCP_INFO_LENGTH_V1 = 192;
+    private static final int SHORT_TEST_TCP_INFO = 8;
+    private static final String TCP_ESTABLISHED = "TCP_ESTABLISHED";
+    private static final String TCP_FIN_WAIT1 = "TCP_FIN_WAIT1";
+    private static final String TCP_SYN_SENT = "TCP_SYN_SENT";
+    private static final String UNKNOWN_20 = "UNKNOWN:20";
+    // Refer to rfc793 for the value definition.
+    private static final String TCP_INFO_HEX =
+            "01" +                // state = TCP_ESTABLISHED
+            "00" +                // ca_state = TCP_CA_OPEN
+            "00" +                // retransmits = 0
+            "00" +                // probes = 0
+            "00" +                // backoff = 0
+            "07" +                // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
+            "88" +                // wscale = 8
+            "00" +                // delivery_rate_app_limited = 0
+            "001B914A" +          // rto = 1806666
+            "00000000" +          // ato = 0
+            "0000052E" +          // sndMss = 1326
+            "00000218" +          // rcvMss = 536
+            "00000000" +          // unsacked = 0
+            "00000000" +          // acked = 0
+            "00000000" +          // lost = 0
+            "00000000" +          // retrans = 0
+            "00000000" +          // fackets = 0
+            "000000BB" +          // lastDataSent = 187
+            "00000000" +          // lastAckSent = 0
+            "000000BB" +          // lastDataRecv = 187
+            "000000BB" +          // lastDataAckRecv = 187
+            "000005DC" +          // pmtu = 1500
+            "00015630" +          // rcvSsthresh = 87600
+            "00092C3E" +          // rttt = 601150
+            "0004961F" +          // rttvar = 300575
+            "00000578" +          // sndSsthresh = 1400
+            "0000000A" +          // sndCwnd = 10
+            "000005A8" +          // advmss = 1448
+            "00000003" +          // reordering = 3
+            "00000000" +          // rcvrtt = 0
+            "00015630" +          // rcvspace = 87600
+            "00000000" +          // totalRetrans = 0
+            "000000000000AC53" +  // pacingRate = 44115
+            "FFFFFFFFFFFFFFFF" +  // maxPacingRate = 18446744073709551615
+            "0000000000000001" +  // bytesAcked = 1
+            "0000000000000000" +  // bytesReceived = 0
+            "00000002" +          // SegsOut = 2
+            "00000001" +          // SegsIn = 1
+            "00000000" +          // NotSentBytes = 0
+            "00092C3E" +          // minRtt = 601150
+            "00000000" +          // DataSegsIn = 0
+            "00000000" +          // DataSegsOut = 0
+            "0000000000000000" +  // deliverRate = 0
+            "0000000000000000" +  // busyTime = 0
+            "0000000000000000" +  // RwndLimited = 0
+            "0000000000000000";   // sndBufLimited = 0
+    private static final byte[] TCP_INFO_BYTES =
+            HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false);
+
+    @Test
+    public void testParseTcpInfo() {
+        final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
+        final HashMap<TcpInfo.Field, Number> expected = makeTestTcpInfoHash();
+        final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
+
+        assertEquals(parsedInfo, new TcpInfo(expected));
+    }
+
+    @Test
+    public void testValidOffset() {
+        final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
+
+        final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();
+        final TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);
+
+        assertEquals(parsedInfo, new TcpInfo(expected));
+    }
+
+    @Test
+    public void testTcpStateName() {
+        assertEquals(TcpInfo.getTcpStateName(4), TCP_FIN_WAIT1);
+        assertEquals(TcpInfo.getTcpStateName(1), TCP_ESTABLISHED);
+        assertEquals(TcpInfo.getTcpStateName(2), TCP_SYN_SENT);
+        assertEquals(TcpInfo.getTcpStateName(20), UNKNOWN_20);
+    }
+
+    private static final String MALFORMED_TCP_INFO_HEX =
+            "01" +                // state = TCP_ESTABLISHED
+            "00" +                // ca_state = TCP_CA_OPEN
+            "00" +                // retransmits = 0
+            "00" +                // probes = 0
+            "00" +                // backoff = 0
+            "07" +                // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
+            "88" +                // wscale = 8
+            "00" +                // delivery_rate_app_limited = 0
+            "001B";               // Incomplete bytes, expect to be an int.
+    private static final byte[] MALFORMED_TCP_INFO_BYTES =
+            HexEncoding.decode(MALFORMED_TCP_INFO_HEX.toCharArray(), false);
+    @Test
+    public void testMalformedTcpInfo() {
+        final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES);
+        final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();
+
+        TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);
+        assertEquals(parsedInfo, new TcpInfo(expected));
+
+        parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
+        assertEquals(parsedInfo, null);
+    }
+
+    @Test
+    public void testGetValue() {
+        ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
+
+        final HashMap<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();
+        expected.put(TcpInfo.Field.MAX_PACING_RATE, 10_000L);
+        expected.put(TcpInfo.Field.FACKETS, 10);
+
+        final TcpInfo expectedInfo = new TcpInfo(expected);
+        assertEquals((byte) 0x01, expectedInfo.getValue(TcpInfo.Field.STATE));
+        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.CASTATE));
+        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.RETRANSMITS));
+        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.PROBES));
+        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.BACKOFF));
+        assertEquals((byte) 0x07, expectedInfo.getValue(TcpInfo.Field.OPTIONS));
+        assertEquals((byte) 0x88, expectedInfo.getValue(TcpInfo.Field.WSCALE));
+        assertEquals((byte) 0x00, expectedInfo.getValue(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED));
+
+        assertEquals(10_000L, expectedInfo.getValue(TcpInfo.Field.MAX_PACING_RATE));
+        assertEquals(10, expectedInfo.getValue(TcpInfo.Field.FACKETS));
+        assertEquals(null, expectedInfo.getValue(TcpInfo.Field.RTT));
+
+    }
+
+    // Make a TcpInfo contains only first 8 bytes.
+    private HashMap<TcpInfo.Field, Number> makeShortTestTcpInfoHash() {
+        final HashMap<TcpInfo.Field, Number> info = new HashMap<TcpInfo.Field, Number>();
+        info.put(TcpInfo.Field.STATE, (byte) 0x01);
+        info.put(TcpInfo.Field.CASTATE, (byte) 0x00);
+        info.put(TcpInfo.Field.RETRANSMITS, (byte) 0x00);
+        info.put(TcpInfo.Field.PROBES, (byte) 0x00);
+        info.put(TcpInfo.Field.BACKOFF, (byte) 0x00);
+        info.put(TcpInfo.Field.OPTIONS, (byte) 0x07);
+        info.put(TcpInfo.Field.WSCALE, (byte) 0x88);
+        info.put(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED, (byte) 0x00);
+
+        return info;
+    }
+
+    private HashMap<TcpInfo.Field, Number> makeTestTcpInfoHash() {
+        final HashMap<TcpInfo.Field, Number> info = makeShortTestTcpInfoHash();
+        info.put(TcpInfo.Field.RTO, 1806666);
+        info.put(TcpInfo.Field.ATO, 0);
+        info.put(TcpInfo.Field.SND_MSS, 1326);
+        info.put(TcpInfo.Field.RCV_MSS, 536);
+        info.put(TcpInfo.Field.UNACKED, 0);
+        info.put(TcpInfo.Field.SACKED, 0);
+        info.put(TcpInfo.Field.LOST, 0);
+        info.put(TcpInfo.Field.RETRANS, 0);
+        info.put(TcpInfo.Field.FACKETS, 0);
+        info.put(TcpInfo.Field.LAST_DATA_SENT, 187);
+        info.put(TcpInfo.Field.LAST_ACK_SENT, 0);
+        info.put(TcpInfo.Field.LAST_DATA_RECV, 187);
+        info.put(TcpInfo.Field.LAST_ACK_RECV, 187);
+        info.put(TcpInfo.Field.PMTU, 1500);
+        info.put(TcpInfo.Field.RCV_SSTHRESH, 87600);
+        info.put(TcpInfo.Field.RTT, 601150);
+        info.put(TcpInfo.Field.RTTVAR, 300575);
+        info.put(TcpInfo.Field.SND_SSTHRESH, 1400);
+        info.put(TcpInfo.Field.SND_CWND, 10);
+        info.put(TcpInfo.Field.ADVMSS, 1448);
+        info.put(TcpInfo.Field.REORDERING, 3);
+        info.put(TcpInfo.Field.RCV_RTT, 0);
+        info.put(TcpInfo.Field.RCV_SPACE, 87600);
+        info.put(TcpInfo.Field.TOTAL_RETRANS, 0);
+        info.put(TcpInfo.Field.PACING_RATE, 44115L);
+        info.put(TcpInfo.Field.MAX_PACING_RATE, -1L);
+        info.put(TcpInfo.Field.BYTES_ACKED, 1L);
+        info.put(TcpInfo.Field.BYTES_RECEIVED, 0L);
+        info.put(TcpInfo.Field.SEGS_OUT, 2);
+        info.put(TcpInfo.Field.SEGS_IN, 1);
+        info.put(TcpInfo.Field.NOTSENT_BYTES, 0);
+        info.put(TcpInfo.Field.MIN_RTT, 601150);
+        info.put(TcpInfo.Field.DATA_SEGS_IN, 0);
+        info.put(TcpInfo.Field.DATA_SEGS_OUT, 0);
+        info.put(TcpInfo.Field.DELIVERY_RATE, 0L);
+        info.put(TcpInfo.Field.BUSY_TIME, 0L);
+        info.put(TcpInfo.Field.RWND_LIMITED, 0L);
+        info.put(TcpInfo.Field.SNDBUF_LIMITED, 0L);
+
+        return info;
+    }
+}
diff --git a/tests/unit/src/android/networkstack/util/DnsUtilsTest.kt b/tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt
similarity index 97%
rename from tests/unit/src/android/networkstack/util/DnsUtilsTest.kt
rename to tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt
index 815fc60..59d96be 100644
--- a/tests/unit/src/android/networkstack/util/DnsUtilsTest.kt
+++ b/tests/unit/src/com/android/networkstack/util/DnsUtilsTest.kt
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package android.networkstack.util
+package com.android.networkstack.netlink.util
 
 import android.net.DnsResolver
 import android.net.DnsResolver.FLAG_EMPTY
 import android.net.DnsResolver.TYPE_A
 import android.net.DnsResolver.TYPE_AAAA
 import android.net.Network
+import com.android.testutils.FakeDns
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.networkstack.util.DnsUtils