Issue #6728 - QUIC and HTTP/3
- Incremented test timeouts. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
4d778b1aff
commit
eb8444e2c2
|
@ -284,7 +284,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche</artifactId>
|
||||
<artifactId>quic-quiche-common</artifactId>
|
||||
<version>10.0.8-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -439,4 +439,37 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>earlier-than-jdk17</id>
|
||||
<activation>
|
||||
<jdk>(,17)</jdk>
|
||||
</activation>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-jna</artifactId>
|
||||
<version>10.0.8-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk17</id>
|
||||
<activation>
|
||||
<jdk>[17,)</jdk>
|
||||
</activation>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-panama-jdk</artifactId>
|
||||
<version>10.0.8-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -22,25 +22,24 @@
|
|||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-jna</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http2</groupId>
|
||||
<artifactId>http2-http-client-transport</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -49,4 +48,19 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk17</id>
|
||||
<activation>
|
||||
<jdk>[17,)</jdk>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-panama-jdk</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -21,14 +21,12 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<artifactId>quic-quiche-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
|
|
@ -8,44 +8,23 @@
|
|||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>quic-quiche</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Jetty :: QUIC :: Quiche</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.quic-quiche</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${maven.checkstyle.plugin.version}</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<modules>
|
||||
<module>quic-quiche-common</module>
|
||||
<module>quic-quiche-jna</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk17</id>
|
||||
<activation>
|
||||
<jdk>[17,)</jdk>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>quic-quiche-panama-jdk</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche</artifactId>
|
||||
<version>10.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>quic-quiche-common</artifactId>
|
||||
<name>Jetty :: QUIC :: Quiche :: Common</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.quic-quiche-common</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${maven.checkstyle.plugin.version}</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -11,14 +11,12 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
// The module must be open to allow JNA to find the native lib.
|
||||
open module org.eclipse.jetty.quic.quiche
|
||||
module org.eclipse.jetty.quic.quiche
|
||||
{
|
||||
requires org.eclipse.jetty.util;
|
||||
requires org.slf4j;
|
||||
|
||||
requires transitive com.sun.jna;
|
||||
|
||||
exports org.eclipse.jetty.quic.quiche.ffi;
|
||||
exports org.eclipse.jetty.quic.quiche;
|
||||
|
||||
uses org.eclipse.jetty.quic.quiche.QuicheBinding;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche;
|
||||
|
||||
interface Quiche
|
||||
{
|
||||
// The current QUIC wire version.
|
||||
int QUICHE_PROTOCOL_VERSION = 0x00000001;
|
||||
// The maximum length of a connection ID.
|
||||
int QUICHE_MAX_CONN_ID_LEN = 20;
|
||||
|
||||
interface quiche_cc_algorithm
|
||||
{
|
||||
int QUICHE_CC_RENO = 0,
|
||||
QUICHE_CC_CUBIC = 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface QuicheBinding
|
||||
{
|
||||
boolean isUsable();
|
||||
int priority();
|
||||
|
||||
byte[] fromPacket(ByteBuffer packet);
|
||||
QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException;
|
||||
boolean negotiate(QuicheConnection.TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException;
|
||||
QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException;
|
||||
}
|
|
@ -13,19 +13,12 @@
|
|||
|
||||
package org.eclipse.jetty.quic.quiche;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.ffi.LibQuiche;
|
||||
|
||||
public class QuicheConfig
|
||||
{
|
||||
static
|
||||
{
|
||||
LibQuiche.Logging.enable();
|
||||
}
|
||||
|
||||
public enum CongestionControl
|
||||
{
|
||||
RENO(LibQuiche.quiche_cc_algorithm.QUICHE_CC_RENO),
|
||||
CUBIC(LibQuiche.quiche_cc_algorithm.QUICHE_CC_CUBIC);
|
||||
RENO(Quiche.quiche_cc_algorithm.QUICHE_CC_RENO),
|
||||
CUBIC(Quiche.quiche_cc_algorithm.QUICHE_CC_CUBIC);
|
||||
|
||||
private final int value;
|
||||
CongestionControl(int value)
|
||||
|
@ -39,7 +32,7 @@ public class QuicheConfig
|
|||
}
|
||||
}
|
||||
|
||||
private int version = LibQuiche.QUICHE_PROTOCOL_VERSION;
|
||||
private int version = Quiche.QUICHE_PROTOCOL_VERSION;
|
||||
private Boolean verifyPeer;
|
||||
private String certChainPemPath;
|
||||
private String privKeyPemPath;
|
|
@ -0,0 +1,194 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class QuicheConnection
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(QuicheConnection.class);
|
||||
static final QuicheBinding QUICHE_BINDING;
|
||||
|
||||
static
|
||||
{
|
||||
LOG.info("found Quiche binding implementations: {}", TypeUtil.serviceStream(ServiceLoader.load(QuicheBinding.class)).sorted(Comparator.comparingInt(QuicheBinding::priority)).collect(Collectors.toList()));
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
List<QuicheBinding> bindings = TypeUtil.serviceStream(ServiceLoader.load(QuicheBinding.class))
|
||||
.sorted(Comparator.comparingInt(QuicheBinding::priority))
|
||||
.collect(Collectors.toList());
|
||||
LOG.debug("found Quiche binding implementations: {}", bindings);
|
||||
}
|
||||
QUICHE_BINDING = TypeUtil.serviceStream(ServiceLoader.load(QuicheBinding.class))
|
||||
.filter(QuicheBinding::isUsable)
|
||||
.min(Comparator.comparingInt(QuicheBinding::priority))
|
||||
.orElseThrow(() -> new IllegalStateException("no Quiche binding implementation found"));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("using Quiche binding implementation: {}", QUICHE_BINDING.getClass().getName());
|
||||
LOG.info("using Quiche binding implementation: {}", QUICHE_BINDING.getClass().getName());
|
||||
}
|
||||
|
||||
public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer) throws IOException
|
||||
{
|
||||
return connect(quicheConfig, peer, Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
}
|
||||
|
||||
public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException
|
||||
{
|
||||
return QUICHE_BINDING.connect(quicheConfig, peer, connectionIdLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully consumes the {@code packetRead} buffer.
|
||||
* @return true if a negotiation packet was written to the {@code packetToSend} buffer, false if negotiation failed
|
||||
* and the {@code packetRead} buffer can be dropped.
|
||||
*/
|
||||
public static boolean negotiate(TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException
|
||||
{
|
||||
return QUICHE_BINDING.negotiate(tokenMinter, packetRead, packetToSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully consumes the {@code packetRead} buffer if the connection was accepted.
|
||||
* @return an established connection if accept succeeded, null if accept failed and negotiation should be tried.
|
||||
*/
|
||||
public static QuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
|
||||
{
|
||||
return QUICHE_BINDING.tryAccept(quicheConfig, tokenValidator, packetRead, peer);
|
||||
}
|
||||
|
||||
public final List<Long> readableStreamIds()
|
||||
{
|
||||
return iterableStreamIds(false);
|
||||
}
|
||||
|
||||
public final List<Long> writableStreamIds()
|
||||
{
|
||||
return iterableStreamIds(true);
|
||||
}
|
||||
|
||||
protected abstract List<Long> iterableStreamIds(boolean write);
|
||||
|
||||
/**
|
||||
* Read the buffer of cipher text coming from the network.
|
||||
* @param buffer the buffer to read.
|
||||
* @param peer the address of the peer from which the buffer was received.
|
||||
* @return how many bytes were consumed.
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract int feedCipherBytes(ByteBuffer buffer, SocketAddress peer) throws IOException;
|
||||
|
||||
/**
|
||||
* Fill the given buffer with cipher text to be sent.
|
||||
* @param buffer the buffer to fill.
|
||||
* @return how many bytes were added to the buffer.
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract int drainCipherBytes(ByteBuffer buffer) throws IOException;
|
||||
|
||||
public abstract boolean isConnectionClosed();
|
||||
|
||||
public abstract boolean isConnectionEstablished();
|
||||
|
||||
public abstract long nextTimeout();
|
||||
|
||||
public abstract void onTimeout();
|
||||
|
||||
public abstract String getNegotiatedProtocol();
|
||||
|
||||
public abstract boolean close(long error, String reason);
|
||||
|
||||
public abstract void dispose();
|
||||
|
||||
public abstract boolean isDraining();
|
||||
|
||||
public abstract int maxLocalStreams();
|
||||
|
||||
public abstract long windowCapacity();
|
||||
|
||||
public abstract long windowCapacity(long streamId) throws IOException;
|
||||
|
||||
public abstract void shutdownStream(long streamId, boolean writeSide, long error) throws IOException;
|
||||
|
||||
public final void feedFinForStream(long streamId) throws IOException
|
||||
{
|
||||
feedClearBytesForStream(streamId, BufferUtil.EMPTY_BUFFER, true);
|
||||
}
|
||||
|
||||
public final int feedClearBytesForStream(long streamId, ByteBuffer buffer) throws IOException
|
||||
{
|
||||
return feedClearBytesForStream(streamId, buffer, false);
|
||||
}
|
||||
|
||||
public abstract int feedClearBytesForStream(long streamId, ByteBuffer buffer, boolean last) throws IOException;
|
||||
|
||||
public abstract int drainClearBytesForStream(long streamId, ByteBuffer buffer) throws IOException;
|
||||
|
||||
public abstract boolean isStreamFinished(long streamId);
|
||||
|
||||
public abstract CloseInfo getRemoteCloseInfo();
|
||||
|
||||
public static class CloseInfo
|
||||
{
|
||||
private final long error;
|
||||
private final String reason;
|
||||
|
||||
public CloseInfo(long error, String reason)
|
||||
{
|
||||
this.error = error;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public long error()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
public String reason()
|
||||
{
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public interface TokenMinter
|
||||
{
|
||||
int MAX_TOKEN_LENGTH = 48;
|
||||
byte[] mint(byte[] dcid, int len);
|
||||
}
|
||||
|
||||
public interface TokenValidator
|
||||
{
|
||||
byte[] validate(byte[] token, int len);
|
||||
}
|
||||
|
||||
public static class TokenValidationException extends IOException
|
||||
{
|
||||
public TokenValidationException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,42 +18,22 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.ffi.LibQuiche;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint32_t_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint8_t_pointer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class QuicheConnectionId
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(QuicheConnectionId.class);
|
||||
private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
static
|
||||
{
|
||||
LibQuiche.Logging.enable();
|
||||
}
|
||||
|
||||
private final byte[] dcid;
|
||||
private final int hashCode;
|
||||
private final String string;
|
||||
private String string;
|
||||
|
||||
private QuicheConnectionId(byte[] dcid)
|
||||
{
|
||||
this.dcid = Objects.requireNonNull(dcid);
|
||||
this.hashCode = Arrays.hashCode(dcid);
|
||||
this.string = bytesToHex(dcid);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
int c = bytes[i] & 0xFF;
|
||||
hexChars[i * 2] = HEX_ARRAY[c >>> 4];
|
||||
hexChars[i * 2 + 1] = HEX_ARRAY[c & 0x0F];
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,44 +41,13 @@ public class QuicheConnectionId
|
|||
*/
|
||||
public static QuicheConnectionId fromPacket(ByteBuffer packet)
|
||||
{
|
||||
uint8_t_pointer type = new uint8_t_pointer();
|
||||
uint32_t_pointer version = new uint32_t_pointer();
|
||||
|
||||
// Source Connection ID
|
||||
byte[] scid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer scidLen = new size_t_pointer(scid.length);
|
||||
|
||||
// Destination Connection ID
|
||||
byte[] dcid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer dcidLen = new size_t_pointer(dcid.length);
|
||||
|
||||
byte[] token = new byte[48]; // TODO the token buffer size depends on the token minter.
|
||||
size_t_pointer tokenLen = new size_t_pointer(token.length);
|
||||
|
||||
int rc = LibQuiche.INSTANCE.quiche_header_info(packet, new size_t(packet.remaining()), new size_t(LibQuiche.QUICHE_MAX_CONN_ID_LEN),
|
||||
version, type,
|
||||
scid, scidLen,
|
||||
dcid, dcidLen,
|
||||
token, tokenLen);
|
||||
if (rc < 0)
|
||||
return null;
|
||||
byte[] sizedDcid = resizeIfNeeded(dcid, (int)dcidLen.getValue());
|
||||
return new QuicheConnectionId(sizedDcid);
|
||||
}
|
||||
|
||||
private static byte[] resizeIfNeeded(byte[] buffer, int length)
|
||||
{
|
||||
byte[] sizedBuffer;
|
||||
if (length == buffer.length)
|
||||
{
|
||||
sizedBuffer = buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
sizedBuffer = new byte[length];
|
||||
System.arraycopy(buffer, 0, sizedBuffer, 0, sizedBuffer.length);
|
||||
}
|
||||
return sizedBuffer;
|
||||
byte[] bytes = QuicheConnection.QUICHE_BINDING.fromPacket(packet);
|
||||
if (bytes != null && bytes.length == 0)
|
||||
throw new IllegalStateException();
|
||||
QuicheConnectionId connectionId = bytes == null ? null : new QuicheConnectionId(bytes);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("snooped connection ID from packet: [{}]", connectionId);
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -121,6 +70,21 @@ public class QuicheConnectionId
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if (string == null)
|
||||
string = bytesToHex(dcid);
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
private static String bytesToHex(byte[] bytes)
|
||||
{
|
||||
byte[] hexChars = new byte[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++)
|
||||
{
|
||||
int c = bytes[i] & 0xFF;
|
||||
hexChars[i * 2] = HEX_ARRAY[c >>> 4];
|
||||
hexChars[i * 2 + 1] = HEX_ARRAY[c & 0x0F];
|
||||
}
|
||||
return new String(hexChars, StandardCharsets.US_ASCII);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche</artifactId>
|
||||
<version>10.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>quic-quiche-jna</artifactId>
|
||||
<name>Jetty :: QUIC :: Quiche :: JNA Binding</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.quic-quiche-jna</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${maven.checkstyle.plugin.version}</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
module org.eclipse.jetty.quic.quiche.jna
|
||||
{
|
||||
requires com.sun.jna;
|
||||
requires org.eclipse.jetty.quic.quiche;
|
||||
requires org.eclipse.jetty.util;
|
||||
requires org.slf4j;
|
||||
|
||||
// Allow JNA to use reflection on the implementation classes.
|
||||
opens org.eclipse.jetty.quic.quiche.jna to com.sun.jna;
|
||||
opens org.eclipse.jetty.quic.quiche.jna.linux to com.sun.jna;
|
||||
opens org.eclipse.jetty.quic.quiche.jna.macos to com.sun.jna;
|
||||
opens org.eclipse.jetty.quic.quiche.jna.windows to com.sun.jna;
|
||||
|
||||
provides org.eclipse.jetty.quic.quiche.QuicheBinding with
|
||||
org.eclipse.jetty.quic.quiche.jna.JnaQuicheBinding;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.QuicheBinding;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JnaQuicheBinding implements QuicheBinding
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JnaQuicheBinding.class);
|
||||
|
||||
@Override
|
||||
public boolean isUsable()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make a Quiche call to confirm.
|
||||
LibQuiche.INSTANCE.quiche_version();
|
||||
return true;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug("JNA quiche binding is not usable", x);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority()
|
||||
{
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fromPacket(ByteBuffer packet)
|
||||
{
|
||||
return JnaQuicheConnection.fromPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException
|
||||
{
|
||||
return JnaQuicheConnection.connect(quicheConfig, peer, connectionIdLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean negotiate(QuicheConnection.TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException
|
||||
{
|
||||
return JnaQuicheConnection.negotiate(tokenMinter, packetRead, packetToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
|
||||
{
|
||||
return JnaQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, peer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getClass().getSimpleName() + "{p=" + priority() + " u=" + isUsable() + "}";
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -22,28 +22,16 @@ import java.security.SecureRandom;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.ffi.LibQuiche;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.bool_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.char_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.sockaddr_storage;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.ssize_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint32_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint32_t_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint64_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint64_t_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint8_t_pointer;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class QuicheConnection
|
||||
public class JnaQuicheConnection extends QuicheConnection
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(QuicheConnection.class);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JnaQuicheConnection.class);
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
static
|
||||
|
@ -56,18 +44,64 @@ public class QuicheConnection
|
|||
private LibQuiche.quiche_conn quicheConn;
|
||||
private LibQuiche.quiche_config quicheConfig;
|
||||
|
||||
private QuicheConnection(LibQuiche.quiche_conn quicheConn, LibQuiche.quiche_config quicheConfig)
|
||||
private JnaQuicheConnection(LibQuiche.quiche_conn quicheConn, LibQuiche.quiche_config quicheConfig)
|
||||
{
|
||||
this.quicheConn = quicheConn;
|
||||
this.quicheConfig = quicheConfig;
|
||||
}
|
||||
|
||||
public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer) throws IOException
|
||||
public static byte[] fromPacket(ByteBuffer packet)
|
||||
{
|
||||
uint8_t_pointer type = new uint8_t_pointer();
|
||||
uint32_t_pointer version = new uint32_t_pointer();
|
||||
|
||||
// Source Connection ID
|
||||
byte[] scid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer scidLen = new size_t_pointer(scid.length);
|
||||
|
||||
// Destination Connection ID
|
||||
byte[] dcid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer dcidLen = new size_t_pointer(dcid.length);
|
||||
|
||||
byte[] token = new byte[QuicheConnection.TokenMinter.MAX_TOKEN_LENGTH];
|
||||
size_t_pointer tokenLen = new size_t_pointer(token.length);
|
||||
|
||||
LOG.debug("getting header info (fromPacket)...");
|
||||
int rc = LibQuiche.INSTANCE.quiche_header_info(packet, new size_t(packet.remaining()), new size_t(LibQuiche.QUICHE_MAX_CONN_ID_LEN),
|
||||
version, type,
|
||||
scid, scidLen,
|
||||
dcid, dcidLen,
|
||||
token, tokenLen);
|
||||
if (rc < 0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("quiche cannot read header info from packet {}", BufferUtil.toDetailString(packet));
|
||||
return null;
|
||||
}
|
||||
return resizeIfNeeded(dcid, (int)dcidLen.getValue());
|
||||
}
|
||||
|
||||
private static byte[] resizeIfNeeded(byte[] buffer, int length)
|
||||
{
|
||||
byte[] sizedBuffer;
|
||||
if (length == buffer.length)
|
||||
{
|
||||
sizedBuffer = buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
sizedBuffer = new byte[length];
|
||||
System.arraycopy(buffer, 0, sizedBuffer, 0, sizedBuffer.length);
|
||||
}
|
||||
return sizedBuffer;
|
||||
}
|
||||
|
||||
public static JnaQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer) throws IOException
|
||||
{
|
||||
return connect(quicheConfig, peer, LibQuiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
}
|
||||
|
||||
public static QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException
|
||||
public static JnaQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException
|
||||
{
|
||||
if (connectionIdLength > LibQuiche.QUICHE_MAX_CONN_ID_LEN)
|
||||
throw new IOException("Connection ID length is too large: " + connectionIdLength + " > " + LibQuiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
|
@ -77,7 +111,7 @@ public class QuicheConnection
|
|||
|
||||
SizedStructure<sockaddr> s = sockaddr.convert(peer);
|
||||
LibQuiche.quiche_conn quicheConn = LibQuiche.INSTANCE.quiche_connect(peer.getHostName(), scid, new size_t(scid.length), s.getStructure(), s.getSize(), libQuicheConfig);
|
||||
return new QuicheConnection(quicheConn, libQuicheConfig);
|
||||
return new JnaQuicheConnection(quicheConn, libQuicheConfig);
|
||||
}
|
||||
|
||||
private static LibQuiche.quiche_config buildConfig(QuicheConfig config) throws IOException
|
||||
|
@ -168,9 +202,10 @@ public class QuicheConnection
|
|||
byte[] dcid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer dcid_len = new size_t_pointer(dcid.length);
|
||||
|
||||
byte[] token = new byte[512]; // TODO the token buffer size depends on the token minter.
|
||||
byte[] token = new byte[TokenMinter.MAX_TOKEN_LENGTH];
|
||||
size_t_pointer token_len = new size_t_pointer(token.length);
|
||||
|
||||
LOG.debug("getting header info (packetType)...");
|
||||
int rc = LibQuiche.INSTANCE.quiche_header_info(packet, new size_t(packet.remaining()), new size_t(LibQuiche.QUICHE_MAX_CONN_ID_LEN),
|
||||
version, type,
|
||||
scid, scid_len,
|
||||
|
@ -200,10 +235,10 @@ public class QuicheConnection
|
|||
byte[] dcid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer dcid_len = new size_t_pointer(dcid.length);
|
||||
|
||||
byte[] token = new byte[512]; // TODO the token buffer size depends on the token minter.
|
||||
byte[] token = new byte[TokenMinter.MAX_TOKEN_LENGTH];
|
||||
size_t_pointer token_len = new size_t_pointer(token.length);
|
||||
|
||||
LOG.debug("getting header info...");
|
||||
LOG.debug("getting header info (negotiate)...");
|
||||
int rc = LibQuiche.INSTANCE.quiche_header_info(packetRead, new size_t(packetRead.remaining()), new size_t(LibQuiche.QUICHE_MAX_CONN_ID_LEN),
|
||||
version, type,
|
||||
scid, scid_len,
|
||||
|
@ -259,7 +294,7 @@ public class QuicheConnection
|
|||
* Fully consumes the {@code packetRead} buffer if the connection was accepted.
|
||||
* @return an established connection if accept succeeded, null if accept failed and negotiation should be tried.
|
||||
*/
|
||||
public static QuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
|
||||
public static JnaQuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
|
||||
{
|
||||
uint8_t_pointer type = new uint8_t_pointer();
|
||||
uint32_t_pointer version = new uint32_t_pointer();
|
||||
|
@ -272,10 +307,10 @@ public class QuicheConnection
|
|||
byte[] dcid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
|
||||
size_t_pointer dcid_len = new size_t_pointer(dcid.length);
|
||||
|
||||
byte[] token = new byte[512]; // TODO the token buffer size depends on the token minter.
|
||||
byte[] token = new byte[TokenMinter.MAX_TOKEN_LENGTH];
|
||||
size_t_pointer token_len = new size_t_pointer(token.length);
|
||||
|
||||
LOG.debug("getting header info...");
|
||||
LOG.debug("getting header info (tryAccept)...");
|
||||
int rc = LibQuiche.INSTANCE.quiche_header_info(packetRead, new size_t(packetRead.remaining()), new size_t(LibQuiche.QUICHE_MAX_CONN_ID_LEN),
|
||||
version, type,
|
||||
scid, scid_len,
|
||||
|
@ -322,7 +357,7 @@ public class QuicheConnection
|
|||
}
|
||||
|
||||
LOG.debug("connection created");
|
||||
QuicheConnection quicheConnection = new QuicheConnection(quicheConn, libQuicheConfig);
|
||||
JnaQuicheConnection quicheConnection = new JnaQuicheConnection(quicheConn, libQuicheConfig);
|
||||
LOG.debug("accepted, immediately receiving the same packet - remaining in buffer: {}", packetRead.remaining());
|
||||
while (packetRead.hasRemaining())
|
||||
{
|
||||
|
@ -337,17 +372,8 @@ public class QuicheConnection
|
|||
throw new IOException("unable to set qlog path to " + filename);
|
||||
}
|
||||
|
||||
public List<Long> readableStreamIds()
|
||||
{
|
||||
return iterableStreamIds(false);
|
||||
}
|
||||
|
||||
public List<Long> writableStreamIds()
|
||||
{
|
||||
return iterableStreamIds(true);
|
||||
}
|
||||
|
||||
private List<Long> iterableStreamIds(boolean write)
|
||||
@Override
|
||||
protected List<Long> iterableStreamIds(boolean write)
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
|
@ -378,6 +404,7 @@ public class QuicheConnection
|
|||
* @return how many bytes were consumed.
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public int feedCipherBytes(ByteBuffer buffer, SocketAddress peer) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -403,6 +430,7 @@ public class QuicheConnection
|
|||
* @return how many bytes were added to the buffer.
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public int drainCipherBytes(ByteBuffer buffer) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -424,6 +452,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectionClosed()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -434,6 +463,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectionEstablished()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -454,6 +484,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextTimeout()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -464,6 +495,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -474,6 +506,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNegotiatedProtocol()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -487,6 +520,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean close(long error, String reason)
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -509,6 +543,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -522,6 +557,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraining()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -532,6 +568,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxLocalStreams()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -544,6 +581,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long windowCapacity()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -556,6 +594,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long windowCapacity(long streamId) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -572,6 +611,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownStream(long streamId, boolean writeSide, long error) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -586,25 +626,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
public void feedFinForStream(long streamId) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("connection was released");
|
||||
int written = LibQuiche.INSTANCE.quiche_conn_stream_send(quicheConn, new uint64_t(streamId), BufferUtil.EMPTY_BUFFER, new size_t(0), true).intValue();
|
||||
if (written == LibQuiche.quiche_error.QUICHE_ERR_DONE)
|
||||
return;
|
||||
if (written < 0L)
|
||||
throw new IOException("failed to write FIN to stream " + streamId + "; err=" + LibQuiche.quiche_error.errToString(written));
|
||||
}
|
||||
}
|
||||
|
||||
public int feedClearBytesForStream(long streamId, ByteBuffer buffer) throws IOException
|
||||
{
|
||||
return feedClearBytesForStream(streamId, buffer, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int feedClearBytesForStream(long streamId, ByteBuffer buffer, boolean last) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -621,6 +643,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int drainClearBytesForStream(long streamId, ByteBuffer buffer) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -638,6 +661,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreamFinished(long streamId)
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -648,6 +672,7 @@ public class QuicheConnection
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseInfo getRemoteCloseInfo()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
|
@ -663,44 +688,4 @@ public class QuicheConnection
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CloseInfo
|
||||
{
|
||||
private final long error;
|
||||
private final String reason;
|
||||
|
||||
private CloseInfo(long error, String reason)
|
||||
{
|
||||
this.error = error;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public long error()
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
public String reason()
|
||||
{
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public interface TokenMinter
|
||||
{
|
||||
byte[] mint(byte[] dcid, int len);
|
||||
}
|
||||
|
||||
public interface TokenValidator
|
||||
{
|
||||
byte[] validate(byte[] token, int len);
|
||||
}
|
||||
|
||||
public static class TokenValidationException extends IOException
|
||||
{
|
||||
public TokenValidationException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.ptr.ByReference;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi.linux;
|
||||
package org.eclipse.jetty.quic.quiche.jna.linux;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
|
@ -21,11 +21,11 @@ import java.net.SocketAddress;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint16_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint32_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.jna.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint16_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint32_t;
|
||||
|
||||
public interface netinet_linux
|
||||
{
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi.macos;
|
||||
package org.eclipse.jetty.quic.quiche.jna.macos;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
|
@ -21,12 +21,12 @@ import java.net.SocketAddress;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint16_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint32_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint8_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.jna.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint16_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint32_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint8_t;
|
||||
|
||||
public interface netinet_macos
|
||||
{
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.IntegerType;
|
||||
import com.sun.jna.Native;
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.ptr.ByReference;
|
|
@ -11,15 +11,15 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.Structure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.linux.netinet_linux;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.macos.netinet_macos;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.windows.netinet_windows;
|
||||
import org.eclipse.jetty.quic.quiche.jna.linux.netinet_linux;
|
||||
import org.eclipse.jetty.quic.quiche.jna.macos.netinet_macos;
|
||||
import org.eclipse.jetty.quic.quiche.jna.windows.netinet_windows;
|
||||
|
||||
import static com.sun.jna.Platform.isLinux;
|
||||
import static com.sun.jna.Platform.isMac;
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.IntegerType;
|
||||
import com.sun.jna.Native;
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.IntegerType;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.IntegerType;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.ptr.ByReference;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.IntegerType;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.ptr.ByReference;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.IntegerType;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import com.sun.jna.ptr.ByReference;
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.ffi.windows;
|
||||
package org.eclipse.jetty.quic.quiche.jna.windows;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
|
@ -21,11 +21,11 @@ import java.net.SocketAddress;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import com.sun.jna.Structure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint16_t;
|
||||
import org.eclipse.jetty.quic.quiche.ffi.uint32_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.SizedStructure;
|
||||
import org.eclipse.jetty.quic.quiche.jna.size_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.sockaddr;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint16_t;
|
||||
import org.eclipse.jetty.quic.quiche.jna.uint32_t;
|
||||
|
||||
public interface netinet_windows
|
||||
{
|
|
@ -0,0 +1 @@
|
|||
org.eclipse.jetty.quic.quiche.jna.JnaQuicheBinding
|
|
@ -11,7 +11,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche;
|
||||
package org.eclipse.jetty.quic.quiche.jna;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -25,7 +25,9 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.ffi.LibQuiche;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||
import org.eclipse.jetty.quic.quiche.SSLKeyPair;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -37,14 +39,14 @@ import static org.hamcrest.core.Is.is;
|
|||
|
||||
public class LowLevelQuicheTest
|
||||
{
|
||||
private final Collection<QuicheConnection> connectionsToDisposeOf = new ArrayList<>();
|
||||
private final Collection<JnaQuicheConnection> connectionsToDisposeOf = new ArrayList<>();
|
||||
|
||||
private InetSocketAddress clientSocketAddress;
|
||||
private InetSocketAddress serverSocketAddress;
|
||||
private QuicheConfig clientQuicheConfig;
|
||||
private QuicheConfig serverQuicheConfig;
|
||||
private QuicheConnection.TokenMinter tokenMinter;
|
||||
private QuicheConnection.TokenValidator tokenValidator;
|
||||
private JnaQuicheConnection.TokenMinter tokenMinter;
|
||||
private JnaQuicheConnection.TokenValidator tokenValidator;
|
||||
|
||||
@BeforeEach
|
||||
protected void setUp() throws Exception
|
||||
|
@ -88,7 +90,7 @@ public class LowLevelQuicheTest
|
|||
@AfterEach
|
||||
protected void tearDown()
|
||||
{
|
||||
connectionsToDisposeOf.forEach(QuicheConnection::dispose);
|
||||
connectionsToDisposeOf.forEach(JnaQuicheConnection::dispose);
|
||||
connectionsToDisposeOf.clear();
|
||||
}
|
||||
|
||||
|
@ -96,9 +98,9 @@ public class LowLevelQuicheTest
|
|||
public void testFinishedAsSoonAsFinIsFed() throws Exception
|
||||
{
|
||||
// establish connection
|
||||
Map.Entry<QuicheConnection, QuicheConnection> entry = connectClientToServer();
|
||||
QuicheConnection clientQuicheConnection = entry.getKey();
|
||||
QuicheConnection serverQuicheConnection = entry.getValue();
|
||||
Map.Entry<JnaQuicheConnection, JnaQuicheConnection> entry = connectClientToServer();
|
||||
JnaQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
JnaQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
|
||||
// client sends 16 bytes of payload over stream 0
|
||||
assertThat(clientQuicheConnection.feedClearBytesForStream(0, ByteBuffer.allocate(16)
|
||||
|
@ -136,9 +138,9 @@ public class LowLevelQuicheTest
|
|||
public void testNotFinishedAsLongAsStreamHasReadableBytes() throws Exception
|
||||
{
|
||||
// establish connection
|
||||
Map.Entry<QuicheConnection, QuicheConnection> entry = connectClientToServer();
|
||||
QuicheConnection clientQuicheConnection = entry.getKey();
|
||||
QuicheConnection serverQuicheConnection = entry.getValue();
|
||||
Map.Entry<JnaQuicheConnection, JnaQuicheConnection> entry = connectClientToServer();
|
||||
JnaQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
JnaQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
|
||||
// client sends 16 bytes of payload over stream 0 and finish it
|
||||
assertThat(clientQuicheConnection.feedClearBytesForStream(0, ByteBuffer.allocate(16)
|
||||
|
@ -172,18 +174,18 @@ public class LowLevelQuicheTest
|
|||
clientQuicheConfig.setApplicationProtos("€");
|
||||
|
||||
// establish connection
|
||||
Map.Entry<QuicheConnection, QuicheConnection> entry = connectClientToServer();
|
||||
QuicheConnection clientQuicheConnection = entry.getKey();
|
||||
QuicheConnection serverQuicheConnection = entry.getValue();
|
||||
Map.Entry<JnaQuicheConnection, JnaQuicheConnection> entry = connectClientToServer();
|
||||
JnaQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
JnaQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
|
||||
assertThat(clientQuicheConnection.getNegotiatedProtocol(), is("€"));
|
||||
assertThat(serverQuicheConnection.getNegotiatedProtocol(), is("€"));
|
||||
}
|
||||
|
||||
private void drainServerToFeedClient(Map.Entry<QuicheConnection, QuicheConnection> entry, int expectedSize) throws IOException
|
||||
private void drainServerToFeedClient(Map.Entry<JnaQuicheConnection, JnaQuicheConnection> entry, int expectedSize) throws IOException
|
||||
{
|
||||
QuicheConnection clientQuicheConnection = entry.getKey();
|
||||
QuicheConnection serverQuicheConnection = entry.getValue();
|
||||
JnaQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
JnaQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(LibQuiche.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
|
||||
int drained = serverQuicheConnection.drainCipherBytes(buffer);
|
||||
|
@ -193,10 +195,10 @@ public class LowLevelQuicheTest
|
|||
assertThat(fed, is(expectedSize));
|
||||
}
|
||||
|
||||
private void drainClientToFeedServer(Map.Entry<QuicheConnection, QuicheConnection> entry, int expectedSize) throws IOException
|
||||
private void drainClientToFeedServer(Map.Entry<JnaQuicheConnection, JnaQuicheConnection> entry, int expectedSize) throws IOException
|
||||
{
|
||||
QuicheConnection clientQuicheConnection = entry.getKey();
|
||||
QuicheConnection serverQuicheConnection = entry.getValue();
|
||||
JnaQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
JnaQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(LibQuiche.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
|
||||
int drained = clientQuicheConnection.drainCipherBytes(buffer);
|
||||
|
@ -206,21 +208,21 @@ public class LowLevelQuicheTest
|
|||
assertThat(fed, is(expectedSize));
|
||||
}
|
||||
|
||||
private Map.Entry<QuicheConnection, QuicheConnection> connectClientToServer() throws IOException
|
||||
private Map.Entry<JnaQuicheConnection, JnaQuicheConnection> connectClientToServer() throws IOException
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate(LibQuiche.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(LibQuiche.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
|
||||
QuicheConnection clientQuicheConnection = QuicheConnection.connect(clientQuicheConfig, serverSocketAddress);
|
||||
JnaQuicheConnection clientQuicheConnection = JnaQuicheConnection.connect(clientQuicheConfig, serverSocketAddress);
|
||||
connectionsToDisposeOf.add(clientQuicheConnection);
|
||||
|
||||
int drained = clientQuicheConnection.drainCipherBytes(buffer);
|
||||
assertThat(drained, is(1200));
|
||||
buffer.flip();
|
||||
|
||||
QuicheConnection serverQuicheConnection = QuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, clientSocketAddress);
|
||||
JnaQuicheConnection serverQuicheConnection = JnaQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, clientSocketAddress);
|
||||
assertThat(serverQuicheConnection, is(nullValue()));
|
||||
boolean negotiated = QuicheConnection.negotiate(tokenMinter, buffer, buffer2);
|
||||
boolean negotiated = JnaQuicheConnection.negotiate(tokenMinter, buffer, buffer2);
|
||||
assertThat(negotiated, is(true));
|
||||
buffer2.flip();
|
||||
|
||||
|
@ -232,7 +234,7 @@ public class LowLevelQuicheTest
|
|||
assertThat(drained, is(1200));
|
||||
buffer.flip();
|
||||
|
||||
serverQuicheConnection = QuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, clientSocketAddress);
|
||||
serverQuicheConnection = JnaQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, clientSocketAddress);
|
||||
assertThat(serverQuicheConnection, is(not(nullValue())));
|
||||
connectionsToDisposeOf.add(serverQuicheConnection);
|
||||
|
||||
|
@ -247,7 +249,7 @@ public class LowLevelQuicheTest
|
|||
assertThat(serverQuicheConnection.isConnectionEstablished(), is(false));
|
||||
assertThat(clientQuicheConnection.isConnectionEstablished(), is(false));
|
||||
|
||||
AbstractMap.SimpleImmutableEntry<QuicheConnection, QuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
|
||||
AbstractMap.SimpleImmutableEntry<JnaQuicheConnection, JnaQuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
|
||||
|
||||
int protosLen = 0;
|
||||
for (String proto : clientQuicheConfig.getApplicationProtos())
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-parent</artifactId>
|
||||
<version>10.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>quic-quiche-panama-jdk</artifactId>
|
||||
<name>Jetty :: QUIC :: Quiche :: Panama Binding (jdk package)</name>
|
||||
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.quic-quiche-panama-jdk</bundle-symbolic-name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>${maven.checkstyle.plugin.version}</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<compilerArgs>
|
||||
<arg>--add-modules</arg>
|
||||
<arg>jdk.incubator.foreign</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<argLine>
|
||||
@{argLine} ${jetty.surefire.argLine} --enable-native-access org.eclipse.jetty.quic.quiche.panama.jdk
|
||||
</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
module org.eclipse.jetty.quic.quiche.panama.jdk
|
||||
{
|
||||
requires jdk.incubator.foreign;
|
||||
requires org.eclipse.jetty.quic.quiche;
|
||||
requires org.eclipse.jetty.util;
|
||||
requires org.slf4j;
|
||||
|
||||
provides org.eclipse.jetty.quic.quiche.QuicheBinding with
|
||||
org.eclipse.jetty.quic.quiche.panama.jdk.PanamaJdkQuicheBinding;
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Locale;
|
||||
|
||||
import jdk.incubator.foreign.CLinker;
|
||||
import jdk.incubator.foreign.FunctionDescriptor;
|
||||
import jdk.incubator.foreign.MemoryAddress;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
import jdk.incubator.foreign.SymbolLookup;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
class NativeHelper
|
||||
{
|
||||
private static final CLinker LINKER = CLinker.getInstance();
|
||||
private static final ClassLoader CLASSLOADER = NativeHelper.class.getClassLoader();
|
||||
private static final SymbolLookup LIBRARIES = lookup();
|
||||
private static final MethodHandles.Lookup MH_LOOKUP = MethodHandles.lookup();
|
||||
|
||||
private static void loadNativeLibraryFromClasspath() {
|
||||
try
|
||||
{
|
||||
String libName = getNativePrefix() + "/" + System.mapLibraryName("quiche");
|
||||
File lib = extractFromResourcePath(libName, NativeHelper.class.getClassLoader());
|
||||
System.load(lib.getAbsolutePath());
|
||||
//TODO delete lib?
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError("Cannot find quiche native library").initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getNativePrefix()
|
||||
{
|
||||
// TODO: add macos and windows
|
||||
String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||
|
||||
String osArch = System.getProperty("os.arch");
|
||||
switch (osArch)
|
||||
{
|
||||
case "amd64":
|
||||
osArch = "x86-64";
|
||||
break;
|
||||
// TODO: add arm64
|
||||
}
|
||||
|
||||
return osName + "-" + osArch;
|
||||
}
|
||||
|
||||
private static File extractFromResourcePath(String libName, ClassLoader classLoader) throws IOException
|
||||
{
|
||||
File target = new File(System.getProperty("java.io.tmpdir"), libName);
|
||||
target.getParentFile().mkdir();
|
||||
try (InputStream is = classLoader.getResourceAsStream(libName); OutputStream os = new FileOutputStream(target))
|
||||
{
|
||||
IO.copy(is, os);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
private static SymbolLookup lookup()
|
||||
{
|
||||
loadNativeLibraryFromClasspath();
|
||||
SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
|
||||
SymbolLookup systemLookup = CLinker.systemLookup();
|
||||
return name -> loaderLookup.lookup(name).or(() -> systemLookup.lookup(name));
|
||||
}
|
||||
|
||||
static MethodHandle downcallHandle(String name, String desc, FunctionDescriptor fdesc)
|
||||
{
|
||||
return LIBRARIES.lookup(name)
|
||||
.map(addr ->
|
||||
{
|
||||
MethodType mt = MethodType.fromMethodDescriptorString(desc, CLASSLOADER);
|
||||
return LINKER.downcallHandle(addr, mt, fdesc);
|
||||
})
|
||||
.orElseThrow(() ->
|
||||
{
|
||||
throw new UnsatisfiedLinkError("unresolved symbol: " + name);
|
||||
});
|
||||
}
|
||||
|
||||
static <T> MemoryAddress upcallHandle(Class<T> clazz, T t, String name, String desc, FunctionDescriptor fdesc, ResourceScope scope)
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodHandle handle = MH_LOOKUP.findVirtual(clazz, name, MethodType.fromMethodDescriptorString(desc, CLASSLOADER));
|
||||
handle = handle.bindTo(t);
|
||||
return LINKER.upcallStub(handle, fdesc, scope);
|
||||
}
|
||||
catch (NoSuchMethodException | IllegalAccessException e)
|
||||
{
|
||||
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError("unresolved symbol: " + name).initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.QuicheBinding;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PanamaJdkQuicheBinding implements QuicheBinding
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PanamaJdkQuicheBinding.class);
|
||||
|
||||
@Override
|
||||
public boolean isUsable()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make a Quiche call to confirm.
|
||||
quiche_h.quiche_version();
|
||||
return true;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug("Panama(jdk) quiche binding is not usable", x);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority()
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fromPacket(ByteBuffer packet)
|
||||
{
|
||||
return PanamaJdkQuicheConnection.fromPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException
|
||||
{
|
||||
return PanamaJdkQuicheConnection.connect(quicheConfig, peer, connectionIdLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean negotiate(QuicheConnection.TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException
|
||||
{
|
||||
return PanamaJdkQuicheConnection.negotiate(tokenMinter, packetRead, packetToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
|
||||
{
|
||||
return PanamaJdkQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, peer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getClass().getSimpleName() + "{p=" + priority() + " u=" + isUsable() + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,890 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.incubator.foreign.CLinker;
|
||||
import jdk.incubator.foreign.MemoryAddress;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.eclipse.jetty.quic.quiche.panama.jdk.quiche_h.C_FALSE;
|
||||
import static org.eclipse.jetty.quic.quiche.panama.jdk.quiche_h.C_TRUE;
|
||||
|
||||
public class PanamaJdkQuicheConnection extends QuicheConnection
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PanamaJdkQuicheConnection.class);
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
// Quiche does not allow concurrent calls with the same connection.
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private MemoryAddress quicheConn;
|
||||
private MemoryAddress quicheConfig;
|
||||
private ResourceScope scope;
|
||||
private MemorySegment sendInfo;
|
||||
private MemorySegment recvInfo;
|
||||
private MemorySegment stats;
|
||||
|
||||
private PanamaJdkQuicheConnection(MemoryAddress quicheConn, MemoryAddress quicheConfig, ResourceScope scope)
|
||||
{
|
||||
this.quicheConn = quicheConn;
|
||||
this.quicheConfig = quicheConfig;
|
||||
this.scope = scope;
|
||||
this.sendInfo = quiche_send_info.allocate(scope);
|
||||
this.recvInfo = quiche_recv_info.allocate(scope);
|
||||
this.stats = quiche_stats.allocate(scope);
|
||||
}
|
||||
|
||||
public static byte[] fromPacket(ByteBuffer packet)
|
||||
{
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
MemorySegment type = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
|
||||
MemorySegment version = MemorySegment.allocateNative(CLinker.C_INT, scope);
|
||||
|
||||
// Source Connection ID
|
||||
MemorySegment scid = MemorySegment.allocateNative(quiche_h.QUICHE_MAX_CONN_ID_LEN, scope);
|
||||
MemorySegment scid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(scid.byteSize());
|
||||
|
||||
// Destination Connection ID
|
||||
MemorySegment dcid = MemorySegment.allocateNative(quiche_h.QUICHE_MAX_CONN_ID_LEN, scope);
|
||||
MemorySegment dcid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(dcid.byteSize());
|
||||
|
||||
MemorySegment token = MemorySegment.allocateNative(QuicheConnection.TokenMinter.MAX_TOKEN_LENGTH, scope);
|
||||
MemorySegment token_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
token_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(token.byteSize());
|
||||
|
||||
LOG.debug("getting header info (fromPacket)...");
|
||||
int rc;
|
||||
|
||||
if (packet.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
MemorySegment packetReadSegment = MemorySegment.ofByteBuffer(packet);
|
||||
rc = quiche_h.quiche_header_info(packetReadSegment.address(), packet.remaining(), quiche_h.QUICHE_MAX_CONN_ID_LEN,
|
||||
version.address(), type.address(),
|
||||
scid.address(), scid_len.address(),
|
||||
dcid.address(), dcid_len.address(),
|
||||
token.address(), token_len.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
|
||||
MemorySegment packetReadSegment = MemorySegment.allocateNative(packet.remaining(), scope);
|
||||
int prevPosition = packet.position();
|
||||
packetReadSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(packet);
|
||||
packet.position(prevPosition);
|
||||
rc = quiche_h.quiche_header_info(packetReadSegment.address(), packet.remaining(), quiche_h.QUICHE_MAX_CONN_ID_LEN,
|
||||
version.address(), type.address(),
|
||||
scid.address(), scid_len.address(),
|
||||
dcid.address(), dcid_len.address(),
|
||||
token.address(), token_len.address());
|
||||
}
|
||||
if (rc < 0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("quiche cannot read header info from packet {}", BufferUtil.toDetailString(packet));
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[(int)dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong()];
|
||||
dcid.asByteBuffer().order(ByteOrder.nativeOrder()).get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static PanamaJdkQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer) throws IOException
|
||||
{
|
||||
return connect(quicheConfig, peer, quiche_h.QUICHE_MAX_CONN_ID_LEN);
|
||||
}
|
||||
|
||||
public static PanamaJdkQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress peer, int connectionIdLength) throws IOException
|
||||
{
|
||||
if (connectionIdLength > quiche_h.QUICHE_MAX_CONN_ID_LEN)
|
||||
throw new IOException("Connection ID length is too large: " + connectionIdLength + " > " + quiche_h.QUICHE_MAX_CONN_ID_LEN);
|
||||
ResourceScope scope = ResourceScope.newSharedScope();
|
||||
byte[] scidBytes = new byte[connectionIdLength];
|
||||
SECURE_RANDOM.nextBytes(scidBytes);
|
||||
MemorySegment scid = MemorySegment.allocateNative(scidBytes.length, scope);
|
||||
scid.asByteBuffer().order(ByteOrder.nativeOrder()).put(scidBytes);
|
||||
MemoryAddress libQuicheConfig = buildConfig(quicheConfig, scope);
|
||||
|
||||
MemorySegment s = sockaddr.convert(peer, scope);
|
||||
MemoryAddress quicheConn = quiche_h.quiche_connect(CLinker.toCString(peer.getHostName(), scope), scid, scid.byteSize(), s, s.byteSize(), libQuicheConfig);
|
||||
return new PanamaJdkQuicheConnection(quicheConn, libQuicheConfig, scope);
|
||||
}
|
||||
|
||||
private static MemoryAddress buildConfig(QuicheConfig config, ResourceScope scope) throws IOException
|
||||
{
|
||||
MemoryAddress quicheConfig = quiche_h.quiche_config_new(config.getVersion());
|
||||
if (quicheConfig == null)
|
||||
throw new IOException("Failed to create quiche config");
|
||||
|
||||
Boolean verifyPeer = config.getVerifyPeer();
|
||||
if (verifyPeer != null)
|
||||
quiche_h.quiche_config_verify_peer(quicheConfig, verifyPeer ? C_TRUE : C_FALSE);
|
||||
|
||||
String certChainPemPath = config.getCertChainPemPath();
|
||||
if (certChainPemPath != null)
|
||||
quiche_h.quiche_config_load_cert_chain_from_pem_file(quicheConfig, CLinker.toCString(certChainPemPath, scope).address());
|
||||
|
||||
String privKeyPemPath = config.getPrivKeyPemPath();
|
||||
if (privKeyPemPath != null)
|
||||
quiche_h.quiche_config_load_priv_key_from_pem_file(quicheConfig, CLinker.toCString(privKeyPemPath, scope).address());
|
||||
|
||||
String[] applicationProtos = config.getApplicationProtos();
|
||||
if (applicationProtos != null)
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
for (String proto : applicationProtos)
|
||||
{
|
||||
byte[] bytes = proto.getBytes(StandardCharsets.UTF_8);
|
||||
baos.write(bytes.length);
|
||||
baos.write(bytes);
|
||||
}
|
||||
byte[] bytes = baos.toByteArray();
|
||||
MemorySegment segment = MemorySegment.allocateNative(bytes.length, scope);
|
||||
segment.asByteBuffer().order(ByteOrder.nativeOrder()).put(bytes);
|
||||
quiche_h.quiche_config_set_application_protos(quicheConfig, segment.address(), segment.byteSize());
|
||||
}
|
||||
|
||||
QuicheConfig.CongestionControl cc = config.getCongestionControl();
|
||||
if (cc != null)
|
||||
quiche_h.quiche_config_set_cc_algorithm(quicheConfig, cc.getValue());
|
||||
|
||||
Long maxIdleTimeout = config.getMaxIdleTimeout();
|
||||
if (maxIdleTimeout != null)
|
||||
quiche_h.quiche_config_set_max_idle_timeout(quicheConfig, maxIdleTimeout);
|
||||
|
||||
Long initialMaxData = config.getInitialMaxData();
|
||||
if (initialMaxData != null)
|
||||
quiche_h.quiche_config_set_initial_max_data(quicheConfig, initialMaxData);
|
||||
|
||||
Long initialMaxStreamDataBidiLocal = config.getInitialMaxStreamDataBidiLocal();
|
||||
if (initialMaxStreamDataBidiLocal != null)
|
||||
quiche_h.quiche_config_set_initial_max_stream_data_bidi_local(quicheConfig, initialMaxStreamDataBidiLocal);
|
||||
|
||||
Long initialMaxStreamDataBidiRemote = config.getInitialMaxStreamDataBidiRemote();
|
||||
if (initialMaxStreamDataBidiRemote != null)
|
||||
quiche_h.quiche_config_set_initial_max_stream_data_bidi_remote(quicheConfig, initialMaxStreamDataBidiRemote);
|
||||
|
||||
Long initialMaxStreamDataUni = config.getInitialMaxStreamDataUni();
|
||||
if (initialMaxStreamDataUni != null)
|
||||
quiche_h.quiche_config_set_initial_max_stream_data_uni(quicheConfig, initialMaxStreamDataUni);
|
||||
|
||||
Long initialMaxStreamsBidi = config.getInitialMaxStreamsBidi();
|
||||
if (initialMaxStreamsBidi != null)
|
||||
quiche_h.quiche_config_set_initial_max_streams_bidi(quicheConfig, initialMaxStreamsBidi);
|
||||
|
||||
Long initialMaxStreamsUni = config.getInitialMaxStreamsUni();
|
||||
if (initialMaxStreamsUni != null)
|
||||
quiche_h.quiche_config_set_initial_max_streams_uni(quicheConfig, initialMaxStreamsUni);
|
||||
|
||||
Boolean disableActiveMigration = config.getDisableActiveMigration();
|
||||
if (disableActiveMigration != null)
|
||||
quiche_h.quiche_config_set_disable_active_migration(quicheConfig, disableActiveMigration ? C_TRUE : C_FALSE);
|
||||
|
||||
return quicheConfig;
|
||||
}
|
||||
|
||||
public static boolean negotiate(TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException
|
||||
{
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
MemorySegment packetReadSegment;
|
||||
if (packetRead.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
packetReadSegment = MemorySegment.ofByteBuffer(packetRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
|
||||
packetReadSegment = MemorySegment.allocateNative(packetRead.remaining(), scope);
|
||||
int prevPosition = packetRead.position();
|
||||
packetReadSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(packetRead);
|
||||
packetRead.position(prevPosition);
|
||||
}
|
||||
|
||||
MemorySegment type = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
|
||||
MemorySegment version = MemorySegment.allocateNative(CLinker.C_INT, scope);
|
||||
|
||||
// Source Connection ID
|
||||
MemorySegment scid = MemorySegment.allocateNative(quiche_h.QUICHE_MAX_CONN_ID_LEN, scope);
|
||||
MemorySegment scid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(scid.byteSize());
|
||||
|
||||
// Destination Connection ID
|
||||
MemorySegment dcid = MemorySegment.allocateNative(quiche_h.QUICHE_MAX_CONN_ID_LEN, scope);
|
||||
MemorySegment dcid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(dcid.byteSize());
|
||||
|
||||
MemorySegment token = MemorySegment.allocateNative(TokenMinter.MAX_TOKEN_LENGTH, scope);
|
||||
MemorySegment token_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
token_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(token.byteSize());
|
||||
|
||||
LOG.debug("getting header info (negotiate)...");
|
||||
int rc = quiche_h.quiche_header_info(packetReadSegment.address(), packetRead.remaining(), quiche_h.QUICHE_MAX_CONN_ID_LEN,
|
||||
version.address(), type.address(),
|
||||
scid.address(), scid_len.address(),
|
||||
dcid.address(), dcid_len.address(),
|
||||
token.address(), token_len.address());
|
||||
if (rc < 0)
|
||||
throw new IOException("failed to parse header: " + quiche_h.quiche_error.errToString(rc));
|
||||
packetRead.position(packetRead.limit());
|
||||
|
||||
LOG.debug("version: {}", version.asByteBuffer().order(ByteOrder.nativeOrder()).getInt());
|
||||
LOG.debug("type: {}", type.asByteBuffer().order(ByteOrder.nativeOrder()).get());
|
||||
LOG.debug("scid len: {}", scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
LOG.debug("dcid len: {}", dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
LOG.debug("token len: {}", token_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
|
||||
if (quiche_h.quiche_version_is_supported(version.asByteBuffer().order(ByteOrder.nativeOrder()).getInt()) == C_FALSE)
|
||||
{
|
||||
LOG.debug("version negotiation");
|
||||
|
||||
MemorySegment packetToSendSegment;
|
||||
if (packetToSend.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
packetToSendSegment = MemorySegment.ofByteBuffer(packetToSend);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
|
||||
packetToSendSegment = MemorySegment.allocateNative(packetToSend.remaining(), scope);
|
||||
}
|
||||
|
||||
long generated = quiche_h.quiche_negotiate_version(scid.address(), scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong(), dcid.address(), dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong(), packetToSendSegment.address(), packetToSend.remaining());
|
||||
if (generated < 0)
|
||||
throw new IOException("failed to create vneg packet : " + quiche_h.quiche_error.errToString(generated));
|
||||
if (!packetToSend.isDirect())
|
||||
packetToSend.put(packetToSendSegment.asByteBuffer().order(ByteOrder.nativeOrder()).limit((int)generated));
|
||||
else
|
||||
packetToSend.position((int)(packetToSend.position() + generated));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong() == 0L)
|
||||
{
|
||||
LOG.debug("stateless retry");
|
||||
|
||||
byte[] dcidBytes = new byte[(int)dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong()];
|
||||
dcid.asByteBuffer().order(ByteOrder.nativeOrder()).get(dcidBytes);
|
||||
byte[] tokenBytes = tokenMinter.mint(dcidBytes, dcidBytes.length);
|
||||
token.asByteBuffer().order(ByteOrder.nativeOrder()).put(tokenBytes);
|
||||
|
||||
byte[] newCid = new byte[quiche_h.QUICHE_MAX_CONN_ID_LEN];
|
||||
SECURE_RANDOM.nextBytes(newCid);
|
||||
MemorySegment newCidSegment = MemorySegment.allocateNative(newCid.length, scope);
|
||||
newCidSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(newCid);
|
||||
|
||||
MemorySegment packetToSendSegment;
|
||||
if (packetToSend.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
packetToSendSegment = MemorySegment.ofByteBuffer(packetToSend);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
|
||||
packetToSendSegment = MemorySegment.allocateNative(packetToSend.remaining(), scope);
|
||||
}
|
||||
|
||||
long generated = quiche_h.quiche_retry(scid.address(), scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong(),
|
||||
dcid.address(), dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong(),
|
||||
newCidSegment.address(), newCid.length,
|
||||
token.address(), tokenBytes.length,
|
||||
version.asByteBuffer().order(ByteOrder.nativeOrder()).getInt(),
|
||||
packetToSendSegment.address(), packetToSendSegment.byteSize()
|
||||
);
|
||||
if (generated < 0)
|
||||
throw new IOException("failed to create retry packet: " + quiche_h.quiche_error.errToString(generated));
|
||||
if (!packetToSend.isDirect())
|
||||
packetToSend.put(packetToSendSegment.asByteBuffer().order(ByteOrder.nativeOrder()).limit((int)generated));
|
||||
else
|
||||
packetToSend.position((int)(packetToSend.position() + generated));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static PanamaJdkQuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress peer) throws IOException
|
||||
{
|
||||
ResourceScope scope = ResourceScope.newSharedScope();
|
||||
|
||||
MemorySegment type = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
|
||||
MemorySegment version = MemorySegment.allocateNative(CLinker.C_INT, scope);
|
||||
|
||||
// Source Connection ID
|
||||
MemorySegment scid = MemorySegment.allocateNative(quiche_h.QUICHE_MAX_CONN_ID_LEN, scope);
|
||||
MemorySegment scid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(scid.byteSize());
|
||||
|
||||
// Destination Connection ID
|
||||
MemorySegment dcid = MemorySegment.allocateNative(quiche_h.QUICHE_MAX_CONN_ID_LEN, scope);
|
||||
MemorySegment dcid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(dcid.byteSize());
|
||||
|
||||
MemorySegment token = MemorySegment.allocateNative(TokenMinter.MAX_TOKEN_LENGTH, scope);
|
||||
MemorySegment token_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
token_len.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(token.byteSize());
|
||||
|
||||
LOG.debug("getting header info (tryAccept)...");
|
||||
int rc;
|
||||
|
||||
if (packetRead.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
MemorySegment packetReadSegment = MemorySegment.ofByteBuffer(packetRead);
|
||||
rc = quiche_h.quiche_header_info(packetReadSegment.address(), packetRead.remaining(), quiche_h.QUICHE_MAX_CONN_ID_LEN,
|
||||
version.address(), type.address(),
|
||||
scid.address(), scid_len.address(),
|
||||
dcid.address(), dcid_len.address(),
|
||||
token.address(), token_len.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
|
||||
try (ResourceScope segmentScope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
MemorySegment packetReadSegment = MemorySegment.allocateNative(packetRead.remaining(), segmentScope);
|
||||
int prevPosition = packetRead.position();
|
||||
packetReadSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(packetRead);
|
||||
packetRead.position(prevPosition);
|
||||
rc = quiche_h.quiche_header_info(packetReadSegment.address(), packetRead.remaining(), quiche_h.QUICHE_MAX_CONN_ID_LEN,
|
||||
version.address(), type.address(),
|
||||
scid.address(), scid_len.address(),
|
||||
dcid.address(), dcid_len.address(),
|
||||
token.address(), token_len.address());
|
||||
}
|
||||
}
|
||||
if (rc < 0)
|
||||
{
|
||||
scope.close();
|
||||
throw new IOException("failed to parse header: " + quiche_h.quiche_error.errToString(rc));
|
||||
}
|
||||
|
||||
LOG.debug("version: {}", version.asByteBuffer().order(ByteOrder.nativeOrder()).getInt());
|
||||
LOG.debug("type: {}", type.asByteBuffer().order(ByteOrder.nativeOrder()).get());
|
||||
LOG.debug("scid len: {}", scid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
LOG.debug("dcid len: {}", dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
LOG.debug("token len: {}", token_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
|
||||
if (quiche_h.quiche_version_is_supported(version.asByteBuffer().order(ByteOrder.nativeOrder()).getInt()) == C_FALSE)
|
||||
{
|
||||
LOG.debug("need version negotiation");
|
||||
scope.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
int tokenLen = (int)token_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong();
|
||||
if (tokenLen == 0)
|
||||
{
|
||||
LOG.debug("need stateless retry");
|
||||
scope.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
LOG.debug("token validation...");
|
||||
// Original Destination Connection ID
|
||||
byte[] tokenBytes = new byte[(int)token.byteSize()];
|
||||
token.asByteBuffer().order(ByteOrder.nativeOrder()).get(tokenBytes, 0, tokenLen);
|
||||
byte[] odcidBytes = tokenValidator.validate(tokenBytes, tokenLen);
|
||||
if (odcidBytes == null)
|
||||
{
|
||||
scope.close();
|
||||
throw new TokenValidationException("invalid address validation token");
|
||||
}
|
||||
LOG.debug("validated token");
|
||||
MemorySegment odcid = MemorySegment.allocateNative(odcidBytes.length, scope);
|
||||
odcid.asByteBuffer().order(ByteOrder.nativeOrder()).put(odcidBytes);
|
||||
|
||||
LOG.debug("connection creation...");
|
||||
MemoryAddress libQuicheConfig = buildConfig(quicheConfig, scope);
|
||||
|
||||
MemorySegment s = sockaddr.convert(peer, scope);
|
||||
MemoryAddress quicheConn = quiche_h.quiche_accept(dcid.address(), dcid_len.asByteBuffer().order(ByteOrder.nativeOrder()).getLong(), odcid.address(), odcid.byteSize(), s.address(), s.byteSize(), libQuicheConfig);
|
||||
|
||||
if (quicheConn == null)
|
||||
{
|
||||
quiche_h.quiche_config_free(libQuicheConfig);
|
||||
scope.close();
|
||||
throw new IOException("failed to create connection");
|
||||
}
|
||||
|
||||
LOG.debug("connection created");
|
||||
PanamaJdkQuicheConnection quicheConnection = new PanamaJdkQuicheConnection(quicheConn, libQuicheConfig, scope);
|
||||
LOG.debug("accepted, immediately receiving the same packet - remaining in buffer: {}", packetRead.remaining());
|
||||
while (packetRead.hasRemaining())
|
||||
{
|
||||
quicheConnection.feedCipherBytes(packetRead, peer);
|
||||
}
|
||||
return quicheConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Long> iterableStreamIds(boolean write)
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
|
||||
MemoryAddress quiche_stream_iter;
|
||||
if (write)
|
||||
quiche_stream_iter = quiche_h.quiche_conn_writable(quicheConn);
|
||||
else
|
||||
quiche_stream_iter = quiche_h.quiche_conn_readable(quicheConn);
|
||||
|
||||
List<Long> result = new ArrayList<>();
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
MemorySegment streamIdSegment = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
while (quiche_h.quiche_stream_iter_next(quiche_stream_iter, streamIdSegment.address()) != C_FALSE)
|
||||
{
|
||||
long streamId = streamIdSegment.asByteBuffer().order(ByteOrder.nativeOrder()).getLong();
|
||||
result.add(streamId);
|
||||
}
|
||||
}
|
||||
|
||||
quiche_h.quiche_stream_iter_free(quiche_stream_iter);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int feedCipherBytes(ByteBuffer buffer, SocketAddress peer) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("Cannot receive when not connected");
|
||||
|
||||
long received;
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
quiche_recv_info.setSocketAddress(recvInfo, peer, scope);
|
||||
if (buffer.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
MemorySegment bufferSegment = MemorySegment.ofByteBuffer(buffer);
|
||||
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment.address(), buffer.remaining(), recvInfo.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
|
||||
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
|
||||
int prevPosition = buffer.position();
|
||||
bufferSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(buffer);
|
||||
buffer.position(prevPosition);
|
||||
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment.address(), buffer.remaining(), recvInfo.address());
|
||||
}
|
||||
}
|
||||
if (received < 0)
|
||||
throw new IOException("failed to receive packet; err=" + quiche_h.quiche_error.errToString(received));
|
||||
buffer.position((int)(buffer.position() + received));
|
||||
return (int)received;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int drainCipherBytes(ByteBuffer buffer) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("Cannot send when not connected");
|
||||
|
||||
int prevPosition = buffer.position();
|
||||
long written;
|
||||
if (buffer.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
MemorySegment bufferSegment = MemorySegment.ofByteBuffer(buffer);
|
||||
written = quiche_h.quiche_conn_send(quicheConn, bufferSegment.address(), buffer.remaining(), sendInfo.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
|
||||
written = quiche_h.quiche_conn_send(quicheConn, bufferSegment.address(), buffer.remaining(), sendInfo.address());
|
||||
buffer.put(bufferSegment.asByteBuffer().order(ByteOrder.nativeOrder()).slice().limit((int)written));
|
||||
buffer.position(prevPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (written == quiche_h.quiche_error.QUICHE_ERR_DONE)
|
||||
return 0;
|
||||
if (written < 0L)
|
||||
throw new IOException("failed to send packet; err=" + quiche_h.quiche_error.errToString(written));
|
||||
buffer.position((int)(prevPosition + written));
|
||||
return (int)written;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectionClosed()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
return quiche_h.quiche_conn_is_closed(quicheConn) != C_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectionEstablished()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
return quiche_h.quiche_conn_is_established(quicheConn) != C_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextTimeout()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
return quiche_h.quiche_conn_timeout_as_millis(quicheConn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
quiche_h.quiche_conn_on_timeout(quicheConn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNegotiatedProtocol()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock(); ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
|
||||
MemorySegment outSegment = MemorySegment.allocateNative(CLinker.C_POINTER, scope);
|
||||
MemorySegment outLenSegment = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
quiche_h.quiche_conn_application_proto(quicheConn, outSegment.address(), outLenSegment.address());
|
||||
|
||||
long outLen = outLenSegment.asByteBuffer().order(ByteOrder.nativeOrder()).getLong();
|
||||
if (outLen == 0L)
|
||||
return null;
|
||||
byte[] out = new byte[(int)outLen];
|
||||
// dereference outSegment pointer
|
||||
MemoryAddress memoryAddress = MemoryAddress.ofLong(outSegment.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
memoryAddress.asSegment(outLen, ResourceScope.globalScope()).asByteBuffer().get(out);
|
||||
|
||||
return new String(out, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean close(long error, String reason)
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("connection was released");
|
||||
return false;
|
||||
}
|
||||
|
||||
int rc;
|
||||
if (reason == null)
|
||||
{
|
||||
rc = quiche_h.quiche_conn_close(quicheConn, C_TRUE, error, MemoryAddress.NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8);
|
||||
MemorySegment reasonSegment = MemorySegment.allocateNative(reasonBytes.length, scope);
|
||||
reasonSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(reasonBytes);
|
||||
int length = reasonBytes.length;
|
||||
MemoryAddress reasonAddress = reasonSegment.address();
|
||||
rc = quiche_h.quiche_conn_close(quicheConn, C_TRUE, error, reasonAddress, length);
|
||||
}
|
||||
}
|
||||
|
||||
if (rc == 0)
|
||||
return true;
|
||||
if (rc == quiche_h.quiche_error.QUICHE_ERR_DONE)
|
||||
return false;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("could not close connection: {}", quiche_h.quiche_error.errToString(rc));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn != null)
|
||||
quiche_h.quiche_conn_free(quicheConn);
|
||||
quicheConn = null;
|
||||
|
||||
if (quicheConfig != null)
|
||||
quiche_h.quiche_config_free(quicheConfig);
|
||||
quicheConfig = null;
|
||||
|
||||
if (scope != null)
|
||||
scope.close();
|
||||
scope = null;
|
||||
sendInfo = null;
|
||||
recvInfo = null;
|
||||
stats = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDraining()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
return quiche_h.quiche_conn_is_draining(quicheConn) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxLocalStreams()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
quiche_h.quiche_conn_stats(quicheConn, stats.address());
|
||||
return (int)quiche_stats.get_peer_initial_max_streams_bidi(stats);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long windowCapacity()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
quiche_h.quiche_conn_stats(quicheConn, stats.address());
|
||||
return quiche_stats.get_cwnd(stats);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long windowCapacity(long streamId) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("connection was released");
|
||||
long value = quiche_h.quiche_conn_stream_capacity(quicheConn, streamId);
|
||||
if (value < 0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("could not read window capacity for stream {} err={}", streamId, quiche_h.quiche_error.errToString(value));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownStream(long streamId, boolean writeSide, long error) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("connection was released");
|
||||
int direction = writeSide ? quiche_h.quiche_shutdown.QUICHE_SHUTDOWN_WRITE : quiche_h.quiche_shutdown.QUICHE_SHUTDOWN_READ;
|
||||
int rc = quiche_h.quiche_conn_stream_shutdown(quicheConn, streamId, direction, error);
|
||||
if (rc == 0 || rc == quiche_h.quiche_error.QUICHE_ERR_DONE)
|
||||
return;
|
||||
throw new IOException("failed to shutdown stream " + streamId + ": " + quiche_h.quiche_error.errToString(rc));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int feedClearBytesForStream(long streamId, ByteBuffer buffer, boolean last) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("connection was released");
|
||||
|
||||
long written;
|
||||
if (buffer.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
MemorySegment bufferSegment = MemorySegment.ofByteBuffer(buffer);
|
||||
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, bufferSegment.address(), buffer.remaining(), last ? C_TRUE : C_FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
if (buffer.remaining() == 0)
|
||||
{
|
||||
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, MemoryAddress.NULL, 0, last ? C_TRUE : C_FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
|
||||
int prevPosition = buffer.position();
|
||||
bufferSegment.asByteBuffer().order(ByteOrder.nativeOrder()).put(buffer);
|
||||
buffer.position(prevPosition);
|
||||
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, bufferSegment.address(), buffer.remaining(), last ? C_TRUE : C_FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (written == quiche_h.quiche_error.QUICHE_ERR_DONE)
|
||||
return 0;
|
||||
if (written < 0L)
|
||||
throw new IOException("failed to write to stream " + streamId + "; err=" + quiche_h.quiche_error.errToString(written));
|
||||
buffer.position((int)(buffer.position() + written));
|
||||
return (int)written;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int drainClearBytesForStream(long streamId, ByteBuffer buffer) throws IOException
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IOException("connection was released");
|
||||
|
||||
long read;
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
if (buffer.isDirect())
|
||||
{
|
||||
// If the ByteBuffer is direct, it can be used without any copy.
|
||||
MemorySegment bufferSegment = MemorySegment.ofByteBuffer(buffer);
|
||||
MemorySegment fin = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
|
||||
read = quiche_h.quiche_conn_stream_recv(quicheConn, streamId, bufferSegment.address(), buffer.remaining(), fin.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
|
||||
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
|
||||
|
||||
MemorySegment fin = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
|
||||
read = quiche_h.quiche_conn_stream_recv(quicheConn, streamId, bufferSegment.address(), buffer.remaining(), fin.address());
|
||||
|
||||
int prevPosition = buffer.position();
|
||||
buffer.put(bufferSegment.asByteBuffer().order(ByteOrder.nativeOrder()).limit((int)read));
|
||||
buffer.position(prevPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (read == quiche_h.quiche_error.QUICHE_ERR_DONE)
|
||||
return isStreamFinished(streamId) ? -1 : 0;
|
||||
if (read < 0L)
|
||||
throw new IOException("failed to read from stream " + streamId + "; err=" + quiche_h.quiche_error.errToString(read));
|
||||
buffer.position((int)(buffer.position() + read));
|
||||
return (int)read;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStreamFinished(long streamId)
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
return quiche_h.quiche_conn_stream_finished(quicheConn, streamId) != C_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseInfo getRemoteCloseInfo()
|
||||
{
|
||||
try (AutoLock ignore = lock.lock())
|
||||
{
|
||||
if (quicheConn == null)
|
||||
throw new IllegalStateException("connection was released");
|
||||
try (ResourceScope scope = ResourceScope.newConfinedScope())
|
||||
{
|
||||
MemorySegment app = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
|
||||
MemorySegment error = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
MemorySegment reason = MemorySegment.allocateNative(CLinker.C_POINTER, scope);
|
||||
MemorySegment reasonLength = MemorySegment.allocateNative(CLinker.C_LONG, scope);
|
||||
if (quiche_h.quiche_conn_peer_error(quicheConn, app.address(), error.address(), reason.address(), reasonLength.address()) != C_FALSE)
|
||||
{
|
||||
long errorValue = error.asByteBuffer().order(ByteOrder.nativeOrder()).getLong();
|
||||
long reasonLengthValue = reasonLength.asByteBuffer().order(ByteOrder.nativeOrder()).getLong();
|
||||
|
||||
String reasonValue;
|
||||
if (reasonLengthValue == 0L)
|
||||
{
|
||||
reasonValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] reasonBytes = new byte[(int)reasonLengthValue];
|
||||
// dereference reason pointer
|
||||
MemoryAddress memoryAddress = MemoryAddress.ofLong(reason.asByteBuffer().order(ByteOrder.nativeOrder()).getLong());
|
||||
memoryAddress.asSegment(reasonLengthValue, ResourceScope.globalScope()).asByteBuffer().get(reasonBytes);
|
||||
reasonValue = new String(reasonBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
return new CloseInfo(errorValue, reasonValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import jdk.incubator.foreign.MemoryHandles;
|
||||
import jdk.incubator.foreign.MemoryLayout;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
|
||||
import static jdk.incubator.foreign.CLinker.C_INT;
|
||||
import static jdk.incubator.foreign.CLinker.C_POINTER;
|
||||
|
||||
public class quiche_recv_info
|
||||
{
|
||||
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
|
||||
C_POINTER.withName("from"),
|
||||
C_INT.withName("from_len"),
|
||||
MemoryLayout.paddingLayout(32)
|
||||
);
|
||||
|
||||
private static final VarHandle from = MemoryHandles.asAddressVarHandle(LAYOUT.varHandle(long.class, MemoryLayout.PathElement.groupElement("from")));
|
||||
private static final VarHandle from_len = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.groupElement("from_len"));
|
||||
|
||||
public static MemorySegment allocate(ResourceScope scope)
|
||||
{
|
||||
return MemorySegment.allocateNative(LAYOUT, scope);
|
||||
}
|
||||
|
||||
public static void setSocketAddress(MemorySegment recvInfo, SocketAddress peer, ResourceScope scope)
|
||||
{
|
||||
MemorySegment sockAddrSegment = sockaddr.convert(peer, scope);
|
||||
from.set(recvInfo, sockAddrSegment.address());
|
||||
from_len.set(recvInfo, (int)sockAddrSegment.byteSize());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import jdk.incubator.foreign.MemoryLayout;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
|
||||
import static jdk.incubator.foreign.CLinker.C_CHAR;
|
||||
import static jdk.incubator.foreign.CLinker.C_INT;
|
||||
import static jdk.incubator.foreign.CLinker.C_LONG;
|
||||
import static jdk.incubator.foreign.CLinker.C_SHORT;
|
||||
|
||||
public class quiche_send_info
|
||||
{
|
||||
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout( // struct sockaddr_storage
|
||||
MemoryLayout.structLayout(
|
||||
C_SHORT.withName("ss_family"),
|
||||
MemoryLayout.sequenceLayout(118, C_CHAR).withName("__ss_padding"),
|
||||
C_LONG.withName("__ss_align")
|
||||
).withName("to"),
|
||||
C_INT.withName("to_len"),
|
||||
MemoryLayout.paddingLayout(32),
|
||||
MemoryLayout.structLayout( // struct timespec
|
||||
C_LONG.withName("tv_sec"),
|
||||
C_LONG.withName("tv_nsec")
|
||||
).withName("at")
|
||||
);
|
||||
|
||||
public static MemorySegment allocate(ResourceScope scope)
|
||||
{
|
||||
return MemorySegment.allocateNative(LAYOUT, scope);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
import jdk.incubator.foreign.MemoryLayout;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
|
||||
import static jdk.incubator.foreign.CLinker.C_CHAR;
|
||||
import static jdk.incubator.foreign.CLinker.C_LONG;
|
||||
|
||||
public class quiche_stats
|
||||
{
|
||||
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
|
||||
C_LONG.withName("recv"),
|
||||
C_LONG.withName("sent"),
|
||||
C_LONG.withName("lost"),
|
||||
C_LONG.withName("retrans"),
|
||||
C_LONG.withName("rtt"),
|
||||
C_LONG.withName("cwnd"),
|
||||
C_LONG.withName("sent_bytes"),
|
||||
C_LONG.withName("recv_bytes"),
|
||||
C_LONG.withName("lost_bytes"),
|
||||
C_LONG.withName("stream_retrans_bytes"),
|
||||
C_LONG.withName("pmtu"),
|
||||
C_LONG.withName("delivery_rate"),
|
||||
C_LONG.withName("peer_max_idle_timeout"),
|
||||
C_LONG.withName("peer_max_udp_payload_size"),
|
||||
C_LONG.withName("peer_initial_max_data"),
|
||||
C_LONG.withName("peer_initial_max_stream_data_bidi_local"),
|
||||
C_LONG.withName("peer_initial_max_stream_data_bidi_remote"),
|
||||
C_LONG.withName("peer_initial_max_stream_data_uni"),
|
||||
C_LONG.withName("peer_initial_max_streams_bidi"),
|
||||
C_LONG.withName("peer_initial_max_streams_uni"),
|
||||
C_LONG.withName("peer_ack_delay_exponent"),
|
||||
C_LONG.withName("peer_max_ack_delay"),
|
||||
C_CHAR.withName("peer_disable_active_migration"),
|
||||
MemoryLayout.paddingLayout(56),
|
||||
C_LONG.withName("peer_active_conn_id_limit"),
|
||||
C_LONG.withName("peer_max_datagram_frame_size")
|
||||
);
|
||||
|
||||
public static MemorySegment allocate(ResourceScope scope)
|
||||
{
|
||||
return MemorySegment.allocateNative(LAYOUT, scope);
|
||||
}
|
||||
|
||||
private static final VarHandle cwnd = LAYOUT.varHandle(long.class, MemoryLayout.PathElement.groupElement("cwnd"));
|
||||
private static final VarHandle peer_initial_max_streams_bidi = LAYOUT.varHandle(long.class, MemoryLayout.PathElement.groupElement("peer_initial_max_streams_bidi"));
|
||||
|
||||
public static long get_cwnd(MemorySegment stats)
|
||||
{
|
||||
return (long)cwnd.get(stats);
|
||||
}
|
||||
|
||||
public static long get_peer_initial_max_streams_bidi(MemorySegment stats)
|
||||
{
|
||||
return (long)peer_initial_max_streams_bidi.get(stats);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
|
||||
public class sockaddr
|
||||
{
|
||||
// TODO: linux-specific constants
|
||||
private static final short AF_INET = 2;
|
||||
private static final short AF_INET6 = 10;
|
||||
|
||||
public static MemorySegment convert(SocketAddress socketAddress, ResourceScope scope)
|
||||
{
|
||||
if (!(socketAddress instanceof InetSocketAddress))
|
||||
throw new IllegalArgumentException("Expected InetSocketAddress instance, got: " + socketAddress);
|
||||
InetSocketAddress inetSocketAddress = (InetSocketAddress)socketAddress;
|
||||
InetAddress address = inetSocketAddress.getAddress();
|
||||
if (address instanceof Inet4Address)
|
||||
{
|
||||
// TODO: linux-specific implementation
|
||||
MemorySegment sin = sockaddr_in.allocate(scope);
|
||||
sockaddr_in.set_sin_family(sin, AF_INET);
|
||||
sockaddr_in.set_sin_port(sin, (short) inetSocketAddress.getPort());
|
||||
sockaddr_in.set_sin_addr(sin, ByteBuffer.wrap(address.getAddress()).getInt());
|
||||
return sin;
|
||||
}
|
||||
else if (address instanceof Inet6Address)
|
||||
{
|
||||
// TODO: linux-specific implementation
|
||||
MemorySegment sin6 = sockaddr_in6.allocate(scope);
|
||||
sockaddr_in6.set_sin6_family(sin6, AF_INET6);
|
||||
sockaddr_in6.set_sin6_port(sin6, (short) inetSocketAddress.getPort());
|
||||
sockaddr_in6.set_sin6_addr(sin6, address.getAddress());
|
||||
sockaddr_in6.set_sin6_scope_id(sin6, 0);
|
||||
sockaddr_in6.set_sin6_flowinfo(sin6, 0);
|
||||
return sin6;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedOperationException("Unsupported InetAddress: " + address);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
import jdk.incubator.foreign.GroupLayout;
|
||||
import jdk.incubator.foreign.MemoryLayout;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
|
||||
import static jdk.incubator.foreign.CLinker.C_CHAR;
|
||||
import static jdk.incubator.foreign.CLinker.C_INT;
|
||||
import static jdk.incubator.foreign.CLinker.C_SHORT;
|
||||
|
||||
class sockaddr_in
|
||||
{
|
||||
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
|
||||
C_SHORT.withName("sin_family"),
|
||||
C_SHORT.withName("sin_port"),
|
||||
C_INT.withName("sin_addr"),
|
||||
MemoryLayout.sequenceLayout(8, C_CHAR).withName("sin_zero")
|
||||
).withName("sockaddr_in");
|
||||
|
||||
private static final VarHandle sin_family = LAYOUT.varHandle(short.class, MemoryLayout.PathElement.groupElement("sin_family"));
|
||||
private static final VarHandle sin_port = LAYOUT.varHandle(short.class, MemoryLayout.PathElement.groupElement("sin_port"));
|
||||
private static final VarHandle sin_addr = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.groupElement("sin_addr"));
|
||||
|
||||
public static MemorySegment allocate(ResourceScope scope)
|
||||
{
|
||||
return MemorySegment.allocateNative(LAYOUT, scope);
|
||||
}
|
||||
|
||||
public static void set_sin_family(MemorySegment sin, short value)
|
||||
{
|
||||
sin_family.set(sin, value);
|
||||
}
|
||||
|
||||
public static void set_sin_port(MemorySegment sin, short value)
|
||||
{
|
||||
sin_port.set(sin, value);
|
||||
}
|
||||
|
||||
public static void set_sin_addr(MemorySegment sin, int value)
|
||||
{
|
||||
sin_addr.set(sin, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import jdk.incubator.foreign.MemoryLayout;
|
||||
import jdk.incubator.foreign.MemorySegment;
|
||||
import jdk.incubator.foreign.ResourceScope;
|
||||
|
||||
import static jdk.incubator.foreign.CLinker.C_CHAR;
|
||||
import static jdk.incubator.foreign.CLinker.C_INT;
|
||||
import static jdk.incubator.foreign.CLinker.C_SHORT;
|
||||
|
||||
class sockaddr_in6
|
||||
{
|
||||
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
|
||||
C_SHORT.withName("sin6_family"),
|
||||
C_SHORT.withName("sin6_port"),
|
||||
C_INT.withName("sin6_flowinfo"),
|
||||
MemoryLayout.sequenceLayout(16, C_CHAR).withName("sin6_addr"),
|
||||
C_INT.withName("sin6_scope_id")
|
||||
).withName("sockaddr_in6");
|
||||
|
||||
private static final VarHandle sin6_family = LAYOUT.varHandle(short.class, MemoryLayout.PathElement.groupElement("sin6_family"));
|
||||
private static final VarHandle sin6_port = LAYOUT.varHandle(short.class, MemoryLayout.PathElement.groupElement("sin6_port"));
|
||||
private static final VarHandle sin6_scope_id = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.groupElement("sin6_scope_id"));
|
||||
private static final VarHandle sin6_flowinfo = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.groupElement("sin6_flowinfo"));
|
||||
|
||||
public static MemorySegment allocate(ResourceScope scope)
|
||||
{
|
||||
return MemorySegment.allocateNative(LAYOUT, scope);
|
||||
}
|
||||
|
||||
public static void set_sin6_addr(MemorySegment sin6, byte[] value)
|
||||
{
|
||||
sin6.asSlice(8, 16).asByteBuffer().order(ByteOrder.nativeOrder()).put(value);
|
||||
}
|
||||
|
||||
public static void set_sin6_family(MemorySegment sin6, short value)
|
||||
{
|
||||
sin6_family.set(sin6, value);
|
||||
}
|
||||
|
||||
public static void set_sin6_port(MemorySegment sin6, short value)
|
||||
{
|
||||
sin6_port.set(sin6, value);
|
||||
}
|
||||
|
||||
public static void set_sin6_scope_id(MemorySegment sin6, int value)
|
||||
{
|
||||
sin6_scope_id.set(sin6, value);
|
||||
}
|
||||
|
||||
public static void set_sin6_flowinfo(MemorySegment sin6, int value)
|
||||
{
|
||||
sin6_flowinfo.set(sin6, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.eclipse.jetty.quic.quiche.panama.jdk.PanamaJdkQuicheBinding
|
|
@ -0,0 +1,287 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.quic.quiche.panama.jdk;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConfig;
|
||||
import org.eclipse.jetty.quic.quiche.QuicheConnection;
|
||||
import org.eclipse.jetty.quic.quiche.SSLKeyPair;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
public class LowLevelQuicheTest
|
||||
{
|
||||
private final Collection<PanamaJdkQuicheConnection> connectionsToDisposeOf = new ArrayList<>();
|
||||
|
||||
private InetSocketAddress clientSocketAddress;
|
||||
private InetSocketAddress serverSocketAddress;
|
||||
private QuicheConfig clientQuicheConfig;
|
||||
private QuicheConfig serverQuicheConfig;
|
||||
private PanamaJdkQuicheConnection.TokenMinter tokenMinter;
|
||||
private PanamaJdkQuicheConnection.TokenValidator tokenValidator;
|
||||
|
||||
@BeforeEach
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
clientSocketAddress = new InetSocketAddress("localhost", 9999);
|
||||
serverSocketAddress = new InetSocketAddress("localhost", 8888);
|
||||
|
||||
clientQuicheConfig = new QuicheConfig();
|
||||
clientQuicheConfig.setApplicationProtos("http/0.9");
|
||||
clientQuicheConfig.setDisableActiveMigration(true);
|
||||
clientQuicheConfig.setVerifyPeer(false);
|
||||
clientQuicheConfig.setMaxIdleTimeout(1_000L);
|
||||
clientQuicheConfig.setInitialMaxData(10_000_000L);
|
||||
clientQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
|
||||
clientQuicheConfig.setInitialMaxStreamDataBidiRemote(10_000_000L);
|
||||
clientQuicheConfig.setInitialMaxStreamDataUni(10_000_000L);
|
||||
clientQuicheConfig.setInitialMaxStreamsUni(100L);
|
||||
clientQuicheConfig.setInitialMaxStreamsBidi(100L);
|
||||
clientQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
|
||||
|
||||
SSLKeyPair serverKeyPair = new SSLKeyPair(Paths.get(Objects.requireNonNull(getClass().getResource("/keystore.p12")).toURI()).toFile(), "PKCS12", "storepwd".toCharArray(), "mykey", "storepwd".toCharArray());
|
||||
File[] pemFiles = serverKeyPair.export(new File(System.getProperty("java.io.tmpdir")));
|
||||
serverQuicheConfig = new QuicheConfig();
|
||||
serverQuicheConfig.setPrivKeyPemPath(pemFiles[0].getPath());
|
||||
serverQuicheConfig.setCertChainPemPath(pemFiles[1].getPath());
|
||||
serverQuicheConfig.setApplicationProtos("http/0.9");
|
||||
serverQuicheConfig.setVerifyPeer(false);
|
||||
serverQuicheConfig.setMaxIdleTimeout(1_000L);
|
||||
serverQuicheConfig.setInitialMaxData(10_000_000L);
|
||||
serverQuicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
|
||||
serverQuicheConfig.setInitialMaxStreamDataBidiRemote(10_000_000L);
|
||||
serverQuicheConfig.setInitialMaxStreamDataUni(10_000_000L);
|
||||
serverQuicheConfig.setInitialMaxStreamsUni(100L);
|
||||
serverQuicheConfig.setInitialMaxStreamsBidi(100L);
|
||||
serverQuicheConfig.setCongestionControl(QuicheConfig.CongestionControl.CUBIC);
|
||||
|
||||
tokenMinter = new TestTokenMinter();
|
||||
tokenValidator = new TestTokenValidator();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
protected void tearDown()
|
||||
{
|
||||
connectionsToDisposeOf.forEach(PanamaJdkQuicheConnection::dispose);
|
||||
connectionsToDisposeOf.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishedAsSoonAsFinIsFed() throws Exception
|
||||
{
|
||||
// establish connection
|
||||
Map.Entry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> entry = connectClientToServer();
|
||||
PanamaJdkQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
PanamaJdkQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
|
||||
// client sends 16 bytes of payload over stream 0
|
||||
assertThat(clientQuicheConnection.feedClearBytesForStream(0, ByteBuffer.allocate(16)
|
||||
.putInt(0xdeadbeef)
|
||||
.putInt(0xcafebabe)
|
||||
.putInt(0xdeadc0de)
|
||||
.putInt(0xbaddecaf)
|
||||
.flip()), is(16));
|
||||
drainClientToFeedServer(entry, 59);
|
||||
|
||||
// server checks that stream 0 is readable
|
||||
List<Long> readableStreamIds = serverQuicheConnection.readableStreamIds();
|
||||
assertThat(readableStreamIds.size(), is(1));
|
||||
assertThat(readableStreamIds.get(0), is(0L));
|
||||
|
||||
// server reads 16 bytes from stream 0
|
||||
assertThat(serverQuicheConnection.drainClearBytesForStream(0, ByteBuffer.allocate(1000)), is(16));
|
||||
|
||||
// assert that stream 0 is not finished on server
|
||||
assertThat(serverQuicheConnection.isStreamFinished(0), is(false));
|
||||
|
||||
// client finishes stream 0
|
||||
clientQuicheConnection.feedFinForStream(0);
|
||||
|
||||
drainClientToFeedServer(entry, 43);
|
||||
readableStreamIds = serverQuicheConnection.readableStreamIds();
|
||||
assertThat(readableStreamIds.size(), is(1));
|
||||
assertThat(readableStreamIds.get(0), is(0L));
|
||||
|
||||
// assert that stream 0 is finished on server
|
||||
assertThat(serverQuicheConnection.isStreamFinished(0), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFinishedAsLongAsStreamHasReadableBytes() throws Exception
|
||||
{
|
||||
// establish connection
|
||||
Map.Entry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> entry = connectClientToServer();
|
||||
PanamaJdkQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
PanamaJdkQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
|
||||
// client sends 16 bytes of payload over stream 0 and finish it
|
||||
assertThat(clientQuicheConnection.feedClearBytesForStream(0, ByteBuffer.allocate(16)
|
||||
.putInt(0xdeadbeef)
|
||||
.putInt(0xcafebabe)
|
||||
.putInt(0xdeadc0de)
|
||||
.putInt(0xbaddecaf)
|
||||
.flip()), is(16));
|
||||
clientQuicheConnection.feedFinForStream(0);
|
||||
drainClientToFeedServer(entry, 59);
|
||||
|
||||
// server checks that stream 0 is readable
|
||||
List<Long> readableStreamIds = serverQuicheConnection.readableStreamIds();
|
||||
assertThat(readableStreamIds.size(), is(1));
|
||||
assertThat(readableStreamIds.get(0), is(0L));
|
||||
|
||||
// assert that stream 0 is not finished on server
|
||||
assertThat(serverQuicheConnection.isStreamFinished(0), is(false));
|
||||
|
||||
// server reads 16 bytes from stream 0
|
||||
assertThat(serverQuicheConnection.drainClearBytesForStream(0, ByteBuffer.allocate(1000)), is(16));
|
||||
|
||||
// assert that stream 0 is finished on server
|
||||
assertThat(serverQuicheConnection.isStreamFinished(0), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplicationProtocol() throws Exception
|
||||
{
|
||||
serverQuicheConfig.setApplicationProtos("€");
|
||||
clientQuicheConfig.setApplicationProtos("€");
|
||||
|
||||
// establish connection
|
||||
Map.Entry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> entry = connectClientToServer();
|
||||
PanamaJdkQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
PanamaJdkQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
|
||||
assertThat(clientQuicheConnection.getNegotiatedProtocol(), is("€"));
|
||||
assertThat(serverQuicheConnection.getNegotiatedProtocol(), is("€"));
|
||||
}
|
||||
|
||||
private void drainServerToFeedClient(Map.Entry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> entry, int expectedSize) throws IOException
|
||||
{
|
||||
PanamaJdkQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
PanamaJdkQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(quiche_h.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
|
||||
int drained = serverQuicheConnection.drainCipherBytes(buffer);
|
||||
assertThat(drained, is(expectedSize));
|
||||
buffer.flip();
|
||||
int fed = clientQuicheConnection.feedCipherBytes(buffer, serverSocketAddress);
|
||||
assertThat(fed, is(expectedSize));
|
||||
}
|
||||
|
||||
private void drainClientToFeedServer(Map.Entry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> entry, int expectedSize) throws IOException
|
||||
{
|
||||
PanamaJdkQuicheConnection clientQuicheConnection = entry.getKey();
|
||||
PanamaJdkQuicheConnection serverQuicheConnection = entry.getValue();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(quiche_h.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
|
||||
int drained = clientQuicheConnection.drainCipherBytes(buffer);
|
||||
assertThat(drained, is(expectedSize));
|
||||
buffer.flip();
|
||||
int fed = serverQuicheConnection.feedCipherBytes(buffer, clientSocketAddress);
|
||||
assertThat(fed, is(expectedSize));
|
||||
}
|
||||
|
||||
private Map.Entry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> connectClientToServer() throws IOException
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate(quiche_h.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
ByteBuffer buffer2 = ByteBuffer.allocate(quiche_h.QUICHE_MIN_CLIENT_INITIAL_LEN);
|
||||
|
||||
PanamaJdkQuicheConnection clientQuicheConnection = PanamaJdkQuicheConnection.connect(clientQuicheConfig, serverSocketAddress);
|
||||
connectionsToDisposeOf.add(clientQuicheConnection);
|
||||
|
||||
int drained = clientQuicheConnection.drainCipherBytes(buffer);
|
||||
assertThat(drained, is(1200));
|
||||
buffer.flip();
|
||||
|
||||
PanamaJdkQuicheConnection serverQuicheConnection = PanamaJdkQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, clientSocketAddress);
|
||||
assertThat(serverQuicheConnection, is(nullValue()));
|
||||
boolean negotiated = PanamaJdkQuicheConnection.negotiate(tokenMinter, buffer, buffer2);
|
||||
assertThat(negotiated, is(true));
|
||||
buffer2.flip();
|
||||
|
||||
int fed = clientQuicheConnection.feedCipherBytes(buffer2, serverSocketAddress);
|
||||
assertThat(fed, is(79));
|
||||
|
||||
buffer.clear();
|
||||
drained = clientQuicheConnection.drainCipherBytes(buffer);
|
||||
assertThat(drained, is(1200));
|
||||
buffer.flip();
|
||||
|
||||
serverQuicheConnection = PanamaJdkQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, clientSocketAddress);
|
||||
assertThat(serverQuicheConnection, is(not(nullValue())));
|
||||
connectionsToDisposeOf.add(serverQuicheConnection);
|
||||
|
||||
buffer.clear();
|
||||
drained = serverQuicheConnection.drainCipherBytes(buffer);
|
||||
assertThat(drained, is(1200));
|
||||
buffer.flip();
|
||||
|
||||
fed = clientQuicheConnection.feedCipherBytes(buffer, serverSocketAddress);
|
||||
assertThat(fed, is(1200));
|
||||
|
||||
assertThat(serverQuicheConnection.isConnectionEstablished(), is(false));
|
||||
assertThat(clientQuicheConnection.isConnectionEstablished(), is(false));
|
||||
|
||||
AbstractMap.SimpleImmutableEntry<PanamaJdkQuicheConnection, PanamaJdkQuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
|
||||
|
||||
int protosLen = 0;
|
||||
for (String proto : clientQuicheConfig.getApplicationProtos())
|
||||
protosLen += 1 + proto.getBytes(StandardCharsets.UTF_8).length;
|
||||
|
||||
drainServerToFeedClient(entry, 300 + protosLen);
|
||||
assertThat(serverQuicheConnection.isConnectionEstablished(), is(false));
|
||||
assertThat(clientQuicheConnection.isConnectionEstablished(), is(true));
|
||||
|
||||
drainClientToFeedServer(entry, 1200);
|
||||
assertThat(serverQuicheConnection.isConnectionEstablished(), is(true));
|
||||
assertThat(clientQuicheConnection.isConnectionEstablished(), is(true));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private static class TestTokenMinter implements QuicheConnection.TokenMinter
|
||||
{
|
||||
@Override
|
||||
public byte[] mint(byte[] dcid, int len)
|
||||
{
|
||||
return ByteBuffer.allocate(len).put(dcid, 0, len).array();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestTokenValidator implements QuicheConnection.TokenValidator
|
||||
{
|
||||
@Override
|
||||
public byte[] validate(byte[] token, int len)
|
||||
{
|
||||
return ByteBuffer.allocate(len).put(token, 0, len).array();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.quic.LEVEL=DEBUG
|
||||
org.eclipse.jetty.quic.quiche.LEVEL=INFO
|
Binary file not shown.
|
@ -22,12 +22,14 @@
|
|||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-jna</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
@ -35,4 +37,19 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jdk17</id>
|
||||
<activation>
|
||||
<jdk>[17,)</jdk>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-panama-jdk</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
12
pom.xml
12
pom.xml
|
@ -1681,7 +1681,17 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche</artifactId>
|
||||
<artifactId>quic-quiche-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-jna</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-quiche-panama-jdk</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -87,6 +87,9 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
|
|||
client.setMaxConnectionsPerDestination(32768);
|
||||
client.setMaxRequestsQueuedPerDestination(1024 * 1024);
|
||||
});
|
||||
scenario.setConnectionIdleTimeout(120000);
|
||||
scenario.setRequestIdleTimeout(120000);
|
||||
scenario.client.setIdleTimeout(120000);
|
||||
|
||||
// At least 25k requests to warmup properly (use -XX:+PrintCompilation to verify JIT activity)
|
||||
int runs = 1;
|
||||
|
@ -165,7 +168,7 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
|
|||
|
||||
// Dumps the state of the client if the test takes too long
|
||||
Thread testThread = Thread.currentThread();
|
||||
long maxTime = Math.max(15000, (long)iterations * factor);
|
||||
long maxTime = Math.max(60000, (long)iterations * factor);
|
||||
Scheduler.Task task = scenario.client.getScheduler().schedule(() ->
|
||||
{
|
||||
logger.warn("Interrupting test, it is taking too long (maxTime={} ms){}{}{}{}", maxTime,
|
||||
|
@ -292,7 +295,7 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
|
|||
latch.countDown();
|
||||
}
|
||||
});
|
||||
int maxTime = 15000;
|
||||
int maxTime = 30000;
|
||||
if (!await(requestLatch, maxTime, TimeUnit.MILLISECONDS))
|
||||
{
|
||||
logger.warn("Request {} took too long (maxTime={} ms){}{}{}{}", requestId, maxTime,
|
||||
|
|
Loading…
Reference in New Issue