Merge remote-tracking branch 'origin/jetty-12.0.x' into fix/12.0.x/path-mappings-handler-context

This commit is contained in:
Joakim Erdfelt 2024-04-24 08:48:08 -05:00
commit 4c512a55ef
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
240 changed files with 8458 additions and 4660 deletions

View File

@ -74,7 +74,7 @@ jobs:
- name: Set up Maven
run:
mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.9.4"
mvn -e -B -V org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper "-Dmaven=3.9.6"
- name: Clean install dependencies and build
env:

4
Jenkinsfile vendored
View File

@ -106,12 +106,12 @@ def mavenBuild(jdk, cmdline, mvnName) {
//-Dmaven.build.cache.configPath=$MVN_BUILD_CACHE_CONFIG
buildCache = useBuildCache()
if (buildCache) {
echo "Using build cache"
echo "Using build cache"
extraArgs = " -Dmaven.build.cache.restoreGeneratedSources=false -Dmaven.build.cache.remote.url=http://nginx-cache-service.jenkins.svc.cluster.local:80 -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=remote-build-cache-server -Daether.connector.http.supportWebDav=true "
} else {
// when not using cache
echo "Not using build cache"
extraArgs = " -Dmaven.test.failure.ignore=true -Dmaven.build.cache.enabled=false "
extraArgs = " -Dmaven.test.failure.ignore=true -Dmaven.build.cache.skipCache=true -Dmaven.build.cache.remote.url=http://nginx-cache-service.jenkins.svc.cluster.local:80 -Dmaven.build.cache.remote.enabled=true -Dmaven.build.cache.remote.save.enabled=true -Dmaven.build.cache.remote.server.id=remote-build-cache-server -Daether.connector.http.supportWebDav=true "
}
if (env.BRANCH_NAME ==~ /PR-\d+/) {
if (pullRequest.labels.contains("build-all-tests")) {

View File

@ -63,6 +63,7 @@ This may be very time-consuming if you have many jars.
Jetty can reduce the time taken by limiting the jars that are scanned.
[[og-container-include-jar-pattern]]
===== The container classpath
By default, Jetty will _not_ scan any classes that are on the container's classpath.
@ -92,6 +93,7 @@ Here's an example from a context XML file that includes any jar whose name start
Note that the order of the patterns defines the ordering of the scanning of the jars or class directories.
[[og-web-inf-include-jar-pattern]]
===== The webapp classpath
By default, Jetty will scan __all__ classes from `WEB-INF/classes` and _all_ jars from `WEB-INF/lib` according to the order, if any, established by absolute or relative ordering clauses in `web.xml`.

View File

@ -159,6 +159,7 @@ Refer to the link:https://docs.oracle.com/javase/7/docs/api/javax/security/auth/
* link:{javadoc-url}/org/eclipse/jetty/security/jaas/spi/DataSourceLoginModule.html[`org.eclipse.jetty.security.jaas.spi.DataSourceLoginModule`]
* link:{javadoc-url}/org/eclipse/jetty/security/jaas/spi/LdapLoginModule.html[`org.eclipse.jetty.security.jaas.ldap.LdapLoginModule`]
[[og-password]]
[NOTE]
====
Passwords can be obfuscated using the xref:og-tools-password[Jetty Password tool].

View File

@ -24,7 +24,7 @@ A Jetty module is defined in a `<name>.mod` file, where `<name>` is the module n
Jetty module files are read from the typical xref:og-start-configure[configuration source directories], under the `modules/` subdirectory; from higher priority to lower priority:
* The `$JETTY_BASE/modules/` directory.
* If a directory is specified with the `--include-jetty-dir` option, its `modules/` subdirectory.
* If a directory is specified with the `--add-config-dir` option, its `modules/` subdirectory.
* The `$JETTY_HOME/modules/` directory.
The standard Jetty modules that Jetty provides out-of-the-box are under `$JETTY_HOME/modules/`.
@ -74,7 +74,7 @@ The Jetty XML file of a Jetty module may instantiate and assemble together its o
The Jetty module's XML files are read from the typical xref:og-start-configure[configuration source directories], under the `etc/` subdirectory; from higher priority to lower priority:
* The `$JETTY_BASE/etc/` directory.
* If a directory is specified with the `--include-jetty-dir` option, its `etc/` subdirectory.
* If a directory is specified with the `--add-config-dir` option, its `etc/` subdirectory.
* The `$JETTY_HOME/etc/` directory.
The standard Jetty modules XML files that Jetty provides out-of-the-box are under `$JETTY_HOME/etc/`.
@ -128,7 +128,7 @@ Appends the value to the existing value.
This is useful to append a value to properties that accept a comma separated list of values, for example:
+
----
jetty.webapp.addServerClasses+=,com.acme
jetty.webapp.addProtectedClasses+=,com.acme
----
+
// TODO: check what happens if the property is empty and +=,value is done: is the comma stripped? If so add a sentence about this.

View File

@ -65,7 +65,7 @@ Because the Google Cloud DataStore is not a technology provided by the Eclipse F
As GCloud requires certain Java Commons Logging features to work correctly, Jetty routes these through SLF4J.
By default, Jetty implements the SLF4J api, but you can choose a different logging implementation by following the instructions xref:og-server-logging[here]
IMPORTANT: If you want to use updated versions of the jar files automatically downloaded during the module enablement, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
IMPORTANT: If you want to use updated versions of the jar files automatically downloaded during the module enablement, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-create-files=<module name>` command line option to prevent errors when starting your server.
==== Configuration

View File

@ -27,7 +27,7 @@ Because Hazelcast is not a technology provided by the Eclipse Foundation, you wi
Hazelcast-specific jar files will be downloaded and saved to a directory named `$JETTY_BASE/lib/hazelcast/`.
NOTE: If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
NOTE: If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-create-files=<module name>` command line option to prevent errors when starting your server.
====== Configuration

View File

@ -30,7 +30,7 @@ Infinispan-specific jar files are download to the directory named `$JETTY_BASE/l
In addition to adding these modules to the classpath of the server it also added several ini configuration files to the `$JETTY_BASE/start.d` directory.
NOTE: If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
NOTE: If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-create-files=<module name>` command line option to prevent errors when starting your server.
====== Configuration
@ -83,7 +83,7 @@ Enabling the `session-store-infinispan-embedded` module runs an in-process insta
Because Infinispan is not a technology provided by the Eclipse Foundation, you will be prompted to assent to the licenses of the external vendor (Apache in this case).
Infinispan-specific jar files will be downloaded and saved to a directory named `$JETTY_BASE/lib/infinispan/`.
NOTE: If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
NOTE: If you have updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-create-files=<module name>` command line option to prevent errors when starting your server.
====== Configuration

View File

@ -19,7 +19,7 @@ Enabling the `session-store-mongo` module configures Jetty to store session data
Because MongoDB is not a technology provided by the Eclipse Foundation, you will be prompted to assent to the licenses of the external vendor (Apache in this case) during the install.
Jars needed by MongoDB are downloaded and stored into a directory named `$JETTY_BASE/lib/nosql/`.
IMPORTANT: If you want to use updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-file-validation=<module name>` command line option to prevent errors when starting your server.
IMPORTANT: If you want to use updated versions of the jar files automatically downloaded by Jetty, you can place them in the associated `$JETTY_BASE/lib/` directory and use the `--skip-create-files=<module name>` command line option to prevent errors when starting your server.
===== Configuration

View File

@ -18,7 +18,7 @@ Within the Jetty start mechanism, the source of configurations is layered in thi
* The command line options.
* The `$JETTY_BASE` directory, and its files.
* The directory specified with the `--include-jetty-dir` option, and its files.
* The directory specified with the `--add-config-dir` option, and its files.
* The `$JETTY_HOME` directory, and its files.
[[og-start-configure-enable]]
@ -31,7 +31,7 @@ $ java -jar $JETTY_HOME/start.jar --add-modules=server,http
----
The Jetty start mechanism will look for the specified modules following the order specified above.
In the common case (without a `--include-jetty-dir` directory), it will look in `$JETTY_BASE/modules/` first and then in `$JETTY_HOME/modules/`.
In the common case (without a `--add-config-dir` directory), it will look in `$JETTY_BASE/modules/` first and then in `$JETTY_HOME/modules/`.
Since the `server` and `http` modules are standard Jetty modules, they are present in `$JETTY_HOME/modules/` and loaded from there.

View File

@ -30,11 +30,6 @@ Other popular tools include Ant and Gradle.
[[configuring-embedded-jetty-with-maven]]
==== Using Embedded Jetty with Maven
To understand the basic operations of building and running against Jetty, first review:
* xref:advanced-embedding[Embedding with Jetty]
* xref:jetty-helloworld[Jetty HelloWorld example]
Maven uses convention over configuration, so it is best to use the project structure Maven recommends.
You can use _xref:archetypes[http://maven.apache.org/guides/introduction/introduction-to-archetypes.html[archetypes]]_ to quickly setup Maven projects, but we will set up the structure manually for this simple tutorial example:
@ -279,4 +274,4 @@ A Web Application Archive (WAR) file can be produced from the project with the c
> mvn package
----
The resulting war file is in the `target` directory and may be deployed on any standard servlet server, including xref:configuring-deployment[Jetty].
The resulting war file is in the `target` directory and may be deployed on any standard servlet server, including xref:og-deploy[Jetty].

View File

@ -43,7 +43,7 @@ However, `jetty:run-war` is designed to be run at the command line, whereas `jet
====
While the Jetty Maven Plugin can be very useful for development we do not recommend its use in a _production capacity_.
In order for the plugin to work it needs to leverage many internal Maven APIs and Maven itself it not a production deployment tool.
We recommend either the traditional link:{DISTGUIDE}[distribution] deployment approach or using xref:advanced-embedding[embedded Jetty].
We recommend either the traditional xrefr:og-deploy[distribution] deployment approach or using xref:og-arch[embedded Jetty].
====
[[get-up-and-running]]
@ -138,11 +138,11 @@ host:::
The particular interface for the connector to listen on.
By default, all interfaces.
name:::
The name of the connector, which is useful for xref:serving-webapp-from-particular-port[configuring contexts to respond only on particular connectors].
The name of the connector, which is useful for configuring contexts to respond only on particular connectors.
idleTimeout:::
Maximum idle time for a connection.
You could instead configure the connectors in a standard xref:jetty-xml-config[Jetty xml config file] and put its location into the `jettyXml` parameter.
Note that since Jetty 9.0 it is no longer possible to configure a xref:maven-config-https[https connector] directly in the pom.xml: you need to xref:maven-config-https[use Jetty xml config files to do it].
You could instead configure the connectors in a standard xref:og-xml[jetty xml config file] and put its location into the `jettyXml` parameter.
Note that since Jetty 9.0 it is no longer possible to configure a https connector directly in the pom.xml: you need to use jetty xml config files to do it.
loginServices::
Optional.
A list of `org.eclipse.jetty.security.LoginService` implementations. Note that there is no default realm.
@ -158,7 +158,7 @@ There are three other ways to configure the `RequestLog`:
* In a context xml config file, as specified in the `contextXml` parameter.
* In the `webApp` element.
+
See xref:configuring-jetty-request-logs[Configuring Request Logs] for more information.
See xref:pg-server-http-request-logging[Configuring Request Logs] for more information.
server::
Optional as of Jetty 9.3.1.
This would configure an instance of `org.eclipse.jetty.server.Server` for the plugin to use, however it is usually _not_ necessary to configure this, as the plugin will automatically configure one for you.
@ -366,12 +366,12 @@ See the section on xref:using-overlaid-wars[overlaid wars] for more information.
containerIncludeJarPattern;;
Defaults to `.*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*taglibs-standard-impl-.*\.jar`.
This is a pattern that is applied to the names of the jars on the container's classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc.
This is analogous to the context attribute xref:container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:container-include-jar-pattern[here].
This is analogous to the context attribute xref:og-container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:og-container-include-jar-pattern[here].
You can define extra patterns of jars that will be included in the scan.
webInfIncludeJarPattern;;
Defaults to matching _all_ of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib).
You can make this pattern more restrictive to only match certain jars by using this setter.
This is analogous to the context attribute xref:web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:web-inf-include-jar-pattern[here].
This is analogous to the context attribute xref:og-web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:og-web-inf-include-jar-pattern[here].
contextXml::
The path to a context xml file that is applied to your webapp AFTER the `webApp` element.
classesDirectory::
@ -484,12 +484,12 @@ You can use this to replace or add configuration.
containerIncludeJarPattern:::
Defaults to `.*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*taglibs-standard-impl-.*\.jar`.
This is a pattern that is applied to the names of the jars on the container's classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc.
This is analogous to the context attribute xref:container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:container-include-jar-pattern[here].
This is analogous to the context attribute xref:og-container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:og-container-include-jar-pattern[here].
You can define extra patterns of jars that will be included in the scan.
webInfIncludeJarPattern:::
Defaults to matching _all_ of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib).
You can make this pattern more restrictive to only match certain jars by using this setter.
This is analogous to the context attribute xref:web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:web-inf-include-jar-pattern[here].
This is analogous to the context attribute xref:og-web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:og-web-inf-include-jar-pattern[here].
tempDirectory:::
The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running.
The default is `${project.build.outputDirectory}/tmp`.
@ -591,12 +591,12 @@ See the section on xref:using-overlaid-wars[overlaid wars] for more information.
containerIncludeJarPattern;;
Defaults to `.*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*taglibs-standard-impl-.*\.jar`.
This is a pattern that is applied to the names of the jars on the container's classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc.
This is analogous to the context attribute xref:container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:container-include-jar-pattern[here].
This is analogous to the context attribute xref:og-container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:og-container-include-jar-pattern[here].
You can define extra patterns of jars that will be included in the scan.
webInfIncludeJarPattern;;
Defaults to matching _all_ of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib).
You can make this pattern more restrictive to only match certain jars by using this setter.
This is analogous to the context attribute xref:web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:web-inf-include-jar-pattern[here].
This is analogous to the context attribute xref:og-web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:og-web-inf-include-jar-pattern[here].
contextXml::
The path to a context xml file that is applied to your webapp AFTER the `webApp` element.
classesDirectory::
@ -662,12 +662,12 @@ You can use this to replace or add configuration.
containerIncludeJarPattern:::
Defaults to `.*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*taglibs-standard-impl-.*\.jar`.
This is a pattern that is applied to the names of the jars on the container's classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc.
This is analogous to the context attribute xref:container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:container-include-jar-pattern[here].
This is analogous to the context attribute xref:og-container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:og-container-include-jar-pattern[here].
You can define extra patterns of jars that will be included in the scan.
webInfIncludeJarPattern:::
Defaults to matching _all_ of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib).
You can make this pattern more restrictive to only match certain jars by using this setter.
This is analogous to the context attribute xref:web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:web-inf-include-jar-pattern[here].
This is analogous to the context attribute xref:og-web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:og-web-inf-include-jar-pattern[here].
tempDirectory:::
The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running.
The default is `${project.build.outputDirectory}/tmp`.
@ -743,7 +743,7 @@ No programmatic declarations of servlets, filters and listeners can be taken int
You can calculate the effective web.xml for any pre-built war file by setting the `&lt;webApp&gt;&lt;war&gt;` parameter, or you can calculate it for the unassembled webapp by setting all of the usual `&lt;webApp&gt;` parameters as for `jetty:run`.
Other useful information about your webapp that is produced as part of the analysis is also stored as context parameters in the effective-web.xml.
The effective-web.xml can be used in conjunction with the xref:quickstart-webapp[Quickstart] feature to quickly start your webapp (note that Quickstart is not appropriate for the mvn Jetty goals).
The effective-web.xml can be used in conjunction with the xref:og-quickstart[Quickstart] feature to quickly start your webapp (note that Quickstart is not appropriate for the mvn jetty goals).
The effective web.xml from these combined sources is generated into a file, which by default is `target/effective-web.xml`, but can be changed by setting the `effectiveWebXml` configuration parameter.
@ -768,12 +768,12 @@ You can use this to replace or add configuration.
containerIncludeJarPattern:::
Defaults to `.*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*taglibs-standard-impl-.*\.jar`.
This is a pattern that is applied to the names of the jars on the container's classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc.
This is analogous to the context attribute xref:container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:container-include-jar-pattern[here].
This is analogous to the context attribute xref:og-container-include-jar-pattern[org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern] that is documented xref:og-container-include-jar-pattern[here].
You can define extra patterns of jars that will be included in the scan.
webInfIncludeJarPattern:::
Defaults to matching _all_ of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib).
You can make this pattern more restrictive to only match certain jars by using this setter.
This is analogous to the context attribute xref:web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:web-inf-include-jar-pattern[here].
This is analogous to the context attribute xref:og-web-inf-include-jar-pattern[org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern] that is documented xref:og-web-inf-include-jar-pattern[here].
tempDirectory:::
The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running.
The default is `${project.build.outputDirectory}/tmp`.

View File

@ -35,4 +35,5 @@ include::http3/server-http3.adoc[]
include::compliance/server-compliance.adoc[]
include::sessions/sessions.adoc[]
include::websocket/server-websocket.adoc[]
include::fastcgi/server-fastcgi.adoc[]
include::server-io-arch.adoc[]

View File

@ -410,8 +410,8 @@
jetty-test-helper,
jetty-maven-plugin,
jetty-jspc-maven-plugin,
jetty-quic-foreign-incubator,
jetty-quic-foreign-jna,
jetty-quic-quiche-foreign,
jetty-quic-quiche-jna,
alpn-api,
javax.servlet,
javax.websocket,
@ -426,8 +426,7 @@
mailapi,
javax.security.auth.message,
plexus-utils,
plexus-interpolation,
jetty-quic-quiche-foreign-incubator</excludeArtifactIds>
plexus-interpolation</excludeArtifactIds>
<classifier>sources</classifier>
<failOnMissingClassifierArtifact>false</failOnMissingClassifierArtifact>
<excludes>META-INF/**,module-info.java</excludes>

View File

@ -247,7 +247,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>jetty-quic-quiche-foreign-incubator</artifactId>
<artifactId>jetty-quic-quiche-foreign</artifactId>
<version>12.0.9-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -279,7 +279,7 @@ public class Socks5Proxy extends Proxy
.put(Socks5.ADDRESS_TYPE_IPV4);
for (int i = 1; i <= 4; ++i)
{
byteBuffer.put(Byte.parseByte(matcher.group(i)));
byteBuffer.put((byte)Integer.parseInt(matcher.group(i)));
}
byteBuffer.putShort(port)
.flip();

View File

@ -85,7 +85,7 @@ public class Socks5ProxyTest
byte ip1 = 127;
byte ip2 = 0;
byte ip3 = 0;
byte ip4 = 13;
short ip4 = 255;
String serverHost = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
int serverPort = proxyPort + 1; // Any port will do
String method = "GET";
@ -128,7 +128,7 @@ public class Socks5ProxyTest
assertEquals(ip1, buffer.get());
assertEquals(ip2, buffer.get());
assertEquals(ip3, buffer.get());
assertEquals(ip4, buffer.get());
assertEquals((byte)ip4, buffer.get());
assertEquals(serverPort, buffer.getShort() & 0xFFFF);
// Write connect response.

View File

@ -0,0 +1,61 @@
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-core</artifactId>
<version>12.0.9-SNAPSHOT</version>
</parent>
<artifactId>jetty-ee</artifactId>
<name>Core :: EE Common</name>
<properties>
<bundle-symbolic-name>${project.groupId}.ee</bundle-symbolic-name>
<spotbugs.onlyAnalyze>org.eclipse.jetty.ee.*</spotbugs.onlyAnalyze>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>jetty-test-multipart</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.ee=org.eclipse.jetty.logging</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addProtectedClasses">
<Arg><Ref refid="Server"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.server.addProtectedClasses"/></Arg>
</Call>
</Arg>
</Call>
<Call class="org.eclipse.jetty.ee.WebAppClassLoading" name="addHiddenClasses">
<Arg><Ref refid="Server"/></Arg>
<Arg>
<Call class="org.eclipse.jetty.util.StringUtil" name="csvSplit">
<Arg><Property name="jetty.server.addHiddenClasses"/></Arg>
</Call>
</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,31 @@
# DO NOT EDIT THIS FILE - See: https://eclipse.dev/jetty/documentation/
[description]
# tag::description[]
This module provide common configuration of Java Servlet web applications over all environments.
# end::description[]
[xml]
etc/jetty-ee-webapp.xml
[lib]
lib/jetty-ee-${jetty.version}.jar
[ini-template]
# tag::ini-template[]
## Add to the server wide default jars and packages protected or hidden from webapps.
## Protected (aka System) classes cannot be overridden by a webapp.
## Hidden (aka Server) classes cannot be seen by a webapp
## Lists of patterns are comma separated and may be either:
## + a qualified classname e.g. 'com.acme.Foo'
## + a package name e.g. 'net.example.'
## + a jar file e.g. '${jetty.base.uri}/lib/dependency.jar'
## + a directory of jars,resource or classes e.g. '${jetty.base.uri}/resources'
## + A pattern preceded with a '-' is an exclusion, all other patterns are inclusions
##
## The +=, operator appends to a CSV list with a comma as needed.
##
#jetty.server.addProtectedClasses+=,org.example.
#jetty.server.addHiddenClasses+=,org.example.
# end::ini-template[]

View File

@ -0,0 +1,22 @@
//
// ========================================================================
// Copyright (c) 1995 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.ee
{
requires org.slf4j;
requires transitive org.eclipse.jetty.util;
requires transitive org.eclipse.jetty.server;
exports org.eclipse.jetty.ee;
}

View File

@ -0,0 +1,214 @@
//
// ========================================================================
// Copyright (c) 1995 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.ee;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.ClassMatcher;
import org.eclipse.jetty.util.component.Environment;
/**
* Common attributes and methods for configuring the {@link ClassLoader Class loading} of web application:
* <ul>
* <li>Protected (a.k.a. System) classes are classes typically provided by the JVM, that cannot be replaced by the
* web application, and they are always loaded via the environment or system classloader. They are visible but
* protected.</li>
* <li>Hidden (a.k.a. Server) classes are those used to implement the Server and are not made available to the
* web application. They are hidden from the web application {@link ClassLoader}.</li>
* </ul>
* <p>These protections are set to reasonable defaults {@link #DEFAULT_PROTECTED_CLASSES} and {@link #DEFAULT_HIDDEN_CLASSES},
* which may be programmatically configured and will affect the defaults applied to all web applications in the same JVM.
*
* <p>
* The defaults applied by a specific {@link Server} can be configured using {@link #addProtectedClasses(Server, String...)} and
* {@link #addHiddenClasses(Server, String...)}. Alternately the {@link Server} attributes {@link #PROTECTED_CLASSES_ATTRIBUTE}
* and {@link #HIDDEN_CLASSES_ATTRIBUTE} may be used to direct set a {@link ClassMatcher} to use for all web applications
* within the server instance.
* </p>
* <p>
* The defaults applied by a specific {@link Environment} can be configured using {@link #addProtectedClasses(Environment, String...)} and
* {@link #addHiddenClasses(Environment, String...)}. Alternately the {@link Environment} attributes {@link #PROTECTED_CLASSES_ATTRIBUTE}
* and {@link #HIDDEN_CLASSES_ATTRIBUTE} may be used to direct set a {@link ClassMatcher} to use for all web applications
* within the server instance.
* </p>
* <p>
* Ultimately, the configurations set by this class only affects the defaults applied to each web application
* {@link org.eclipse.jetty.server.handler.ContextHandler Context} and the {@link ClassMatcher} fields of the web applications
* can be directly access to configure a specific context.
* </p>
*/
public class WebAppClassLoading
{
public static final String PROTECTED_CLASSES_ATTRIBUTE = "org.eclipse.jetty.webapp.systemClasses";
public static final String HIDDEN_CLASSES_ATTRIBUTE = "org.eclipse.jetty.webapp.serverClasses";
/**
* The default protected (system) classes used by a web application, which will be applied to the {@link ClassMatcher}s created
* by {@link #getProtectedClasses(Environment)}.
*/
public static final ClassMatcher DEFAULT_PROTECTED_CLASSES = new ClassMatcher(
"java.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
"javax.", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
"jakarta.", // Jakarta classes (per servlet spec v5.0 / Section 15.2.1)
"org.xml.", // javax.xml
"org.w3c." // javax.xml
);
/**
* The default hidden (server) classes used by a web application, which can be applied to the {@link ClassMatcher}s created
* by {@link #getHiddenClasses(Environment)}.
*/
public static final ClassMatcher DEFAULT_HIDDEN_CLASSES = new ClassMatcher(
"org.eclipse.jetty." // hide jetty classes
);
/**
* Get the default protected (system) classes for a {@link Server}
* @param server The {@link Server} for the defaults
* @return The default protected (system) classes for the {@link Server}, which will be empty if not previously configured.
*/
public static ClassMatcher getProtectedClasses(Server server)
{
return getClassMatcher(server, PROTECTED_CLASSES_ATTRIBUTE, null);
}
/**
* Get the default protected (system) classes for an {@link Environment}
* @param environment The {@link Server} for the defaults
* @return The default protected (system) classes for the {@link Environment}, which will be the {@link #DEFAULT_PROTECTED_CLASSES} if not previously configured.
*/
public static ClassMatcher getProtectedClasses(Environment environment)
{
return getClassMatcher(environment, PROTECTED_CLASSES_ATTRIBUTE, DEFAULT_PROTECTED_CLASSES);
}
/**
* Add a protected (system) Class pattern to use for all WebAppContexts.
* @param patterns the patterns to use
*/
public static void addProtectedClasses(String... patterns)
{
DEFAULT_PROTECTED_CLASSES.add(patterns);
}
/**
* Add a protected (system) Class pattern to use for all WebAppContexts of a given {@link Server}.
* @param attributes The {@link Attributes} instance to add classes to
* @param patterns the patterns to use
*/
public static void addProtectedClasses(Attributes attributes, String... patterns)
{
if (patterns != null && patterns.length > 0)
getClassMatcher(attributes, PROTECTED_CLASSES_ATTRIBUTE, null).add(patterns);
}
/**
* Add a protected (system) Class pattern to use for all WebAppContexts of a given {@link Server}.
* @param server The {@link Server} instance to add classes to
* @param patterns the patterns to use
*/
public static void addProtectedClasses(Server server, String... patterns)
{
if (patterns != null && patterns.length > 0)
getClassMatcher(server, PROTECTED_CLASSES_ATTRIBUTE, null).add(patterns);
}
/**
* Add a protected (system) Class pattern to use for WebAppContexts of a given environment.
* @param environment The {@link Environment} instance to add classes to
* @param patterns the patterns to use
*/
public static void addProtectedClasses(Environment environment, String... patterns)
{
if (patterns != null && patterns.length > 0)
getClassMatcher(environment, PROTECTED_CLASSES_ATTRIBUTE, DEFAULT_PROTECTED_CLASSES).add(patterns);
}
/**
* Get the default hidden (server) classes for a {@link Server}
* @param server The {@link Server} for the defaults
* @return The default hidden (server) classes for the {@link Server}, which will be empty if not previously configured.
*
*/
public static ClassMatcher getHiddenClasses(Server server)
{
return getClassMatcher(server, HIDDEN_CLASSES_ATTRIBUTE, null);
}
/**
* Get the default hidden (server) classes for an {@link Environment}
* @param environment The {@link Server} for the defaults
* @return The default hidden (server) classes for the {@link Environment}, which will be {@link #DEFAULT_PROTECTED_CLASSES} if not previously configured.
*/
public static ClassMatcher getHiddenClasses(Environment environment)
{
return getClassMatcher(environment, HIDDEN_CLASSES_ATTRIBUTE, DEFAULT_HIDDEN_CLASSES);
}
/**
* Add a hidden (server) Class pattern to use for all WebAppContexts of a given {@link Server}.
* @param patterns the patterns to use
*/
public static void addHiddenClasses(String... patterns)
{
DEFAULT_HIDDEN_CLASSES.add(patterns);
}
/**
* Add a hidden (server) Class pattern to use for all WebAppContexts of a given {@link Server}.
* @param attributes The {@link Attributes} instance to add classes to
* @param patterns the patterns to use
*/
@Deprecated (forRemoval = true)
public static void addHiddenClasses(Attributes attributes, String... patterns)
{
if (patterns != null && patterns.length > 0)
getClassMatcher(attributes, HIDDEN_CLASSES_ATTRIBUTE, null).add(patterns);
}
/**
* Add a hidden (server) Class pattern to use for all WebAppContexts of a given {@link Server}.
* @param server The {@link Server} instance to add classes to
* @param patterns the patterns to use
*/
public static void addHiddenClasses(Server server, String... patterns)
{
if (patterns != null && patterns.length > 0)
getClassMatcher(server, HIDDEN_CLASSES_ATTRIBUTE, null).add(patterns);
}
/**
* Add a hidden (server) Class pattern to use for all ee9 WebAppContexts.
* @param environment The {@link Environment} instance to add classes to
* @param patterns the patterns to use
*/
public static void addHiddenClasses(Environment environment, String... patterns)
{
if (patterns != null && patterns.length > 0)
getClassMatcher(environment, HIDDEN_CLASSES_ATTRIBUTE, DEFAULT_HIDDEN_CLASSES).add(patterns);
}
private static ClassMatcher getClassMatcher(Attributes attributes, String attribute, ClassMatcher defaultPatterns)
{
Object existing = attributes.getAttribute(attribute);
if (existing instanceof ClassMatcher cm)
return cm;
ClassMatcher classMatcher = (existing instanceof String[] stringArray)
? new ClassMatcher(stringArray) : new ClassMatcher(defaultPatterns);
attributes.setAttribute(attribute, classMatcher);
return classMatcher;
}
}

View File

@ -0,0 +1,222 @@
//
// ========================================================================
// Copyright (c) 1995 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 orge.eclipse.jetty.ee;
import java.util.Arrays;
import org.eclipse.jetty.ee.WebAppClassLoading;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ClassMatcher;
import org.eclipse.jetty.util.component.Environment;
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.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItemInArray;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
public class WebAppClassLoadingTest
{
@BeforeEach
public void beforeEach()
{
Environment.ensure("Test");
}
@AfterEach
public void afterEach()
{
Environment.ensure("Test").clearAttributes();
}
@Test
public void testServerDefaults()
{
Server server = new Server();
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
assertThat(protect.size(), is(0));
assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
assertThat(hide.size(), is(0));
assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testServerAttributeDefaults()
{
Server server = new Server();
ClassMatcher protect = new ClassMatcher("org.protect.");
server.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, protect);
ClassMatcher hide = new ClassMatcher("org.hide.");
server.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, hide);
assertThat(WebAppClassLoading.getProtectedClasses(server), sameInstance(protect));
assertThat(WebAppClassLoading.getHiddenClasses(server), sameInstance(hide));
}
@Test
public void testServerStringAttributeDefaults()
{
Server server = new Server();
server.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, new String[] {"org.protect."});
server.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, new String[] {"org.hide."});
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
assertThat(protect.size(), is(1));
assertThat(Arrays.asList(protect.getPatterns()), contains("org.protect."));
assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
assertThat(hide.size(), is(1));
assertThat(Arrays.asList(hide.getPatterns()), contains("org.hide."));
assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testServerProgrammaticDefaults()
{
Server server = new Server();
WebAppClassLoading.addProtectedClasses(server, "org.protect.");
WebAppClassLoading.addHiddenClasses(server, "org.hide.");
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
assertThat(protect.size(), is(1));
assertThat(Arrays.asList(protect.getPatterns()), contains("org.protect."));
assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
assertThat(hide.size(), is(1));
assertThat(Arrays.asList(hide.getPatterns()), contains("org.hide."));
assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testServerAddPatterns()
{
Server server = new Server();
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(server);
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(server);
assertThat(protect.size(), is(0));
assertThat(hide.size(), is(0));
WebAppClassLoading.addProtectedClasses(server, "org.protect.", "com.protect.");
WebAppClassLoading.addHiddenClasses(server, "org.hide.", "com.hide.");
assertThat(protect.size(), is(2));
assertThat(Arrays.asList(protect.getPatterns()), containsInAnyOrder("org.protect.", "com.protect."));
assertThat(server.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
assertThat(hide.size(), is(2));
assertThat(Arrays.asList(hide.getPatterns()), containsInAnyOrder("org.hide.", "com.hide."));
assertThat(server.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testEnvironmentDefaults()
{
Environment environment = Environment.get("Test");
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
assertThat(protect, equalTo(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES));
assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
assertThat(hide, equalTo(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES));
assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testEnvironmentAttributeDefaults()
{
Environment environment = Environment.get("Test");
ClassMatcher protect = new ClassMatcher("org.protect.");
environment.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, protect);
ClassMatcher hide = new ClassMatcher("org.hide.");
environment.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, hide);
assertThat(WebAppClassLoading.getProtectedClasses(environment), sameInstance(protect));
assertThat(WebAppClassLoading.getHiddenClasses(environment), sameInstance(hide));
}
@Test
public void testEnvironmentStringAttributeDefaults()
{
Environment environment = Environment.get("Test");
environment.setAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE, new String[] {"org.protect."});
environment.setAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE, new String[] {"org.hide."});
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
assertThat(protect.size(), is(1));
assertThat(Arrays.asList(protect.getPatterns()), contains("org.protect."));
assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
assertThat(hide.size(), is(1));
assertThat(Arrays.asList(hide.getPatterns()), contains("org.hide."));
assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testEnvironmentProgrammaticDefaults()
{
Environment environment = Environment.get("Test");
WebAppClassLoading.addProtectedClasses(environment, "org.protect.");
WebAppClassLoading.addHiddenClasses(environment, "org.hide.");
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
assertThat(protect.size(), is(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES.size() + 1));
assertThat(protect.getPatterns(), hasItemInArray("org.protect."));
for (String pattern : WebAppClassLoading.DEFAULT_PROTECTED_CLASSES)
assertThat(protect.getPatterns(), hasItemInArray(pattern));
assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
assertThat(hide.size(), is(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES.size() + 1));
assertThat(hide.getPatterns(), hasItemInArray("org.hide."));
for (String pattern : WebAppClassLoading.DEFAULT_HIDDEN_CLASSES)
assertThat(hide.getPatterns(), hasItemInArray(pattern));
assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
@Test
public void testEnvironmentAddPatterns()
{
Environment environment = Environment.get("Test");
ClassMatcher protect = WebAppClassLoading.getProtectedClasses(environment);
ClassMatcher hide = WebAppClassLoading.getHiddenClasses(environment);
assertThat(protect, equalTo(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES));
assertThat(hide, equalTo(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES));
WebAppClassLoading.addProtectedClasses(environment, "org.protect.", "com.protect.");
WebAppClassLoading.addHiddenClasses(environment, "org.hide.", "com.hide.");
assertThat(protect.size(), is(WebAppClassLoading.DEFAULT_PROTECTED_CLASSES.size() + 2));
assertThat(protect.getPatterns(), hasItemInArray("org.protect."));
assertThat(protect.getPatterns(), hasItemInArray("com.protect."));
for (String pattern : WebAppClassLoading.DEFAULT_PROTECTED_CLASSES)
assertThat(protect.getPatterns(), hasItemInArray(pattern));
assertThat(environment.getAttribute(WebAppClassLoading.PROTECTED_CLASSES_ATTRIBUTE), sameInstance(protect));
assertThat(hide.size(), is(WebAppClassLoading.DEFAULT_HIDDEN_CLASSES.size() + 2));
assertThat(hide.getPatterns(), hasItemInArray("org.hide."));
assertThat(hide.getPatterns(), hasItemInArray("com.hide."));
for (String pattern : WebAppClassLoading.DEFAULT_HIDDEN_CLASSES)
assertThat(hide.getPatterns(), hasItemInArray(pattern));
assertThat(environment.getAttribute(WebAppClassLoading.HIDDEN_CLASSES_ATTRIBUTE), sameInstance(hide));
}
}

View File

@ -397,7 +397,7 @@ public class ServerFCGIConnection extends AbstractMetaDataConnection implements
public void onFailure(int request, Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("Request {} failure on {}: {}", request, stream, failure);
LOG.debug("Request {} failure on {}", request, stream, failure);
if (stream != null)
{
Runnable runnable = stream.getHttpChannel().onFailure(new BadMessageException(null, failure));
@ -407,4 +407,16 @@ public class ServerFCGIConnection extends AbstractMetaDataConnection implements
stream = null;
}
}
@Override
public void close()
{
if (stream != null)
{
Runnable task = stream.getHttpChannel().onClose();
if (task != null)
task.run();
}
super.close();
}
}

View File

@ -22,7 +22,9 @@ import org.eclipse.jetty.util.thread.ThreadIdPool;
/**
* ThreadLocal data parsers for HTTP style dates
* @deprecated use {@link HttpDateTime} instead
*/
@Deprecated(since = "12.0.9", forRemoval = true)
public class DateParser
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@ -47,6 +49,10 @@ public class DateParser
"EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss"
};
/**
* @deprecated use {@link HttpDateTime#parseToEpoch(String)} instead
*/
@Deprecated(since = "12.0.9", forRemoval = true)
public static long parseDate(String date)
{
return DATE_PARSER.apply(DateParser::new, DateParser::parse, date);

View File

@ -67,7 +67,7 @@ public class GZIPContentDecoder implements Destroyable
_inflaterEntry = inflaterPool.acquire();
_inflater = _inflaterEntry.get();
_bufferSize = bufferSize;
_pool = byteBufferPool != null ? byteBufferPool : new ByteBufferPool.NonPooling();
_pool = byteBufferPool != null ? byteBufferPool : ByteBufferPool.NON_POOLING;
reset();
}

View File

@ -14,9 +14,6 @@
package org.eclipse.jetty.http;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
@ -921,22 +918,19 @@ public interface HttpCookie
*/
static String formatExpires(Instant expires)
{
return DateTimeFormatter.RFC_1123_DATE_TIME
.withZone(ZoneOffset.UTC)
.format(expires);
return HttpDateTime.format(expires);
}
/**
* <p>Parses the {@code Expires} attribute value
* (in RFC 1123 format) into an {@link Instant}.</p>
* <p>Parses the {@code Expires} Date/Time attribute value
* into an {@link Instant}.</p>
*
* @param expires an instant in the RFC 1123 string format
* @param expires a date/time in one of the RFC6265 supported formats
* @return an {@link Instant} parsed from the given string
*/
static Instant parseExpires(String expires)
{
// TODO: RFC 1123 format only for now, see https://www.rfc-editor.org/rfc/rfc2616#section-3.3.1.
return ZonedDateTime.parse(expires, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant();
return HttpDateTime.parse(expires).toInstant();
}
private static Map<String, String> lazyAttributePut(Map<String, String> attributes, String key, String value)

View File

@ -0,0 +1,248 @@
//
// ========================================================================
// Copyright (c) 1995 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.http;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.time.ZoneOffset.UTC;
/**
* HTTP Date/Time parsing and formatting.
*
* <p>Supports the following Date/Time formats found in both
* <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-date-time-formats">RFC 9110 (HTTP Semantics)</a> and
* <a href="https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1">RFC 6265 (HTTP State Management Mechanism)</a>
* </p>
*
* <ul>
* <li>{@code Sun, 06 Nov 1994 08:49:37 GMT} - RFC 1123 (preferred)</li>
* <li>{@code Sunday, 06-Nov-94 08:49:37 GMT} - RFC 850 (obsolete)</li>
* <li>{@code Sun Nov 6 08:49:37 1994} - ANSI C's {@code asctime()} format (obsolete)</li>
* </ul>
*/
public class HttpDateTime
{
private static final Logger LOG = LoggerFactory.getLogger(HttpDateTime.class);
private static final Index<Integer> MONTH_CACHE = new Index.Builder<Integer>()
.caseSensitive(false)
// Note: Calendar.Month fields are zero based.
.with("Jan", Calendar.JANUARY + 1)
.with("Feb", Calendar.FEBRUARY + 1)
.with("Mar", Calendar.MARCH + 1)
.with("Apr", Calendar.APRIL + 1)
.with("May", Calendar.MAY + 1)
.with("Jun", Calendar.JUNE + 1)
.with("Jul", Calendar.JULY + 1)
.with("Aug", Calendar.AUGUST + 1)
.with("Sep", Calendar.SEPTEMBER + 1)
.with("Oct", Calendar.OCTOBER + 1)
.with("Nov", Calendar.NOVEMBER + 1)
.with("Dec", Calendar.DECEMBER + 1)
.build();
/**
* Delimiters for parsing as found in <a href="https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1">RFC6265: Date/Time Delimiters</a>
*/
private static final String DELIMITERS = new String(
StringUtil.fromHexString(
"09" + // %x09
"202122232425262728292a2b2c2d2e2f" + // %x20-2F
"3b3c3d3e3f40" + // %x3B-40
"5b5c5d5e5f60" + // %x5B-60
"7b7c7d7e" // %x7B-7E
), StandardCharsets.US_ASCII);
private HttpDateTime()
{
}
/**
* Similar to {@link #parse(String)} but returns unix epoch
*
* @param datetime the Date/Time to parse.
* @return unix epoch in milliseconds, or -1 if unable to parse the input date/time
*/
public static long parseToEpoch(String datetime)
{
try
{
ZonedDateTime dateTime = parse(datetime);
return TimeUnit.SECONDS.toMillis(dateTime.toEpochSecond());
}
catch (IllegalArgumentException e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to parse Date/Time: {}", datetime, e);
return -1;
}
}
/**
* <p>Parses a Date/Time value</p>
*
* <p>
* Parsing is done according to the algorithm specified in
* <a href="https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1">RFC6265: Section 5.1.1: Date</a>
* </p>
*
* @param datetime a Date/Time string in a supported format
* @return an {@link ZonedDateTime} parsed from the given string
* @throws IllegalArgumentException if unable to parse date/time
*/
public static ZonedDateTime parse(String datetime)
{
Objects.requireNonNull(datetime, "Date/Time string cannot be null");
int year = -1;
int month = -1;
int day = -1;
int hour = -1;
int minute = -1;
int second = -1;
try
{
int tokenCount = 0;
StringTokenizer tokenizer = new StringTokenizer(datetime, DELIMITERS);
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken();
// ensure we don't exceed the number of expected tokens.
if (++tokenCount > 6)
{
// This is a horribly bad syntax / format
throw new IllegalStateException("Too many delimiters for a Date/Time format");
}
if (token.isBlank())
continue; // skip blank tokens
// RFC 6265 - Section 5.1.1 - Step 2.1 - time (00:00:00)
if (hour == -1 && token.length() == 8 && token.charAt(2) == ':' && token.charAt(5) == ':')
{
second = StringUtil.toInt(token, 6);
minute = StringUtil.toInt(token, 3);
hour = StringUtil.toInt(token, 0);
continue;
}
// RFC 6265 - Section 5.1.1 - Step 2.2
if (day == -1 && token.length() <= 2)
{
day = StringUtil.toInt(token, 0);
continue;
}
// RFC 6265 - Section 5.1.1 - Step 2.3
if (month == -1 && token.length() == 3)
{
Integer m = MONTH_CACHE.getBest(token);
if (m != null)
{
month = m;
continue;
}
}
// RFC 6265 - Section 5.1.1 - Step 2.4
if (year == -1)
{
if (token.length() <= 2)
{
year = StringUtil.toInt(token, 0);
}
else if (token.length() == 4)
{
year = StringUtil.toInt(token, 0);
}
continue;
}
}
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Ignore: Unable to parse Date/Time", x);
}
// RFC 6265 - Section 5.1.1 - Step 3
if ((year > 70) && (year <= 99))
year += 1900;
// RFC 6265 - Section 5.1.1 - Step 4
if ((year >= 0) && (year <= 69))
year += 2000;
// RFC 6265 - Section 5.1.1 - Step 5
if (day == -1)
throw new IllegalArgumentException("Missing [day]: " + datetime);
if (month == -1)
throw new IllegalArgumentException("Missing [month]: " + datetime);
if (year == -1)
throw new IllegalArgumentException("Missing [year]: " + datetime);
if (year < 1601)
throw new IllegalArgumentException("Too far in past [year]: " + datetime);
if (hour == -1)
throw new IllegalArgumentException("Missing [time]: " + datetime);
if (day < 1 || day > 31)
throw new IllegalArgumentException("Invalid [day]: " + datetime);
if (month < 1 || month > 12)
throw new IllegalArgumentException("Invalid [month]: " + datetime);
if (hour > 23)
throw new IllegalArgumentException("Invalid [hour]: " + datetime);
if (minute > 59)
throw new IllegalArgumentException("Invalid [minute]: " + datetime);
if (second > 59)
throw new IllegalArgumentException("Invalid [second]: " + datetime);
// RFC 6265 - Section 5.1.1 - Step 6
try
{
ZonedDateTime dateTime = ZonedDateTime.of(year,
month, day, hour, minute, second, 0, UTC);
// RFC 6265 - Section 5.1.1 - Step 7
return dateTime;
}
catch (DateTimeException e)
{
throw new IllegalArgumentException("Invalid date/time: " + datetime, e);
}
}
/**
* Formats provided Date/Time to a String following preferred RFC 1123 syntax from
* both HTTP and Cookie specs.
*
* @param datetime the date/time to format
* @return the String representation of the date/time
*/
public static String format(TemporalAccessor datetime)
{
return DateTimeFormatter.RFC_1123_DATE_TIME
.withZone(UTC)
.format(datetime);
}
}

View File

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
@ -517,10 +518,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
if (val == null)
return -1;
final long date = DateParser.parseDate(val);
if (date == -1)
throw new IllegalArgumentException("Cannot convert date: " + val);
return date;
return TimeUnit.SECONDS.toMillis(HttpDateTime.parse(val).toEpochSecond());
}
/**
@ -898,8 +896,8 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable add(String name, String value)
{
if (value == null)
throw new IllegalArgumentException("null value");
Objects.requireNonNull(name);
Objects.requireNonNull(value);
return add(new HttpField(name, value));
}
@ -914,6 +912,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable add(String name, long value)
{
Objects.requireNonNull(name);
return add(new HttpField.LongValueHttpField(name, value));
}
@ -928,6 +927,8 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable add(HttpHeader header, HttpHeaderValue value)
{
Objects.requireNonNull(header);
Objects.requireNonNull(value);
return add(header, value.toString());
}
@ -942,8 +943,8 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable add(HttpHeader header, String value)
{
if (value == null)
throw new IllegalArgumentException("null value");
Objects.requireNonNull(header);
Objects.requireNonNull(value);
return add(new HttpField(header, value));
}
@ -958,6 +959,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable add(HttpHeader header, long value)
{
Objects.requireNonNull(header);
return add(new HttpField.LongValueHttpField(header, value));
}
@ -969,6 +971,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable add(HttpField field)
{
Objects.requireNonNull(field);
ListIterator<HttpField> i = listIterator(size());
i.add(field);
return this;
@ -1000,8 +1003,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
default Mutable add(String name, List<String> list)
{
Objects.requireNonNull(name);
if (list == null)
throw new IllegalArgumentException("null list");
Objects.requireNonNull(list);
if (list.isEmpty())
return this;
if (list.size() == 1)
@ -1024,6 +1026,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable addCSV(HttpHeader header, String... values)
{
Objects.requireNonNull(header);
QuotedCSV existing = null;
for (HttpField f : this)
{
@ -1051,6 +1054,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable addCSV(String name, String... values)
{
Objects.requireNonNull(name);
QuotedCSV existing = null;
for (HttpField f : this)
{
@ -1078,6 +1082,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable addDateField(String name, long date)
{
Objects.requireNonNull(name);
add(name, DateGenerator.formatDate(date));
return this;
}
@ -1107,6 +1112,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default void ensureField(HttpField field)
{
Objects.requireNonNull(field);
HttpHeader header = field.getHeader();
// Is the field value multi valued?
if (field.getValue().indexOf(',') < 0)
@ -1138,6 +1144,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable put(HttpField field)
{
Objects.requireNonNull(field);
boolean put = false;
ListIterator<HttpField> i = listIterator();
while (i.hasNext())
@ -1172,6 +1179,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable put(String name, String value)
{
Objects.requireNonNull(name);
if (value == null)
return remove(name);
return put(new HttpField(name, value));
@ -1188,6 +1196,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable put(HttpHeader header, HttpHeaderValue value)
{
Objects.requireNonNull(header);
if (value == null)
return remove(header);
return put(new HttpField(header, value.toString()));
@ -1204,6 +1213,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable put(HttpHeader header, String value)
{
Objects.requireNonNull(header);
if (value == null)
return remove(header);
return put(new HttpField(header, value));
@ -1243,6 +1253,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable putDate(HttpHeader name, long date)
{
Objects.requireNonNull(name);
return put(name, DateGenerator.formatDate(date));
}
@ -1258,6 +1269,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable putDate(String name, long date)
{
Objects.requireNonNull(name);
return put(name, DateGenerator.formatDate(date));
}
@ -1271,6 +1283,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable put(HttpHeader header, long value)
{
Objects.requireNonNull(header);
if (value == 0 && header == HttpHeader.CONTENT_LENGTH)
return put(HttpFields.CONTENT_LENGTH_0);
return put(new HttpField.LongValueHttpField(header, value));
@ -1286,6 +1299,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable put(String name, long value)
{
Objects.requireNonNull(name);
if (value == 0 && HttpHeader.CONTENT_LENGTH.is(name))
return put(HttpFields.CONTENT_LENGTH_0);
return put(new HttpField.LongValueHttpField(name, value));
@ -1369,7 +1383,9 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable computeField(HttpHeader header, BiFunction<HttpHeader, List<HttpField>, HttpField> computeFn)
{
return put(computeFn.apply(header, stream().filter(f -> f.getHeader() == header).collect(Collectors.toList())));
Objects.requireNonNull(header);
HttpField result = computeFn.apply(header, stream().filter(f -> f.getHeader() == header).toList());
return result != null ? put(result) : remove(header);
}
/**
@ -1382,7 +1398,9 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable computeField(String name, BiFunction<String, List<HttpField>, HttpField> computeFn)
{
return put(computeFn.apply(name, stream().filter(f -> f.is(name)).collect(Collectors.toList())));
Objects.requireNonNull(name);
HttpField result = computeFn.apply(name, stream().filter(f -> f.is(name)).toList());
return result != null ? put(result) : remove(name);
}
/**
@ -1393,6 +1411,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable remove(HttpHeader header)
{
Objects.requireNonNull(header);
Iterator<HttpField> i = iterator();
while (i.hasNext())
{
@ -1430,6 +1449,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
*/
default Mutable remove(String name)
{
Objects.requireNonNull(name);
for (ListIterator<HttpField> i = listIterator(); i.hasNext(); )
{
HttpField f = i.next();
@ -1658,6 +1678,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
@Override
public Mutable put(HttpField field)
{
Objects.requireNonNull(field);
// rewrite put to ensure that removes are called before replace
int put = -1;
ListIterator<HttpField> i = _fields.listIterator();
@ -1677,7 +1698,7 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
{
field = onAddField(field);
if (field != null)
add(field);
_fields.add(field);
}
else
{

View File

@ -95,6 +95,7 @@ public class HttpParser
public static final Logger LOG = LoggerFactory.getLogger(HttpParser.class);
public static final int INITIAL_URI_LENGTH = 256;
private static final int MAX_CHUNK_LENGTH = Integer.MAX_VALUE / 16 - 16;
private static final String UNMATCHED_VALUE = "\u0000";
/**
* Cache of common {@link HttpField}s including: <UL>
@ -165,7 +166,7 @@ public class HttpParser
Map<String, HttpField> map = new LinkedHashMap<>();
for (HttpHeader h : HttpHeader.values())
{
HttpField httpField = new HttpField(h, (String)null);
HttpField httpField = new HttpField(h, UNMATCHED_VALUE);
map.put(httpField.toString(), httpField);
}
return map;
@ -1377,25 +1378,28 @@ public class HttpParser
String n = cachedField.getName();
String v = cachedField.getValue();
if (CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode))
if (v != UNMATCHED_VALUE)
{
// Have to get the fields exactly from the buffer to match case
String en = BufferUtil.toString(buffer, buffer.position() - 1, n.length(), StandardCharsets.US_ASCII);
if (!n.equals(en))
if (CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode))
{
reportComplianceViolation(CASE_SENSITIVE_FIELD_NAME, en);
n = en;
cachedField = new HttpField(cachedField.getHeader(), n, v);
// Have to get the fields exactly from the buffer to match case
String en = BufferUtil.toString(buffer, buffer.position() - 1, n.length(), StandardCharsets.US_ASCII);
if (!n.equals(en))
{
reportComplianceViolation(CASE_SENSITIVE_FIELD_NAME, en);
n = en;
cachedField = new HttpField(cachedField.getHeader(), n, v);
}
}
}
if (v != null && isHeaderCacheCaseSensitive())
{
String ev = BufferUtil.toString(buffer, buffer.position() + n.length() + 1, v.length(), StandardCharsets.ISO_8859_1);
if (!v.equals(ev))
if (isHeaderCacheCaseSensitive())
{
v = ev;
cachedField = new HttpField(cachedField.getHeader(), n, v);
String ev = BufferUtil.toString(buffer, buffer.position() + n.length() + 1, v.length(), StandardCharsets.ISO_8859_1);
if (!v.equals(ev))
{
v = ev;
cachedField = new HttpField(cachedField.getHeader(), n, v);
}
}
}
@ -1404,7 +1408,7 @@ public class HttpParser
int posAfterName = buffer.position() + n.length() + 1;
if (v == null || (posAfterName + v.length()) >= buffer.limit())
if (v == UNMATCHED_VALUE || (posAfterName + v.length()) >= buffer.limit())
{
// Header only
setState(FieldState.VALUE);

View File

@ -239,7 +239,7 @@ public class MultiPartByteRanges
super(null, null, headers);
this.resource = resource;
this.byteRange = byteRange;
this.bufferPool = bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool;
this.bufferPool = bufferPool == null ? ByteBufferPool.NON_POOLING : bufferPool;
}
@Override

View File

@ -77,7 +77,7 @@ public class CachingHttpContentFactory implements HttpContent.Factory
public CachingHttpContentFactory(HttpContent.Factory authority, ByteBufferPool bufferPool)
{
_authority = authority;
_bufferPool = bufferPool != null ? bufferPool : new ByteBufferPool.NonPooling();
_bufferPool = bufferPool != null ? bufferPool : ByteBufferPool.NON_POOLING;
}
protected ConcurrentMap<String, CachingHttpContent> getCache()

View File

@ -14,7 +14,6 @@
package org.eclipse.jetty.http;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.List;
import org.junit.jupiter.params.ParameterizedTest;
@ -90,7 +89,7 @@ public class HttpCookieTest
public static List<Arguments> invalidAttributes()
{
return List.of(
Arguments.of("Expires", "blah", DateTimeParseException.class),
Arguments.of("Expires", "blah", IllegalArgumentException.class),
Arguments.of("HttpOnly", "blah", IllegalArgumentException.class),
Arguments.of("Max-Age", "blah", NumberFormatException.class),
Arguments.of("SameSite", "blah", IllegalArgumentException.class),
@ -105,4 +104,5 @@ public class HttpCookieTest
assertThrows(failure, () -> HttpCookie.build("A", "1")
.attribute(name, value));
}
}

View File

@ -0,0 +1,214 @@
//
// ========================================================================
// Copyright (c) 1995 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.http;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class HttpDateTimeTest
{
public static Stream<Arguments> dateTimeValid()
{
List<Arguments> args = new ArrayList<>();
// Examples taken from:
// - https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.7
// - RFC 6265
// - issue discussions
// - curl results from major players like cloudflare
// Preferred RFC 1123 syntax
args.add(Arguments.of("Wed, 09 Jun 2021 10:18:14 GMT", "2021/06/09 10:18:14 GMT"));
args.add(Arguments.of("Sun, 06 Nov 1994 08:49:37 GMT", "1994/11/06 08:49:37 GMT"));
args.add(Arguments.of("Tue, 16 Apr 2024 17:28:27 GMT", "2024/04/16 17:28:27 GMT"));
args.add(Arguments.of("Thu, 2 Nov 2017 13:37:22 GMT", "2017/11/02 13:37:22 GMT"));
// Obsolete RFC 850 syntax
args.add(Arguments.of("Tue, 10-May-22 21:23:37 GMT", "2022/05/10 21:23:37 GMT"));
args.add(Arguments.of("Sun, 03-May-20 18:54:31 GMT", "2020/05/03 18:54:31 GMT"));
args.add(Arguments.of("Tue, 16-Apr-24 17:28:27 GMT", "2024/04/16 17:28:27 GMT"));
args.add(Arguments.of("Sunday, 06-Nov-94 08:49:37 GMT", "1994/11/06 08:49:37 GMT"));
args.add(Arguments.of("Sun, 06-Nov-94 08:49:37 GMT", "1994/11/06 08:49:37 GMT"));
args.add(Arguments.of("Mon, 05-May-2014 13:13:45 GMT", "2014/05/05 13:13:45 GMT"));
args.add(Arguments.of("Thu, 02-May-2013 13:14:16 GMT", "2013/05/02 13:14:16 GMT"));
// Obsolete ANSI C's asctime() format
args.add(Arguments.of("Sun Nov 6 08:49:37 1994", "1994/11/06 08:49:37 GMT"));
args.add(Arguments.of("Tue Apr 16 09:59:47 0000", "2000/04/16 09:59:47 GMT"));
args.add(Arguments.of("Tue Apr 16 09:59:47 00", "2000/04/16 09:59:47 GMT"));
// Unusual formats seen elsewhere
// RFC 1123 syntax with single digit day
args.add(Arguments.of("Wed, 9 Jun 2021 10:18:14 GMT", "2021/06/09 10:18:14 GMT"));
// RFC 850 with single digit day
args.add(Arguments.of("Sun, 3-May-20 18:54:31 GMT", "2020/05/03 18:54:31 GMT"));
// unix epoch (and zero time)
args.add(Arguments.of("Thu, 01 Jan 1970 00:00:00 GMT", "1970/01/01 00:00:00 GMT"));
// extra spaces
args.add(Arguments.of(" Wed, 22 Aug 1984 13:14:15 GMT", "1984/08/22 13:14:15 GMT"));
// seemingly invalid day (looks negative, but it isn't, as '-' is a delimiter per spec)
args.add(Arguments.of("Thu, -2 Nov 2017 13:37:22 GMT", "2017/11/02 13:37:22 GMT"));
// year zero (which is a valid year, assumed to be the year 2000 per spec)
args.add(Arguments.of("Thu, 22 Nov 0000 23:45:12 GMT", "2000/11/22 23:45:12 GMT"));
args.add(Arguments.of("Sun, 03-May-00 18:54:31 GMT", "2000/05/03 18:54:31 GMT"));
// unexpected timezone (the timezone is ignored per spec)
args.add(Arguments.of("Sun, 06 Nov 1994 08:49:37 PST", "1994/11/06 08:49:37 GMT"));
// long weekday (weekday is ignored per spec)
args.add(Arguments.of("Wednesday, 09 Jun 2021 10:18:14 GMT", "2021/06/09 10:18:14 GMT"));
// date time before unix epoch
args.add(Arguments.of("Fri, 13 Mar 1964 11:22:33 GMT", "1964/03/13 11:22:33 GMT"));
return args.stream();
}
@ParameterizedTest
@MethodSource("dateTimeValid")
public void testParseValid(String input, String expected)
{
ZonedDateTime actual = HttpDateTime.parse(input);
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
String actualStr = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss O").format(actual);
assertEquals(expected, actualStr);
}
public static Stream<Arguments> dateTimeInvalid()
{
List<Arguments> args = new ArrayList<>();
// Preferred RFC 1123 syntax
// - invalid Year
args.add(Arguments.of("Sun, 06 Nov 65535 08:49:37 GMT", "Missing [year]"));
args.add(Arguments.of("Thu, 14 Oct 1535 01:02:00 GMT", "Too far in past [year]"));
args.add(Arguments.of("Wed, 09 Jun -123 10:18:14 GMT", "Missing [year]"));
// - no year
args.add(Arguments.of("Wed, 09 Jun 10:18:14 GMT", "Missing [year]"));
// - invalid day
args.add(Arguments.of("Tue, 00 Apr 2024 17:28:27 GMT", "Invalid [day]"));
args.add(Arguments.of("Tue, 31 Feb 2020 17:28:27 GMT", "Invalid date/time"));
// - long form month
args.add(Arguments.of("Wed, 22 August 1984 01:02:03 GMT", "Missing [month]"));
// - no day
args.add(Arguments.of("Thu, Nov 2017 13:37:22 GMT", "Missing [day]"));
// - single digit hour
args.add(Arguments.of("Wed, 09 Jun 2021 2:18:14 GMT", "Missing [time]"));
// - no time
args.add(Arguments.of("Thu, 01 Jan 1970 GMT", "Missing [time]"));
// - invalid time (negative hour)
args.add(Arguments.of("Thu, 2 Nov 2017 -3:14:15 GMT", "Missing [time]"));
// - invalid time (negative minute)
args.add(Arguments.of("Thu, 2 Nov 2017 13:-4:15 GMT", "Missing [time]"));
// - invalid time (negative second)
args.add(Arguments.of("Thu, 2 Nov 2017 13:14:-5 GMT", "Missing [time]"));
// Obsolete RFC 850 syntax
// - invalid year
args.add(Arguments.of("Sun, 03-May-65535 18:54:31 GMT", "Missing [year]"));
// - no year
args.add(Arguments.of("Tue, 16-Apr- 17:28:27 GMT", "Missing [year]"));
// - invalid day
args.add(Arguments.of("Sunday, 00-Nov-94 08:49:37 GMT", "Invalid [day]"));
// - long form month
args.add(Arguments.of("Wednesday, 22-August-84 01:02:03 GMT", "Missing [month]"));
// - single digit hour
args.add(Arguments.of("Sunday, 01-Nov-94 8:49:37 GMT", "Missing [time]"));
// - no day (the '94' is parsed as day as its the first 2 digit number in the string)
args.add(Arguments.of("Sun, Nov-94 08:49:37 GMT", "Missing [year]"));
// - no time
args.add(Arguments.of("Mon, 05-May-2014 GMT", "Missing [time]"));
// - bad time (negative hour)
args.add(Arguments.of("Thu, 02-May-2013 -3:14:15 GMT", "Missing [time]"));
// - bad time (negative minute)
args.add(Arguments.of("Thu, 02-May-2013 13:-4:15 GMT", "Missing [time]"));
// - bad time (negative second)
args.add(Arguments.of("Thu, 02-May-2013 13:14:-5 GMT", "Missing [time]"));
// - bad time (no seconds) - will not find time
args.add(Arguments.of("Thu, 02-May-2013 13:14 GMT", "Missing [time]"));
// - bad time (am/pm) - will not find time, and will not understand the AM/PM
args.add(Arguments.of("Thu, 02-May-2013 13:14 PM GMT", "Missing [time]"));
// Obsolete ANSI C's asctime() format
// - invalid year
args.add(Arguments.of("Sun Nov 6 08:49:37 65535", "Missing [year]"));
// Horribly bad Date/Time formats
args.add(Arguments.of("3%~", "Missing [month]"));
args.add(Arguments.of("3%~ GMT", "Missing [month]"));
return args.stream();
}
@ParameterizedTest
@MethodSource("dateTimeInvalid")
public void testParseInvalid(String input, String expectedMsg)
{
IllegalArgumentException syntaxException = assertThrows(
IllegalArgumentException.class, () -> HttpDateTime.parse(input)
);
assertThat(syntaxException.getMessage(), containsString(expectedMsg));
}
@ParameterizedTest
@MethodSource("dateTimeInvalid")
public void testParseToEpochInvalid(String input, String expectedMsg)
{
assertThat(HttpDateTime.parseToEpoch(input), is(-1L));
}
@Test
public void testParseToEpochBeforeEpoch()
{
long epoch = HttpDateTime.parseToEpoch("Fri, 13 Mar 1964 11:22:33 GMT");
assertThat(epoch, is(-183127047000L));
}
@Test
public void testFormatZonedDateTime()
{
// When "Back to the Future" released
ZonedDateTime zonedDateTime = ZonedDateTime.of(1984, 7, 3, 8, 10, 30, 0, ZoneId.of("US/Pacific"));
String actual = HttpDateTime.format(zonedDateTime);
assertThat(actual, is("Tue, 3 Jul 1984 15:10:30 GMT"));
}
@Test
public void testFormatInstant()
{
// When "Tron" released
long epochMillis = 395054120000L;
Instant instant = Instant.ofEpochMilli(epochMillis);
String actual = HttpDateTime.format(instant);
assertThat(actual, is("Fri, 9 Jul 1982 09:15:20 GMT"));
}
}

View File

@ -1004,7 +1004,7 @@ public class HttpFieldsTest
public void testAddNullValueList()
{
HttpFields.Mutable fields = HttpFields.build();
assertThrows(IllegalArgumentException.class, () -> fields.add("name", (List<String>)null));
assertThrows(NullPointerException.class, () -> fields.add("name", (List<String>)null));
assertThat(fields.size(), is(0));
List<String> list = new ArrayList<>();
fields.add("name", list);
@ -1374,4 +1374,56 @@ public class HttpFieldsTest
fields.ensureField(new HttpField("Test", "three, four"));
assertThat(fields.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Test: one, two, three, four"));
}
@Test
public void testWrapperComputeFieldCallingOnField()
{
var wrapper = new HttpFields.Mutable.Wrapper(HttpFields.build())
{
final List<String> actions = new ArrayList<>();
@Override
public HttpField onAddField(HttpField field)
{
actions.add("onAddField");
return super.onAddField(field);
}
@Override
public boolean onRemoveField(HttpField field)
{
actions.add("onRemoveField");
return super.onRemoveField(field);
}
@Override
public HttpField onReplaceField(HttpField oldField, HttpField newField)
{
actions.add("onReplaceField");
return super.onReplaceField(oldField, newField);
}
};
wrapper.computeField("non-existent", (name, httpFields) -> null);
assertThat(wrapper.size(), is(0));
assertThat(wrapper.actions, is(List.of()));
wrapper.computeField("non-existent", (name, httpFields) -> new HttpField("non-existent", "a"));
wrapper.computeField("non-existent", (name, httpFields) -> new HttpField("non-existent", "b"));
wrapper.computeField("non-existent", (name, httpFields) -> null);
assertThat(wrapper.size(), is(0));
assertThat(wrapper.actions, is(List.of("onAddField", "onReplaceField", "onRemoveField")));
wrapper.actions.clear();
wrapper.computeField(HttpHeader.VARY, (name, httpFields) -> null);
assertThat(wrapper.size(), is(0));
assertThat(wrapper.actions, is(List.of()));
wrapper.computeField(HttpHeader.VARY, (name, httpFields) -> new HttpField(HttpHeader.VARY, "a"));
wrapper.computeField(HttpHeader.VARY, (name, httpFields) -> new HttpField(HttpHeader.VARY, "b"));
wrapper.computeField(HttpHeader.VARY, (name, httpFields) -> null);
assertThat(wrapper.size(), is(0));
assertThat(wrapper.actions, is(List.of("onAddField", "onReplaceField", "onRemoveField")));
wrapper.actions.clear();
}
}

View File

@ -99,8 +99,8 @@ public class HttpGeneratorClientTest
HttpFields.Mutable fields = HttpFields.build();
fields.add("Host", "something");
assertThrows(IllegalArgumentException.class, () -> fields.add("Null", (String)null));
assertThrows(IllegalArgumentException.class, () -> fields.add("Null", (List<String>)null));
assertThrows(NullPointerException.class, () -> fields.add("Null", (String)null));
assertThrows(NullPointerException.class, () -> fields.add("Null", (List<String>)null));
fields.add("Empty", "");
RequestInfo info = new RequestInfo("GET", "/index.html", fields);
assertFalse(gen.isChunking());

View File

@ -1971,7 +1971,8 @@ public class HttpParserTest
"+10",
"1.0",
"1,0",
"10,"
"10,",
"10A"
})
public void testBadContentLengths(String contentLength)
{
@ -1994,6 +1995,116 @@ public class HttpParserTest
assertEquals(HttpParser.State.CLOSED, parser.getState());
}
@ParameterizedTest
@ValueSource(strings = {
" 10 ",
"10 ",
" 10",
"\t10",
"\t10\t",
"10\t",
" \t \t \t 10"
})
public void testContentLengthWithOWS(String contentLength)
{
String rawRequest = """
GET /test HTTP/1.1\r
Host: localhost\r
Content-Length: @LEN@\r
\r
1234567890
""".replace("@LEN@", contentLength);
ByteBuffer buffer = BufferUtil.toBuffer(rawRequest);
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/test", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
assertEquals("Host", _hdr[0]);
assertEquals("localhost", _val[0]);
assertEquals(_content.length(), 10);
assertEquals(parser.getContentLength(), 10);
assertTrue(_headerCompleted);
assertTrue(_messageCompleted);
}
@ParameterizedTest
@ValueSource(strings = {
" chunked ",
"chunked ",
" chunked",
"\tchunked",
"\tchunked\t",
"chunked\t",
" \t \t \t chunked"
})
public void testTransferEncodingWithOWS(String transferEncoding)
{
String rawRequest = """
GET /test HTTP/1.1\r
Host: localhost\r
Transfer-Encoding: @TE@\r
\r
1\r
X\r
0\r
\r
""".replace("@TE@", transferEncoding);
ByteBuffer buffer = BufferUtil.toBuffer(rawRequest);
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/test", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
assertEquals("Host", _hdr[0]);
assertEquals("localhost", _val[0]);
assertEquals("Transfer-Encoding", _hdr[1]);
assertEquals("chunked", _val[1]);
assertTrue(_headerCompleted);
assertTrue(_messageCompleted);
}
@ParameterizedTest
@ValueSource(strings = {
" testhost ",
"testhost ",
" testhost",
"\ttesthost",
"\ttesthost\t",
"testhost\t",
" \t \t \t testhost"
})
public void testHostWithOWS(String host)
{
String rawRequest = """
GET /test HTTP/1.1\r
Host: @HOST@\r
\r
""".replace("@HOST@", host);
ByteBuffer buffer = BufferUtil.toBuffer(rawRequest);
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/test", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
assertEquals("Host", _hdr[0]);
assertEquals("testhost", _val[0]);
assertTrue(_headerCompleted);
assertTrue(_messageCompleted);
}
@ParameterizedTest
@ValueSource(strings = {"\r\n", "\n"})
public void testMultipleContentLengthWithLargerThenCorrectValue(String eoln)

View File

@ -55,7 +55,7 @@ public class DataGenerateParseTest
byteBuffer.get(inputBytes);
DataFrame input = new DataFrame(ByteBuffer.wrap(inputBytes), true);
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
new MessageGenerator(bufferPool, null, true).generate(accumulator, 0, input, null);

View File

@ -34,7 +34,7 @@ public class GoAwayGenerateParseTest
{
GoAwayFrame input = GoAwayFrame.CLIENT_GRACEFUL;
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
new ControlGenerator(bufferPool, true).generate(accumulator, 0, input, null);

View File

@ -49,7 +49,7 @@ public class HeadersGenerateParseTest
QpackEncoder encoder = new QpackEncoder(instructions -> {});
encoder.setMaxHeadersSize(4 * 1024);
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
new MessageGenerator(bufferPool, encoder, true).generate(accumulator, 0, input, null);

View File

@ -46,7 +46,7 @@ public class SettingsGenerateParseTest
{
SettingsFrame input = new SettingsFrame(settings);
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
new ControlGenerator(bufferPool, true).generate(accumulator, 0, input, null);

View File

@ -379,7 +379,7 @@ public class QpackDecoder implements Dumpable
LOG.debug("Duplicate: index={}", index);
DynamicTable dynamicTable = _context.getDynamicTable();
Entry referencedEntry = dynamicTable.get(index);
Entry referencedEntry = dynamicTable.getRelative(index);
// Add the new Entry to the DynamicTable.
Entry entry = new Entry(referencedEntry.getHttpField());
@ -396,7 +396,7 @@ public class QpackDecoder implements Dumpable
StaticTable staticTable = QpackContext.getStaticTable();
DynamicTable dynamicTable = _context.getDynamicTable();
Entry referencedEntry = isDynamicTableIndex ? dynamicTable.get(nameIndex) : staticTable.get(nameIndex);
Entry referencedEntry = isDynamicTableIndex ? dynamicTable.getRelative(nameIndex) : staticTable.get(nameIndex);
// Add the new Entry to the DynamicTable.
Entry entry = new Entry(new HttpField(referencedEntry.getHttpField().getHeader(), referencedEntry.getHttpField().getName(), value));

View File

@ -107,6 +107,11 @@ public class QpackEncoder implements Dumpable
_parser = new EncoderInstructionParser(_instructionHandler);
}
QpackContext getQpackContext()
{
return _context;
}
Map<Long, StreamInfo> getStreamInfoMap()
{
return _streamInfoMap;

View File

@ -68,14 +68,14 @@ public abstract class EncodableEntry
{
// Indexed Field Line with Static Reference.
buffer.put((byte)(0x80 | 0x40));
int relativeIndex = _entry.getIndex();
NBitIntegerEncoder.encode(buffer, 6, relativeIndex);
int index = _entry.getIndex();
NBitIntegerEncoder.encode(buffer, 6, index);
}
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
buffer.put((byte)0x80);
int relativeIndex = base - (_entry.getIndex() + 1);
int relativeIndex = (base - 1) - _entry.getIndex();
NBitIntegerEncoder.encode(buffer, 6, relativeIndex);
}
else
@ -100,7 +100,7 @@ public abstract class EncodableEntry
else if (_entry.getIndex() < base)
{
// Indexed Field Line with Dynamic Reference.
int relativeIndex = base - (_entry.getIndex() + 1);
int relativeIndex = (base - 1) - _entry.getIndex();
return NBitIntegerEncoder.octetsNeeded(6, relativeIndex);
}
else
@ -150,7 +150,7 @@ public abstract class EncodableEntry
{
// Literal Field Line with Dynamic Name Reference.
buffer.put((byte)(0x40 | (allowIntermediary ? 0x20 : 0x00)));
int relativeIndex = base - (_nameEntry.getIndex() + 1);
int relativeIndex = (base - 1) - _nameEntry.getIndex();
NBitIntegerEncoder.encode(buffer, 4, relativeIndex);
}
else

View File

@ -66,14 +66,6 @@ public class QpackContext
return _dynamicTable.get(StringUtil.asciiToLowerCase(name));
}
public Entry get(int index)
{
if (index <= StaticTable.STATIC_SIZE)
return __staticTable.get(index);
return _dynamicTable.get(index);
}
/**
* Get the relative Index of an entry.
* @param entry the entry to get the index of.
@ -83,7 +75,6 @@ public class QpackContext
{
if (entry.isStatic())
return entry.getIndex();
return _dynamicTable.index(entry);
return _dynamicTable.relativeIndexOf(entry);
}
}

View File

@ -229,7 +229,7 @@ public class EncodedFieldSection
public HttpField decode(QpackContext context)
{
if (_dynamicTable)
return context.getDynamicTable().getAbsolute(_base - (_index + 1)).getHttpField();
return context.getDynamicTable().getRelative(_base, _index).getHttpField();
else
return QpackContext.getStaticTable().get(_index).getHttpField();
}
@ -247,7 +247,7 @@ public class EncodedFieldSection
@Override
public HttpField decode(QpackContext context)
{
return context.getDynamicTable().getAbsolute(_base + _index).getHttpField();
return context.getDynamicTable().getPostBase(_base, _index).getHttpField();
}
}
@ -272,7 +272,7 @@ public class EncodedFieldSection
{
HttpField field;
if (_dynamicTable)
field = context.getDynamicTable().getAbsolute(_base - (_nameIndex + 1)).getHttpField();
field = context.getDynamicTable().getRelative(_base, _nameIndex).getHttpField();
else
field = QpackContext.getStaticTable().get(_nameIndex).getHttpField();
@ -296,7 +296,7 @@ public class EncodedFieldSection
@Override
public HttpField decode(QpackContext context)
{
HttpField field = context.getDynamicTable().getAbsolute(_base + _nameIndex).getHttpField();
HttpField field = context.getDynamicTable().getPostBase(_base, _nameIndex).getHttpField();
return new HttpField(field.getHeader(), field.getName(), _value);
}
}

View File

@ -115,17 +115,9 @@ public class DynamicTable implements Iterable<Entry>, Dumpable
* @param entry the entry to find the relative index of.
* @return the relative index of this entry.
*/
public int index(Entry entry)
public int relativeIndexOf(Entry entry)
{
if (_entries.isEmpty())
throw new IllegalArgumentException("Invalid Index");
Entry firstEntry = _entries.get(0);
int index = entry.getIndex() - firstEntry.getIndex();
if (index >= _entries.size())
throw new IllegalArgumentException("Invalid Index");
return index;
return _absoluteIndex - 1 - entry.getIndex();
}
/**
@ -146,7 +138,7 @@ public class DynamicTable implements Iterable<Entry>, Dumpable
if (index < 0 || index >= _entries.size())
throw new IllegalArgumentException("Invalid Index " + index);
return _entries.get(index);
return get(index);
}
public Entry get(int index)
@ -164,9 +156,24 @@ public class DynamicTable implements Iterable<Entry>, Dumpable
return _fieldMap.get(field);
}
public Entry getRelative(int index)
{
return getRelative(getInsertCount(), index);
}
public Entry getRelative(int base, int index)
{
return getAbsolute(base - 1 - index);
}
public Entry getPostBase(int base, int index)
{
return getAbsolute(base + index);
}
public int getBase()
{
if (_entries.size() == 0)
if (_entries.isEmpty())
return _absoluteIndex;
return _entries.get(0).getIndex();
}

View File

@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class DecoderInstructionParserTest
{
private final ByteBufferPool bufferPool = new ByteBufferPool.NonPooling();
private final ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
private DecoderInstructionParser _instructionParser;
private DecoderParserDebugHandler _handler;

View File

@ -25,7 +25,7 @@ import static org.hamcrest.Matchers.is;
public class InstructionGeneratorTest
{
private final ByteBufferPool _bufferPool = new ByteBufferPool.NonPooling();
private final ByteBufferPool _bufferPool = ByteBufferPool.NON_POOLING;
private String toHexString(Instruction instruction)
{

View File

@ -0,0 +1,116 @@
//
// ========================================================================
// Copyright (c) 1995 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.http3.qpack;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class QpackDecoderTest
{
private QpackDecoder _decoder;
private TestDecoderHandler _decoderHandler;
@BeforeEach
public void before()
{
_decoderHandler = new TestDecoderHandler();
_decoder = new QpackDecoder(_decoderHandler);
_decoder.setBeginNanoTimeSupplier(NanoTime::now);
}
@Test
public void testDynamicNameReference() throws Exception
{
_decoder.setMaxTableCapacity(2048);
QpackDecoder.InstructionHandler instructionHandler = _decoder.getInstructionHandler();
instructionHandler.onSetDynamicTableCapacity(2048);
instructionHandler.onInsertWithLiteralName("name0", "value0");
instructionHandler.onInsertWithLiteralName("name1", "value1");
instructionHandler.onInsertWithLiteralName("name2", "value2");
instructionHandler.onInsertWithLiteralName("name3", "value3");
instructionHandler.onInsertNameWithReference(5, false, "static0");
instructionHandler.onInsertWithLiteralName("name4", "value4");
instructionHandler.onInsertNameWithReference(2, true, "dynamic0");
instructionHandler.onDuplicate(6);
// Indexes into the static table are absolute.
Entry entry = _decoder.getQpackContext().getDynamicTable().get(4);
assertThat(entry.getHttpField().getName(), equalTo("cookie"));
assertThat(entry.getHttpField().getValue(), equalTo("static0"));
// Named reference is relative to the most recently inserted entry.
entry = _decoder.getQpackContext().getDynamicTable().get(6);
assertThat(entry.getHttpField().getName(), equalTo("name3"));
assertThat(entry.getHttpField().getValue(), equalTo("dynamic0"));
// Duplicate reference is relative to the most recently inserted entry.
entry = _decoder.getQpackContext().getDynamicTable().get(7);
assertThat(entry.getHttpField().getName(), equalTo("name0"));
assertThat(entry.getHttpField().getValue(), equalTo("value0"));
}
@Test
public void testDecodeRequest() throws Exception
{
_decoder.setMaxTableCapacity(2048);
QpackDecoder.InstructionHandler instructionHandler = _decoder.getInstructionHandler();
instructionHandler.onSetDynamicTableCapacity(2048);
instructionHandler.onInsertNameWithReference(0, false, "licensed.app");
instructionHandler.onInsertWithLiteralName("sec-ch-ua", "\"Not A(Brand\";v=\"99\", \"Brave\";v=\"121\", \"Chromium\";v=\"121\"");
instructionHandler.onInsertWithLiteralName("sec-ch-ua-mobile", "?0");
instructionHandler.onInsertWithLiteralName("sec-ch-ua-platform", "Windows");
instructionHandler.onInsertWithLiteralName("dnt", "1");
instructionHandler.onInsertNameWithReference(95, false, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36");
instructionHandler.onInsertNameWithReference(29, false, "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8");
instructionHandler.onInsertWithLiteralName("sec-gpc", "1");
instructionHandler.onInsertWithLiteralName("sec-fetch-site", "none");
instructionHandler.onInsertWithLiteralName("sec-fetch-mode", "navigate");
instructionHandler.onInsertWithLiteralName("sec-fetch-user", "?1");
instructionHandler.onInsertWithLiteralName("sec-fetch-dest", "value=document");
instructionHandler.onInsertNameWithReference(72, false, "en-US,en;q=0.9");
instructionHandler.onInsertNameWithReference(8, false, "2024-02-17T02:19:47.4433882Z");
instructionHandler.onInsertNameWithReference(1, false, "/login/GoogleLogin.js");
instructionHandler.onInsertNameWithReference(90, false, "https://licensed.app");
instructionHandler.onInsertNameWithReference(7, true, "same-origin");
instructionHandler.onInsertNameWithReference(7, true, "cors");
instructionHandler.onInsertNameWithReference(6, true, "script");
instructionHandler.onInsertNameWithReference(13, false, "https://licensed.app/");
assertTrue(_decoder.decode(0, fromHex("1500D193D78592848f918e90Dd8c83828180Df87"), _decoderHandler));
MetaData metaData = _decoderHandler.getMetaData();
// Check headers were correctly referenced from dynamic table.
assertThat(metaData.getHttpFields().get("sec-fetch-site"), equalTo("same-origin"));
assertThat(metaData.getHttpFields().get("sec-fetch-mode"), equalTo("cors"));
assertThat(metaData.getHttpFields().get("sec-fetch-dest"), equalTo("script"));
}
private ByteBuffer fromHex(String hex)
{
return BufferUtil.toBuffer(StringUtil.fromHexString(hex));
}
}

View File

@ -33,7 +33,7 @@ public class QpackTestUtil
{
public static ByteBuffer toBuffer(Instruction... instructions)
{
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
for (Instruction instruction : instructions)
{
@ -57,7 +57,7 @@ public class QpackTestUtil
public static ByteBuffer toBuffer(List<Instruction> instructions)
{
ByteBufferPool bufferPool = new ByteBufferPool.NonPooling();
ByteBufferPool bufferPool = ByteBufferPool.NON_POOLING;
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
instructions.forEach(i -> i.encode(bufferPool, accumulator));
assertThat(accumulator.getSize(), is(instructions.size()));

View File

@ -20,7 +20,7 @@ lib/http3/jetty-http3-qpack-${jetty.version}.jar
lib/http3/jetty-http3-server-${jetty.version}.jar
lib/http3/jetty-quic-common-${jetty.version}.jar
lib/http3/jetty-quic-quiche-common-${jetty.version}.jar
lib/http3/jetty-quic-quiche-foreign-incubator-${jetty.version}.jar
lib/http3/jetty-quic-quiche-foreign-${jetty.version}.jar
lib/http3/jetty-quic-quiche-jna-${jetty.version}.jar
lib/http3/jetty-quic-server-${jetty.version}.jar

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.http3.tests;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -21,6 +22,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
@ -30,6 +32,8 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -236,4 +240,45 @@ public class HttpClientTransportOverHTTP3Test extends AbstractClientServerTest
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testDynamicTableReference() throws Exception
{
start(new Handler.Abstract()
{
@Override
public boolean handle(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
HttpFields.Mutable headers = response.getHeaders();
headers.add("header1", "value1");
headers.add("header2", "value2");
headers.add("header3", "value3");
headers.add("header4", "value4");
headers.add("header5", "value5");
// This header should reference the named header already in the dynamic table.
headers.add("header5", "value6");
response.write(true, null, callback);
return true;
}
});
ContentResponse response = httpClient.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.timeout(5, TimeUnit.SECONDS)
.send();
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
assertHeader(response, "header1", "value1");
assertHeader(response, "header2", "value2");
assertHeader(response, "header3", "value3");
assertHeader(response, "header4", "value4");
assertHeader(response, "header5", "value5", "value6");
}
private void assertHeader(ContentResponse response, String header, String... values)
{
assertThat(response.getHeaders().getValuesList(header), equalTo(Arrays.asList(values)));
}
}

View File

@ -2,4 +2,5 @@
org.eclipse.jetty.jmx.LEVEL=INFO
#org.eclipse.jetty.http3.LEVEL=DEBUG
#org.eclipse.jetty.quic.LEVEL=DEBUG
#org.eclipse.jetty.http3.qpack.QpackDecoder.LEVEL=DEBUG
org.eclipse.jetty.quic.quiche.LEVEL=INFO

View File

@ -658,12 +658,12 @@ public class ArrayByteBufferPool implements ByteBufferPool, Dumpable
public Tracking()
{
this(0, -1, Integer.MAX_VALUE);
super();
}
public Tracking(int minCapacity, int maxCapacity, int maxBucketSize)
{
this(minCapacity, maxCapacity, maxBucketSize, -1L, -1L);
super(minCapacity, maxCapacity, maxBucketSize);
}
public Tracking(int minCapacity, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory)

View File

@ -44,7 +44,7 @@ public class ByteBufferAccumulator implements AutoCloseable
public ByteBufferAccumulator(ByteBufferPool bufferPool, boolean direct)
{
_bufferPool = (bufferPool == null) ? new ByteBufferPool.NonPooling() : bufferPool;
_bufferPool = (bufferPool == null) ? ByteBufferPool.NON_POOLING : bufferPool;
_direct = direct;
}

View File

@ -53,7 +53,7 @@ public class ByteBufferAggregator
throw new IllegalArgumentException("startSize must be > 0, was: " + startSize);
if (startSize > maxSize)
throw new IllegalArgumentException("maxSize (" + maxSize + ") must be >= startSize (" + startSize + ")");
_bufferPool = (bufferPool == null) ? new ByteBufferPool.NonPooling() : bufferPool;
_bufferPool = (bufferPool == null) ? ByteBufferPool.NON_POOLING : bufferPool;
_direct = direct;
_maxSize = maxSize;
_currentSize = startSize;

View File

@ -36,7 +36,7 @@ public class ByteBufferOutputStream2 extends OutputStream
public ByteBufferOutputStream2(ByteBufferPool bufferPool, boolean direct)
{
_accumulator = new ByteBufferAccumulator(bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool, direct);
_accumulator = new ByteBufferAccumulator(bufferPool == null ? ByteBufferPool.NON_POOLING : bufferPool, direct);
}
/**

View File

@ -45,6 +45,8 @@ import org.eclipse.jetty.util.BufferUtil;
*/
public interface ByteBufferPool
{
ByteBufferPool NON_POOLING = new NonPooling();
/**
* <p>Acquires a {@link RetainableByteBuffer} from this pool.</p>
*

View File

@ -62,7 +62,7 @@ public class IOResources
throw new IllegalArgumentException("Resource length exceeds 2 GiB: " + resource);
int length = (int)longLength;
bufferPool = bufferPool == null ? new ByteBufferPool.NonPooling() : bufferPool;
bufferPool = bufferPool == null ? ByteBufferPool.NON_POOLING : bufferPool;
// Optimize for PathResource.
Path path = resource.getPath();
@ -371,7 +371,7 @@ public class IOResources
if (first > -1)
channel.position(first);
this.sink = sink;
this.pool = pool == null ? new ByteBufferPool.NonPooling() : pool;
this.pool = pool == null ? ByteBufferPool.NON_POOLING : pool;
this.bufferSize = bufferSize <= 0 ? 4096 : bufferSize;
this.direct = direct;
this.remainingLength = length;

View File

@ -64,7 +64,7 @@ public class BufferedContentSink implements Content.Sink
if (maxBufferSize < maxAggregationSize)
throw new IllegalArgumentException("maxBufferSize (" + maxBufferSize + ") must be >= maxAggregationSize (" + maxAggregationSize + ")");
_delegate = delegate;
_bufferPool = (bufferPool == null) ? new ByteBufferPool.NonPooling() : bufferPool;
_bufferPool = (bufferPool == null) ? ByteBufferPool.NON_POOLING : bufferPool;
_direct = direct;
_maxBufferSize = maxBufferSize;
_maxAggregationSize = maxAggregationSize;

View File

@ -54,7 +54,7 @@ public class InputStreamContentSource implements Content.Source
public InputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool)
{
this.inputStream = Objects.requireNonNull(inputStream);
this.bufferPool = bufferPool != null ? bufferPool : new ByteBufferPool.NonPooling();
this.bufferPool = bufferPool != null ? bufferPool : ByteBufferPool.NON_POOLING;
}
public int getBufferSize()

View File

@ -63,7 +63,7 @@ public class PathContentSource implements Content.Source
throw new AccessDeniedException(path.toString());
this.path = path;
this.length = Files.size(path);
this.byteBufferPool = byteBufferPool != null ? byteBufferPool : new ByteBufferPool.NonPooling();
this.byteBufferPool = byteBufferPool != null ? byteBufferPool : ByteBufferPool.NON_POOLING;
}
catch (IOException x)
{

View File

@ -25,7 +25,7 @@ etc/jetty-test-keystore.xml
[ini]
bouncycastle.version?=@bouncycastle.version@
jetty.webapp.addServerClasses+=,${jetty.base.uri}/lib/bouncycastle/
jetty.webapp.addHiddenClasses+=,${jetty.base.uri}/lib/bouncycastle/
jetty.sslContext.keyStorePath?=etc/test-keystore.p12
jetty.sslContext.keyStoreType?=PKCS12
jetty.sslContext.keyStorePassword?=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4

View File

@ -51,31 +51,29 @@
<profiles>
<profile>
<id>jdk17</id>
<id>enable-foreign</id>
<activation>
<jdk>17</jdk>
<jdk>[22,)</jdk>
</activation>
<!--
This profile makes sure the Foreign binding is used for tests when running exactly on JDK 17.
Older and newer JDKs will revert to the JNA binding.
This profile makes sure the Foreign binding is used for tests when running on JDK 22+.
Older JDKs will revert to the JNA binding.
-->
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>jetty-quic-quiche-foreign-incubator</artifactId>
<artifactId>jetty-quic-quiche-foreign</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Make sure to use the Foreign binding by adding and opening the jdk.incubator.foreign module. -->
<!-- Make sure to use the Foreign binding by allowing native access to the foreign module. -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine}
${jetty.surefire.argLine}
--add-modules=jdk.incubator.foreign
--add-opens jdk.incubator.foreign/jdk.incubator.foreign=ALL-UNNAMED
--enable-native-access ALL-UNNAMED</argLine>
--enable-native-access org.eclipse.jetty.quic.quiche.foreign</argLine>
</configuration>
</plugin>
</plugins>

View File

@ -22,6 +22,7 @@ import java.util.stream.IntStream;
import org.eclipse.jetty.io.AbstractEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.FillInterest;
import org.eclipse.jetty.io.WriteFlusher;
import org.eclipse.jetty.util.BufferUtil;
@ -249,7 +250,19 @@ public class QuicStreamEndPoint extends AbstractEndPoint
if (LOG.isDebugEnabled())
LOG.debug("stream #{} is readable, processing: {}", streamId, interested);
if (interested)
{
getFillInterest().fillable();
}
else
{
QuicStreamEndPoint streamEndPoint = getQuicSession().getStreamEndPoint(streamId);
if (streamEndPoint.isStreamFinished())
{
EofException e = new EofException();
streamEndPoint.getFillInterest().onFail(e);
streamEndPoint.getQuicSession().onFailure(e);
}
}
return interested;
}

View File

@ -34,6 +34,8 @@ public abstract class QuicheConnection
static
{
// This code is safe even if trying to load a QuicheBinding instance throws an error,
// as in that case a warning would be logged and the binding ignored.
if (LOG.isDebugEnabled())
{
List<QuicheBinding> bindings = TypeUtil.serviceStream(ServiceLoader.load(QuicheBinding.class))

View File

@ -1,153 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign.incubator;
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 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 Platform PLATFORM;
private static final CLinker LINKER;
private static final ClassLoader CLASSLOADER;
private static final SymbolLookup LIBRARIES;
private static final MethodHandles.Lookup MH_LOOKUP;
static
{
String arch = System.getProperty("os.arch");
if ("x86_64".equals(arch) || "amd64".equals(arch))
arch = "x86-64";
String osName = System.getProperty("os.name");
String prefix;
if (osName.startsWith("Linux"))
{
prefix = "linux-" + arch;
PLATFORM = Platform.LINUX;
}
else if (osName.startsWith("Mac") || osName.startsWith("Darwin"))
{
prefix = "darwin-" + arch;
PLATFORM = Platform.MAC;
}
else if (osName.startsWith("Windows"))
{
prefix = "win32-" + arch;
PLATFORM = Platform.WINDOWS;
}
else
throw new UnsatisfiedLinkError("Unsupported OS: " + osName);
LINKER = CLinker.getInstance();
CLASSLOADER = NativeHelper.class.getClassLoader();
LIBRARIES = lookup(prefix);
MH_LOOKUP = MethodHandles.lookup();
}
private static SymbolLookup lookup(String prefix)
{
loadNativeLibraryFromClasspath(prefix);
SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
SymbolLookup systemLookup = CLinker.systemLookup();
return name -> loaderLookup.lookup(name).or(() -> systemLookup.lookup(name));
}
private static void loadNativeLibraryFromClasspath(String prefix) {
try
{
String libName = prefix + "/" + System.mapLibraryName("quiche");
File lib = extractFromResourcePath(libName, NativeHelper.class.getClassLoader());
System.load(lib.getAbsolutePath());
lib.deleteOnExit();
}
catch (Throwable x)
{
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError("cannot find quiche native library").initCause(x);
}
}
private static File extractFromResourcePath(String libName, ClassLoader classLoader) throws IOException
{
File target = new File(System.getProperty("java.io.tmpdir"), libName);
target.getParentFile().mkdirs();
try (InputStream is = classLoader.getResourceAsStream(libName); OutputStream os = new FileOutputStream(target))
{
IO.copy(is, os);
}
return target;
}
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);
}
}
public static boolean isLinux()
{
return PLATFORM == Platform.LINUX;
}
public static boolean isMac()
{
return PLATFORM == Platform.MAC;
}
public static boolean isWindows()
{
return PLATFORM == Platform.WINDOWS;
}
private enum Platform
{
LINUX, MAC, WINDOWS
}
}

View File

@ -1,57 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign.incubator;
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),
C_POINTER.withName("to"),
C_INT.withName("to_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"));
private static final VarHandle to = MemoryHandles.asAddressVarHandle(LAYOUT.varHandle(long.class, MemoryLayout.PathElement.groupElement("to")));
private static final VarHandle to_len = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.groupElement("to_len"));
public static MemorySegment allocate(ResourceScope scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
}
public static void setSocketAddress(MemorySegment recvInfo, SocketAddress local, SocketAddress peer, ResourceScope scope)
{
MemorySegment peerSockAddrSegment = sockaddr.convert(peer, scope);
from.set(recvInfo, peerSockAddrSegment.address());
from_len.set(recvInfo, (int)peerSockAddrSegment.byteSize());
MemorySegment localSockAddrSegment = sockaddr.convert(local, scope);
to.set(recvInfo, localSockAddrSegment.address());
to_len.set(recvInfo, (int)localSockAddrSegment.byteSize());
}
}

View File

@ -1,52 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign.incubator;
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(
MemoryLayout.structLayout( // struct sockaddr_storage
C_SHORT.withName("ss_family"),
MemoryLayout.sequenceLayout(118, C_CHAR).withName("__ss_padding"),
C_LONG.withName("__ss_align")
).withName("from"),
C_INT.withName("from_len"),
MemoryLayout.paddingLayout(32),
MemoryLayout.structLayout( // struct sockaddr_storage
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);
}
}

View File

@ -1 +0,0 @@
org.eclipse.jetty.quic.quiche.foreign.incubator.ForeignIncubatorQuicheBinding

View File

@ -7,11 +7,11 @@
<artifactId>jetty-quic-quiche</artifactId>
<version>12.0.9-SNAPSHOT</version>
</parent>
<artifactId>jetty-quic-quiche-foreign-incubator</artifactId>
<name>Core :: QUIC :: Quiche :: Foreign (Java 17)</name>
<artifactId>jetty-quic-quiche-foreign</artifactId>
<name>Core :: QUIC :: Quiche :: Foreign</name>
<properties>
<bundle-symbolic-name>${project.groupId}.quic-quiche-foreign-incubator</bundle-symbolic-name>
<bundle-symbolic-name>${project.groupId}.quic-quiche-foreign</bundle-symbolic-name>
<checkstyle.skip>true</checkstyle.skip>
</properties>
@ -38,55 +38,28 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>--add-modules</arg>
<arg>jdk.incubator.foreign</arg>
</compilerArgs>
<release>22</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>17</source>
<release>17</release>
<release>22</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>@{argLine}
${jetty.surefire.argLine}
--add-opens org.eclipse.jetty.quic.quiche.foreign.incubator/org.eclipse.jetty.quic.quiche.foreign.incubator=ALL-UNNAMED
--enable-native-access org.eclipse.jetty.quic.quiche.foreign.incubator</argLine>
--enable-native-access org.eclipse.jetty.quic.quiche.foreign</argLine>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!--
This module can be built with JDK 17+ but can only be tested on JDK 17 as the Foreign module
changed both its API (18) and its name (19), so tests are disabled on JDKs over 17.
Eventually, new Foreign modules for JDKs 18 and 19 should be added.
-->
<id>jdk18</id>
<activation>
<jdk>[18,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -11,13 +11,14 @@
// ========================================================================
//
module org.eclipse.jetty.quic.quiche.foreign.incubator
import org.eclipse.jetty.quic.quiche.foreign.ForeignQuicheBinding;
module org.eclipse.jetty.quic.quiche.foreign
{
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.foreign.incubator.ForeignIncubatorQuicheBinding;
ForeignQuicheBinding;
}

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.io.IOException;
import java.net.InetSocketAddress;
@ -24,9 +24,9 @@ import org.eclipse.jetty.quic.quiche.QuicheConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ForeignIncubatorQuicheBinding implements QuicheBinding
public class ForeignQuicheBinding implements QuicheBinding
{
private static final Logger LOG = LoggerFactory.getLogger(ForeignIncubatorQuicheBinding.class);
private static final Logger LOG = LoggerFactory.getLogger(ForeignQuicheBinding.class);
@Override
public boolean isUsable()
@ -39,7 +39,8 @@ public class ForeignIncubatorQuicheBinding implements QuicheBinding
}
catch (Throwable x)
{
LOG.debug("foreign incubator quiche binding is not usable", x);
if (LOG.isDebugEnabled())
LOG.debug("java.lang.foreign quiche binding is not usable", x);
return false;
}
}
@ -53,25 +54,25 @@ public class ForeignIncubatorQuicheBinding implements QuicheBinding
@Override
public byte[] fromPacket(ByteBuffer packet)
{
return ForeignIncubatorQuicheConnection.fromPacket(packet);
return ForeignQuicheConnection.fromPacket(packet);
}
@Override
public QuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException
{
return ForeignIncubatorQuicheConnection.connect(quicheConfig, local, peer, connectionIdLength);
return ForeignQuicheConnection.connect(quicheConfig, local, peer, connectionIdLength);
}
@Override
public boolean negotiate(QuicheConnection.TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException
{
return ForeignIncubatorQuicheConnection.negotiate(tokenMinter, packetRead, packetToSend);
return ForeignQuicheConnection.negotiate(tokenMinter, packetRead, packetToSend);
}
@Override
public QuicheConnection tryAccept(QuicheConfig quicheConfig, QuicheConnection.TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException
{
return ForeignIncubatorQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, local, peer);
return ForeignQuicheConnection.tryAccept(quicheConfig, tokenValidator, packetRead, local, peer);
}
@Override

View File

@ -11,23 +11,21 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
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.Quiche;
import org.eclipse.jetty.quic.quiche.Quiche.quic_error;
import org.eclipse.jetty.quic.quiche.Quiche.quiche_error;
@ -39,81 +37,80 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MAX_CONN_ID_LEN;
import static org.eclipse.jetty.quic.quiche.foreign.incubator.quiche_h.C_FALSE;
import static org.eclipse.jetty.quic.quiche.foreign.incubator.quiche_h.C_TRUE;
public class ForeignIncubatorQuicheConnection extends QuicheConnection
public class ForeignQuicheConnection extends QuicheConnection
{
private static final Logger LOG = LoggerFactory.getLogger(ForeignIncubatorQuicheConnection.class);
private static final Logger LOG = LoggerFactory.getLogger(ForeignQuicheConnection.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 quicheConn;
private MemorySegment quicheConfig;
private Arena scope;
private MemorySegment sendInfo;
private MemorySegment recvInfo;
private MemorySegment stats;
private MemorySegment transportParams;
private MemorySegment pathStats;
private ForeignIncubatorQuicheConnection(MemoryAddress quicheConn, MemoryAddress quicheConfig, ResourceScope scope)
private ForeignQuicheConnection(MemorySegment quicheConn, MemorySegment quicheConfig, Arena 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);
this.transportParams = quiche_transport_params.allocate(scope);
this.pathStats = quiche_path_stats.allocate(scope);
}
public static byte[] fromPacket(ByteBuffer packet)
{
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
MemorySegment type = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
MemorySegment version = MemorySegment.allocateNative(CLinker.C_INT, scope);
MemorySegment type = scope.allocate(NativeHelper.C_CHAR);
MemorySegment version = scope.allocate(NativeHelper.C_INT);
// Source Connection ID
MemorySegment scid = MemorySegment.allocateNative(QUICHE_MAX_CONN_ID_LEN, scope);
MemorySegment scid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(scid_len, scid.byteSize());
MemorySegment scid = scope.allocate(QUICHE_MAX_CONN_ID_LEN);
MemorySegment scid_len = scope.allocate(NativeHelper.C_LONG);
scid_len.set(NativeHelper.C_LONG, 0L, scid.byteSize());
// Destination Connection ID
MemorySegment dcid = MemorySegment.allocateNative(QUICHE_MAX_CONN_ID_LEN, scope);
MemorySegment dcid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(dcid_len, dcid.byteSize());
MemorySegment dcid = scope.allocate(QUICHE_MAX_CONN_ID_LEN);
MemorySegment dcid_len = scope.allocate(NativeHelper.C_LONG);
dcid_len.set(NativeHelper.C_LONG, 0L, dcid.byteSize());
MemorySegment token = MemorySegment.allocateNative(QuicheConnection.TokenMinter.MAX_TOKEN_LENGTH, scope);
MemorySegment token_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(token_len, token.byteSize());
MemorySegment token = scope.allocate(QuicheConnection.TokenMinter.MAX_TOKEN_LENGTH);
MemorySegment token_len = scope.allocate(NativeHelper.C_LONG);
token_len.set(NativeHelper.C_LONG, 0L, token.byteSize());
LOG.debug("getting header info (fromPacket)...");
if (LOG.isDebugEnabled())
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_MAX_CONN_ID_LEN,
version.address(), type.address(),
scid.address(), scid_len.address(),
dcid.address(), dcid_len.address(),
token.address(), token_len.address());
MemorySegment packetReadSegment = MemorySegment.ofBuffer(packet);
rc = quiche_h.quiche_header_info(packetReadSegment, packet.remaining(), QUICHE_MAX_CONN_ID_LEN,
version, type,
scid, scid_len,
dcid, dcid_len,
token, token_len);
}
else
{
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
MemorySegment packetReadSegment = MemorySegment.allocateNative(packet.remaining(), scope);
MemorySegment packetReadSegment = scope.allocate(packet.remaining());
int prevPosition = packet.position();
packetReadSegment.asByteBuffer().put(packet);
packet.position(prevPosition);
rc = quiche_h.quiche_header_info(packetReadSegment.address(), packet.remaining(), QUICHE_MAX_CONN_ID_LEN,
version.address(), type.address(),
scid.address(), scid_len.address(),
dcid.address(), dcid_len.address(),
token.address(), token_len.address());
rc = quiche_h.quiche_header_info(packetReadSegment, packet.remaining(), QUICHE_MAX_CONN_ID_LEN,
version, type,
scid, scid_len,
dcid, dcid_len,
token, token_len);
}
if (rc < 0)
{
@ -122,60 +119,62 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
return null;
}
byte[] bytes = new byte[(int)getLong(dcid_len)];
byte[] bytes = new byte[(int)dcid_len.get(NativeHelper.C_LONG, 0L)];
dcid.asByteBuffer().get(bytes);
return bytes;
}
}
public static ForeignIncubatorQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer) throws IOException
public static ForeignQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer) throws IOException
{
return connect(quicheConfig, local, peer, QUICHE_MAX_CONN_ID_LEN);
}
public static ForeignIncubatorQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException
public static ForeignQuicheConnection connect(QuicheConfig quicheConfig, InetSocketAddress local, InetSocketAddress peer, int connectionIdLength) throws IOException
{
if (connectionIdLength > QUICHE_MAX_CONN_ID_LEN)
throw new IOException("Connection ID length is too large: " + connectionIdLength + " > " + QUICHE_MAX_CONN_ID_LEN);
ResourceScope scope = ResourceScope.newSharedScope();
Arena arena = Arena.ofShared();
boolean keepScope = false;
try
{
byte[] scidBytes = new byte[connectionIdLength];
SECURE_RANDOM.nextBytes(scidBytes);
MemorySegment scid = MemorySegment.allocateNative(scidBytes.length, scope);
MemorySegment scid = arena.allocate(scidBytes.length);
scid.asByteBuffer().put(scidBytes);
MemoryAddress libQuicheConfig = buildConfig(quicheConfig, scope);
MemorySegment libQuicheConfig = buildConfig(quicheConfig, arena);
MemorySegment localSockaddr = sockaddr.convert(local, scope);
MemorySegment peerSockaddr = sockaddr.convert(peer, scope);
MemoryAddress quicheConn = quiche_h.quiche_connect(CLinker.toCString(peer.getHostString(), scope), scid, scid.byteSize(), localSockaddr, localSockaddr.byteSize(), peerSockaddr, peerSockaddr.byteSize(), libQuicheConfig);
ForeignIncubatorQuicheConnection connection = new ForeignIncubatorQuicheConnection(quicheConn, libQuicheConfig, scope);
MemorySegment localSockaddr = sockaddr.convert(local, arena);
MemorySegment peerSockaddr = sockaddr.convert(peer, arena);
MemorySegment quicheConn = quiche_h.quiche_connect(arena.allocateFrom(peer.getHostString()), scid, scid.byteSize(), localSockaddr, (int)localSockaddr.byteSize(), peerSockaddr, (int)peerSockaddr.byteSize(), libQuicheConfig);
ForeignQuicheConnection connection = new ForeignQuicheConnection(quicheConn, libQuicheConfig, arena);
keepScope = true;
return connection;
}
finally
{
if (!keepScope)
scope.close();
arena.close();
}
}
private static MemoryAddress buildConfig(QuicheConfig config, ResourceScope scope) throws IOException
private static MemorySegment buildConfig(QuicheConfig config, SegmentAllocator allocator) throws IOException
{
MemoryAddress quicheConfig = quiche_h.quiche_config_new(config.getVersion());
MemorySegment 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);
quiche_h.quiche_config_verify_peer(quicheConfig, verifyPeer);
String trustedCertsPemPath = config.getTrustedCertsPemPath();
if (trustedCertsPemPath != null)
{
int rc = quiche_h.quiche_config_load_verify_locations_from_file(quicheConfig, CLinker.toCString(trustedCertsPemPath, scope).address());
int rc = quiche_h.quiche_config_load_verify_locations_from_file(quicheConfig, allocator.allocateFrom(trustedCertsPemPath));
if (rc < 0)
throw new IOException("Error loading trusted certificates file " + trustedCertsPemPath + " : " + Quiche.quiche_error.errToString(rc));
}
@ -183,7 +182,8 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
String certChainPemPath = config.getCertChainPemPath();
if (certChainPemPath != null)
{
int rc = quiche_h.quiche_config_load_cert_chain_from_pem_file(quicheConfig, CLinker.toCString(certChainPemPath, scope).address());
int rc = quiche_h.quiche_config_load_cert_chain_from_pem_file(quicheConfig, allocator.allocateFrom(certChainPemPath));
if (rc < 0)
throw new IOException("Error loading certificate chain file " + certChainPemPath + " : " + Quiche.quiche_error.errToString(rc));
}
@ -191,7 +191,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
String privKeyPemPath = config.getPrivKeyPemPath();
if (privKeyPemPath != null)
{
int rc = quiche_h.quiche_config_load_priv_key_from_pem_file(quicheConfig, CLinker.toCString(privKeyPemPath, scope).address());
int rc = quiche_h.quiche_config_load_priv_key_from_pem_file(quicheConfig, allocator.allocateFrom(privKeyPemPath));
if (rc < 0)
throw new IOException("Error loading private key file " + privKeyPemPath + " : " + Quiche.quiche_error.errToString(rc));
}
@ -207,9 +207,9 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
baos.write(bytes);
}
byte[] bytes = baos.toByteArray();
MemorySegment segment = MemorySegment.allocateNative(bytes.length, scope);
MemorySegment segment = allocator.allocate(bytes.length);
segment.asByteBuffer().put(bytes);
quiche_h.quiche_config_set_application_protos(quicheConfig, segment.address(), segment.byteSize());
quiche_h.quiche_config_set_application_protos(quicheConfig, segment, segment.byteSize());
}
QuicheConfig.CongestionControl cc = config.getCongestionControl();
@ -246,7 +246,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
Boolean disableActiveMigration = config.getDisableActiveMigration();
if (disableActiveMigration != null)
quiche_h.quiche_config_set_disable_active_migration(quicheConfig, disableActiveMigration ? C_TRUE : C_FALSE);
quiche_h.quiche_config_set_disable_active_migration(quicheConfig, disableActiveMigration);
Long maxConnectionWindow = config.getMaxConnectionWindow();
if (maxConnectionWindow != null)
@ -265,73 +265,78 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
public static boolean negotiate(TokenMinter tokenMinter, ByteBuffer packetRead, ByteBuffer packetToSend) throws IOException
{
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
MemorySegment packetReadSegment;
if (packetRead.isDirect())
{
// If the ByteBuffer is direct, it can be used without any copy.
packetReadSegment = MemorySegment.ofByteBuffer(packetRead);
packetReadSegment = MemorySegment.ofBuffer(packetRead);
}
else
{
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
packetReadSegment = MemorySegment.allocateNative(packetRead.remaining(), scope);
packetReadSegment = scope.allocate(packetRead.remaining());
int prevPosition = packetRead.position();
packetReadSegment.asByteBuffer().put(packetRead);
packetRead.position(prevPosition);
}
MemorySegment type = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
MemorySegment version = MemorySegment.allocateNative(CLinker.C_INT, scope);
MemorySegment type = scope.allocate(NativeHelper.C_CHAR);
MemorySegment version = scope.allocate(NativeHelper.C_INT);
// Source Connection ID
MemorySegment scid = MemorySegment.allocateNative(QUICHE_MAX_CONN_ID_LEN, scope);
MemorySegment scid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(scid_len, scid.byteSize());
MemorySegment scid = scope.allocate(QUICHE_MAX_CONN_ID_LEN);
MemorySegment scid_len = scope.allocate(NativeHelper.C_LONG);
scid_len.set(NativeHelper.C_LONG, 0L, scid.byteSize());
// Destination Connection ID
MemorySegment dcid = MemorySegment.allocateNative(QUICHE_MAX_CONN_ID_LEN, scope);
MemorySegment dcid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(dcid_len, dcid.byteSize());
MemorySegment dcid = scope.allocate(QUICHE_MAX_CONN_ID_LEN);
MemorySegment dcid_len = scope.allocate(NativeHelper.C_LONG);
dcid_len.set(NativeHelper.C_LONG, 0L, dcid.byteSize());
MemorySegment token = MemorySegment.allocateNative(TokenMinter.MAX_TOKEN_LENGTH, scope);
MemorySegment token_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(token_len, token.byteSize());
MemorySegment token = scope.allocate(TokenMinter.MAX_TOKEN_LENGTH);
MemorySegment token_len = scope.allocate(NativeHelper.C_LONG);
token_len.set(NativeHelper.C_LONG, 0L, token.byteSize());
LOG.debug("getting header info (negotiate)...");
int rc = quiche_h.quiche_header_info(packetReadSegment.address(), packetRead.remaining(), 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 (LOG.isDebugEnabled())
LOG.debug("getting header info (negotiate)...");
int rc = quiche_h.quiche_header_info(packetReadSegment, packetRead.remaining(), QUICHE_MAX_CONN_ID_LEN,
version, type,
scid, scid_len,
dcid, dcid_len,
token, token_len);
if (rc < 0)
throw new IOException("failed to parse header: " + quiche_error.errToString(rc));
packetRead.position(packetRead.limit());
LOG.debug("version: {}", getInt(version));
LOG.debug("type: {}", getByte(type));
LOG.debug("scid len: {}", getLong(scid_len));
LOG.debug("dcid len: {}", getLong(dcid_len));
LOG.debug("token len: {}", getLong(token_len));
if (quiche_h.quiche_version_is_supported(getInt(version)) == C_FALSE)
if (LOG.isDebugEnabled())
{
LOG.debug("version negotiation");
LOG.debug("version: {}", version.get(NativeHelper.C_INT, 0L));
LOG.debug("type: {}", type.get(NativeHelper.C_CHAR, 0L));
LOG.debug("scid len: {}", scid_len.get(NativeHelper.C_LONG, 0L));
LOG.debug("dcid len: {}", dcid_len.get(NativeHelper.C_LONG, 0L));
LOG.debug("token len: {}", token_len.get(NativeHelper.C_LONG, 0L));
}
if (!quiche_h.quiche_version_is_supported(version.get(NativeHelper.C_INT, 0L)))
{
if (LOG.isDebugEnabled())
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);
packetToSendSegment = MemorySegment.ofBuffer(packetToSend);
}
else
{
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
packetToSendSegment = MemorySegment.allocateNative(packetToSend.remaining(), scope);
packetToSendSegment = scope.allocate(packetToSend.remaining());
}
long generated = quiche_h.quiche_negotiate_version(scid.address(), getLong(scid_len), dcid.address(), getLong(dcid_len), packetToSendSegment.address(), packetToSend.remaining());
long generated = quiche_h.quiche_negotiate_version(scid, scid_len.get(NativeHelper.C_LONG, 0L), dcid, dcid_len.get(NativeHelper.C_LONG, 0L), packetToSendSegment, packetToSend.remaining());
if (generated < 0)
throw new IOException("failed to create vneg packet : " + quiche_error.errToString(generated));
if (!packetToSend.isDirect())
@ -341,38 +346,39 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
return true;
}
if (getLong(token_len) == 0L)
if (token_len.get(NativeHelper.C_LONG, 0L) == 0L)
{
LOG.debug("stateless retry");
if (LOG.isDebugEnabled())
LOG.debug("stateless retry");
byte[] dcidBytes = new byte[(int)getLong(dcid_len)];
byte[] dcidBytes = new byte[(int)dcid_len.get(NativeHelper.C_LONG, 0L)];
dcid.asByteBuffer().get(dcidBytes);
byte[] tokenBytes = tokenMinter.mint(dcidBytes, dcidBytes.length);
token.asByteBuffer().put(tokenBytes);
byte[] newCid = new byte[QUICHE_MAX_CONN_ID_LEN];
SECURE_RANDOM.nextBytes(newCid);
MemorySegment newCidSegment = MemorySegment.allocateNative(newCid.length, scope);
MemorySegment newCidSegment = scope.allocate(newCid.length);
newCidSegment.asByteBuffer().put(newCid);
MemorySegment packetToSendSegment;
if (packetToSend.isDirect())
{
// If the ByteBuffer is direct, it can be used without any copy.
packetToSendSegment = MemorySegment.ofByteBuffer(packetToSend);
packetToSendSegment = MemorySegment.ofBuffer(packetToSend);
}
else
{
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
packetToSendSegment = MemorySegment.allocateNative(packetToSend.remaining(), scope);
packetToSendSegment = scope.allocate(packetToSend.remaining());
}
long generated = quiche_h.quiche_retry(scid.address(), getLong(scid_len),
dcid.address(), getLong(dcid_len),
newCidSegment.address(), newCid.length,
token.address(), tokenBytes.length,
getInt(version),
packetToSendSegment.address(), packetToSendSegment.byteSize()
long generated = quiche_h.quiche_retry(scid, scid_len.get(NativeHelper.C_LONG, 0L),
dcid, dcid_len.get(NativeHelper.C_LONG, 0L),
newCidSegment, newCid.length,
token, tokenBytes.length,
version.get(NativeHelper.C_INT, 0L),
packetToSendSegment, packetToSendSegment.byteSize()
);
if (generated < 0)
throw new IOException("failed to create retry packet: " + quiche_error.errToString(generated));
@ -387,106 +393,117 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
}
}
public static ForeignIncubatorQuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException
public static ForeignQuicheConnection tryAccept(QuicheConfig quicheConfig, TokenValidator tokenValidator, ByteBuffer packetRead, SocketAddress local, SocketAddress peer) throws IOException
{
boolean keepScope = false;
ResourceScope scope = ResourceScope.newSharedScope();
Arena scope = Arena.ofShared();
try
{
MemorySegment type = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
MemorySegment version = MemorySegment.allocateNative(CLinker.C_INT, scope);
MemorySegment type = scope.allocate(NativeHelper.C_CHAR);
MemorySegment version = scope.allocate(NativeHelper.C_INT);
// Source Connection ID
MemorySegment scid = MemorySegment.allocateNative(QUICHE_MAX_CONN_ID_LEN, scope);
MemorySegment scid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(scid_len, scid.byteSize());
MemorySegment scid = scope.allocate(QUICHE_MAX_CONN_ID_LEN);
MemorySegment scid_len = scope.allocate(NativeHelper.C_LONG);
scid_len.set(NativeHelper.C_LONG, 0L, scid.byteSize());
// Destination Connection ID
MemorySegment dcid = MemorySegment.allocateNative(QUICHE_MAX_CONN_ID_LEN, scope);
MemorySegment dcid_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(dcid_len, dcid.byteSize());
MemorySegment dcid = scope.allocate(QUICHE_MAX_CONN_ID_LEN);
MemorySegment dcid_len = scope.allocate(NativeHelper.C_LONG);
dcid_len.set(NativeHelper.C_LONG, 0L, dcid.byteSize());
MemorySegment token = MemorySegment.allocateNative(TokenMinter.MAX_TOKEN_LENGTH, scope);
MemorySegment token_len = MemorySegment.allocateNative(CLinker.C_LONG, scope);
putLong(token_len, token.byteSize());
MemorySegment token = scope.allocate(TokenMinter.MAX_TOKEN_LENGTH);
MemorySegment token_len = scope.allocate(NativeHelper.C_LONG);
token_len.set(NativeHelper.C_LONG, 0L, token.byteSize());
LOG.debug("getting header info (tryAccept)...");
if (LOG.isDebugEnabled())
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_MAX_CONN_ID_LEN,
version.address(), type.address(),
scid.address(), scid_len.address(),
dcid.address(), dcid_len.address(),
token.address(), token_len.address());
MemorySegment packetReadSegment = MemorySegment.ofBuffer(packetRead);
rc = quiche_h.quiche_header_info(packetReadSegment, packetRead.remaining(), QUICHE_MAX_CONN_ID_LEN,
version, type,
scid, scid_len,
dcid, dcid_len,
token, token_len);
}
else
{
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
try (ResourceScope segmentScope = ResourceScope.newConfinedScope())
try (Arena segmentScope = Arena.ofConfined())
{
MemorySegment packetReadSegment = MemorySegment.allocateNative(packetRead.remaining(), segmentScope);
MemorySegment packetReadSegment = segmentScope.allocate(packetRead.remaining());
int prevPosition = packetRead.position();
packetReadSegment.asByteBuffer().put(packetRead);
packetRead.position(prevPosition);
rc = quiche_h.quiche_header_info(packetReadSegment.address(), packetRead.remaining(), QUICHE_MAX_CONN_ID_LEN,
version.address(), type.address(),
scid.address(), scid_len.address(),
dcid.address(), dcid_len.address(),
token.address(), token_len.address());
rc = quiche_h.quiche_header_info(packetReadSegment, packetRead.remaining(), QUICHE_MAX_CONN_ID_LEN,
version, type,
scid, scid_len,
dcid, dcid_len,
token, token_len);
}
}
if (rc < 0)
throw new IOException("failed to parse header: " + quiche_error.errToString(rc));
LOG.debug("version: {}", getInt(version));
LOG.debug("type: {}", getByte(type));
LOG.debug("scid len: {}", getLong(scid_len));
LOG.debug("dcid len: {}", getLong(dcid_len));
LOG.debug("token len: {}", getLong(token_len));
if (quiche_h.quiche_version_is_supported(getInt(version)) == C_FALSE)
if (LOG.isDebugEnabled())
{
LOG.debug("need version negotiation");
LOG.debug("version: {}", version.get(NativeHelper.C_INT, 0L));
LOG.debug("type: {}", type.get(NativeHelper.C_CHAR, 0L));
LOG.debug("scid len: {}", scid_len.get(NativeHelper.C_LONG, 0L));
LOG.debug("dcid len: {}", dcid_len.get(NativeHelper.C_LONG, 0L));
LOG.debug("token len: {}", token_len.get(NativeHelper.C_LONG, 0L));
}
if (!quiche_h.quiche_version_is_supported(version.get(NativeHelper.C_INT, 0L)))
{
if (LOG.isDebugEnabled())
LOG.debug("need version negotiation");
return null;
}
int tokenLen = (int)getLong(token_len);
int tokenLen = (int)token_len.get(NativeHelper.C_LONG, 0L);
if (tokenLen == 0)
{
LOG.debug("need stateless retry");
if (LOG.isDebugEnabled())
LOG.debug("need stateless retry");
return null;
}
LOG.debug("token validation...");
if (LOG.isDebugEnabled())
LOG.debug("token validation...");
// Original Destination Connection ID
byte[] tokenBytes = new byte[(int)token.byteSize()];
token.asByteBuffer().get(tokenBytes, 0, tokenLen);
byte[] odcidBytes = tokenValidator.validate(tokenBytes, tokenLen);
if (odcidBytes == null)
throw new TokenValidationException("invalid address validation token");
LOG.debug("validated token");
MemorySegment odcid = MemorySegment.allocateNative(odcidBytes.length, scope);
if (LOG.isDebugEnabled())
LOG.debug("validated token");
MemorySegment odcid = scope.allocate(odcidBytes.length);
odcid.asByteBuffer().put(odcidBytes);
LOG.debug("connection creation...");
MemoryAddress libQuicheConfig = buildConfig(quicheConfig, scope);
if (LOG.isDebugEnabled())
LOG.debug("connection creation...");
MemorySegment libQuicheConfig = buildConfig(quicheConfig, scope);
MemorySegment localSockaddr = sockaddr.convert(local, scope);
MemorySegment peerSockaddr = sockaddr.convert(peer, scope);
MemoryAddress quicheConn = quiche_h.quiche_accept(dcid.address(), getLong(dcid_len), odcid.address(), odcid.byteSize(), localSockaddr.address(), localSockaddr.byteSize(), peerSockaddr.address(), peerSockaddr.byteSize(), libQuicheConfig);
MemorySegment quicheConn = quiche_h.quiche_accept(dcid, dcid_len.get(NativeHelper.C_LONG, 0L), odcid, odcid.byteSize(), localSockaddr, (int)localSockaddr.byteSize(), peerSockaddr, (int)peerSockaddr.byteSize(), libQuicheConfig);
if (quicheConn == null)
{
quiche_h.quiche_config_free(libQuicheConfig);
throw new IOException("failed to create connection");
}
LOG.debug("connection created");
ForeignIncubatorQuicheConnection quicheConnection = new ForeignIncubatorQuicheConnection(quicheConn, libQuicheConfig, scope);
LOG.debug("accepted, immediately receiving the same packet - remaining in buffer: {}", packetRead.remaining());
if (LOG.isDebugEnabled())
LOG.debug("connection created");
ForeignQuicheConnection quicheConnection = new ForeignQuicheConnection(quicheConn, libQuicheConfig, scope);
if (LOG.isDebugEnabled())
LOG.debug("accepted, immediately receiving the same packet - remaining in buffer: {}", packetRead.remaining());
while (packetRead.hasRemaining())
{
quicheConnection.feedCipherBytes(packetRead, local, peer);
@ -508,19 +525,18 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
if (quicheConn == null)
throw new IllegalStateException("connection was released");
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
MemorySegment outSegment = MemorySegment.allocateNative(CLinker.C_POINTER, scope);
MemorySegment outLenSegment = MemorySegment.allocateNative(CLinker.C_LONG, scope);
quiche_h.quiche_conn_peer_cert(quicheConn, outSegment.address(), outLenSegment.address());
MemorySegment outSegment = scope.allocate(NativeHelper.C_POINTER);
MemorySegment outLenSegment = scope.allocate(NativeHelper.C_LONG);
quiche_h.quiche_conn_peer_cert(quicheConn, outSegment, outLenSegment);
long outLen = getLong(outLenSegment);
long outLen = outLenSegment.get(NativeHelper.C_LONG, 0L);
if (outLen == 0L)
return null;
byte[] out = new byte[(int)outLen];
// dereference outSegment pointer
MemoryAddress memoryAddress = MemoryAddress.ofLong(getLong(outSegment));
memoryAddress.asSegment(outLen, ResourceScope.globalScope()).asByteBuffer().get(out);
outSegment.get(NativeHelper.C_POINTER, 0L).reinterpret(outLen).asByteBuffer().get(out);
return out;
}
}
@ -534,19 +550,19 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
if (quicheConn == null)
throw new IllegalStateException("connection was released");
MemoryAddress quiche_stream_iter;
MemorySegment 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())
try (Arena scope = Arena.ofConfined())
{
MemorySegment streamIdSegment = MemorySegment.allocateNative(CLinker.C_LONG, scope);
while (quiche_h.quiche_stream_iter_next(quiche_stream_iter, streamIdSegment.address()) != C_FALSE)
MemorySegment streamIdSegment = scope.allocate(NativeHelper.C_LONG);
while (quiche_h.quiche_stream_iter_next(quiche_stream_iter, streamIdSegment))
{
long streamId = getLong(streamIdSegment);
long streamId = streamIdSegment.get(NativeHelper.C_LONG, 0L);
result.add(streamId);
}
}
@ -565,23 +581,23 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
throw new IOException("Cannot receive when not connected");
long received;
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
quiche_recv_info.setSocketAddress(recvInfo, local, 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());
MemorySegment bufferSegment = MemorySegment.ofBuffer(buffer);
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment, buffer.remaining(), recvInfo);
}
else
{
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
MemorySegment bufferSegment = scope.allocate(buffer.remaining());
int prevPosition = buffer.position();
bufferSegment.asByteBuffer().put(buffer);
buffer.position(prevPosition);
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment.address(), buffer.remaining(), recvInfo.address());
received = quiche_h.quiche_conn_recv(quicheConn, bufferSegment, buffer.remaining(), recvInfo);
}
}
// If quiche_conn_recv() fails, quiche_conn_local_error() can be called to get the standard error.
@ -607,16 +623,16 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
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());
MemorySegment bufferSegment = MemorySegment.ofBuffer(buffer);
written = quiche_h.quiche_conn_send(quicheConn, bufferSegment, buffer.remaining(), sendInfo);
}
else
{
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
written = quiche_h.quiche_conn_send(quicheConn, bufferSegment.address(), buffer.remaining(), sendInfo.address());
MemorySegment bufferSegment = scope.allocate(buffer.remaining());
written = quiche_h.quiche_conn_send(quicheConn, bufferSegment, buffer.remaining(), sendInfo);
buffer.put(bufferSegment.asByteBuffer().slice().limit((int)written));
buffer.position(prevPosition);
}
@ -638,7 +654,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
return quiche_h.quiche_conn_is_closed(quicheConn) != C_FALSE;
return quiche_h.quiche_conn_is_closed(quicheConn) != false;
}
}
@ -649,7 +665,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
return quiche_h.quiche_conn_is_established(quicheConn) != C_FALSE;
return quiche_h.quiche_conn_is_established(quicheConn) != false;
}
}
@ -678,23 +694,21 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
@Override
public String getNegotiatedProtocol()
{
try (AutoLock ignore = lock.lock(); ResourceScope scope = ResourceScope.newConfinedScope())
try (AutoLock ignore = lock.lock(); Arena scope = Arena.ofConfined())
{
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());
MemorySegment outSegment = scope.allocate(NativeHelper.C_POINTER);
MemorySegment outLenSegment = scope.allocate(NativeHelper.C_LONG);
quiche_h.quiche_conn_application_proto(quicheConn, outSegment, outLenSegment);
long outLen = getLong(outLenSegment);
long outLen = outLenSegment.get(NativeHelper.C_LONG, 0L);
if (outLen == 0L)
return null;
byte[] out = new byte[(int)outLen];
// dereference outSegment pointer
MemoryAddress memoryAddress = MemoryAddress.ofLong(getLong(outSegment));
memoryAddress.asSegment(outLen, ResourceScope.globalScope()).asByteBuffer().get(out);
outSegment.get(NativeHelper.C_POINTER, 0L).reinterpret(outLen).asByteBuffer().get(out);
return new String(out, StandardCharsets.UTF_8);
}
}
@ -714,18 +728,17 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
int rc;
if (reason == null)
{
rc = quiche_h.quiche_conn_close(quicheConn, C_TRUE, error, MemoryAddress.NULL, 0);
rc = quiche_h.quiche_conn_close(quicheConn, true, error, MemorySegment.NULL, 0);
}
else
{
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8);
MemorySegment reasonSegment = MemorySegment.allocateNative(reasonBytes.length, scope);
MemorySegment reasonSegment = scope.allocate(reasonBytes.length);
reasonSegment.asByteBuffer().put(reasonBytes);
int length = reasonBytes.length;
MemoryAddress reasonAddress = reasonSegment.address();
rc = quiche_h.quiche_conn_close(quicheConn, C_TRUE, error, reasonAddress, length);
rc = quiche_h.quiche_conn_close(quicheConn, true, error, reasonSegment, length);
}
}
@ -757,7 +770,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
scope = null;
sendInfo = null;
recvInfo = null;
stats = null;
transportParams = null;
pathStats = null;
}
}
@ -769,7 +782,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
return quiche_h.quiche_conn_is_draining(quicheConn) != C_FALSE;
return quiche_h.quiche_conn_is_draining(quicheConn);
}
}
@ -780,8 +793,8 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
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);
quiche_h.quiche_conn_peer_transport_params(quicheConn, transportParams);
return (int)quiche_transport_params.get_peer_initial_max_streams_bidi(transportParams);
}
}
@ -792,7 +805,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
quiche_h.quiche_conn_path_stats(quicheConn, 0L, pathStats.address());
quiche_h.quiche_conn_path_stats(quicheConn, 0L, pathStats);
return quiche_path_stats.get_cwnd(pathStats);
}
}
@ -841,25 +854,25 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
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);
MemorySegment bufferSegment = MemorySegment.ofBuffer(buffer);
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, bufferSegment, buffer.remaining(), last);
}
else
{
// If the ByteBuffer is heap-allocated, it must be copied to native memory.
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
if (buffer.remaining() == 0)
{
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, MemoryAddress.NULL, 0, last ? C_TRUE : C_FALSE);
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, MemorySegment.NULL, 0, last);
}
else
{
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
MemorySegment bufferSegment = scope.allocate(buffer.remaining());
int prevPosition = buffer.position();
bufferSegment.asByteBuffer().put(buffer);
buffer.position(prevPosition);
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, bufferSegment.address(), buffer.remaining(), last ? C_TRUE : C_FALSE);
written = quiche_h.quiche_conn_stream_send(quicheConn, streamId, bufferSegment, buffer.remaining(), last);
}
}
}
@ -887,22 +900,22 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
throw new IOException("connection was released");
long read;
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
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());
MemorySegment bufferSegment = MemorySegment.ofBuffer(buffer);
MemorySegment fin = scope.allocate(NativeHelper.C_CHAR);
read = quiche_h.quiche_conn_stream_recv(quicheConn, streamId, bufferSegment, buffer.remaining(), fin);
}
else
{
// If the ByteBuffer is heap-allocated, native memory must be copied to it.
MemorySegment bufferSegment = MemorySegment.allocateNative(buffer.remaining(), scope);
MemorySegment bufferSegment = scope.allocate(buffer.remaining());
MemorySegment fin = MemorySegment.allocateNative(CLinker.C_CHAR, scope);
read = quiche_h.quiche_conn_stream_recv(quicheConn, streamId, bufferSegment.address(), buffer.remaining(), fin.address());
MemorySegment fin = scope.allocate(NativeHelper.C_CHAR);
read = quiche_h.quiche_conn_stream_recv(quicheConn, streamId, bufferSegment, buffer.remaining(), fin);
int prevPosition = buffer.position();
buffer.put(bufferSegment.asByteBuffer().limit((int)read));
@ -926,7 +939,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
return quiche_h.quiche_conn_stream_finished(quicheConn, streamId) != C_FALSE;
return quiche_h.quiche_conn_stream_finished(quicheConn, streamId);
}
}
@ -937,16 +950,16 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
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)
MemorySegment app = scope.allocate(NativeHelper.C_CHAR);
MemorySegment error = scope.allocate(NativeHelper.C_LONG);
MemorySegment reason = scope.allocate(NativeHelper.C_POINTER);
MemorySegment reasonLength = scope.allocate(NativeHelper.C_LONG);
if (quiche_h.quiche_conn_peer_error(quicheConn, app, error, reason, reasonLength))
{
long errorValue = getLong(error);
long reasonLengthValue = getLong(reasonLength);
long errorValue = error.get(NativeHelper.C_LONG, 0L);
long reasonLengthValue = reasonLength.get(NativeHelper.C_LONG, 0L);
String reasonValue;
if (reasonLengthValue == 0L)
@ -957,8 +970,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
byte[] reasonBytes = new byte[(int)reasonLengthValue];
// dereference reason pointer
MemoryAddress memoryAddress = MemoryAddress.ofLong(getLong(reason));
memoryAddress.asSegment(reasonLengthValue, ResourceScope.globalScope()).asByteBuffer().get(reasonBytes);
reason.get(NativeHelper.C_POINTER, 0L).reinterpret(reasonLengthValue).asByteBuffer().get(reasonBytes);
reasonValue = new String(reasonBytes, StandardCharsets.UTF_8);
}
@ -976,16 +988,16 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
try (ResourceScope scope = ResourceScope.newConfinedScope())
try (Arena scope = Arena.ofConfined())
{
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_local_error(quicheConn, app.address(), error.address(), reason.address(), reasonLength.address()) != C_FALSE)
MemorySegment app = scope.allocate(NativeHelper.C_CHAR);
MemorySegment error = scope.allocate(NativeHelper.C_LONG);
MemorySegment reason = scope.allocate(NativeHelper.C_POINTER);
MemorySegment reasonLength = scope.allocate(NativeHelper.C_LONG);
if (quiche_h.quiche_conn_local_error(quicheConn, app, error, reason, reasonLength))
{
long errorValue = getLong(error);
long reasonLengthValue = getLong(reasonLength);
long errorValue = error.get(NativeHelper.C_LONG, 0L);
long reasonLengthValue = reasonLength.get(NativeHelper.C_LONG, 0L);
String reasonValue;
if (reasonLengthValue == 0L)
@ -996,8 +1008,7 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
{
byte[] reasonBytes = new byte[(int)reasonLengthValue];
// dereference reason pointer
MemoryAddress memoryAddress = MemoryAddress.ofLong(getLong(reason));
memoryAddress.asSegment(reasonLengthValue, ResourceScope.globalScope()).asByteBuffer().get(reasonBytes);
reason.get(NativeHelper.C_POINTER, 0L).reinterpret(reasonLengthValue).asByteBuffer().get(reasonBytes);
reasonValue = new String(reasonBytes, StandardCharsets.UTF_8);
}
@ -1007,24 +1018,4 @@ public class ForeignIncubatorQuicheConnection extends QuicheConnection
}
}
}
private static void putLong(MemorySegment memorySegment, long value)
{
memorySegment.asByteBuffer().order(ByteOrder.nativeOrder()).putLong(value);
}
private static long getLong(MemorySegment memorySegment)
{
return memorySegment.asByteBuffer().order(ByteOrder.nativeOrder()).getLong();
}
private static int getInt(MemorySegment memorySegment)
{
return memorySegment.asByteBuffer().order(ByteOrder.nativeOrder()).getInt();
}
private static byte getByte(MemorySegment memorySegment)
{
return memorySegment.asByteBuffer().get();
}
}

View File

@ -0,0 +1,148 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.nio.file.Files;
import java.nio.file.Path;
import org.eclipse.jetty.util.IO;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
public class NativeHelper
{
public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN;
public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE;
public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT;
public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG;
public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT;
public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE;
public static final AddressLayout C_POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE));
public static final ValueLayout.OfLong C_LONG = ValueLayout.JAVA_LONG;
private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup().or(Linker.nativeLinker().defaultLookup());
private static final Platform PLATFORM;
static
{
String arch = System.getProperty("os.arch");
if ("x86_64".equals(arch) || "amd64".equals(arch))
arch = "x86-64";
String osName = System.getProperty("os.name");
String prefix;
if (osName.startsWith("Linux"))
{
prefix = "linux-" + arch;
PLATFORM = Platform.LINUX;
}
else if (osName.startsWith("Mac") || osName.startsWith("Darwin"))
{
prefix = "darwin-" + arch;
PLATFORM = Platform.MAC;
}
else if (osName.startsWith("Windows"))
{
prefix = "win32-" + arch;
PLATFORM = Platform.WINDOWS;
}
else
{
throw new UnsatisfiedLinkError("Unsupported OS: " + osName);
}
loadNativeLibraryFromClasspath(prefix);
}
private static void loadNativeLibraryFromClasspath(String prefix)
{
try
{
String libName = prefix + "/" + System.mapLibraryName("quiche");
Path lib = extractFromResourcePath(libName, NativeHelper.class.getClassLoader());
System.load(lib.toAbsolutePath().toString());
lib.toFile().deleteOnExit();
}
catch (Throwable x)
{
throw (UnsatisfiedLinkError)new UnsatisfiedLinkError("Cannot find quiche native library for architecture " + prefix).initCause(x);
}
}
private static Path extractFromResourcePath(String libName, ClassLoader classLoader) throws IOException
{
Path target = Path.of(System.getProperty("java.io.tmpdir")).resolve(libName);
Files.createDirectories(target.getParent());
try (InputStream is = classLoader.getResourceAsStream(libName);
OutputStream os = Files.newOutputStream(target))
{
IO.copy(is, os);
}
return target;
}
public static MethodHandle downcallHandle(String symbol, FunctionDescriptor fdesc)
{
return Linker.nativeLinker().downcallHandle(
SYMBOL_LOOKUP.find(symbol).orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol)),
fdesc);
}
public static <T> MemorySegment upcallMemorySegment(Class<T> clazz, String methodName, T instance, FunctionDescriptor fdesc, Arena scope)
{
try
{
MethodHandle handle = MethodHandles.lookup().findVirtual(clazz, methodName, fdesc.toMethodType());
handle = handle.bindTo(instance);
return Linker.nativeLinker().upcallStub(handle, fdesc, scope);
}
catch (ReflectiveOperationException ex)
{
throw new AssertionError(ex);
}
}
public static boolean isLinux()
{
return PLATFORM == Platform.LINUX;
}
public static boolean isMac()
{
return PLATFORM == Platform.MAC;
}
public static boolean isWindows()
{
return PLATFORM == Platform.WINDOWS;
}
private enum Platform
{
LINUX, MAC, WINDOWS
}
}

View File

@ -11,8 +11,11 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator.linux;
package org.eclipse.jetty.quic.quiche.foreign.linux;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.VarHandle;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -22,20 +25,16 @@ import java.net.SocketAddress;
import java.nio.ByteBuffer;
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;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_CHAR;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_INT;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_SHORT;
public class sockaddr_linux
{
private static final short AF_INET = 2;
private static final short AF_INET6 = 10;
public static MemorySegment convert(SocketAddress socketAddress, ResourceScope scope)
public static MemorySegment convert(SocketAddress socketAddress, SegmentAllocator scope)
{
if (!(socketAddress instanceof InetSocketAddress))
throw new IllegalArgumentException("Expected InetSocketAddress instance, got: " + socketAddress);
@ -45,7 +44,7 @@ public class sockaddr_linux
{
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_port(sin, (short)inetSocketAddress.getPort());
sockaddr_in.set_sin_addr(sin, ByteBuffer.wrap(address.getAddress()).getInt());
return sin;
}
@ -53,7 +52,7 @@ public class sockaddr_linux
{
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_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);
@ -74,28 +73,28 @@ public class sockaddr_linux
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"));
private static final VarHandle sin_family = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_family"));
private static final VarHandle sin_port = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_port"));
private static final VarHandle sin_addr = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_addr"));
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
public static void set_sin_family(MemorySegment sin, short value)
{
sin_family.set(sin, value);
sin_family.set(sin, 0L, value);
}
public static void set_sin_port(MemorySegment sin, short value)
{
sin_port.set(sin, value);
sin_port.set(sin, 0L, value);
}
public static void set_sin_addr(MemorySegment sin, int value)
{
sin_addr.set(sin, value);
sin_addr.set(sin, 0L, value);
}
}
@ -109,14 +108,14 @@ public class sockaddr_linux
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"));
private static final VarHandle sin6_family = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_family"));
private static final VarHandle sin6_port = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_port"));
private static final VarHandle sin6_scope_id = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_scope_id"));
private static final VarHandle sin6_flowinfo = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_flowinfo"));
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
public static void set_sin6_addr(MemorySegment sin6, byte[] value)
@ -126,22 +125,22 @@ public class sockaddr_linux
public static void set_sin6_family(MemorySegment sin6, short value)
{
sin6_family.set(sin6, value);
sin6_family.set(sin6, 0L, value);
}
public static void set_sin6_port(MemorySegment sin6, short value)
{
sin6_port.set(sin6, value);
sin6_port.set(sin6, 0L, value);
}
public static void set_sin6_scope_id(MemorySegment sin6, int value)
{
sin6_scope_id.set(sin6, value);
sin6_scope_id.set(sin6, 0L, value);
}
public static void set_sin6_flowinfo(MemorySegment sin6, int value)
{
sin6_flowinfo.set(sin6, value);
sin6_flowinfo.set(sin6, 0L, value);
}
}
}

View File

@ -11,8 +11,11 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator.macos;
package org.eclipse.jetty.quic.quiche.foreign.macos;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.VarHandle;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -22,20 +25,16 @@ import java.net.SocketAddress;
import java.nio.ByteBuffer;
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;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_CHAR;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_INT;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_SHORT;
public class sockaddr_macos
{
private static final byte AF_INET = 2;
private static final byte AF_INET6 = 30;
public static MemorySegment convert(SocketAddress socketAddress, ResourceScope scope)
public static MemorySegment convert(SocketAddress socketAddress, SegmentAllocator scope)
{
if (!(socketAddress instanceof InetSocketAddress))
throw new IllegalArgumentException("Expected InetSocketAddress instance, got: " + socketAddress);
@ -46,7 +45,7 @@ public class sockaddr_macos
MemorySegment sin = sockaddr_in.allocate(scope);
sockaddr_in.set_sin_len(sin, (byte)sin.byteSize());
sockaddr_in.set_sin_family(sin, AF_INET);
sockaddr_in.set_sin_port(sin, (short) inetSocketAddress.getPort());
sockaddr_in.set_sin_port(sin, (short)inetSocketAddress.getPort());
sockaddr_in.set_sin_addr(sin, ByteBuffer.wrap(address.getAddress()).getInt());
return sin;
}
@ -55,7 +54,7 @@ public class sockaddr_macos
MemorySegment sin6 = sockaddr_in6.allocate(scope);
sockaddr_in6.set_sin6_len(sin6, (byte)sin6.byteSize());
sockaddr_in6.set_sin6_family(sin6, AF_INET6);
sockaddr_in6.set_sin6_port(sin6, (short) inetSocketAddress.getPort());
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);
@ -77,34 +76,34 @@ public class sockaddr_macos
MemoryLayout.sequenceLayout(8, C_CHAR).withName("sin_zero")
).withName("sockaddr_in");
private static final VarHandle sin_len = LAYOUT.varHandle(byte.class, MemoryLayout.PathElement.groupElement("sin_len"));
private static final VarHandle sin_family = LAYOUT.varHandle(byte.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"));
private static final VarHandle sin_len = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_len"));
private static final VarHandle sin_family = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_family"));
private static final VarHandle sin_port = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_port"));
private static final VarHandle sin_addr = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_addr"));
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
public static void set_sin_family(MemorySegment sin, byte value)
{
sin_family.set(sin, value);
sin_family.set(sin, 0L, value);
}
public static void set_sin_len(MemorySegment sin, byte value)
{
sin_len.set(sin, value);
sin_len.set(sin, 0L, value);
}
public static void set_sin_port(MemorySegment sin, short value)
{
sin_port.set(sin, value);
sin_port.set(sin, 0L, value);
}
public static void set_sin_addr(MemorySegment sin, int value)
{
sin_addr.set(sin, value);
sin_addr.set(sin, 0L, value);
}
}
@ -119,15 +118,15 @@ public class sockaddr_macos
C_INT.withName("sin6_scope_id")
).withName("sockaddr_in6");
private static final VarHandle sin6_len = LAYOUT.varHandle(byte.class, MemoryLayout.PathElement.groupElement("sin6_len"));
private static final VarHandle sin6_family = LAYOUT.varHandle(byte.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"));
private static final VarHandle sin6_len = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_len"));
private static final VarHandle sin6_family = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_family"));
private static final VarHandle sin6_port = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_port"));
private static final VarHandle sin6_scope_id = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_scope_id"));
private static final VarHandle sin6_flowinfo = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_flowinfo"));
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
public static void set_sin6_addr(MemorySegment sin6, byte[] value)
@ -137,27 +136,27 @@ public class sockaddr_macos
public static void set_sin6_len(MemorySegment sin6, byte value)
{
sin6_len.set(sin6, value);
sin6_len.set(sin6, 0L, value);
}
public static void set_sin6_family(MemorySegment sin6, byte value)
{
sin6_family.set(sin6, value);
sin6_family.set(sin6, 0L, value);
}
public static void set_sin6_port(MemorySegment sin6, short value)
{
sin6_port.set(sin6, value);
sin6_port.set(sin6, 0L, value);
}
public static void set_sin6_scope_id(MemorySegment sin6, int value)
{
sin6_scope_id.set(sin6, value);
sin6_scope_id.set(sin6, 0L, value);
}
public static void set_sin6_flowinfo(MemorySegment sin6, int value)
{
sin6_flowinfo.set(sin6, value);
sin6_flowinfo.set(sin6, 0L, value);
}
}
}

View File

@ -11,39 +11,29 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
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_INT;
import static jdk.incubator.foreign.CLinker.C_LONG;
import static jdk.incubator.foreign.CLinker.C_SHORT;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_BOOL;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_INT;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_LONG;
public class quiche_path_stats
{
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
MemoryLayout.structLayout( // struct sockaddr_storage
C_SHORT.withName("ss_family"),
MemoryLayout.sequenceLayout(118, C_CHAR).withName("__ss_padding"),
C_LONG.withName("__ss_align")
).withName("local_addr"),
sockaddr_storage.layout().withName("local_addr"),
C_INT.withName("local_addr_len"),
MemoryLayout.paddingLayout(32),
MemoryLayout.structLayout( // struct sockaddr_storage
C_SHORT.withName("ss_family"),
MemoryLayout.sequenceLayout(118, C_CHAR).withName("__ss_padding"),
C_LONG.withName("__ss_align")
).withName("peer_addr"),
MemoryLayout.paddingLayout(4),
sockaddr_storage.layout().withName("peer_addr"),
C_INT.withName("peer_addr_len"),
MemoryLayout.paddingLayout(32),
MemoryLayout.paddingLayout(4),
C_LONG.withName("validation_state"),
C_CHAR.withName("active"),
MemoryLayout.paddingLayout(56),
C_BOOL.withName("active"),
MemoryLayout.paddingLayout(7),
C_LONG.withName("recv"),
C_LONG.withName("sent"),
C_LONG.withName("lost"),
@ -58,15 +48,15 @@ public class quiche_path_stats
C_LONG.withName("delivery_rate")
);
private static final VarHandle cwnd = LAYOUT.varHandle(long.class, MemoryLayout.PathElement.groupElement("cwnd"));
private static final VarHandle cwnd = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("cwnd"));
public static long get_cwnd(MemorySegment stats)
{
return (long)cwnd.get(stats);
return (long)cwnd.get(stats, 0L);
}
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
}

View File

@ -0,0 +1,55 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.VarHandle;
import java.net.SocketAddress;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_INT;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.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(4),
C_POINTER.withName("to"),
C_INT.withName("to_len"),
MemoryLayout.paddingLayout(4)
);
private static final VarHandle from = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("from"));
private static final VarHandle from_len = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("from_len"));
private static final VarHandle to = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("to"));
private static final VarHandle to_len = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("to_len"));
public static MemorySegment allocate(SegmentAllocator scope)
{
return scope.allocate(LAYOUT);
}
public static void setSocketAddress(MemorySegment recvInfo, SocketAddress local, SocketAddress peer, SegmentAllocator scope)
{
MemorySegment peerSockAddrSegment = sockaddr.convert(peer, scope);
from.set(recvInfo, 0L, peerSockAddrSegment);
from_len.set(recvInfo, 0L, (int)peerSockAddrSegment.byteSize());
MemorySegment localSockAddrSegment = sockaddr.convert(local, scope);
to.set(recvInfo, 0L, localSockAddrSegment);
to_len.set(recvInfo, 0L, (int)localSockAddrSegment.byteSize());
}
}

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_INT;
public class quiche_send_info
{
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
sockaddr_storage.layout().withName("from"),
C_INT.withName("from_len"),
MemoryLayout.paddingLayout(4),
sockaddr_storage.layout().withName("to"),
C_INT.withName("to_len"),
MemoryLayout.paddingLayout(4),
timespec.layout().withName("at")
);
public static MemorySegment allocate(SegmentAllocator scope)
{
return scope.allocate(LAYOUT);
}
}

View File

@ -11,29 +11,19 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.VarHandle;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_BOOL;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_LONG;
import static jdk.incubator.foreign.CLinker.C_CHAR;
import static jdk.incubator.foreign.CLinker.C_LONG;
public class quiche_stats
public class quiche_transport_params
{
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("sent_bytes"),
C_LONG.withName("recv_bytes"),
C_LONG.withName("lost_bytes"),
C_LONG.withName("stream_retrans_bytes"),
C_LONG.withName("paths_count"),
C_LONG.withName("peer_max_idle_timeout"),
C_LONG.withName("peer_max_udp_payload_size"),
C_LONG.withName("peer_initial_max_data"),
@ -44,21 +34,21 @@ public class quiche_stats
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_BOOL.withName("peer_disable_active_migration"),
MemoryLayout.paddingLayout(7),
C_LONG.withName("peer_active_conn_id_limit"),
C_LONG.withName("peer_max_datagram_frame_size")
);
public static MemorySegment allocate(ResourceScope scope)
private static final VarHandle peer_initial_max_streams_bidi = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("peer_initial_max_streams_bidi"));
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
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_peer_initial_max_streams_bidi(MemorySegment stats)
public static long get_peer_initial_max_streams_bidi(MemorySegment quicheTransportParams)
{
return (long)peer_initial_max_streams_bidi.get(stats);
return (long)peer_initial_max_streams_bidi.get(quicheTransportParams, 0L);
}
}

View File

@ -11,26 +11,26 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.net.SocketAddress;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import org.eclipse.jetty.quic.quiche.foreign.incubator.linux.sockaddr_linux;
import org.eclipse.jetty.quic.quiche.foreign.incubator.macos.sockaddr_macos;
import org.eclipse.jetty.quic.quiche.foreign.incubator.windows.sockaddr_windows;
import org.eclipse.jetty.quic.quiche.foreign.linux.sockaddr_linux;
import org.eclipse.jetty.quic.quiche.foreign.macos.sockaddr_macos;
import org.eclipse.jetty.quic.quiche.foreign.windows.sockaddr_windows;
public class sockaddr
{
public static MemorySegment convert(SocketAddress socketAddress, ResourceScope scope)
public static MemorySegment convert(SocketAddress socketAddress, SegmentAllocator allocator)
{
if (NativeHelper.isLinux())
return sockaddr_linux.convert(socketAddress, scope);
return sockaddr_linux.convert(socketAddress, allocator);
if (NativeHelper.isMac())
return sockaddr_macos.convert(socketAddress, scope);
return sockaddr_macos.convert(socketAddress, allocator);
if (NativeHelper.isWindows())
return sockaddr_windows.convert(socketAddress, scope);
return sockaddr_windows.convert(socketAddress, allocator);
throw new UnsupportedOperationException("Unsupported OS: " + System.getProperty("os.name"));
}
}

View File

@ -0,0 +1,35 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign;
import java.lang.foreign.MemoryLayout;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_CHAR;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_LONG;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_SHORT;
public class sockaddr_storage
{
private static final MemoryLayout $LAYOUT = MemoryLayout.structLayout(
C_SHORT.withName("ss_family"),
MemoryLayout.sequenceLayout(118, C_CHAR).withName("__ss_padding"),
C_LONG.withName("__ss_align")
);
public static MemoryLayout layout()
{
return $LAYOUT;
}
}

View File

@ -0,0 +1,32 @@
//
// ========================================================================
// Copyright (c) 1995 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.foreign;
import java.lang.foreign.MemoryLayout;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_LONG;
public class timespec
{
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
C_LONG.withName("tv_sec"),
C_LONG.withName("tv_nsec")
);
public static MemoryLayout layout()
{
return LAYOUT;
}
}

View File

@ -11,8 +11,11 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator.windows;
package org.eclipse.jetty.quic.quiche.foreign.windows;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.VarHandle;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -22,20 +25,16 @@ import java.net.SocketAddress;
import java.nio.ByteBuffer;
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;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_CHAR;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_INT;
import static org.eclipse.jetty.quic.quiche.foreign.NativeHelper.C_SHORT;
public class sockaddr_windows
{
private static final short AF_INET = 2;
private static final short AF_INET6 = 23;
public static MemorySegment convert(SocketAddress socketAddress, ResourceScope scope)
public static MemorySegment convert(SocketAddress socketAddress, SegmentAllocator scope)
{
if (!(socketAddress instanceof InetSocketAddress))
throw new IllegalArgumentException("Expected InetSocketAddress instance, got: " + socketAddress);
@ -45,7 +44,7 @@ public class sockaddr_windows
{
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_port(sin, (short)inetSocketAddress.getPort());
sockaddr_in.set_sin_addr(sin, ByteBuffer.wrap(address.getAddress()).getInt());
return sin;
}
@ -53,7 +52,7 @@ public class sockaddr_windows
{
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_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);
@ -74,28 +73,28 @@ public class sockaddr_windows
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"));
private static final VarHandle sin_family = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_family"));
private static final VarHandle sin_port = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_port"));
private static final VarHandle sin_addr = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin_addr"));
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
public static void set_sin_family(MemorySegment sin, short value)
{
sin_family.set(sin, value);
sin_family.set(sin, 0L, value);
}
public static void set_sin_port(MemorySegment sin, short value)
{
sin_port.set(sin, value);
sin_port.set(sin, 0L, value);
}
public static void set_sin_addr(MemorySegment sin, int value)
{
sin_addr.set(sin, value);
sin_addr.set(sin, 0L, value);
}
}
@ -109,14 +108,14 @@ public class sockaddr_windows
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"));
private static final VarHandle sin6_family = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_family"));
private static final VarHandle sin6_port = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_port"));
private static final VarHandle sin6_scope_id = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_scope_id"));
private static final VarHandle sin6_flowinfo = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sin6_flowinfo"));
public static MemorySegment allocate(ResourceScope scope)
public static MemorySegment allocate(SegmentAllocator scope)
{
return MemorySegment.allocateNative(LAYOUT, scope);
return scope.allocate(LAYOUT);
}
public static void set_sin6_addr(MemorySegment sin6, byte[] value)
@ -126,22 +125,22 @@ public class sockaddr_windows
public static void set_sin6_family(MemorySegment sin6, short value)
{
sin6_family.set(sin6, value);
sin6_family.set(sin6, 0L, value);
}
public static void set_sin6_port(MemorySegment sin6, short value)
{
sin6_port.set(sin6, value);
sin6_port.set(sin6, 0L, value);
}
public static void set_sin6_scope_id(MemorySegment sin6, int value)
{
sin6_scope_id.set(sin6, value);
sin6_scope_id.set(sin6, 0L, value);
}
public static void set_sin6_flowinfo(MemorySegment sin6, int value)
{
sin6_flowinfo.set(sin6, value);
sin6_flowinfo.set(sin6, 0L, value);
}
}
}

View File

@ -0,0 +1 @@
org.eclipse.jetty.quic.quiche.foreign.ForeignQuicheBinding

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.io.IOException;
import java.io.InputStream;
@ -51,14 +51,14 @@ public class LowLevelQuicheClientCertTest
{
public WorkDir workDir;
private final Collection<ForeignIncubatorQuicheConnection> connectionsToDisposeOf = new ArrayList<>();
private final Collection<ForeignQuicheConnection> connectionsToDisposeOf = new ArrayList<>();
private InetSocketAddress clientSocketAddress;
private InetSocketAddress serverSocketAddress;
private QuicheConfig clientQuicheConfig;
private QuicheConfig serverQuicheConfig;
private ForeignIncubatorQuicheConnection.TokenMinter tokenMinter;
private ForeignIncubatorQuicheConnection.TokenValidator tokenValidator;
private ForeignQuicheConnection.TokenMinter tokenMinter;
private ForeignQuicheConnection.TokenValidator tokenValidator;
private Certificate[] serverCertificateChain;
@BeforeEach
@ -115,7 +115,7 @@ public class LowLevelQuicheClientCertTest
@AfterEach
public void tearDown()
{
connectionsToDisposeOf.forEach(ForeignIncubatorQuicheConnection::dispose);
connectionsToDisposeOf.forEach(ForeignQuicheConnection::dispose);
connectionsToDisposeOf.clear();
}
@ -123,9 +123,9 @@ public class LowLevelQuicheClientCertTest
public void testClientCert() throws Exception
{
// establish connection
Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry = connectClientToServer();
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry = connectClientToServer();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
// assert that the client certificate was correctly received by the server
byte[] receivedClientCertificate = serverQuicheConnection.getPeerCertificate();
@ -138,10 +138,10 @@ public class LowLevelQuicheClientCertTest
assertThat(Arrays.equals(configuredServerCertificate, receivedServerCertificate), is(true));
}
private void drainServerToFeedClient(Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry, int expectedSize) throws IOException
private void drainServerToFeedClient(Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry, int expectedSize) throws IOException
{
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
int drained = serverQuicheConnection.drainCipherBytes(buffer);
@ -151,10 +151,10 @@ public class LowLevelQuicheClientCertTest
assertThat(fed, is(expectedSize));
}
private void drainServerToFeedClient(Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry, int expectedSizeLowerBound, int expectedSizeUpperBound) throws IOException
private void drainServerToFeedClient(Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry, int expectedSizeLowerBound, int expectedSizeUpperBound) throws IOException
{
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
int drained = serverQuicheConnection.drainCipherBytes(buffer);
@ -165,10 +165,10 @@ public class LowLevelQuicheClientCertTest
assertThat(fed, is(both(greaterThanOrEqualTo(expectedSizeLowerBound)).and(lessThanOrEqualTo(expectedSizeUpperBound))));
}
private void drainClientToFeedServer(Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry, int expectedSize) throws IOException
private void drainClientToFeedServer(Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry, int expectedSize) throws IOException
{
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
int drained = clientQuicheConnection.drainCipherBytes(buffer);
@ -178,21 +178,21 @@ public class LowLevelQuicheClientCertTest
assertThat(fed, is(expectedSize));
}
private Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> connectClientToServer() throws IOException
private Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> connectClientToServer() throws IOException
{
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
ByteBuffer buffer2 = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
ForeignIncubatorQuicheConnection clientQuicheConnection = ForeignIncubatorQuicheConnection.connect(clientQuicheConfig, clientSocketAddress, serverSocketAddress);
ForeignQuicheConnection clientQuicheConnection = ForeignQuicheConnection.connect(clientQuicheConfig, clientSocketAddress, serverSocketAddress);
connectionsToDisposeOf.add(clientQuicheConnection);
int drained = clientQuicheConnection.drainCipherBytes(buffer);
assertThat(drained, is(1200));
buffer.flip();
ForeignIncubatorQuicheConnection serverQuicheConnection = ForeignIncubatorQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
ForeignQuicheConnection serverQuicheConnection = ForeignQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
assertThat(serverQuicheConnection, is(nullValue()));
boolean negotiated = ForeignIncubatorQuicheConnection.negotiate(tokenMinter, buffer, buffer2);
boolean negotiated = ForeignQuicheConnection.negotiate(tokenMinter, buffer, buffer2);
assertThat(negotiated, is(true));
buffer2.flip();
@ -204,7 +204,7 @@ public class LowLevelQuicheClientCertTest
assertThat(drained, is(1200));
buffer.flip();
serverQuicheConnection = ForeignIncubatorQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
serverQuicheConnection = ForeignQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
assertThat(serverQuicheConnection, is(not(nullValue())));
connectionsToDisposeOf.add(serverQuicheConnection);
@ -219,7 +219,7 @@ public class LowLevelQuicheClientCertTest
assertThat(serverQuicheConnection.isConnectionEstablished(), is(false));
assertThat(clientQuicheConnection.isConnectionEstablished(), is(false));
AbstractMap.SimpleImmutableEntry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
AbstractMap.SimpleImmutableEntry<ForeignQuicheConnection, ForeignQuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
int protosLen = 0;
for (String proto : clientQuicheConfig.getApplicationProtos())

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.quic.quiche.foreign.incubator;
package org.eclipse.jetty.quic.quiche.foreign;
import java.io.IOException;
import java.io.InputStream;
@ -36,8 +36,6 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.eclipse.jetty.quic.quiche.Quiche.QUICHE_MIN_CLIENT_INITIAL_LEN;
@ -46,21 +44,19 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
// TODO: make this test work in Java 18 too.
@EnabledOnJre(value = JRE.JAVA_17, disabledReason = "Java 18's Foreign APIs are incompatible with Java 17's Foreign APIs")
@ExtendWith(WorkDirExtension.class)
public class LowLevelQuicheTest
{
public WorkDir workDir;
private final Collection<ForeignIncubatorQuicheConnection> connectionsToDisposeOf = new ArrayList<>();
private final Collection<ForeignQuicheConnection> connectionsToDisposeOf = new ArrayList<>();
private InetSocketAddress clientSocketAddress;
private InetSocketAddress serverSocketAddress;
private QuicheConfig clientQuicheConfig;
private QuicheConfig serverQuicheConfig;
private ForeignIncubatorQuicheConnection.TokenMinter tokenMinter;
private ForeignIncubatorQuicheConnection.TokenValidator tokenValidator;
private ForeignQuicheConnection.TokenMinter tokenMinter;
private ForeignQuicheConnection.TokenValidator tokenValidator;
private Certificate[] serverCertificateChain;
@BeforeEach
@ -113,7 +109,7 @@ public class LowLevelQuicheTest
@AfterEach
public void tearDown()
{
connectionsToDisposeOf.forEach(ForeignIncubatorQuicheConnection::dispose);
connectionsToDisposeOf.forEach(ForeignQuicheConnection::dispose);
connectionsToDisposeOf.clear();
}
@ -121,9 +117,9 @@ public class LowLevelQuicheTest
public void testFinishedAsSoonAsFinIsFed() throws Exception
{
// establish connection
Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry = connectClientToServer();
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry = connectClientToServer();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
// client sends 16 bytes of payload over stream 0
assertThat(clientQuicheConnection.feedClearBytesForStream(0, ByteBuffer.allocate(16)
@ -169,9 +165,9 @@ public class LowLevelQuicheTest
public void testNotFinishedAsLongAsStreamHasReadableBytes() throws Exception
{
// establish connection
Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry = connectClientToServer();
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry = connectClientToServer();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
// client sends 16 bytes of payload over stream 0 and finish it
assertThat(clientQuicheConnection.feedClearBytesForStream(0, ByteBuffer.allocate(16)
@ -213,9 +209,9 @@ public class LowLevelQuicheTest
clientQuicheConfig.setApplicationProtos("");
// establish connection
Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry = connectClientToServer();
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry = connectClientToServer();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
assertThat(clientQuicheConnection.getNegotiatedProtocol(), is(""));
assertThat(serverQuicheConnection.getNegotiatedProtocol(), is(""));
@ -229,10 +225,10 @@ public class LowLevelQuicheTest
assertThat(Arrays.equals(serverCert, peerCertificate), is(true));
}
private void drainServerToFeedClient(Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry, int expectedSize) throws IOException
private void drainServerToFeedClient(Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry, int expectedSize) throws IOException
{
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
int drained = serverQuicheConnection.drainCipherBytes(buffer);
@ -242,10 +238,10 @@ public class LowLevelQuicheTest
assertThat(fed, is(expectedSize));
}
private void drainClientToFeedServer(Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry, int expectedSize) throws IOException
private void drainClientToFeedServer(Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> entry, int expectedSize) throws IOException
{
ForeignIncubatorQuicheConnection clientQuicheConnection = entry.getKey();
ForeignIncubatorQuicheConnection serverQuicheConnection = entry.getValue();
ForeignQuicheConnection clientQuicheConnection = entry.getKey();
ForeignQuicheConnection serverQuicheConnection = entry.getValue();
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
int drained = clientQuicheConnection.drainCipherBytes(buffer);
@ -255,21 +251,21 @@ public class LowLevelQuicheTest
assertThat(fed, is(expectedSize));
}
private Map.Entry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> connectClientToServer() throws IOException
private Map.Entry<ForeignQuicheConnection, ForeignQuicheConnection> connectClientToServer() throws IOException
{
ByteBuffer buffer = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
ByteBuffer buffer2 = ByteBuffer.allocate(QUICHE_MIN_CLIENT_INITIAL_LEN);
ForeignIncubatorQuicheConnection clientQuicheConnection = ForeignIncubatorQuicheConnection.connect(clientQuicheConfig, clientSocketAddress, serverSocketAddress);
ForeignQuicheConnection clientQuicheConnection = ForeignQuicheConnection.connect(clientQuicheConfig, clientSocketAddress, serverSocketAddress);
connectionsToDisposeOf.add(clientQuicheConnection);
int drained = clientQuicheConnection.drainCipherBytes(buffer);
assertThat(drained, is(1200));
buffer.flip();
ForeignIncubatorQuicheConnection serverQuicheConnection = ForeignIncubatorQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
ForeignQuicheConnection serverQuicheConnection = ForeignQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
assertThat(serverQuicheConnection, is(nullValue()));
boolean negotiated = ForeignIncubatorQuicheConnection.negotiate(tokenMinter, buffer, buffer2);
boolean negotiated = ForeignQuicheConnection.negotiate(tokenMinter, buffer, buffer2);
assertThat(negotiated, is(true));
buffer2.flip();
@ -281,7 +277,7 @@ public class LowLevelQuicheTest
assertThat(drained, is(1200));
buffer.flip();
serverQuicheConnection = ForeignIncubatorQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
serverQuicheConnection = ForeignQuicheConnection.tryAccept(serverQuicheConfig, tokenValidator, buffer, serverSocketAddress, clientSocketAddress);
assertThat(serverQuicheConnection, is(not(nullValue())));
connectionsToDisposeOf.add(serverQuicheConnection);
@ -296,7 +292,7 @@ public class LowLevelQuicheTest
assertThat(serverQuicheConnection.isConnectionEstablished(), is(false));
assertThat(clientQuicheConnection.isConnectionEstablished(), is(false));
AbstractMap.SimpleImmutableEntry<ForeignIncubatorQuicheConnection, ForeignIncubatorQuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
AbstractMap.SimpleImmutableEntry<ForeignQuicheConnection, ForeignQuicheConnection> entry = new AbstractMap.SimpleImmutableEntry<>(clientQuicheConnection, serverQuicheConnection);
int protosLen = 0;
for (String proto : clientQuicheConfig.getApplicationProtos())

View File

@ -631,9 +631,9 @@ public class JnaQuicheConnection extends QuicheConnection
{
if (quicheConn == null)
throw new IllegalStateException("connection was released");
LibQuiche.quiche_stats stats = new LibQuiche.quiche_stats();
LibQuiche.INSTANCE.quiche_conn_stats(quicheConn, stats);
return stats.peer_initial_max_streams_bidi.intValue();
LibQuiche.quiche_transport_params params = new LibQuiche.quiche_transport_params();
LibQuiche.INSTANCE.quiche_conn_peer_transport_params(quicheConn, params);
return params.peer_initial_max_streams_bidi.intValue();
}
}

View File

@ -31,7 +31,7 @@ public interface LibQuiche extends Library
{
// This interface is a translation of the quiche.h header of a specific version.
// It needs to be reviewed each time the native lib version changes.
String EXPECTED_QUICHE_VERSION = "0.20.1";
String EXPECTED_QUICHE_VERSION = "0.21.0";
// The charset used to convert java.lang.String to char * and vice versa.
Charset CHARSET = StandardCharsets.UTF_8;
@ -148,43 +148,14 @@ public interface LibQuiche extends Library
}
@Structure.FieldOrder({
"recv", "sent", "lost", "retrans", "sent_bytes", "recv_bytes", "lost_bytes",
"stream_retrans_bytes", "paths_count", "peer_max_idle_timeout",
"peer_max_udp_payload_size", "peer_initial_max_data", "peer_initial_max_stream_data_bidi_local",
"peer_initial_max_stream_data_bidi_remote", "peer_initial_max_stream_data_uni",
"peer_initial_max_streams_bidi", "peer_initial_max_streams_uni", "peer_ack_delay_exponent",
"peer_max_ack_delay", "peer_disable_active_migration", "peer_active_conn_id_limit",
"peer_max_idle_timeout", "peer_max_udp_payload_size", "peer_initial_max_data",
"peer_initial_max_stream_data_bidi_local", "peer_initial_max_stream_data_bidi_remote",
"peer_initial_max_stream_data_uni", "peer_initial_max_streams_bidi", "peer_initial_max_streams_uni",
"peer_ack_delay_exponent", "peer_max_ack_delay", "peer_disable_active_migration", "peer_active_conn_id_limit",
"peer_max_datagram_frame_size"
})
class quiche_stats extends Structure
class quiche_transport_params extends Structure
{
// The number of QUIC packets received on this connection.
public size_t recv;
// The number of QUIC packets sent on this connection.
public size_t sent;
// The number of QUIC packets that were lost.
public size_t lost;
// The number of sent QUIC packets with retranmitted data.
public size_t retrans;
// The number of sent bytes.
public uint64_t sent_bytes;
// The number of received bytes.
public uint64_t recv_bytes;
// The number of bytes lost.
public uint64_t lost_bytes;
// The number of stream bytes retransmitted.
public uint64_t stream_retrans_bytes;
// The number of known paths for the connection.
public size_t paths_count;
// The maximum idle timeout.
public uint64_t peer_max_idle_timeout;
@ -225,6 +196,52 @@ public interface LibQuiche extends Library
public ssize_t peer_max_datagram_frame_size;
}
@Structure.FieldOrder({
"recv", "sent", "lost", "retrans", "sent_bytes", "recv_bytes", "lost_bytes", "stream_retrans_bytes", "paths_count",
"reset_stream_count_local", "stopped_stream_count_local", "reset_stream_count_remote", "stopped_stream_count_remote"
})
class quiche_stats extends Structure
{
// The number of QUIC packets received on this connection.
public size_t recv;
// The number of QUIC packets sent on this connection.
public size_t sent;
// The number of QUIC packets that were lost.
public size_t lost;
// The number of sent QUIC packets with retransmitted data.
public size_t retrans;
// The number of sent bytes.
public uint64_t sent_bytes;
// The number of received bytes.
public uint64_t recv_bytes;
// The number of bytes lost.
public uint64_t lost_bytes;
// The number of stream bytes retransmitted.
public uint64_t stream_retrans_bytes;
// The number of known paths for the connection.
public size_t paths_count;
// The number of streams reset by local.
public uint64_t reset_stream_count_local;
// The number of streams stopped by local.
public uint64_t stopped_stream_count_local;
// The number of streams reset by remote.
public uint64_t reset_stream_count_remote;
// The number of streams stopped by remote.
public uint64_t stopped_stream_count_remote;
}
@Structure.FieldOrder({
"local_addr", "local_addr_len", "peer_addr", "peer_addr_len",
"validation_state", "active", "recv", "sent", "lost", "retrans",
@ -373,6 +390,10 @@ public interface LibQuiche extends Library
// field of `quiche_stats`).
int quiche_conn_path_stats(quiche_conn conn, size_t idx, quiche_path_stats out);
// Returns the peer's transport parameters in |out|. Returns false if we have
// not yet processed the peer's transport parameters.
boolean quiche_conn_peer_transport_params(quiche_conn conn, quiche_transport_params out);
// Returns whether or not this is a server-side connection.
boolean quiche_conn_is_server(quiche_conn conn);
@ -381,8 +402,8 @@ public interface LibQuiche extends Library
// Schedule an ack-eliciting packet on the specified path.
ssize_t quiche_conn_send_ack_eliciting_on_path(quiche_conn conn,
sockaddr local, size_t local_len,
sockaddr peer, size_t peer_len);
sockaddr local, size_t local_len,
sockaddr peer, size_t peer_len);
@Structure.FieldOrder({"from", "from_len", "to", "to_len", "at"})
class quiche_send_info extends Structure

View File

@ -13,7 +13,18 @@
<modules>
<module>jetty-quic-quiche-common</module>
<module>jetty-quic-quiche-foreign-incubator</module>
<module>jetty-quic-quiche-jna</module>
</modules>
<profiles>
<profile>
<id>enable-foreign</id>
<activation>
<jdk>[22,)</jdk>
</activation>
<modules>
<module>jetty-quic-quiche-foreign</module>
</modules>
</profile>
</profiles>
</project>

View File

@ -40,14 +40,14 @@
<profiles>
<profile>
<id>jdk17</id>
<id>enable-foreign</id>
<activation>
<jdk>17</jdk>
<jdk>[22,)</jdk>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.quic</groupId>
<artifactId>jetty-quic-quiche-foreign-incubator</artifactId>
<artifactId>jetty-quic-quiche-foreign</artifactId>
</dependency>
</dependencies>
</profile>

View File

@ -369,7 +369,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
{
try
{
if (_ignorePathMap != null && _ignorePathMap.getMatched(request.getHttpURI().toString()) != null)
if (_ignorePathMap != null && _ignorePathMap.getMatched(request.getHttpURI().getCanonicalPath()) != null)
return;
if (_filter != null && !_filter.test(request, response))

View File

@ -93,11 +93,22 @@ public interface HttpChannel extends Invocable
*
* @param failure the failure cause.
* @return a {@code Runnable} that performs the failure action, or {@code null}
* if no failure action need be performed by the calling thread
* if no failure action needs be performed by the calling thread
* @see Request#addFailureListener(Consumer)
*/
Runnable onFailure(Throwable failure);
/**
* <p>Notifies this {@code HttpChannel} that an asynchronous close happened.</p>
*
* @return a {@code Runnable} that performs the close action, or {@code null}
* if no close action needs be performed by the calling thread
*/
default Runnable onClose()
{
return null;
}
/**
* Recycle the HttpChannel, so that a new cycle of calling {@link #setHttpStream(HttpStream)},
* {@link #onRequest(MetaData.Request)} etc. may be performed on the channel.

View File

@ -28,8 +28,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.http.ByteRange;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.DateParser;
import org.eclipse.jetty.http.EtagUtils;
import org.eclipse.jetty.http.HttpDateTime;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@ -372,7 +372,8 @@ public class ResourceService
return true;
}
long ifmsl = DateParser.parseDate(ifms);
// TODO: what should we do when we get a crappy date?
long ifmsl = HttpDateTime.parseToEpoch(ifms);
if (ifmsl != -1)
{
long lm = content.getResource().lastModified().toEpochMilli();
@ -387,7 +388,8 @@ public class ResourceService
// Parse the if[un]modified dates and compare to resource
if (ifums != null && ifm == null)
{
long ifumsl = DateParser.parseDate(ifums);
// TODO: what should we do when we get a crappy date?
long ifumsl = HttpDateTime.parseToEpoch(ifums);
if (ifumsl != -1)
{
long lm = content.getResource().lastModified().toEpochMilli();

Some files were not shown because too many files have changed in this diff Show More