java-topology/test/jdk/java/net/httpclient/quic/BuffersReaderVLTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

313 lines
15 KiB
Java

/*
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.ByteBuffer;
import java.util.List;
import jdk.internal.net.http.quic.BuffersReader;
import jdk.internal.net.http.quic.BuffersReader.ListBuffersReader;
import jdk.internal.net.http.quic.VariableLengthEncoder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
/*
* @test
* @library /test/lib
* @modules java.net.http/jdk.internal.net.http.quic
* @run junit/othervm BuffersReaderVLTest
* @summary Tests to check quic/util methods encode/decodeVariableLength methods
* work as expected.
*/
public class BuffersReaderVLTest {
static final Class<? extends Throwable> IAE = IllegalArgumentException.class;
public static Object[][] decodeInvariants() {
return new Object[][]
{
{ new byte[]{7}, 7, 1 }, // 00
{ new byte[]{65, 11}, 267, 2 }, // 01
{ new byte[]{-65, 11, 22, 33}, 1057691169, 4 }, // 10
{ new byte[]{-1, 11, 22, 33, 44, 55, 66, 77}, 4542748980864827981L, 8 }, // 11
{ new byte[]{-1, -11, -22, -33, -44, -55, -66, -77}, 4608848040752168627L, 8 },
{ new byte[]{}, -1, 0 },
{ new byte[]{-65}, -1, 0 },
};
}
public static Object[][] prefixInvariants() {
return new Object[][]
{
{ Long.MAX_VALUE, 0, IAE },
{ 4611686018427387903L+1, 0, IAE },
{ 4611686018427387903L, 3, null },
{ 4611686018427387903L-1, 3, null },
{ 1073741823+1, 3, null },
{ 1073741823, 2, null }, // (length > (1L << 30)-1)
{ 1073741823-1, 2, null },
{ 16383+1, 2, null },
{ 16383, 1, null }, // (length > (1L << 14)-1
{ 16383-1, 1, null },
{ 63+1, 1, null },
{ 63 , 0, null }, // (length > (1L << 6)-1
{ 63-1, 0, null },
{ 100, 1, null },
{ 10, 0, null },
{ 1, 0, null },
{ 0, 0, null }, // (length >= 0)
{ -1, 0, IAE },
{ -10, 0, IAE },
{ -100, 0, IAE },
{ Long.MIN_VALUE, 0, IAE },
{ -4611686018427387903L-1, 0, IAE },
{ -4611686018427387903L, 0, IAE },
{ -4611686018427387903L+1, 0, IAE },
{ -1073741823-1, 0, IAE },
{ -1073741823, 0, IAE }, // (length > (1L << 30)-1)
{ -1073741823+1, 0, IAE },
{ -16383-1, 0, IAE },
{ -16383, 0, IAE }, // (length > (1L << 14)-1
{ -16383+1, 0, IAE },
{ -63-1, 0, IAE },
{ -63 , 0, IAE }, // (length > (1L << 6)-1
{ -63+1, 0, IAE },
};
}
@ParameterizedTest
@MethodSource("decodeInvariants")
public void testDecode(byte[] values, long expectedLength, int expectedPosition) {
ByteBuffer bb = ByteBuffer.wrap(values);
BuffersReader br = BuffersReader.list(bb);
var actualLength = VariableLengthEncoder.decode(br);
assertEquals(expectedLength, actualLength);
var actualPosition = bb.position();
assertEquals(expectedPosition, actualPosition);
assertEquals(expectedPosition, br.position());
br.release();
assertEquals(expectedPosition, br.read());
}
@ParameterizedTest
@MethodSource("decodeInvariants")
public void testPeek(byte[] values, long expectedLength, int expectedPosition) {
ByteBuffer bb = ByteBuffer.wrap(values);
BuffersReader br = BuffersReader.list(bb);
var actualLength = VariableLengthEncoder.peekEncodedValue(br, 0);
assertEquals(expectedLength, actualLength);
var actualPosition = bb.position();
assertEquals(0, actualPosition);
assertEquals(0, br.position());
br.release();
assertEquals(0, br.read());
}
// Encode the given length and then decodes it and compares
// the results, asserting various invariants along the way.
@ParameterizedTest
@MethodSource("prefixInvariants")
public void testEncodeDecode(long length, int expectedPrefix, Class<? extends Exception> exception) {
if (exception != null) {
assertThrows(exception, () -> VariableLengthEncoder.getEncodedSize(length));
assertThrows(exception, () -> VariableLengthEncoder.encode(ByteBuffer.allocate(16), length));
} else {
var actualSize = VariableLengthEncoder.getEncodedSize(length);
assertEquals(1 << expectedPrefix, actualSize);
assertTrue(actualSize > 0, "length is negative or zero: " + actualSize);
assertTrue(actualSize < 9, "length is too big: " + actualSize);
// Use different offsets for the position at which to encode/decode
for (int offset : List.of(0, 10)) {
System.out.printf("Encode/Decode %s on %s bytes with offset %s%n",
length, actualSize, offset);
// allocate buffers: one exact, one too short, one too long
ByteBuffer exact = ByteBuffer.allocate(actualSize + offset);
exact.position(offset);
ByteBuffer shorter = ByteBuffer.allocate(actualSize - 1 + offset);
shorter.position(offset);
ByteBuffer shorterref = ByteBuffer.allocate(actualSize - 1 + offset);
shorterref.position(offset);
ByteBuffer longer = ByteBuffer.allocate(actualSize + 10 + offset);
longer.position(offset);
// attempt to encode with a buffer too short
assertThrows(IAE, () -> VariableLengthEncoder.encode(shorter, length));
assertEquals(offset, shorter.position());
assertEquals(shorter.capacity(), shorter.limit());
assertEquals(-1, shorter.mismatch(shorterref));
assertEquals(-1, shorterref.mismatch(shorter));
// attempt to encode with a buffer that has the exact size
var exactres = VariableLengthEncoder.encode(exact, length);
assertEquals(actualSize, exactres);
assertEquals(actualSize + offset, exact.position());
assertFalse(exact.hasRemaining());
// attempt to encode with a buffer that has more bytes
var longres = VariableLengthEncoder.encode(longer, length);
assertEquals(actualSize, longres);
assertEquals(offset + actualSize, longer.position());
assertEquals(longer.capacity(), longer.limit());
assertEquals(10, longer.remaining());
// compare encodings
// first reset buffer positions for reading.
exact.position(offset);
longer.position(offset);
assertEquals(actualSize, longer.mismatch(exact));
assertEquals(actualSize, exact.mismatch(longer));
// decode with a buffer that is missing the last
// byte...
var shortSlice = exact.duplicate();
shortSlice.position(offset);
shortSlice.limit(offset + actualSize -1);
ListBuffersReader br = BuffersReader.list(shortSlice);
var actualLength = VariableLengthEncoder.decode(br);
assertEquals(-1L, actualLength);
assertEquals(offset, shortSlice.position());
assertEquals(offset + actualSize - 1, shortSlice.limit());
assertEquals(offset, br.position());
assertEquals(offset + actualSize - 1, br.limit());
br.release();
// decode with the exact buffer
br = BuffersReader.list(exact);
actualLength = VariableLengthEncoder.decode(br);
assertEquals(length, actualLength);
assertEquals(offset + actualSize, exact.position());
assertFalse(exact.hasRemaining());
assertEquals(offset + actualSize, br.position());
assertFalse(br.hasRemaining());
br.release();
assertEquals(actualSize, br.read());
assertFalse(br.hasRemaining());
// decode with the longer buffer
long read = br.read();
br.add(longer);
actualLength = VariableLengthEncoder.decode(br);
assertEquals(length, actualLength);
assertEquals(offset + actualSize, longer.position());
assertEquals(10, longer.remaining());
assertEquals(offset + actualSize, br.position());
assertEquals(10, br.remaining());
br.release();
assertEquals(actualSize, br.read() - read);
assertEquals(10, br.remaining());
}
}
}
// Encode the given length and then peeks it and compares
// the results, asserting various invariants along the way.
@ParameterizedTest
@MethodSource("prefixInvariants")
public void testEncodePeek(long length, int expectedPrefix, Class<? extends Exception> exception) {
if (exception != null) {
assertThrows(exception, () -> VariableLengthEncoder.getEncodedSize(length));
assertThrows(exception, () -> VariableLengthEncoder.encode(ByteBuffer.allocate(16), length));
return;
}
var actualSize = VariableLengthEncoder.getEncodedSize(length);
assertEquals(1 << expectedPrefix, actualSize);
assertTrue(actualSize > 0, "length is negative or zero: " + actualSize);
assertTrue(actualSize < 9, "length is too big: " + actualSize);
// Use different offsets for the position at which to encode/decode
for (int offset : List.of(0, 10)) {
System.out.printf("Encode/Peek %s on %s bytes with offset %s%n",
length, actualSize, offset);
// allocate buffers: one exact, one too long
ByteBuffer exact = ByteBuffer.allocate(actualSize + offset);
exact.position(offset);
ByteBuffer longer = ByteBuffer.allocate(actualSize + 10 + offset);
longer.position(offset);
// attempt to encode with a buffer that has the exact size
var exactres = VariableLengthEncoder.encode(exact, length);
assertEquals(actualSize, exactres);
assertEquals(actualSize + offset, exact.position());
assertFalse(exact.hasRemaining());
// attempt to encode with a buffer that has more bytes
var longres = VariableLengthEncoder.encode(longer, length);
assertEquals(actualSize, longres);
assertEquals(offset + actualSize, longer.position());
assertEquals(longer.capacity(), longer.limit());
assertEquals(10, longer.remaining());
// compare encodings
// first reset buffer positions for reading.
exact.position(offset);
longer.position(offset);
assertEquals(actualSize, longer.mismatch(exact));
assertEquals(actualSize, exact.mismatch(longer));
exact.position(0);
longer.position(0);
exact.limit(exact.capacity());
longer.limit(longer.capacity());
// decode with a buffer that is missing the last
// byte...
var shortSlice = exact.duplicate();
shortSlice.position(0);
shortSlice.limit(offset + actualSize - 1);
// need at least one byte to decode the size len...
var expectedSize = shortSlice.limit() <= offset ? -1 : actualSize;
assertEquals(expectedSize, VariableLengthEncoder.peekEncodedValueSize(shortSlice, offset));
var actualLength = VariableLengthEncoder.peekEncodedValue(shortSlice, offset);
assertEquals(-1L, actualLength);
assertEquals(0, shortSlice.position());
assertEquals(offset + actualSize - 1, shortSlice.limit());
// decode with the exact buffer
assertEquals(actualSize, VariableLengthEncoder.peekEncodedValueSize(exact, offset));
actualLength = VariableLengthEncoder.peekEncodedValue(exact, offset);
assertEquals(length, actualLength);
assertEquals(0, exact.position());
assertEquals(exact.capacity(), exact.limit());
// decode with the longer buffer
assertEquals(actualSize, VariableLengthEncoder.peekEncodedValueSize(longer, offset));
actualLength = VariableLengthEncoder.peekEncodedValue(longer, offset);
assertEquals(length, actualLength);
assertEquals(0, longer.position());
assertEquals(longer.capacity(), longer.limit());
}
}
}