jdk/test/jdk/java/net/httpclient/quic/AckElicitingTest.java
2026-02-20 20:21:00 +00:00

731 lines
29 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.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HexFormat;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import jdk.internal.net.http.quic.PeerConnectionId;
import jdk.internal.net.quic.QuicKeyUnavailableException;
import jdk.internal.net.quic.QuicOneRttContext;
import jdk.internal.net.quic.QuicVersion;
import jdk.internal.net.http.quic.frames.AckFrame;
import jdk.internal.net.http.quic.frames.AckFrame.AckRange;
import jdk.internal.net.http.quic.frames.ConnectionCloseFrame;
import jdk.internal.net.http.quic.frames.CryptoFrame;
import jdk.internal.net.http.quic.frames.DataBlockedFrame;
import jdk.internal.net.http.quic.frames.HandshakeDoneFrame;
import jdk.internal.net.http.quic.frames.MaxDataFrame;
import jdk.internal.net.http.quic.frames.MaxStreamDataFrame;
import jdk.internal.net.http.quic.frames.MaxStreamsFrame;
import jdk.internal.net.http.quic.frames.NewConnectionIDFrame;
import jdk.internal.net.http.quic.frames.NewTokenFrame;
import jdk.internal.net.http.quic.frames.PaddingFrame;
import jdk.internal.net.http.quic.frames.PathChallengeFrame;
import jdk.internal.net.http.quic.frames.PathResponseFrame;
import jdk.internal.net.http.quic.frames.PingFrame;
import jdk.internal.net.http.quic.frames.QuicFrame;
import jdk.internal.net.http.quic.frames.ResetStreamFrame;
import jdk.internal.net.http.quic.frames.RetireConnectionIDFrame;
import jdk.internal.net.http.quic.frames.StopSendingFrame;
import jdk.internal.net.http.quic.frames.StreamDataBlockedFrame;
import jdk.internal.net.http.quic.frames.StreamFrame;
import jdk.internal.net.http.quic.frames.StreamsBlockedFrame;
import jdk.internal.net.http.quic.packets.QuicPacketDecoder;
import jdk.internal.net.http.quic.packets.QuicPacketEncoder;
import jdk.internal.net.http.quic.CodingContext;
import jdk.internal.net.http.quic.QuicConnectionId;
import jdk.internal.net.http.quic.packets.QuicPacket;
import jdk.internal.net.http.quic.packets.QuicPacket.PacketNumberSpace;
import jdk.internal.net.http.quic.packets.QuicPacket.PacketType;
import jdk.internal.net.quic.QuicTLSEngine;
import jdk.internal.net.quic.QuicTransportException;
import jdk.internal.net.quic.QuicTransportParametersConsumer;
import jdk.test.lib.RandomFactory;
import javax.crypto.AEADBadTagException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import static jdk.internal.net.http.quic.frames.QuicFrame.*;
import static jdk.internal.net.http.quic.frames.ConnectionCloseFrame.CONNECTION_CLOSE_VARIANT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
/*
* @test
* @summary tests the logic to decide whether a packet or
* a frame is ACK-eliciting.
* @library /test/lib
* @run junit AckElicitingTest
* @run junit/othervm -Dseed=-7997973196290088038 AckElicitingTest
*/
public class AckElicitingTest {
static final Random RANDOM = RandomFactory.getRandom();
private static class DummyQuicTLSEngine implements QuicTLSEngine {
@Override
public HandshakeState getHandshakeState() {
throw new AssertionError("should not come here!");
}
@Override
public boolean isTLSHandshakeComplete() {
return true;
}
@Override
public KeySpace getCurrentSendKeySpace() {
throw new AssertionError("should not come here!");
}
@Override
public boolean keysAvailable(KeySpace keySpace) {
return true;
}
@Override
public void discardKeys(KeySpace keySpace) {
// no-op
}
@Override
public void setLocalQuicTransportParameters(ByteBuffer params) {
throw new AssertionError("should not come here!");
}
@Override
public void restartHandshake() throws IOException {
throw new AssertionError("should not come here!");
}
@Override
public void setRemoteQuicTransportParametersConsumer(QuicTransportParametersConsumer consumer) {
throw new AssertionError("should not come here!");
}
@Override
public void deriveInitialKeys(QuicVersion version, ByteBuffer connectionId) { }
@Override
public int getHeaderProtectionSampleSize(KeySpace keySpace) {
return 0;
}
@Override
public ByteBuffer computeHeaderProtectionMask(KeySpace keySpace, boolean incoming, ByteBuffer sample) {
return ByteBuffer.allocate(5);
}
@Override
public int getAuthTagSize() {
return 0;
}
@Override
public void encryptPacket(KeySpace keySpace, long packetNumber,
IntFunction<ByteBuffer> headerGenerator,
ByteBuffer packetPayload, ByteBuffer output)
throws QuicKeyUnavailableException, QuicTransportException {
// this dummy QUIC TLS engine doesn't do any encryption.
// we just copy over the raw packet payload into the output buffer
output.put(packetPayload);
}
@Override
public void decryptPacket(KeySpace keySpace, long packetNumber, int keyPhase,
ByteBuffer packet, int headerLength, ByteBuffer output) {
packet.position(packet.position() + headerLength);
output.put(packet);
}
@Override
public void signRetryPacket(QuicVersion quicVersion,
ByteBuffer originalConnectionId, ByteBuffer packet, ByteBuffer output) {
throw new AssertionError("should not come here!");
}
@Override
public void verifyRetryPacket(QuicVersion quicVersion,
ByteBuffer originalConnectionId, ByteBuffer packet) throws AEADBadTagException {
throw new AssertionError("should not come here!");
}
@Override
public ByteBuffer getHandshakeBytes(KeySpace keySpace) {
throw new AssertionError("should not come here!");
}
@Override
public void consumeHandshakeBytes(KeySpace keySpace, ByteBuffer payload) {
throw new AssertionError("should not come here!");
}
@Override
public Runnable getDelegatedTask() {
throw new AssertionError("should not come here!");
}
@Override
public boolean tryMarkHandshakeDone() {
throw new AssertionError("should not come here!");
}
@Override
public boolean tryReceiveHandshakeDone() {
throw new AssertionError("should not come here!");
}
@Override
public Set<QuicVersion> getSupportedQuicVersions() {
return Set.of(QuicVersion.QUIC_V1);
}
@Override
public void setUseClientMode(boolean mode) {
throw new AssertionError("should not come here!");
}
@Override
public boolean getUseClientMode() {
throw new AssertionError("should not come here!");
}
@Override
public SSLParameters getSSLParameters() {
throw new AssertionError("should not come here!");
}
@Override
public void setSSLParameters(SSLParameters sslParameters) {
throw new AssertionError("should not come here!");
}
@Override
public String getApplicationProtocol() {
return null;
}
@Override
public SSLSession getSession() { throw new AssertionError("should not come here!"); }
@Override
public SSLSession getHandshakeSession() { throw new AssertionError("should not come here!"); }
@Override
public void versionNegotiated(QuicVersion quicVersion) {
// no-op
}
@Override
public void setOneRttContext(QuicOneRttContext ctx) {
// no-op
}
}
private static final QuicTLSEngine TLS_ENGINE = new DummyQuicTLSEngine();
private static abstract class TestCodingContext implements CodingContext {
TestCodingContext() { }
@Override
public int writePacket(QuicPacket packet, ByteBuffer buffer) {
throw new AssertionError("should not come here!");
}
@Override
public QuicPacket parsePacket(ByteBuffer src) throws IOException {
throw new AssertionError("should not come here!");
}
@Override
public boolean verifyToken(QuicConnectionId destinationID, byte[] token) {
return true;
}
@Override
public QuicTLSEngine getTLSEngine() {
return TLS_ENGINE;
}
}
static final int CIDLEN = RANDOM.nextInt(5, QuicConnectionId.MAX_CONNECTION_ID_LENGTH + 1);
private static final TestCodingContext CONTEXT = new TestCodingContext() {
@Override
public long largestProcessedPN(PacketNumberSpace packetSpace) {
return 0;
}
@Override
public long largestAckedPN(PacketNumberSpace packetSpace) {
return 0;
}
@Override
public int connectionIdLength() {
return CIDLEN;
}
@Override
public QuicConnectionId originalServerConnId() {
return null;
}
};
/**
* A record to store all the input of a given test case.
* @param type a concrete frame type or packet type
* @param describer a function to describe the {@code obj} instance
* for tracing/diagnosis purposes
* @param ackEliciting the function we want to test. This is either
* {@link QuicFrame#isAckEliciting()
* QuicFrame::isAckEliciting} or
* {@link QuicPacket#isAckEliciting()
* QuicPacket::isAckEliciting}
* @param obj the instance on which to call the {@code ackEliciting}
* function.
* @param expected the expected result of calling
* {@code obj.ackEliciting()}
* @param <T> A concrete subclass of {@link QuicFrame} or {@link QuicPacket}
*/
record TestCase<T>(Class<? extends T> type,
Function<T, String> describer,
Predicate<? super T> ackEliciting,
T obj,
boolean expected) {
@Override
public String toString() {
// shorter & better toString than the default
return "%s(%s)"
.formatted(type.getSimpleName(), describer.apply(obj));
}
private static String describeFrame(QuicFrame frame) {
long type = frame.getTypeField();
return HexFormat.of().toHexDigits((byte)type);
}
/**
* Creates an instance of {@code TestCase} for a concrete frame type
* @param type the concrete frame class
* @param frame the concrete instance
* @param expected whether {@link QuicFrame#isAckEliciting()}
* should return true for that instance.
* @param <T> a concrete subclass of {@code QuicFrame}
* @return a new instance of {@code TestCase}
*/
public static <T extends QuicFrame> TestCase<T>
of(Class<? extends T> type, T frame, boolean expected) {
return new TestCase<>(type, TestCase::describeFrame,
QuicFrame::isAckEliciting, frame, expected);
}
/**
* Creates an instance of {@code TestCase} for a concrete frame type
* @param frame the concrete frame instance
* @param expected whether {@link QuicFrame#isAckEliciting()}
* should return true for that instance.
* @param <T> a concrete subclass of {@code QuicFrame}
* @return a new instance of {@code TestCase}
*/
public static <T extends QuicFrame> TestCase<T> of(T frame, boolean expected) {
return new TestCase<>((Class<T>)frame.getClass(),
TestCase::describeFrame,
QuicFrame::isAckEliciting,
frame, expected);
}
/**
* Creates an instance of {@code TestCase} for a concrete packet type
* @param packet the concrete packet instance
* @param expected whether {@link QuicPacket#isAckEliciting()}
* should return true for that instance.
* @param <T> a concrete subclass of {@code QuicPacket}
* @return a new instance of {@code TestCase}
*/
public static <T extends QuicPacket> TestCase<T> of(T packet, boolean expected) {
return new TestCase<T>((Class<T>)packet.getClass(),
(p) -> p.frames().stream()
.map(Object::getClass)
.map(Class::getSimpleName)
.collect(Collectors.joining(", ")),
QuicPacket::isAckEliciting,
packet, expected);
}
}
// convenient alias to shorten lines in data providers
private static <T extends QuicFrame> TestCase<T> of(T frame, boolean ackEliciting) {
return TestCase.of(frame, ackEliciting);
}
// convenient alias to shorten lines in data providers
private static <T extends QuicPacket> TestCase<T> of(T packet, boolean ackEliciting) {
return TestCase.of(packet, ackEliciting);
}
/**
* Create a new instance of the given frame type, populated with
* dummy values.
* @param frameClass the frame type
* @param <T> a concrete subclass of {@code QuicFrame}
* @return a new instance of the given concrete class.
*/
static <T extends QuicFrame> T newFrame(Class<T> frameClass) {
var frameType = QuicFrame.frameTypeOf(frameClass);
if (frameType == CONNECTION_CLOSE) {
if (RANDOM.nextBoolean()) {
frameType = CONNECTION_CLOSE_VARIANT;
}
}
long streamId = 4;
long largestAcknowledge = 3;
long ackDelay = 20;
long gap = 0;
long range = largestAcknowledge;
long offset = 0;
boolean fin = false;
int length = 10;
long errorCode = 1;
long finalSize = 10;
int size = 3;
long maxData = 10;
boolean maxStreamsBidi = true;
long maxStreams = 100;
long maxStreamData = 10;
long sequenceNumber = 4;
long retirePriorTo = 3;
String reason = "none";
long errorFrameType = ACK;
int pathChallengeLen = PathChallengeFrame.LENGTH;
int pathResponseLen = PathResponseFrame.LENGTH;
var frame = switch (frameType) {
case ACK -> new AckFrame(largestAcknowledge, ackDelay, List.of(new AckRange(gap, range)));
case STREAM -> new StreamFrame(streamId, offset, length, fin, ByteBuffer.allocate(length));
case RESET_STREAM -> new ResetStreamFrame(streamId, errorCode, finalSize);
case PADDING -> new PaddingFrame(size);
case PING -> new PingFrame();
case STOP_SENDING -> new StopSendingFrame(streamId, errorCode);
case CRYPTO -> new CryptoFrame(offset, length, ByteBuffer.allocate(length));
case NEW_TOKEN -> new NewTokenFrame(ByteBuffer.allocate(length));
case DATA_BLOCKED -> new DataBlockedFrame(maxData);
case MAX_DATA -> new MaxDataFrame(maxData);
case MAX_STREAMS -> new MaxStreamsFrame(maxStreamsBidi, maxStreams);
case MAX_STREAM_DATA -> new MaxStreamDataFrame(streamId, maxStreamData);
case STREAM_DATA_BLOCKED -> new StreamDataBlockedFrame(streamId, maxStreamData);
case STREAMS_BLOCKED -> new StreamsBlockedFrame(maxStreamsBidi, maxStreams);
case NEW_CONNECTION_ID -> new NewConnectionIDFrame(sequenceNumber, retirePriorTo,
ByteBuffer.allocate(length), ByteBuffer.allocate(16));
case RETIRE_CONNECTION_ID -> new RetireConnectionIDFrame(sequenceNumber);
case PATH_CHALLENGE -> new PathChallengeFrame(ByteBuffer.allocate(pathChallengeLen));
case PATH_RESPONSE -> new PathResponseFrame(ByteBuffer.allocate(pathResponseLen));
case CONNECTION_CLOSE -> new ConnectionCloseFrame(errorCode, errorFrameType, reason);
case CONNECTION_CLOSE_VARIANT -> new ConnectionCloseFrame(errorCode, reason);
case HANDSHAKE_DONE -> new HandshakeDoneFrame();
default -> throw new IllegalArgumentException("Unrecognised frame");
};
return frameClass.cast(frame);
}
/**
* Creates a list of {@code TestCase} to test all possible concrete
* subclasses of {@code QuicFrame}.
*
* @return a list of {@code TestCase} to test all possible concrete
* subclasses of {@code QuicFrame}
*/
static List<TestCase<? extends QuicFrame>> createFramesTests() {
List<TestCase<? extends QuicFrame>> frames = new ArrayList<>();
frames.add(of(newFrame(AckFrame.class), false));
frames.add(of(newFrame(ConnectionCloseFrame.class), false));
frames.add(of(newFrame(PaddingFrame.class), false));
for (var frameType : QuicFrame.class.getPermittedSubclasses()) {
if (frameType == AckFrame.class) continue;
if (frameType == ConnectionCloseFrame.class) continue;
if (frameType == PaddingFrame.class) continue;
Class<? extends QuicFrame> quicFrameClass = (Class<? extends QuicFrame>)frameType;
frames.add(of(newFrame(quicFrameClass), true));
}
return List.copyOf(frames);
}
/**
* Creates a {@code QuicPacket} containing the given list of frames.
* @param frames a list of frames
* @return a new instance of {@code QuicPacket}
*/
static QuicPacket createPacket(List<QuicFrame> frames) {
PacketType[] values = PacketType.values();
int index = PacketType.NONE.ordinal();
while (index == PacketType.NONE.ordinal()) {
index = RANDOM.nextInt(0, values.length);
}
PacketType packetType = values[index];
QuicPacketEncoder encoder = QuicPacketEncoder.of(QuicVersion.QUIC_V1);
byte[] scid = new byte[CIDLEN];
RANDOM.nextBytes(scid);
byte[] dcid = new byte[CIDLEN];
RANDOM.nextBytes(dcid);
QuicConnectionId source = new PeerConnectionId(scid);
QuicConnectionId dest = new PeerConnectionId(dcid);
long largestAckedPacket = CONTEXT.largestAckedPN(packetType);
QuicPacket packet = switch (packetType) {
case NONE -> throw new AssertionError("should not come here");
// TODO: add more packet types
default -> encoder.newInitialPacket(source, dest, null, largestAckedPacket + 1 ,
largestAckedPacket, frames, CONTEXT);
};
return packet;
}
/**
* Creates a random instance of {@code QuicPacket} containing a
* pseudo random list of concrete {@link QuicFrame} instances.
* @param ackEliciting whether the returned packet should be
* ack eliciting.
* @return a new QuicPacket
*/
static QuicPacket createPacket(boolean ackEliciting) {
List<QuicFrame> frames = new ArrayList<>();
int mincount = ackEliciting ? 1 : 0;
int ackCount = RANDOM.nextInt(mincount, 5);
int nackCount = RANDOM.nextInt(0, 10);
// TODO: maybe refactor this to make sure the frame
// we use are compatible with the packet type.
List<Class<? extends QuicFrame>> noAckFrames = List.of(AckFrame.class,
PaddingFrame.class, ConnectionCloseFrame.class);
for (int i=0; i < nackCount ; i++) {
frames.add(newFrame(noAckFrames.get(i % noAckFrames.size())));
}
if (ackEliciting) {
// TODO: maybe refactor this to make sure the frame
// we use are compatible with the packet type.
Class<?>[] frameClasses = QuicFrame.class.getPermittedSubclasses();
for (int i=0; i < ackCount; i++) {
Class<?> selected;
do {
int fx = RANDOM.nextInt(0, frameClasses.length);
selected = frameClasses[fx];
} while (noAckFrames.contains(selected));
frames.add(newFrame((Class<? extends QuicFrame>) selected));
}
}
if (!ackEliciting || RANDOM.nextBoolean()) {
// if !ackEliciting we always shuffle.
// Otherwise, we only shuffle half the time.
Collections.shuffle(frames, RANDOM);
}
return createPacket(mergeConsecutivePaddingFrames(frames));
}
private static List<QuicFrame> mergeConsecutivePaddingFrames(List<QuicFrame> frames) {
var iterator = frames.listIterator();
QuicFrame previous = null;
while (iterator.hasNext()) {
var frame = iterator.next();
if (previous instanceof PaddingFrame prevPad
&& frame instanceof PaddingFrame nextPad) {
int previousIndex = iterator.previousIndex();
QuicFrame merged = new PaddingFrame(prevPad.size() + nextPad.size());
frames.set(previousIndex, merged);
iterator.remove();
} else {
previous = frame;
}
}
return frames;
}
/**
* Creates a list of {@code TestCase} to test random instances of
* {@code QuicPacket} containing random instances of {@link QuicFrame}
* @return a list of {@code TestCase} to test random instances of
* {@code QuicPacket} containing random instances of {@link QuicFrame}
*/
static List<TestCase<? extends QuicPacket>> createPacketsTests() {
List<TestCase<? extends QuicPacket>> packets = new ArrayList<>();
packets.add(of(createPacket(List.of(newFrame(AckFrame.class))), false));
packets.add(of(createPacket(List.of(newFrame(ConnectionCloseFrame.class))), false));
packets.add(of(createPacket(List.of(newFrame(PaddingFrame.class))), false));
var frames = new ArrayList<>(List.of(
newFrame(PaddingFrame.class),
newFrame(AckFrame.class),
newFrame(ConnectionCloseFrame.class)));
Collections.shuffle(frames, RANDOM);
packets.add(of(createPacket(List.copyOf(frames)), false));
int maxPackets = RANDOM.nextInt(5, 11);
for (int i = 0; i < maxPackets ; i++) {
packets.add(of(createPacket(true), true));
}
return List.copyOf(packets);
}
/**
* A provider of test case to test
* {@link QuicFrame#isAckEliciting()}.
* @return test case to test
* {@link QuicFrame#isAckEliciting()}
*/
public static Object[][] framesDataProvider() {
return createFramesTests().stream()
.map(List::of)
.map(List::toArray)
.toArray(Object[][]::new);
}
/**
* A provider of test case to test
* {@link QuicPacket#isAckEliciting()}.
* @return test case to test
* {@link QuicPacket#isAckEliciting()}
*/
public static Object[][] packetsDataProvider() {
return createPacketsTests().stream()
.map(List::of)
.map(List::toArray)
.toArray(Object[][]::new);
}
/**
* Verifies the behavior of {@link QuicFrame#isAckEliciting()}
* with the given test case inputs.
* @param test the test inputs
* @param <T> a concrete subclass of QuicFrame
*/
@ParameterizedTest
@MethodSource("framesDataProvider")
<T extends QuicFrame> void testFrames(TestCase<T> test) {
testAckEliciting(test.type(),
test.describer(),
test.ackEliciting(),
test.obj(),
test.expected());
}
/**
* Verifies the behavior of {@link QuicPacket#isAckEliciting()}
* with the given test case inputs.
* @param test the test inputs
* @param <T> a concrete subclass of QuickPacket
*/
@ParameterizedTest
@MethodSource("packetsDataProvider")
<T extends QuicPacket> void testPackets(TestCase<T> test) {
testAckEliciting(test.type(),
test.describer(),
test.ackEliciting(),
test.obj(),
test.expected());
}
/**
* Asserts that {@code ackEliciting.test(obj) == expected}.
* @param type the concrete type of {@code obj}
* @param describer a function to describe {@code obj}
* @param ackEliciting the function being tested
* @param obj the instance on which to call the function being tested
* @param expected the expected result of {@code ackEliciting.test(obj)}
* @param <T> the concrete class being tested
*/
private <T> void testAckEliciting(Class<? extends T> type,
Function<? super T, String> describer,
Predicate<? super T> ackEliciting,
T obj,
boolean expected) {
System.out.printf("%ntestAckEliciting: %s(%s) - expecting %s%n",
type.getSimpleName(),
describer.apply(obj),
expected);
assertEquals(expected, ackEliciting.test(obj), describer.apply(obj));
if (obj instanceof QuicFrame frame) {
checkFrame(frame);
} else if (obj instanceof QuicPacket packet) {
checkPacket(packet);
}
}
// This is not a full-fledged test for frame encoding/decoding.
// Just a smoke test to verify that the ACK-eliciting property
// survives encoding/decoding
private void checkFrame(QuicFrame frame) {
int size = frame.size();
ByteBuffer buffer = ByteBuffer.allocate(size);
System.out.println("Checking frame: " + frame.getClass());
try {
frame.encode(buffer);
buffer.flip();
var decoded = QuicFrame.decode(buffer);
checkFrame(decoded, frame);
} catch (QuicTransportException x) {
throw new AssertionError(frame.getClass().getName(), x);
}
}
// This is not a full-fledged test for frame equality:
// And we still need a proper decoding/encoding test for frames
private void checkFrame(QuicFrame decoded, QuicFrame expected) {
System.out.printf("Comparing frames: %s with %s%n",
decoded.getClass().getSimpleName(),
expected.getClass().getSimpleName());
assertEquals(expected.getClass(), decoded.getClass());
assertEquals(expected.size(), decoded.size());
assertEquals(expected.getTypeField(), decoded.getTypeField());
assertEquals(expected.isAckEliciting(), decoded.isAckEliciting());
}
// This is not a full-fledged test for packet encoding/decoding.
// Just a smoke test to verify that the ACK-eliciting property
// survives encoding/decoding
private void checkPacket(QuicPacket packet) {
int size = packet.size();
ByteBuffer buffer = ByteBuffer.allocate(size);
System.out.println("Checking packet: " + packet.getClass());
try {
var encoder = QuicPacketEncoder.of(QuicVersion.QUIC_V1);
var decoder = QuicPacketDecoder.of(QuicVersion.QUIC_V1);
encoder.encode(packet, buffer, CONTEXT);
buffer.flip();
var decoded = decoder.decode(buffer, CONTEXT);
assertEquals(packet.size(), decoded.size());
assertEquals(packet.packetType(), decoded.packetType());
assertEquals(packet.payloadSize(), decoded.payloadSize());
assertEquals(packet.isAckEliciting(), decoded.isAckEliciting());
var frames = packet.frames();
var decodedFrames = decoded.frames();
assertEquals(frames.size(), decodedFrames.size());
} catch (Exception x) {
throw new AssertionError(packet.getClass().getName(), x);
}
}
}