/* * Copyright © 2020, 2021, 2022, 2023 Peter Doornbosch * * This file is part of Kwik, an implementation of the QUIC protocol in Java. * * Kwik is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your option) * any later version. * * Kwik 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 Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ package net.luminis.quic.send; import net.luminis.quic.*; import net.luminis.quic.frame.*; import net.luminis.quic.packet.HandshakePacket; import net.luminis.quic.packet.InitialPacket; import net.luminis.quic.packet.QuicPacket; import net.luminis.quic.packet.ShortHeaderPacket; import net.luminis.quic.test.TestClock; import org.assertj.core.data.Percentage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Percentage.withPercentage; import static org.mockito.Mockito.*; class PacketAssemblerTest extends AbstractSenderTest { public static final int MAX_PACKET_SIZE = 1232; private TestClock clock; private SendRequestQueue sendRequestQueue; private InitialPacketAssembler initialPacketAssembler; private PacketAssembler handshakePacketAssembler; private PacketAssembler oneRttPacketAssembler; private AckGenerator initialAckGenerator; private AckGenerator handshakeAckGenerator; private AckGenerator oneRttAckGenerator; @BeforeEach void initObjectUnderTest() { clock = new TestClock(); sendRequestQueue = new SendRequestQueue(clock, null); VersionHolder version = new VersionHolder(Version.getDefault()); initialAckGenerator = new AckGenerator(PnSpace.Initial, mock(Sender.class)); initialPacketAssembler = new InitialPacketAssembler(version, sendRequestQueue, initialAckGenerator); handshakeAckGenerator = new AckGenerator(PnSpace.Handshake, mock(Sender.class)); handshakePacketAssembler = new PacketAssembler(version, EncryptionLevel.Handshake, sendRequestQueue, handshakeAckGenerator); oneRttAckGenerator = new AckGenerator(clock, PnSpace.App, mock(Sender.class)); oneRttPacketAssembler = new PacketAssembler(version, EncryptionLevel.App, sendRequestQueue, oneRttAckGenerator); } @Test void sendSingleShortPacket() { // Given byte[] destCid = new byte[] { 0x0c, 0x0a, 0x0f, 0x0e }; // When sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[7], true), 4 + 7, null); // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, destCid).get().getPacket(); assertThat(packet).isInstanceOf(ShortHeaderPacket.class); assertThat(packet.getDestinationConnectionId()).isEqualTo(destCid); assertThat(packet.getFrames()).containsExactly(new StreamFrame(0, new byte[7], true)); assertThat(packet.generatePacketBytes(aead).length).isLessThan(MAX_PACKET_SIZE); } @Test void sendSingleAck() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); // When sendRequestQueue.addAckRequest(0); // This means the caller wants to send an _explicit_ ack. // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isInstanceOf(ShortHeaderPacket.class); assertThat(packet.getFrames()) .hasSize(1) .allSatisfy(frame -> { assertThat(frame).isInstanceOf(AckFrame.class); assertThat(((AckFrame) frame).getLargestAcknowledged()).isEqualTo(0); }); } @Test void sendAckAndStreamData() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(3, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(8, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(10, 20, EncryptionLevel.App)); // When sendRequestQueue.addAckRequest(0); sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[maxSize - (3 + 2)], true), // Stream length will be > 63, so 2 bytes for length field (3 + 2) + 1, null); // Send at least 1 byte of data // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isInstanceOf(ShortHeaderPacket.class); assertThat(packet.getFrames()).anySatisfy(frame -> { assertThat(frame).isInstanceOf(StreamFrame.class); assertThat(((StreamFrame) frame).getStreamData().length).isCloseTo(1200, withPercentage(0.5)); }); assertThat(packet.getFrames()).anySatisfy(frame -> { assertThat(frame).isInstanceOf(AckFrame.class); assertThat(((AckFrame) frame).getLargestAcknowledged()).isEqualTo(10); }); assertThat(packet.generatePacketBytes(aead).length).isCloseTo(MAX_PACKET_SIZE, Percentage.withPercentage(0.25)); } @Test void sendMultipleFrames() { // When sendRequestQueue.addRequest(new MaxStreamDataFrame(0, 0x01000000000000l), null); // 10 bytes sendRequestQueue.addRequest(new MaxDataFrame(0x05000000000000l), null); // 9 bytes sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[maxSize - (3 + 2)], true), (3 + 2) + 1, null); // Stream length will be > 63, so 2 bytes // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]).get().getPacket(); assertThat(packet.getFrames()).hasOnlyElementsOfTypes(MaxStreamDataFrame.class, MaxDataFrame.class, StreamFrame.class); assertThat(packet.getFrames()).anySatisfy(frame -> { assertThat(frame).isInstanceOf(StreamFrame.class); // Short packet overhead is 18 to 21, so available for stream frame: 1232 - (18 ~ 21) - 10 - 9 = 1192 ~ 1195. // Stream Frame overhead: 5 bytes, so Stream Frame can contain 1187 ~ 1190 bytes assertThat(((StreamFrame) frame).getStreamData().length).isBetween(1187, 1192); }); assertThat(packet.generatePacketBytes(aead).length).isCloseTo(MAX_PACKET_SIZE, Percentage.withPercentage(0.25)); } @Test void whenFirstFrameDoesNotFitFindOneThatDoes() { // Given int remainingCwndSize = 25; // Which leaves room for approx 7 bytes payload. // When sendRequestQueue.addRequest(new MaxStreamDataFrame(0, 0x01000000000000l), null); // 10 bytes frame length sendRequestQueue.addRequest(new DataBlockedFrame(60), null); // 2 bytes frame length sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[Integer.min(maxSize, 63) - (3 + 1)], true), 5, null); // Then QuicPacket packet = oneRttPacketAssembler.assemble(remainingCwndSize, 1232, new byte[0], new byte[0]).get().getPacket(); assertThat(packet.getFrames()) .hasAtLeastOneElementOfType(DataBlockedFrame.class) .hasAtLeastOneElementOfType(StreamFrame.class); assertThat(packet.generatePacketBytes(aead).length).isLessThanOrEqualTo(remainingCwndSize); } @Test void sendHandshakePacketWithMaxLengthCrypto() { // Given byte[] srcCid = new byte[] { (byte) 0xba, (byte) 0xbe }; byte[] destCid = new byte[] { 0x0c, 0x0a, 0x0f, 0x0e }; // When sendRequestQueue.addRequest(maxSize -> new CryptoFrame(Version.getDefault(), 0, new byte[maxSize - (2 + (maxSize < 64? 1: 2))]), (2 + 2) + 1, null); // Then QuicPacket packet = handshakePacketAssembler.assemble(12000, MAX_PACKET_SIZE, srcCid, destCid).get().getPacket(); int generatedPacketLength = packet.generatePacketBytes(aead).length; assertThat(packet).isInstanceOf(HandshakePacket.class); assertThat(((HandshakePacket) packet).getSourceConnectionId()).isEqualTo(srcCid); assertThat(packet.getDestinationConnectionId()).isEqualTo(destCid); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfTypes(CryptoFrame.class); assertThat(generatedPacketLength) .isLessThanOrEqualTo(MAX_PACKET_SIZE) .isEqualTo(MAX_PACKET_SIZE); } @Test void sendInitialPacketWithToken() { // Given byte[] srcCid = new byte[] { (byte) 0xba, (byte) 0xbe }; byte[] destCid = new byte[] { 0x0c, 0x0a, 0x0f, 0x0e }; initialPacketAssembler.setInitialToken(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }); // When sendRequestQueue.addRequest(maxSize -> new CryptoFrame(Version.getDefault(), 0, new byte[234]), (3 + 2) + 234, null); // Then QuicPacket packet = initialPacketAssembler.assemble(12000, 1232, srcCid, destCid).get().getPacket(); assertThat(packet).isInstanceOf(InitialPacket.class); assertThat(((InitialPacket) packet).getSourceConnectionId()).isEqualTo(srcCid); assertThat(packet.getDestinationConnectionId()).isEqualTo(destCid); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfTypes(CryptoFrame.class); assertThat(((InitialPacket) packet).getToken()).hasSize(16); assertThat(packet.generatePacketBytes(aead).length) .isLessThanOrEqualTo(MAX_PACKET_SIZE); } @Test void sendInitialPacketWithoutToken() { // Given byte[] srcCid = new byte[] { (byte) 0xba, (byte) 0xbe }; byte[] destCid = new byte[] { 0x0c, 0x0a, 0x0f, 0x0e }; // When sendRequestQueue.addRequest(maxSize -> new CryptoFrame(Version.getDefault(), 0, new byte[234]), (3 + 2) + 234, null); // Then QuicPacket packet = initialPacketAssembler.assemble(12000, 1232, srcCid, destCid).get().getPacket(); assertThat(packet).isInstanceOf(InitialPacket.class); assertThat(((InitialPacket) packet).getSourceConnectionId()).isEqualTo(srcCid); assertThat(packet.getDestinationConnectionId()).isEqualTo(destCid); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfTypes(CryptoFrame.class); assertThat(packet.generatePacketBytes(aead).length) .isLessThanOrEqualTo(MAX_PACKET_SIZE); } @Test void anyInitialPacketShouldHaveToken() { // Given initialPacketAssembler.setInitialToken(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }); initialAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.Initial)); // When sendRequestQueue.addAckRequest(0); // This means the caller wants to send an _explicit_ ack. // Then InitialPacket packet = (InitialPacket) initialPacketAssembler.assemble(12000, 1232, new byte[0], new byte[0]).get().getPacket(); assertThat(packet.getFrames()) .hasOnlyElementsOfTypes(AckFrame.class, Padding.class); assertThat(packet.getToken()).hasSize(8); assertThat(packet.generatePacketBytes(aead).length) .isLessThanOrEqualTo(MAX_PACKET_SIZE); } @Test void whenNothingToSendDelayedAckIsSendAfterDelay() throws Exception { // Given int ackDelay = 10; // When oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(ackDelay); // Then Optional firstCheck = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[]{ (byte) 0xdc, 0x1d }); // When clock.fastForward(ackDelay); // Then Optional packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[]{ (byte) 0xdc, 0x1d }).map(e -> e.getPacket()); assertThat(firstCheck).isEmpty(); assertThat(packet.isPresent()).isTrue(); assertThat(packet.get().getFrames()).allSatisfy(frame -> { assertThat(frame).isInstanceOf(AckFrame.class); assertThat(((AckFrame) frame).getAckDelay()).isGreaterThanOrEqualTo(ackDelay); }); } @Test void whenSendingDataSentPacketWillIncludeAck() throws Exception { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(3, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(8, 20, EncryptionLevel.App)); // When sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[32], true), 37, null); // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isInstanceOf(ShortHeaderPacket.class); assertThat(packet.getFrames()) .hasSize(2) .hasOnlyElementsOfTypes(StreamFrame.class, AckFrame.class) .anySatisfy(frame -> { assertThat(frame).isInstanceOf(AckFrame.class); assertThat(((AckFrame) frame).getLargestAcknowledged()).isEqualTo(8); }); } @Test void whenSendingLargestPossibleFrameStillImplicitAckIsIncluded() throws Exception { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(3, 20, EncryptionLevel.App)); oneRttAckGenerator.packetReceived(new MockPacket(8, 20, EncryptionLevel.App)); // When sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[maxSize - (3 + 2)], true), // Stream length will be > 63, so 2 bytes for length field (3 + 2) + 1, null); // Send at least 1 byte of data // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, MAX_PACKET_SIZE, null, new byte[0]).get().getPacket(); assertThat(packet.getFrames()) .hasSize(2) .hasOnlyElementsOfTypes(StreamFrame.class, AckFrame.class); byte[] packetBytes = packet.generatePacketBytes(aead); assertThat(packetBytes.length) .isLessThanOrEqualTo(MAX_PACKET_SIZE) .isEqualTo(MAX_PACKET_SIZE); } @Test void whenNoDataToSendButAnExplicitAckIsQueueAssembleWillCreateAckOnlyPacket() throws Exception { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); // When sendRequestQueue.addAckRequest(); // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isNotNull(); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfType(AckFrame.class); } @Test void whenExplicitAckIsAssembledNextTimeItWillNot() throws Exception { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); // When sendRequestQueue.addAckRequest(); oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]); // Then Optional optionalSendItem = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]); assertThat(optionalSendItem).isEmpty(); } @Test void whenNoDataToSendAndNoExcplicitAckToSendAssembleWillNotGenerateAckOnlyPacket() throws Exception { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); // When // Nothing, no explicit ack requested // Then Optional packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]); assertThat(packet).isEmpty(); } @Test void whenCwndReachedNoDataIsSent() { // When sendRequestQueue.addRequest(new MaxDataFrame(102_000), null); int currentCwndRemaining = 16; // Then Optional packet = oneRttPacketAssembler.assemble(currentCwndRemaining, 1232, null, new byte[0]); assertThat(packet).isEmpty(); } @Test void whenAddingProbeAndRequestListIsEmptyThenPingFrameShouldBeSent() { // When sendRequestQueue.addProbeRequest(); // Then QuicPacket packet = oneRttPacketAssembler.assemble(12000, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isNotNull(); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfType(PingFrame.class); } @Test void whenCwndReachedSendingProbeLeadsToSinglePing() { // When int currentCwndRemaining = 16; sendRequestQueue.addRequest(new MaxDataFrame(102_000), null); sendRequestQueue.addProbeRequest(); // Then QuicPacket packet = oneRttPacketAssembler.assemble(currentCwndRemaining, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isNotNull(); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfType(PingFrame.class); // And Optional another = oneRttPacketAssembler.assemble(currentCwndRemaining, 1232, null, new byte[0]); assertThat(another).isEmpty(); } @Test void whenAddingProbeToNonEmptySendQueueAndCwndIsLargeEnoughTheNextPacketIsSent() { // When sendRequestQueue.addRequest(new MaxDataFrame(102_000), null); sendRequestQueue.addProbeRequest(); // Then QuicPacket packet = oneRttPacketAssembler.assemble(60, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isNotNull(); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfType(MaxDataFrame.class); } @Test void whenProbeContainsDataThisIsSendInsteadOfQueuedFrames() { // When sendRequestQueue.addRequest(new MaxDataFrame(102_000), null); sendRequestQueue.addProbeRequest(List.of(new CryptoFrame(Version.getDefault(), 0, new byte[100]))); // Then QuicPacket packet = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]).get().getPacket(); assertThat(packet).isNotNull(); assertThat(packet.getFrames()) .hasSize(1) .hasOnlyElementsOfType(CryptoFrame.class); } @Test void testFrameCallbacksAreCalledByPacketLostCallback() { // Given Consumer callback1 = mock(Consumer.class); sendRequestQueue.addRequest(new MaxDataFrame(102_000), callback1); Consumer callback2 = mock(Consumer.class); sendRequestQueue.addRequest(new StreamFrame(1, new byte[924], true), callback2); // When SendItem sendItem = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]).get(); sendItem.getPacketLostCallback().accept(sendItem.getPacket()); // Then verify(callback1).accept(argThat(frame -> frame instanceof MaxDataFrame)); verify(callback2).accept(argThat(frame -> frame instanceof StreamFrame)); } @Test void testInPresenceOfAckFrameAllFrameCallbacksAreCalledByPacketLostCallback() { // Given Consumer callback1 = mock(Consumer.class); sendRequestQueue.addRequest(new MaxDataFrame(102_000), callback1); Consumer callback2 = mock(Consumer.class); sendRequestQueue.addRequest(new StreamFrame(1, new byte[924], true), callback2); oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(0); // When SendItem sendItem = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]).get(); sendItem.getPacketLostCallback().accept(sendItem.getPacket()); // Then assertThat(sendItem.getPacket().getFrames()).hasAtLeastOneElementOfType(AckFrame.class); verify(callback1).accept(argThat(frame -> frame instanceof MaxDataFrame)); verify(callback2).accept(argThat(frame -> frame instanceof StreamFrame)); } @Test void createdPacketHasPacketNumberSet() { // Given sendRequestQueue.addRequest(new MaxStreamDataFrame(0, 0x01000000000000l), null); // When QuicPacket packet = oneRttPacketAssembler.assemble(1200, 1232, new byte[0], new byte[0]).get().getPacket(); // Then assertThat(packet.getPacketNumber()).isNotNull(); assertThat(packet.getPacketNumber()).isEqualTo(0); } @Test void consecutivePacketsHaveIncreasingPacketNumber() { // Given sendRequestQueue.addRequest(new StreamFrame(0, new byte[1160], false), f -> {}); sendRequestQueue.addRequest(new StreamFrame(0, new byte[1160], false), f -> {}); sendRequestQueue.addRequest(new StreamFrame(0, new byte[1160], false), f -> {}); // When QuicPacket packet1 = oneRttPacketAssembler.assemble(1200, 1232, new byte[0], new byte[0]).get().getPacket(); QuicPacket packet2 = oneRttPacketAssembler.assemble(1200, 1232, new byte[0], new byte[0]).get().getPacket(); QuicPacket packet3 = oneRttPacketAssembler.assemble(1200, 1232, new byte[0], new byte[0]).get().getPacket(); // Then assertThat(packet2.getPacketNumber()).isGreaterThan(packet1.getPacketNumber()); assertThat(packet3.getPacketNumber()).isGreaterThan(packet2.getPacketNumber()); } @Test void whenAckIsSendThenAckSendRequestIsCleared() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(0); // When SendItem firstSendItem = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]).get(); // Then Optional secondSendItem = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]); assertThat(secondSendItem).isEmpty(); assertThat(firstSendItem.getPacket().getFrames()) .hasSize(1) .hasOnlyElementsOfType(AckFrame.class); } @Test void whenAckWasRequestedButIsNotNecessaryAnymoreDoNotSendIt() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addRequest(new StreamFrame(0, new byte[160], false), f -> {}); // Simulate race condition where ack is picked up by a "normal" send SendItem firstSendItem = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]).get(); // Before the ack request was actually queued. sendRequestQueue.addAckRequest(0); // Then Optional secondSendItem = oneRttPacketAssembler.assemble(1200, 1232, null, new byte[0]); assertThat(secondSendItem).isEmpty(); assertThat(firstSendItem.getPacket().getFrames()) .hasSize(2) .hasAtLeastOneElementOfType(AckFrame.class); } @Test void whenExplicitAckDoesNotFitInPacketDontSendIt() { // This can happen when coalescing packets into one datagram and the space left is not enough // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(1200, 20, null, new byte[0]); // Then assertThat(optionalSendItem).isEmpty(); assertThat(oneRttAckGenerator.hasNewAckToSend()).isTrue(); } @Test void explicitAckIsSentEvenIfCWndIsZero() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(0, 25, null, new byte[0]); // Then assertThat(optionalSendItem).isPresent(); assertThat(optionalSendItem.get().getPacket().getFrames()).hasOnlyElementsOfType(AckFrame.class); } @Test void whenExplicitAckDoesNotFitInPacketItIsSendWithNextPacket() { // This can happen when coalescing packets into one datagram and the space left is not enough // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(); oneRttPacketAssembler.assemble(1200, 20, null, new byte[0]); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(1200, 200, null, new byte[0]); // Then assertThat(optionalSendItem).isPresent(); assertThat(optionalSendItem.get().getPacket().getFrames()).hasOnlyElementsOfType(AckFrame.class); } @Test void whenAckDoesNotFitInPacketItShouldNotBeAdded() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[32], true), 37, null); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(4, 1200, null, new byte[0]); // Then assertThat(optionalSendItem).isEmpty(); } @Test void whenAckDoesNotFitWithOtherFrameOnlyFrameShouldBeAdded() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addRequest(new PingFrame(), f -> {}); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(18 + 4, 1200, null, new byte[0]); // Then assertThat(optionalSendItem).isPresent(); assertThat(optionalSendItem.get().getPacket().getFrames()).hasOnlyElementsOfType(PingFrame.class); } @Test void whenSupplierReturnsNothingAssembleDoesNotReturnFrames() { // Given sendRequestQueue.addRequest(size -> null, 20, f -> {}); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(6000, 1200, null, new byte[0]); // Then assertThat(optionalSendItem).isEmpty(); } @Test void whenSupplierReturnsNothingNextInQueueIsUseds() { // Given sendRequestQueue.addRequest(size -> null, 20, f -> {}); sendRequestQueue.addRequest(new PingFrame(), f -> {}); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(6000, 1200, null, new byte[0]); // Then assertThat(optionalSendItem).isPresent(); assertThat(optionalSendItem.get().getPacket().getFrames()).hasOnlyElementsOfType(PingFrame.class); } @Test void whenSupplierReturnsNothingButThereIsAckToSendAssembleReturnsPacket() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addRequest(size -> null, 20, f -> {}); sendRequestQueue.addAckRequest(); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(6000, 1200, null, new byte[0]); // Then assertThat(optionalSendItem).isPresent(); assertThat(optionalSendItem.get().getPacket().getFrames()).hasOnlyElementsOfType(AckFrame.class); } @Test void whenSupplierReturnsNothingButThereIsOptionalAckToSendAssembleReturnsNothing() { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addRequest(size -> null, 20, f -> {}); // When Optional optionalSendItem = oneRttPacketAssembler.assemble(6000, 1200, null, new byte[0]); // Then assertThat(optionalSendItem).isEmpty(); } @Test void whenExplicitAckIsSentImplicitlySendRequestQueueDoesNotContainAckRequestAnymore() throws Exception { // Given // ... there is a delayed ack pending int ackDelay = 20; oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(ackDelay); // As test is using mock sender, this call must be done explicitly in the test // When // ... it is send together with a ack-eliciting packet sendRequestQueue.addRequest(new PingFrame(), Sender.NO_RETRANSMIT); Optional firstPacket = oneRttPacketAssembler.assemble(6000, 1200, null, new byte[0]); assertThat(firstPacket).isPresent(); assertThat(firstPacket.get().getPacket().getFrames()).hasAtLeastOneElementOfType(AckFrame.class); // Then // ... (even) after delay time clock.fastForward(ackDelay); // ... no ack is sent. Optional secondPacket = oneRttPacketAssembler.assemble(6000, 1200, null, new byte[0]); assertThat(secondPacket).isEmpty(); } @Test void whenAckDoesNotFitInPacketItStaysQueued() throws Exception { // Given oneRttAckGenerator.packetReceived(new MockPacket(0, 20, EncryptionLevel.App)); sendRequestQueue.addAckRequest(); // As test is using mock sender, this call must be done explicitly in the test // When oneRttPacketAssembler.assemble(6000, 2, null, new byte[0]); // Then assertThat(sendRequestQueue.mustSendAck()).isTrue(); } @Test void sizeOfAssembledPacketShouldNotBeGreaterThanMaxRequested() throws Exception { // Given sendRequestQueue.addRequest(maxSize -> new StreamFrame(0, new byte[maxSize - (3 + 2)], true), // Stream length will be > 63, so 2 bytes for length field (3 + 2) + 1, // Send at least 1 byte of data null); // When int maxSize = 1229; Optional item = handshakePacketAssembler.assemble(6000, maxSize, new byte[0], new byte[0]); // Then QuicPacket packet = item.get().getPacket(); assertThat(packet.generatePacketBytes(TestUtils.createKeys()).length).isLessThanOrEqualTo(maxSize); } @Test void whenPacketDoesNotFitInPacketSizeAssembleShouldNotReturnPacket() throws Exception { sendRequestQueue.addRequest(new CryptoFrame(Version.getDefault(), 0, new byte[1000]), f -> {}); Optional item = oneRttPacketAssembler.assemble(6000, 500, null, new byte[0]); assertThat(item).isNotPresent(); } @Test void evenSmallestProbePacketMustObeyMaxPacketSizeLimit() throws Exception { sendRequestQueue.addProbeRequest(List.of(new CryptoFrame(Version.getDefault(), new byte[90]))); int maxAvailablePacketSize = 10; Optional item = oneRttPacketAssembler.assemble(6000, maxAvailablePacketSize, new byte[0], new byte[0]); assertThat(item).isNotPresent(); } }