diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java index 4143fb56139..29d2832ed91 100644 --- a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java +++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/internal/ClientHTTP3Session.java @@ -184,7 +184,7 @@ public class ClientHTTP3Session extends ClientProtocolSession else if (key == SettingsFrame.MAX_FIELD_SECTION_SIZE) { // Must cap the maxHeaderSize to avoid large allocations. - int maxHeadersSize = (int)Math.min(value, configuration.getMaxResponseHeadersSize()); + int maxHeadersSize = (int)Math.min(value, configuration.getMaxRequestHeadersSize()); encoder.setMaxHeadersSize(maxHeadersSize); } else if (key == SettingsFrame.MAX_BLOCKED_STREAMS) diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java index d17576398cb..7b6984452a4 100644 --- a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java +++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/ClientServerTest.java @@ -373,13 +373,22 @@ public class ClientServerTest extends AbstractClientServerTest } }); - int maxRequestHeadersSize = 128; + int maxRequestHeadersSize = 256; HTTP3Configuration http3Configuration = http3Client.getHTTP3Configuration(); http3Configuration.setMaxRequestHeadersSize(maxRequestHeadersSize); // Disable the dynamic table, otherwise the large header - // is sent as string literal on the encoder stream. + // is sent as string literal on the encoder stream, rather than the message stream. http3Configuration.setMaxEncoderTableCapacity(0); - Session.Client clientSession = newSession(new Session.Client.Listener() {}); + CountDownLatch settingsLatch = new CountDownLatch(1); + Session.Client clientSession = newSession(new Session.Client.Listener() + { + @Override + public void onSettings(Session session, SettingsFrame frame) + { + settingsLatch.countDown(); + } + }); + assertTrue(settingsLatch.await(5, TimeUnit.SECONDS)); CountDownLatch requestFailureLatch = new CountDownLatch(1); HttpFields largeHeaders = HttpFields.build().put("too-large", "x".repeat(2 * maxRequestHeadersSize)); @@ -413,7 +422,7 @@ public class ClientServerTest extends AbstractClientServerTest @Test public void testResponseHeadersTooLarge() throws Exception { - int maxResponseHeadersSize = 128; + int maxResponseHeadersSize = 256; CountDownLatch settingsLatch = new CountDownLatch(2); AtomicReference serverSessionRef = new AtomicReference<>(); CountDownLatch responseFailureLatch = new CountDownLatch(1); @@ -458,7 +467,7 @@ public class ClientServerTest extends AbstractClientServerTest assertNotNull(h3); HTTP3Configuration http3Configuration = h3.getHTTP3Configuration(); // Disable the dynamic table, otherwise the large header - // is sent as string literal on the encoder stream. + // is sent as string literal on the encoder stream, rather than the message stream. http3Configuration.setMaxEncoderTableCapacity(0); http3Configuration.setMaxResponseHeadersSize(maxResponseHeadersSize); @@ -519,10 +528,10 @@ public class ClientServerTest extends AbstractClientServerTest @Override public void onDataAvailable(Stream.Server stream) { - // TODO: we should not be needing this!!! + // Calling readData() triggers the read+parse + // of the trailer, and returns no data. Stream.Data data = stream.readData(); assertNull(data); - stream.demand(); } @Override diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java index ca150fe7695..5a4f980df8a 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java +++ b/jetty-quic/quic-quiche/quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/JnaQuicheConnection.java @@ -690,7 +690,7 @@ public class JnaQuicheConnection extends QuicheConnection { if (quicheConn == null) throw new IOException("connection was released"); - int written = LibQuiche.INSTANCE.quiche_conn_stream_send(quicheConn, new uint64_t(streamId), buffer, new size_t(buffer.remaining()), last).intValue(); + int written = LibQuiche.INSTANCE.quiche_conn_stream_send(quicheConn, new uint64_t(streamId), jnaBuffer(buffer), new size_t(buffer.remaining()), last).intValue(); if (written == quiche_error.QUICHE_ERR_DONE) return 0; if (written < 0L) @@ -700,6 +700,35 @@ public class JnaQuicheConnection extends QuicheConnection } } + /** + * JNA requires ByteBuffers that are either direct or backed by an array. + * Read-only heap buffer are not direct and are considered not backed by an + * array, so buffer.hasArray() returns false for then an JNA rejects them + * by throwing IllegalStateException with the message "Buffer arguments + * must be direct or have a primitive backing array" from this native + * method in dispatch.c:615: + *
+     * static void
+     * dispatch(JNIEnv *env, void* func, jint flags, jobjectArray args,
+     *          ffi_type *return_type, void *presult)
+     * 
+ * so this method ensures the buffer fulfils JNA's conditions, or it copies + * the given buffer into a new heap buffer and returns the copy, while also + * keeping the limit and position of the original buffer and setting them + * on the new buffer in a way comparable to the original buffer's. + */ + private static ByteBuffer jnaBuffer(ByteBuffer buffer) + { + if (buffer.isDirect() || buffer.hasArray()) + return buffer; + ByteBuffer jnaBuffer = ByteBuffer.allocate(buffer.remaining()); + int oldPosition = buffer.position(); + jnaBuffer.put(buffer); + jnaBuffer.flip(); + buffer.position(oldPosition); + return jnaBuffer; + } + @Override public int drainClearBytesForStream(long streamId, ByteBuffer buffer) throws IOException {