/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/net/httpclient/quic/PacketEncodingTest.java
1 445 строк
67 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 jdk.internal.net.http.common.Utils; import jdk.internal.net.http.quic.CodingContext; import jdk.internal.net.http.quic.PeerConnectionId; import jdk.internal.net.http.quic.QuicConnectionIdFactory; import jdk.internal.net.http.quic.packets.LongHeader; import jdk.internal.net.quic.QuicKeyUnavailableException; import jdk.internal.net.quic.QuicOneRttContext; import jdk.internal.net.quic.QuicTransportException; import jdk.internal.net.quic.QuicVersion; import jdk.internal.net.http.quic.frames.CryptoFrame; import jdk.internal.net.http.quic.frames.PaddingFrame; import jdk.internal.net.http.quic.frames.QuicFrame; import jdk.internal.net.http.quic.packets.HandshakePacket; import jdk.internal.net.http.quic.packets.InitialPacket; import jdk.internal.net.http.quic.packets.LongHeaderPacket; import jdk.internal.net.http.quic.packets.OneRttPacket; import jdk.internal.net.http.quic.packets.QuicPacket; import jdk.internal.net.http.quic.packets.QuicPacket.HeadersType; import jdk.internal.net.http.quic.packets.QuicPacket.PacketNumberSpace; import jdk.internal.net.http.quic.packets.QuicPacket.PacketType; import jdk.internal.net.http.quic.packets.QuicPacketDecoder; import jdk.internal.net.http.quic.packets.QuicPacketEncoder; import jdk.internal.net.http.quic.packets.QuicPacketNumbers; import jdk.internal.net.http.quic.packets.RetryPacket; import jdk.internal.net.http.quic.packets.ShortHeaderPacket; import jdk.internal.net.http.quic.packets.VersionNegotiationPacket; import jdk.internal.net.http.quic.packets.ZeroRttPacket; import jdk.internal.net.http.quic.QuicConnectionId; import jdk.internal.net.quic.QuicTLSEngine; import jdk.internal.net.quic.QuicTransportParametersConsumer; import jdk.internal.net.http.quic.VariableLengthEncoder; import javax.crypto.AEADBadTagException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HexFormat; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.function.IntFunction; import java.util.stream.Collectors; import static jdk.internal.net.http.quic.packets.QuicPacketNumbers.computePacketNumberLength; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; /* * @test * @library /test/lib * @summary test packet encoding and decoding in unencrypted form and without * any network involvement. * @run junit/othervm -Dseed=2646683818688275736 ${test.main.class} * @run junit/othervm -Dseed=-3723256402256409075 ${test.main.class} * @run junit/othervm -Dseed=-3689060484817342283 ${test.main.class} * @run junit/othervm -Dseed=2425718686525936108 ${test.main.class} * @run junit/othervm -Dseed=-2996954753243104355 ${test.main.class} * @run junit/othervm -Dseed=8750823652999067800 ${test.main.class} * @run junit/othervm -Dseed=2906555779406889127 ${test.main.class} * @run junit/othervm -Dseed=902801756808168822 ${test.main.class} * @run junit/othervm -Dseed=5643545543196691308 ${test.main.class} * @run junit/othervm -Dseed=2646683818688275736 ${test.main.class} * @run junit/othervm -Djdk.internal.httpclient.debug=true ${test.main.class} */ public class PacketEncodingTest { public static Object[][] longHeaderPacketProvider() { final QuicVersion[] quicVersions = QuicVersion.values(); final List<Object[]> params = new ArrayList<>(); for (final QuicVersion version : quicVersions) { final var p = new Object[][] { // quic-version, srcIdLen, dstIdLen, pn, largestAck new Object[] {version, 20, 20, 0L, -1L}, new Object[] {version, 10, 20, 1L, 0L}, new Object[] {version, 10, 20, 255L, 0L}, new Object[] {version, 12, 15, 0xFFFFL, 0L}, new Object[] {version, 9, 8, 0x7FFFFFFFL, 255L}, new Object[] {version, 13, 11, 0x8FFFFFFFL, 0x10000000L}, new Object[] {version, 19, 6, 0xFFFFFFFFL, 0xFFFFFFFEL}, new Object[] {version, 6, 17, 0xFFFFFFFFFFL, 0xFFFFFFFF00L}, new Object[] {version, 15, 14, 0x7FFFFFFFFFFFL, 0x7FFFFFFFFF00L}, new Object[] {version, 7, 9, 0xa82f9b32L, 0xa82f30eaL}, new Object[] {version, 18, 16, 0xace8feL, 0xabe8b3L}, new Object[] {version, 16, 19, 0xac5c02L, 0xabe8b3L} }; params.addAll(Arrays.asList(p)); } return params.toArray(Object[][]::new); } public static Object[][] shortHeaderPacketProvider() { final QuicVersion[] quicVersions = QuicVersion.values(); final List<Object[]> params = new ArrayList<>(); for (final QuicVersion version : quicVersions) { final var p = new Object[][] { new Object[] {version, 20, 0L, -1L}, new Object[] {version, 17, 1L, 0L}, new Object[] {version, 10, 255L, 0L}, new Object[] {version, 12, 0xFFFFL, 0L}, new Object[] {version, 9, 0x7FFFFFFFL, 255L}, new Object[] {version, 13, 0x8FFFFFFFL, 0x10000000L}, new Object[] {version, 19, 0xFFFFFFFFL, 0xFFFFFFFEL}, new Object[] {version, 6, 0xFFFFFFFFFFL, 0xFFFFFFFF00L}, new Object[] {version, 15, 0x7FFFFFFFFFFFL, 0x7FFFFFFFFF00L}, new Object[] {version, 7, 0xa82f9b32L, 0xa82f30eaL}, new Object[] {version, 18, 0xace8feL, 0xabe8b3L}, new Object[] {version, 16, 0xac5c02L, 0xabe8b3L}, }; params.addAll(Arrays.asList(p)); } return params.toArray(Object[][]::new); } public static Object[][] versionAndRetryProvider() { final QuicVersion[] quicVersions = QuicVersion.values(); final List<Object[]> params = new ArrayList<>(); for (final QuicVersion version : quicVersions) { final var p = new Object[][] { // quic-version, srcIdLen, dstIdLen, pn, largestAck new Object[] {version, 20, 20}, new Object[] {version, 10, 20}, new Object[] {version, 12, 15}, new Object[] {version, 9, 8}, new Object[] {version, 13, 11}, new Object[] {version, 19, 6}, new Object[] {version, 6, 17}, new Object[] {version, 15, 14}, new Object[] {version, 7, 9}, new Object[] {version, 18, 16}, new Object[] {version, 16, 19}, }; params.addAll(Arrays.asList(p)); } return params.toArray(Object[][]::new); } private static final AtomicLong IDS = new AtomicLong(); private static final Random RANDOM = jdk.test.lib.RandomFactory.getRandom(); private static final int MAX_DATAGRAM_IPV6 = 65527; byte[] randomIdBytes(int connectionLength) { byte[] bytes = new byte[connectionLength]; RANDOM.nextBytes(bytes); return bytes; } 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 version, ByteBuffer originalConnectionId, ByteBuffer packet, ByteBuffer output) { output.put(ByteBuffer.allocate(16)); } @Override public void verifyRetryPacket(QuicVersion version, ByteBuffer originalConnectionId, ByteBuffer packet) throws AEADBadTagException { } @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 QuicConnectionId originalServerConnId() { throw new AssertionError("should not come here!"); } @Override public QuicTLSEngine getTLSEngine() { return TLS_ENGINE; } @Override public int minShortPacketPayloadSize(int destConnectionIdLength) { return 100 - (destConnectionIdLength - connectionIdLength()); } } private void checkLongHeaderPacket(LongHeaderPacket packet, PacketType packetType, int versionNumber, PacketNumberSpace packetNumberSpace, long packetNumber, QuicConnectionId srcConnectionId, QuicConnectionId destConnectionId, List<QuicFrame> payload, int padding) { List<QuicFrame> expected; if (padding == 0) { expected = payload; } else if (payload.get(0) instanceof PaddingFrame pf) { expected = new ArrayList<>(payload); expected.set(0, new PaddingFrame(padding + pf.size())); } else { expected = new ArrayList<>(payload.size()+1); expected.add(new PaddingFrame(padding)); expected.addAll(payload); } checkLongHeaderPacket(packet, packetType, versionNumber, packetNumberSpace, packetNumber, srcConnectionId, destConnectionId, expected); } private void checkLongHeaderPacket(LongHeaderPacket packet, PacketType packetType, int versionNumber, PacketNumberSpace packetNumberSpace, long packetNumber, QuicConnectionId srcConnectionId, QuicConnectionId destConnectionId, List<QuicFrame> payload) { // Check created packet assertEquals(HeadersType.LONG, packet.headersType()); assertEquals(packetType, packet.packetType()); boolean hasLength = switch (packetType) { case VERSIONS, RETRY -> false; default -> true; }; assertEquals(hasLength, packet.hasLength()); assertEquals(packetNumberSpace, packet.numberSpace()); if (payload == null) { assertTrue(packet.frames().isEmpty()); } else { assertEquals(getBuffers(payload), getBuffers(packet.frames())); } assertEquals(versionNumber, packet.version()); assertEquals(packetNumber, packet.packetNumber()); assertEquals(srcConnectionId, packet.sourceId()); assertEquals(destConnectionId, packet.destinationId()); } private static ByteBuffer encodeFrame(QuicFrame frame) { ByteBuffer result = ByteBuffer.allocate(frame.size()); frame.encode(result); return result; } private static List<ByteBuffer> getBuffers(List<QuicFrame> payload) { return payload.stream().map(PacketEncodingTest::encodeFrame).toList(); } private static List<ByteBuffer> getBuffers(List<QuicFrame> payload, int minSize) { int payloadSize = payload.stream().mapToInt(QuicFrame::size).sum(); if (payloadSize < minSize) { payload = new ArrayList<>(payload); payload.add(0, new PaddingFrame(minSize - payloadSize)); } return payload.stream().map(PacketEncodingTest::encodeFrame).toList(); } private static String toHex(ByteBuffer buffer) { byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes, 0, buffer.remaining()); return HexFormat.of().formatHex(bytes); } private static String toHex(List<ByteBuffer> byteBuffers) { return "0x" + byteBuffers.stream() .map(PacketEncodingTest::toHex) .collect(Collectors.joining(":")); } private void checkLongHeaderPacketAt(ByteBuffer datagram, int offset, PacketType packetType, int versionNumber, QuicConnectionId srcConnectionId, QuicConnectionId destConnectionId) { assertEquals(HeadersType.LONG, QuicPacketDecoder.peekHeaderType(datagram, offset)); assertEquals(packetType, QuicPacketDecoder.of(datagram, offset).peekPacketType(datagram, offset)); LongHeader header = QuicPacketDecoder.peekLongHeader(datagram, offset); assertNotNull(header, "Could not parse packet header"); assertEquals(versionNumber, header.version()); assertTrue(header.destinationId() .matches(destConnectionId.asReadOnlyBuffer()), "Destination ID doesn't match"); assertTrue(header.sourceId() .matches(srcConnectionId.asReadOnlyBuffer()), "Source ID doesn't match"); } private List<QuicFrame> frames(byte[] payload) throws IOException { return frames(payload, false); } private List<QuicFrame> frames(byte[] payload, boolean insert) throws IOException { int payloadSize = payload.length; ByteBuffer buf = ByteBuffer.wrap(payload); List<QuicFrame> frames = new ArrayList<>(); int remaining = payloadSize; while (remaining > 7) { int size = RANDOM.nextInt(1, remaining - 6); byte[] data = new byte[size]; RANDOM.nextBytes(data); QuicFrame frame = new CryptoFrame(0, size, ByteBuffer.wrap(data)); int encoded = frame.size(); assertTrue(encoded > 0, String.valueOf(encoded)); assertTrue(encoded <= remaining, String.valueOf(encoded)); if (insert) { frames.add(0, frame); buf.position(remaining - encoded); } else { frames.add(frame); } frame.encode(buf); remaining -= encoded; } if (remaining > 0) { var padding = new PaddingFrame(remaining); if (insert) { frames.add(0, padding); buf.position(0); } else { frames.add(padding); } padding.encode(buf); } if (insert) { assertEquals(remaining, buf.position()); assertEquals(payloadSize - remaining, buf.remaining()); } else { assertEquals(0, buf.remaining()); } return List.copyOf(frames); } private ByteBuffer toByteBuffer(QuicPacketEncoder encoder, QuicPacket outgoingQuicPacket, CodingContext context) throws Exception { int size = outgoingQuicPacket.size(); ByteBuffer buffer = ByteBuffer.allocate(size); encoder.encode(outgoingQuicPacket, buffer, context); assertEquals(size, buffer.position(), " for " + outgoingQuicPacket); buffer.flip(); return buffer; } private void checkShortHeaderPacket(ShortHeaderPacket packet, PacketType packetType, PacketNumberSpace packetNumberSpace, long packetNumber, QuicConnectionId destConnectionId, List<QuicFrame> payload, int minSize) { // Check created packet assertEquals(HeadersType.SHORT, packet.headersType()); assertEquals(packetType, packet.packetType()); assertEquals(false, packet.hasLength()); assertEquals(packetNumberSpace, packet.numberSpace()); assertEquals(getBuffers(payload, minSize), getBuffers(packet.frames())); assertEquals(packetNumber, packet.packetNumber()); assertEquals(destConnectionId, packet.destinationId()); } private void checkShortHeaderPacketAt(ByteBuffer datagram, int offset, PacketType packetType, QuicConnectionId destConnectionId, CodingContext context) { assertEquals(HeadersType.SHORT, QuicPacketDecoder.peekHeaderType(datagram, offset)); assertEquals(packetType, QuicPacketDecoder.of(QuicVersion.QUIC_V1).peekPacketType(datagram, offset)); assertEquals(0, QuicPacketDecoder.peekVersion(datagram, offset)); int pos = datagram.position(); if (pos != offset) datagram.position(offset); try { assertEquals(-1, QuicPacketDecoder.peekShortConnectionId(datagram, destConnectionId.length()) .mismatch(destConnectionId.asReadOnlyBuffer())); } finally { if (pos != offset) datagram.position(pos); } } @ParameterizedTest @MethodSource("longHeaderPacketProvider") public void testInitialPacket(QuicVersion quicVersion, int srcIdLength, int destIdLength, long packetNumber, long largestAcked) throws Exception { System.out.printf("%ntestInitialPacket(qv:%s, scid:%d, dcid:%d, pn:%d, ack:%d)%n", quicVersion, srcIdLength, destIdLength, packetNumber, largestAcked); QuicPacketEncoder encoder = QuicPacketEncoder.of(quicVersion); QuicPacketDecoder decoder = QuicPacketDecoder.of(quicVersion); byte[] destid = QuicConnectionIdFactory.getClient() .newConnectionId(destIdLength, IDS.incrementAndGet()); assert destid.length <= 20; final QuicConnectionId destConnectionId = new PeerConnectionId(destid); assertEquals(destConnectionId.length(), destid.length, "dcid length"); destIdLength = destid.length; byte[] srcid = randomIdBytes(srcIdLength); final QuicConnectionId srcConnectionId = new PeerConnectionId(srcid); assertEquals(srcConnectionId.length(), srcid.length, "scid length"); int bound = MAX_DATAGRAM_IPV6 - srcIdLength - destid.length - 7 - QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked) - VariableLengthEncoder.getEncodedSize(MAX_DATAGRAM_IPV6); // ensure that bound - tokenLength - 1 > 0 assert bound > 4; int tokenLength = RANDOM.nextInt(bound - 4); byte[] token = tokenLength == 0 ? null : new byte[tokenLength]; if (token != null) RANDOM.nextBytes(token); int packetNumberLength = QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked); int payloadSize = Math.max(RANDOM.nextInt(bound - tokenLength - 1) + 1, 4 - packetNumberLength); System.out.printf("testInitialPacket.encode(scid:%s, dcid:%s, token:%d, payload:%d)%n", srcIdLength, destIdLength, tokenLength, payloadSize); CodingContext context = new TestCodingContext() { @Override public long largestProcessedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.INITIAL ? largestAcked : -1; } @Override public long largestAckedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.INITIAL ? largestAcked : -1; } @Override public int connectionIdLength() { return srcIdLength; } }; int minsize = encoder.computeMaxInitialPayloadSize(context, computePacketNumberLength(packetNumber, context.largestAckedPN(PacketNumberSpace.INITIAL)), tokenLength, srcIdLength, destIdLength, 1200); int padding = (payloadSize < minsize) ? minsize - payloadSize : 0; System.out.println("testInitialPacket: available=%s, payload=%s, padding=%s" .formatted(minsize, payloadSize, padding)); byte[] payload = new byte[payloadSize]; List<QuicFrame> frames = frames(payload, padding != 0); assertEquals(payloadSize, frames.stream().mapToInt(QuicFrame::size) .reduce(0, Math::addExact)); // Create an initial packet var packet = encoder.newInitialPacket(srcConnectionId, destConnectionId, token, packetNumber, largestAcked, frames, context); if (padding > 0) { var frames2 = new ArrayList<>(frames); frames2.add(0, new PaddingFrame(padding)); var packet2 = encoder.newInitialPacket(srcConnectionId, destConnectionId, token, packetNumber, largestAcked, frames2, context); assertEquals(padding + (1200 - packet2.size()), padding); } // Check created packet assertTrue(packet instanceof InitialPacket); var initialPacket = (InitialPacket) packet; System.out.printf("%s: pn:%s, tklen:%s, payloadSize:%s, padding:%s, packet::size:%s, " + "\n\tinputFrames: %s, " + "\n\tencodedFrames:%s%n", PacketType.INITIAL, packetNumber, tokenLength, payload.length, padding, packet.size(), frames, packet.frames()); checkLongHeaderPacket(initialPacket, PacketType.INITIAL, quicVersion.versionNumber(), PacketNumberSpace.INITIAL, packetNumber, srcConnectionId, destConnectionId, frames, padding); assertEquals(tokenLength, initialPacket.tokenLength()); assertEquals(token, initialPacket.token()); assertEquals(true, initialPacket.hasLength()); assertEquals(packetNumberLength + payloadSize + padding, initialPacket.length()); // Check that peeking at the encoded packet returns correct information // Decode the two packets in the datagram ByteBuffer encoded = toByteBuffer(encoder, packet, context); checkLongHeaderPacketAt(encoded, 0, PacketType.INITIAL, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // coalesce two packets in a single datagram and check // the peek methods again int offset = RANDOM.nextInt(256); int second = offset + encoded.limit(); System.out.printf("testInitialPacket.encode(offset:%d, second:%d)%n", offset, second); ByteBuffer datagram = ByteBuffer.allocate(encoded.limit() * 2 + offset * 2); datagram.position(offset); datagram.put(encoded); encoded.flip(); datagram.put(encoded); encoded.flip(); datagram.flip(); // check header, type and version of both packets System.out.printf("datagram(offset:%d, second:%d, position:%d, limit:%d)%n", offset, second, datagram.position(), datagram.limit()); System.out.printf("reading first datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, offset, PacketType.INITIAL, quicVersion.versionNumber(), srcConnectionId, destConnectionId); System.out.printf("reading second datagram(offset:%d, position:%d, limit:%d)%n", second, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, second, PacketType.INITIAL, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // check that skip packet can skip both packets datagram.position(0); datagram.limit(datagram.capacity()); decoder.skipPacket(datagram, offset); assertEquals(second, datagram.position()); decoder.skipPacket(datagram, second); assertEquals(offset, datagram.remaining()); datagram.position(offset); int size = second - offset; for (int i=0; i<2; i++) { int pos = datagram.position(); System.out.printf("Decoding packet: %d at %d%n", (i+1), pos); var decodedPacket = decoder.decode(datagram, context); assertEquals(pos + size, datagram.position()); assertTrue(decodedPacket instanceof InitialPacket, "decoded: " + decodedPacket); InitialPacket initialDecoded = InitialPacket.class.cast(decodedPacket); checkLongHeaderPacket(initialDecoded, PacketType.INITIAL, quicVersion.versionNumber(), PacketNumberSpace.INITIAL, packetNumber, srcConnectionId, destConnectionId, frames, padding); assertEquals(packet.size(), decodedPacket.size()); assertEquals(size, decodedPacket.size()); assertEquals(tokenLength, initialDecoded.tokenLength()); assertArrayEquals(token, initialDecoded.token()); assertEquals(initialPacket.length(), initialDecoded.length()); assertEquals(packetNumberLength + payloadSize + padding, initialDecoded.length()); } assertEquals(second + second - offset, datagram.position()); } @ParameterizedTest @MethodSource("longHeaderPacketProvider") public void testHandshakePacket(QuicVersion quicVersion, int srcIdLength, int destIdLength, long packetNumber, long largestAcked) throws Exception { System.out.printf("%ntestHandshakePacket(qv:%s, scid:%d, dcid:%d, pn:%d, ack:%d)%n", quicVersion, srcIdLength, destIdLength, packetNumber, largestAcked); QuicPacketEncoder encoder = QuicPacketEncoder.of(quicVersion); QuicPacketDecoder decoder = QuicPacketDecoder.of(quicVersion); byte[] destid = QuicConnectionIdFactory.getClient() .newConnectionId(destIdLength, IDS.incrementAndGet()); assert destid.length <= 20; QuicConnectionId destConnectionId = new PeerConnectionId(destid); byte[] srcid = randomIdBytes(srcIdLength); QuicConnectionId srcConnectionId = new PeerConnectionId(srcid); int bound = MAX_DATAGRAM_IPV6 - srcIdLength - destid.length - 7 - QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked) - VariableLengthEncoder.getEncodedSize(MAX_DATAGRAM_IPV6); int packetNumberLength = QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked); int payloadSize = Math.max(RANDOM.nextInt(bound - 1) + 1, 4 - packetNumberLength); byte[] payload = new byte[payloadSize]; var frames = frames(payload); System.out.printf("testHandshakePacket.encode(payload:%d)%n", payloadSize); CodingContext context = new TestCodingContext() { @Override public long largestProcessedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.HANDSHAKE ? largestAcked : -1; } @Override public long largestAckedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.HANDSHAKE ? largestAcked : -1; } @Override public int connectionIdLength() { return srcIdLength; } }; // Create an initial packet var packet = encoder.newHandshakePacket(srcConnectionId, destConnectionId, packetNumber, largestAcked, frames, context); // Check created packet assertTrue(packet instanceof HandshakePacket); var handshakePacket = (HandshakePacket) packet; checkLongHeaderPacket(handshakePacket, PacketType.HANDSHAKE, quicVersion.versionNumber(), PacketNumberSpace.HANDSHAKE, packetNumber, srcConnectionId, destConnectionId, frames); assertEquals(true, handshakePacket.hasLength()); assertEquals(packetNumberLength + payloadSize, handshakePacket.length()); // Decode the two packets in the datagram // Check that peeking at the encoded packet returns correct information ByteBuffer encoded = toByteBuffer(encoder, packet, context); checkLongHeaderPacketAt(encoded, 0, PacketType.HANDSHAKE, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // coalesce two packets in a single datagram and check // the peek methods again int offset = RANDOM.nextInt(256); int second = offset + encoded.limit(); System.out.printf("testHandshakePacket.encode(offset:%d, second:%d)%n", offset, second); ByteBuffer datagram = ByteBuffer.allocate(encoded.limit() * 2 + offset * 2); datagram.position(offset); datagram.put(encoded); encoded.flip(); datagram.put(encoded); encoded.flip(); datagram.flip(); // check header, type and version of both packets System.out.printf("datagram(offset:%d, second:%d, position:%d, limit:%d)%n", offset, second, datagram.position(), datagram.limit()); // set position to first packet to check connection ids System.out.printf("reading first datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, offset, PacketType.HANDSHAKE, quicVersion.versionNumber(), srcConnectionId, destConnectionId); System.out.printf("reading second datagram(offset:%d, position:%d, limit:%d)%n", second, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, second, PacketType.HANDSHAKE, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // check that skip packet can skip both packets datagram.position(0); datagram.limit(datagram.capacity()); decoder.skipPacket(datagram, offset); assertEquals(second, datagram.position()); decoder.skipPacket(datagram, second); assertEquals(offset, datagram.remaining()); datagram.position(offset); int size = second - offset; for (int i=0; i<2; i++) { int pos = datagram.position(); System.out.printf("Decoding packet: %d at %d%n", (i+1), pos); var decodedPacket = decoder.decode(datagram, context); assertEquals(pos + size, datagram.position()); assertTrue(decodedPacket instanceof HandshakePacket, "decoded: " + decodedPacket); HandshakePacket handshakeDecoded = HandshakePacket.class.cast(decodedPacket); checkLongHeaderPacket(handshakeDecoded, PacketType.HANDSHAKE, quicVersion.versionNumber(), PacketNumberSpace.HANDSHAKE, packetNumber, srcConnectionId, destConnectionId, frames); assertEquals(packet.size(), decodedPacket.size()); assertEquals(size, decodedPacket.size()); assertEquals(handshakePacket.length(), handshakeDecoded.length()); assertEquals(packetNumberLength + payloadSize, handshakeDecoded.length()); } assertEquals(second + second - offset, datagram.position()); } @ParameterizedTest @MethodSource("longHeaderPacketProvider") public void testZeroRTTPacket(QuicVersion quicVersion, int srcIdLength, int destIdLength, long packetNumber, long largestAcked) throws Exception { System.out.printf("%ntestZeroRTTPacket(qv:%s, scid:%d, dcid:%d, pn:%d, ack:%d)%n", quicVersion, srcIdLength, destIdLength, packetNumber, largestAcked); QuicPacketEncoder encoder = QuicPacketEncoder.of(quicVersion); QuicPacketDecoder decoder = QuicPacketDecoder.of(quicVersion); byte[] destid = QuicConnectionIdFactory.getClient() .newConnectionId(destIdLength, IDS.incrementAndGet()); assert destid.length <= 20; QuicConnectionId destConnectionId = new PeerConnectionId(destid); byte[] srcid = randomIdBytes(srcIdLength); QuicConnectionId srcConnectionId = new PeerConnectionId(srcid); int bound = MAX_DATAGRAM_IPV6 - srcIdLength - destid.length - 7 - QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked) - VariableLengthEncoder.getEncodedSize(MAX_DATAGRAM_IPV6); int packetNumberLength = QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked); int payloadSize = Math.max(RANDOM.nextInt(bound - 1) + 1, 4 - packetNumberLength); byte[] payload = new byte[payloadSize]; var frames = frames(payload); System.out.printf("testZeroRTTPacket.encode(payload:%d)%n", payloadSize); CodingContext context = new TestCodingContext() { @Override public long largestProcessedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.APPLICATION ? largestAcked : -1; } @Override public long largestAckedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.APPLICATION ? largestAcked : -1; } @Override public int connectionIdLength() { return srcIdLength; } }; // Create an initial packet var packet = encoder.newZeroRttPacket(srcConnectionId, destConnectionId, packetNumber, largestAcked, frames, context); // Check created packet assertTrue(packet instanceof ZeroRttPacket); var zeroRttPacket = (ZeroRttPacket) packet; checkLongHeaderPacket(zeroRttPacket, PacketType.ZERORTT, quicVersion.versionNumber(), PacketNumberSpace.APPLICATION, packetNumber, srcConnectionId, destConnectionId, frames); assertEquals(true, zeroRttPacket.hasLength()); assertEquals(packetNumberLength + payloadSize, zeroRttPacket.length()); // Check that peeking at the encoded packet returns correct information ByteBuffer encoded = toByteBuffer(encoder, packet, context); checkLongHeaderPacketAt(encoded, 0, PacketType.ZERORTT, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // coalesce two packets in a single datagram and check // the peek methods again int offset = RANDOM.nextInt(256); int second = offset + encoded.limit(); System.out.printf("testZeroRTTPacket.encode(offset:%d, second:%d)%n", offset, second); ByteBuffer datagram = ByteBuffer.allocate(encoded.limit() * 2 + offset * 2); datagram.position(offset); datagram.put(encoded); encoded.flip(); datagram.put(encoded); encoded.flip(); datagram.flip(); // check header, type and version of both packets System.out.printf("datagram(offset:%d, second:%d, position:%d, limit:%d)%n", offset, second, datagram.position(), datagram.limit()); // set position to first packet to check connection ids System.out.printf("reading first datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, offset, PacketType.ZERORTT, quicVersion.versionNumber(), srcConnectionId, destConnectionId); System.out.printf("reading second datagram(offset:%d, position:%d, limit:%d)%n", second, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, second, PacketType.ZERORTT, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // check that skip packet can skip both packets datagram.position(0); datagram.limit(datagram.capacity()); decoder.skipPacket(datagram, offset); assertEquals(second, datagram.position()); decoder.skipPacket(datagram, second); assertEquals(offset, datagram.remaining()); // Decode the two packets in the datagram datagram.position(offset); int size = second - offset; for (int i=0; i<2; i++) { int pos = datagram.position(); System.out.printf("Decoding packet: %d at %d%n", (i+1), pos); var decodedPacket = decoder.decode(datagram, context); assertEquals(pos + size, datagram.position()); assertTrue(decodedPacket instanceof ZeroRttPacket, "decoded: " + decodedPacket); ZeroRttPacket zeroRttDecoded = ZeroRttPacket.class.cast(decodedPacket); checkLongHeaderPacket(zeroRttDecoded, PacketType.ZERORTT, quicVersion.versionNumber(), PacketNumberSpace.APPLICATION, packetNumber, srcConnectionId, destConnectionId, frames); assertEquals(packet.size(), decodedPacket.size()); assertEquals(size, decodedPacket.size()); assertEquals(zeroRttPacket.length(), zeroRttDecoded.length()); assertEquals(packetNumberLength + payloadSize, zeroRttDecoded.length()); } assertEquals(second + second - offset, datagram.position()); } @ParameterizedTest @MethodSource("versionAndRetryProvider") public void testVersionNegotiationPacket(QuicVersion quicVersion, int srcIdLength, int destIdLength) throws Exception { System.out.printf("%ntestVersionNegotiationPacket(qv:%s, scid:%d, dcid:%d, pn:%d, ack:%d)%n", quicVersion, srcIdLength, destIdLength, -1, -1); QuicPacketEncoder encoder = QuicPacketEncoder.of(quicVersion); QuicPacketDecoder decoder = QuicPacketDecoder.of(quicVersion); byte[] destid = QuicConnectionIdFactory.getClient() .newConnectionId(destIdLength, IDS.incrementAndGet()); assert destid.length <= 20; QuicConnectionId destConnectionId = new PeerConnectionId(destid); byte[] srcid = randomIdBytes(srcIdLength); QuicConnectionId srcConnectionId = new PeerConnectionId(srcid); final List<Integer> versionList = new ArrayList<>(); for (final QuicVersion qv : QuicVersion.values()) { versionList.add(qv.versionNumber()); } System.out.printf("testVersionNegotiationPacket.encode(versions:%d)%n", versionList.size()); // Create an initial packet var packet = QuicPacketEncoder.newVersionNegotiationPacket(srcConnectionId, destConnectionId, versionList.stream().mapToInt(Integer::intValue).toArray()); // Check created packet assertTrue(packet instanceof VersionNegotiationPacket); var versionPacket = (VersionNegotiationPacket) packet; checkLongHeaderPacket(versionPacket, PacketType.VERSIONS, 0, PacketNumberSpace.NONE, -1, srcConnectionId, destConnectionId, null); assertEquals(false, versionPacket.hasLength()); assertArrayEquals(versionList.stream().mapToInt(Integer::intValue).toArray(), versionPacket.supportedVersions()); CodingContext context = new TestCodingContext() { @Override public long largestProcessedPN(PacketNumberSpace packetSpace) { return -1; } @Override public long largestAckedPN(PacketNumberSpace packetSpace) { return -1; } @Override public int connectionIdLength() { return srcIdLength; } }; // Check that peeking at the encoded packet returns correct information ByteBuffer encoded = toByteBuffer(encoder, packet, context); checkLongHeaderPacketAt(encoded, 0, PacketType.VERSIONS, 0, srcConnectionId, destConnectionId); // version negotiation packets can't be coalesced int offset = RANDOM.nextInt(256); int end = offset + encoded.limit(); System.out.printf("testVersionNegotiationPacket.encode(offset:%d, end:%d)%n", offset, end); ByteBuffer datagram = ByteBuffer.allocate(encoded.limit() + offset); datagram.position(offset); datagram.put(encoded); encoded.flip(); datagram.flip(); // check header, type and version of both packets System.out.printf("datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); // set position to first packet to check connection ids System.out.printf("reading datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, offset, PacketType.VERSIONS, 0, srcConnectionId, destConnectionId); // check that skip packet can skip packet datagram.position(0); datagram.limit(datagram.capacity()); decoder.skipPacket(datagram, offset); assertEquals(end, datagram.position()); assertEquals(0, datagram.remaining()); // Decode the two packets in the datagram datagram.position(offset); int size = end - offset; for (int i=0; i<1; i++) { int pos = datagram.position(); System.out.printf("Decoding packet: %d at %d%n", (i+1), pos); var decodedPacket = decoder.decode(datagram, context); assertEquals(pos + size, datagram.position()); assertTrue(decodedPacket instanceof VersionNegotiationPacket, "decoded: " + decodedPacket); VersionNegotiationPacket decodedVersion = VersionNegotiationPacket.class.cast(decodedPacket); checkLongHeaderPacket(decodedVersion, PacketType.VERSIONS, 0, PacketNumberSpace.NONE, -1, srcConnectionId, destConnectionId, null); assertEquals(packet.size(), decodedPacket.size()); assertEquals(size, decodedPacket.size()); assertArrayEquals(versionList.stream().mapToInt(Integer::intValue).toArray(), decodedVersion.supportedVersions()); } assertEquals(end, datagram.position()); } @ParameterizedTest @MethodSource("versionAndRetryProvider") public void testRetryPacket(QuicVersion quicVersion, int srcIdLength, int destIdLength) throws Exception { System.out.printf("%ntestRetryPacket(qv:%s, scid:%d, dcid:%d, pn:%d, ack:%d)%n", quicVersion, srcIdLength, destIdLength, -1, -1); QuicPacketEncoder encoder = QuicPacketEncoder.of(quicVersion); QuicPacketDecoder decoder = QuicPacketDecoder.of(quicVersion); byte[] destid = QuicConnectionIdFactory.getClient() .newConnectionId(destIdLength, IDS.incrementAndGet()); assert destid.length <= 20; QuicConnectionId destConnectionId = new PeerConnectionId(destid); byte[] srcid = randomIdBytes(srcIdLength); QuicConnectionId srcConnectionId = new PeerConnectionId(srcid); byte[] origId = randomIdBytes(destIdLength); QuicConnectionId origConnectionId = new PeerConnectionId(origId); int bound = (MAX_DATAGRAM_IPV6 - srcIdLength - destid.length - 7); int retryTokenLength = RANDOM.nextInt(bound - 16) + 1; byte[] retryToken = new byte[retryTokenLength]; RANDOM.nextBytes(retryToken); System.out.printf("testRetryPacket.encode(token:%d)%n", retryTokenLength); int expectedSize = 7 + 16 + destid.length + srcIdLength + retryTokenLength; // Create an initial packet var packet = encoder.newRetryPacket(srcConnectionId, destConnectionId, retryToken); // Check created packet assertTrue(packet instanceof RetryPacket); var retryPacket = (RetryPacket) packet; checkLongHeaderPacket(retryPacket, PacketType.RETRY, quicVersion.versionNumber(), PacketNumberSpace.NONE, -1, srcConnectionId, destConnectionId, null); assertEquals(false, retryPacket.hasLength()); assertArrayEquals(retryToken, retryPacket.retryToken()); assertEquals(expectedSize, retryPacket.size()); CodingContext context = new TestCodingContext() { @Override public long largestProcessedPN(PacketNumberSpace packetSpace) { return -1; } @Override public long largestAckedPN(PacketNumberSpace packetSpace) { return -1; } @Override public int connectionIdLength() { return srcIdLength; } @Override public QuicConnectionId originalServerConnId() { return origConnectionId; } }; // Check that peeking at the encoded packet returns correct information ByteBuffer encoded = toByteBuffer(encoder, packet, context); checkLongHeaderPacketAt(encoded, 0, PacketType.RETRY, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // version negotiation packets can't be coalesced int offset = RANDOM.nextInt(256); int end = offset + encoded.limit(); System.out.printf("testRetryPacket.encode(offset:%d, end:%d)%n", offset, end); ByteBuffer datagram = ByteBuffer.allocate(encoded.limit() + offset); datagram.position(offset); datagram.put(encoded); encoded.flip(); datagram.flip(); // check header, type and version of both packets System.out.printf("datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); // set position to first packet to check connection ids System.out.printf("reading datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); checkLongHeaderPacketAt(datagram, offset, PacketType.RETRY, quicVersion.versionNumber(), srcConnectionId, destConnectionId); // check that skip packet can skip packet datagram.position(0); datagram.limit(datagram.capacity()); decoder.skipPacket(datagram, offset); assertEquals(end, datagram.position()); assertEquals(0, datagram.remaining()); // Decode the two packets in the datagram datagram.position(offset); int size = end - offset; for (int i=0; i<1; i++) { int pos = datagram.position(); System.out.printf("Decoding packet: %d at %d%n", (i+1), pos); var decodedPacket = decoder.decode(datagram, context); assertEquals(pos + size, datagram.position()); assertTrue(decodedPacket instanceof RetryPacket, "decoded: " + decodedPacket); RetryPacket decodedRetry = RetryPacket.class.cast(decodedPacket); checkLongHeaderPacket(decodedRetry, PacketType.RETRY, quicVersion.versionNumber(), PacketNumberSpace.NONE, -1, srcConnectionId, destConnectionId, null); assertEquals(packet.size(), decodedPacket.size()); assertEquals(size, decodedPacket.size()); assertEquals(expectedSize, decodedPacket.size()); assertArrayEquals(retryToken, decodedRetry.retryToken()); } assertEquals(end, datagram.position()); } @ParameterizedTest @MethodSource("shortHeaderPacketProvider") public void testOneRTTPacket(QuicVersion quicVersion, int destIdLength, long packetNumber, long largestAcked) throws Exception { System.out.printf("%ntestOneRTTPacket(qv:%s, dcid:%d, pn:%d, ack:%d)%n", quicVersion, destIdLength, packetNumber, largestAcked); QuicPacketEncoder encoder = QuicPacketEncoder.of(quicVersion); QuicPacketDecoder decoder = QuicPacketDecoder.of(quicVersion); byte[] destid = QuicConnectionIdFactory.getClient() .newConnectionId(destIdLength, IDS.incrementAndGet()); assert destid.length <= 20; QuicConnectionId destConnectionId = new PeerConnectionId(destid); int bound = MAX_DATAGRAM_IPV6 - destid.length - 7 - QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked) - VariableLengthEncoder.getEncodedSize(MAX_DATAGRAM_IPV6); int packetNumberLength = QuicPacketNumbers.computePacketNumberLength(packetNumber, largestAcked); int payloadSize = Math.max(RANDOM.nextInt(bound - 1) + 1, 4 - packetNumberLength); byte[] payload = new byte[payloadSize]; var frames = frames(payload); CodingContext context = new TestCodingContext() { @Override public long largestProcessedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.APPLICATION ? largestAcked : -1; } @Override public long largestAckedPN(PacketNumberSpace packetSpace) { return packetSpace == PacketNumberSpace.APPLICATION ? largestAcked : -1; } // since we're going to decode the short packet, we need to return // the same length that was used as destination cid in the packet @Override public int connectionIdLength() { return destid.length; } }; int paddedPayLoadSize = Math.max(payloadSize + packetNumberLength, context.minShortPacketPayloadSize(destid.length)); System.out.printf("testOneRTTPacket.encode(payload:%d, padded:%d, destid.length: %d)%n", payloadSize, paddedPayLoadSize, destid.length); int expectedSize = 1 + destid.length + paddedPayLoadSize; // Create an 1-RTT packet OneRttPacket packet = encoder.newOneRttPacket(destConnectionId, packetNumber, largestAcked, frames, context); int minPayloadSize = context.minShortPacketPayloadSize(destConnectionId.length()) - packetNumberLength; checkShortHeaderPacket(packet, PacketType.ONERTT, PacketNumberSpace.APPLICATION, packetNumber, destConnectionId, frames, minPayloadSize); assertEquals(false, packet.hasLength()); assertEquals(expectedSize, packet.size()); // Check that peeking at the encoded packet returns correct information ByteBuffer encoded = toByteBuffer(encoder, packet, context); checkShortHeaderPacketAt(encoded, 0, PacketType.ONERTT, destConnectionId, context); // write packet at an offset in the datagram to simulate // short packet coalesced after long packet and check // the peek methods again int offset = RANDOM.nextInt(256); int end = offset + encoded.limit(); System.out.printf("testOneRTTPacket.encode(offset:%d, end:%d)%n", offset, end); ByteBuffer datagram = ByteBuffer.allocate(encoded.limit() + offset * 2); datagram.position(offset); datagram.put(encoded); encoded.flip(); datagram.flip(); assert datagram.limit() == offset + encoded.remaining(); // set position to first packet to check connection ids System.out.printf("reading datagram(offset:%d, position:%d, limit:%d)%n", offset, datagram.position(), datagram.limit()); checkShortHeaderPacketAt(datagram, offset, PacketType.ONERTT, destConnectionId, context); // check that skip packet can skip packet at offset datagram.position(0); datagram.limit(end); decoder.skipPacket(datagram, offset); assertEquals(offset + expectedSize, datagram.position()); assertEquals(datagram.limit(), datagram.position()); assertEquals(datagram.capacity() - offset, datagram.position()); // Decode the packet in the datagram datagram.position(offset); int size = expectedSize; for (int i=0; i<1; i++) { int pos = datagram.position(); System.out.printf("Decoding packet: %d at %d%n", (i+1), pos); var decodedPacket = decoder.decode(datagram, context); assertEquals(pos + size, datagram.position()); assertTrue(decodedPacket instanceof OneRttPacket, "decoded: " + decodedPacket); OneRttPacket oneRttDecoded = OneRttPacket.class.cast(decodedPacket); List<QuicFrame> expectedFrames = frames; if (frames.size() > 0 && frames.get(0) instanceof PaddingFrame) { // The first frame should be a crypto frame, except if payloadSize // was less than 7. int frameSizes = frames.stream().mapToInt(QuicFrame::size).sum(); assert frameSizes == payloadSize; assert frameSizes <= 7; // decoder will coalesce padding frames. So instead of finding // two padding frames in the decoded packet we will find just one. // To make the check pass, we should expect a bigger padding frame. if (minPayloadSize > frameSizes) { // replace the first frame with a bigger padding frame expectedFrames = new ArrayList<>(frames); var first = frames.get(0); // replace the first frame with a bigger padding frame that // coalesce the first padding payload frame with the padding that // should have been added by the encoder. // We will then be able to check that the decoded packet contains // that single bigger padding frame. expectedFrames.set(0, new PaddingFrame(minPayloadSize - frameSizes + first.size())); } } checkShortHeaderPacket(oneRttDecoded, PacketType.ONERTT, PacketNumberSpace.APPLICATION, packetNumber, destConnectionId, expectedFrames, minPayloadSize); assertEquals(packet.size(), decodedPacket.size()); assertEquals(size, decodedPacket.size()); } assertEquals(offset + size, datagram.position()); assertEquals(0, datagram.remaining()); assertEquals(end, datagram.limit()); } @Test public void testNoMismatch() { List<ByteBuffer> match1 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4}), ByteBuffer.wrap(new byte[] {5, 6}), ByteBuffer.wrap(new byte[] {7, 8}), ByteBuffer.wrap(new byte[] {9}), ByteBuffer.wrap(new byte[] {10, 11, 12}), ByteBuffer.wrap(new byte[] {13, 14, 15, 16}), ByteBuffer.wrap(new byte[] {17, 18, 19, 20}) ); List<ByteBuffer> match2 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4, 5}), ByteBuffer.wrap(new byte[] {6}), ByteBuffer.wrap(new byte[] {7}), ByteBuffer.wrap(new byte[] {8, 9}), ByteBuffer.wrap(new byte[] {10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20}) ); List<ByteBuffer> match3 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}), ByteBuffer.wrap(new byte[] {9, 10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20}) ); assertEquals(-1, Utils.mismatch(match1, match1)); assertEquals(-1, Utils.mismatch(match2, match2)); assertEquals(-1, Utils.mismatch(match3, match3)); assertEquals(-1, Utils.mismatch(match1, match2)); assertEquals(-1, Utils.mismatch(match2, match1)); assertEquals(-1, Utils.mismatch(match1, match3)); assertEquals(-1, Utils.mismatch(match3, match1)); assertEquals(-1, Utils.mismatch(match2, match3)); assertEquals(-1, Utils.mismatch(match3, match2)); } @Test public void testMismatch() { // match1, match2, match3 match with each others List<ByteBuffer> match1 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4}), ByteBuffer.wrap(new byte[] {5, 6}), ByteBuffer.wrap(new byte[] {7, 8}), ByteBuffer.wrap(new byte[] {9}), ByteBuffer.wrap(new byte[] {10, 11, 12}), ByteBuffer.wrap(new byte[] {13, 14, 15, 16}), ByteBuffer.wrap(new byte[] {17, 18, 19, 20}) ); List<ByteBuffer> match2 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4, 5}), ByteBuffer.wrap(new byte[] {6}), ByteBuffer.wrap(new byte[] {7}), ByteBuffer.wrap(new byte[] {8, 9}), ByteBuffer.wrap(new byte[] {10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20}) ); List<ByteBuffer> match3 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}), ByteBuffer.wrap(new byte[] {9, 10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20}) ); // nomatch0, nomatch10, nomatch19 differ from the previous // list at some index in [0..20[ // nomatch0 mismatches at index 0 List<ByteBuffer> nomatch0 = List.of( ByteBuffer.wrap(new byte[] {21, 2, 3}), ByteBuffer.wrap(new byte[] {4}), ByteBuffer.wrap(new byte[] {5, 6}), ByteBuffer.wrap(new byte[] {7, 8}), ByteBuffer.wrap(new byte[] {9}), ByteBuffer.wrap(new byte[] {10, 11, 12}), ByteBuffer.wrap(new byte[] {13, 14, 15, 16}), ByteBuffer.wrap(new byte[] {17, 18, 19, 20}) ); // nomatch10 mismatches at index 10 List<ByteBuffer> nomatch10 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4, 5}), ByteBuffer.wrap(new byte[] {6}), ByteBuffer.wrap(new byte[] {7}), ByteBuffer.wrap(new byte[] {8, 9}), ByteBuffer.wrap(new byte[] {10, 31}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20}) ); // nomatch19 mismatches at index 19 List<ByteBuffer> nomatch19 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}), ByteBuffer.wrap(new byte[] {9, 10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 40}) ); // morematch1 has one more byte at the end List<ByteBuffer> morematch1 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4}), ByteBuffer.wrap(new byte[] {5, 6}), ByteBuffer.wrap(new byte[] {7, 8}), ByteBuffer.wrap(new byte[] {9}), ByteBuffer.wrap(new byte[] {10, 11, 12}), ByteBuffer.wrap(new byte[] {13, 14, 15, 16}), ByteBuffer.wrap(new byte[] {17, 18, 19, 20, 41}) ); // morematch2 and morematch3 have the same 3 additional // bytes at the end List<ByteBuffer> morematch2 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3}), ByteBuffer.wrap(new byte[] {4, 5}), ByteBuffer.wrap(new byte[] {6}), ByteBuffer.wrap(new byte[] {7}), ByteBuffer.wrap(new byte[] {8, 9}), ByteBuffer.wrap(new byte[] {10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20}), ByteBuffer.wrap(new byte[] {41, 42, 43}) ); List<ByteBuffer> morematch3 = List.of( ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}), ByteBuffer.wrap(new byte[] {9, 10, 11}), ByteBuffer.wrap(new byte[] {12, 13, 14}), ByteBuffer.wrap(new byte[] {15}), ByteBuffer.wrap(new byte[] {16, 17}), ByteBuffer.wrap(new byte[] {18, 19, 20, 41, 42, 43}) ); assertEquals(-1L, Utils.mismatch(nomatch0, nomatch0)); assertEquals(-1L, Utils.mismatch(nomatch10, nomatch10)); assertEquals(-1L, Utils.mismatch(nomatch19, nomatch19)); assertEquals(-1L, Utils.mismatch(morematch1, morematch1)); assertEquals(-1L, Utils.mismatch(morematch2, morematch2)); assertEquals(-1L, Utils.mismatch(morematch3, morematch3)); assertEquals(-1L, Utils.mismatch(morematch2, morematch3)); assertEquals(-1L, Utils.mismatch(morematch3, morematch2)); for (var match : List.of(match1, match2, match3)) { assertEquals(0L, Utils.mismatch(match, nomatch0)); assertEquals(10L, Utils.mismatch(match, nomatch10)); assertEquals(19L, Utils.mismatch(match, nomatch19)); assertEquals(0L, Utils.mismatch(nomatch0, match)); assertEquals(10L, Utils.mismatch(nomatch10, match)); assertEquals(19L, Utils.mismatch(nomatch19, match)); for (var morematch : List.of(morematch1, morematch2, morematch3)) { assertEquals(20L, Utils.mismatch(match, morematch)); assertEquals(20L, Utils.mismatch(morematch, match)); } } } }