diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicChannel.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicChannel.java index 4fc21740e..c939ec25b 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicChannel.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicChannel.java @@ -172,6 +172,13 @@ default ChannelPromise voidPromise() { */ boolean isTimedOut(); + /** + * Returns the {@link QuicTransportParameters} of the peer once received, or {@code null} if not known yet. + * + * @return peerTransportParams. + */ + QuicTransportParameters peerTransportParameters(); + /** * Creates a stream that is using this {@link QuicChannel} and notifies the {@link Future} once done. * The {@link ChannelHandler} (if not {@code null}) is added to the {@link io.netty.channel.ChannelPipeline} of the diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionStats.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionStats.java index d6e094f22..0be920974 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionStats.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionStats.java @@ -17,7 +17,7 @@ /** * Statistics about the {@code QUIC} connection. If unknown by the implementation it might return {@code -1} values - * for the various methods. + * for the various methods. */ public interface QuicConnectionStats { /** diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicTransportParameters.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicTransportParameters.java new file mode 100644 index 000000000..d6c63516a --- /dev/null +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicTransportParameters.java @@ -0,0 +1,113 @@ +/* + * 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.incubator.codec.quic; + +/** + * Transport parameters for QUIC. + */ +public interface QuicTransportParameters { + + /** + * The maximum idle timeout. + * @return timeout. + */ + long maxIdleTimeout(); + + /** + * The maximum UDP payload size. + * + * @return maximum payload size. + */ + long maxUdpPayloadSize(); + + /** + * The initial flow control maximum data for the connection. + * + * @return flowcontrol. + */ + long initialMaxData(); + + /** + * The initial flow control maximum data for local bidirectional streams. + * + * @return flowcontrol. + */ + long initialMaxStreamDataBidiLocal(); + + /** + * The initial flow control maximum data for remote bidirectional streams. + * + * @return flowcontrol. + */ + long initialMaxStreamDataBidiRemote(); + + /** + * The initial flow control maximum data for unidirectional streams. + * + * @return flowcontrol. + */ + long initialMaxStreamDataUni(); + + + /** + * The initial maximum bidirectional streams. + * + * @return streams. + */ + long initialMaxStreamsBidi(); + + /** + * The initial maximum unidirectional streams. + * + * @return streams. + */ + long initialMaxStreamsUni(); + + /** + * The ACK delay exponent + * + * @return exponent. + */ + long ackDelayExponent(); + + /** + * The max ACK delay. + * + * @return delay. + */ + long maxAckDelay(); + + /** + * Whether active migration is disabled. + * + * @return disabled. + */ + boolean disableActiveMigration(); + + /** + * The active connection ID limit. + * + * @return limit. + */ + long activeConnIdLimit(); + + /** + * DATAGRAM frame extension parameter, if any. + * + * @return param. + */ + long maxDatagramFrameSize(); +} diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/Quiche.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/Quiche.java index d136d02cc..5f1472f0e 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/Quiche.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/Quiche.java @@ -456,6 +456,13 @@ static native int quiche_conn_stream_priority( */ static native long[] quiche_conn_stats(long connAddr); + /** + * See + * + * quiche_conn_stats. + */ + static native long[] quiche_conn_peer_transport_params(long connAddr); + /** * See * quiche_conn_timeout_as_nanos. diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicChannel.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicChannel.java index f2817de96..9377f2b42 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicChannel.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicChannel.java @@ -1840,4 +1840,9 @@ private QuicConnectionStats collectStats0(QuicheQuicConnection connection, Promi promise.setSuccess(connStats); return connStats; } + + @Override + public QuicTransportParameters peerTransportParameters() { + return connection.peerParameters(); + } } diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnection.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnection.java index d4e0d9ac2..b9642fe0a 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnection.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnection.java @@ -155,6 +155,20 @@ QuicConnectionAddress connectionId(Supplier idSupplier) { return id == null ? null : new QuicConnectionAddress(id); } + QuicheQuicTransportParameters peerParameters() { + final long[] ret; + synchronized (this) { + if (connection == -1) { + return null; + } + ret = Quiche.quiche_conn_peer_transport_params(connection); + } + if (ret == null) { + return null; + } + return new QuicheQuicTransportParameters(ret); + } + QuicheQuicSslEngine engine() { return engine; } diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicTransportParameters.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicTransportParameters.java new file mode 100644 index 000000000..55e8a709c --- /dev/null +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicTransportParameters.java @@ -0,0 +1,89 @@ +/* + * 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.incubator.codec.quic; + +final class QuicheQuicTransportParameters implements QuicTransportParameters { + private final long[] values; + + QuicheQuicTransportParameters(long[] values) { + this.values = values; + } + + @Override + public long maxIdleTimeout() { + return values[0]; + } + + @Override + public long maxUdpPayloadSize() { + return values[1]; + } + + @Override + public long initialMaxData() { + return values[2]; + } + + @Override + public long initialMaxStreamDataBidiLocal() { + return values[3]; + } + + @Override + public long initialMaxStreamDataBidiRemote() { + return values[4]; + } + + @Override + public long initialMaxStreamDataUni() { + return values[5]; + } + + @Override + public long initialMaxStreamsBidi() { + return values[6]; + } + + @Override + public long initialMaxStreamsUni() { + return values[7]; + } + + @Override + public long ackDelayExponent() { + return values[8]; + } + + @Override + public long maxAckDelay() { + return values[9]; + } + + @Override + public boolean disableActiveMigration() { + return values[10] == 1; + } + + @Override + public long activeConnIdLimit() { + return values[11]; + } + + @Override + public long maxDatagramFrameSize() { + return values[12]; + } +} diff --git a/codec-native-quic/src/main/c/netty_quic_quiche.c b/codec-native-quic/src/main/c/netty_quic_quiche.c index 7f8fa31a0..bb57701cb 100644 --- a/codec-native-quic/src/main/c/netty_quic_quiche.c +++ b/codec-native-quic/src/main/c/netty_quic_quiche.c @@ -518,6 +518,38 @@ static jlongArray netty_quiche_conn_stats(JNIEnv* env, jclass clazz, jlong conn) return statsArray; } +static jlongArray netty_quiche_conn_peer_transport_params(JNIEnv* env, jclass clazz, jlong conn) { + // See https://github.com/cloudflare/quiche/blob/master/quiche/include/quiche.h#L563 + quiche_transport_params params = {0,0,0,0,0,0,0,0,0,0,false,0,0}; + if (!quiche_conn_peer_transport_params((quiche_conn *) conn, ¶ms)) { + return NULL; + } + + jlongArray paramsArray = (*env)->NewLongArray(env, 13); + if (paramsArray == NULL) { + // This will put an OOME on the stack + return NULL; + } + jlong paramsArrayElements[] = { + (jlong)params.peer_max_idle_timeout, + (jlong)params.peer_max_udp_payload_size, + (jlong)params.peer_initial_max_data, + (jlong)params.peer_initial_max_stream_data_bidi_local, + (jlong)params.peer_initial_max_stream_data_bidi_remote, + (jlong)params.peer_initial_max_stream_data_uni, + (jlong)params.peer_initial_max_streams_bidi, + (jlong)params.peer_initial_max_streams_uni, + (jlong)params.peer_ack_delay_exponent, + (jlong)params.peer_disable_active_migration ? 1: 0, + (jlong)params.peer_active_conn_id_limit, + (jlong)params.peer_max_datagram_frame_size + }; + (*env)->SetLongArrayRegion(env, paramsArray, 0, 13, paramsArrayElements); + return paramsArray; +} + + + static jlong netty_quiche_conn_timeout_as_nanos(JNIEnv* env, jclass clazz, jlong conn) { return quiche_conn_timeout_as_nanos((quiche_conn *) conn); } @@ -839,6 +871,7 @@ static const JNINativeMethod fixed_method_table[] = { { "quiche_conn_is_closed", "(J)Z", (void *) netty_quiche_conn_is_closed }, { "quiche_conn_is_timed_out", "(J)Z", (void *) netty_quiche_conn_is_timed_out }, { "quiche_conn_stats", "(J)[J", (void *) netty_quiche_conn_stats }, + { "quiche_conn_peer_transport_params", "(J)[J", (void *) netty_quiche_conn_peer_transport_params }, { "quiche_conn_timeout_as_nanos", "(J)J", (void *) netty_quiche_conn_timeout_as_nanos }, { "quiche_conn_on_timeout", "(J)V", (void *) netty_quiche_conn_on_timeout }, { "quiche_conn_readable", "(J)J", (void *) netty_quiche_conn_readable }, diff --git a/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicTransportParametersTest.java b/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicTransportParametersTest.java new file mode 100644 index 000000000..dd0e189c0 --- /dev/null +++ b/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicTransportParametersTest.java @@ -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.incubator.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 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(1L)); + } +}