/* * 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); } } } }