diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2023-01-04 00:50:05 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2023-01-04 00:50:05 +0000 |
commit | c7e226d1cd090242ccc8bdcc788bb01510bdafb6 (patch) | |
tree | 744476d2a8d5bf548839ee63107069e47e15e14c | |
parent | b37499519cde9b663737e533602b3365320ee822 (diff) | |
parent | afec7966bd3b964154ff2258c742a22fe01cc07c (diff) | |
download | net-c7e226d1cd090242ccc8bdcc788bb01510bdafb6.tar.gz |
Merge "Add wear tethering util classes"
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); + } + } + } +} |