Add wear tethering util classes

Bug: 245971639

Change-Id: I97655ece334ca93743896e89e12f8501f4e24776
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index d8a397d..1fe6c2c 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -304,6 +304,30 @@
     lint: { strict_updatability_linting: true },
 }
 
+java_library {
+    name: "net-utils-device-common-async",
+    srcs: [
+        "device/com/android/net/module/util/async/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    visibility: [
+        "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/libs/net/common/testutils:__subpackages__",
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+    libs: [
+        "framework-annotations-lib",
+    ],
+    static_libs: [
+    ],
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
 // Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
 // included either (some annotations would be duplicated on the bootclasspath).
 filegroup {
diff --git a/staticlibs/device/com/android/net/module/util/async/Assertions.java b/staticlibs/device/com/android/net/module/util/async/Assertions.java
new file mode 100644
index 0000000..ce701d0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/Assertions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.async;
+
+import android.os.Build;
+
+/**
+ * Implements basic assert functions for runtime error-checking.
+ *
+ * @hide
+ */
+public final class Assertions {
+    public static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
+
+    public static void throwsIfOutOfBounds(int totalLength, int pos, int len) {
+        if (!IS_USER_BUILD && ((totalLength | pos | len) < 0 || pos > totalLength - len)) {
+            throw new ArrayIndexOutOfBoundsException(
+                "length=" + totalLength + "; regionStart=" + pos + "; regionLength=" + len);
+        }
+    }
+
+    public static void throwsIfOutOfBounds(byte[] buffer, int pos, int len) {
+        throwsIfOutOfBounds(buffer != null ? buffer.length : 0, pos, len);
+    }
+
+    private Assertions() {}
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java
new file mode 100644
index 0000000..92daa08
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 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.async;
+
+import java.util.Arrays;
+
+/**
+ * Implements a circular read-write byte buffer.
+ *
+ * @hide
+ */
+public final class CircularByteBuffer implements ReadableByteBuffer {
+    private final byte[] mBuffer;
+    private final int mCapacity;
+    private int mReadPos;
+    private int mWritePos;
+    private int mSize;
+
+    public CircularByteBuffer(int capacity) {
+        mCapacity = capacity;
+        mBuffer = new byte[mCapacity];
+    }
+
+    @Override
+    public void clear() {
+        mReadPos = 0;
+        mWritePos = 0;
+        mSize = 0;
+        Arrays.fill(mBuffer, (byte) 0);
+    }
+
+    @Override
+    public int capacity() {
+        return mCapacity;
+    }
+
+    @Override
+    public int size() {
+        return mSize;
+    }
+
+    /** Returns the amount of remaining writeable space in this buffer. */
+    public int freeSize() {
+        return mCapacity - mSize;
+    }
+
+    @Override
+    public byte peek(int offset) {
+        if (offset < 0 || offset >= size()) {
+            throw new IllegalArgumentException("Invalid offset=" + offset + ", size=" + size());
+        }
+
+        return mBuffer[(mReadPos + offset) % mCapacity];
+    }
+
+    @Override
+    public void readBytes(byte[] dst, int dstPos, int dstLen) {
+        if (dst != null) {
+            Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen);
+        }
+        if (dstLen > size()) {
+            throw new IllegalArgumentException("Invalid len=" + dstLen + ", size=" + size());
+        }
+
+        while (dstLen > 0) {
+            final int copyLen = getCopyLen(mReadPos, mWritePos, dstLen);
+            if (dst != null) {
+                System.arraycopy(mBuffer, mReadPos, dst, dstPos, copyLen);
+            }
+            dstPos += copyLen;
+            dstLen -= copyLen;
+            mSize -= copyLen;
+            mReadPos = (mReadPos + copyLen) % mCapacity;
+        }
+
+        if (mSize == 0) {
+            // Reset to the beginning for better contiguous access.
+            mReadPos = 0;
+            mWritePos = 0;
+        }
+    }
+
+    @Override
+    public void peekBytes(int offset, byte[] dst, int dstPos, int dstLen) {
+        Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen);
+        if (offset + dstLen > size()) {
+            throw new IllegalArgumentException("Invalid len=" + dstLen
+                    + ", offset=" + offset + ", size=" + size());
+        }
+
+        int tmpReadPos = (mReadPos + offset) % mCapacity;
+        while (dstLen > 0) {
+            final int copyLen = getCopyLen(tmpReadPos, mWritePos, dstLen);
+            System.arraycopy(mBuffer, tmpReadPos, dst, dstPos, copyLen);
+            dstPos += copyLen;
+            dstLen -= copyLen;
+            tmpReadPos = (tmpReadPos + copyLen) % mCapacity;
+        }
+    }
+
+    @Override
+    public int getDirectReadSize() {
+        if (size() == 0) {
+            return 0;
+        }
+        return (mReadPos < mWritePos ? (mWritePos - mReadPos) : (mCapacity - mReadPos));
+    }
+
+    @Override
+    public int getDirectReadPos() {
+        return mReadPos;
+    }
+
+    @Override
+    public byte[] getDirectReadBuffer() {
+        return mBuffer;
+    }
+
+    @Override
+    public void accountForDirectRead(int len) {
+        if (len < 0 || len > size()) {
+            throw new IllegalArgumentException("Invalid len=" + len + ", size=" + size());
+        }
+
+        mSize -= len;
+        mReadPos = (mReadPos + len) % mCapacity;
+    }
+
+    /** Copies given data to the end of the buffer. */
+    public void writeBytes(byte[] buffer, int pos, int len) {
+        Assertions.throwsIfOutOfBounds(buffer, pos, len);
+        if (len > freeSize()) {
+            throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize());
+        }
+
+        while (len > 0) {
+            final int copyLen = getCopyLen(mWritePos, mReadPos,len);
+            System.arraycopy(buffer, pos, mBuffer, mWritePos, copyLen);
+            pos += copyLen;
+            len -= copyLen;
+            mSize += copyLen;
+            mWritePos = (mWritePos + copyLen) % mCapacity;
+        }
+    }
+
+    private int getCopyLen(int startPos, int endPos, int len) {
+        if (startPos < endPos) {
+            return Math.min(len, endPos - startPos);
+        } else {
+            return Math.min(len, mCapacity - startPos);
+        }
+    }
+
+    /** Returns the amount of contiguous writeable space. */
+    public int getDirectWriteSize() {
+        if (freeSize() == 0) {
+            return 0;  // Return zero in case buffer is full.
+        }
+        return (mWritePos < mReadPos ? (mReadPos - mWritePos) : (mCapacity - mWritePos));
+    }
+
+    /** Returns the position of contiguous writeable space. */
+    public int getDirectWritePos() {
+        return mWritePos;
+    }
+
+    /** Returns the buffer reference for direct write operation. */
+    public byte[] getDirectWriteBuffer() {
+        return mBuffer;
+    }
+
+    /** Must be called after performing a direct write using getDirectWriteBuffer(). */
+    public void accountForDirectWrite(int len) {
+        if (len < 0 || len > freeSize()) {
+            throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize());
+        }
+
+        mSize += len;
+        mWritePos = (mWritePos + len) % mCapacity;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("CircularByteBuffer{c=");
+        sb.append(mCapacity);
+        sb.append(",s=");
+        sb.append(mSize);
+        sb.append(",r=");
+        sb.append(mReadPos);
+        sb.append(",w=");
+        sb.append(mWritePos);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java
new file mode 100644
index 0000000..7f82404
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.async;
+
+/**
+ * Allows reading from a buffer of bytes. The data can be read and thus removed,
+ * or peeked at without disturbing the buffer state.
+ *
+ * @hide
+ */
+public interface ReadableByteBuffer {
+    /** Returns the size of the buffered data. */
+    int size();
+
+    /**
+     * Returns the maximum amount of the buffered data.
+     *
+     * The caller may use this method in combination with peekBytes()
+     * to estimate when the buffer needs to be emptied using readData().
+     */
+    int capacity();
+
+    /** Clears all buffered data. */
+    void clear();
+
+    /** Returns a single byte at the given offset. */
+    byte peek(int offset);
+
+    /** Copies an array of bytes from the given offset to "dst". */
+    void peekBytes(int offset, byte[] dst, int dstPos, int dstLen);
+
+    /** Reads and removes an array of bytes from the head of the buffer. */
+    void readBytes(byte[] dst, int dstPos, int dstLen);
+
+    /** Returns the amount of contiguous readable data. */
+    int getDirectReadSize();
+
+    /** Returns the position of contiguous readable data. */
+    int getDirectReadPos();
+
+    /** Returns the buffer reference for direct read operation. */
+    byte[] getDirectReadBuffer();
+
+    /** Must be called after performing a direct read using getDirectReadBuffer(). */
+    void accountForDirectRead(int len);
+}
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 21e8c64..6e223bd 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -12,14 +12,15 @@
     min_sdk_version: "29",
     defaults: ["framework-connectivity-test-defaults"],
     static_libs: [
-        "net-utils-framework-common",
-        "net-utils-device-common-ip",
         "androidx.test.rules",
         "mockito-target-extended-minus-junit4",
-        "net-utils-device-common",
-        "net-utils-device-common-bpf",
-        "net-tests-utils",
         "netd-client",
+        "net-tests-utils",
+        "net-utils-framework-common",
+        "net-utils-device-common",
+        "net-utils-device-common-async",
+        "net-utils-device-common-bpf",
+        "net-utils-device-common-ip",
     ],
     libs: [
         "android.test.runner",
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
new file mode 100644
index 0000000..01abee2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2022 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.async;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CircularByteBufferTest {
+    @Test
+    public void writeBytes() {
+        final int capacity = 23;
+        CircularByteBuffer buffer = new CircularByteBuffer(capacity);
+        assertEquals(0, buffer.size());
+        assertEquals(0, buffer.getDirectReadSize());
+        assertEquals(capacity, buffer.freeSize());
+        assertEquals(capacity, buffer.getDirectWriteSize());
+
+        final byte[] writeBuffer = new byte[15];
+        buffer.writeBytes(writeBuffer, 0, writeBuffer.length);
+
+        assertEquals(writeBuffer.length, buffer.size());
+        assertEquals(writeBuffer.length, buffer.getDirectReadSize());
+        assertEquals(capacity - writeBuffer.length, buffer.freeSize());
+        assertEquals(capacity - writeBuffer.length, buffer.getDirectWriteSize());
+
+        buffer.clear();
+        assertEquals(0, buffer.size());
+        assertEquals(0, buffer.getDirectReadSize());
+        assertEquals(capacity, buffer.freeSize());
+        assertEquals(capacity, buffer.getDirectWriteSize());
+    }
+
+    @Test
+    public void writeBytes_withRollover() {
+        doTestReadWriteWithRollover(new BufferAccessor(false, false));
+    }
+
+    @Test
+    public void writeBytes_withFullBuffer() {
+        doTestReadWriteWithFullBuffer(new BufferAccessor(false, false));
+    }
+
+    @Test
+    public void directWriteBytes_withRollover() {
+        doTestReadWriteWithRollover(new BufferAccessor(true, true));
+    }
+
+    @Test
+    public void directWriteBytes_withFullBuffer() {
+        doTestReadWriteWithFullBuffer(new BufferAccessor(true, true));
+    }
+
+    private void doTestReadWriteWithFullBuffer(BufferAccessor accessor) {
+        CircularByteBuffer buffer = doTestReadWrite(accessor, 20, 5, 4);
+
+        assertEquals(0, buffer.size());
+        assertEquals(20, buffer.freeSize());
+    }
+
+    private void doTestReadWriteWithRollover(BufferAccessor accessor) {
+        // All buffer sizes are prime numbers to ensure that some read or write
+        // operations will roll over the end of the internal buffer.
+        CircularByteBuffer buffer = doTestReadWrite(accessor, 31, 13, 7);
+
+        assertNotEquals(0, buffer.size());
+    }
+
+    private CircularByteBuffer doTestReadWrite(BufferAccessor accessor,
+            final int capacity, final int writeLen, final int readLen) {
+        CircularByteBuffer buffer = new CircularByteBuffer(capacity);
+
+        final byte[] writeBuffer = new byte[writeLen + 2];
+        final byte[] peekBuffer = new byte[readLen + 2];
+        final byte[] readBuffer = new byte[readLen + 2];
+
+        final int numIterations = 1011;
+        final int maxRemaining = readLen - 1;
+
+        int currentWriteSymbol = 0;
+        int expectedReadSymbol = 0;
+        int expectedSize = 0;
+        int totalWritten = 0;
+        int totalRead = 0;
+
+        for (int i = 0; i < numIterations; i++) {
+            // Fill in with write buffers as much as possible.
+            while (buffer.freeSize() >= writeLen) {
+                currentWriteSymbol = fillTestBytes(writeBuffer, 1, writeLen, currentWriteSymbol);
+                accessor.writeBytes(buffer, writeBuffer, 1, writeLen);
+
+                expectedSize += writeLen;
+                totalWritten += writeLen;
+                assertEquals(expectedSize, buffer.size());
+                assertEquals(capacity - expectedSize, buffer.freeSize());
+            }
+
+            // Keep reading into read buffers while there's still data.
+            while (buffer.size() >= readLen) {
+                peekBuffer[1] = 0;
+                peekBuffer[2] = 0;
+                buffer.peekBytes(2, peekBuffer, 3, readLen - 2);
+                assertEquals(0, peekBuffer[1]);
+                assertEquals(0, peekBuffer[2]);
+
+                peekBuffer[2] = buffer.peek(1);
+
+                accessor.readBytes(buffer, readBuffer, 1, readLen);
+                peekBuffer[1] = readBuffer[1];
+
+                expectedReadSymbol = checkTestBytes(
+                    readBuffer, 1, readLen, expectedReadSymbol, totalRead);
+
+                assertArrayEquals(peekBuffer, readBuffer);
+
+                expectedSize -= readLen;
+                totalRead += readLen;
+                assertEquals(expectedSize, buffer.size());
+                assertEquals(capacity - expectedSize, buffer.freeSize());
+            }
+
+            if (buffer.size() > maxRemaining) {
+                fail("Too much data remaining: " + buffer.size());
+            }
+        }
+
+        final int maxWritten = capacity * numIterations;
+        final int minWritten = maxWritten / 2;
+        if (totalWritten < minWritten || totalWritten > maxWritten
+                || (totalWritten - totalRead) > maxRemaining) {
+            fail("Unexpected counts: read=" + totalRead + ", written=" + totalWritten
+                    + ", minWritten=" + minWritten + ", maxWritten=" + maxWritten);
+        }
+
+        return buffer;
+    }
+
+    @Test
+    public void readBytes_overflow() {
+        CircularByteBuffer buffer = new CircularByteBuffer(23);
+
+        final byte[] dataBuffer = new byte[15];
+        buffer.writeBytes(dataBuffer, 0, dataBuffer.length - 2);
+
+        try {
+            buffer.readBytes(dataBuffer, 0, dataBuffer.length);
+            assertTrue(false);
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        assertEquals(13, buffer.size());
+        assertEquals(10, buffer.freeSize());
+    }
+
+    @Test
+    public void writeBytes_overflow() {
+        CircularByteBuffer buffer = new CircularByteBuffer(23);
+
+        final byte[] dataBuffer = new byte[15];
+        buffer.writeBytes(dataBuffer, 0, dataBuffer.length);
+
+        try {
+            buffer.writeBytes(dataBuffer, 0, dataBuffer.length);
+            assertTrue(false);
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        assertEquals(15, buffer.size());
+        assertEquals(8, buffer.freeSize());
+    }
+
+    private static int fillTestBytes(byte[] buffer, int pos, int len, int startValue) {
+        for (int i = 0; i < len; i++) {
+            buffer[pos + i] = (byte) (startValue & 0xFF);
+            startValue = (startValue + 1) % 256;
+        }
+        return startValue;
+    }
+
+    private static int checkTestBytes(
+            byte[] buffer, int pos, int len, int startValue, int totalRead) {
+        for (int i = 0; i < len; i++) {
+            byte expectedValue = (byte) (startValue & 0xFF);
+            if (expectedValue != buffer[pos + i]) {
+                fail("Unexpected byte=" + (((int) buffer[pos + i]) & 0xFF)
+                        + ", expected=" + (((int) expectedValue) & 0xFF)
+                        + ", pos=" + (totalRead + i));
+            }
+            startValue = (startValue + 1) % 256;
+        }
+        return startValue;
+    }
+
+    private static final class BufferAccessor {
+        private final boolean mDirectRead;
+        private final boolean mDirectWrite;
+
+        BufferAccessor(boolean directRead, boolean directWrite) {
+            mDirectRead = directRead;
+            mDirectWrite = directWrite;
+        }
+
+        void writeBytes(CircularByteBuffer buffer, byte[] src, int pos, int len) {
+            if (mDirectWrite) {
+                while (len > 0) {
+                    if (buffer.getDirectWriteSize() == 0) {
+                        fail("Direct write size is zero: free=" + buffer.freeSize()
+                                + ", size=" + buffer.size());
+                    }
+                    int copyLen = Math.min(len, buffer.getDirectWriteSize());
+                    System.arraycopy(src, pos, buffer.getDirectWriteBuffer(),
+                        buffer.getDirectWritePos(), copyLen);
+                    buffer.accountForDirectWrite(copyLen);
+                    len -= copyLen;
+                    pos += copyLen;
+                }
+            } else {
+                buffer.writeBytes(src, pos, len);
+            }
+        }
+
+        void readBytes(CircularByteBuffer buffer, byte[] dst, int pos, int len) {
+            if (mDirectRead) {
+                while (len > 0) {
+                    if (buffer.getDirectReadSize() == 0) {
+                        fail("Direct read size is zero: free=" + buffer.freeSize()
+                                + ", size=" + buffer.size());
+                    }
+                    int copyLen = Math.min(len, buffer.getDirectReadSize());
+                    System.arraycopy(
+                        buffer.getDirectReadBuffer(), buffer.getDirectReadPos(), dst, pos, copyLen);
+                    buffer.accountForDirectRead(copyLen);
+                    len -= copyLen;
+                    pos += copyLen;
+                }
+            } else {
+                buffer.readBytes(dst, pos, len);
+            }
+        }
+    }
+}