From 9e7db9d2b91855b1d6e11acf38803e7462aa89b6 Mon Sep 17 00:00:00 2001 From: Dries C Date: Wed, 21 Feb 2024 17:59:15 +0100 Subject: [PATCH 1/3] Add ability to get path stats --- .../incubator/codec/quic/QuicChannel.java | 17 +++ .../codec/quic/QuicConnectionPathStats.java | 104 ++++++++++++++ .../io/netty/incubator/codec/quic/Quiche.java | 7 + .../codec/quic/QuicheQuicChannel.java | 27 ++++ .../quic/QuicheQuicConnectionPathStats.java | 134 +++++++++++++++++ .../src/main/c/netty_quic_quiche.c | 34 +++++ .../quic/QuicConnectionPathStatsTest.java | 135 ++++++++++++++++++ 7 files changed, 458 insertions(+) create mode 100644 codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java create mode 100644 codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java create mode 100644 codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java 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 0aaa057d..130bdff1 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 @@ -306,6 +306,23 @@ default Future collectStats() { */ Future collectStats(Promise promise); + /** + * Collects statistics about the path of the connection and notifies the {@link Future} once done. + * + * @return the {@link Future} that is notified once the stats were collected. + */ + default Future collectPathStats(int pathIdx) { + return collectPathStats(pathIdx, eventLoop().newPromise()); + } + + /** + * Collects statistics about the path of the connection and notifies the {@link Promise} once done. + * + * @param promise the {@link ChannelPromise} that is notified once the stats were collected. + * @return the {@link Future} that is notified once the stats were collected. + */ + Future collectPathStats(int pathIdx, Promise promise); + /** * Creates a new {@link QuicChannelBootstrap} that can be used to create and connect new {@link QuicChannel}s to * endpoints using the given {@link Channel} as transport layer. diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java new file mode 100644 index 00000000..a0eee88b --- /dev/null +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java @@ -0,0 +1,104 @@ +/* + * 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.incubator.codec.quic; + +import java.net.InetSocketAddress; + +/** + * Statistics about a path of the {@code QUIC} connection. If unknown by the implementation it might return {@code -1} values + * for the various methods. + */ +public interface QuicConnectionPathStats { + /** + * @return The local address used by this path. + */ + InetSocketAddress localAddress(); + + /** + * @return The peer address seen by this path. + */ + InetSocketAddress peerAddress(); + + /** + * @return The validation state of the path. + */ + long validationState(); + + /** + * @return Whether this path is active. + */ + boolean active(); + + /** + * @return The number of QUIC packets received on this path. + */ + long recv(); + + /** + * @return The number of QUIC packets sent on this path. + */ + long sent(); + + /** + * @return The number of QUIC packets that were lost on this path. + */ + long lost(); + + /** + * @return The number of sent QUIC packets with retransmitted data on this path. + */ + long retrans(); + + /** + * @return The estimated round-trip time of the path (in nanoseconds). + */ + long rtt(); + + /** + * @return The size of the path's congestion window in bytes. + */ + long cwnd(); + + /** + * @return The number of sent bytes on this path. + */ + long sentBytes(); + + /** + * @return The number of received bytes on this path. + */ + long recvBytes(); + + /** + * @return The number of bytes lost on this path. + */ + long lostBytes(); + + /** + * @return The number of stream bytes retransmitted on this path. + */ + long streamRetransBytes(); + + /** + * @return The current PMTU for the path. + */ + long pmtu(); + + /** + * @return The most recent data delivery rate estimate in bytes/s. + */ + long deliveryRate(); +} 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 b8c13ccc..b7a1f7fb 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 @@ -514,6 +514,13 @@ static native int quiche_conn_stream_priority( */ static native void quiche_stream_iter_free(long iterAddr); + + /** + * See + * quiche_conn_path_stats. + */ + static native Object[] quiche_conn_path_stats(long connAddr, long streamIdx); + /** * See * 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 316c2288..b658db6c 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 @@ -2050,6 +2050,33 @@ private QuicConnectionStats collectStats0(QuicheQuicConnection connection, Promi return connStats; } + @Override + public Future collectPathStats(int pathIdx, Promise promise) { + if (eventLoop().inEventLoop()) { + collectPathStats0(pathIdx, promise); + } else { + eventLoop().execute(() -> collectPathStats0(pathIdx, promise)); + } + return promise; + } + + private void collectPathStats0(int pathIdx, Promise promise) { + collectPathStats0(connection, pathIdx, promise); + } + + private QuicConnectionPathStats collectPathStats0(QuicheQuicConnection connection, int pathIdx, Promise promise) { + final Object[] stats = Quiche.quiche_conn_path_stats(connection.address(), pathIdx); + if (stats == null) { + promise.setFailure(new IllegalStateException("native quiche_conn_path_stats(...) failed")); + return null; + } + + final QuicheQuicConnectionPathStats connStats = + new QuicheQuicConnectionPathStats(stats); + 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/QuicheQuicConnectionPathStats.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java new file mode 100644 index 00000000..2fd98285 --- /dev/null +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java @@ -0,0 +1,134 @@ +/* + * 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.incubator.codec.quic; + +import io.netty.util.internal.StringUtil; + +import java.net.InetSocketAddress; + +final class QuicheQuicConnectionPathStats implements QuicConnectionPathStats { + + private final Object[] values; + + QuicheQuicConnectionPathStats(Object[] values) { + this.values = values; + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) values[0]; + } + + @Override + public InetSocketAddress peerAddress() { + return (InetSocketAddress) values[1]; + } + + public long validationState() { + return (long) values[2]; + } + + @Override + public boolean active() { + return (boolean) values[3]; + } + + @Override + public long recv() { + return (long) values[4]; + } + + @Override + public long sent() { + return (long) values[5]; + } + + @Override + public long lost() { + return (long) values[6]; + } + + @Override + public long retrans() { + return (long) values[7]; + } + + @Override + public long rtt() { + return (long) values[8]; + } + + @Override + public long cwnd() { + return (long) values[9]; + } + + @Override + public long sentBytes() { + return (long) values[10]; + } + + @Override + public long recvBytes() { + return (long) values[11]; + } + + @Override + public long lostBytes() { + return (long) values[12]; + } + + @Override + public long streamRetransBytes() { + return (long) values[13]; + } + + @Override + public long pmtu() { + return (long) values[14]; + } + + @Override + public long deliveryRate() { + return (long) values[15]; + } + + /** + * Returns the {@link String} representation of stats. + */ + @Override + public String toString() { + return StringUtil.simpleClassName(this) + "[" + + "local=" + localAddress() + + ", peer=" + peerAddress() + + ", validationState=" + validationState() + + ", active=" + active() + + ", recv=" + recv() + + ", sent=" + sent() + + ", lost=" + lost() + + ", retrans=" + retrans() + + ", rtt=" + rtt() + + ", cwnd=" + cwnd() + + ", sentBytes=" + sentBytes() + + ", recvBytes=" + recvBytes() + + ", lostBytes=" + lostBytes() + + ", streamRetransBytes=" + streamRetransBytes() + + ", pmtu=" + pmtu() + + ", deliveryRate=" + deliveryRate() + + ']'; + } + +} 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 9e324369..7cc68aee 100644 --- a/codec-native-quic/src/main/c/netty_quic_quiche.c +++ b/codec-native-quic/src/main/c/netty_quic_quiche.c @@ -719,6 +719,39 @@ static jobject netty_new_socket_address(JNIEnv* env, const struct sockaddr_stora return (*env)->NewObject(env, inetsocketaddress_class, inetsocketaddress_class_constructor, address, port); } +static jobjectArray netty_quiche_conn_path_stats(JNIEnv* env, jclass clazz, jlong conn, jlong idx) { + quiche_path_stats stats = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + quiche_conn_path_stats((quiche_conn *) conn, idx, &stats); + + jobject localAddr = netty_new_socket_address(env, &stats.local_addr); + if (localAddr == NULL) { + return NULL; + } + jobject peerAddr = netty_new_socket_address(env, &stats.peer_addr); + if (peerAddr == NULL) { + return NULL; + } + + jobjectArray array = (*env)->NewObjectArray(env, 16, object_class, NULL); + (*env)->SetObjectArrayElement(env, array, 0, localAddr); + (*env)->SetObjectArrayElement(env, array, 1, peerAddr); + (*env)->SetObjectArrayElement(env, array, 2, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.validation_state)); + (*env)->SetObjectArrayElement(env, array, 3, (*env)->CallStaticObjectMethod(env, boolean_class, boolean_class_valueof, stats.active ? JNI_TRUE : JNI_FALSE)); + (*env)->SetObjectArrayElement(env, array, 4, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.recv)); + (*env)->SetObjectArrayElement(env, array, 5, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.sent)); + (*env)->SetObjectArrayElement(env, array, 6, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.lost)); + (*env)->SetObjectArrayElement(env, array, 7, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.retrans)); + (*env)->SetObjectArrayElement(env, array, 8, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.rtt)); + (*env)->SetObjectArrayElement(env, array, 9, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.cwnd)); + (*env)->SetObjectArrayElement(env, array, 10, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.sent_bytes)); + (*env)->SetObjectArrayElement(env, array, 11, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.recv_bytes)); + (*env)->SetObjectArrayElement(env, array, 12, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.lost_bytes)); + (*env)->SetObjectArrayElement(env, array, 13, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.stream_retrans_bytes)); + (*env)->SetObjectArrayElement(env, array, 14, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.pmtu)); + (*env)->SetObjectArrayElement(env, array, 15, (*env)->CallStaticObjectMethod(env, long_class, long_class_valueof, (jlong) stats.delivery_rate)); + return array; +} + static jobjectArray netty_quiche_path_event_new(JNIEnv* env, jclass clazz, jlong ev) { struct sockaddr_storage local; socklen_t local_len; @@ -1168,6 +1201,7 @@ static const JNINativeMethod fixed_method_table[] = { { "sockaddr_cmp", "(JJ)I", (void *) netty_sockaddr_cmp}, { "quiche_conn_path_event_next", "(J)J", (void *) netty_quiche_conn_path_event_next }, { "quiche_path_event_type", "(J)I", (void *) netty_quiche_path_event_type }, + { "quiche_conn_path_stats", "(JJ)[Ljava/lang/Object;", (void *) netty_quiche_conn_path_stats }, { "quiche_path_event_new", "(J)[Ljava/lang/Object;", (void *) netty_quiche_path_event_new }, { "quiche_path_event_validated", "(J)[Ljava/lang/Object;", (void *) netty_quiche_path_event_validated }, { "quiche_path_event_failed_validation", "(J)[Ljava/lang/Object;", (void *) netty_quiche_path_event_failed_validation }, diff --git a/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java b/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java new file mode 100644 index 00000000..6915b04e --- /dev/null +++ b/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java @@ -0,0 +1,135 @@ +/* + * 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.incubator.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.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class QuicConnectionPathStatsTest extends AbstractQuicTest { + + @ParameterizedTest + @MethodSource("newSslTaskExecutors") + public void testPathStatsAreCollected(Executor executor) throws Throwable { + Channel server = null; + Channel channel = null; + AtomicInteger counter = new AtomicInteger(); + + Promise serverActiveStats = ImmediateEventExecutor.INSTANCE.newPromise(); + QuicChannelValidationHandler serverHandler = new QuicChannelValidationHandler() { + @Override + public void channelActive(ChannelHandlerContext ctx) { + super.channelActive(ctx); + collectPathStats(ctx, serverActiveStats); + } + + private void collectPathStats(ChannelHandlerContext ctx, Promise promise) { + QuicheQuicChannel channel = (QuicheQuicChannel) ctx.channel(); + channel.collectPathStats(0, 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(serverActiveStats.sync().getNow()); + assertEquals(1, counter.get()); + + serverHandler.assertState(); + clientHandler.assertState(); + } finally { + QuicTestUtils.closeIfNotNull(channel); + QuicTestUtils.closeIfNotNull(server); + + shutdown(executor); + } + } + + private static void assertStats(QuicConnectionPathStats 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)); + assertThat(stats.rtt(), greaterThan(0L)); + } +} From e059297818c78f435ff548ea51fd39adf95bc277 Mon Sep 17 00:00:00 2001 From: Dries C Date: Mon, 26 Feb 2024 20:47:25 +0100 Subject: [PATCH 2/3] Fix for when connection is already freed + Copyright year --- .../incubator/codec/quic/QuicConnectionPathStats.java | 2 +- .../io/netty/incubator/codec/quic/QuicheQuicChannel.java | 9 ++++++++- .../codec/quic/QuicheQuicConnectionPathStats.java | 2 +- .../codec/quic/QuicConnectionPathStatsTest.java | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java index a0eee88b..3697362c 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicConnectionPathStats.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Netty Project + * Copyright 2024 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 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 b658db6c..29a61b0b 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 @@ -2061,7 +2061,14 @@ public Future collectPathStats(int pathIdx, Promise promise) { - collectPathStats0(connection, pathIdx, promise); + QuicheQuicConnection conn = connection; + + if (conn.isFreed()) { + promise.setFailure(new IllegalStateException("Connection is closed")); + return; + } + + collectPathStats0(conn, pathIdx, promise); } private QuicConnectionPathStats collectPathStats0(QuicheQuicConnection connection, int pathIdx, Promise promise) { diff --git a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java index 2fd98285..db7d3924 100644 --- a/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java +++ b/codec-classes-quic/src/main/java/io/netty/incubator/codec/quic/QuicheQuicConnectionPathStats.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Netty Project + * Copyright 2024 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 diff --git a/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java b/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java index 6915b04e..dc17079f 100644 --- a/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java +++ b/codec-native-quic/src/test/java/io/netty/incubator/codec/quic/QuicConnectionPathStatsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Netty Project + * Copyright 2024 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 From a9ef6e85f320234848d8f05030850711f42ae75a Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Thu, 21 Mar 2024 09:20:56 +0100 Subject: [PATCH 3/3] Update codec-native-quic/src/main/c/netty_quic_quiche.c Check for return value --- codec-native-quic/src/main/c/netty_quic_quiche.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 7cc68aee..66fa0ab6 100644 --- a/codec-native-quic/src/main/c/netty_quic_quiche.c +++ b/codec-native-quic/src/main/c/netty_quic_quiche.c @@ -721,7 +721,10 @@ static jobject netty_new_socket_address(JNIEnv* env, const struct sockaddr_stora static jobjectArray netty_quiche_conn_path_stats(JNIEnv* env, jclass clazz, jlong conn, jlong idx) { quiche_path_stats stats = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; - quiche_conn_path_stats((quiche_conn *) conn, idx, &stats); + if (quiche_conn_path_stats((quiche_conn *) conn, idx, &stats) != 0) { + // The idx is not valid. + return NULL; + } jobject localAddr = netty_new_socket_address(env, &stats.local_addr); if (localAddr == NULL) {