blob: 01abee2b4a37ad6a8592a4688bc191d1ec0d90bd [file] [log] [blame]
/*
* 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);
}
}
}
}