/* * Copyright © 2019, 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; import net.luminis.quic.ack.Range; import net.luminis.quic.frame.AckFrame; import net.luminis.quic.packet.RetryPacket; import net.luminis.quic.packet.VersionNegotiationPacket; import net.luminis.quic.send.Sender; import net.luminis.quic.test.TestClock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.time.Clock; import java.util.ArrayList; import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.*; class AckGeneratorTest { private TestClock clock; private AckGenerator ackGenerator; private Sender sender; @BeforeEach void initObjectUnderTest() { clock = new TestClock(); sender = mock(Sender.class); ackGenerator = new AckGenerator(clock, PnSpace.App, sender); } @Test void newGeneratorDoesNotGenerateAck() { assertThat(ackGenerator.hasAckToSend()).isEqualTo(false); } @Test void receivingPacketLeadsToSingleAck() { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); assertThat(ackGenerator.hasAckToSend()).isEqualTo(true); AckFrame ack = ackGenerator.generateAckForPacket(0).get(); assertThat(ack.getLargestAcknowledged()).isEqualTo(0); assertThat(ack.getAckedPacketNumbers()).containsOnly(0L); } @Test void receivingMultipleConsequetivePacketLeadsToRangeAck() { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(1, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(2, 83, EncryptionLevel.Initial)); assertThat(ackGenerator.hasAckToSend()).isEqualTo(true); AckFrame ack = ackGenerator.generateAckForPacket(0).get(); assertThat(ack.getLargestAcknowledged()).isEqualTo(2); assertThat(ack.getAckedPacketNumbers()).containsOnly(0L, 1L, 2L); } @Test void afterReceivingMorePacketsOldAcksRemain() { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(1, 83, EncryptionLevel.Initial)); AckFrame ack1 = ackGenerator.generateAckForPacket(1).get(); assertThat(ack1.getAckedPacketNumbers()).containsOnly(0L, 1L); ackGenerator.packetReceived(new MockPacket(3, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(5, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(6, 83, EncryptionLevel.Initial)); AckFrame ack2 = ackGenerator.generateAckForPacket(1).get(); assertThat(ack2.getLargestAcknowledged()).isEqualTo(6); assertThat(ack2.getAckedPacketNumbers()).containsOnly(0L, 1L, 3L, 5L, 6L); } @Test void afterProcessingReceivedAckForAllSentAcksThereAreNoAcksToSend() { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(1, 83, EncryptionLevel.Initial)); AckFrame ack1 = ackGenerator.generateAckForPacket(1).get(); assertThat(ack1.getAckedPacketNumbers()).containsOnly(0L, 1L); ackGenerator.process(new AckFrame(1)); assertThat(ackGenerator.hasAckToSend()).isEqualTo(false); } @Test void afterProcessingReceivedAckAcknowledgedAcksAreRemoved() { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); ackGenerator.packetReceived(new MockPacket(1, 83, EncryptionLevel.Initial)); AckFrame ack1 = ackGenerator.generateAckForPacket(6).get(); assertThat(ack1.getAckedPacketNumbers()).containsOnly(0L, 1L); ackGenerator.process(new AckFrame(6)); // This acks the ack sent in packet 6 -> ack1 ackGenerator.packetReceived(new MockPacket(2, 83, EncryptionLevel.Initial)); assertThat(ackGenerator.hasAckToSend()).isEqualTo(true); AckFrame ack2 = ackGenerator.generateAckForPacket(2).get(); assertThat(ack2.getAckedPacketNumbers()).containsOnly(2L); } @Test void receivingVersionNegotiationPacketDoesNotLeadToAck() { ackGenerator.packetReceived(new VersionNegotiationPacket()); assertThat(ackGenerator.hasAckToSend()).isEqualTo(false); } @Test void receivingRetryPacketDoesNotLeadToAck() { ackGenerator.packetReceived(new RetryPacket(Version.getDefault())); assertThat(ackGenerator.hasAckToSend()).isEqualTo(false); } @Test void afterSendingAckThereIsNoNewAckToSend() throws Exception { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); assertThat(ackGenerator.hasNewAckToSend()).isEqualTo(true); AckFrame ack = ackGenerator.generateAckForPacket(0).get(); assertThat(ackGenerator.hasNewAckToSend()).isEqualTo(false); } @Test void ifTheNotAcknowledgedPacketIsAckOnlyThereIsNowAckNewToSend() throws Exception { ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial, new AckFrame())); assertThat(ackGenerator.hasNewAckToSend()).isEqualTo(false); } @Test void ifAckIsDelayedTheDelayFieldIsSet() throws Exception { // Given ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); // When clock.fastForward(10); // Then AckFrame ackFrame = ackGenerator.generateAckForPacket(13).get(); assertThat(ackFrame.getAckDelay()).isGreaterThanOrEqualTo(10); } @Test void ifAckIsDelayedThenDelayFieldIsOnlySetForFirstAck() throws Exception { // Given ackGenerator.packetReceived(new MockPacket(0, 83, EncryptionLevel.Initial)); // When clock.fastForward(10); // Then AckFrame firstAckFrame = ackGenerator.generateAckForPacket(13).get(); AckFrame secondAckFrame = ackGenerator.generateAckForPacket(14).get(); assertThat(secondAckFrame.getAckDelay()).isEqualTo(0); } @Test void ifAcksAreDelayedThenAckDelayShouldBeBasedOnOldestAck() throws Exception { // Given ackGenerator.packetReceived(new MockPacket(1, 83, EncryptionLevel.Initial)); // When clock.fastForward(10); ackGenerator.packetReceived(new MockPacket(2, 83, EncryptionLevel.Initial)); clock.fastForward(10); // Then Optional ack = ackGenerator.generateAckForPacket(13); assertThat(ack.get().getAckDelay()) .isGreaterThanOrEqualTo(20) .isLessThanOrEqualTo(25); } @Test void oneRttAcksAreGeneratedForEverySecondPacket() { // Given ackGenerator.packetReceived(new MockPacket(1, 83, EncryptionLevel.App)); // Then verify(sender, times(1)).sendAck(PnSpace.App, 20); clearInvocations(sender); // And When ackGenerator.packetReceived(new MockPacket(2, 83, EncryptionLevel.App)); // Then verify(sender, timeout(1)).sendAck(PnSpace.App, 0); } @Test void removeOneExactlyMatchingAcknowlegdedRange() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(15, 19), range(7, 10), range(1, 4))); AckFrame ackFrame = new AckFrame(range(7, 10)); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(15, 19), range(1, 4)); } @Test void removeMultipleExactlyMatchingAcknowlegdedRanges() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(27, 36), range(25, 25), range(15, 19), range(7, 10), range(1, 4))); AckFrame ackFrame = new AckFrame(List.of(range(25, 25), range(15, 19))); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(27, 36), range(7, 10), range(1, 4)); } @Test void doNotRemoteNotMatchingAcknowlegdedRange() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(25, 29), range(15, 19), range(7, 10), range(1, 4))); // Overlapping AckFrame ackFrame = new AckFrame(List.of(range(21, 23), range(15, 19))); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(25, 29), range(7, 10), range(1, 4)); } @Test void removeOnePartlyMatchingAcknowlegdedRange1() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(15, 19), range(7, 10), range(1, 4))); // Overlapping AckFrame ackFrame = new AckFrame(range(7, 11)); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(15, 19), range(1, 4)); } @Test void removeOnePartlyMatchingAcknowlegdedRange2() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(15, 19), range(7, 10), range(1, 4))); // Overlapping AckFrame ackFrame = new AckFrame(range(7, 9)); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(15, 19), range(10), range(1, 4)); } @Test void removeOnePartlyMatchingAcknowlegdedRange3() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(15, 19), range(7, 10), range(1, 4))); // Overlapping AckFrame ackFrame = new AckFrame(range(6, 9)); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(15, 19), range(10), range(1, 4)); } @Test void removeOnePartlyMatchingAcknowlegdedRange4() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(15, 19), range(7, 10), range(1, 4))); // Overlapping AckFrame ackFrame = new AckFrame(range(5, 10)); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(15, 19), range(1, 4)); } @Test void removeOnePartlyMatchingAcknowlegdedRange5() { // Given ArrayList ackedRanges = new ArrayList<>(List.of(range(15, 19), range(7, 10), range(1, 4))); // Overlapping AckFrame ackFrame = new AckFrame(range(2, 6)); // When ackGenerator.removeAcknowlegdedRanges(ackedRanges, ackFrame); // Then assertThat(ackedRanges).containsExactly(range(15, 19), range(7, 10), range(1)); } Range range(int from, int to) { return new Range(from, to); } Range range(int single) { return new Range(single, single); } }