summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2023-01-04 00:50:05 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-01-04 00:50:05 +0000
commitc7e226d1cd090242ccc8bdcc788bb01510bdafb6 (patch)
tree744476d2a8d5bf548839ee63107069e47e15e14c
parentb37499519cde9b663737e533602b3365320ee822 (diff)
parentafec7966bd3b964154ff2258c742a22fe01cc07c (diff)
downloadnet-c7e226d1cd090242ccc8bdcc788bb01510bdafb6.tar.gz
Merge "Add wear tethering util classes"
-rw-r--r--common/Android.bp24
-rw-r--r--common/device/com/android/net/module/util/async/Assertions.java41
-rw-r--r--common/device/com/android/net/module/util/async/CircularByteBuffer.java210
-rw-r--r--common/device/com/android/net/module/util/async/ReadableByteBuffer.java60
-rw-r--r--common/tests/unit/Android.bp9
-rw-r--r--common/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java267
6 files changed, 607 insertions, 4 deletions
diff --git a/common/Android.bp b/common/Android.bp
index d8a397d0..1fe6c2cc 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -304,6 +304,30 @@ java_library {
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/common/device/com/android/net/module/util/async/Assertions.java b/common/device/com/android/net/module/util/async/Assertions.java
new file mode 100644
index 00000000..ce701d05
--- /dev/null
+++ b/common/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/common/device/com/android/net/module/util/async/CircularByteBuffer.java b/common/device/com/android/net/module/util/async/CircularByteBuffer.java
new file mode 100644
index 00000000..92daa08f
--- /dev/null
+++ b/common/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/common/device/com/android/net/module/util/async/ReadableByteBuffer.java b/common/device/com/android/net/module/util/async/ReadableByteBuffer.java
new file mode 100644
index 00000000..7f824049
--- /dev/null
+++ b/common/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/common/tests/unit/Android.bp b/common/tests/unit/Android.bp
index 21e8c641..6e223bd4 100644
--- a/common/tests/unit/Android.bp
+++ b/common/tests/unit/Android.bp
@@ -12,14 +12,15 @@ android_library {
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",
+ "netd-client",
+ "net-tests-utils",
+ "net-utils-framework-common",
"net-utils-device-common",
+ "net-utils-device-common-async",
"net-utils-device-common-bpf",
- "net-tests-utils",
- "netd-client",
+ "net-utils-device-common-ip",
],
libs: [
"android.test.runner",
diff --git a/common/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java b/common/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
new file mode 100644
index 00000000..01abee2b
--- /dev/null
+++ b/common/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);
+ }
+ }
+ }
+}