Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f4d4f6da authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "update structure of TcpInfo" am: 67ab4671

Change-Id: I1bfcb6fb644cabb91966cc5e1cdac7989fddd878
parents 3bd658a6 67ab4671
Loading
Loading
Loading
Loading
+43 −89
Original line number Diff line number Diff line
@@ -22,11 +22,9 @@ import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
@@ -91,27 +89,39 @@ public class TcpInfo {
    }

    private static final String TAG = "TcpInfo";
    private final Map<Field, Number> mFieldsValues;
    @VisibleForTesting
    static final int LOST_OFFSET = getFieldOffset(Field.LOST);
    @VisibleForTesting
    static final int RETRANSMITS_OFFSET = getFieldOffset(Field.RETRANSMITS);
    @VisibleForTesting
    static final int SEGS_IN_OFFSET = getFieldOffset(Field.SEGS_IN);
    @VisibleForTesting
    static final int SEGS_OUT_OFFSET = getFieldOffset(Field.SEGS_OUT);
    final int mSegsIn;
    final int mSegsOut;
    final int mLost;
    final int mRetransmits;

    private static int getFieldOffset(@NonNull final Field needle) {
        int offset = 0;
        for (final Field field : Field.values()) {
            if (field == needle) return offset;
            offset += field.size;
        }
        throw new IllegalArgumentException("Unknown field");
    }

    private TcpInfo(@NonNull ByteBuffer bytes, int infolen) {
        // SEGS_IN is the last required field in the buffer, so if the buffer is long enough for
        // SEGS_IN it's long enough for everything
        if (SEGS_IN_OFFSET + Field.SEGS_IN.size > infolen) {
            throw new IllegalArgumentException("Length " + infolen + " is less than required.");
        }
        final int start = bytes.position();
        final LinkedHashMap<Field, Number> fields = new LinkedHashMap<>();
        for (final Field field : Field.values()) {
            switch (field.size) {
                case Byte.BYTES:
                    fields.put(field, getByte(bytes, start, infolen));
                    break;
                case Integer.BYTES:
                    fields.put(field, getInt(bytes, start, infolen));
                    break;
                case Long.BYTES:
                    fields.put(field, getLong(bytes, start, infolen));
                    break;
                default:
                    Log.e(TAG, "Unexpected size:" + field.size);
            }
        }
        mFieldsValues = Collections.unmodifiableMap(fields);
        mSegsIn = bytes.getInt(start + SEGS_IN_OFFSET);
        mSegsOut = bytes.getInt(start + SEGS_OUT_OFFSET);
        mLost = bytes.getInt(start + LOST_OFFSET);
        mRetransmits = bytes.get(start + RETRANSMITS_OFFSET);
        // tcp_info structure grows over time as new fields are added. Jump to the end of the
        // structure, as unknown fields might remain at the end of the structure if the tcp_info
        // struct was expanded.
@@ -119,12 +129,11 @@ public class TcpInfo {
    }

    @VisibleForTesting
    TcpInfo(@NonNull Map<Field, Number> info) {
        final LinkedHashMap<Field, Number> fields = new LinkedHashMap<>();
        for (final Field field : Field.values()) {
            fields.put(field, info.get(field));
        }
        mFieldsValues = Collections.unmodifiableMap(fields);
    TcpInfo(int retransmits, int lost, int segsOut, int segsIn) {
        mRetransmits = retransmits;
        mLost = lost;
        mSegsOut = segsOut;
        mSegsIn = segsIn;
    }

    /** Parse a TcpInfo from a giving ByteBuffer with a specific length. */
@@ -132,53 +141,13 @@ public class TcpInfo {
    public static TcpInfo parse(@NonNull ByteBuffer bytes, int infolen) {
        try {
            return new TcpInfo(bytes, infolen);
        } catch (BufferUnderflowException | IllegalArgumentException e) {
        } catch (BufferUnderflowException | BufferOverflowException | IllegalArgumentException
                | IndexOutOfBoundsException e) {
            Log.e(TAG, "parsing error.", e);
            return null;
        }
    }

    /**
     * Helper function for handling different struct tcp_info versions in the kernel.
     */
    private static boolean isValidTargetPosition(int start, int len, int pos, int targetBytes)
            throws IllegalArgumentException {
        // Equivalent to new Range(start, start + len).contains(new Range(pos, pos + targetBytes))
        if (len < 0 || targetBytes < 0) throw new IllegalArgumentException();
        // Check that start < pos < start + len
        if (pos < start || pos > start + len) return false;
        // Pos is inside the range and targetBytes is positive. Offset is valid if end of 2nd range
        // is below end of 1st range.
        return pos + targetBytes <= start + len;
    }

    /** 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 (!isValidTargetPosition(start, len, buffer.position(), Byte.BYTES)) return null;

        return buffer.get();
    }

    @Nullable
    private static Integer getInt(@NonNull ByteBuffer buffer, int start, int len) {
        if (!isValidTargetPosition(start, len, buffer.position(), Integer.BYTES)) return null;

        return buffer.getInt();
    }

    @Nullable
    private static Long getLong(@NonNull ByteBuffer buffer, int start, int len) {
        if (!isValidTargetPosition(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);
    }
@@ -210,33 +179,18 @@ public class TcpInfo {
        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;
        return mSegsIn == other.mSegsIn && mSegsOut == other.mSegsOut
            && mRetransmits == other.mRetransmits && mLost == other.mLost;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mFieldsValues.values().toArray());
        return Objects.hash(mLost, mRetransmits, mSegsIn, mSegsOut);
    }

    @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;
        return "TcpInfo{lost=" + mLost + ", retransmit=" + mRetransmits + ", received=" + mSegsIn
                + ", sent=" + mSegsOut + "}";
    }
}
+8 −8
Original line number Diff line number Diff line
@@ -340,16 +340,16 @@ public class TcpSocketTracker {
            return null;
        }

        stat.sentCount = current.tcpInfo.getValue(TcpInfo.Field.SEGS_OUT).intValue();
        stat.receivedCount = current.tcpInfo.getValue(TcpInfo.Field.SEGS_IN).intValue();
        stat.lostCount = current.tcpInfo.getValue(TcpInfo.Field.LOST).intValue();
        stat.retransmitCount = current.tcpInfo.getValue(TcpInfo.Field.RETRANSMITS).intValue();
        stat.sentCount = current.tcpInfo.mSegsOut;
        stat.receivedCount = current.tcpInfo.mSegsIn;
        stat.lostCount = current.tcpInfo.mLost;
        stat.retransmitCount = current.tcpInfo.mRetransmits;

        if (previous != null && previous.tcpInfo != null) {
            stat.sentCount -= previous.tcpInfo.getValue(TcpInfo.Field.SEGS_OUT).intValue();
            stat.receivedCount -= previous.tcpInfo.getValue(TcpInfo.Field.SEGS_IN).intValue();
            stat.lostCount -= previous.tcpInfo.getValue(TcpInfo.Field.LOST).intValue();
            stat.retransmitCount -= previous.tcpInfo.getValue(TcpInfo.Field.RETRANSMITS).intValue();
            stat.sentCount -= previous.tcpInfo.mSegsOut;
            stat.receivedCount -= previous.tcpInfo.mSegsIn;
            stat.lostCount -= previous.tcpInfo.mLost;
            stat.retransmitCount -= previous.tcpInfo.mRetransmits;
        }

        return stat;
+26 −41
Original line number Diff line number Diff line
@@ -89,6 +89,8 @@ public class TcpInfoTest {
            "0000000000000000";   // sndBufLimited = 0
    private static final byte[] TCP_INFO_BYTES =
            HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false);
    private static final TcpInfo TEST_TCPINFO =
            new TcpInfo(0 /* retransmits */, 0 /* lost */, 2 /* segsOut */, 1 /* segsIn */);

    private static final String EXPANDED_TCP_INFO_HEX = TCP_INFO_HEX
            + "00000000"         // tcpi_delivered
@@ -100,39 +102,47 @@ public class TcpInfoTest {
    @Test
    public void testParseTcpInfo() {
        final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
        final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash();
        // Length is less than required
        final TcpInfo nullInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);
        assertEquals(nullInfo, null);

        final TcpInfo parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        assertEquals(parsedInfo, TEST_TCPINFO);

        // Make a data that TcpInfo is not started from the begining of the buffer.
        final ByteBuffer bufferWithHeader =
                ByteBuffer.allocate(EXPANDED_TCP_INFO_BYTES.length + TCP_INFO_BYTES.length);
        bufferWithHeader.put(EXPANDED_TCP_INFO_BYTES);
        bufferWithHeader.put(TCP_INFO_BYTES);
        final TcpInfo infoWithHeader = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        bufferWithHeader.position(EXPANDED_TCP_INFO_BYTES.length);
        assertEquals(parsedInfo, TEST_TCPINFO);
    }

        assertEquals(parsedInfo, new TcpInfo(expected));
    @Test
    public void testFieldOffset() {
        assertEquals(TcpInfo.RETRANSMITS_OFFSET, 2);
        assertEquals(TcpInfo.LOST_OFFSET, 32);
        assertEquals(TcpInfo.SEGS_OUT_OFFSET, 136);
        assertEquals(TcpInfo.SEGS_IN_OFFSET, 140);
    }

    @Test
    public void testParseTcpInfoExpanded() {
        final ByteBuffer buffer = ByteBuffer.wrap(EXPANDED_TCP_INFO_BYTES);
        final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash();
        final TcpInfo parsedInfo =
                TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1 + EXPANDED_TCP_INFO_LENGTH);

        assertEquals(parsedInfo, new TcpInfo(expected));
        assertEquals(parsedInfo, TEST_TCPINFO);
        assertEquals(buffer.limit(), buffer.position());

        // reset the index.
        buffer.position(0);
        final TcpInfo parsedInfoShorterLen = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        assertEquals(parsedInfoShorterLen, new TcpInfo(expected));
        assertEquals(parsedInfoShorterLen, TEST_TCPINFO);
        assertEquals(TCP_INFO_LENGTH_V1, buffer.position());
    }

    @Test
    public void testValidOffset() {
        final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);

        final Map<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);
