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
|
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:
|
between subproject name, package name, artifact names, and java module. The following reorgnizations were performed:
|
||||||
|
|
||||||
netty/all -> [todo]
|
netty/all ->
|
||||||
netty/bom -> [todo]
|
netty/bom ->
|
||||||
netty/buffer -> netty-buffer
|
netty/buffer -> netty-buffer
|
||||||
netty/codec -> netty-handler-codec, netty-handler-codec-compression, netty-handler-codec-protobuf
|
netty/codec -> netty-handler-codec, netty-handler-codec-compression, netty-handler-codec-protobuf
|
||||||
netty/codec-dns -> [todo]
|
netty/codec-dns -> netty-handler-codec-dns
|
||||||
netty/codec-haproxy -> [todo]
|
netty/codec-haproxy ->
|
||||||
netty/codec-http -> netty-handler-codec-http, netty-handler-codec-rtsp, netty-handler-codec-spdy
|
netty/codec-http -> netty-handler-codec-http, netty-handler-codec-rtsp, netty-handler-codec-spdy
|
||||||
netty/codec-http2 ->
|
netty/codec-http2 ->
|
||||||
netty/codec-memcache ->
|
netty/codec-memcache ->
|
||||||
|
@ -62,7 +62,7 @@ netty/handler -> netty-handler
|
||||||
netty/handler-proxy
|
netty/handler-proxy
|
||||||
netty/handler-ssl-ocsp
|
netty/handler-ssl-ocsp
|
||||||
netty/resolver -> netty-resolver
|
netty/resolver -> netty-resolver
|
||||||
netty/resolver-dns ->
|
netty/resolver-dns -> netty-resolver-dns
|
||||||
netty/resolver-dns-classes-macos -> [dropped]
|
netty/resolver-dns-classes-macos -> [dropped]
|
||||||
netty/resolver-dns-native-macos -> [dropped]
|
netty/resolver-dns-native-macos -> [dropped]
|
||||||
netty/transport -> netty-channel
|
netty/transport -> netty-channel
|
||||||
|
|
|
@ -31,7 +31,7 @@ ext {
|
||||||
apply plugin: 'com.google.osdetector'
|
apply plugin: 'com.google.osdetector'
|
||||||
|
|
||||||
subprojects {
|
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/repositories/maven.gradle')
|
||||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||||
|
|
|
@ -12,8 +12,8 @@ test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
failFast = false
|
failFast = false
|
||||||
ignoreFailures = true
|
ignoreFailures = true
|
||||||
minHeapSize = "1g" // initial heap size
|
minHeapSize = "2g" // initial heap size
|
||||||
maxHeapSize = "2g" // maximum heap size
|
maxHeapSize = "4g" // maximum heap size
|
||||||
jvmArgs '--add-exports=java.base/jdk.internal=ALL-UNNAMED',
|
jvmArgs '--add-exports=java.base/jdk.internal=ALL-UNNAMED',
|
||||||
'--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
|
'--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
|
||||||
'--add-exports=java.base/sun.nio.ch=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 */
|
/* 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 */
|
/* 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-epoll')
|
||||||
implementation project(':netty-channel-unix')
|
implementation project(':netty-channel-unix')
|
||||||
runtimeOnly project(path: ':netty-handler-codec-quic-native', configuration: osdetector.classifier)
|
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
|
* 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
|
* {@link BoringSSLKeylog} for detail, logging keys are following
|
||||||
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
* <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.
|
* 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,
|
* 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
|
* 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
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
package io.netty.channel;
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
import io.netty.nativeimage.ChannelHandlerMetadataUtil;
|
import io.netty.util.concurrent.ImmediateExecutor;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public class NativeImageHandlerMetadataTest {
|
public class QuicheQuicClientCodecTest extends QuicheQuicCodecTest<QuicClientCodecBuilder> {
|
||||||
|
@Override
|
||||||
@Test
|
protected QuicClientCodecBuilder newCodecBuilder() {
|
||||||
public void collectAndCompareMetadata() {
|
return QuicTestUtils.newQuicClientBuilder(ImmediateExecutor.INSTANCE);
|
||||||
ChannelHandlerMetadataUtil.generateMetadata("io.netty.bootstrap", "io.netty.channel");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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-testsuite'
|
||||||
include 'netty-util'
|
include 'netty-util'
|
||||||
include 'netty-zlib'
|
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