update to latest quic native, add quic tests
This commit is contained in:
parent
e293f2e603
commit
938371e9eb
46 changed files with 5231 additions and 19 deletions
10
NOTICE.txt
10
NOTICE.txt
|
@ -42,12 +42,12 @@ Subproject organization
|
|||
Original netty subproject names are not related to package names. I reorganized the names to allow better assignment
|
||||
between subproject name, package name, artifact names, and java module. The following reorgnizations were performed:
|
||||
|
||||
netty/all -> [todo]
|
||||
netty/bom -> [todo]
|
||||
netty/all ->
|
||||
netty/bom ->
|
||||
netty/buffer -> netty-buffer
|
||||
netty/codec -> netty-handler-codec, netty-handler-codec-compression, netty-handler-codec-protobuf
|
||||
netty/codec-dns -> [todo]
|
||||
netty/codec-haproxy -> [todo]
|
||||
netty/codec-dns -> netty-handler-codec-dns
|
||||
netty/codec-haproxy ->
|
||||
netty/codec-http -> netty-handler-codec-http, netty-handler-codec-rtsp, netty-handler-codec-spdy
|
||||
netty/codec-http2 ->
|
||||
netty/codec-memcache ->
|
||||
|
@ -62,7 +62,7 @@ netty/handler -> netty-handler
|
|||
netty/handler-proxy
|
||||
netty/handler-ssl-ocsp
|
||||
netty/resolver -> netty-resolver
|
||||
netty/resolver-dns ->
|
||||
netty/resolver-dns -> netty-resolver-dns
|
||||
netty/resolver-dns-classes-macos -> [dropped]
|
||||
netty/resolver-dns-native-macos -> [dropped]
|
||||
netty/transport -> netty-channel
|
||||
|
|
|
@ -31,7 +31,7 @@ ext {
|
|||
apply plugin: 'com.google.osdetector'
|
||||
|
||||
subprojects {
|
||||
if (!it.name.endsWith('-native')) {
|
||||
if (!it.name.endsWith('-native') && it.name != 'test-results') {
|
||||
apply from: rootProject.file('gradle/repositories/maven.gradle')
|
||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||
|
|
|
@ -12,8 +12,8 @@ test {
|
|||
useJUnitPlatform()
|
||||
failFast = false
|
||||
ignoreFailures = true
|
||||
minHeapSize = "1g" // initial heap size
|
||||
maxHeapSize = "2g" // maximum heap size
|
||||
minHeapSize = "2g" // initial heap size
|
||||
maxHeapSize = "4g" // maximum heap size
|
||||
jvmArgs '--add-exports=java.base/jdk.internal=ALL-UNNAMED',
|
||||
'--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
|
||||
'--add-exports=java.base/sun.nio.ch=ALL-UNNAMED',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
/* currently we do not build our C code natively, but we provide copies of the binaries in META-INF/native */
|
||||
|
||||
/* the static library is included in other native builds, so nothing is provided here */
|
||||
|
|
2
netty-handler-codec-quic-native/NOTICE.txt
Normal file
2
netty-handler-codec-quic-native/NOTICE.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
Because we moved from "io.netty.incubator" to "io.netty", we use only a specially prepared linux x86-64 shared library
|
Binary file not shown.
BIN
netty-handler-codec-quic-native/src/main/resources/META-INF/native/libnetty_quiche_linux_x86_64.so
Normal file → Executable file
BIN
netty-handler-codec-quic-native/src/main/resources/META-INF/native/libnetty_quiche_linux_x86_64.so
Normal file → Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -3,4 +3,6 @@ dependencies {
|
|||
implementation project(':netty-channel-epoll')
|
||||
implementation project(':netty-channel-unix')
|
||||
runtimeOnly project(path: ':netty-handler-codec-quic-native', configuration: osdetector.classifier)
|
||||
testImplementation testLibs.assertj
|
||||
testImplementation project(':netty-handler-ssl-bouncycastle')
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@ public final class QuicSslContextBuilder {
|
|||
|
||||
/**
|
||||
* Enable / disable keylog. When enabled, TLS keys are logged to an internal logger named
|
||||
* "io.netty.incubator.codec.quic.BoringSSLLogginKeylog" with DEBUG level, see
|
||||
* "io.netty.codec.quic.BoringSSLLogginKeylog" with DEBUG level, see
|
||||
* {@link BoringSSLKeylog} for detail, logging keys are following
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.concurrent.ImmediateExecutor;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Timeout(10)
|
||||
public abstract class AbstractQuicTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void ensureAvailability() {
|
||||
Quic.ensureAvailability();
|
||||
}
|
||||
|
||||
static Executor[] newSslTaskExecutors() {
|
||||
return new Executor[] {
|
||||
ImmediateExecutor.INSTANCE,
|
||||
Executors.newSingleThreadExecutor()
|
||||
};
|
||||
}
|
||||
|
||||
static void shutdown(Executor executor) {
|
||||
if (executor instanceof ExecutorService) {
|
||||
((ExecutorService) executor).shutdown();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class FlushStrategyTest {
|
||||
|
||||
@Test
|
||||
public void testAfterNumBytes() {
|
||||
FlushStrategy strategy = FlushStrategy.afterNumBytes(10);
|
||||
assertFalse(strategy.shouldFlushNow(1, 10));
|
||||
assertTrue(strategy.shouldFlushNow(1, 11));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAfterNumPackets() {
|
||||
FlushStrategy strategy = FlushStrategy.afterNumPackets(10);
|
||||
assertFalse(strategy.shouldFlushNow(10, 10));
|
||||
assertTrue(strategy.shouldFlushNow(11, 11));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
public class InsecureQuicTokenHandlerTest extends AbstractQuicTest {
|
||||
|
||||
@Test
|
||||
public void testMaxTokenLength() {
|
||||
assertEquals(InsecureQuicTokenHandler.MAX_TOKEN_LEN, InsecureQuicTokenHandler.INSTANCE.maxTokenLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenProcessingIpv4() throws UnknownHostException {
|
||||
testTokenProcessing(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenProcessingIpv6() throws UnknownHostException {
|
||||
testTokenProcessing(false);
|
||||
}
|
||||
|
||||
private static void testTokenProcessing(boolean ipv4) throws UnknownHostException {
|
||||
byte[] bytes = new byte[Quiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
ThreadLocalRandom.current().nextBytes(bytes);
|
||||
ByteBuf dcid = Unpooled.wrappedBuffer(bytes);
|
||||
ByteBuf out = Unpooled.buffer();
|
||||
try {
|
||||
final InetSocketAddress validAddress;
|
||||
final InetSocketAddress invalidAddress;
|
||||
if (ipv4) {
|
||||
validAddress = new InetSocketAddress(
|
||||
InetAddress.getByAddress(new byte[] { 10, 10, 10, 1}), 9999);
|
||||
invalidAddress = new InetSocketAddress(
|
||||
InetAddress.getByAddress(new byte[] { 10, 10, 10, 10}), 9999);
|
||||
} else {
|
||||
validAddress = new InetSocketAddress(InetAddress.getByAddress(
|
||||
new byte[] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1}), 9999);
|
||||
invalidAddress = new InetSocketAddress(InetAddress.getByAddress(
|
||||
new byte[] { 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}), 9999);
|
||||
}
|
||||
|
||||
InsecureQuicTokenHandler.INSTANCE.writeToken(out, dcid, validAddress);
|
||||
assertThat(out.readableBytes(), lessThanOrEqualTo(InsecureQuicTokenHandler.INSTANCE.maxTokenLength()));
|
||||
assertNotEquals(-1, InsecureQuicTokenHandler.INSTANCE.validateToken(out, validAddress));
|
||||
|
||||
// Use another address and check that the validate fails.
|
||||
assertEquals(-1, InsecureQuicTokenHandler.INSTANCE.validateToken(out, invalidAddress));
|
||||
} finally {
|
||||
dcid.release();
|
||||
out.release();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class QuicChannelDatagramTest extends AbstractQuicTest {
|
||||
|
||||
private static final Random random = new Random();
|
||||
static final byte[] data = new byte[512];
|
||||
|
||||
static {
|
||||
random.nextBytes(data);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testDatagramFlushInChannelRead(Executor executor) throws Throwable {
|
||||
testDatagram(executor, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testDatagramFlushInChannelReadComplete(Executor executor) throws Throwable {
|
||||
testDatagram(executor, true);
|
||||
}
|
||||
|
||||
private void testDatagram(Executor executor, boolean flushInReadComplete) throws Throwable {
|
||||
AtomicReference<QuicDatagramExtensionEvent> serverEventRef = new AtomicReference<>();
|
||||
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
final ChannelFuture future;
|
||||
if (!flushInReadComplete) {
|
||||
future = ctx.writeAndFlush(msg);
|
||||
} else {
|
||||
future = ctx.write(msg);
|
||||
}
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (flushInReadComplete) {
|
||||
ctx.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof QuicDatagramExtensionEvent) {
|
||||
serverEventRef.set((QuicDatagramExtensionEvent) evt);
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
};
|
||||
Channel server = QuicTestUtils.newServer(QuicTestUtils.newQuicServerBuilder(executor)
|
||||
.datagram(10, 10),
|
||||
InsecureQuicTokenHandler.INSTANCE, serverHandler , new ChannelInboundHandlerAdapter());
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
|
||||
Promise<ByteBuf> receivedBuffer = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
AtomicReference<QuicDatagramExtensionEvent> clientEventRef = new AtomicReference<>();
|
||||
Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder(executor)
|
||||
.datagram(10, 10));
|
||||
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (!receivedBuffer.trySuccess((ByteBuf) msg)) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof QuicDatagramExtensionEvent) {
|
||||
clientEventRef.set((QuicDatagramExtensionEvent) evt);
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
receivedBuffer.tryFailure(cause);
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.remoteAddress(address)
|
||||
.connect()
|
||||
.get();
|
||||
quicChannel.writeAndFlush(Unpooled.copiedBuffer(data)).sync();
|
||||
|
||||
ByteBuf buffer = receivedBuffer.get();
|
||||
ByteBuf expected = Unpooled.wrappedBuffer(data);
|
||||
assertEquals(expected, buffer);
|
||||
buffer.release();
|
||||
expected.release();
|
||||
|
||||
assertNotEquals(0, serverEventRef.get().maxLength());
|
||||
assertNotEquals(0, clientEventRef.get().maxLength());
|
||||
|
||||
quicChannel.close().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testDatagramNoAutoReadMaxMessagesPerRead1(Executor executor) throws Throwable {
|
||||
testDatagramNoAutoRead(executor, 1, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testDatagramNoAutoReadMaxMessagesPerRead3(Executor executor) throws Throwable {
|
||||
testDatagramNoAutoRead(executor, 3, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testDatagramNoAutoReadMaxMessagesPerRead1OutSideEventLoop(Executor executor) throws Throwable {
|
||||
testDatagramNoAutoRead(executor, 1, true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testDatagramNoAutoReadMaxMessagesPerRead3OutSideEventLoop(Executor executor) throws Throwable {
|
||||
testDatagramNoAutoRead(executor, 3, true);
|
||||
}
|
||||
|
||||
private void testDatagramNoAutoRead(Executor executor, int maxMessagesPerRead, boolean readLater) throws Throwable {
|
||||
Promise<Void> serverPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
Promise<ByteBuf> clientPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
|
||||
int numDatagrams = 5;
|
||||
AtomicInteger serverReadCount = new AtomicInteger();
|
||||
CountDownLatch latch = new CountDownLatch(numDatagrams);
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
private int readPerLoop;
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
readPerLoop++;
|
||||
|
||||
ctx.writeAndFlush(msg).addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
if (serverReadCount.incrementAndGet() == numDatagrams) {
|
||||
serverPromise.trySuccess(null);
|
||||
}
|
||||
} else {
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (readPerLoop > maxMessagesPerRead) {
|
||||
ctx.close();
|
||||
serverPromise.tryFailure(new AssertionError(
|
||||
"Read more then " + maxMessagesPerRead + " time per read loop"));
|
||||
return;
|
||||
}
|
||||
readPerLoop = 0;
|
||||
if (serverReadCount.get() < numDatagrams) {
|
||||
if (readLater) {
|
||||
ctx.executor().execute(ctx::read);
|
||||
} else {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Channel server = QuicTestUtils.newServer(QuicTestUtils.newQuicServerBuilder(executor)
|
||||
.option(ChannelOption.AUTO_READ, false)
|
||||
.option(ChannelOption.MAX_MESSAGES_PER_READ, maxMessagesPerRead)
|
||||
.datagram(10, 10),
|
||||
InsecureQuicTokenHandler.INSTANCE, serverHandler, new ChannelInboundHandlerAdapter());
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
|
||||
Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder(executor)
|
||||
.datagram(10, 10));
|
||||
AtomicInteger clientReadCount = new AtomicInteger();
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler() {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
|
||||
if (clientReadCount.incrementAndGet() == numDatagrams) {
|
||||
if (!clientPromise.trySuccess((ByteBuf) msg)) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
} else {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
} else {
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
clientPromise.tryFailure(cause);
|
||||
}
|
||||
};
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.remoteAddress(address)
|
||||
.connect()
|
||||
.get();
|
||||
for (int i = 0; i < numDatagrams; i++) {
|
||||
quicChannel.writeAndFlush(Unpooled.copiedBuffer(data)).sync();
|
||||
// Let's add some sleep in between as this is UDP so we may loose some data otherwise.
|
||||
Thread.sleep(50);
|
||||
}
|
||||
assertTrue(serverPromise.await(3000), "Server received: " + serverReadCount.get() +
|
||||
", Client received: " + clientReadCount.get());
|
||||
serverPromise.sync();
|
||||
|
||||
assertTrue(clientPromise.await(3000), "Server received: " + serverReadCount.get() +
|
||||
", Client received: " + clientReadCount.get());
|
||||
ByteBuf buffer = clientPromise.get();
|
||||
ByteBuf expected = Unpooled.wrappedBuffer(data);
|
||||
assertEquals(expected, buffer);
|
||||
buffer.release();
|
||||
expected.release();
|
||||
|
||||
quicChannel.close().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,439 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.AbstractByteBufAllocator;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.buffer.UnpooledDirectByteBuf;
|
||||
import io.netty.buffer.UnpooledHeapByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ImmediateExecutor;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class QuicChannelEchoTest extends AbstractQuicTest {
|
||||
|
||||
private static final Random random = new Random();
|
||||
static final byte[] data = new byte[1048576];
|
||||
|
||||
static {
|
||||
random.nextBytes(data);
|
||||
}
|
||||
|
||||
public static Collection<Object[]> data() {
|
||||
List<Object[]> config = new ArrayList<>();
|
||||
for (int a = 0; a < 2; a++) {
|
||||
for (int b = 0; b < 2; b++) {
|
||||
for (int c = 0; c < 2; c++) {
|
||||
config.add(new Object[] { a == 0, b == 0, c == 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private void setAllocator(Channel channel, ByteBufAllocator allocator) {
|
||||
channel.config().setAllocator(allocator);
|
||||
}
|
||||
|
||||
private ByteBufAllocator getAllocator(boolean directBuffer) {
|
||||
if (directBuffer) {
|
||||
return new UnpooledByteBufAllocator(true);
|
||||
} else {
|
||||
// Force usage of heap buffers and also ensure memoryAddress() is not not supported.
|
||||
return new AbstractByteBufAllocator(false) {
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer() {
|
||||
return heapBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer(int initialCapacity) {
|
||||
return heapBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) {
|
||||
return heapBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
|
||||
return new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
|
||||
return new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectBufferPooled() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name =
|
||||
"{index}: autoRead = {0}, directBuffer = {1}, composite = {2}")
|
||||
@MethodSource("data")
|
||||
public void testEchoStartedFromServer(boolean autoRead, boolean directBuffer, boolean composite) throws Throwable {
|
||||
ByteBufAllocator allocator = getAllocator(directBuffer);
|
||||
final EchoHandler sh = new EchoHandler(true, autoRead, allocator);
|
||||
final EchoHandler ch = new EchoHandler(false, autoRead, allocator);
|
||||
AtomicReference<List<ChannelFuture>> writeFutures = new AtomicReference<>();
|
||||
Channel server = QuicTestUtils.newServer(ImmediateExecutor.INSTANCE, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
setAllocator(ctx.channel(), allocator);
|
||||
((QuicChannel) ctx.channel()).createStream(QuicStreamType.BIDIRECTIONAL, sh)
|
||||
.addListener((Future<QuicStreamChannel> future) -> {
|
||||
QuicStreamChannel stream = future.getNow();
|
||||
setAllocator(stream, allocator);
|
||||
List<ChannelFuture> futures = writeAllData(stream, composite, allocator);
|
||||
writeFutures.set(futures);
|
||||
});
|
||||
|
||||
ctx.channel().config().setAutoRead(autoRead);
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
}, sh);
|
||||
setAllocator(server, allocator);
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(ImmediateExecutor.INSTANCE);
|
||||
QuicChannel quicChannel = null;
|
||||
try {
|
||||
quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
})
|
||||
.streamHandler(ch)
|
||||
// Use the same allocator for the streams.
|
||||
.streamOption(ChannelOption.ALLOCATOR, allocator)
|
||||
.remoteAddress(address)
|
||||
.option(ChannelOption.AUTO_READ, autoRead)
|
||||
.option(ChannelOption.ALLOCATOR, allocator)
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
waitForData(ch, sh);
|
||||
|
||||
for (;;) {
|
||||
List<ChannelFuture> futures = writeFutures.get();
|
||||
if (futures != null) {
|
||||
for (ChannelFuture f: futures) {
|
||||
f.sync();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
waitForData(sh, ch);
|
||||
|
||||
// Close underlying streams.
|
||||
sh.channel.close().sync();
|
||||
ch.channel.close().sync();
|
||||
|
||||
// Close underlying quic channels
|
||||
sh.channel.parent().close().sync();
|
||||
ch.channel.parent().close().sync();
|
||||
|
||||
checkForException(ch, sh);
|
||||
} finally {
|
||||
server.close().sync();
|
||||
QuicTestUtils.closeIfNotNull(quicChannel);
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name =
|
||||
"{index}: autoRead = {0}, directBuffer = {1}, composite = {2}")
|
||||
@MethodSource("data")
|
||||
public void testEchoStartedFromClient(boolean autoRead, boolean directBuffer, boolean composite) throws Throwable {
|
||||
ByteBufAllocator allocator = getAllocator(directBuffer);
|
||||
|
||||
final EchoHandler sh = new EchoHandler(true, autoRead, allocator);
|
||||
final EchoHandler ch = new EchoHandler(false, autoRead, allocator);
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
setAllocator(ctx.channel(), allocator);
|
||||
ctx.channel().config().setAutoRead(autoRead);
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Channel server = QuicTestUtils.newServer(ImmediateExecutor.INSTANCE, serverHandler, sh);
|
||||
setAllocator(server, allocator);
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(ImmediateExecutor.INSTANCE);
|
||||
QuicChannel quicChannel = null;
|
||||
try {
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
};
|
||||
quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(ch)
|
||||
// Use the same allocator for the streams.
|
||||
.streamOption(ChannelOption.ALLOCATOR, allocator)
|
||||
.remoteAddress(address)
|
||||
.option(ChannelOption.AUTO_READ, autoRead)
|
||||
.option(ChannelOption.ALLOCATOR, allocator)
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
QuicStreamChannel stream = quicChannel.createStream(QuicStreamType.BIDIRECTIONAL, ch).sync().getNow();
|
||||
setAllocator(stream, allocator);
|
||||
|
||||
assertEquals(QuicStreamType.BIDIRECTIONAL, stream.type());
|
||||
assertEquals(0, stream.streamId());
|
||||
assertTrue(stream.isLocalCreated());
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ch.counter = 0;
|
||||
sh.counter = 0;
|
||||
List<ChannelFuture> futures = writeAllData(stream, composite, allocator);
|
||||
|
||||
for (ChannelFuture f : futures) {
|
||||
f.sync();
|
||||
}
|
||||
waitForData(ch, sh);
|
||||
waitForData(sh, ch);
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
// Close underlying streams.
|
||||
sh.channel.close().sync();
|
||||
ch.channel.close().sync();
|
||||
|
||||
// Close underlying quic channels
|
||||
sh.channel.parent().close().sync();
|
||||
ch.channel.parent().close().sync();
|
||||
checkForException(ch, sh);
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().syncUninterruptibly();
|
||||
QuicTestUtils.closeIfNotNull(quicChannel);
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
}
|
||||
}
|
||||
|
||||
private List<ChannelFuture> writeAllData(Channel channel, boolean composite, ByteBufAllocator allocator) {
|
||||
if (composite) {
|
||||
CompositeByteBuf compositeByteBuf = allocator.compositeBuffer();
|
||||
for (int i = 0; i < data.length;) {
|
||||
int length = Math.min(random.nextInt(1024 * 64), data.length - i);
|
||||
ByteBuf buf = allocator.buffer().writeBytes(data, i, length);
|
||||
compositeByteBuf.addComponent(true, buf);
|
||||
i += length;
|
||||
}
|
||||
return Collections.singletonList(channel.writeAndFlush(compositeByteBuf));
|
||||
} else {
|
||||
List<ChannelFuture> futures = new ArrayList<>();
|
||||
for (int i = 0; i < data.length;) {
|
||||
int length = Math.min(random.nextInt(1024 * 64), data.length - i);
|
||||
ByteBuf buf = allocator.buffer().writeBytes(data, i, length);
|
||||
futures.add(channel.writeAndFlush(buf));
|
||||
i += length;
|
||||
}
|
||||
return futures;
|
||||
}
|
||||
}
|
||||
|
||||
private static void waitForData(EchoHandler h1, EchoHandler h2) {
|
||||
while (h1.counter < data.length) {
|
||||
if (h2.exception.get() != null) {
|
||||
break;
|
||||
}
|
||||
if (h1.exception.get() != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkForException(EchoHandler h1, EchoHandler h2) throws Throwable {
|
||||
if (h1.exception.get() != null && !(h1.exception.get() instanceof IOException)) {
|
||||
throw h1.exception.get();
|
||||
}
|
||||
if (h2.exception.get() != null && !(h2.exception.get() instanceof IOException)) {
|
||||
throw h2.exception.get();
|
||||
}
|
||||
if (h1.exception.get() != null) {
|
||||
throw h1.exception.get();
|
||||
}
|
||||
if (h2.exception.get() != null) {
|
||||
throw h2.exception.get();
|
||||
}
|
||||
}
|
||||
|
||||
private class EchoHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
private final boolean server;
|
||||
private final boolean autoRead;
|
||||
private final ByteBufAllocator allocator;
|
||||
volatile Channel channel;
|
||||
final AtomicReference<Throwable> exception = new AtomicReference<>();
|
||||
volatile int counter;
|
||||
|
||||
EchoHandler(boolean server, boolean autoRead, ByteBufAllocator allocator) {
|
||||
this.server = server;
|
||||
this.autoRead = autoRead;
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
ctx.channel().config().setAutoRead(autoRead);
|
||||
setAllocator(ctx.channel(), allocator);
|
||||
ctx.fireChannelRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
channel = ctx.channel();
|
||||
QuicStreamChannel channel = (QuicStreamChannel) ctx.channel();
|
||||
assertEquals(QuicStreamType.BIDIRECTIONAL, channel.type());
|
||||
if (channel.isLocalCreated()) {
|
||||
// Server starts with 1, client with 0
|
||||
assertEquals(server ? 1 : 0, channel.streamId());
|
||||
} else {
|
||||
// Server starts with 1, client with 0
|
||||
assertEquals(server ? 0 : 1, channel.streamId());
|
||||
}
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
|
||||
byte[] actual = new byte[in.readableBytes()];
|
||||
in.readBytes(actual);
|
||||
|
||||
int lastIdx = counter;
|
||||
for (int i = 0; i < actual.length; i ++) {
|
||||
assertEquals(data[i + lastIdx], actual[i]);
|
||||
}
|
||||
|
||||
if (!((QuicStreamChannel) ctx.channel()).isLocalCreated()) {
|
||||
channel.write(Unpooled.wrappedBuffer(actual));
|
||||
}
|
||||
|
||||
counter += actual.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
try {
|
||||
ctx.flush();
|
||||
} finally {
|
||||
if (!autoRead) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx,
|
||||
Throwable cause) {
|
||||
if (exception.compareAndSet(null, cause)) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
class QuicChannelValidationHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private volatile Throwable cause;
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
void assertState() throws Throwable {
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.util.concurrent.ImmediateExecutor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class QuicCodecBuilderTest {
|
||||
|
||||
@Test
|
||||
void testCopyConstructor() throws IllegalAccessException {
|
||||
TestQuicCodecBuilder original = new TestQuicCodecBuilder();
|
||||
init(original);
|
||||
TestQuicCodecBuilder copy = new TestQuicCodecBuilder(original);
|
||||
assertThat(copy).usingRecursiveComparison().isEqualTo(original);
|
||||
}
|
||||
|
||||
private static void init(TestQuicCodecBuilder builder) throws IllegalAccessException {
|
||||
Field[] fields = builder.getClass().getSuperclass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
modifyField(builder, field);
|
||||
}
|
||||
}
|
||||
|
||||
private static void modifyField(TestQuicCodecBuilder builder, Field field) throws IllegalAccessException {
|
||||
field.setAccessible(true);
|
||||
Class<?> clazz = field.getType();
|
||||
if (Boolean.class == clazz) {
|
||||
field.set(builder, Boolean.TRUE);
|
||||
} else if (Integer.class == clazz) {
|
||||
field.set(builder, Integer.MIN_VALUE);
|
||||
} else if (Long.class == clazz) {
|
||||
field.set(builder, Long.MIN_VALUE);
|
||||
} else if (QuicCongestionControlAlgorithm.class == clazz) {
|
||||
field.set(builder, QuicCongestionControlAlgorithm.CUBIC);
|
||||
} else if (FlushStrategy.class == clazz) {
|
||||
field.set(builder, FlushStrategy.afterNumBytes(10));
|
||||
} else if (Function.class == clazz) {
|
||||
field.set(builder, Function.identity());
|
||||
} else if (boolean.class == clazz) {
|
||||
field.setBoolean(builder, true);
|
||||
} else if (int.class == clazz) {
|
||||
field.setInt(builder, -1);
|
||||
} else if (byte[].class == clazz) {
|
||||
field.set(builder, new byte[16]);
|
||||
} else if (Executor.class == clazz) {
|
||||
field.set(builder, ImmediateExecutor.INSTANCE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown field type " + clazz);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestQuicCodecBuilder extends QuicCodecBuilder<TestQuicCodecBuilder> {
|
||||
|
||||
TestQuicCodecBuilder() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
TestQuicCodecBuilder(TestQuicCodecBuilder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestQuicCodecBuilder clone() {
|
||||
// no-op
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler build(
|
||||
QuicheConfig config,
|
||||
Function<QuicChannel, ? extends QuicSslEngine> sslContextProvider,
|
||||
Executor sslTaskExecutor,
|
||||
int localConnIdLength,
|
||||
FlushStrategy flushStrategy) {
|
||||
// no-op
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class QuicConnectionAddressTest extends AbstractQuicTest {
|
||||
|
||||
@Test
|
||||
public void testNullByteArray() {
|
||||
assertThrows(NullPointerException.class, () -> new QuicConnectionAddress((byte[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullByteBuffer() {
|
||||
assertThrows(NullPointerException.class, () -> new QuicConnectionAddress((ByteBuffer) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testByteArrayIsCloned() {
|
||||
byte[] bytes = new byte[8];
|
||||
ThreadLocalRandom.current().nextBytes(bytes);
|
||||
QuicConnectionAddress address = new QuicConnectionAddress(bytes);
|
||||
assertEquals(ByteBuffer.wrap(bytes), address.connId);
|
||||
ThreadLocalRandom.current().nextBytes(bytes);
|
||||
assertNotEquals(ByteBuffer.wrap(bytes), address.connId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tesByteBufferIsDuplicated() {
|
||||
byte[] bytes = new byte[8];
|
||||
ThreadLocalRandom.current().nextBytes(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
QuicConnectionAddress address = new QuicConnectionAddress(bytes);
|
||||
assertEquals(buffer, address.connId);
|
||||
buffer.position(1);
|
||||
assertNotEquals(buffer, address.connId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class QuicConnectionIdGeneratorTest extends AbstractQuicTest {
|
||||
|
||||
@Test
|
||||
public void testRandomness() {
|
||||
QuicConnectionIdGenerator idGenerator = QuicConnectionIdGenerator.randomGenerator();
|
||||
ByteBuffer id = idGenerator.newId(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
ByteBuffer id2 = idGenerator.newId(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
assertThat(id.remaining(), greaterThan(0));
|
||||
assertThat(id2.remaining(), greaterThan(0));
|
||||
assertNotEquals(id, id2);
|
||||
|
||||
id = idGenerator.newId(10);
|
||||
id2 = idGenerator.newId(10);
|
||||
assertEquals(10, id.remaining());
|
||||
assertEquals(10, id2.remaining());
|
||||
assertNotEquals(id, id2);
|
||||
|
||||
byte[] input = new byte[1024];
|
||||
ThreadLocalRandom.current().nextBytes(input);
|
||||
id = idGenerator.newId(ByteBuffer.wrap(input), 10);
|
||||
id2 = idGenerator.newId(ByteBuffer.wrap(input), 10);
|
||||
assertEquals(10, id.remaining());
|
||||
assertEquals(10, id2.remaining());
|
||||
assertNotEquals(id, id2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowsIfInputTooBig() {
|
||||
QuicConnectionIdGenerator idGenerator = QuicConnectionIdGenerator.randomGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () -> idGenerator.newId(Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowsIfInputTooBig2() {
|
||||
QuicConnectionIdGenerator idGenerator = QuicConnectionIdGenerator.randomGenerator();
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
idGenerator.newId(ByteBuffer.wrap(new byte[8]), Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignIdGenerator() {
|
||||
QuicConnectionIdGenerator idGenerator = QuicConnectionIdGenerator.signGenerator();
|
||||
|
||||
byte[] input = new byte[1024];
|
||||
byte[] input2 = new byte[1024];
|
||||
ThreadLocalRandom.current().nextBytes(input);
|
||||
ThreadLocalRandom.current().nextBytes(input2);
|
||||
ByteBuffer id = idGenerator.newId(ByteBuffer.wrap(input), 10);
|
||||
ByteBuffer id2 = idGenerator.newId(ByteBuffer.wrap(input), 10);
|
||||
ByteBuffer id3 = idGenerator.newId(ByteBuffer.wrap(input2), 10);
|
||||
assertEquals(10, id.remaining());
|
||||
assertEquals(10, id2.remaining());
|
||||
assertEquals(10, id3.remaining());
|
||||
assertEquals(id, id2);
|
||||
assertNotEquals(id, id3);
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> idGenerator.newId(10));
|
||||
assertThrows(NullPointerException.class, () -> idGenerator.newId(null, 10));
|
||||
assertThrows(IllegalArgumentException.class, () -> idGenerator.newId(ByteBuffer.wrap(new byte[0]), 10));
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
idGenerator.newId(ByteBuffer.wrap(input), Integer.MAX_VALUE));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
|
||||
public class QuicConnectionStatsTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testStatsAreCollected(Executor executor) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
Promise<QuicConnectionStats> serverActiveStats = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
Promise<QuicConnectionStats> serverInactiveStats = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
collectStats(ctx, serverActiveStats);
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
collectStats(ctx, serverInactiveStats);
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
private void collectStats(ChannelHandlerContext ctx, Promise<QuicConnectionStats> promise) {
|
||||
QuicheQuicChannel channel = (QuicheQuicChannel) ctx.channel();
|
||||
channel.collectStats(promise);
|
||||
}
|
||||
};
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
try {
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
counter.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
// Let's just echo back the message.
|
||||
ctx.writeAndFlush(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect().get();
|
||||
assertNotNull(quicChannel.collectStats().sync().getNow());
|
||||
quicChannel.createStream(QuicStreamType.BIDIRECTIONAL, new ChannelInboundHandlerAdapter() {
|
||||
private final int bufferSize = 8;
|
||||
private int received;
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(bufferSize));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
received += buffer.readableBytes();
|
||||
buffer.release();
|
||||
if (received == bufferSize) {
|
||||
ctx.close().addListener((ChannelFuture future) -> {
|
||||
// Close the underlying QuicChannel as well.
|
||||
future.channel().parent().close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}).sync();
|
||||
|
||||
// Wait until closure
|
||||
quicChannel.closeFuture().sync();
|
||||
assertStats(quicChannel.collectStats().sync().getNow());
|
||||
assertNotNull(serverActiveStats.sync().getNow());
|
||||
assertStats(serverInactiveStats.sync().getNow());
|
||||
assertEquals(1, counter.get());
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertStats(QuicConnectionStats stats) {
|
||||
assertNotNull(stats);
|
||||
assertThat(stats.lost(), greaterThanOrEqualTo(0L));
|
||||
assertThat(stats.recv(), greaterThan(0L));
|
||||
assertThat(stats.sent(), greaterThan(0L));
|
||||
assertThat(stats.sentBytes(), greaterThan(0L));
|
||||
assertThat(stats.recvBytes(), greaterThan(0L));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class QuicPacketTypeTest extends AbstractQuicTest {
|
||||
|
||||
@Test
|
||||
public void testOfValidType() {
|
||||
for (QuicPacketType type: QuicPacketType.values()) {
|
||||
assertEquals(type, QuicPacketType.of(type.type));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfInvalidType() {
|
||||
assertThrows(IllegalArgumentException.class, () -> QuicPacketType.of((byte) -1));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class QuicReadableTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCorrectlyHandleReadableStreams(Executor executor) throws Throwable {
|
||||
int numOfStreams = 256;
|
||||
int readStreams = numOfStreams / 2;
|
||||
// We do write longs.
|
||||
int expectedDataRead = readStreams * Long.BYTES;
|
||||
final CountDownLatch latch = new CountDownLatch(numOfStreams);
|
||||
final AtomicInteger bytesRead = new AtomicInteger();
|
||||
final AtomicReference<Throwable> serverErrorRef = new AtomicReference<>();
|
||||
final AtomicReference<Throwable> clientErrorRef = new AtomicReference<>();
|
||||
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
Channel server = QuicTestUtils.newServer(
|
||||
QuicTestUtils.newQuicServerBuilder(executor).initialMaxStreamsBidirectional(5000),
|
||||
InsecureQuicTokenHandler.INSTANCE,
|
||||
serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
private int counter;
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
// Ensure we dont read from the streams so all of these will be reported as readable
|
||||
ctx.channel().config().setAutoRead(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
counter++;
|
||||
latch.countDown();
|
||||
if (counter > readStreams) {
|
||||
// Now set it to readable again for some channels
|
||||
ctx.channel().config().setAutoRead(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
bytesRead.addAndGet(buffer.readableBytes());
|
||||
buffer.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
serverErrorRef.set(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
Channel channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
ByteBuf data = Unpooled.directBuffer().writeLong(8);
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
List<Channel> streams = new ArrayList<>();
|
||||
for (int i = 0; i < numOfStreams; i++) {
|
||||
QuicStreamChannel stream = quicChannel.createStream(
|
||||
QuicStreamType.BIDIRECTIONAL, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
clientErrorRef.set(cause);
|
||||
}
|
||||
}).get();
|
||||
streams.add(stream.writeAndFlush(data.retainedSlice()).sync().channel());
|
||||
}
|
||||
latch.await();
|
||||
while (bytesRead.get() < expectedDataRead) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
for (Channel stream: streams) {
|
||||
stream.close().sync();
|
||||
}
|
||||
quicChannel.close().sync();
|
||||
|
||||
throwIfNotNull(serverErrorRef);
|
||||
throwIfNotNull(clientErrorRef);
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
data.release();
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwIfNotNull(AtomicReference<Throwable> errorRef) throws Throwable {
|
||||
Throwable cause = errorRef.get();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class QuicSslContextTest {
|
||||
|
||||
@Test
|
||||
public void testSessionContextSettingsForClient() {
|
||||
testSessionContextSettings(QuicSslContextBuilder.forClient(), 20, 50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionContextSettingsForServer() {
|
||||
testSessionContextSettings(QuicSslContextBuilder.forServer(new X509ExtendedKeyManager() {
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
return EmptyArrays.EMPTY_STRINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
return EmptyArrays.EMPTY_STRINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return null;
|
||||
}
|
||||
}, null), 20, 50);
|
||||
}
|
||||
|
||||
private void testSessionContextSettings(QuicSslContextBuilder builder, int size, int timeout) {
|
||||
SslContext context = builder.sessionCacheSize(size).sessionTimeout(timeout).build();
|
||||
assertEquals(size, context.sessionCacheSize());
|
||||
assertEquals(timeout, context.sessionTimeout());
|
||||
SSLSessionContext sessionContext = context.sessionContext();
|
||||
assertEquals(size, sessionContext.getSessionCacheSize());
|
||||
assertEquals(timeout, sessionContext.getSessionTimeout());
|
||||
|
||||
int newSize = size / 2;
|
||||
sessionContext.setSessionCacheSize(newSize);
|
||||
assertEquals(newSize, context.sessionCacheSize());
|
||||
|
||||
int newTimeout = timeout / 2;
|
||||
sessionContext.setSessionTimeout(newTimeout);
|
||||
assertEquals(newTimeout, sessionContext.getSessionTimeout());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
||||
import io.netty.channel.socket.ChannelOutputShutdownException;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
|
||||
public class QuicStreamChannelCloseTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseFromServerWhileInActiveUnidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromServerWhileInActive(executor, QuicStreamType.UNIDIRECTIONAL, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseFromServerWhileInActiveBidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromServerWhileInActive(executor, QuicStreamType.BIDIRECTIONAL, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testHalfCloseFromServerWhileInActiveUnidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromServerWhileInActive(executor, QuicStreamType.UNIDIRECTIONAL, true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testHalfCloseFromServerWhileInActiveBidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromServerWhileInActive(executor, QuicStreamType.BIDIRECTIONAL, true);
|
||||
}
|
||||
|
||||
private static void testCloseFromServerWhileInActive(Executor executor, QuicStreamType type,
|
||||
boolean halfClose) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
try {
|
||||
final Promise<Channel> streamPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
QuicChannelValidationHandler serverHandler = new StreamCreationHandler(type, halfClose, streamPromise);
|
||||
server = QuicTestUtils.newServer(executor, serverHandler,
|
||||
new ChannelInboundHandlerAdapter());
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new StreamHandler())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
Channel streamChannel = streamPromise.get();
|
||||
|
||||
// Wait for the steam to close. It needs to happen before the 5-second connection idle timeout.
|
||||
streamChannel.closeFuture().get(3000, TimeUnit.MILLISECONDS);
|
||||
|
||||
streamChannel.parent().close();
|
||||
|
||||
// Wait till the client was closed
|
||||
quicChannel.closeFuture().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseFromClientWhileInActiveUnidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromClientWhileInActive(executor, QuicStreamType.UNIDIRECTIONAL, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseFromClientWhileInActiveBidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromClientWhileInActive(executor, QuicStreamType.BIDIRECTIONAL, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testHalfCloseFromClientWhileInActiveUnidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromClientWhileInActive(executor, QuicStreamType.UNIDIRECTIONAL, true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testHalfCloseFromClientWhileInActiveBidirectional(Executor executor) throws Throwable {
|
||||
testCloseFromClientWhileInActive(executor, QuicStreamType.BIDIRECTIONAL, true);
|
||||
}
|
||||
|
||||
private static void testCloseFromClientWhileInActive(Executor executor, QuicStreamType type,
|
||||
boolean halfClose) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
try {
|
||||
final Promise<Channel> streamPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, new StreamHandler());
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
|
||||
StreamCreationHandler creationHandler = new StreamCreationHandler(type, halfClose, streamPromise);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(creationHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
Channel streamChannel = streamPromise.get();
|
||||
|
||||
// Wait for the steam to close. It needs to happen before the 5-second connection idle timeout.
|
||||
streamChannel.closeFuture().get(3000, TimeUnit.MILLISECONDS);
|
||||
|
||||
streamChannel.parent().close();
|
||||
|
||||
// Wait till the client was closed
|
||||
quicChannel.closeFuture().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
creationHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testWriteToUnidirectionalAfterShutdownOutput(Executor executor) throws Throwable {
|
||||
testWriteAfterClosedOrShutdown(executor, QuicStreamType.UNIDIRECTIONAL, true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testWriteToBidirectionalAfterShutdownOutput(Executor executor) throws Throwable {
|
||||
testWriteAfterClosedOrShutdown(executor, QuicStreamType.BIDIRECTIONAL, true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testWriteToUnidirectionalAfterClose(Executor executor) throws Throwable {
|
||||
testWriteAfterClosedOrShutdown(executor, QuicStreamType.UNIDIRECTIONAL, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testWriteToBidirectionalAfterClose(Executor executor) throws Throwable {
|
||||
testWriteAfterClosedOrShutdown(executor, QuicStreamType.BIDIRECTIONAL, false);
|
||||
}
|
||||
|
||||
private static void testWriteAfterClosedOrShutdown(Executor executor, QuicStreamType type,
|
||||
boolean halfClose) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
try {
|
||||
final Promise<Channel> streamPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
server = QuicTestUtils.newServer(executor, new ChannelInboundHandlerAdapter(), new StreamHandler());
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
|
||||
StreamCreationAndTearDownHandler creationHandler =
|
||||
new StreamCreationAndTearDownHandler(type, halfClose, streamPromise);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(creationHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
// ChannelOutputShutdownException should only be used when its a BIDIRECTIONAL channel and half-closure
|
||||
// is used.
|
||||
Class<? extends Throwable> causeClass =
|
||||
halfClose && type != QuicStreamType.UNIDIRECTIONAL ?
|
||||
ChannelOutputShutdownException.class : ClosedChannelException.class;
|
||||
assertInstanceOf(causeClass, streamPromise.await().cause());
|
||||
quicChannel.close().sync();
|
||||
|
||||
// Wait till the client was closed
|
||||
quicChannel.closeFuture().sync();
|
||||
creationHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamCreationAndTearDownHandler extends QuicChannelValidationHandler {
|
||||
private final QuicStreamType type;
|
||||
private final boolean halfClose;
|
||||
private final Promise<Channel> streamPromise;
|
||||
|
||||
StreamCreationAndTearDownHandler(QuicStreamType type, boolean halfClose, Promise<Channel> streamPromise) {
|
||||
this.type = type;
|
||||
this.halfClose = halfClose;
|
||||
this.streamPromise = streamPromise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicChannel channel = (QuicChannel) ctx.channel();
|
||||
channel.createStream(type, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
final ChannelFuture future;
|
||||
if (halfClose) {
|
||||
future = ((QuicStreamChannel) ctx.channel()).shutdownOutput();
|
||||
} else {
|
||||
future = ctx.channel().close();
|
||||
}
|
||||
future.addListener(f -> {
|
||||
ctx.channel().writeAndFlush("Unsupported message").addListener(wf -> {
|
||||
streamPromise.setFailure(wf.cause());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamCreationHandler extends QuicChannelValidationHandler {
|
||||
private final QuicStreamType type;
|
||||
private final boolean halfClose;
|
||||
private final Promise<Channel> streamPromise;
|
||||
|
||||
StreamCreationHandler(QuicStreamType type, boolean halfClose, Promise<Channel> streamPromise) {
|
||||
this.type = type;
|
||||
this.halfClose = halfClose;
|
||||
this.streamPromise = streamPromise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicChannel channel = (QuicChannel) ctx.channel();
|
||||
channel.createStream(type, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
streamPromise.trySuccess(ctx.channel());
|
||||
// Do the write and close the channel
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(8))
|
||||
.addListener(halfClose
|
||||
? QuicStreamChannel.SHUTDOWN_OUTPUT
|
||||
: ChannelFutureListener.CLOSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
|
||||
// Received a FIN
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class QuicStreamChannelCreationTest extends AbstractQuicTest {
|
||||
|
||||
private static final AttributeKey<String> ATTRIBUTE_KEY = AttributeKey.newInstance("testKey");
|
||||
private static final String ATTRIBUTE_VALUE = "Test";
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCreateStream(Executor executor) throws Throwable {
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
Channel server = QuicTestUtils.newServer(executor, serverHandler,
|
||||
new ChannelInboundHandlerAdapter());
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(address)
|
||||
.connect()
|
||||
.get();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
QuicStreamChannel stream = quicChannel.createStream(QuicStreamType.UNIDIRECTIONAL,
|
||||
new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
assertQuicStreamChannel((QuicStreamChannel) ctx.channel(),
|
||||
QuicStreamType.UNIDIRECTIONAL, Boolean.TRUE, null);
|
||||
latch.countDown();
|
||||
}
|
||||
}).sync().get();
|
||||
assertQuicStreamChannel(stream, QuicStreamType.UNIDIRECTIONAL, Boolean.TRUE, null);
|
||||
latch.await();
|
||||
stream.close().sync();
|
||||
quicChannel.close().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCreateStreamViaBootstrap(Executor executor) throws Throwable {
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
Channel server = QuicTestUtils.newServer(executor, serverHandler,
|
||||
new ChannelInboundHandlerAdapter());
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(address)
|
||||
.connect()
|
||||
.get();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
QuicStreamChannel stream = quicChannel.newStreamBootstrap()
|
||||
.type(QuicStreamType.UNIDIRECTIONAL)
|
||||
.attr(ATTRIBUTE_KEY, ATTRIBUTE_VALUE)
|
||||
.option(ChannelOption.AUTO_READ, Boolean.FALSE)
|
||||
.handler(new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
assertQuicStreamChannel((QuicStreamChannel) ctx.channel(),
|
||||
QuicStreamType.UNIDIRECTIONAL, Boolean.FALSE, ATTRIBUTE_VALUE);
|
||||
latch.countDown();
|
||||
}
|
||||
}).create().sync().get();
|
||||
assertQuicStreamChannel(stream, QuicStreamType.UNIDIRECTIONAL, Boolean.FALSE, ATTRIBUTE_VALUE);
|
||||
latch.await();
|
||||
stream.close().sync();
|
||||
quicChannel.close().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().syncUninterruptibly();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertQuicStreamChannel(QuicStreamChannel channel, QuicStreamType expectedType,
|
||||
Boolean expectedAutoRead, String expectedAttribute) {
|
||||
assertEquals(expectedType, channel.type());
|
||||
assertEquals(expectedAutoRead, channel.config().getOption(ChannelOption.AUTO_READ));
|
||||
assertEquals(expectedAttribute, channel.attr(ATTRIBUTE_KEY).get());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
public class QuicStreamFrameTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseHalfClosureUnidirectional(Executor executor) throws Throwable {
|
||||
testCloseHalfClosure(executor, QuicStreamType.UNIDIRECTIONAL);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseHalfClosureBidirectional(Executor executor) throws Throwable {
|
||||
testCloseHalfClosure(executor, QuicStreamType.BIDIRECTIONAL);
|
||||
}
|
||||
|
||||
private static void testCloseHalfClosure(Executor executor, QuicStreamType type) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
QuicChannelValidationHandler clientHandler = new StreamCreationHandler(type);
|
||||
try {
|
||||
StreamHandler handler = new StreamHandler();
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, handler);
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
handler.assertSequence();
|
||||
quicChannel.closeFuture().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamCreationHandler extends QuicChannelValidationHandler {
|
||||
private final QuicStreamType type;
|
||||
|
||||
StreamCreationHandler(QuicStreamType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicChannel channel = (QuicChannel) ctx.channel();
|
||||
channel.createStream(type, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
// Do the write and close the channel
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(8))
|
||||
.addListener(QuicStreamChannel.SHUTDOWN_OUTPUT);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamHandler extends ChannelInboundHandlerAdapter {
|
||||
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
ctx.channel().config().setOption(QuicChannelOption.READ_FRAMES, true);
|
||||
queue.add(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
queue.add(3);
|
||||
// Close the QUIC channel as well.
|
||||
ctx.channel().parent().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
|
||||
queue.add(2);
|
||||
if (((QuicStreamChannel) ctx.channel()).type() == QuicStreamType.BIDIRECTIONAL) {
|
||||
// Let's write back a fin which will also close the channel and so call channelInactive(...)
|
||||
ctx.writeAndFlush(new DefaultQuicStreamFrame(Unpooled.EMPTY_BUFFER, true));
|
||||
}
|
||||
ctx.channel().parent().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
QuicStreamFrame frame = (QuicStreamFrame) msg;
|
||||
if (frame.hasFin()) {
|
||||
queue.add(1);
|
||||
}
|
||||
frame.release();
|
||||
}
|
||||
|
||||
void assertSequence() throws Exception {
|
||||
assertEquals(0, (int) queue.take());
|
||||
assertEquals(1, (int) queue.take());
|
||||
assertEquals(2, (int) queue.take());
|
||||
assertEquals(3, (int) queue.take());
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.socket.ChannelInputShutdownEvent;
|
||||
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class QuicStreamHalfClosureTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseHalfClosureUnidirectional(Executor executor) throws Throwable {
|
||||
testCloseHalfClosure(executor, QuicStreamType.UNIDIRECTIONAL);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCloseHalfClosureBidirectional(Executor executor) throws Throwable {
|
||||
testCloseHalfClosure(executor, QuicStreamType.BIDIRECTIONAL);
|
||||
}
|
||||
|
||||
private static void testCloseHalfClosure(Executor executor, QuicStreamType type) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
QuicChannelValidationHandler clientHandler = new StreamCreationHandler(type);
|
||||
try {
|
||||
StreamHandler handler = new StreamHandler();
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, handler);
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
handler.assertSequence();
|
||||
quicChannel.closeFuture().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamCreationHandler extends QuicChannelValidationHandler {
|
||||
private final QuicStreamType type;
|
||||
|
||||
StreamCreationHandler(QuicStreamType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicChannel channel = (QuicChannel) ctx.channel();
|
||||
channel.createStream(type, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
// Do the write and close the channel
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(8))
|
||||
.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamHandler extends ChannelInboundHandlerAdapter {
|
||||
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
queue.add(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
queue.add(5);
|
||||
// Close the QUIC channel as well.
|
||||
ctx.channel().parent().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
if (((QuicStreamChannel) ctx.channel()).isInputShutdown()) {
|
||||
queue.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (evt == ChannelInputShutdownEvent.INSTANCE) {
|
||||
addIsShutdown(ctx);
|
||||
queue.add(3);
|
||||
} else if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
|
||||
queue.add(4);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void addIsShutdown(ChannelHandlerContext ctx) {
|
||||
if (((QuicStreamChannel) ctx.channel()).isInputShutdown()) {
|
||||
queue.add(2);
|
||||
}
|
||||
}
|
||||
|
||||
void assertSequence() throws Exception {
|
||||
assertEquals(0, (int) queue.take());
|
||||
int value = queue.take();
|
||||
if (value == 1) {
|
||||
// If we did see the value of 1 it should be followed by 2 directly.
|
||||
assertEquals(2, (int) queue.take());
|
||||
} else {
|
||||
assertEquals(2, value);
|
||||
}
|
||||
assertEquals(3, (int) queue.take());
|
||||
assertEquals(4, (int) queue.take());
|
||||
assertEquals(5, (int) queue.take());
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class QuicStreamIdGeneratorTest extends AbstractQuicTest {
|
||||
|
||||
@Test
|
||||
public void testServerStreamIds() {
|
||||
QuicStreamIdGenerator generator = new QuicStreamIdGenerator(true);
|
||||
assertEquals(1, generator.nextStreamId(true));
|
||||
assertEquals(5, generator.nextStreamId(true));
|
||||
assertEquals(3, generator.nextStreamId(false));
|
||||
assertEquals(9, generator.nextStreamId(true));
|
||||
assertEquals(7, generator.nextStreamId(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientStreamIds() {
|
||||
QuicStreamIdGenerator generator = new QuicStreamIdGenerator(false);
|
||||
assertEquals(0, generator.nextStreamId(true));
|
||||
assertEquals(4, generator.nextStreamId(true));
|
||||
assertEquals(2, generator.nextStreamId(false));
|
||||
assertEquals(8, generator.nextStreamId(true));
|
||||
assertEquals(6, generator.nextStreamId(false));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class QuicStreamLimitTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testStreamLimitEnforcedWhenCreatingViaClientBidirectional(Executor executor) throws Throwable {
|
||||
testStreamLimitEnforcedWhenCreatingViaClient(executor, QuicStreamType.BIDIRECTIONAL);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testStreamLimitEnforcedWhenCreatingViaClientUnidirectional(Executor executor) throws Throwable {
|
||||
testStreamLimitEnforcedWhenCreatingViaClient(executor, QuicStreamType.UNIDIRECTIONAL);
|
||||
}
|
||||
|
||||
private static void testStreamLimitEnforcedWhenCreatingViaClient(Executor executor, QuicStreamType type) throws Throwable {
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
Channel server = QuicTestUtils.newServer(
|
||||
QuicTestUtils.newQuicServerBuilder(executor).initialMaxStreamsBidirectional(1)
|
||||
.initialMaxStreamsUnidirectional(1),
|
||||
InsecureQuicTokenHandler.INSTANCE,
|
||||
serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(executor);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch2 = new CountDownLatch(1);
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof QuicStreamLimitChangedEvent) {
|
||||
if (latch.getCount() == 0) {
|
||||
latch2.countDown();
|
||||
} else {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
super.userEventTriggered(ctx, evt);
|
||||
}
|
||||
};
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(address)
|
||||
.connect().get();
|
||||
latch.await();
|
||||
assertEquals(1, quicChannel.peerAllowedStreams(QuicStreamType.UNIDIRECTIONAL));
|
||||
assertEquals(1, quicChannel.peerAllowedStreams(QuicStreamType.BIDIRECTIONAL));
|
||||
QuicStreamChannel stream = quicChannel.createStream(
|
||||
type, new ChannelInboundHandlerAdapter()).get();
|
||||
|
||||
assertEquals(0, quicChannel.peerAllowedStreams(type));
|
||||
|
||||
// Second stream creation should fail.
|
||||
Throwable cause = quicChannel.createStream(
|
||||
type, new ChannelInboundHandlerAdapter()).await().cause();
|
||||
assertThat(cause, CoreMatchers.instanceOf(IOException.class));
|
||||
stream.close().sync();
|
||||
latch2.await();
|
||||
|
||||
assertEquals(1, quicChannel.peerAllowedStreams(type));
|
||||
quicChannel.close().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testStreamLimitEnforcedWhenCreatingViaServerBidirectional(Executor executor) throws Throwable {
|
||||
testStreamLimitEnforcedWhenCreatingViaServer(executor, QuicStreamType.BIDIRECTIONAL);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testStreamLimitEnforcedWhenCreatingViaServerUnidirectional(Executor executor) throws Throwable {
|
||||
testStreamLimitEnforcedWhenCreatingViaServer(executor, QuicStreamType.UNIDIRECTIONAL);
|
||||
}
|
||||
|
||||
private static void testStreamLimitEnforcedWhenCreatingViaServer(Executor executor, QuicStreamType type) throws Throwable {
|
||||
Promise<Void> streamPromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
Promise<Throwable> stream2Promise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicChannel channel = (QuicChannel) ctx.channel();
|
||||
channel.createStream(type, new ChannelInboundHandlerAdapter())
|
||||
.addListener((Future<QuicStreamChannel> future) -> {
|
||||
if (future.isSuccess()) {
|
||||
QuicStreamChannel stream = future.getNow();
|
||||
streamPromise.setSuccess(null);
|
||||
channel.createStream(type, new ChannelInboundHandlerAdapter())
|
||||
.addListener((Future<QuicStreamChannel> f) -> {
|
||||
stream.close();
|
||||
stream2Promise.setSuccess(f.cause());
|
||||
});
|
||||
} else {
|
||||
streamPromise.setFailure(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
Channel server = QuicTestUtils.newServer(
|
||||
QuicTestUtils.newQuicServerBuilder(executor),
|
||||
InsecureQuicTokenHandler.INSTANCE,
|
||||
serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder(executor)
|
||||
.initialMaxStreamsBidirectional(1).initialMaxStreamsUnidirectional(1));
|
||||
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(address)
|
||||
.connect().get();
|
||||
streamPromise.sync();
|
||||
// Second stream creation should fail.
|
||||
assertThat(stream2Promise.get(), CoreMatchers.instanceOf(IOException.class));
|
||||
quicChannel.close().sync();
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class QuicStreamShutdownTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testShutdownInputClosureCausesStreamStopped(Executor executor) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
try {
|
||||
server = QuicTestUtils.newServer(executor, new ChannelInboundHandlerAdapter(), new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ChannelFutureListener futureListener = new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture channelFuture) {
|
||||
Throwable cause = channelFuture.cause();
|
||||
if (cause instanceof QuicException &&
|
||||
((QuicException) cause).error() == QuicError.STREAM_STOPPED) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
ctx.write(buffer.retainedDuplicate()).addListener(futureListener);
|
||||
ctx.writeAndFlush(buffer).addListener(futureListener);
|
||||
}
|
||||
});
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(new ChannelInboundHandlerAdapter())
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
QuicStreamChannel streamChannel = quicChannel.createStream(QuicStreamType.BIDIRECTIONAL,
|
||||
new ChannelInboundHandlerAdapter()).sync().getNow();
|
||||
streamChannel.shutdownInput().sync();
|
||||
assertTrue(streamChannel.isInputShutdown());
|
||||
streamChannel.writeAndFlush(Unpooled.buffer().writeLong(8)).sync();
|
||||
|
||||
latch.await();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.concurrent.PromiseNotifier;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
public class QuicStreamTypeTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testUnidirectionalCreatedByClient(Executor executor) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
|
||||
try {
|
||||
Promise<Throwable> serverWritePromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicStreamChannel channel = (QuicStreamChannel) ctx.channel();
|
||||
assertEquals(QuicStreamType.UNIDIRECTIONAL, channel.type());
|
||||
assertFalse(channel.isLocalCreated());
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(8))
|
||||
.addListener(future -> serverWritePromise.setSuccess(future.cause()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
});
|
||||
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.sync()
|
||||
.get();
|
||||
QuicStreamChannel streamChannel = quicChannel.createStream(
|
||||
QuicStreamType.UNIDIRECTIONAL, new ChannelInboundHandlerAdapter()).get();
|
||||
// Do the write which should succeed
|
||||
streamChannel.writeAndFlush(Unpooled.buffer().writeZero(8)).sync();
|
||||
|
||||
// Close stream and quic channel
|
||||
streamChannel.close().sync();
|
||||
quicChannel.close().sync();
|
||||
assertThat(serverWritePromise.get(), instanceOf(UnsupportedOperationException.class));
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testUnidirectionalCreatedByServer(Executor executor) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
Promise<Void> serverWritePromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
Promise<Throwable> clientWritePromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicChannel channel = (QuicChannel) ctx.channel();
|
||||
channel.createStream(QuicStreamType.UNIDIRECTIONAL, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
// Do the write which should succeed
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(8))
|
||||
.addListener(new PromiseNotifier<>(serverWritePromise));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
try {
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, new ChannelInboundHandlerAdapter());
|
||||
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
// Do the write should fail
|
||||
ctx.writeAndFlush(Unpooled.buffer().writeZero(8))
|
||||
.addListener(future -> clientWritePromise.setSuccess(future.cause()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
// Close the QUIC channel as well.
|
||||
ctx.channel().parent().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
// Let's close the stream
|
||||
ctx.close();
|
||||
}
|
||||
})
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect()
|
||||
.get();
|
||||
|
||||
quicChannel.closeFuture().sync();
|
||||
assertTrue(serverWritePromise.await().isSuccess());
|
||||
assertThat(clientWritePromise.get(), instanceOf(UnsupportedOperationException.class));
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class QuicTest extends AbstractQuicTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Quic.ensureAvailability();
|
||||
assertNotNull(Quiche.quiche_version());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionSupported() {
|
||||
// Only v1 should be supported.
|
||||
assertFalse(Quic.isVersionSupported(0xff00_001c));
|
||||
assertFalse(Quic.isVersionSupported(0xff00_001d));
|
||||
assertFalse(Quic.isVersionSupported(0xff00_001c));
|
||||
assertTrue(Quic.isVersionSupported(0x0000_0001));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToAttributesArrayDoesCopy() {
|
||||
AttributeKey<String> key = AttributeKey.valueOf(UUID.randomUUID().toString());
|
||||
String value = "testValue";
|
||||
Map<AttributeKey<?>, Object> attributes = new HashMap<>();
|
||||
attributes.put(key, value);
|
||||
Map.Entry<AttributeKey<?>, Object>[] array = Quic.toAttributesArray(attributes);
|
||||
assertEquals(1, array.length);
|
||||
attributes.put(key, "newTestValue");
|
||||
Map.Entry<AttributeKey<?>, Object> entry = array[0];
|
||||
assertEquals(key, entry.getKey());
|
||||
assertEquals(value, entry.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToOptionsArrayDoesCopy() {
|
||||
Map<ChannelOption<?>, Object> options = new HashMap<>();
|
||||
options.put(ChannelOption.AUTO_READ, true);
|
||||
Map.Entry<ChannelOption<?>, Object>[] array = Quic.toOptionsArray(options);
|
||||
assertEquals(1, array.length);
|
||||
options.put(ChannelOption.AUTO_READ, false);
|
||||
Map.Entry<ChannelOption<?>, Object> entry = array[0];
|
||||
assertEquals(ChannelOption.AUTO_READ, entry.getKey());
|
||||
assertEquals(true, entry.getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.FixedRecvByteBufAllocator;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollChannelOption;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.handler.ssl.bouncycastle.SelfSignedCertificate;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import io.netty.util.NetUtil;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
final class QuicTestUtils {
|
||||
static final String[] PROTOS = new String[]{"hq-29"};
|
||||
static final SelfSignedCertificate SELF_SIGNED_CERTIFICATE;
|
||||
|
||||
private static final int DATAGRAM_SIZE = 2048;
|
||||
|
||||
static {
|
||||
SelfSignedCertificate cert;
|
||||
try {
|
||||
cert = new SelfSignedCertificate();
|
||||
} catch (Exception e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
SELF_SIGNED_CERTIFICATE = cert;
|
||||
}
|
||||
|
||||
private QuicTestUtils() {
|
||||
}
|
||||
|
||||
private static final EventLoopGroup GROUP = Epoll.isAvailable() ? new EpollEventLoopGroup() :
|
||||
new NioEventLoopGroup();
|
||||
|
||||
static {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
GROUP.shutdownGracefully().sync();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Channel newClient(Executor sslTaskExecutor) throws Exception {
|
||||
return newClient(newQuicClientBuilder(sslTaskExecutor));
|
||||
}
|
||||
|
||||
private static Bootstrap newBootstrap() {
|
||||
Bootstrap bs = new Bootstrap();
|
||||
if (GROUP instanceof EpollEventLoopGroup) {
|
||||
bs.channel(EpollDatagramChannel.class)
|
||||
// Use recvmmsg when possible.
|
||||
.option(EpollChannelOption.MAX_DATAGRAM_PAYLOAD_SIZE, DATAGRAM_SIZE)
|
||||
.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(DATAGRAM_SIZE * 8));
|
||||
} else {
|
||||
bs.channel(NioDatagramChannel.class)
|
||||
.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(DATAGRAM_SIZE));
|
||||
}
|
||||
return bs.group(GROUP);
|
||||
}
|
||||
|
||||
static Channel newClient(QuicClientCodecBuilder builder) throws Exception {
|
||||
return newBootstrap()
|
||||
// We don't want any special handling of the channel so just use a dummy handler.
|
||||
.handler(builder.build())
|
||||
.bind(new InetSocketAddress(NetUtil.LOCALHOST4, 0)).sync().channel();
|
||||
}
|
||||
|
||||
static QuicChannelBootstrap newQuicChannelBootstrap(Channel channel) {
|
||||
QuicChannelBootstrap bs = QuicChannel.newBootstrap(channel);
|
||||
if (GROUP instanceof EpollEventLoopGroup) {
|
||||
bs.option(QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR,
|
||||
EpollQuicUtils.newSegmentedAllocator(10));
|
||||
}
|
||||
return bs;
|
||||
}
|
||||
|
||||
static QuicClientCodecBuilder newQuicClientBuilder(Executor sslTaskExecutor) {
|
||||
return newQuicClientBuilder(sslTaskExecutor, QuicSslContextBuilder.forClient()
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE).applicationProtocols(PROTOS).build());
|
||||
}
|
||||
|
||||
static QuicClientCodecBuilder newQuicClientBuilder(Executor sslTaskExecutor, QuicSslContext sslContext) {
|
||||
return new QuicClientCodecBuilder()
|
||||
.sslEngineProvider(q -> sslContext.newEngine(q.alloc()))
|
||||
.maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
|
||||
.initialMaxData(10000000)
|
||||
.initialMaxStreamDataBidirectionalLocal(1000000)
|
||||
.initialMaxStreamDataBidirectionalRemote(1000000)
|
||||
.initialMaxStreamsBidirectional(100)
|
||||
.initialMaxStreamsUnidirectional(100)
|
||||
.initialMaxStreamDataUnidirectional(1000000)
|
||||
.activeMigration(false).sslTaskExecutor(sslTaskExecutor);
|
||||
}
|
||||
|
||||
static QuicServerCodecBuilder newQuicServerBuilder(Executor sslTaskExecutor) {
|
||||
return newQuicServerBuilder(sslTaskExecutor, QuicSslContextBuilder.forServer(
|
||||
SELF_SIGNED_CERTIFICATE.privateKey(), null, SELF_SIGNED_CERTIFICATE.certificate())
|
||||
.applicationProtocols(PROTOS).build());
|
||||
}
|
||||
|
||||
static QuicServerCodecBuilder newQuicServerBuilder(Executor sslTaskExecutor, QuicSslContext context) {
|
||||
QuicServerCodecBuilder builder = new QuicServerCodecBuilder()
|
||||
.sslEngineProvider(q -> context.newEngine(q.alloc()))
|
||||
.maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
|
||||
.initialMaxData(10000000)
|
||||
.initialMaxStreamDataBidirectionalLocal(1000000)
|
||||
.initialMaxStreamDataBidirectionalRemote(1000000)
|
||||
.initialMaxStreamDataUnidirectional(1000000)
|
||||
.initialMaxStreamsBidirectional(100)
|
||||
.initialMaxStreamsUnidirectional(100)
|
||||
.activeMigration(false)
|
||||
.sslTaskExecutor(sslTaskExecutor);
|
||||
if (GROUP instanceof EpollEventLoopGroup) {
|
||||
builder.option(QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR,
|
||||
EpollQuicUtils.newSegmentedAllocator(10));
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static Bootstrap newServerBootstrap(QuicServerCodecBuilder serverBuilder,
|
||||
QuicTokenHandler tokenHandler, ChannelHandler handler,
|
||||
ChannelHandler streamHandler) {
|
||||
serverBuilder.tokenHandler(tokenHandler)
|
||||
.streamHandler(streamHandler);
|
||||
if (handler != null) {
|
||||
serverBuilder.handler(handler);
|
||||
}
|
||||
ChannelHandler codec = serverBuilder.build();
|
||||
return newBootstrap()
|
||||
// We don't want any special handling of the channel so just use a dummy handler.
|
||||
.handler(codec)
|
||||
.localAddress(new InetSocketAddress(NetUtil.LOCALHOST4, 0));
|
||||
}
|
||||
|
||||
static Channel newServer(QuicServerCodecBuilder serverBuilder, QuicTokenHandler tokenHandler,
|
||||
ChannelHandler handler, ChannelHandler streamHandler)
|
||||
throws Exception {
|
||||
return newServerBootstrap(serverBuilder, tokenHandler, handler, streamHandler)
|
||||
.bind().sync().channel();
|
||||
}
|
||||
|
||||
static Channel newServer(Executor sslTaskExecutor, QuicTokenHandler tokenHandler,
|
||||
ChannelHandler handler, ChannelHandler streamHandler)
|
||||
throws Exception {
|
||||
return newServer(newQuicServerBuilder(sslTaskExecutor), tokenHandler, handler, streamHandler);
|
||||
}
|
||||
|
||||
static Channel newServer(Executor sslTaskExecutor, ChannelHandler handler,
|
||||
ChannelHandler streamHandler) throws Exception {
|
||||
return newServer(sslTaskExecutor, InsecureQuicTokenHandler.INSTANCE, handler, streamHandler);
|
||||
}
|
||||
|
||||
static void closeIfNotNull(Channel channel) throws Exception {
|
||||
if (channel != null) {
|
||||
channel.close().sync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
public class QuicTransportParametersTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testParameters(Executor executor) throws Throwable {
|
||||
Channel server = null;
|
||||
Channel channel = null;
|
||||
Promise<QuicTransportParameters> serverParams = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
QuicheQuicChannel channel = (QuicheQuicChannel) ctx.channel();
|
||||
serverParams.setSuccess(channel.peerTransportParameters());
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
};
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
try {
|
||||
server = QuicTestUtils.newServer(executor, serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
channel = QuicTestUtils.newClient(executor);
|
||||
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(server.localAddress())
|
||||
.connect().get();
|
||||
assertTransportParameters(quicChannel.peerTransportParameters());
|
||||
assertTransportParameters(serverParams.sync().getNow());
|
||||
|
||||
quicChannel.close().sync();
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
QuicTestUtils.closeIfNotNull(channel);
|
||||
QuicTestUtils.closeIfNotNull(server);
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertTransportParameters(QuicTransportParameters parameters) {
|
||||
assertNotNull(parameters);
|
||||
assertThat(parameters.maxIdleTimeout(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.maxUdpPayloadSize(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.initialMaxData(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.initialMaxStreamDataBidiLocal(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.initialMaxStreamDataBidiRemote(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.initialMaxStreamDataUni(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.initialMaxStreamsBidi(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.initialMaxStreamsUni(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.ackDelayExponent(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.maxAckDelay(), greaterThanOrEqualTo(1L));
|
||||
assertFalse(parameters.disableActiveMigration());
|
||||
assertThat(parameters.activeConnIdLimit(), greaterThanOrEqualTo(1L));
|
||||
assertThat(parameters.maxDatagramFrameSize(), greaterThanOrEqualTo(0L));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.concurrent.PromiseNotifier;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class QuicWritableTest extends AbstractQuicTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCorrectlyHandleWritabilityReadRequestedInReadComplete(Executor executor) throws Throwable {
|
||||
testCorrectlyHandleWritability(executor, true);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
public void testCorrectlyHandleWritabilityReadRequestedInRead(Executor executor) throws Throwable {
|
||||
testCorrectlyHandleWritability(executor, false);
|
||||
}
|
||||
|
||||
private static void testCorrectlyHandleWritability(Executor executor, boolean readInComplete) throws Throwable {
|
||||
int bufferSize = 64 * 1024;
|
||||
Promise<Void> writePromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
final AtomicReference<Throwable> serverErrorRef = new AtomicReference<>();
|
||||
final AtomicReference<Throwable> clientErrorRef = new AtomicReference<>();
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
Channel server = QuicTestUtils.newServer(
|
||||
QuicTestUtils.newQuicServerBuilder(executor).initialMaxStreamsBidirectional(5000),
|
||||
InsecureQuicTokenHandler.INSTANCE,
|
||||
serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
buffer.release();
|
||||
ctx.writeAndFlush(ctx.alloc().buffer(bufferSize).writeZero(bufferSize))
|
||||
.addListener(new PromiseNotifier<>(writePromise));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
serverErrorRef.set(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder(executor)
|
||||
.initialMaxStreamDataBidirectionalLocal(bufferSize / 4));
|
||||
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(address)
|
||||
.connect()
|
||||
.get();
|
||||
QuicStreamChannel stream = quicChannel.createStream(
|
||||
QuicStreamType.BIDIRECTIONAL, new ChannelInboundHandlerAdapter() {
|
||||
int bytes;
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
ctx.channel().config().setAutoRead(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.writeAndFlush(ctx.alloc().buffer(8).writeLong(8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
if (bytes == 0) {
|
||||
// First read
|
||||
assertFalse(writePromise.isDone());
|
||||
}
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
bytes += buffer.readableBytes();
|
||||
buffer.release();
|
||||
if (bytes == bufferSize) {
|
||||
ctx.close();
|
||||
assertTrue(writePromise.isDone());
|
||||
}
|
||||
|
||||
if (!readInComplete) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
if (readInComplete) {
|
||||
ctx.read();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
clientErrorRef.set(cause);
|
||||
}
|
||||
}).get();
|
||||
assertFalse(writePromise.isDone());
|
||||
|
||||
// Let's trigger the reads. This will ensure we will consume the data and the remote peer
|
||||
// should be notified that it can write more data.
|
||||
stream.read();
|
||||
|
||||
writePromise.sync();
|
||||
stream.closeFuture().sync();
|
||||
quicChannel.close().sync();
|
||||
|
||||
throwIfNotNull(serverErrorRef);
|
||||
throwIfNotNull(clientErrorRef);
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("newSslTaskExecutors")
|
||||
@Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
|
||||
public void testBytesUntilUnwritable(Executor executor) throws Throwable {
|
||||
Promise<Void> writePromise = ImmediateEventExecutor.INSTANCE.newPromise();
|
||||
final AtomicReference<Throwable> serverErrorRef = new AtomicReference<>();
|
||||
final AtomicReference<Throwable> clientErrorRef = new AtomicReference<>();
|
||||
final CountDownLatch writableAgainLatch = new CountDownLatch(1);
|
||||
int firstWriteNumBytes = 8;
|
||||
int maxData = 32 * 1024;
|
||||
final AtomicLong beforeWritableRef = new AtomicLong();
|
||||
QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler();
|
||||
Channel server = QuicTestUtils.newServer(
|
||||
QuicTestUtils.newQuicServerBuilder(executor).initialMaxStreamsBidirectional(5000),
|
||||
InsecureQuicTokenHandler.INSTANCE,
|
||||
serverHandler, new ChannelInboundHandlerAdapter() {
|
||||
|
||||
private int numBytesRead;
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
numBytesRead += buffer.readableBytes();
|
||||
buffer.release();
|
||||
if (numBytesRead == firstWriteNumBytes) {
|
||||
long before = ctx.channel().bytesBeforeUnwritable();
|
||||
beforeWritableRef.set(before);
|
||||
assertTrue(before > 0);
|
||||
|
||||
while (before != 0) {
|
||||
int size = (int) Math.min(before, 1024);
|
||||
ctx.write(ctx.alloc().buffer(size).writeZero(size));
|
||||
long newBefore = ctx.channel().bytesBeforeUnwritable();
|
||||
|
||||
assertEquals(before, newBefore + size);
|
||||
before = newBefore;
|
||||
}
|
||||
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(new PromiseNotifier<>(writePromise));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) {
|
||||
if (ctx.channel().isWritable()) {
|
||||
if (ctx.channel().bytesBeforeUnwritable() > 0) {
|
||||
writableAgainLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
serverErrorRef.set(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
InetSocketAddress address = (InetSocketAddress) server.localAddress();
|
||||
Channel channel = QuicTestUtils.newClient(QuicTestUtils.newQuicClientBuilder(executor)
|
||||
.initialMaxStreamDataBidirectionalLocal(maxData));
|
||||
|
||||
QuicChannelValidationHandler clientHandler = new QuicChannelValidationHandler();
|
||||
try {
|
||||
QuicChannel quicChannel = QuicTestUtils.newQuicChannelBootstrap(channel)
|
||||
.handler(clientHandler)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.remoteAddress(address)
|
||||
.connect()
|
||||
.get();
|
||||
QuicStreamChannel stream = quicChannel.createStream(
|
||||
QuicStreamType.BIDIRECTIONAL, new ChannelInboundHandlerAdapter() {
|
||||
int bytes;
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) {
|
||||
ctx.channel().config().setAutoRead(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.writeAndFlush(ctx.alloc().buffer(firstWriteNumBytes).writeZero(firstWriteNumBytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
bytes += buffer.readableBytes();
|
||||
buffer.release();
|
||||
if (bytes == beforeWritableRef.get()) {
|
||||
assertTrue(writePromise.isDone());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
clientErrorRef.set(cause);
|
||||
}
|
||||
}).get();
|
||||
|
||||
// Let's trigger the reads. This will ensure we will consume the data and the remote peer
|
||||
// should be notified that it can write more data.
|
||||
stream.read();
|
||||
|
||||
writePromise.sync();
|
||||
writableAgainLatch.await();
|
||||
stream.close().sync();
|
||||
stream.closeFuture().sync();
|
||||
quicChannel.close().sync();
|
||||
|
||||
throwIfNotNull(serverErrorRef);
|
||||
throwIfNotNull(clientErrorRef);
|
||||
|
||||
serverHandler.assertState();
|
||||
clientHandler.assertState();
|
||||
} finally {
|
||||
server.close().sync();
|
||||
// Close the parent Datagram channel as well.
|
||||
channel.close().sync();
|
||||
|
||||
shutdown(executor);
|
||||
}
|
||||
}
|
||||
|
||||
private static void throwIfNotNull(AtomicReference<Throwable> errorRef) throws Throwable {
|
||||
Throwable cause = errorRef.get();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
|
@ -13,16 +13,13 @@
|
|||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.channel;
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.nativeimage.ChannelHandlerMetadataUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import io.netty.util.concurrent.ImmediateExecutor;
|
||||
|
||||
public class NativeImageHandlerMetadataTest {
|
||||
|
||||
@Test
|
||||
public void collectAndCompareMetadata() {
|
||||
ChannelHandlerMetadataUtil.generateMetadata("io.netty.bootstrap", "io.netty.channel");
|
||||
public class QuicheQuicClientCodecTest extends QuicheQuicCodecTest<QuicClientCodecBuilder> {
|
||||
@Override
|
||||
protected QuicClientCodecBuilder newCodecBuilder() {
|
||||
return QuicTestUtils.newQuicClientBuilder(ImmediateExecutor.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOutboundHandlerAdapter;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public abstract class QuicheQuicCodecTest<B extends QuicCodecBuilder<B>> extends AbstractQuicTest {
|
||||
|
||||
protected abstract B newCodecBuilder();
|
||||
|
||||
@Test
|
||||
public void testDefaultVersionIsV1() {
|
||||
B builder = newCodecBuilder();
|
||||
assertEquals(0x0000_0001, builder.version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushStrategyUsedWithBytes() {
|
||||
testFlushStrategy(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushStrategyUsedWithPackets() {
|
||||
testFlushStrategy(false);
|
||||
}
|
||||
|
||||
private void testFlushStrategy(boolean useBytes) {
|
||||
final int bytes = 8;
|
||||
final AtomicInteger numBytesTracker = new AtomicInteger();
|
||||
final AtomicInteger numPacketsTracker = new AtomicInteger();
|
||||
final AtomicInteger flushCount = new AtomicInteger();
|
||||
B builder = newCodecBuilder();
|
||||
builder.flushStrategy((numPackets, numBytes) -> {
|
||||
numPacketsTracker.set(numPackets);
|
||||
numBytesTracker.set(numBytes);
|
||||
if (useBytes) {
|
||||
return numBytes > 8;
|
||||
}
|
||||
if (numPackets == 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new ChannelOutboundHandlerAdapter() {
|
||||
@Override
|
||||
public void flush(ChannelHandlerContext ctx) throws Exception {
|
||||
flushCount.incrementAndGet();
|
||||
super.flush(ctx);
|
||||
}
|
||||
}, builder.build());
|
||||
assertEquals(0, numPacketsTracker.get());
|
||||
assertEquals(0, numBytesTracker.get());
|
||||
assertEquals(0, flushCount.get());
|
||||
|
||||
channel.write(new DatagramPacket(Unpooled.buffer().writeZero(bytes), new InetSocketAddress(0)));
|
||||
assertEquals(1, numPacketsTracker.get());
|
||||
assertEquals(8, numBytesTracker.get());
|
||||
assertEquals(0, flushCount.get());
|
||||
|
||||
channel.write(new DatagramPacket(Unpooled.buffer().writeZero(bytes), new InetSocketAddress(0)));
|
||||
assertEquals(2, numPacketsTracker.get());
|
||||
assertEquals(16, numBytesTracker.get());
|
||||
assertEquals(1, flushCount.get());
|
||||
|
||||
// As a flush did happen we should see two packets in the outbound queue.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
DatagramPacket packet = channel.readOutbound();
|
||||
assertNotNull(packet);
|
||||
packet.release();
|
||||
}
|
||||
|
||||
ChannelFuture future = channel.write(new DatagramPacket(Unpooled.buffer().writeZero(bytes),
|
||||
new InetSocketAddress(0)));
|
||||
assertEquals(1, numPacketsTracker.get());
|
||||
assertEquals(8, numBytesTracker.get());
|
||||
assertEquals(1, flushCount.get());
|
||||
|
||||
// We never flushed the last datagram packet so it should be failed.
|
||||
assertFalse(channel.finish());
|
||||
assertTrue(future.isDone());
|
||||
assertFalse(future.isSuccess());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.concurrent.ImmediateExecutor;
|
||||
|
||||
public class QuicheQuicServerCodecTest extends QuicheQuicCodecTest<QuicServerCodecBuilder> {
|
||||
@Override
|
||||
protected QuicServerCodecBuilder newCodecBuilder() {
|
||||
return QuicTestUtils.newQuicServerBuilder(ImmediateExecutor.INSTANCE)
|
||||
.streamHandler(new ChannelInboundHandlerAdapter())
|
||||
.tokenHandler(InsecureQuicTokenHandler.INSTANCE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
handlers=java.util.logging.ConsoleHandler
|
||||
.level=ALL
|
||||
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
|
||||
java.util.logging.ConsoleHandler.level=ALL
|
||||
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
|
||||
jdk.event.security.level=INFO
|
||||
org.junit.jupiter.engine.execution.ConditionEvaluator.level=OFF
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"rules": [
|
||||
{"excludeClasses": "**"},
|
||||
{"includeClasses": "io.netty.handler.codec.quic.**"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"rules": [
|
||||
{"includeClasses": "**"},
|
||||
{"excludeClasses": "ch.qos.logback.**"},
|
||||
{"excludeClasses": "io.netty.handler.codec.quic.NoValidationQuicTokenHandler"},
|
||||
{"excludeClasses": "io.netty.handler.codec.quic.QuicChannelValidationHandler"},
|
||||
{"excludeClasses": "org.apache.maven.surefire.**"}
|
||||
],
|
||||
"regexRules": [
|
||||
{"excludeClasses": ".*Test.*"}
|
||||
]
|
||||
}
|
|
@ -86,3 +86,4 @@ include 'netty-tcnative-boringssl-static-native'
|
|||
include 'netty-testsuite'
|
||||
include 'netty-util'
|
||||
include 'netty-zlib'
|
||||
include 'test-results'
|
||||
|
|
45
test-results/build.gradle
Normal file
45
test-results/build.gradle
Normal file
|
@ -0,0 +1,45 @@
|
|||
plugins {
|
||||
id 'base'
|
||||
id 'test-report-aggregation'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testReportAggregation project(':netty-buffer')
|
||||
testReportAggregation project(':netty-bzip2')
|
||||
testReportAggregation project(':netty-channel')
|
||||
testReportAggregation project(':netty-channel-epoll')
|
||||
testReportAggregation project(':netty-channel-sctp')
|
||||
testReportAggregation project(':netty-channel-unix')
|
||||
testReportAggregation project(':netty-handler')
|
||||
testReportAggregation project(':netty-handler-codec')
|
||||
testReportAggregation project(':netty-handler-codec-compression')
|
||||
testReportAggregation project(':netty-handler-codec-dns')
|
||||
testReportAggregation project(':netty-handler-codec-http')
|
||||
testReportAggregation project(':netty-handler-codec-http2')
|
||||
testReportAggregation project(':netty-handler-codec-http3')
|
||||
testReportAggregation project(':netty-handler-codec-quic')
|
||||
testReportAggregation project(':netty-handler-codec-rtsp')
|
||||
testReportAggregation project(':netty-handler-codec-sctp')
|
||||
testReportAggregation project(':netty-handler-codec-spdy')
|
||||
testReportAggregation project(':netty-handler-ssl')
|
||||
testReportAggregation project(':netty-handler-ssl-bouncycastle')
|
||||
testReportAggregation project(':netty-internal-tcnative')
|
||||
testReportAggregation project(':netty-jctools')
|
||||
testReportAggregation project(':netty-resolver')
|
||||
testReportAggregation project(':netty-resolver-dns')
|
||||
testReportAggregation project(':netty-testsuite')
|
||||
testReportAggregation project(':netty-util')
|
||||
testReportAggregation project(':netty-zlib')
|
||||
}
|
||||
|
||||
reporting {
|
||||
reports {
|
||||
testAggregateTestReport(AggregateTestReport) {
|
||||
testType = TestSuiteType.UNIT_TEST
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('check') {
|
||||
dependsOn tasks.named('testAggregateTestReport', TestReport)
|
||||
}
|
Loading…
Reference in a new issue