@@ -156,39 +166,14 @@ public class TcpInfoTest {
    @Test
    public void testMalformedTcpInfo() {
        final ByteBuffer buffer = ByteBuffer.wrap(MALFORMED_TCP_INFO_BYTES);
        final Map<TcpInfo.Field, Number> expected = makeShortTestTcpInfoHash();

        TcpInfo parsedInfo = TcpInfo.parse(buffer, SHORT_TEST_TCP_INFO);
        assertEquals(parsedInfo, new TcpInfo(expected));
        assertEquals(parsedInfo, null);

        parsedInfo = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
        assertEquals(parsedInfo, null);
    }

    @Test
    public void testGetValue() {
        ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);

        final Map<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 Map<TcpInfo.Field, Number> makeShortTestTcpInfoHash() {
        final Map<TcpInfo.Field, Number> info = new LinkedHashMap<>();
+4 −47
Original line number Diff line number Diff line
@@ -61,7 +61,6 @@ import org.mockito.quality.Strictness;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;

// TODO: Add more tests for missing coverage.
@RunWith(AndroidJUnit4.class)
@@ -174,6 +173,8 @@ public class TcpSocketTrackerTest {
            "0000000000000000"; // deliverRate = 0
    private static final byte[] SOCK_DIAG_TCP_INET_BYTES =
            HexEncoding.decode(SOCK_DIAG_TCP_INET_HEX.toCharArray(), false);
    private static final TcpInfo TEST_TCPINFO =
            new TcpInfo(5 /* retransmits */, 0 /* lost */, 10 /* segsOut */, 0 /* segsIn */);

    private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_INET_HEX
            // struct nlmsghdr
@@ -253,52 +254,8 @@ public class TcpSocketTrackerTest {
        buffer.position(SOCKDIAG_MSG_HEADER_SIZE);
        final TcpSocketTracker.SocketInfo parsed =
                tst.parseSockInfo(buffer, AF_INET, 276, 100L);
        final HashMap<TcpInfo.Field, Number> expected = new HashMap<>();
        expected.put(TcpInfo.Field.STATE, (byte) 0x01);
        expected.put(TcpInfo.Field.CASTATE, (byte) 0x00);
        expected.put(TcpInfo.Field.RETRANSMITS, (byte) 0x05);
        expected.put(TcpInfo.Field.PROBES, (byte) 0x00);
        expected.put(TcpInfo.Field.BACKOFF, (byte) 0x00);
        expected.put(TcpInfo.Field.OPTIONS, (byte) 0x07);
        expected.put(TcpInfo.Field.WSCALE, (byte) 0x88);
        expected.put(TcpInfo.Field.DELIVERY_RATE_APP_LIMITED, (byte) 0x00);
        expected.put(TcpInfo.Field.RTO, 1806666);
        expected.put(TcpInfo.Field.ATO, 0);
        expected.put(TcpInfo.Field.SND_MSS, 1326);
        expected.put(TcpInfo.Field.RCV_MSS, 536);
        expected.put(TcpInfo.Field.UNACKED, 0);
        expected.put(TcpInfo.Field.SACKED, 0);
        expected.put(TcpInfo.Field.LOST, 0);
        expected.put(TcpInfo.Field.RETRANS, 0);
        expected.put(TcpInfo.Field.FACKETS, 0);
        expected.put(TcpInfo.Field.LAST_DATA_SENT, 187);
        expected.put(TcpInfo.Field.LAST_ACK_SENT, 0);
        expected.put(TcpInfo.Field.LAST_DATA_RECV, 187);
        expected.put(TcpInfo.Field.LAST_ACK_RECV, 187);
        expected.put(TcpInfo.Field.PMTU, 1500);
        expected.put(TcpInfo.Field.RCV_SSTHRESH, 87600);
        expected.put(TcpInfo.Field.RTT, 601150);
        expected.put(TcpInfo.Field.RTTVAR, 300575);
        expected.put(TcpInfo.Field.SND_SSTHRESH, 1400);
        expected.put(TcpInfo.Field.SND_CWND, 10);
        expected.put(TcpInfo.Field.ADVMSS, 1448);
        expected.put(TcpInfo.Field.REORDERING, 3);
        expected.put(TcpInfo.Field.RCV_RTT, 0);
        expected.put(TcpInfo.Field.RCV_SPACE, 87600);
        expected.put(TcpInfo.Field.TOTAL_RETRANS, 0);
        expected.put(TcpInfo.Field.PACING_RATE, 44115L);
        expected.put(TcpInfo.Field.MAX_PACING_RATE, -1L);
        expected.put(TcpInfo.Field.BYTES_ACKED, 1L);
        expected.put(TcpInfo.Field.BYTES_RECEIVED, 0L);
        expected.put(TcpInfo.Field.SEGS_OUT, 10);
        expected.put(TcpInfo.Field.SEGS_IN, 0);
        expected.put(TcpInfo.Field.NOTSENT_BYTES, 0);
        expected.put(TcpInfo.Field.MIN_RTT, 601150);
        expected.put(TcpInfo.Field.DATA_SEGS_IN, 0);
        expected.put(TcpInfo.Field.DATA_SEGS_OUT, 0);
        expected.put(TcpInfo.Field.DELIVERY_RATE, 0L);

        assertEquals(parsed.tcpInfo, new TcpInfo(expected));

        assertEquals(parsed.tcpInfo, TEST_TCPINFO);
        assertEquals(parsed.fwmark, 789125);
        assertEquals(parsed.updateTime, 100);
        assertEquals(parsed.ipFamily, AF_INET);