/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/net/httpclient/quic/AckElicitingTest.java
730 строк
29 KB
Daisuke Yamazaki
8372325: Refactor tests under jdk/java/net/httpclient to use ${test.main.class}
09 апр 2026, 13:51
09 апр 2026, 13:51
261011a
Код
Авторство
О чём код?
/* * 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 ${test.main.class} * @run junit/othervm -Dseed=-7997973196290088038 ${test.main.class} */ 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); } } }