main
commit
b85679f001
@ -0,0 +1,57 @@
|
||||
# Eclipse project files
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
|
||||
# IntelliJ IDEA project files and directories
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
.shelf/
|
||||
|
||||
# Geany project file
|
||||
.geany
|
||||
|
||||
# KDevelop project file and directory
|
||||
.kdev4/
|
||||
*.kdev4
|
||||
|
||||
# Build targets
|
||||
/target
|
||||
*/target
|
||||
|
||||
# Report directories
|
||||
/reports
|
||||
*/reports
|
||||
|
||||
# Mac-specific directory that no other operating system needs.
|
||||
.DS_Store
|
||||
|
||||
# JVM crash logs
|
||||
hs_err_pid*.log
|
||||
replay_pid*.log
|
||||
|
||||
# JVM dumps
|
||||
*.hprof.xz
|
||||
*.threads
|
||||
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
*/.unison.*
|
||||
|
||||
# exclude mainframer files
|
||||
mainframer
|
||||
.mainframer
|
||||
|
||||
# exclude docker-sync stuff
|
||||
.docker-sync
|
||||
*/.docker-sync
|
||||
|
||||
# exclude vscode files
|
||||
.vscode/
|
||||
*.factorypath
|
||||
|
||||
# exclude file created by the flatten plugin
|
||||
.flattened-pom.xml
|
||||
.java-version
|
Binary file not shown.
@ -0,0 +1,18 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://maven-central.storage-download.googleapis.com/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
|
||||
distributionSha256Sum=7822eb593d29558d8edf87845a2c47e36e2a89d17a84cd2390824633214ed423
|
@ -0,0 +1,10 @@
|
||||
brew 'autoconf'
|
||||
brew 'automake'
|
||||
brew 'libtool'
|
||||
brew 'openssl'
|
||||
brew 'perl'
|
||||
brew 'ninja'
|
||||
brew 'golang'
|
||||
brew 'cmake'
|
||||
brew 'apr'
|
||||
brew 'rust'
|
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,49 @@
|
||||
|
||||
The Netty Project
|
||||
=================
|
||||
|
||||
Please visit the Netty web site for more information:
|
||||
|
||||
* https://netty.io/
|
||||
|
||||
Copyright 2020 The Netty Project
|
||||
|
||||
The Netty Project licenses this file to you under the Apache License,
|
||||
version 2.0 (the "License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at:
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Also, please refer to each LICENSE.<component>.txt file, which is located in
|
||||
the 'license' directory of the distribution file, for the license terms of the
|
||||
components that this product depends on.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
This product contains the Maven wrapper scripts from 'Maven Wrapper', that provides an easy way to ensure a user has everything necessary to run the Maven build.
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.mvn-wrapper.txt (Apache License 2.0)
|
||||
* HOMEPAGE:
|
||||
* https://github.com/takari/maven-wrapper
|
||||
|
||||
This product is statically linked against Quiche.
|
||||
|
||||
* LICENSE:
|
||||
* license/LICENSE.quiche.txt (BSD2)
|
||||
* HOMEPAGE:
|
||||
* https://github.com/cloudflare/quiche
|
||||
|
||||
|
||||
This product is statically linked against boringssl.
|
||||
|
||||
* LICENSE (Combination ISC and OpenSSL license)
|
||||
* license/LICENSE.boringssl.txt (Combination ISC and OpenSSL license)
|
||||
* HOMEPAGE:
|
||||
* https://boringssl.googlesource.com/boringssl/
|
@ -0,0 +1,52 @@
|
||||
![Build project](https://github.com/netty/netty-incubator-codec-quic/workflows/Build%20project/badge.svg)
|
||||
|
||||
# Netty QUIC codec
|
||||
|
||||
This is a new experimental QUIC codec for netty which makes use of [quiche](https://github.com/cloudflare/quiche).
|
||||
|
||||
## How to include the dependency
|
||||
|
||||
To include the dependency you need to ensure you also specify the right classifier. At the moment we only support Linux
|
||||
x86_64 / aarch_64, macOS / OSX x86_64 / aarch_64 and Windows x86_64 but this may change.
|
||||
|
||||
As an example this is how you would include the dependency in maven:
|
||||
For Linux x86_64:
|
||||
```
|
||||
<dependency>
|
||||
<groupId>io.netty.incubator</groupId>
|
||||
<artifactId>netty-incubator-codec-native-quic</artifactId>
|
||||
<version>0.0.21.Final</version>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
For macOS / OSX:
|
||||
|
||||
```
|
||||
<dependency>
|
||||
<groupId>io.netty.incubator</groupId>
|
||||
<artifactId>netty-incubator-codec-native-quic</artifactId>
|
||||
<version>0.0.21.Final</version>
|
||||
<classifier>osx-x86_64</classifier>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
For Windows:
|
||||
|
||||
```
|
||||
<dependency>
|
||||
<groupId>io.netty.incubator</groupId>
|
||||
<artifactId>netty-incubator-codec-native-quic</artifactId>
|
||||
<version>0.0.21.Final</version>
|
||||
<classifier>windows-x86_64</classifier>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## How to use this codec ?
|
||||
|
||||
For some examples please check our
|
||||
[example package](https://github.com/netty/netty-incubator-codec-quic/tree/main/codec-native-quic/src/test/java/io/netty/incubator/codec/quic).
|
||||
This contains a server and a client that can speak some limited HTTP/0.9 with each other.
|
||||
|
||||
For more "advanced" use cases, consider checking our
|
||||
[netty-incubator-codec-http3](https://github.com/netty/netty-incubator-codec-http3) project.
|
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2021 The Netty Project
|
||||
~
|
||||
~ The Netty Project licenses this file to you under the Apache License,
|
||||
~ version 2.0 (the "License"); you may not use this file except in compliance
|
||||
~ with the License. You may obtain a copy of the License at:
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
~ License for the specific language governing permissions and limitations
|
||||
~ under the License.
|
||||
-->
|
||||
<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 https://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.xbib.netty</groupId>
|
||||
<artifactId>netty-handler-codec-parent-quic</artifactId>
|
||||
<version>0.0.56.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>netty-handler-codec-classes-quic</artifactId>
|
||||
<version>0.0.56.Final-SNAPSHOT</version>
|
||||
<name>Netty/Handler/Codec/Classes/Quic</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<javaModuleName>io.netty.handler.codec.classes.quic</javaModuleName>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-jar</id>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Automatic-Module-Name>${javaModuleName}</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
<index>true</index>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-buffer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-classes-epoll</artifactId>
|
||||
<!-- Let's mark as optional as it is not strictly needed -->
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.handler.ssl.util.LazyX509Certificate;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
final class BoringSSL {
|
||||
private BoringSSL() { }
|
||||
|
||||
static final int SSL_VERIFY_NONE = BoringSSLNativeStaticallyReferencedJniMethods.ssl_verify_none();
|
||||
static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = BoringSSLNativeStaticallyReferencedJniMethods
|
||||
.ssl_verify_fail_if_no_peer_cert();
|
||||
static final int SSL_VERIFY_PEER = BoringSSLNativeStaticallyReferencedJniMethods.ssl_verify_peer();
|
||||
static final int X509_V_OK = BoringSSLNativeStaticallyReferencedJniMethods.x509_v_ok();
|
||||
static final int X509_V_ERR_CERT_HAS_EXPIRED =
|
||||
BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_cert_has_expired();
|
||||
static final int X509_V_ERR_CERT_NOT_YET_VALID =
|
||||
BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_cert_not_yet_valid();
|
||||
static final int X509_V_ERR_CERT_REVOKED = BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_cert_revoked();
|
||||
static final int X509_V_ERR_UNSPECIFIED = BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_unspecified();
|
||||
|
||||
static long SSLContext_new(boolean server, String[] applicationProtocols,
|
||||
BoringSSLHandshakeCompleteCallback handshakeCompleteCallback,
|
||||
BoringSSLCertificateCallback certificateCallback,
|
||||
BoringSSLCertificateVerifyCallback verifyCallback,
|
||||
BoringSSLTlsextServernameCallback servernameCallback,
|
||||
BoringSSLKeylogCallback keylogCallback,
|
||||
BoringSSLSessionCallback sessionCallback,
|
||||
BoringSSLPrivateKeyMethod privateKeyMethod,
|
||||
BoringSSLSessionTicketCallback sessionTicketCallback,
|
||||
int verifyMode,
|
||||
byte[][] subjectNames) {
|
||||
return SSLContext_new0(server, toWireFormat(applicationProtocols),
|
||||
handshakeCompleteCallback, certificateCallback, verifyCallback, servernameCallback,
|
||||
keylogCallback, sessionCallback, privateKeyMethod, sessionTicketCallback, verifyMode, subjectNames);
|
||||
}
|
||||
|
||||
private static byte[] toWireFormat(String[] applicationProtocols) {
|
||||
if (applicationProtocols == null) {
|
||||
return null;
|
||||
}
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
for (String p : applicationProtocols) {
|
||||
byte[] bytes = p.getBytes(StandardCharsets.US_ASCII);
|
||||
out.write(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static native long SSLContext_new0(boolean server,
|
||||
byte[] applicationProtocols, Object handshakeCompleteCallback,
|
||||
Object certificateCallback, Object verifyCallback,
|
||||
Object servernameCallback, Object keylogCallback,
|
||||
Object sessionCallback,
|
||||
Object privateKeyMethod,
|
||||
Object sessionTicketCallback,
|
||||
int verifyDepth, byte[][] subjectNames);
|
||||
static native void SSLContext_set_early_data_enabled(long context, boolean enabled);
|
||||
static native long SSLContext_setSessionCacheSize(long context, long size);
|
||||
static native long SSLContext_setSessionCacheTimeout(long context, long size);
|
||||
|
||||
static native void SSLContext_setSessionTicketKeys(long context, boolean enableCallback);
|
||||
|
||||
static native void SSLContext_free(long context);
|
||||
static long SSL_new(long context, boolean server, String hostname) {
|
||||
return SSL_new0(context, server, tlsExtHostName(hostname));
|
||||
}
|
||||
static native long SSL_new0(long context, boolean server, String hostname);
|
||||
static native void SSL_free(long ssl);
|
||||
|
||||
static native Runnable SSL_getTask(long ssl);
|
||||
|
||||
static native void SSL_cleanup(long ssl);
|
||||
|
||||
static native long EVP_PKEY_parse(byte[] bytes, String pass);
|
||||
static native void EVP_PKEY_free(long key);
|
||||
|
||||
static native long CRYPTO_BUFFER_stack_new(long ssl, byte[][] bytes);
|
||||
static native void CRYPTO_BUFFER_stack_free(long chain);
|
||||
|
||||
static native String ERR_last_error();
|
||||
|
||||
private static String tlsExtHostName(String hostname) {
|
||||
if (hostname != null && hostname.endsWith(".")) {
|
||||
// Strip trailing dot if included.
|
||||
// See https://github.com/netty/netty-tcnative/issues/400
|
||||
hostname = hostname.substring(0, hostname.length() - 1);
|
||||
}
|
||||
return hostname;
|
||||
}
|
||||
|
||||
static X509Certificate[] certificates(byte[][] chain) {
|
||||
X509Certificate[] peerCerts = new X509Certificate[chain.length];
|
||||
for (int i = 0; i < peerCerts.length; i++) {
|
||||
peerCerts[i] = new LazyX509Certificate(chain[i]);
|
||||
}
|
||||
return peerCerts;
|
||||
}
|
||||
|
||||
static byte[][] subjectNames(X509Certificate[] certificates) {
|
||||
byte[][] subjectNames = new byte[certificates.length][];
|
||||
for (int i = 0; i < certificates.length; i++) {
|
||||
subjectNames[i] = certificates[i].getSubjectX500Principal().getEncoded();
|
||||
}
|
||||
return subjectNames;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.concurrent.Future;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
public interface BoringSSLAsyncPrivateKeyMethod {
|
||||
int SSL_SIGN_RSA_PKCS1_SHA1 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA1;
|
||||
int SSL_SIGN_RSA_PKCS1_SHA256 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256;
|
||||
int SSL_SIGN_RSA_PKCS1_SHA384 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384;
|
||||
int SSL_SIGN_RSA_PKCS1_SHA512 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512;
|
||||
int SSL_SIGN_ECDSA_SHA1 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SHA1;
|
||||
int SSL_SIGN_ECDSA_SECP256R1_SHA256 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256;
|
||||
int SSL_SIGN_ECDSA_SECP384R1_SHA384 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384;
|
||||
int SSL_SIGN_ECDSA_SECP521R1_SHA512 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512;
|
||||
int SSL_SIGN_RSA_PSS_RSAE_SHA256 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256;
|
||||
int SSL_SIGN_RSA_PSS_RSAE_SHA384 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384;
|
||||
int SSL_SIGN_RSA_PSS_RSAE_SHA512 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512;
|
||||
int SSL_SIGN_ED25519 = BoringSSLPrivateKeyMethod.SSL_SIGN_ED25519;
|
||||
int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_MD5_SHA1;
|
||||
|
||||
/**
|
||||
* Signs the input with the given key and notifies the returned {@link Future} with the signed bytes.
|
||||
*
|
||||
* @param engine the {@link SSLEngine}
|
||||
* @param signatureAlgorithm the algorithm to use for signing
|
||||
* @param input the digest itself
|
||||
* @return the {@link Future} that will be notified with the signed data
|
||||
* (must not be {@code null}) when the operation completes.
|
||||
*/
|
||||
Future<byte[]> sign(SSLEngine engine, int signatureAlgorithm, byte[] input);
|
||||
|
||||
/**
|
||||
* Decrypts the input with the given key and notifies the returned {@link Future} with the decrypted bytes.
|
||||
*
|
||||
* @param engine the {@link SSLEngine}
|
||||
* @param input the input which should be decrypted
|
||||
* @return the {@link Future} that will be notified with the decrypted data
|
||||
* (must not be {@code null}) when the operation completes.
|
||||
*/
|
||||
Future<byte[]> decrypt(SSLEngine engine, byte[] input);
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
final class BoringSSLCertificateCallback {
|
||||
private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
private static final byte[] END_PRIVATE_KEY = "\n-----END PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||
|
||||
/**
|
||||
* The types contained in the {@code keyTypeBytes} array.
|
||||
*/
|
||||
// Extracted from https://github.com/openssl/openssl/blob/master/include/openssl/tls1.h
|
||||
private static final byte TLS_CT_RSA_SIGN = 1;
|
||||
private static final byte TLS_CT_DSS_SIGN = 2;
|
||||
private static final byte TLS_CT_RSA_FIXED_DH = 3;
|
||||
private static final byte TLS_CT_DSS_FIXED_DH = 4;
|
||||
private static final byte TLS_CT_ECDSA_SIGN = 64;
|
||||
private static final byte TLS_CT_RSA_FIXED_ECDH = 65;
|
||||
private static final byte TLS_CT_ECDSA_FIXED_ECDH = 66;
|
||||
|
||||
// Code in this class is inspired by code of conscrypts:
|
||||
// - https://android.googlesource.com/platform/external/
|
||||
// conscrypt/+/master/src/main/java/org/conscrypt/OpenSSLEngineImpl.java
|
||||
// - https://android.googlesource.com/platform/external/
|
||||
// conscrypt/+/master/src/main/java/org/conscrypt/SSLParametersImpl.java
|
||||
//
|
||||
static final String KEY_TYPE_RSA = "RSA";
|
||||
static final String KEY_TYPE_DH_RSA = "DH_RSA";
|
||||
static final String KEY_TYPE_EC = "EC";
|
||||
static final String KEY_TYPE_EC_EC = "EC_EC";
|
||||
static final String KEY_TYPE_EC_RSA = "EC_RSA";
|
||||
|
||||
// key type mappings for types.
|
||||
private static final Map<String, String> KEY_TYPES = new HashMap<String, String>();
|
||||
static {
|
||||
KEY_TYPES.put("RSA", KEY_TYPE_RSA);
|
||||
KEY_TYPES.put("DHE_RSA", KEY_TYPE_RSA);
|
||||
KEY_TYPES.put("ECDHE_RSA", KEY_TYPE_RSA);
|
||||
KEY_TYPES.put("ECDHE_ECDSA", KEY_TYPE_EC);
|
||||
KEY_TYPES.put("ECDH_RSA", KEY_TYPE_EC_RSA);
|
||||
KEY_TYPES.put("ECDH_ECDSA", KEY_TYPE_EC_EC);
|
||||
KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
|
||||
}
|
||||
|
||||
private static final Set<String> SUPPORTED_KEY_TYPES = Collections.unmodifiableSet(new LinkedHashSet<>(
|
||||
Arrays.asList(KEY_TYPE_RSA,
|
||||
KEY_TYPE_DH_RSA,
|
||||
KEY_TYPE_EC,
|
||||
KEY_TYPE_EC_RSA,
|
||||
KEY_TYPE_EC_EC)));
|
||||
|
||||
// Directly returning this is safe as we never modify it within our JNI code.
|
||||
private static final long[] NO_KEY_MATERIAL_CLIENT_SIDE = new long[] { 0, 0 };
|
||||
|
||||
private final QuicheQuicSslEngineMap engineMap;
|
||||
private final X509ExtendedKeyManager keyManager;
|
||||
private final String password;
|
||||
|
||||
BoringSSLCertificateCallback(QuicheQuicSslEngineMap engineMap, X509ExtendedKeyManager keyManager, String password) {
|
||||
this.engineMap = engineMap;
|
||||
this.keyManager = keyManager;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
long[] handle(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals, String[] authMethods) {
|
||||
QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (keyManager == null) {
|
||||
if (engine.getUseClientMode()) {
|
||||
return NO_KEY_MATERIAL_CLIENT_SIDE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (engine.getUseClientMode()) {
|
||||
final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
|
||||
final String[] keyTypes = keyTypesSet.toArray(new String[0]);
|
||||
final X500Principal[] issuers;
|
||||
if (asn1DerEncodedPrincipals == null) {
|
||||
issuers = null;
|
||||
} else {
|
||||
issuers = new X500Principal[asn1DerEncodedPrincipals.length];
|
||||
for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
|
||||
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
|
||||
}
|
||||
}
|
||||
return removeMappingIfNeeded(ssl, selectKeyMaterialClientSide(ssl, engine, keyTypes, issuers));
|
||||
} else {
|
||||
// For now we just ignore the asn1DerEncodedPrincipals as this is kind of inline with what the
|
||||
// OpenJDK SSLEngineImpl does.
|
||||
return removeMappingIfNeeded(ssl, selectKeyMaterialServerSide(ssl, engine, authMethods));
|
||||
}
|
||||
} catch (SSLException e) {
|
||||
engineMap.remove(ssl);
|
||||
return null;
|
||||
} catch (Throwable cause) {
|
||||
engineMap.remove(ssl);
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
private long[] removeMappingIfNeeded(long ssl, long[] result) {
|
||||
if (result == null) {
|
||||
engineMap.remove(ssl);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private long[] selectKeyMaterialServerSide(long ssl, QuicheQuicSslEngine engine, String[] authMethods)
|
||||
throws SSLException {
|
||||
if (authMethods.length == 0) {
|
||||
throw new SSLHandshakeException("Unable to find key material");
|
||||
}
|
||||
|
||||
// authMethods may contain duplicates or may result in the same type
|
||||
// but call chooseServerAlias(...) may be expensive. So let's ensure
|
||||
// we filter out duplicates.
|
||||
Set<String> typeSet = new HashSet<String>(KEY_TYPES.size());
|
||||
for (String authMethod : authMethods) {
|
||||
String type = KEY_TYPES.get(authMethod);
|
||||
if (type != null && typeSet.add(type)) {
|
||||
String alias = chooseServerAlias(engine, type);
|
||||
if (alias != null) {
|
||||
return selectMaterial(ssl, engine, alias) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new SSLHandshakeException("Unable to find key material for auth method(s): "
|
||||
+ Arrays.toString(authMethods));
|
||||
}
|
||||
|
||||
private long[] selectKeyMaterialClientSide(long ssl, QuicheQuicSslEngine engine, String[] keyTypes,
|
||||
X500Principal[] issuer) {
|
||||
String alias = chooseClientAlias(engine, keyTypes, issuer);
|
||||
// Only try to set the keymaterial if we have a match. This is also consistent with what OpenJDK does:
|
||||
// https://hg.openjdk.java.net/jdk/jdk11/file/76072a077ee1/
|
||||
// src/java.base/share/classes/sun/security/ssl/CertificateRequest.java#l362
|
||||
if (alias != null) {
|
||||
return selectMaterial(ssl, engine, alias) ;
|
||||
}
|
||||
return NO_KEY_MATERIAL_CLIENT_SIDE;
|
||||
}
|
||||
|
||||
private long[] selectMaterial(long ssl, QuicheQuicSslEngine engine, String alias) {
|
||||
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
|
||||
if (certificates == null || certificates.length == 0) {
|
||||
return null;
|
||||
}
|
||||
byte[][] certs = new byte[certificates.length][];
|
||||
|
||||
for (int i = 0; i < certificates.length; i++) {
|
||||
try {
|
||||
certs[i] = certificates[i].getEncoded();
|
||||
} catch (CertificateEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final long key;
|
||||
PrivateKey privateKey = keyManager.getPrivateKey(alias);
|
||||
if (privateKey == BoringSSLKeylessPrivateKey.INSTANCE) {
|
||||
key = 0;
|
||||
} else {
|
||||
byte[] pemKey = toPemEncoded(privateKey);
|
||||
if (pemKey == null) {
|
||||
return null;
|
||||
}
|
||||
key = BoringSSL.EVP_PKEY_parse(pemKey, password);
|
||||
}
|
||||
long chain = BoringSSL.CRYPTO_BUFFER_stack_new(ssl, certs);
|
||||
engine.setLocalCertificateChain(certificates);
|
||||
|
||||
// Return and signal that the key and chain should be released as well.
|
||||
return new long[] { key, chain };
|
||||
}
|
||||
|
||||
private static byte[] toPemEncoded(PrivateKey key) {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
out.write(BEGIN_PRIVATE_KEY);
|
||||
out.write(Base64.getEncoder().encode(key.getEncoded()));
|
||||
out.write(END_PRIVATE_KEY);
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private String chooseClientAlias(QuicheQuicSslEngine engine,
|
||||
String[] keyTypes, X500Principal[] issuer) {
|
||||
return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
|
||||
}
|
||||
|
||||
private String chooseServerAlias(QuicheQuicSslEngine engine, String type) {
|
||||
return keyManager.chooseEngineServerAlias(type, null, engine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the supported key types for client certificates.
|
||||
*
|
||||
* @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
|
||||
* See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
|
||||
* @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
|
||||
* {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
|
||||
*/
|
||||
private static Set<String> supportedClientKeyTypes(byte[] clientCertificateTypes) {
|
||||
if (clientCertificateTypes == null) {
|
||||
// Try all of the supported key types.
|
||||
return SUPPORTED_KEY_TYPES;
|
||||
}
|
||||
Set<String> result = new HashSet<>(clientCertificateTypes.length);
|
||||
for (byte keyTypeCode : clientCertificateTypes) {
|
||||
String keyType = clientKeyType(keyTypeCode);
|
||||
if (keyType == null) {
|
||||
// Unsupported client key type -- ignore
|
||||
continue;
|
||||
}
|
||||
result.add(keyType);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String clientKeyType(byte clientCertificateType) {
|
||||
// See also https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
|
||||
switch (clientCertificateType) {
|
||||
case TLS_CT_RSA_SIGN:
|
||||
return KEY_TYPE_RSA; // RFC rsa_sign
|
||||
case TLS_CT_RSA_FIXED_DH:
|
||||
return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
|
||||
case TLS_CT_ECDSA_SIGN:
|
||||
return KEY_TYPE_EC; // RFC ecdsa_sign
|
||||
case TLS_CT_RSA_FIXED_ECDH:
|
||||
return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
|
||||
case TLS_CT_ECDSA_FIXED_ECDH:
|
||||
return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Execute {@link BoringSSLCertificateCallback#handle(long, byte[], byte[][], String[])}.
|
||||
*/
|
||||
final class BoringSSLCertificateCallbackTask extends BoringSSLTask {
|
||||
private final byte[] keyTypeBytes;
|
||||
private final byte[][] asn1DerEncodedPrincipals;
|
||||
private final String[] authMethods;
|
||||
private final BoringSSLCertificateCallback callback;
|
||||
|
||||
// Accessed via JNI.
|
||||
private long key;
|
||||
private long chain;
|
||||
|
||||
BoringSSLCertificateCallbackTask(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals, String[] authMethods,
|
||||
BoringSSLCertificateCallback callback) {
|
||||
// It is important that this constructor never throws. Be sure to not change this!
|
||||
super(ssl);
|
||||
// It's ok to not clone the arrays as we create these in JNI and not-reuse.
|
||||
this.keyTypeBytes = keyTypeBytes;
|
||||
this.asn1DerEncodedPrincipals = asn1DerEncodedPrincipals;
|
||||
this.authMethods = authMethods;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
// See https://www.openssl.org/docs/man1.0.2/man3/SSL_set_cert_cb.html.
|
||||
@Override
|
||||
protected void runTask(long ssl, TaskCallback taskCallback) {
|
||||
try {
|
||||
long[] result = callback.handle(ssl, keyTypeBytes, asn1DerEncodedPrincipals, authMethods);
|
||||
if (result == null) {
|
||||
taskCallback.onResult(ssl, 0);
|
||||
} else {
|
||||
this.key = result[0];
|
||||
this.chain = result[1];
|
||||
taskCallback.onResult(ssl, 1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Just catch the exception and return 0 to fail the handshake.
|
||||
// The problem is that rethrowing here is really "useless" as we will process it as part of an openssl
|
||||
// c callback which needs to return 0 for an error to abort the handshake.
|
||||
taskCallback.onResult(ssl, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroy() {
|
||||
if (key != 0) {
|
||||
BoringSSL.EVP_PKEY_free(key);
|
||||
key = 0;
|
||||
}
|
||||
if (chain != 0) {
|
||||
BoringSSL.CRYPTO_BUFFER_stack_free(chain);
|
||||
chain = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.handler.ssl.OpenSslCertificateException;
|
||||
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.CertificateRevokedException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
final class BoringSSLCertificateVerifyCallback {
|
||||
|
||||
private static final boolean TRY_USING_EXTENDED_TRUST_MANAGER;
|
||||
static {
|
||||
boolean tryUsingExtendedTrustManager;
|
||||
try {
|
||||
Class.forName(X509ExtendedTrustManager.class.getName());
|
||||
tryUsingExtendedTrustManager = true;
|
||||
} catch (Throwable cause) {
|
||||
tryUsingExtendedTrustManager = false;
|
||||
}
|
||||
TRY_USING_EXTENDED_TRUST_MANAGER = tryUsingExtendedTrustManager;
|
||||
}
|
||||
|
||||
private final QuicheQuicSslEngineMap engineMap;
|
||||
private final X509TrustManager manager;
|
||||
|
||||
BoringSSLCertificateVerifyCallback(QuicheQuicSslEngineMap engineMap, X509TrustManager manager) {
|
||||
this.engineMap = engineMap;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
int verify(long ssl, byte[][] x509, String authAlgorithm) {
|
||||
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
// May be null if it was destroyed in the meantime.
|
||||
return BoringSSL.X509_V_ERR_UNSPECIFIED;
|
||||
}
|
||||
|
||||
if (manager == null) {
|
||||
engineMap.remove(ssl);
|
||||
return BoringSSL.X509_V_ERR_UNSPECIFIED;
|
||||
}
|
||||
|
||||
X509Certificate[] peerCerts = BoringSSL.certificates(x509);
|
||||
try {
|
||||
if (engine.getUseClientMode()) {
|
||||
if (TRY_USING_EXTENDED_TRUST_MANAGER && manager instanceof X509ExtendedTrustManager) {
|
||||
((X509ExtendedTrustManager) manager).checkServerTrusted(peerCerts, authAlgorithm, engine);
|
||||
} else {
|
||||
manager.checkServerTrusted(peerCerts, authAlgorithm);
|
||||
}
|
||||
} else {
|
||||
if (TRY_USING_EXTENDED_TRUST_MANAGER && manager instanceof X509ExtendedTrustManager) {
|
||||
((X509ExtendedTrustManager) manager).checkClientTrusted(peerCerts, authAlgorithm, engine);
|
||||
} else {
|
||||
manager.checkClientTrusted(peerCerts, authAlgorithm);
|
||||
}
|
||||
}
|
||||
return BoringSSL.X509_V_OK;
|
||||
} catch (Throwable cause) {
|
||||
engineMap.remove(ssl);
|
||||
// Try to extract the correct error code that should be used.
|
||||
if (cause instanceof OpenSslCertificateException) {
|
||||
// This will never return a negative error code as its validated when constructing the
|
||||
// OpenSslCertificateException.
|
||||
return ((OpenSslCertificateException) cause).errorCode();
|
||||
}
|
||||
if (cause instanceof CertificateExpiredException) {
|
||||
return BoringSSL.X509_V_ERR_CERT_HAS_EXPIRED;
|
||||
}
|
||||
if (cause instanceof CertificateNotYetValidException) {
|
||||
return BoringSSL.X509_V_ERR_CERT_NOT_YET_VALID;
|
||||
}
|
||||
return translateToError(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static int translateToError(Throwable cause) {
|
||||
if (cause instanceof CertificateRevokedException) {
|
||||
return BoringSSL.X509_V_ERR_CERT_REVOKED;
|
||||
}
|
||||
|
||||
// The X509TrustManagerImpl uses a Validator which wraps a CertPathValidatorException into
|
||||
// an CertificateException. So we need to handle the wrapped CertPathValidatorException to be
|
||||
// able to send the correct alert.
|
||||
Throwable wrapped = cause.getCause();
|
||||
while (wrapped != null) {
|
||||
if (wrapped instanceof CertPathValidatorException) {
|
||||
CertPathValidatorException ex = (CertPathValidatorException) wrapped;
|
||||
CertPathValidatorException.Reason reason = ex.getReason();
|
||||
if (reason == CertPathValidatorException.BasicReason.EXPIRED) {
|
||||
return BoringSSL.X509_V_ERR_CERT_HAS_EXPIRED;
|
||||
}
|
||||
if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) {
|
||||
return BoringSSL.X509_V_ERR_CERT_NOT_YET_VALID;
|
||||
}
|
||||
if (reason == CertPathValidatorException.BasicReason.REVOKED) {
|
||||
return BoringSSL.X509_V_ERR_CERT_REVOKED;
|
||||
}
|
||||
}
|
||||
wrapped = wrapped.getCause();
|
||||
}
|
||||
return BoringSSL.X509_V_ERR_UNSPECIFIED;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
|
||||
/**
|
||||
* Execute {@link BoringSSLCertificateVerifyCallback#verify(long, byte[][], String)}.
|
||||
*/
|
||||
final class BoringSSLCertificateVerifyCallbackTask extends BoringSSLTask {
|
||||
private final byte[][] x509;
|
||||
private final String authAlgorithm;
|
||||
private final BoringSSLCertificateVerifyCallback verifier;
|
||||
|
||||
BoringSSLCertificateVerifyCallbackTask(long ssl, byte[][] x509, String authAlgorithm,
|
||||
BoringSSLCertificateVerifyCallback verifier) {
|
||||
super(ssl);
|
||||
this.x509 = x509;
|
||||
this.authAlgorithm = authAlgorithm;
|
||||
this.verifier = verifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runTask(long ssl, TaskCallback callback) {
|
||||
int result = verifier.verify(ssl, x509, authAlgorithm);
|
||||
callback.onResult(ssl, result);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
final class BoringSSLHandshakeCompleteCallback {
|
||||
|
||||
private final QuicheQuicSslEngineMap map;
|
||||
|
||||
BoringSSLHandshakeCompleteCallback(QuicheQuicSslEngineMap map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
void handshakeComplete(long ssl, byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain, long creationTime, long timeout, byte[] applicationProtocol,
|
||||
boolean sessionReused) {
|
||||
QuicheQuicSslEngine engine = map.get(ssl);
|
||||
if (engine != null) {
|
||||
engine.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime,
|
||||
timeout, applicationProtocol, sessionReused);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.KeyManagerFactorySpi;
|
||||
import javax.net.ssl.ManagerFactoryParameters;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.KeyStoreSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* {@link KeyManagerFactory} that can be used to support custom key signing via {@link BoringSSLAsyncPrivateKeyMethod}.
|
||||
*/
|
||||
public final class BoringSSLKeylessManagerFactory extends KeyManagerFactory {
|
||||
|
||||
final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
|
||||
|
||||
private BoringSSLKeylessManagerFactory(KeyManagerFactory keyManagerFactory,
|
||||
BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
|
||||
super(new KeylessManagerFactorySpi(keyManagerFactory),
|
||||
keyManagerFactory.getProvider(), keyManagerFactory.getAlgorithm());
|
||||
this.privateKeyMethod = requireNonNull(privateKeyMethod, "privateKeyMethod");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance.
|
||||
*
|
||||
* @param privateKeyMethod the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
|
||||
* @param chain the {@link File} that contains the {@link X509Certificate} chain.
|
||||
* @return a new factory instance.
|
||||
* @throws CertificateException on error.
|
||||
* @throws IOException on error.
|
||||
* @throws KeyStoreException on error.
|
||||
* @throws NoSuchAlgorithmException on error.
|
||||
* @throws UnrecoverableKeyException on error.
|
||||
*/
|
||||
public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod, File chain)
|
||||
throws CertificateException, IOException,
|
||||
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
return newKeyless(privateKeyMethod, Files.newInputStream(chain.toPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance.
|
||||
*
|
||||
* @param privateKeyMethod the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
|
||||
* @param chain the {@link InputStream} that contains the {@link X509Certificate} chain.
|
||||
* @return a new factory instance.
|
||||
* @throws CertificateException on error.
|
||||
* @throws IOException on error.
|
||||
* @throws KeyStoreException on error.
|
||||
* @throws NoSuchAlgorithmException on error.
|
||||
* @throws UnrecoverableKeyException on error.
|
||||
*/
|
||||
public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
|
||||
InputStream chain)
|
||||
throws CertificateException, IOException,
|
||||
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
return newKeyless(privateKeyMethod, QuicSslContext.toX509Certificates0(chain));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance.
|
||||
*
|
||||
* @param privateKeyMethod the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
|
||||
* @param certificateChain the {@link X509Certificate} chain.
|
||||
* @return a new factory instance.
|
||||
* @throws CertificateException on error.
|
||||
* @throws IOException on error.
|
||||
* @throws KeyStoreException on error.
|
||||
* @throws NoSuchAlgorithmException on error.
|
||||
* @throws UnrecoverableKeyException on error.
|
||||
*/
|
||||
public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
|
||||
X509Certificate... certificateChain)
|
||||
throws CertificateException, IOException,
|
||||
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
checkNotNull(certificateChain, "certificateChain");
|
||||
KeyStore store = new KeylessKeyStore(certificateChain.clone());
|
||||
store.load(null, null);
|
||||
BoringSSLKeylessManagerFactory factory = new BoringSSLKeylessManagerFactory(
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()), privateKeyMethod);
|
||||
factory.init(store, null);
|
||||
return factory;
|
||||
}
|
||||
|
||||
private static final class KeylessManagerFactorySpi extends KeyManagerFactorySpi {
|
||||
|
||||
private final KeyManagerFactory keyManagerFactory;
|
||||
|
||||
KeylessManagerFactorySpi(KeyManagerFactory keyManagerFactory) {
|
||||
this.keyManagerFactory = requireNonNull(keyManagerFactory, "keyManagerFactory");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(KeyStore ks, char[] password)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
keyManagerFactory.init(ks, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(ManagerFactoryParameters spec) {
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyManager[] engineGetKeyManagers() {
|
||||
return keyManagerFactory.getKeyManagers();
|
||||
}
|
||||
}
|
||||
private static final class KeylessKeyStore extends KeyStore {
|
||||
private static final String ALIAS = "key";
|
||||
private KeylessKeyStore(final X509Certificate[] certificateChain) {
|
||||
super(new KeyStoreSpi() {
|
||||
|
||||
private final Date creationDate = new Date();
|
||||
|
||||
@Override
|
||||
public Key engineGetKey(String alias, char[] password) {
|
||||
if (engineContainsAlias(alias)) {
|
||||
return BoringSSLKeylessPrivateKey.INSTANCE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] engineGetCertificateChain(String alias) {
|
||||
return engineContainsAlias(alias)? certificateChain.clone() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate engineGetCertificate(String alias) {
|
||||
return engineContainsAlias(alias)? certificateChain[0] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date engineGetCreationDate(String alias) {
|
||||
return engineContainsAlias(alias)? creationDate : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
|
||||
throws KeyStoreException {
|
||||
throw new KeyStoreException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
|
||||
throw new KeyStoreException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
|
||||
throw new KeyStoreException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||
throw new KeyStoreException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> engineAliases() {
|
||||
return Collections.enumeration(Collections.singleton(ALIAS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineContainsAlias(String alias) {
|
||||
return ALIAS.equals(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsKeyEntry(String alias) {
|
||||
return engineContainsAlias(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsCertificateEntry(String alias) {
|
||||
return engineContainsAlias(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String engineGetCertificateAlias(Certificate cert) {
|
||||
if (cert instanceof X509Certificate) {
|
||||
for (X509Certificate x509Certificate : certificateChain) {
|
||||
if (x509Certificate.equals(cert)) {
|
||||
return ALIAS;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineStore(OutputStream stream, char[] password) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineLoad(InputStream stream, char[] password) {
|
||||
if (stream != null && password != null) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}, null, "keyless");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
final class BoringSSLKeylessPrivateKey implements PrivateKey {
|
||||
|
||||
static final BoringSSLKeylessPrivateKey INSTANCE = new BoringSSLKeylessPrivateKey();
|
||||
|
||||
private BoringSSLKeylessPrivateKey() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "keyless";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat() {
|
||||
return "keyless";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return EmptyArrays.EMPTY_BYTES;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
|
||||
/**
|
||||
* Allow to log keys, logging keys are following
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||
*/
|
||||
public interface BoringSSLKeylog {
|
||||
|
||||
/**
|
||||
* Called when a key should be logged.
|
||||
*
|
||||
* @param engine the engine.
|
||||
* @param key the key.
|
||||
*/
|
||||
void logKey(SSLEngine engine, String key);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
final class BoringSSLKeylogCallback {
|
||||
|
||||
private final QuicheQuicSslEngineMap engineMap;
|
||||
private final BoringSSLKeylog keylog;
|
||||
|
||||
BoringSSLKeylogCallback(QuicheQuicSslEngineMap engineMap, BoringSSLKeylog keylog) {
|
||||
this.engineMap = engineMap;
|
||||
this.keylog = keylog;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
void logKey(long ssl, String key) {
|
||||
SSLEngine engine = engineMap.get(ssl);
|
||||
if (engine != null) {
|
||||
keylog.logKey(engine, key);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
final class BoringSSLLoggingKeylog implements BoringSSLKeylog {
|
||||
static final BoringSSLLoggingKeylog INSTANCE = new BoringSSLLoggingKeylog();
|
||||
|
||||
private BoringSSLLoggingKeylog() {
|
||||
}
|
||||
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(BoringSSLLoggingKeylog.class);
|
||||
|
||||
@Override
|
||||
public void logKey(SSLEngine engine, String key) {
|
||||
logger.debug(key);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
final class BoringSSLNativeStaticallyReferencedJniMethods {
|
||||
static native int ssl_verify_none();
|
||||
static native int ssl_verify_peer();
|
||||
static native int ssl_verify_fail_if_no_peer_cert();
|
||||
|
||||
static native int x509_v_ok();
|
||||
static native int x509_v_err_cert_has_expired();
|
||||
static native int x509_v_err_cert_not_yet_valid();
|
||||
static native int x509_v_err_cert_revoked();
|
||||
static native int x509_v_err_unspecified();
|
||||
static native int ssl_sign_rsa_pkcs_sha1();
|
||||
static native int ssl_sign_rsa_pkcs_sha256();
|
||||
static native int ssl_sign_rsa_pkcs_sha384();
|
||||
static native int ssl_sign_rsa_pkcs_sha512();
|
||||
static native int ssl_sign_ecdsa_pkcs_sha1();
|
||||
static native int ssl_sign_ecdsa_secp256r1_sha256();
|
||||
static native int ssl_sign_ecdsa_secp384r1_sha384();
|
||||
static native int ssl_sign_ecdsa_secp521r1_sha512();
|
||||
static native int ssl_sign_rsa_pss_rsae_sha256();
|
||||
static native int ssl_sign_rsa_pss_rsae_sha384();
|
||||
static native int ssl_sign_rsa_pss_rsae_sha512();
|
||||
static native int ssl_sign_ed25519();
|
||||
static native int ssl_sign_rsa_pkcs1_md5_sha1();
|
||||
|
||||
private BoringSSLNativeStaticallyReferencedJniMethods() { }
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2019 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Allows to customize private key signing / decrypt (when using RSA).
|
||||
*/
|
||||
interface BoringSSLPrivateKeyMethod {
|
||||
int SSL_SIGN_RSA_PKCS1_SHA1 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha1();
|
||||
int SSL_SIGN_RSA_PKCS1_SHA256 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha256();
|
||||
int SSL_SIGN_RSA_PKCS1_SHA384 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha384();
|
||||
int SSL_SIGN_RSA_PKCS1_SHA512 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha512();
|
||||
int SSL_SIGN_ECDSA_SHA1 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_pkcs_sha1();
|
||||
int SSL_SIGN_ECDSA_SECP256R1_SHA256 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_secp256r1_sha256();
|
||||
int SSL_SIGN_ECDSA_SECP384R1_SHA384 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_secp384r1_sha384();
|
||||
int SSL_SIGN_ECDSA_SECP521R1_SHA512 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_secp521r1_sha512();
|
||||
int SSL_SIGN_RSA_PSS_RSAE_SHA256 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pss_rsae_sha256();
|
||||
int SSL_SIGN_RSA_PSS_RSAE_SHA384 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pss_rsae_sha384();
|
||||
int SSL_SIGN_RSA_PSS_RSAE_SHA512 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pss_rsae_sha512();
|
||||
int SSL_SIGN_ED25519 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ed25519();
|
||||
int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs1_md5_sha1();
|
||||
|
||||
/**
|
||||
* Sign the input with given EC key and returns the signed bytes.
|
||||
*
|
||||
* @param ssl the SSL instance
|
||||
* @param signatureAlgorithm the algorithm to use for signing
|
||||
* @param input the input itself
|
||||
* @return the sign
|
||||
* @throws Exception thrown if an error accours while signing.
|
||||
*/
|
||||
void sign(long ssl, int signatureAlgorithm, byte[] input, BiConsumer<byte[], Throwable> callback);
|
||||
|
||||
/**
|
||||
* Decrypts the input with the given RSA key and returns the decrypted bytes.
|
||||
*
|
||||
* @param ssl the SSL instance
|
||||
* @param input the input which should be decrypted
|
||||
* @return the decrypted data
|
||||
* @throws Exception thrown if an error accours while decrypting.
|
||||
*/
|
||||
void decrypt(long ssl, byte[] input, BiConsumer<byte[], Throwable> callback);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
final class BoringSSLPrivateKeyMethodDecryptTask extends BoringSSLPrivateKeyMethodTask {
|
||||
private final byte[] input;
|
||||
|
||||
BoringSSLPrivateKeyMethodDecryptTask(long ssl, byte[] input, BoringSSLPrivateKeyMethod method) {
|
||||
super(ssl, method);
|
||||
// It's OK to not clone the arrays as we create these in JNI and not reuse.
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runMethod(long ssl, BoringSSLPrivateKeyMethod method, BiConsumer<byte[], Throwable> consumer) {
|
||||
method.decrypt(ssl, input, consumer);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
final class BoringSSLPrivateKeyMethodSignTask extends BoringSSLPrivateKeyMethodTask {
|
||||
private final int signatureAlgorithm;
|
||||
private final byte[] digest;
|
||||
|
||||
BoringSSLPrivateKeyMethodSignTask(long ssl, int signatureAlgorithm, byte[] digest, BoringSSLPrivateKeyMethod method) {
|
||||
super(ssl, method);
|
||||
this.signatureAlgorithm = signatureAlgorithm;
|
||||
// It's OK to not clone the arrays as we create these in JNI and not reuse.
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runMethod(long ssl, BoringSSLPrivateKeyMethod method, BiConsumer<byte[], Throwable> callback) {
|
||||
method.sign(ssl, signatureAlgorithm, digest, callback);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
abstract class BoringSSLPrivateKeyMethodTask extends BoringSSLTask {
|
||||
|
||||
private final BoringSSLPrivateKeyMethod method;
|
||||
|
||||
// Will be accessed via JNI.
|
||||
private byte[] resultBytes;
|
||||
|
||||
BoringSSLPrivateKeyMethodTask(long ssl, BoringSSLPrivateKeyMethod method) {
|
||||
super(ssl);
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected final void runTask(long ssl, TaskCallback callback) {
|
||||
runMethod(ssl, method, (result, error) -> {
|
||||
if (result == null || error != null) {
|
||||
callback.onResult(ssl, -1);
|
||||
} else {
|
||||
resultBytes = result;
|
||||
callback.onResult(ssl, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void runMethod(long ssl, BoringSSLPrivateKeyMethod method,
|
||||
BiConsumer<byte[], Throwable> callback);
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
final class BoringSSLSessionCallback {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(BoringSSLSessionCallback.class);
|
||||
private final QuicClientSessionCache sessionCache;
|
||||
private final QuicheQuicSslEngineMap engineMap;
|
||||
|
||||
BoringSSLSessionCallback(QuicheQuicSslEngineMap engineMap, QuicClientSessionCache sessionCache) {
|
||||
this.engineMap = engineMap;
|
||||
this.sessionCache = sessionCache;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
void newSession(long ssl, long creationTime, long timeout, byte[] session, boolean isSingleUse, byte[] peerParams) {
|
||||
if (sessionCache == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
logger.warn("engine is null ssl: {}", ssl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (peerParams == null) {
|
||||
peerParams = EmptyArrays.EMPTY_BYTES;
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("ssl: {}, session: {}, peerParams: {}", ssl, Arrays.toString(session),
|
||||
Arrays.toString(peerParams));
|
||||
}
|
||||
byte[] quicSession = toQuicheQuicSession(session, peerParams);
|
||||
if (quicSession != null) {
|
||||
logger.debug("save session host={}, port={}",
|
||||
engine.getSession().getPeerHost(), engine.getSession().getPeerPort());
|
||||
sessionCache.saveSession(engine.getSession().getPeerHost(), engine.getSession().getPeerPort(),
|
||||
TimeUnit.SECONDS.toMillis(creationTime), TimeUnit.SECONDS.toMillis(timeout),
|
||||
quicSession, isSingleUse);
|
||||
}
|
||||
}
|
||||
|
||||
// Mimic the encoding of quiche: https://github.com/cloudflare/quiche/blob/0.10.0/src/lib.rs#L1668
|
||||
private static byte[] toQuicheQuicSession(byte[] sslSession, byte[] peerParams) {
|
||||
if (sslSession != null && peerParams != null) {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(bos)) {
|
||||
dos.writeLong(sslSession.length);
|
||||
dos.write(sslSession);
|
||||
dos.writeLong(peerParams.length);
|
||||
dos.write(peerParams);
|
||||
return bos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
final class BoringSSLSessionTicketCallback {
|
||||
|
||||
// As we dont assume to have a lot of keys configured we will just use an array for now as a data store.
|
||||
private volatile byte[][] sessionKeys;
|
||||
|
||||
// Accessed via JNI.
|
||||
byte[] findSessionTicket(byte[] keyname) {
|
||||
byte[][] keys = this.sessionKeys;
|
||||
if (keys == null || keys.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (keyname == null) {
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
byte[] key = keys[i];
|
||||
if (PlatformDependent.equals(keyname, 0, key, 1, keyname.length)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void setSessionTicketKeys(SslSessionTicketKey[] keys) {
|
||||
if (keys != null && keys.length != 0) {
|
||||
byte[][] sessionKeys = new byte[keys.length][];
|
||||
for(int i = 0; i < keys.length; ++i) {
|
||||
SslSessionTicketKey key = keys[i];
|
||||
byte[] binaryKey = new byte[49];
|
||||
// We mark the first key as preferred by using 1 as byte marker
|
||||
binaryKey[0] = i == 0 ? (byte) 1 : (byte) 0;
|
||||
int dstCurPos = 1;
|
||||
System.arraycopy(key.name, 0, binaryKey, dstCurPos, 16);
|
||||
dstCurPos += 16;
|
||||
System.arraycopy(key.hmacKey, 0, binaryKey, dstCurPos, 16);
|
||||
dstCurPos += 16;
|
||||
System.arraycopy(key.aesKey, 0, binaryKey, dstCurPos, 16);
|
||||
sessionKeys[i] = binaryKey;
|
||||
}
|
||||
this.sessionKeys = sessionKeys;
|
||||
} else {
|
||||
sessionKeys = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2022 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* A SSL related task that will be returned by {@link BoringSSL#SSL_getTask(long)}.
|
||||
*/
|
||||
abstract class BoringSSLTask implements Runnable {
|
||||
private final long ssl;
|
||||
protected boolean didRun;
|
||||
|
||||
// These fields are accessed via JNI.
|
||||
private int returnValue;
|
||||
private volatile boolean complete;
|
||||
|
||||
protected BoringSSLTask(long ssl) {
|
||||
// It is important that this constructor never throws. Be sure to not change this!
|
||||
this.ssl = ssl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
if (!didRun) {
|
||||
didRun = true;
|
||||
runTask(ssl, (long ssl, int result) -> {
|
||||
returnValue = result;
|
||||
complete = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once the task should be destroyed.
|
||||
*/
|
||||
protected void destroy() {
|
||||
// Noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task and return the return value that should be passed back to OpenSSL.
|
||||
*/
|
||||
protected abstract void runTask(long ssl, TaskCallback callback);
|
||||
|
||||
interface TaskCallback {
|
||||
void onResult(long ssl, int result);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.Mapping;
|
||||
|
||||
final class BoringSSLTlsextServernameCallback {
|
||||
|
||||
private final QuicheQuicSslEngineMap engineMap;
|
||||
private final Mapping<? super String, ? extends QuicSslContext> mapping;
|
||||
|
||||
BoringSSLTlsextServernameCallback(QuicheQuicSslEngineMap engineMap,
|
||||
Mapping<? super String, ? extends QuicSslContext> mapping) {
|
||||
this.engineMap = engineMap;
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
long selectCtx(long ssl, String serverName) {
|
||||
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
// May be null if it was destroyed in the meantime.
|
||||
return -1;
|
||||
}
|
||||
|
||||
QuicSslContext context = mapping.map(serverName);
|
||||
if (context == null) {
|
||||
return -1;
|
||||
}
|
||||
return engine.moveTo(serverName, (QuicheQuicSslContext) context);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.DefaultByteBufHolder;
|
||||
|
||||
public final class DefaultQuicStreamFrame extends DefaultByteBufHolder implements QuicStreamFrame {
|
||||
|
||||
private final boolean fin;
|
||||
|
||||
public DefaultQuicStreamFrame(ByteBuf data, boolean fin) {
|
||||
super(data);
|
||||
this.fin = fin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFin() {
|
||||
return fin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame copy() {
|
||||
return new DefaultQuicStreamFrame(content().copy(), fin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame duplicate() {
|
||||
return new DefaultQuicStreamFrame(content().duplicate(), fin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame retainedDuplicate() {
|
||||
return new DefaultQuicStreamFrame(content().retainedDuplicate(), fin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame replace(ByteBuf content) {
|
||||
return new DefaultQuicStreamFrame(content, fin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame retain() {
|
||||
super.retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame retain(int increment) {
|
||||
super.retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame touch() {
|
||||
super.touch();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame touch(Object hint) {
|
||||
super.touch(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultQuicStreamFrame{" +
|
||||
"fin=" + fin +
|
||||
", content=" + contentToString() +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DefaultQuicStreamFrame that = (DefaultQuicStreamFrame) o;
|
||||
|
||||
if (fin != that.fin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + (fin ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
|
||||
final class DirectIoByteBufAllocator implements ByteBufAllocator {
|
||||
|
||||
private final ByteBufAllocator wrapped;
|
||||
|
||||
DirectIoByteBufAllocator(ByteBufAllocator wrapped) {
|
||||
if (wrapped instanceof DirectIoByteBufAllocator) {
|
||||
wrapped = ((DirectIoByteBufAllocator) wrapped).wrapped();
|
||||
}
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
ByteBufAllocator wrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf buffer() {
|
||||
return wrapped.buffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf buffer(int initialCapacity) {
|
||||
return wrapped.buffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf buffer(int initialCapacity, int maxCapacity) {
|
||||
return wrapped.buffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer() {
|
||||
return directBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer(int initialCapacity) {
|
||||
return directBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) {
|
||||
return directBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf heapBuffer() {
|
||||
return wrapped.heapBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf heapBuffer(int initialCapacity) {
|
||||
return wrapped.heapBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
|
||||
return wrapped.heapBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf directBuffer() {
|
||||
return wrapped.directBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf directBuffer(int initialCapacity) {
|
||||
return wrapped.directBuffer(initialCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
|
||||
return wrapped.directBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeBuffer() {
|
||||
return wrapped.compositeBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeBuffer(int maxNumComponents) {
|
||||
return wrapped.compositeBuffer(maxNumComponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeHeapBuffer() {
|
||||
return wrapped.compositeHeapBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) {
|
||||
return wrapped.compositeHeapBuffer(maxNumComponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeDirectBuffer() {
|
||||
return wrapped.compositeDirectBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) {
|
||||
return wrapped.compositeDirectBuffer(maxNumComponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectBufferPooled() {
|
||||
return wrapped.isDirectBufferPooled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
|
||||
return wrapped.calculateNewCapacity(minNewCapacity, maxCapacity);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.epoll.SegmentedDatagramPacket;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Class that provides utility methods to setup {@code QUIC} when using the {@code EPOLL} transport.
|
||||
*/
|
||||
public final class EpollQuicUtils {
|
||||
|
||||
private EpollQuicUtils() { }
|
||||
|
||||
/**
|
||||
* Return a new {@link SegmentedDatagramPacketAllocator} that can be used while using
|
||||
* {@link io.netty.channel.epoll.EpollDatagramChannel}.
|
||||
*
|
||||
* @param maxNumSegments the maximum number of segments that we try to send in one packet.
|
||||
* @return a allocator.
|
||||
*/
|
||||
public static SegmentedDatagramPacketAllocator newSegmentedAllocator(int maxNumSegments) {
|
||||
ObjectUtil.checkInRange(maxNumSegments, 1, 64, "maxNumSegments");
|
||||
if (SegmentedDatagramPacket.isSupported()) {
|
||||
return new EpollSegmentedDatagramPacketAllocator(maxNumSegments);
|
||||
}
|
||||
return SegmentedDatagramPacketAllocator.NONE;
|
||||
}
|
||||
|
||||
private static final class EpollSegmentedDatagramPacketAllocator implements SegmentedDatagramPacketAllocator {
|
||||
|
||||
private final int maxNumSegments;
|
||||
|
||||
EpollSegmentedDatagramPacketAllocator(int maxNumSegments) {
|
||||
this.maxNumSegments = maxNumSegments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxNumSegments() {
|
||||
return maxNumSegments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatagramPacket newPacket(ByteBuf buffer, int segmentSize, InetSocketAddress remoteAddress) {
|
||||
return new io.netty.channel.unix.SegmentedDatagramPacket(buffer, segmentSize, remoteAddress);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* Allows to configure a strategy for when flushes should be happening.
|
||||
*/
|
||||
public interface FlushStrategy {
|
||||
|
||||
/**
|
||||
* Default {@link FlushStrategy} implementation.
|
||||
*/
|
||||
FlushStrategy DEFAULT = afterNumBytes(20 * Quic.MAX_DATAGRAM_SIZE);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a flush should happen now, {@code false} otherwise.
|
||||
*
|
||||
* @param numPackets the number of packets that were written since the last flush.
|
||||
* @param numBytes the number of bytes that were written since the last flush.
|
||||
* @return {@code true} if a flush should be done now, {@code false} otherwise.
|
||||
*/
|
||||
boolean shouldFlushNow(int numPackets, int numBytes);
|
||||
|
||||
/**
|
||||
* Implementation that flushes after a number of bytes.
|
||||
*
|
||||
* @param bytes the number of bytes after which we should issue a flush.
|
||||
* @return the {@link FlushStrategy}.
|
||||
*/
|
||||
static FlushStrategy afterNumBytes(int bytes) {
|
||||
ObjectUtil.checkPositive(bytes, "bytes");
|
||||
return (numPackets, numBytes) -> numBytes > bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation that flushes after a number of packets.
|
||||
*
|
||||
* @param packets the number of packets after which we should issue a flush.
|
||||
* @return the {@link FlushStrategy}.
|
||||
*/
|
||||
static FlushStrategy afterNumPackets(int packets) {
|
||||
ObjectUtil.checkPositive(packets, "packets");
|
||||
return (numPackets, numBytes) -> numPackets > packets;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
final class Hmac {
|
||||
|
||||
private static final FastThreadLocal<Mac> MACS = new FastThreadLocal<Mac>() {
|
||||
@Override
|
||||
protected Mac initialValue() {
|
||||
return newMac();
|
||||
}
|
||||
};
|
||||
|
||||
private static final String ALGORITM = "HmacSHA256";
|
||||
private static final byte[] randomKey = new byte[16];
|
||||
|
||||
static {
|
||||
new SecureRandom().nextBytes(randomKey);
|
||||
}
|
||||
|
||||
private static Mac newMac() {
|
||||
try {
|
||||
SecretKeySpec keySpec = new SecretKeySpec(randomKey, ALGORITM);
|
||||
Mac mac = Mac.getInstance(ALGORITM);
|
||||
mac.init(keySpec);
|
||||
return mac;
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
static ByteBuffer sign(ByteBuffer input, int outLength) {
|
||||
Mac mac = MACS.get();
|
||||
mac.reset();
|
||||
mac.update(input);
|
||||
byte[] signBytes = mac.doFinal();
|
||||
if (signBytes.length != outLength) {
|
||||
signBytes = Arrays.copyOf(signBytes, outLength);
|
||||
}
|
||||
return ByteBuffer.wrap(signBytes);
|
||||
}
|
||||
|
||||
private Hmac() { }
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* A {@link QuicConnectionIdGenerator} which creates new connection id by signing the given input
|
||||
* using hmac algorithms.
|
||||
*/
|
||||
final class HmacSignQuicConnectionIdGenerator implements QuicConnectionIdGenerator {
|
||||
static final QuicConnectionIdGenerator INSTANCE = new HmacSignQuicConnectionIdGenerator();
|
||||
|
||||
private HmacSignQuicConnectionIdGenerator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer newId(int length) {
|
||||
throw new UnsupportedOperationException(
|
||||
"HmacSignQuicConnectionIdGenerator should always have an input to sign with");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer newId(ByteBuffer buffer, int length) {
|
||||
ObjectUtil.checkNotNull(buffer, "buffer");
|
||||
ObjectUtil.checkPositive(buffer.remaining(), "buffer");
|
||||
ObjectUtil.checkInRange(length, 0, maxConnectionIdLength(), "length");
|
||||
|
||||
return Hmac.sign(buffer, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxConnectionIdLength() {
|
||||
return Quiche.QUICHE_MAX_CONN_ID_LEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdempotent() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A {@link QuicResetTokenGenerator} which creates new reset token by using the connection id by signing the given input
|
||||
* using <a href="https://www.ietf.org/archive/id/draft-ietf-quic-transport-29.html#section-10.4.2">HMAC algorithms</a>.
|
||||
*/
|
||||
final class HmacSignQuicResetTokenGenerator implements QuicResetTokenGenerator {
|
||||
static final QuicResetTokenGenerator INSTANCE = new HmacSignQuicResetTokenGenerator();
|
||||
|
||||
private HmacSignQuicResetTokenGenerator() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBuffer newResetToken(ByteBuffer cid) {
|
||||
ObjectUtil.checkNotNull(cid, "cid");
|
||||
ObjectUtil.checkPositive(cid.remaining(), "cid");
|
||||
return Hmac.sign(cid, Quic.RESET_TOKEN_LEN);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.NetUtil;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Insecure {@link QuicTokenHandler} which only does basic token generation / validation without any
|
||||
* crypto.
|
||||
*
|
||||
* <strong>This shouldn't be used in production.</strong>
|
||||
*/
|
||||
public final class InsecureQuicTokenHandler implements QuicTokenHandler {
|
||||
|
||||
private static final String SERVER_NAME = "netty";
|
||||
private static final byte[] SERVER_NAME_BYTES = SERVER_NAME.getBytes(CharsetUtil.US_ASCII);
|
||||
private static final ByteBuf SERVER_NAME_BUFFER = Unpooled.unreleasableBuffer(
|
||||
Unpooled.wrappedBuffer(SERVER_NAME_BYTES)).asReadOnly();
|
||||
|
||||
// Just package-private for unit tests
|
||||
static final int MAX_TOKEN_LEN = Quiche.QUICHE_MAX_CONN_ID_LEN +
|
||||
NetUtil.LOCALHOST6.getAddress().length + SERVER_NAME_BYTES.length;
|
||||
|
||||
private InsecureQuicTokenHandler() {
|
||||
Quic.ensureAvailability();
|
||||
}
|
||||
|
||||
public static final InsecureQuicTokenHandler INSTANCE = new InsecureQuicTokenHandler();
|
||||
|
||||
@Override
|
||||
public boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address) {
|
||||
byte[] addr = address.getAddress().getAddress();
|
||||
out.writeBytes(SERVER_NAME_BYTES)
|
||||
.writeBytes(addr)
|
||||
.writeBytes(dcid, dcid.readerIndex(), dcid.readableBytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int validateToken(ByteBuf token, InetSocketAddress address) {
|
||||
final byte[] addr = address.getAddress().getAddress();
|
||||
|
||||
int minLength = SERVER_NAME_BYTES.length + address.getAddress().getAddress().length;
|
||||
if (token.readableBytes() <= SERVER_NAME_BYTES.length + addr.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!SERVER_NAME_BUFFER.equals(token.slice(0, SERVER_NAME_BYTES.length))) {
|
||||
return -1;
|
||||
}
|
||||
ByteBuf addressBuffer = Unpooled.wrappedBuffer(addr);
|
||||
try {
|
||||
if (!addressBuffer.equals(token.slice(SERVER_NAME_BYTES.length, addr.length))) {
|
||||
return -1;
|
||||
}
|
||||
} finally {
|
||||
addressBuffer.release();
|
||||
}
|
||||
return minLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxTokenLength() {
|
||||
return MAX_TOKEN_LEN;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* {@link QuicTokenHandler} which will disable token generation / validation completely.
|
||||
* This will reduce the round-trip for QUIC connection migration, but will also weaking the
|
||||
* security during connection establishment.
|
||||
*/
|
||||
final class NoQuicTokenHandler implements QuicTokenHandler {
|
||||
|
||||
public final static QuicTokenHandler INSTANCE = new NoQuicTokenHandler();
|
||||
|
||||
private NoQuicTokenHandler() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int validateToken(ByteBuf token, InetSocketAddress address) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxTokenLength() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Configuration used for setup
|
||||
* <a href="https://quiclog.github.io/internet-drafts/draft-marx-qlog-main-schema.html">qlog</a>.
|
||||
*/
|
||||
public final class QLogConfiguration {
|
||||
|
||||
private final String path;
|
||||
private final String logTitle;
|
||||
private final String logDescription;
|
||||
|
||||
/**
|
||||
* Create a new configuration.
|
||||
*
|
||||
* @param path the path to the log file to use. This file must not exist yet. If the path is a
|
||||
* directory the filename will be generated
|
||||
* @param logTitle the title to use when logging.
|
||||
* @param logDescription the description to use when logging.
|
||||
*/
|
||||
public QLogConfiguration(String path, String logTitle, String logDescription) {
|
||||
this.path = Objects.requireNonNull(path, "path");
|
||||
this.logTitle = Objects.requireNonNull(logTitle, "logTitle");
|
||||
this.logDescription = Objects.requireNonNull(logDescription, "logDescription");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path to the log file.
|
||||
*
|
||||
* @return the path.
|
||||
*/
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title.
|
||||
*
|
||||
* @return the title.
|
||||
*/
|
||||
public String logTitle() {
|
||||
return logTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description.
|
||||
*
|
||||
* @return the description.
|
||||
*/
|
||||
public String logDescription() {
|
||||
return logDescription;
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Quic {
|
||||
@SuppressWarnings("unchecked")
|
||||
static final Map.Entry<ChannelOption<?>, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0];
|
||||
@SuppressWarnings("unchecked")
|
||||
static final Map.Entry<AttributeKey<?>, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0];
|
||||
|
||||
static final int MAX_DATAGRAM_SIZE = 1350;
|
||||
|
||||
static final int RESET_TOKEN_LEN = 16;
|
||||
|
||||
private static final Throwable UNAVAILABILITY_CAUSE;
|
||||
|
||||
static {
|
||||
Throwable cause = null;
|
||||
|
||||
try {
|
||||
String version = Quiche.quiche_version();
|
||||
assert version != null;
|
||||
} catch (Throwable error) {
|
||||
cause = error;
|
||||
}
|
||||
|
||||
UNAVAILABILITY_CAUSE = cause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the given QUIC version is supported.
|
||||
*
|
||||
* @param version the version.
|
||||
* @return {@code true} if supported, {@code false} otherwise.
|
||||
*/
|
||||
public static boolean isVersionSupported(int version) {
|
||||
return isAvailable() && Quiche.quiche_version_is_supported(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the QUIC implementation is usable on the running platform is available.
|
||||
*
|
||||
* @return {@code true} if this QUIC implementation can be used on the current platform, {@code false} otherwise.
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return UNAVAILABILITY_CAUSE == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that QUIC implementation is usable on the running platform is available.
|
||||
*
|
||||
* @throws UnsatisfiedLinkError if unavailable
|
||||
*/
|
||||
public static void ensureAvailability() {
|
||||
if (UNAVAILABILITY_CAUSE != null) {
|
||||
throw (Error) new UnsatisfiedLinkError(
|
||||
"failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cause of unavailability.
|
||||
*
|
||||
* @return the cause if unavailable. {@code null} if available.
|
||||
*/
|
||||
public static Throwable unavailabilityCause() {
|
||||
return UNAVAILABILITY_CAUSE;
|
||||
}
|
||||
|
||||
static Map.Entry<ChannelOption<?>, Object>[] toOptionsArray(Map<ChannelOption<?>, Object> opts) {
|
||||
return new HashMap<>(opts).entrySet().toArray(EMPTY_OPTION_ARRAY);
|
||||
}
|
||||
|
||||
static Map.Entry<AttributeKey<?>, Object>[] toAttributesArray(Map<AttributeKey<?>, Object> attributes) {
|
||||
return new LinkedHashMap<>(attributes).entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
|
||||
}
|
||||
|
||||
private static void setAttributes(Channel channel, Map.Entry<AttributeKey<?>, Object>[] attrs) {
|
||||
for (Map.Entry<AttributeKey<?>, Object> e: attrs) {
|
||||
@SuppressWarnings("unchecked")
|
||||
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
|
||||
channel.attr(key).set(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setChannelOptions(
|
||||
Channel channel, Map.Entry<ChannelOption<?>, Object>[] options, InternalLogger logger) {
|
||||
for (Map.Entry<ChannelOption<?>, Object> e: options) {
|
||||
setChannelOption(channel, e.getKey(), e.getValue(), logger);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void setChannelOption(
|
||||
Channel channel, ChannelOption<?> option, Object value, InternalLogger logger) {
|
||||
try {
|
||||
if (!channel.config().setOption((ChannelOption<Object>) option, value)) {
|
||||
logger.warn("Unknown channel option '{}' for channel '{}'", option, channel);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn(
|
||||
"Failed to set channel option '{}' with value '{}' for channel '{}'", option, value, channel, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*/
|
||||
static <T> void updateOptions(Map<ChannelOption<?>, Object> options, ChannelOption<T> option, T value) {
|
||||
ObjectUtil.checkNotNull(option, "option");
|
||||
if (value == null) {
|
||||
options.remove(option);
|
||||
} else {
|
||||
options.put(option, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*/
|
||||
static <T> void updateAttributes(Map<AttributeKey<?>, Object> attributes, AttributeKey<T> key, T value) {
|
||||
ObjectUtil.checkNotNull(key, "key");
|
||||
if (value == null) {
|
||||
attributes.remove(key);
|
||||
} else {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void setupChannel(Channel ch, Map.Entry<ChannelOption<?>, Object>[] options,
|
||||
Map.Entry<AttributeKey<?>, Object>[] attrs, ChannelHandler handler,
|
||||
InternalLogger logger) {
|
||||
Quic.setChannelOptions(ch, options, logger);
|
||||
Quic.setAttributes(ch, attrs);
|
||||
if (handler != null) {
|
||||
ch.pipeline().addLast(handler);
|
||||
}
|
||||
}
|
||||
|
||||
private Quic() { }
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelProgressivePromise;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* A QUIC {@link Channel}.
|
||||
*/
|
||||
public interface QuicChannel extends Channel {
|
||||
|
||||
@Override
|
||||
default ChannelFuture bind(SocketAddress localAddress) {
|
||||
return pipeline().bind(localAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress) {
|
||||
return pipeline().connect(remoteAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||
return pipeline().connect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture disconnect() {
|
||||
return pipeline().disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture close() {
|
||||
return pipeline().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture deregister() {
|
||||
return pipeline().deregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||
return pipeline().bind(localAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
|
||||
return pipeline().connect(remoteAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
||||
return pipeline().connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture disconnect(ChannelPromise promise) {
|
||||
return pipeline().disconnect(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture close(ChannelPromise promise) {
|
||||
return pipeline().close(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture deregister(ChannelPromise promise) {
|
||||
return pipeline().deregister(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture write(Object msg) {
|
||||
return pipeline().write(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture write(Object msg, ChannelPromise promise) {
|
||||
return pipeline().write(msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
||||
return pipeline().writeAndFlush(msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture writeAndFlush(Object msg) {
|
||||
return pipeline().writeAndFlush(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelPromise newPromise() {
|
||||
return pipeline().newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelProgressivePromise newProgressivePromise() {
|
||||
return pipeline().newProgressivePromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture newSucceededFuture() {
|
||||
return pipeline().newSucceededFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture newFailedFuture(Throwable cause) {
|
||||
return pipeline().newFailedFuture(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelPromise voidPromise() {
|
||||
return pipeline().voidPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
QuicChannel read();
|
||||
|
||||
@Override
|
||||
QuicChannel flush();
|
||||
|
||||
/**
|
||||
* Returns the configuration of this channel.
|
||||
*/
|
||||
@Override
|
||||
QuicChannelConfig config();
|
||||
|
||||
/**
|
||||
* Returns the used {@link SSLEngine} or {@code null} if none is used (yet).
|
||||
*
|
||||
* @return the engine.
|
||||
*/
|
||||
SSLEngine sslEngine();
|
||||
|
||||
/**
|
||||
* Returns the number of streams that can be created before stream creation will fail
|
||||
* with {@link QuicError#STREAM_LIMIT} error.
|
||||
*
|
||||
* @param type the stream type.
|
||||
* @return the number of streams left.
|
||||
*/
|
||||
long peerAllowedStreams(QuicStreamType type);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the connection was closed because of idle timeout.
|
||||
*
|
||||
* @return {@code true} if the connection was closed because of idle timeout, {@code false}.
|
||||
*/
|
||||
boolean isTimedOut();
|
||||
|
||||
/**
|
||||
* Returns the {@link QuicTransportParameters} of the peer once received, or {@code null} if not known yet.
|
||||
*
|
||||
* @return peerTransportParams.
|
||||
*/
|
||||
QuicTransportParameters peerTransportParameters();
|
||||
|
||||
/**
|
||||
* Creates a stream that is using this {@link QuicChannel} and notifies the {@link Future} once done.
|
||||
* The {@link ChannelHandler} (if not {@code null}) is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicStreamChannel} automatically.
|
||||
*
|
||||
* @param type the {@link QuicStreamType} of the {@link QuicStreamChannel}.
|
||||
* @param handler the {@link ChannelHandler} that will be added to the {@link QuicStreamChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline} during the stream creation.
|
||||
* @return the {@link Future} that will be notified once the operation completes.
|
||||
*/
|
||||
default Future<QuicStreamChannel> createStream(QuicStreamType type, ChannelHandler handler) {
|
||||
return createStream(type, handler, eventLoop().newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stream that is using this {@link QuicChannel} and notifies the {@link Promise} once done.
|
||||
* The {@link ChannelHandler} (if not {@code null}) is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicStreamChannel} automatically.
|
||||
*
|
||||
* @param type the {@link QuicStreamType} of the {@link QuicStreamChannel}.
|
||||
* @param handler the {@link ChannelHandler} that will be added to the {@link QuicStreamChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline} during the stream creation.
|
||||
* @param promise the {@link ChannelPromise} that will be notified once the operation completes.
|
||||
* @return the {@link Future} that will be notified once the operation completes.
|
||||
*/
|
||||
Future<QuicStreamChannel> createStream(QuicStreamType type, ChannelHandler handler,
|
||||
Promise<QuicStreamChannel> promise);
|
||||
|
||||
/**
|
||||
* Returns a new {@link QuicStreamChannelBootstrap} which makes it easy to bootstrap new {@link QuicStreamChannel}s
|
||||
* with custom options and attributes. For simpler use-cases you may want to consider using
|
||||
* {@link #createStream(QuicStreamType, ChannelHandler)} or
|
||||
* {@link #createStream(QuicStreamType, ChannelHandler, Promise)} directly.
|
||||
*
|
||||
* @return {@link QuicStreamChannelBootstrap} that can be used to bootstrap a {@link QuicStreamChannel}.
|
||||
*/
|
||||
default QuicStreamChannelBootstrap newStreamBootstrap() {
|
||||
return new QuicStreamChannelBootstrap(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the {@link QuicChannel}
|
||||
*
|
||||
* @param applicationClose {@code true} if an application close should be used,
|
||||
* {@code false} if a normal close should be used.
|
||||
* @param error the application error number, or {@code 0} if no special error should be signaled.
|
||||
* @param reason the reason for the closure (which may be an empty {@link ByteBuf}.
|
||||
* @return the future that is notified.
|
||||
*/
|
||||
default ChannelFuture close(boolean applicationClose, int error, ByteBuf reason) {
|
||||
return close(applicationClose, error, reason, newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the {@link QuicChannel}
|
||||
*
|
||||
* @param applicationClose {@code true} if an application close should be used,
|
||||
* {@code false} if a normal close should be used.
|
||||
* @param error the application error number, or {@code 0} if no special error should be signaled.
|
||||
* @param reason the reason for the closure (which may be an empty {@link ByteBuf}.
|
||||
* @param promise the {@link ChannelPromise} that will be notified.
|
||||
* @return the future that is notified.
|
||||
*/
|
||||
ChannelFuture close(boolean applicationClose, int error, ByteBuf reason, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Collects statistics about the connection and notifies the {@link Future} once done.
|
||||
*
|
||||
* @return the {@link Future} that is notified once the stats were collected.
|
||||
*/
|
||||
default Future<QuicConnectionStats> collectStats() {
|
||||
return collectStats(eventLoop().newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects statistics about the connection and notifies the {@link Promise} once done.
|
||||
*
|
||||
* @param promise the {@link ChannelPromise} that is notified once the stats were collected.
|
||||
* @return the {@link Future} that is notified once the stats were collected.
|
||||
*/
|
||||
Future<QuicConnectionStats> collectStats(Promise<QuicConnectionStats> promise);
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuicChannelBootstrap} that can be used to create and connect new {@link QuicChannel}s to
|
||||
* endpoints using the given {@link Channel} as transport layer.
|
||||
*
|
||||
* @param channel the {@link Channel} that is used as transport layer.
|
||||
* @return {@link QuicChannelBootstrap} that can be used to bootstrap a client side {@link QuicChannel}.
|
||||
*/
|
||||
static QuicChannelBootstrap newBootstrap(Channel channel) {
|
||||
return new QuicChannelBootstrap(channel);
|
||||
}
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bootstrap that helps to bootstrap {@link QuicChannel}s and connecting these to remote peers.
|
||||
*/
|
||||
public final class QuicChannelBootstrap {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(QuicChannelBootstrap.class);
|
||||
|
||||
private final Channel parent;
|
||||
// The order in which ChannelOptions are applied is important they may depend on each other for validation
|
||||
// purposes.
|
||||
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
|
||||
private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
|
||||
private final Map<ChannelOption<?>, Object> streamOptions = new LinkedHashMap<>();
|
||||
private final Map<AttributeKey<?>, Object> streamAttrs = new HashMap<>();
|
||||
|
||||
private SocketAddress local;
|
||||
private SocketAddress remote;
|
||||
private QuicConnectionAddress connectionAddress = QuicConnectionAddress.EPHEMERAL;
|
||||
private ChannelHandler handler;
|
||||
private ChannelHandler streamHandler;
|
||||
|
||||
/**
|
||||
* Creates a new instance which uses the given {@link Channel} to bootstrap the {@link QuicChannel}.
|
||||
* This {@link io.netty.channel.ChannelPipeline} of the {@link Channel} needs to have the quic codec in the
|
||||
* pipeline.
|
||||
*
|
||||
* @param parent the {@link Channel} that is used as the transport layer.
|
||||
*/
|
||||
QuicChannelBootstrap(Channel parent) {
|
||||
Quic.ensureAvailability();
|
||||
this.parent = ObjectUtil.checkNotNull(parent, "parent");
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicChannel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*
|
||||
* @param option the {@link ChannelOption} to apply to the {@link QuicChannel}.
|
||||
* @param value the value of the option.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicChannelBootstrap option(ChannelOption<T> option, T value) {
|
||||
Quic.updateOptions(options, option, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link QuicChannel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*
|
||||
* @param key the {@link AttributeKey} to apply to the {@link QuicChannel}.
|
||||
* @param value the value of the attribute.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicChannelBootstrap attr(AttributeKey<T> key, T value) {
|
||||
Quic.updateAttributes(attrs, key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicChannel} once created.
|
||||
*
|
||||
* @param handler the {@link ChannelHandler} that is added to the {@link QuicChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline}.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicChannelBootstrap handler(ChannelHandler handler) {
|
||||
this.handler = ObjectUtil.checkNotNull(handler, "handler");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*
|
||||
* @param option the {@link ChannelOption} to apply to the {@link QuicStreamChannel}s.
|
||||
* @param value the value of the option.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicChannelBootstrap streamOption(ChannelOption<T> option, T value) {
|
||||
Quic.updateOptions(streamOptions, option, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*
|
||||
* @param key the {@link AttributeKey} to apply to the {@link QuicStreamChannel}s.
|
||||
* @param value the value of the attribute.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicChannelBootstrap streamAttr(AttributeKey<T> key, T value) {
|
||||
Quic.updateAttributes(streamAttrs, key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicStreamChannel} once created.
|
||||
*
|
||||
* @param streamHandler the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline}.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicChannelBootstrap streamHandler(ChannelHandler streamHandler) {
|
||||
this.streamHandler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the local address.
|
||||
*
|
||||
* @param local the {@link SocketAddress} of the local peer.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicChannelBootstrap localAddress(SocketAddress local) {
|
||||
this.local = ObjectUtil.checkNotNull(local, "local");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the remote address of the host to talk to.
|
||||
*
|
||||
* @param remote the {@link SocketAddress} of the remote peer.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicChannelBootstrap remoteAddress(SocketAddress remote) {
|
||||
this.remote = ObjectUtil.checkNotNull(remote, "remote");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link QuicConnectionAddress} to use. If none is specified a random address is generated on your
|
||||
* behalf.
|
||||
*
|
||||
* @param connectionAddress the {@link QuicConnectionAddress} to use.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicChannelBootstrap connectionAddress(QuicConnectionAddress connectionAddress) {
|
||||
this.connectionAddress = ObjectUtil.checkNotNull(connectionAddress, "connectionAddress");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a {@link QuicChannel} to the remote peer and notifies the future once done.
|
||||
*
|
||||
* @return {@link Future} which is notified once the operation completes.
|
||||
*/
|
||||
public Future<QuicChannel> connect() {
|
||||
return connect(parent.eventLoop().newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a {@link QuicChannel} to the remote peer and notifies the promise once done.
|
||||
*
|
||||
* @param promise the {@link Promise} which is notified once the operations completes.
|
||||
* @return {@link Future} which is notified once the operation completes.
|
||||
|
||||
*/
|
||||
public Future<QuicChannel> connect(Promise<QuicChannel> promise) {
|
||||
if (handler == null && streamHandler == null) {
|
||||
throw new IllegalStateException("handler and streamHandler not set");
|
||||
}
|
||||
SocketAddress local = this.local;
|
||||
if (local == null) {
|
||||
local = parent.localAddress();
|
||||
}
|
||||
if (local == null) {
|
||||
local = new InetSocketAddress(0);
|
||||
}
|
||||
|
||||
SocketAddress remote = this.remote;
|
||||
if (remote == null) {
|
||||
remote = parent.remoteAddress();
|
||||
}
|
||||
if (remote == null) {
|
||||
throw new IllegalStateException("remote not set");
|
||||
}
|
||||
|
||||
final QuicConnectionAddress address = connectionAddress;
|
||||
QuicChannel channel = QuicheQuicChannel.forClient(parent, (InetSocketAddress) local,
|
||||
(InetSocketAddress) remote,
|
||||
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
|
||||
|
||||
Quic.setupChannel(channel, Quic.toOptionsArray(options), Quic.toAttributesArray(attrs), handler, logger);
|
||||
EventLoop eventLoop = parent.eventLoop();
|
||||
eventLoop.register(channel).addListener((ChannelFuture future) -> {
|
||||
Throwable cause = future.cause();
|
||||
if (cause != null) {
|
||||
promise.setFailure(cause);
|
||||
} else {
|
||||
channel.connect(address).addListener(f -> {
|
||||
Throwable error = f.cause();
|
||||
if (error != null) {
|
||||
promise.setFailure(error);
|
||||
} else {
|
||||
promise.setSuccess(channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.ChannelConfig;
|
||||
import io.netty.channel.MessageSizeEstimator;
|
||||
import io.netty.channel.RecvByteBufAllocator;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
|
||||
/**
|
||||
* A QUIC {@link ChannelConfig}.
|
||||
*/
|
||||
public interface QuicChannelConfig extends ChannelConfig {
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
QuicChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setWriteSpinCount(int writeSpinCount);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setAllocator(ByteBufAllocator allocator);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setAutoRead(boolean autoRead);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setAutoClose(boolean autoClose);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark);
|
||||
|
||||
@Override
|
||||
QuicChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator);
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
|
||||
/**
|
||||
* {@link ChannelOption}s specific to QUIC.
|
||||
*/
|
||||
public final class QuicChannelOption<T> extends ChannelOption<T> {
|
||||
|
||||
/**
|
||||
* If set to {@code true} the {@link QuicStreamChannel} will read {@link QuicStreamFrame}s and fire it through
|
||||
* the pipeline, if {@code false} it will read {@link io.netty.buffer.ByteBuf} and translate the FIN flag to
|
||||
* events.
|
||||
*/
|
||||
public static final ChannelOption<Boolean> READ_FRAMES =
|
||||
valueOf(QuicChannelOption.class, "READ_FRAMES");
|
||||
|
||||
/**
|
||||
* Enable <a href="https://quiclog.github.io/internet-drafts/draft-marx-qlog-main-schema.html">qlog</a>
|
||||
* for a {@link QuicChannel}.
|
||||
*/
|
||||
public static final ChannelOption<QLogConfiguration> QLOG = valueOf(QuicChannelOption.class, "QLOG");
|
||||
|
||||
/**
|
||||
* Use <a href="https://blog.cloudflare.com/accelerating-udp-packet-transmission-for-quic/">GSO</a>
|
||||
* for QUIC packets if possible.
|
||||
*/
|
||||
public static final ChannelOption<SegmentedDatagramPacketAllocator> SEGMENTED_DATAGRAM_PACKET_ALLOCATOR =
|
||||
valueOf(QuicChannelOption.class, "SEGMENTED_DATAGRAM_PACKET_ALLOCATOR");
|
||||
|
||||
@SuppressWarnings({ "deprecation" })
|
||||
private QuicChannelOption() {
|
||||
super(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link QuicCodecBuilder} that configures and builds a {@link ChannelHandler} that should be added to the
|
||||
* {@link io.netty.channel.ChannelPipeline} of a {@code QUIC} client.
|
||||
*/
|
||||
public final class QuicClientCodecBuilder extends QuicCodecBuilder<QuicClientCodecBuilder> {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public QuicClientCodecBuilder() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
private QuicClientCodecBuilder(QuicCodecBuilder<QuicClientCodecBuilder> builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicClientCodecBuilder clone() {
|
||||
return new QuicClientCodecBuilder(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler build(QuicheConfig config,
|
||||
Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||
Executor sslTaskExecutor,
|
||||
int localConnIdLength, FlushStrategy flushStrategy) {
|
||||
return new QuicheQuicClientCodec(config, sslEngineProvider, sslTaskExecutor, localConnIdLength, flushStrategy);
|
||||
}
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
final class QuicClientSessionCache {
|
||||
|
||||
private static final int DEFAULT_CACHE_SIZE;
|
||||
static {
|
||||
// Respect the same system property as the JDK implementation to make it easy to switch between implementations.
|
||||
int cacheSize = SystemPropertyUtil.getInt("javax.net.ssl.sessionCacheSize", 20480);
|
||||
if (cacheSize >= 0) {
|
||||
DEFAULT_CACHE_SIZE = cacheSize;
|
||||
} else {
|
||||
DEFAULT_CACHE_SIZE = 20480;
|
||||
}
|
||||
}
|
||||
|
||||
private final AtomicInteger maximumCacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE);
|
||||
|
||||
// Let's use the same default value as OpenSSL does.
|
||||
// See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_default_timeout.html
|
||||
private final AtomicInteger sessionTimeout = new AtomicInteger(300);
|
||||
private int sessionCounter;
|
||||
|
||||
private final Map<HostPort, SessionHolder> sessions =
|
||||
new LinkedHashMap<HostPort, SessionHolder>() {
|
||||
|
||||
private static final long serialVersionUID = -7773696788135734448L;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<HostPort, SessionHolder> eldest) {
|
||||
int maxSize = maximumCacheSize.get();
|
||||
return maxSize >= 0 && size() > maxSize;
|
||||
}
|
||||
};
|
||||
|
||||
void saveSession(String host, int port, long creationTime, long timeout, byte[] session, boolean isSingleUse) {
|
||||
HostPort hostPort = keyFor(host, port);
|
||||
if (hostPort != null) {
|
||||
synchronized (sessions) {
|
||||
// Mimic what OpenSSL is doing and expunge every 255 new sessions
|
||||
// See https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_flush_sessions.html
|
||||
if (++sessionCounter == 255) {
|
||||
sessionCounter = 0;
|
||||
expungeInvalidSessions();
|
||||
}
|
||||
|
||||
sessions.put(hostPort, new SessionHolder(creationTime, timeout, session, isSingleUse));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only used for testing.
|
||||
boolean hasSession(String host, int port) {
|
||||
HostPort hostPort = keyFor(host, port);
|
||||
if (hostPort != null) {
|
||||
synchronized (sessions) {
|
||||
return sessions.containsKey(hostPort);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] getSession(String host, int port) {
|
||||
HostPort hostPort = keyFor(host, port);
|
||||
if (hostPort != null) {
|
||||
SessionHolder sessionHolder;
|
||||
synchronized (sessions) {
|
||||
sessionHolder = sessions.get(hostPort);
|
||||
if (sessionHolder == null) {
|
||||
return null;
|
||||
}
|
||||
if (sessionHolder.isSingleUse()) {
|
||||
// Remove session as it should only be re-used once.
|
||||
sessions.remove(hostPort);
|
||||
}
|
||||
}
|
||||
if (sessionHolder.isValid()) {
|
||||
return sessionHolder.sessionBytes();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeSession(String host, int port) {
|
||||
HostPort hostPort = keyFor(host, port);
|
||||
if (hostPort != null) {
|
||||
synchronized (sessions) {
|
||||
sessions.remove(hostPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setSessionTimeout(int seconds) {
|
||||
int oldTimeout = sessionTimeout.getAndSet(seconds);
|
||||
if (oldTimeout > seconds) {
|
||||
// Drain the whole cache as this way we can use the ordering of the LinkedHashMap to detect early
|
||||
// if there are any other sessions left that are invalid.
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
int getSessionTimeout() {
|
||||
return sessionTimeout.get();
|
||||
}
|
||||
|
||||
void setSessionCacheSize(int size) {
|
||||
long oldSize = maximumCacheSize.getAndSet(size);
|
||||
if (oldSize > size || size == 0) {
|
||||
// Just keep it simple for now and drain the whole cache.
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
int getSessionCacheSize() {
|
||||
return maximumCacheSize.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache and free all cached SSL_SESSION*.
|
||||
*/
|
||||
void clear() {
|
||||
synchronized (sessions) {
|
||||
sessions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void expungeInvalidSessions() {
|
||||
assert Thread.holdsLock(sessions);
|
||||
|
||||
if (sessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<HostPort, SessionHolder>> iterator = sessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
SessionHolder sessionHolder = iterator.next().getValue();
|
||||
// As we use a LinkedHashMap we can break the while loop as soon as we find a valid session.
|
||||
// This is true as we always drain the cache as soon as we change the timeout to a smaller value as
|
||||
// it was set before. This way its true that the insertion order matches the timeout order.
|
||||
if (sessionHolder.isValid(now)) {
|
||||
break;
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private static HostPort keyFor(String host, int port) {
|
||||
if (host == null && port < 1) {
|
||||
return null;
|
||||
}
|
||||
return new HostPort(host, port);
|
||||
}
|
||||
|
||||
private static final class SessionHolder {
|
||||
private final long creationTime;
|
||||
private final long timeout;
|
||||
private final byte[] sessionBytes;
|
||||
private final boolean isSingleUse;
|
||||
|
||||
SessionHolder(long creationTime, long timeout, byte[] session, boolean isSingleUse) {
|
||||
this.creationTime = creationTime;
|
||||
this.timeout = timeout;
|
||||
this.sessionBytes = session;
|
||||
this.isSingleUse = isSingleUse;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return isValid(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
boolean isValid(long current) {
|
||||
return current <= creationTime + timeout;
|
||||
}
|
||||
|
||||
boolean isSingleUse() {
|
||||
return isSingleUse;
|
||||
}
|
||||
|
||||
byte[] sessionBytes() {
|
||||
return sessionBytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Host / Port tuple used to find a session in the cache.
|
||||
*/
|
||||
private static final class HostPort {
|
||||
private final int hash;
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
HostPort(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
// Calculate a hashCode that does ignore case.
|
||||
this.hash = 31 * AsciiString.hashCode(host) + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof HostPort)) {
|
||||
return false;
|
||||
}
|
||||
HostPort other = (HostPort) obj;
|
||||
return port == other.port && host.equalsIgnoreCase(other.host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HostPort{" +
|
||||
"host='" + host + '\'' +
|
||||
", port=" + port +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
|
||||
/**
|
||||
* Special {@link QuicClosedChannelException} which also provides extra info if the close was a result of a
|
||||
* {@link QuicConnectionCloseEvent} that was triggered by the remote peer.
|
||||
*/
|
||||
public final class QuicClosedChannelException extends ClosedChannelException {
|
||||
|
||||
private final QuicConnectionCloseEvent event;
|
||||
|
||||
QuicClosedChannelException(QuicConnectionCloseEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link QuicConnectionCloseEvent} that caused the closure or {@code null} if none was received.
|
||||
*
|
||||
* @return the event.
|
||||
*/
|
||||
public QuicConnectionCloseEvent event() {
|
||||
return event;
|
||||
}
|
||||
}
|
@ -0,0 +1,508 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkInRange;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@code QUIC} codec builders.
|
||||
*
|
||||
* @param <B> the type of the {@link QuicCodecBuilder}.
|
||||
*/
|
||||
public abstract class QuicCodecBuilder<B extends QuicCodecBuilder<B>> {
|
||||
private final boolean server;
|
||||
private Boolean grease;
|
||||
private Long maxIdleTimeout;
|
||||
private Long maxRecvUdpPayloadSize;
|
||||
private Long maxSendUdpPayloadSize;
|
||||
private Long initialMaxData;
|
||||
private Long initialMaxStreamDataBidiLocal;
|
||||
private Long initialMaxStreamDataBidiRemote;
|
||||
private Long initialMaxStreamDataUni;
|
||||
private Long initialMaxStreamsBidi;
|
||||
private Long initialMaxStreamsUni;
|
||||
private Long ackDelayExponent;
|
||||
private Long maxAckDelay;
|
||||
private Boolean disableActiveMigration;
|
||||
private Boolean enableHystart;
|
||||
private QuicCongestionControlAlgorithm congestionControlAlgorithm;
|
||||
private int localConnIdLength;
|
||||
private Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
|
||||
private FlushStrategy flushStrategy = FlushStrategy.DEFAULT;
|
||||
private Integer recvQueueLen;
|
||||
private Integer sendQueueLen;
|
||||
private Long activeConnectionIdLimit;
|
||||
private byte[] statelessResetToken;
|
||||
|
||||
private Executor sslTaskExecutor;
|
||||
|
||||
// package-private for testing only
|
||||
int version;
|
||||
|
||||
QuicCodecBuilder(boolean server) {
|
||||
Quic.ensureAvailability();
|
||||
this.version = Quiche.QUICHE_PROTOCOL_VERSION;
|
||||
this.localConnIdLength = Quiche.QUICHE_MAX_CONN_ID_LEN;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
QuicCodecBuilder(QuicCodecBuilder<B> builder) {
|
||||
Quic.ensureAvailability();
|
||||
this.server = builder.server;
|
||||
this.grease = builder.grease;
|
||||
this.maxIdleTimeout = builder.maxIdleTimeout;
|
||||
this.maxRecvUdpPayloadSize = builder.maxRecvUdpPayloadSize;
|
||||
this.maxSendUdpPayloadSize = builder.maxSendUdpPayloadSize;
|
||||
this.initialMaxData = builder.initialMaxData;
|
||||
this.initialMaxStreamDataBidiLocal = builder.initialMaxStreamDataBidiLocal;
|
||||
this.initialMaxStreamDataBidiRemote = builder.initialMaxStreamDataBidiRemote;
|
||||
this.initialMaxStreamDataUni = builder.initialMaxStreamDataUni;
|
||||
this.initialMaxStreamsBidi = builder.initialMaxStreamsBidi;
|
||||
this.initialMaxStreamsUni = builder.initialMaxStreamsUni;
|
||||
this.ackDelayExponent = builder.ackDelayExponent;
|
||||
this.maxAckDelay = builder.maxAckDelay;
|
||||
this.disableActiveMigration = builder.disableActiveMigration;
|
||||
this.enableHystart = builder.enableHystart;
|
||||
this.congestionControlAlgorithm = builder.congestionControlAlgorithm;
|
||||
this.localConnIdLength = builder.localConnIdLength;
|
||||
this.sslEngineProvider = builder.sslEngineProvider;
|
||||
this.flushStrategy = builder.flushStrategy;
|
||||
this.recvQueueLen = builder.recvQueueLen;
|
||||
this.sendQueueLen = builder.sendQueueLen;
|
||||
this.activeConnectionIdLimit = builder.activeConnectionIdLimit;
|
||||
this.statelessResetToken = builder.statelessResetToken;
|
||||
this.sslTaskExecutor = builder.sslTaskExecutor;
|
||||
this.version = builder.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns itself.
|
||||
*
|
||||
* @return itself.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final B self() {
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link FlushStrategy} that will be used to detect when an automatic flush
|
||||
* should happen.
|
||||
*
|
||||
* @param flushStrategy the strategy to use.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B flushStrategy(FlushStrategy flushStrategy) {
|
||||
this.flushStrategy = Objects.requireNonNull(flushStrategy, "flushStrategy");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the congestion control algorithm to use.
|
||||
*
|
||||
* The default is {@link QuicCongestionControlAlgorithm#CUBIC}.
|
||||
*
|
||||
* @param congestionControlAlgorithm the {@link QuicCongestionControlAlgorithm} to use.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B congestionControlAlgorithm(QuicCongestionControlAlgorithm congestionControlAlgorithm) {
|
||||
this.congestionControlAlgorithm = congestionControlAlgorithm;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if <a href="https://tools.ietf.org/html/draft-thomson-quic-bit-grease-00">greasing</a> should be enabled
|
||||
* or not.
|
||||
*
|
||||
* The default value is {@code true}.
|
||||
*
|
||||
* @param enable {@code true} if enabled, {@code false} otherwise.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B grease(boolean enable) {
|
||||
grease = enable;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_idle_timeout">
|
||||
* set_max_idle_timeout</a>.
|
||||
*
|
||||
* The default value is infinite, that is, no timeout is used.
|
||||
*
|
||||
* @param amount the maximum idle timeout.
|
||||
* @param unit the {@link TimeUnit}.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B maxIdleTimeout(long amount, TimeUnit unit) {
|
||||
this.maxIdleTimeout = unit.toMillis(checkPositiveOrZero(amount, "amount"));
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/35e38d987c1e53ef2bd5f23b754c50162b5adac8/src/lib.rs#L669">
|
||||
* set_max_send_udp_payload_size</a>.
|
||||
*
|
||||
* The default and minimum value is 1200.
|
||||
*
|
||||
* @param size the maximum payload size that is advertised to the remote peer.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B maxSendUdpPayloadSize(long size) {
|
||||
this.maxSendUdpPayloadSize = checkPositiveOrZero(size, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/35e38d987c1e53ef2bd5f23b754c50162b5adac8/src/lib.rs#L662">
|
||||
* set_max_recv_udp_payload_size</a>.
|
||||
*
|
||||
* The default value is 65527.
|
||||
*
|
||||
* @param size the maximum payload size that is advertised to the remote peer.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B maxRecvUdpPayloadSize(long size) {
|
||||
this.maxRecvUdpPayloadSize = checkPositiveOrZero(size, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_data">
|
||||
* set_initial_max_data</a>.
|
||||
*
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param value the initial maximum data limit.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B initialMaxData(long value) {
|
||||
this.initialMaxData = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local">
|
||||
* set_initial_max_stream_data_bidi_local</a>.
|
||||
*
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param value the initial maximum data limit for local bidirectional streams.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B initialMaxStreamDataBidirectionalLocal(long value) {
|
||||
this.initialMaxStreamDataBidiLocal = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote">
|
||||
* set_initial_max_stream_data_bidi_remote</a>.
|
||||
*
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param value the initial maximum data limit for remote bidirectional streams.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B initialMaxStreamDataBidirectionalRemote(long value) {
|
||||
this.initialMaxStreamDataBidiRemote = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_uni">
|
||||
* set_initial_max_stream_data_uni</a>.
|
||||
*
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param value the initial maximum data limit for unidirectional streams.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B initialMaxStreamDataUnidirectional(long value) {
|
||||
this.initialMaxStreamDataUni = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_bidi">
|
||||
* set_initial_max_streams_bidi</a>.
|
||||
*
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param value the initial maximum stream limit for bidirectional streams.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B initialMaxStreamsBidirectional(long value) {
|
||||
this.initialMaxStreamsBidi = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_uni">
|
||||
* set_initial_max_streams_uni</a>.
|
||||
*
|
||||
* The default value is 0.
|
||||
*
|
||||
* @param value the initial maximum stream limit for unidirectional streams.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B initialMaxStreamsUnidirectional(long value) {
|
||||
this.initialMaxStreamsUni = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_ack_delay_exponent">
|
||||
* set_ack_delay_exponent</a>.
|
||||
*
|
||||
* The default value is 3.
|
||||
*
|
||||
* @param value the delay exponent used for ACKs.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B ackDelayExponent(long value) {
|
||||
this.ackDelayExponent = checkPositiveOrZero(value, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_ack_delay">
|
||||
* set_max_ack_delay</a>.
|
||||
*
|
||||
* The default value is 25 milliseconds.
|
||||
*
|
||||
* @param amount the max ack delay.
|
||||
* @param unit the {@link TimeUnit}.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B maxAckDelay(long amount, TimeUnit unit) {
|
||||
this.maxAckDelay = unit.toMillis(checkPositiveOrZero(amount, "amount"));
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_disable_active_migration">
|
||||
* set_disable_active_migration</a>.
|
||||
*
|
||||
* The default value is {@code true}.
|
||||
*
|
||||
* @param enable {@code true} if migration should be enabled, {@code false} otherwise.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B activeMigration(boolean enable) {
|
||||
this.disableActiveMigration = !enable;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.enable_hystart">
|
||||
* enable_hystart</a>.
|
||||
*
|
||||
* The default value is {@code true}.
|
||||
*
|
||||
* @param enable {@code true} if Hystart should be enabled.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B hystart(boolean enable) {
|
||||
this.enableHystart = enable;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the local connection id length that is used.
|
||||
*
|
||||
* The default is 20, which is also the maximum that is supported.
|
||||
*
|
||||
* @param value the length of local generated connections ids.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B localConnectionIdLength(int value) {
|
||||
this.localConnIdLength = checkInRange(value, 0, Quiche.QUICHE_MAX_CONN_ID_LEN, "value");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to configure the {@code QUIC version} that should be used.
|
||||
*
|
||||
* The default value is the latest supported version by the underlying library.
|
||||
*
|
||||
* @param version the {@code QUIC version} to use.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B version(int version) {
|
||||
this.version = version;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">
|
||||
* Datagram support.</a>
|
||||
* @param recvQueueLen the RECV queue length.
|
||||
* @param sendQueueLen the SEND queue length.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B datagram(int recvQueueLen, int sendQueueLen) {
|
||||
checkPositive(recvQueueLen, "recvQueueLen");
|
||||
checkPositive(sendQueueLen, "sendQueueLen");
|
||||
|
||||
this.recvQueueLen = recvQueueLen;
|
||||
this.sendQueueLen = sendQueueLen;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link QuicSslContext} that will be used to create {@link QuicSslEngine}s for {@link QuicChannel}s.
|
||||
*
|
||||
* If you need a more flexible way to provide {@link QuicSslEngine}s use {@link #sslEngineProvider(Function)}.
|
||||
*
|
||||
* @param sslContext the context.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B sslContext(QuicSslContext sslContext) {
|
||||
if (server != sslContext.isServer()) {
|
||||
throw new IllegalArgumentException("QuicSslContext.isServer() " + sslContext.isServer()
|
||||
+ " isn't supported by this builder");
|
||||
}
|
||||
return sslEngineProvider(q -> sslContext.newEngine(q.alloc()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Function} that will return the {@link QuicSslEngine} that should be used for the
|
||||
* {@link QuicChannel}.
|
||||
*
|
||||
* @param sslEngineProvider the provider.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B sslEngineProvider(Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider) {
|
||||
this.sslEngineProvider = sslEngineProvider;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to configure a {@link Executor} that will be used to run expensive SSL operations.
|
||||
*
|
||||
* @param sslTaskExecutor the {@link Executor} that will be used to offload expensive SSL operations.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B sslTaskExecutor(Executor sslTaskExecutor) {
|
||||
this.sslTaskExecutor = sslTaskExecutor;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to configure the {@code active connect id limit} that should be used.
|
||||
*
|
||||
* @param limit the limit to use.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B activeConnectionIdLimit(long limit) {
|
||||
checkPositive(limit, "limit");
|
||||
|
||||
this.activeConnectionIdLimit = limit;
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allows to configure the {@code active connect id limit} that should be used.
|
||||
*
|
||||
* @param token the token to use.
|
||||
* @return the instance itself.
|
||||
*/
|
||||
public final B statelessResetToken(byte[] token) {
|
||||
if (token.length != 16) {
|
||||
throw new IllegalArgumentException("token must be 16 bytes but was " + token.length);
|
||||
}
|
||||
|
||||
this.statelessResetToken = token.clone();
|
||||
return self();
|
||||
}
|
||||
|
||||
private QuicheConfig createConfig() {
|
||||
return new QuicheConfig(version, grease,
|
||||
maxIdleTimeout, maxSendUdpPayloadSize, maxRecvUdpPayloadSize, initialMaxData,
|
||||
initialMaxStreamDataBidiLocal, initialMaxStreamDataBidiRemote,
|
||||
initialMaxStreamDataUni, initialMaxStreamsBidi, initialMaxStreamsUni,
|
||||
ackDelayExponent, maxAckDelay, disableActiveMigration, enableHystart,
|
||||
congestionControlAlgorithm, recvQueueLen, sendQueueLen, activeConnectionIdLimit, statelessResetToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the configuration before building the codec.
|
||||
*/
|
||||
protected void validate() {
|
||||
if (sslEngineProvider == null) {
|
||||
throw new IllegalStateException("sslEngineProvider can't be null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the QUIC codec that should be added to the {@link io.netty.channel.ChannelPipeline} of the underlying
|
||||
* {@link io.netty.channel.Channel} which is used as transport for QUIC.
|
||||
*
|
||||
* @return the {@link ChannelHandler} which acts as QUIC codec.
|
||||
*/
|
||||
public final ChannelHandler build() {
|
||||
validate();
|
||||
QuicheConfig config = createConfig();
|
||||
try {
|
||||
return build(config, sslEngineProvider, sslTaskExecutor, localConnIdLength, flushStrategy);
|
||||
} catch (Throwable cause) {
|
||||
config.free();
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the builder
|
||||
*
|
||||
* @return the new instance that is a clone if this instance.
|
||||
*/
|
||||
public abstract B clone();
|
||||
|
||||
/**
|
||||
* Builds the QUIC codec.
|
||||
*
|
||||
* @param config the {@link QuicheConfig} that should be used.
|
||||
* @param sslContextProvider the context provider
|
||||
* @param sslTaskExecutor the {@link Executor} to use.
|
||||
* @param localConnIdLength the local connection id length.
|
||||
* @param flushStrategy the {@link FlushStrategy} that should be used.
|
||||
* @return the {@link ChannelHandler} which acts as codec.
|
||||
*/
|
||||
protected abstract ChannelHandler build(QuicheConfig config,
|
||||
Function<QuicChannel, ? extends QuicSslEngine> sslContextProvider,
|
||||
Executor sslTaskExecutor,
|
||||
int localConnIdLength, FlushStrategy flushStrategy);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Available congestion control algorithms to use.
|
||||
*/
|
||||
public enum QuicCongestionControlAlgorithm {
|
||||
RENO,
|
||||
CUBIC,
|
||||
BBR
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@link QuicConnectionAddress} that can be used to connect too.
|
||||
*/
|
||||
public final class QuicConnectionAddress extends SocketAddress {
|
||||
|
||||
/**
|
||||
* Special {@link QuicConnectionAddress} that should be used when the connection address should be generated
|
||||
* and chosen on the fly.
|
||||
*/
|
||||
public static final QuicConnectionAddress EPHEMERAL = new QuicConnectionAddress(null, false);
|
||||
|
||||
private final String toStr;
|
||||
|
||||
// Accessed by QuicheQuicheChannel
|
||||
final ByteBuffer connId;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param connId the connection id to use.
|
||||
*/
|
||||
public QuicConnectionAddress(byte[] connId) {
|
||||
this(ByteBuffer.wrap(connId.clone()), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param connId the connection id to use.
|
||||
*/
|
||||
public QuicConnectionAddress(ByteBuffer connId) {
|
||||
this(connId, true);
|
||||
}
|
||||
|
||||
private QuicConnectionAddress(ByteBuffer connId, boolean validate) {
|
||||
Quic.ensureAvailability();
|
||||
if (validate && connId.remaining() > Quiche.QUICHE_MAX_CONN_ID_LEN) {
|
||||
throw new IllegalArgumentException("Connection ID can only be of max length "
|
||||
+ Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
}
|
||||
this.connId = connId;
|
||||
if (connId == null) {
|
||||
toStr = "QuicConnectionAddress{EPHEMERAL}";
|
||||
} else {
|
||||
ByteBuf buffer = Unpooled.wrappedBuffer(connId);
|
||||
try {
|
||||
toStr = "QuicConnectionAddress{" +
|
||||
"connId=" + ByteBufUtil.hexDump(buffer) + '}';
|
||||
} finally {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (this == EPHEMERAL) {
|
||||
return System.identityHashCode(EPHEMERAL);
|
||||
}
|
||||
return Objects.hash(connId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof QuicConnectionAddress)) {
|
||||
return false;
|
||||
}
|
||||
QuicConnectionAddress address = (QuicConnectionAddress) obj;
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (connId == null) {
|
||||
return false;
|
||||
}
|
||||
return connId.equals(address.connId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a random generated {@link QuicConnectionAddress} of a given length
|
||||
* that can be used to connect a {@link QuicChannel}
|
||||
*
|
||||
* @param length the length of the {@link QuicConnectionAddress} to generate.
|
||||
* @return the generated address.
|
||||
*/
|
||||
public static QuicConnectionAddress random(int length) {
|
||||
return new QuicConnectionAddress(QuicConnectionIdGenerator.randomGenerator().newId(length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a random generated {@link QuicConnectionAddress} of maximum size
|
||||
* that can be used to connect a {@link QuicChannel}
|
||||
*
|
||||
* @return the generated address.
|
||||
*/
|
||||
public static QuicConnectionAddress random() {
|
||||
return random(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Event that is generated if the remote peer sends a
|
||||
* <a href="https://www.rfc-editor.org/rfc/rfc9000#name-connection_close-frames">CLOSE_CONNECTION frame</a>.
|
||||
* This allows to inspect the various details of the cause of the close.
|
||||
*/
|
||||
public final class QuicConnectionCloseEvent implements QuicEvent {
|
||||
|
||||
final boolean applicationClose;
|
||||
final int error;
|
||||
final byte[] reason;
|
||||
|
||||
QuicConnectionCloseEvent(boolean applicationClose, int error, byte[] reason) {
|
||||
this.applicationClose = applicationClose;
|
||||
this.error = error;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if this was an application close, {@code false} otherwise.
|
||||
*
|
||||
* @return if this is an application close.
|
||||
*/
|
||||
public boolean isApplicationClose() {
|
||||
return applicationClose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the error that was provided for the close.
|
||||
*
|
||||
* @return the error.
|
||||
*/
|
||||
public int error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a <a href="https://www.rfc-editor.org/rfc/rfc9001#section-4.8">TLS error</a>
|
||||
* is contained.
|
||||
* @return {@code true} if this is an {@code TLS error}, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isTlsError() {
|
||||
return !applicationClose && error >= 0x0100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason for the close, which may be empty if no reason was given as part of the close.
|
||||
*
|
||||
* @return the reason.
|
||||
*/
|
||||
public byte[] reason() {
|
||||
return reason.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicConnectionCloseEvent{" +
|
||||
"applicationClose=" + applicationClose +
|
||||
", error=" + error +
|
||||
", reason=" + Arrays.toString(reason) +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the contained {@code TLS error} from the {@code QUIC error}. If the given {@code QUIC error} does not
|
||||
* contain a {@code TLS error} it will return {@code -1}.
|
||||
*
|
||||
* @param error the {@code QUIC error}
|
||||
* @return the {@code TLS error} or {@code -1} if there was no {@code TLS error} contained.
|
||||
*/
|
||||
public static int extractTlsError(int error) {
|
||||
int tlsError = error - 0x0100;
|
||||
if (tlsError < 0) {
|
||||
return -1;
|
||||
}
|
||||
return tlsError;
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Creates new connection id instances.
|
||||
*/
|
||||
public interface QuicConnectionIdGenerator {
|
||||
/**
|
||||
* Creates a new connection id with the given length. This method may not be supported by
|
||||
* a sign id generator implementation as a sign id generator should always have an input
|
||||
* to sign with, otherwise this method may generate the same id which may cause some
|
||||
* unpredictable issues when we use it.
|
||||
*
|
||||
* @param length the length of the id.
|
||||
* @return the id.
|
||||
*/
|
||||
ByteBuffer newId(int length);
|
||||
|
||||
/**
|
||||
* Creates a new connection id with the given length. The given input may be used to sign or
|
||||
* seed the id, or may be ignored (depending on the implementation).
|
||||
*
|
||||
* @param input the input which may be used to generate the id.
|
||||
* @param length the length of the id.
|
||||
* @return the id.
|
||||
*/
|
||||
ByteBuffer newId(ByteBuffer input, int length);
|
||||
|
||||
/**
|
||||
* Returns the maximum length of a connection id.
|
||||
*
|
||||
* @return the maximum length of a connection id that is supported.
|
||||
*/
|
||||
int maxConnectionIdLength();
|
||||
|
||||
/**
|
||||
* Returns true if the implementation is idempotent, which means we will get the same id
|
||||
* with the same input ByteBuffer. Otherwise, returns false.
|
||||
*
|
||||
* @return whether the implementation is idempotent.
|
||||
*/
|
||||
boolean isIdempotent();
|
||||
|
||||
/**
|
||||
* Return a {@link QuicConnectionIdGenerator} which randomly generates new connection ids.
|
||||
*
|
||||
* @return a {@link QuicConnectionIdGenerator} which randomly generated ids.
|
||||
*/
|
||||
static QuicConnectionIdGenerator randomGenerator() {
|
||||
return SecureRandomQuicConnectionIdGenerator.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link QuicConnectionIdGenerator} which generates new connection ids by signing the given input.
|
||||
*
|
||||
* @return a {@link QuicConnectionIdGenerator} which generates ids by signing the given input.
|
||||
*/
|
||||
static QuicConnectionIdGenerator signGenerator() {
|
||||
return HmacSignQuicConnectionIdGenerator.INSTANCE;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Statistics about the {@code QUIC} connection. If unknown by the implementation it might return {@code -1} values
|
||||
* for the various methods.
|
||||
*/
|
||||
public interface QuicConnectionStats {
|
||||
/**
|
||||
* @return The number of QUIC packets received on the connection.
|
||||
*/
|
||||
long recv();
|
||||
|
||||
/**
|
||||
* @return The number of QUIC packets sent on this connection.
|
||||
*/
|
||||
long sent();
|
||||
|
||||
/**
|
||||
* @return The number of QUIC packets that were lost.
|
||||
*/
|
||||
long lost();
|
||||
|
||||
/**
|
||||
* @return The number of sent QUIC packets with retransmitted data.
|
||||
*/
|
||||
long retrans();
|
||||
|
||||
/**
|
||||
* @return The number of sent bytes.
|
||||
*/
|
||||
long sentBytes();
|
||||
|
||||
/**
|
||||
* @return The number of received bytes.
|
||||
*/
|
||||
long recvBytes();
|
||||
|
||||
/**
|
||||
* @return The number of bytes lost.
|
||||
*/
|
||||
long lostBytes();
|
||||
|
||||
/**
|
||||
* @return The number of stream bytes retransmitted.
|
||||
*/
|
||||
long streamRetransBytes();
|
||||
|
||||
/**
|
||||
* @return The number of known paths for the connection.
|
||||
*/
|
||||
long pathsCount();
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* Used when the remote peer supports the
|
||||
* <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">QUIC DATAGRAM extension.</a>
|
||||
*/
|
||||
public final class QuicDatagramExtensionEvent implements QuicExtensionEvent {
|
||||
|
||||
private final int maxLength;
|
||||
|
||||
QuicDatagramExtensionEvent(int maxLength) {
|
||||
this.maxLength = ObjectUtil.checkPositiveOrZero(maxLength, "maxLength");
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum datagram payload length the peer will accept. If you try to write bigger datagrams the write will
|
||||
* fail.
|
||||
*
|
||||
* @return the max length.
|
||||
*/
|
||||
public int maxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicDatagramExtensionEvent{" +
|
||||
"maxLength=" + maxLength +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.collection.IntObjectHashMap;
|
||||
import io.netty.util.collection.IntObjectMap;
|
||||
|
||||
/**
|
||||
* All QUIC error codes identified by Quiche.
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/src/lib.rs#L335-L380">Error</a>
|
||||
*/
|
||||
public enum QuicError {
|
||||
BUFFER_TOO_SHORT(Quiche.QUICHE_ERR_BUFFER_TOO_SHORT, "QUICHE_ERR_BUFFER_TOO_SHORT"),
|
||||
UNKNOWN_VERSION(Quiche.QUICHE_ERR_UNKNOWN_VERSION, "QUICHE_ERR_UNKNOWN_VERSION"),
|
||||
INVALID_FRAME(Quiche.QUICHE_ERR_INVALID_FRAME, "QUICHE_ERR_INVALID_FRAME"),
|
||||
INVALID_PACKET(Quiche.QUICHE_ERR_INVALID_PACKET, "QUICHE_ERR_INVALID_PACKET"),
|
||||
INVALID_STATE(Quiche.QUICHE_ERR_INVALID_STATE, "QUICHE_ERR_INVALID_STATE"),
|
||||
INVALID_STREAM_STATE(Quiche.QUICHE_ERR_INVALID_STREAM_STATE, "QUICHE_ERR_INVALID_STREAM_STATE"),
|
||||
INVALID_TRANSPORT_PARAM(Quiche.QUICHE_ERR_INVALID_TRANSPORT_PARAM, "QUICHE_ERR_INVALID_TRANSPORT_PARAM"),
|
||||
CRYPTO_FAIL(Quiche.QUICHE_ERR_CRYPTO_FAIL, "QUICHE_ERR_CRYPTO_FAIL"),
|
||||
TLS_FAIL(Quiche.QUICHE_ERR_TLS_FAIL, "QUICHE_ERR_TLS_FAIL"),
|
||||
FLOW_CONTROL(Quiche.QUICHE_ERR_FLOW_CONTROL, "QUICHE_ERR_FLOW_CONTROL"),
|
||||
STREAM_LIMIT(Quiche.QUICHE_ERR_STREAM_LIMIT, "QUICHE_ERR_STREAM_LIMIT"),
|
||||
FINAL_SIZE(Quiche.QUICHE_ERR_FINAL_SIZE, "QUICHE_ERR_FINAL_SIZE"),
|
||||
CONGESTION_CONTROL(Quiche.QUICHE_ERR_CONGESTION_CONTROL, "QUICHE_ERR_CONGESTION_CONTROL"),
|
||||
STREAM_RESET(Quiche.QUICHE_ERR_STREAM_RESET, "STREAM_RESET"),
|
||||
STREAM_STOPPED(Quiche.QUICHE_ERR_STREAM_STOPPED, "STREAM_STOPPED"),
|
||||
ID_LIMIT(Quiche.QUICHE_ERR_ID_LIMIT, "ID_LIMIT"),
|
||||
QUT_OF_IDENTIFIERS(Quiche.QUICHE_ERR_OUT_OF_IDENTIFIERS, "OUT_OF_IDENTIFIERS"),
|
||||
KEY_UPDATE(Quiche.QUICHE_ERR_KEY_UPDATE, "KEY_UPDATE");
|
||||
|
||||
private static final IntObjectMap<QuicError> ERROR_MAP = new IntObjectHashMap<>();
|
||||
|
||||
static {
|
||||
for (QuicError errorCode : QuicError.values()) {
|
||||
ERROR_MAP.put(errorCode.code(), errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
QuicError(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
final int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
final String message() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return String.format("QuicError{code=%d, message=%s}", code, message);
|
||||
}
|
||||
|
||||
static QuicError valueOf(int code) {
|
||||
final QuicError errorCode = ERROR_MAP.get(code);
|
||||
if (errorCode == null) {
|
||||
throw new IllegalArgumentException("unknown " + QuicError.class.getSimpleName() + " code: " + code);
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Marker interface for events that will be passed through the {@link io.netty.channel.ChannelPipeline} via
|
||||
* {@link io.netty.channel.ChannelPipeline#fireUserEventTriggered(Object)} to notify the user about {@code QUIC}
|
||||
* specific events.
|
||||
*/
|
||||
public interface QuicEvent { }
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception produced while processing {@code QUIC}.
|
||||
*/
|
||||
public final class QuicException extends IOException {
|
||||
|
||||
private final QuicError error;
|
||||
|
||||
QuicException(QuicError error) {
|
||||
super(error.message());
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link QuicError} which was the cause of the {@link QuicException}.
|
||||
*
|
||||
* @return the {@link QuicError} that caused this {@link QuicException}.
|
||||
*/
|
||||
public QuicError error() {
|
||||
return error;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Marker interface for events that will be passed through the {@link io.netty.channel.ChannelPipeline} via
|
||||
* {@link io.netty.channel.ChannelPipeline#fireUserEventTriggered(Object)} to notify the user about supported
|
||||
* QUIC extensions by the remote peer.
|
||||
*/
|
||||
public interface QuicExtensionEvent extends QuicEvent { }
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static io.netty.handler.codec.quic.Quiche.allocateNativeOrder;
|
||||
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||
|
||||
/**
|
||||
* Parses the QUIC packet header and notifies a callback once parsing was successful.
|
||||
*
|
||||
* Once the parser is not needed anymore the user needs to call {@link #close()} to ensure all resources are
|
||||
* released. Failed to do so may lead to memory leaks.
|
||||
*
|
||||
* This class can be used for advanced use-cases. Usually you want to just use {@link QuicClientCodecBuilder} or
|
||||
* {@link QuicServerCodecBuilder}.
|
||||
*/
|
||||
public final class QuicHeaderParser implements AutoCloseable {
|
||||
private final int maxTokenLength;
|
||||
private final int localConnectionIdLength;
|
||||
private final ByteBuf versionBuffer;
|
||||
private final ByteBuf typeBuffer;
|
||||
private final ByteBuf scidLenBuffer;
|
||||
private final ByteBuf scidBuffer;
|
||||
private final ByteBuf dcidLenBuffer;
|
||||
private final ByteBuf dcidBuffer;
|
||||
private final ByteBuf tokenBuffer;
|
||||
private final ByteBuf tokenLenBuffer;
|
||||
private boolean closed;
|
||||
|
||||
public QuicHeaderParser(int maxTokenLength, int localConnectionIdLength) {
|
||||
Quic.ensureAvailability();
|
||||
this.maxTokenLength = checkPositiveOrZero(maxTokenLength, "maxTokenLength");
|
||||
this.localConnectionIdLength = checkPositiveOrZero(localConnectionIdLength, "localConnectionIdLength");
|
||||
// Allocate the buffer from which we read primative values like integer/long with native order to ensure
|
||||
// we read the right value.
|
||||
versionBuffer = allocateNativeOrder(Integer.BYTES);
|
||||
typeBuffer = allocateNativeOrder(Byte.BYTES);
|
||||
scidLenBuffer = allocateNativeOrder(Integer.BYTES);
|
||||
dcidLenBuffer = allocateNativeOrder(Integer.BYTES);
|
||||
tokenLenBuffer = allocateNativeOrder(Integer.BYTES);
|
||||
|
||||
// Now allocate the buffers that dont need native ordering and so will be cheaper to access when we slice into
|
||||
// these or obtain a view into these via internalNioBuffer(...).
|
||||
scidBuffer = Unpooled.directBuffer(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
dcidBuffer = Unpooled.directBuffer(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
tokenBuffer = Unpooled.directBuffer(maxTokenLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
versionBuffer.release();
|
||||
typeBuffer.release();
|
||||
scidBuffer.release();
|
||||
scidLenBuffer.release();
|
||||
dcidBuffer.release();
|
||||
dcidLenBuffer.release();
|
||||
tokenLenBuffer.release();
|
||||
tokenBuffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a QUIC packet and extract the header values out of it. This method takes no ownership of the packet itself
|
||||
* which means the caller of this method is expected to call {@link ByteBuf#release()} once the packet is not needed
|
||||
* anymore.
|
||||
*
|
||||
* @param sender the sender of the packet. This is directly passed to the {@link QuicHeaderProcessor} once
|
||||
* parsing was successful.
|
||||
* @param recipient the recipient of the packet.This is directly passed to the {@link QuicHeaderProcessor} once
|
||||
* parsing was successful.
|
||||
* @param packet raw QUIC packet itself. The ownership of the packet is not transferred. This is directly
|
||||
* passed to the {@link QuicHeaderProcessor} once parsing was successful.
|
||||
* @param callback the {@link QuicHeaderProcessor} that is called once a QUIC packet could be parsed and all
|
||||
* the header values be extracted.
|
||||
* @throws Exception thrown if we couldn't parse the header or if the {@link QuicHeaderProcessor} throws an
|
||||
* exception.
|
||||
*/
|
||||
public void parse(InetSocketAddress sender,
|
||||
InetSocketAddress recipient, ByteBuf packet, QuicHeaderProcessor callback) throws Exception {
|
||||
if (closed) {
|
||||
throw new IllegalStateException(QuicHeaderParser.class.getSimpleName() + " is already closed");
|
||||
}
|
||||
|
||||
// Set various len values so quiche_header_info can make use of these.
|
||||
scidLenBuffer.setInt(0, Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
dcidLenBuffer.setInt(0, Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||
tokenLenBuffer.setInt(0, maxTokenLength);
|
||||
|
||||
int res = Quiche.quiche_header_info(
|
||||
Quiche.readerMemoryAddress(packet), packet.readableBytes(),
|
||||
localConnectionIdLength,
|
||||
Quiche.memoryAddress(versionBuffer, 0, versionBuffer.capacity()),
|
||||
Quiche.memoryAddress(typeBuffer, 0, typeBuffer.capacity()),
|
||||
Quiche.memoryAddress(scidBuffer, 0, scidBuffer.capacity()),
|
||||
Quiche.memoryAddress(scidLenBuffer, 0, scidLenBuffer.capacity()),
|
||||
Quiche.memoryAddress(dcidBuffer, 0, dcidBuffer.capacity()),
|
||||
Quiche.memoryAddress(dcidLenBuffer, 0, dcidLenBuffer.capacity()),
|
||||
Quiche.memoryAddress(tokenBuffer, 0, tokenBuffer.capacity()),
|
||||
Quiche.writerMemoryAddress(tokenLenBuffer));
|
||||
if (res >= 0) {
|
||||
int version = versionBuffer.getInt(0);
|
||||
byte type = typeBuffer.getByte(0);
|
||||
int scidLen = scidLenBuffer.getInt(0);
|
||||
int dcidLen = dcidLenBuffer.getInt(0);
|
||||
int tokenLen = tokenLenBuffer.getInt(0);
|
||||
|
||||
callback.process(sender, recipient, packet, QuicPacketType.of(type), version,
|
||||
scidBuffer.setIndex(0, scidLen),
|
||||
dcidBuffer.setIndex(0, dcidLen),
|
||||
tokenBuffer.setIndex(0, tokenLen));
|
||||
} else {
|
||||
throw Quiche.newException(res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a QUIC packet and its header could be parsed.
|
||||
*/
|
||||
public interface QuicHeaderProcessor {
|
||||
|
||||
/**
|
||||
* Called when a QUIC packet header was parsed.
|
||||
*
|
||||
* @param sender the sender of the QUIC packet.
|
||||
* @param recipient the recipient of the QUIC packet.
|
||||
* @param packet the raw QUIC packet. The ownership is not transferred, which means you will need to call
|
||||
* {@link ByteBuf#retain()} on it if you want to keep a reference after this method
|
||||
* returns.
|
||||
* @param type the type of the packet.
|
||||
* @param version the version of the packet.
|
||||
* @param scid the source connection id. The ownership is not transferred and its generally not allowed
|
||||
* to hold any references to this buffer outside of the method as it will be re-used.
|
||||
* @param dcid the destination connection id. The ownership is not transferred and its generally not
|
||||
* allowed to hold any references to this buffer outside of the method as it will be
|
||||
* re-used.
|
||||
* @param token the token.The ownership is not transferred and its generally not allowed
|
||||
* to hold any references to this buffer outside of the method as it will be re-used.
|
||||
* @throws Exception throws if an error happens during processing.
|
||||
*/
|
||||
void process(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf packet,
|
||||
QuicPacketType type, int version, ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* The type of the
|
||||
* <a href="https://quicwg.org/base-drafts/rfc9000.html#name-packets-and-frames">QUIC packet</a>.
|
||||
*/
|
||||
public enum QuicPacketType {
|
||||
/**
|
||||
* Initial packet.
|
||||
*/
|
||||
INITIAL((byte) 1),
|
||||
|
||||
/**
|
||||
* Retry packet.
|
||||
*/
|
||||
RETRY((byte) 2),
|
||||
|
||||
/**
|
||||
* Handshake packet.
|
||||
*/
|
||||
HANDSHAKE((byte) 3),
|
||||
|
||||
/**
|
||||
* 0-RTT packet.
|
||||
*/
|
||||
ZERO_RTT((byte) 4),
|
||||
|
||||
/**
|
||||
* 1-RTT short header packet.
|
||||
*/
|
||||
SHORT((byte) 5),
|
||||
|
||||
/**
|
||||
* Version negotiation packet.
|
||||
*/
|
||||
VERSION_NEGOTIATION((byte) 6);
|
||||
|
||||
final byte type;
|
||||
|
||||
QuicPacketType(byte type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link QuicPacketType} for the given byte.
|
||||
*
|
||||
* @param type the byte that represent the type.
|
||||
* @return the {@link QuicPacketType}.
|
||||
*/
|
||||
static QuicPacketType of(byte type) {
|
||||
switch(type) {
|
||||
case 1:
|
||||
return INITIAL;
|
||||
case 2:
|
||||
return RETRY;
|
||||
case 3:
|
||||
return HANDSHAKE;
|
||||
case 4:
|
||||
return ZERO_RTT;
|
||||
case 5:
|
||||
return SHORT;
|
||||
case 6:
|
||||
return VERSION_NEGOTIATION;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown QUIC packet type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* A network path specific {@link QuicEvent}.
|
||||
*/
|
||||
public abstract class QuicPathEvent implements QuicEvent {
|
||||
|
||||
private final InetSocketAddress local;
|
||||
private final InetSocketAddress remote;
|
||||
|
||||
QuicPathEvent(InetSocketAddress local, InetSocketAddress remote) {
|
||||
this.local = requireNonNull(local, "local");
|
||||
this.remote = requireNonNull(remote, "remote");
|
||||
}
|
||||
|
||||
/**
|
||||
* The local address of the network path.
|
||||
*
|
||||
* @return local
|
||||
*/
|
||||
public InetSocketAddress local() {
|
||||
return local;
|
||||
}
|
||||
|
||||
/**
|
||||
* The remote address of the network path.
|
||||
*
|
||||
* @return local
|
||||
*/
|
||||
public InetSocketAddress remote() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent{" +
|
||||
"local=" + local +
|
||||
", remote=" + remote +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QuicPathEvent that = (QuicPathEvent) o;
|
||||
if (!Objects.equals(local, that.local)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(remote, that.remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = local != null ? local.hashCode() : 0;
|
||||
result = 31 * result + (remote != null ? remote.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final class New extends QuicPathEvent {
|
||||
/**
|
||||
* A new network path (local address, remote address) has been seen on a received packet.
|
||||
* Note that this event is only triggered for servers, as the client is responsible from initiating new paths.
|
||||
* The application may then probe this new path, if desired.
|
||||
*
|
||||
* @param local local address.
|
||||
* @param remote remote address.
|
||||
*/
|
||||
public New(InetSocketAddress local, InetSocketAddress remote) {
|
||||
super(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent.New{" +
|
||||
"local=" + local() +
|
||||
", remote=" + remote() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Validated extends QuicPathEvent {
|
||||
/**
|
||||
* The related network path between local and remote has been validated.
|
||||
*
|
||||
* @param local local address.
|
||||
* @param remote remote address.
|
||||
*/
|
||||
public Validated(InetSocketAddress local, InetSocketAddress remote) {
|
||||
super(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent.Validated{" +
|
||||
"local=" + local() +
|
||||
", remote=" + remote() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FailedValidation extends QuicPathEvent {
|
||||
/**
|
||||
* The related network path between local and remote failed to be validated.
|
||||
* This network path will not be used anymore, unless the application requests probing this path again.
|
||||
*
|
||||
* @param local local address.
|
||||
* @param remote remote address.
|
||||
*/
|
||||
public FailedValidation(InetSocketAddress local, InetSocketAddress remote) {
|
||||
super(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent.FailedValidation{" +
|
||||
"local=" + local() +
|
||||
", remote=" + remote() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Closed extends QuicPathEvent {
|
||||
|
||||
/**
|
||||
* The related network path between local and remote has been closed and is now unusable on this connection.
|
||||
*
|
||||
* @param local local address.
|
||||
* @param remote remote address.
|
||||
*/
|
||||
public Closed(InetSocketAddress local, InetSocketAddress remote) {
|
||||
super(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent.Closed{" +
|
||||
"local=" + local() +
|
||||
", remote=" + remote() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ReusedSourceConnectionId extends QuicPathEvent {
|
||||
private final long seq;
|
||||
private final InetSocketAddress oldLocal;
|
||||
private final InetSocketAddress oldRemote;
|
||||
|
||||
/**
|
||||
* The stack observes that the Source Connection ID with the given sequence number,
|
||||
* initially used by the peer over the first pair of addresses, is now reused over
|
||||
* the second pair of addresses.
|
||||
*
|
||||
* @param seq sequence number
|
||||
* @param oldLocal old local address.
|
||||
* @param oldRemote old remote address.
|
||||
* @param local local address.
|
||||
* @param remote remote address.
|
||||
*/
|
||||
public ReusedSourceConnectionId(long seq, InetSocketAddress oldLocal, InetSocketAddress oldRemote,
|
||||
InetSocketAddress local, InetSocketAddress remote) {
|
||||
super(local, remote);
|
||||
this.seq = seq;
|
||||
this.oldLocal = requireNonNull(oldLocal, "oldLocal");
|
||||
this.oldRemote = requireNonNull(oldRemote, "oldRemote");
|
||||
}
|
||||
|
||||
/**
|
||||
* Source connection id sequence number.
|
||||
*
|
||||
* @return sequence number
|
||||
*/
|
||||
public long seq() {
|
||||
return seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* The old local address of the network path.
|
||||
*
|
||||
* @return local
|
||||
*/
|
||||
public InetSocketAddress oldLocal() {
|
||||
return oldLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* The old remote address of the network path.
|
||||
*
|
||||
* @return local
|
||||
*/
|
||||
public InetSocketAddress oldRemote() {
|
||||
return oldRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReusedSourceConnectionId that = (ReusedSourceConnectionId) o;
|
||||
|
||||
if (seq != that.seq) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(oldLocal, that.oldLocal)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(oldRemote, that.oldRemote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + (int) (seq ^ (seq >>> 32));
|
||||
result = 31 * result + (oldLocal != null ? oldLocal.hashCode() : 0);
|
||||
result = 31 * result + (oldRemote != null ? oldRemote.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent.ReusedSourceConnectionId{" +
|
||||
"seq=" + seq +
|
||||
", oldLocal=" + oldLocal +
|
||||
", oldRemote=" + oldRemote +
|
||||
", local=" + local() +
|
||||
", remote=" + remote() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PeerMigrated extends QuicPathEvent {
|
||||
|
||||
/**
|
||||
* The connection observed that the remote migrated over the network path denoted by the pair of addresses,
|
||||
* i.e., non-probing packets have been received on this network path. This is a server side only event.
|
||||
* Note that this event is only raised if the path has been validated.
|
||||
*
|
||||
* @param local local address.
|
||||
* @param remote remote address.
|
||||
*/
|
||||
public PeerMigrated(InetSocketAddress local, InetSocketAddress remote) {
|
||||
super(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicPathEvent.PeerMigrated{" +
|
||||
"local=" + local() +
|
||||
", remote=" + remote() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Generate
|
||||
* <a href="https://www.ietf.org/archive/id/draft-ietf-quic-transport-29.html#name-calculating-a-stateless-res">
|
||||
* stateless reset tokens</a> to use.
|
||||
*/
|
||||
public interface QuicResetTokenGenerator {
|
||||
|
||||
/**
|
||||
* Generate a reset token to use for the given connection id. The returned token MUST be of length 16.
|
||||
* @param cid
|
||||
* @return
|
||||
*/
|
||||
ByteBuffer newResetToken(ByteBuffer cid);
|
||||
|
||||
/**
|
||||
* Return a {@link QuicResetTokenGenerator} which generates new reset tokens by signing the given input.
|
||||
*
|
||||
* @return a {@link QuicResetTokenGenerator} which generates new reset tokens by signing the given input.
|
||||
*/
|
||||
static QuicResetTokenGenerator signGenerator() {
|
||||
return HmacSignQuicResetTokenGenerator.INSTANCE;
|
||||
}
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link QuicCodecBuilder} that configures and builds a {@link ChannelHandler} that should be added to the
|
||||
* {@link io.netty.channel.ChannelPipeline} of a {@code QUIC} server.
|
||||
*/
|
||||
public final class QuicServerCodecBuilder extends QuicCodecBuilder<QuicServerCodecBuilder> {
|
||||
// The order in which ChannelOptions are applied is important they may depend on each other for validation
|
||||
// purposes.
|
||||
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
|
||||
private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
|
||||
private final Map<ChannelOption<?>, Object> streamOptions = new LinkedHashMap<>();
|
||||
private final Map<AttributeKey<?>, Object> streamAttrs = new HashMap<>();
|
||||
private ChannelHandler handler;
|
||||
private ChannelHandler streamHandler;
|
||||
private QuicConnectionIdGenerator connectionIdAddressGenerator;
|
||||
private QuicTokenHandler tokenHandler;
|
||||
private QuicResetTokenGenerator resetTokenGenerator;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public QuicServerCodecBuilder() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
private QuicServerCodecBuilder(QuicServerCodecBuilder builder) {
|
||||
super(builder);
|
||||
options.putAll(builder.options);
|
||||
attrs.putAll(builder.attrs);
|
||||
streamOptions.putAll(builder.streamOptions);
|
||||
streamAttrs.putAll(builder.streamAttrs);
|
||||
handler = builder.handler;
|
||||
streamHandler = builder.streamHandler;
|
||||
connectionIdAddressGenerator = builder.connectionIdAddressGenerator;
|
||||
tokenHandler = builder.tokenHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicServerCodecBuilder clone() {
|
||||
return new QuicServerCodecBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicChannel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*
|
||||
* @param option the {@link ChannelOption} to apply to the {@link QuicChannel}.
|
||||
* @param value the value of the option.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicServerCodecBuilder option(ChannelOption<T> option, T value) {
|
||||
Quic.updateOptions(options, option, value);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link QuicChannel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*
|
||||
* @param key the {@link AttributeKey} to apply to the {@link QuicChannel}.
|
||||
* @param value the value of the attribute.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicServerCodecBuilder attr(AttributeKey<T> key, T value) {
|
||||
Quic.updateAttributes(attrs, key, value);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicChannel} once created.
|
||||
*
|
||||
* @param handler the {@link ChannelHandler} that is added to the {@link QuicChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline}.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicServerCodecBuilder handler(ChannelHandler handler) {
|
||||
this.handler = ObjectUtil.checkNotNull(handler, "handler");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*
|
||||
* @param option the {@link ChannelOption} to apply to the {@link QuicStreamChannel}s.
|
||||
* @param value the value of the option.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicServerCodecBuilder streamOption(ChannelOption<T> option, T value) {
|
||||
Quic.updateOptions(streamOptions, option, value);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*
|
||||
* @param key the {@link AttributeKey} to apply to the {@link QuicStreamChannel}s.
|
||||
* @param value the value of the attribute.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicServerCodecBuilder streamAttr(AttributeKey<T> key, T value) {
|
||||
Quic.updateAttributes(streamAttrs, key, value);
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicStreamChannel} once created.
|
||||
*
|
||||
* @param streamHandler the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline}.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicServerCodecBuilder streamHandler(ChannelHandler streamHandler) {
|
||||
this.streamHandler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link QuicConnectionIdGenerator} to use.
|
||||
*
|
||||
* @param connectionIdAddressGenerator the {@link QuicConnectionIdGenerator} to use.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicServerCodecBuilder connectionIdAddressGenerator(
|
||||
QuicConnectionIdGenerator connectionIdAddressGenerator) {
|
||||
this.connectionIdAddressGenerator = connectionIdAddressGenerator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link QuicTokenHandler} that is used to generate and validate tokens or
|
||||
* {@code null} if no tokens should be used at all.
|
||||
*
|
||||
* @param tokenHandler the {@link QuicTokenHandler} to use.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicServerCodecBuilder tokenHandler(QuicTokenHandler tokenHandler) {
|
||||
this.tokenHandler = tokenHandler;
|
||||
return self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link QuicResetTokenGenerator} that is used to generate stateless reset tokens or
|
||||
* {@code null} if the default should be used.
|
||||
*
|
||||
* @param resetTokenGenerator the {@link QuicResetTokenGenerator} to use.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicServerCodecBuilder resetTokenGenerator(QuicResetTokenGenerator resetTokenGenerator) {
|
||||
this.resetTokenGenerator = resetTokenGenerator;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate() {
|
||||
super.validate();
|
||||
if (handler == null && streamHandler == null) {
|
||||
throw new IllegalStateException("handler and streamHandler not set");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ChannelHandler build(QuicheConfig config,
|
||||
Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||
Executor sslTaskExecutor,
|
||||
int localConnIdLength, FlushStrategy flushStrategy) {
|
||||
validate();
|
||||
QuicTokenHandler tokenHandler = this.tokenHandler;
|
||||
if (tokenHandler == null) {
|
||||
tokenHandler = NoQuicTokenHandler.INSTANCE;
|
||||
}
|
||||
QuicConnectionIdGenerator generator = connectionIdAddressGenerator;
|
||||
if (generator == null) {
|
||||
generator = QuicConnectionIdGenerator.signGenerator();
|
||||
}
|
||||
QuicResetTokenGenerator resetTokenGenerator = this.resetTokenGenerator;
|
||||
if (resetTokenGenerator == null) {
|
||||
resetTokenGenerator = QuicResetTokenGenerator.signGenerator();
|
||||
}
|
||||
ChannelHandler handler = this.handler;
|
||||
ChannelHandler streamHandler = this.streamHandler;
|
||||
return new QuicheQuicServerCodec(config, localConnIdLength, tokenHandler, generator, resetTokenGenerator,
|
||||
flushStrategy, sslEngineProvider, sslTaskExecutor, handler,
|
||||
Quic.toOptionsArray(options), Quic.toAttributesArray(attrs),
|
||||
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* Special {@link SslContext} that can be used for {@code QUIC}.
|
||||
*/
|
||||
public abstract class QuicSslContext extends SslContext {
|
||||
|
||||
@Override
|
||||
public abstract QuicSslEngine newEngine(ByteBufAllocator alloc);
|
||||
|
||||
@Override
|
||||
public abstract QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort);
|
||||
|
||||
@Override
|
||||
public abstract QuicSslSessionContext sessionContext();
|
||||
|
||||
static X509Certificate[] toX509Certificates0(InputStream stream)
|
||||
throws CertificateException {
|
||||
return SslContext.toX509Certificates(stream);
|
||||
}
|
||||
}
|
@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.util.KeyManagerFactoryWrapper;
|
||||
import io.netty.handler.ssl.util.TrustManagerFactoryWrapper;
|
||||
import io.netty.util.Mapping;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import java.io.File;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyStore;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* Builder for configuring a new SslContext for creation.
|
||||
*/
|
||||
public final class QuicSslContextBuilder {
|
||||
|
||||
/**
|
||||
* Special {@link X509ExtendedKeyManager} implementation which will just fail the certificate selection.
|
||||
* This is used as a "dummy" implementation when SNI is used as we should always select an other
|
||||
* {@link QuicSslContext} based on the provided hostname.
|
||||
*/
|
||||
private static final X509ExtendedKeyManager SNI_KEYMANAGER = new X509ExtendedKeyManager() {
|
||||
private final X509Certificate[] emptyCerts = new X509Certificate[0];
|
||||
private final String[] emptyStrings = new String[0];
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
return emptyStrings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
return emptyStrings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return emptyCerts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a builder for new client-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||
*/
|
||||
public static QuicSslContextBuilder forClient() {
|
||||
return new QuicSslContextBuilder(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||
*
|
||||
* @param keyFile a PKCS#8 private key file in PEM format
|
||||
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||
* password-protected
|
||||
* @param certChainFile an X.509 certificate chain file in PEM format
|
||||
* @see #keyManager(File, String, File)
|
||||
*/
|
||||
public static QuicSslContextBuilder forServer(
|
||||
File keyFile, String keyPassword, File certChainFile) {
|
||||
return new QuicSslContextBuilder(true).keyManager(keyFile, keyPassword, certChainFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||
*
|
||||
* @param key a PKCS#8 private key
|
||||
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||
* password-protected
|
||||
* @param certChain the X.509 certificate chain
|
||||
* @see #keyManager(File, String, File)
|
||||
*/
|
||||
public static QuicSslContextBuilder forServer(
|
||||
PrivateKey key, String keyPassword, X509Certificate... certChain) {
|
||||
return new QuicSslContextBuilder(true).keyManager(key, keyPassword, certChain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||
*
|
||||
* @param keyManagerFactory non-{@code null} factory for server's private key
|
||||
* @see #keyManager(KeyManagerFactory, String)
|
||||
*/
|
||||
public static QuicSslContextBuilder forServer(KeyManagerFactory keyManagerFactory, String password) {
|
||||
return new QuicSslContextBuilder(true).keyManager(keyManagerFactory, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for new server-side {@link QuicSslContext} with {@link KeyManager} that can be used for
|
||||
* {@code QUIC}.
|
||||
*
|
||||
* @param keyManager non-{@code null} KeyManager for server's private key
|
||||
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||
* password-protected
|
||||
*/
|
||||
public static QuicSslContextBuilder forServer(KeyManager keyManager, String keyPassword) {
|
||||
return new QuicSslContextBuilder(true).keyManager(keyManager, keyPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables support for
|
||||
* <a href="https://quicwg.org/ops-drafts/draft-ietf-quic-manageability.html#name-server-name-indication-sni">
|
||||
* SNI</a> on the server side.
|
||||
*
|
||||
* @param mapping the {@link Mapping} that is used to map names to the {@link QuicSslContext} to use.
|
||||
* Usually using {@link io.netty.util.DomainWildcardMappingBuilder} should be used
|
||||
* to create the {@link Mapping}.
|
||||
*/
|
||||
public static QuicSslContext buildForServerWithSni(Mapping<? super String, ? extends QuicSslContext> mapping) {
|
||||
return forServer(SNI_KEYMANAGER, null).sni(mapping).build();
|
||||
}
|
||||
|
||||
private final boolean forServer;
|
||||
private TrustManagerFactory trustManagerFactory;
|
||||
private String keyPassword;
|
||||
private KeyManagerFactory keyManagerFactory;
|
||||
private long sessionCacheSize = 20480;
|
||||
private long sessionTimeout = 300;
|
||||
private ClientAuth clientAuth = ClientAuth.NONE;
|
||||
private String[] applicationProtocols;
|
||||
private Boolean earlyData;
|
||||
private BoringSSLKeylog keylog;
|
||||
private Mapping<? super String, ? extends QuicSslContext> mapping;
|
||||
|
||||
private QuicSslContextBuilder(boolean forServer) {
|
||||
this.forServer = forServer;
|
||||
}
|
||||
|
||||
private QuicSslContextBuilder sni(Mapping<? super String, ? extends QuicSslContext> mapping) {
|
||||
this.mapping = checkNotNull(mapping, "mapping");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable / disable the usage of early data.
|
||||
*/
|
||||
public QuicSslContextBuilder earlyData(boolean enabled) {
|
||||
this.earlyData = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable / disable keylog. When enabled, TLS keys are logged to an internal logger named
|
||||
* "io.netty.handler.codec.quic.BoringSSLLogginKeylog" with DEBUG level, see
|
||||
* {@link BoringSSLKeylog} for detail, logging keys are following
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||
*/
|
||||
public QuicSslContextBuilder keylog(boolean enabled) {
|
||||
keylog(enabled ? BoringSSLLoggingKeylog.INSTANCE : null);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable / disable keylog. When enabled, TLS keys are logged to {@link BoringSSLKeylog#logKey(SSLEngine, String)}
|
||||
* logging keys are following
|
||||
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||
*/
|
||||
public QuicSslContextBuilder keylog(BoringSSLKeylog keylog) {
|
||||
this.keylog = keylog;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trusted certificates for verifying the remote endpoint's certificate. The file should
|
||||
* contain an X.509 certificate collection in PEM format. {@code null} uses the system default
|
||||
* which only works with Java 8u261 and later as these versions support TLS1.3,
|
||||
* see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
|
||||
* JDK 8u261 Update Release Notes</a>
|
||||
*/
|
||||
public QuicSslContextBuilder trustManager(File trustCertCollectionFile) {
|
||||
try {
|
||||
return trustManager(QuicheQuicSslContext.toX509Certificates0(trustCertCollectionFile));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("File does not contain valid certificates: "
|
||||
+ trustCertCollectionFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trusted certificates for verifying the remote endpoint's certificate. {@code null} uses the system default
|
||||
* which only works with Java 8u261 and later as these versions support TLS1.3,
|
||||
* see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
|
||||
* JDK 8u261 Update Release Notes</a>
|
||||
*/
|
||||
public QuicSslContextBuilder trustManager(X509Certificate... trustCertCollection) {
|
||||
try {
|
||||
return trustManager(QuicheQuicSslContext.buildTrustManagerFactory0(trustCertCollection));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default
|
||||
* which only works with Java 8u261 and later as these versions support TLS1.3,
|
||||
* see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
|
||||
* JDK 8u261 Update Release Notes</a>
|
||||
*/
|
||||
public QuicSslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) {
|
||||
this.trustManagerFactory = trustManagerFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single trusted manager for verifying the remote endpoint's certificate.
|
||||
* This is helpful when custom implementation of {@link TrustManager} is needed.
|
||||
* Internally, a simple wrapper of {@link TrustManagerFactory} that only produces this
|
||||
* specified {@link TrustManager} will be created, thus all the requirements specified in
|
||||
* {@link #trustManager(TrustManagerFactory trustManagerFactory)} also apply here.
|
||||
*/
|
||||
public QuicSslContextBuilder trustManager(TrustManager trustManager) {
|
||||
return trustManager(new TrustManagerFactoryWrapper(trustManager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
|
||||
* be {@code null} for client contexts, which disables mutual authentication.
|
||||
*
|
||||
* @param keyFile a PKCS#8 private key file in PEM format
|
||||
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||
* password-protected
|
||||
* @param keyCertChainFile an X.509 certificate chain file in PEM format
|
||||
*/
|
||||
public QuicSslContextBuilder keyManager(File keyFile, String keyPassword, File keyCertChainFile) {
|
||||
X509Certificate[] keyCertChain;
|
||||
PrivateKey key;
|
||||
try {
|
||||
keyCertChain = QuicheQuicSslContext.toX509Certificates0(keyCertChainFile);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
|
||||
}
|
||||
try {
|
||||
key = QuicheQuicSslContext.toPrivateKey0(keyFile, keyPassword);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
|
||||
}
|
||||
return keyManager(key, keyPassword, keyCertChain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifying certificate for this host. {@code keyCertChain} and {@code key} may
|
||||
* be {@code null} for client contexts, which disables mutual authentication.
|
||||
*
|
||||
* @param key a PKCS#8 private key file
|
||||
* @param keyPassword the password of the {@code key}, or {@code null} if it's not
|
||||
* password-protected
|
||||
* @param certChain an X.509 certificate chain
|
||||
*/
|
||||
public QuicSslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... certChain) {
|
||||
try {
|
||||
java.security.KeyStore ks = java.security.KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
ks.load(null);
|
||||
char[] pass = keyPassword == null ? new char[0]: keyPassword.toCharArray();
|
||||
ks.setKeyEntry("alias", key, pass, certChain);
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
|
||||
KeyManagerFactory.getDefaultAlgorithm());
|
||||
keyManagerFactory.init(ks, pass);
|
||||
return keyManager(keyManagerFactory, keyPassword);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for
|
||||
* client contexts, which disables mutual authentication.
|
||||
*/
|
||||
public QuicSslContextBuilder keyManager(KeyManagerFactory keyManagerFactory, String keyPassword) {
|
||||
this.keyPassword = keyPassword;
|
||||
this.keyManagerFactory = keyManagerFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single key manager managing the identity information of this host.
|
||||
* This is helpful when custom implementation of {@link KeyManager} is needed.
|
||||
* Internally, a wrapper of {@link KeyManagerFactory} that only produces this specified
|
||||
* {@link KeyManager} will be created, thus all the requirements specified in
|
||||
* {@link #keyManager(KeyManagerFactory, String)} also apply here.
|
||||
*/
|
||||
public QuicSslContextBuilder keyManager(KeyManager keyManager, String password) {
|
||||
return keyManager(new KeyManagerFactoryWrapper(keyManager), password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Application protocol negotiation configuration. {@code null} disables support.
|
||||
*/
|
||||
public QuicSslContextBuilder applicationProtocols(String... applicationProtocols) {
|
||||
this.applicationProtocols = applicationProtocols;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the cache used for storing SSL session objects. {@code 0} to use the
|
||||
* default value.
|
||||
*/
|
||||
public QuicSslContextBuilder sessionCacheSize(long sessionCacheSize) {
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
|
||||
* default value.
|
||||
*/
|
||||
public QuicSslContextBuilder sessionTimeout(long sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client authentication mode.
|
||||
*/
|
||||
public QuicSslContextBuilder clientAuth(ClientAuth clientAuth) {
|
||||
if (!forServer) {
|
||||
throw new UnsupportedOperationException("Only supported for server");
|
||||
}
|
||||
this.clientAuth = checkNotNull(clientAuth, "clientAuth");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link QuicSslContext} instance with configured settings that can be used for {@code QUIC}.
|
||||
*
|
||||
*/
|
||||
public QuicSslContext build() {
|
||||
if (forServer) {
|
||||
return new QuicheQuicSslContext(true, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
|
||||
keyManagerFactory, keyPassword, mapping, earlyData, keylog, applicationProtocols);
|
||||
} else {
|
||||
return new QuicheQuicSslContext(false, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
|
||||
keyManagerFactory, keyPassword, mapping, earlyData, keylog,
|
||||
applicationProtocols);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
/**
|
||||
* An {@link SSLEngine} that can be used for QUIC.
|
||||
*/
|
||||
public abstract class QuicSslEngine extends SSLEngine { }
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
|
||||
/**
|
||||
* {@link SSLSessionContext} which also supports advanced operations.
|
||||
*/
|
||||
public interface QuicSslSessionContext extends SSLSessionContext {
|
||||
|
||||
/**
|
||||
* Sets the {@link SslSessionTicketKey}s that should be used. The first key of the array is used for encryption
|
||||
* and decryption while the rest of the array is only used for decryption. This allows you to better handling
|
||||
* rotating of the keys. The rotating is the responsibility of the user.
|
||||
* If {@code null} is used for {@code keys} a key will automatically generated by the library and also rotated.
|
||||
*
|
||||
* @param keys the tickets to use.
|
||||
*/
|
||||
void setTicketKeys(SslSessionTicketKey... keys);
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@link SocketAddress} for QUIC stream.
|
||||
*/
|
||||
public final class QuicStreamAddress extends SocketAddress {
|
||||
|
||||
private final long streamId;
|
||||
|
||||
public QuicStreamAddress(long streamId) {
|
||||
this.streamId = streamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the stream.
|
||||
*
|
||||
* @return the id.
|
||||
*/
|
||||
public long streamId() {
|
||||
return streamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof QuicStreamAddress)) {
|
||||
return false;
|
||||
}
|
||||
QuicStreamAddress that = (QuicStreamAddress) o;
|
||||
return streamId == that.streamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicStreamAddress{" +
|
||||
"streamId=" + streamId +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelProgressivePromise;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.socket.DuplexChannel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* A QUIC stream.
|
||||
*/
|
||||
public interface QuicStreamChannel extends DuplexChannel {
|
||||
|
||||
/**
|
||||
* Should be added to a {@link ChannelFuture} when the output should be cleanly shutdown via a {@code FIN}. No more
|
||||
* writes will be allowed after this point.
|
||||
*/
|
||||
ChannelFutureListener SHUTDOWN_OUTPUT = f -> ((QuicStreamChannel) f.channel()).shutdownOutput();
|
||||
|
||||
@Override
|
||||
default ChannelFuture bind(SocketAddress socketAddress) {
|
||||
return pipeline().bind(socketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress) {
|
||||
return pipeline().connect(remoteAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||
return pipeline().connect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture disconnect() {
|
||||
return pipeline().disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture close() {
|
||||
return pipeline().close();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture deregister() {
|
||||
return pipeline().deregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture bind(SocketAddress localAddress, ChannelPromise channelPromise) {
|
||||
return pipeline().bind(localAddress, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise channelPromise) {
|
||||
return pipeline().connect(remoteAddress, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture connect(
|
||||
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise channelPromise) {
|
||||
return pipeline().connect(remoteAddress, localAddress, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture disconnect(ChannelPromise channelPromise) {
|
||||
return pipeline().disconnect(channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture close(ChannelPromise channelPromise) {
|
||||
return pipeline().close(channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture deregister(ChannelPromise channelPromise) {
|
||||
return pipeline().deregister(channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture write(Object msg) {
|
||||
return pipeline().write(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture write(Object msg, ChannelPromise channelPromise) {
|
||||
return pipeline().write(msg, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture writeAndFlush(Object msg, ChannelPromise channelPromise) {
|
||||
return pipeline().writeAndFlush(msg, channelPromise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture writeAndFlush(Object msg) {
|
||||
return pipeline().writeAndFlush(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelPromise newPromise() {
|
||||
return pipeline().newPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelProgressivePromise newProgressivePromise() {
|
||||
return pipeline().newProgressivePromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture newSucceededFuture() {
|
||||
return pipeline().newSucceededFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture newFailedFuture(Throwable cause) {
|
||||
return pipeline().newFailedFuture(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelPromise voidPromise() {
|
||||
return pipeline().voidPromise();
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture shutdownInput() {
|
||||
return shutdownInput(newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture shutdownInput(ChannelPromise promise) {
|
||||
return shutdownInput(0, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture shutdownOutput() {
|
||||
return shutdownOutput(newPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
default ChannelFuture shutdown() {
|
||||
return shutdown(newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for calling {@link #shutdownInput(int)} and {@link #shutdownInput(int)}.
|
||||
*
|
||||
* @param error the error to send.
|
||||
* @return the future that is notified on completion.
|
||||
*/
|
||||
default ChannelFuture shutdown(int error) {
|
||||
return shutdown(error, newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for calling {@link #shutdownInput(int, ChannelPromise)} and {@link #shutdownInput(int, ChannelPromise)}.
|
||||
*
|
||||
* @param error the error to send.
|
||||
* @param promise will be notified on completion.
|
||||
* @return the future that is notified on completion.
|
||||
*/
|
||||
ChannelFuture shutdown(int error, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Shutdown the input of the stream with the given error code. This means a {@code STOP_SENDING} frame will
|
||||
* be send to the remote peer and all data received will be discarded.
|
||||
*
|
||||
* @param error the error to send as part of the {@code STOP_SENDING} frame.
|
||||
* @return the future that is notified on completion.
|
||||
*/
|
||||
default ChannelFuture shutdownInput(int error) {
|
||||
return shutdownInput(error, newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the input of the stream with the given error code. This means a {@code STOP_SENDING} frame will
|
||||
* be send to the remote peer and all data received will be discarded.
|
||||
*
|
||||
* @param error the error to send as part of the {@code STOP_SENDING} frame.
|
||||
* @param promise will be notified on completion.
|
||||
* @return the future that is notified on completion.
|
||||
*/
|
||||
ChannelFuture shutdownInput(int error, ChannelPromise promise);
|
||||
|
||||
/**
|
||||
* Shutdown the output of the stream with the given error code. This means a {@code RESET_STREAM} frame will
|
||||
* be send to the remote peer and all data that is not sent yet will be discarded.
|
||||
*
|
||||
* <strong>Important:</strong>If you want to shutdown the output without sending a {@code RESET_STREAM} frame you
|
||||
* should use {@link #shutdownOutput()} which will shutdown the output by sending a {@code FIN} and so signal
|
||||
* a clean shutdown.
|
||||
*
|
||||
* @param error the error to send as part of the {@code RESET_STREAM} frame.
|
||||
* @return the future that is notified on completion.
|
||||
*/
|
||||
default ChannelFuture shutdownOutput(int error) {
|
||||
return shutdownOutput(error, newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the output of the stream with the given error code. This means a {@code RESET_STREAM} frame will
|
||||
* be send to the remote peer and all data that is not sent yet will be discarded.
|
||||
*
|
||||
* <strong>Important:</strong>If you want to shutdown the output without sending a {@code RESET_STREAM} frame you
|
||||
* should use {@link #shutdownOutput(ChannelPromise)} which will shutdown the output by sending a {@code FIN}
|
||||
* and so signal a clean shutdown.
|
||||
*
|
||||
* @param error the error to send as part of the {@code RESET_STREAM} frame.
|
||||
* @param promise will be notified on completion.
|
||||
* @return the future that is notified on completion.
|
||||
*/
|
||||
ChannelFuture shutdownOutput(int error, ChannelPromise promise);
|
||||
|
||||
@Override
|
||||
QuicStreamAddress localAddress();
|
||||
|
||||
@Override
|
||||
QuicStreamAddress remoteAddress();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the stream was created locally.
|
||||
*
|
||||
* @return {@code true} if created locally, {@code false} otherwise.
|
||||
*/
|
||||
boolean isLocalCreated();
|
||||
|
||||
/**
|
||||
* Returns the {@link QuicStreamType} of the stream.
|
||||
*
|
||||
* @return {@link QuicStreamType} of this stream.
|
||||
*/
|
||||
QuicStreamType type();
|
||||
|
||||
/**
|
||||
* The id of the stream.
|
||||
*
|
||||
* @return the stream id of this {@link QuicStreamChannel}.
|
||||
*/
|
||||
long streamId();
|
||||
|
||||
/**
|
||||
* The {@link QuicStreamPriority} if explicit set for the stream via {@link #updatePriority(QuicStreamPriority)} or
|
||||
* {@link #updatePriority(QuicStreamPriority, ChannelPromise)}. Otherwise {@code null}.
|
||||
*
|
||||
* @return the priority if any was set.
|
||||
*/
|
||||
QuicStreamPriority priority();
|
||||
|
||||
/**
|
||||
* Update the priority of the stream. A stream's priority determines the order in which stream data is sent
|
||||
* on the wire (streams with lower priority are sent first).
|
||||
*
|
||||
* @param priority the priority.
|
||||
* @return future that is notified once the operation completes.
|
||||
*/
|
||||
default ChannelFuture updatePriority(QuicStreamPriority priority) {
|
||||
return updatePriority(priority, newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the priority of the stream. A stream's priority determines the order in which stream data is sent
|
||||
* on the wire (streams with lower priority are sent first).
|
||||
*
|
||||
* @param priority the priority.
|
||||
* @param promise notified once operations completes.
|
||||
* @return future that is notified once the operation completes.
|
||||
*/
|
||||
ChannelFuture updatePriority(QuicStreamPriority priority, ChannelPromise promise);
|
||||
|
||||
@Override
|
||||
QuicChannel parent();
|
||||
|
||||
@Override
|
||||
QuicStreamChannel read();
|
||||
|
||||
@Override
|
||||
QuicStreamChannel flush();
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig config();
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Allows to bootstrap outgoing {@link QuicStreamChannel}s.
|
||||
*/
|
||||
public final class QuicStreamChannelBootstrap {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(QuicStreamChannelBootstrap.class);
|
||||
|
||||
private final QuicChannel parent;
|
||||
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
|
||||
private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
|
||||
private ChannelHandler handler;
|
||||
private QuicStreamType type = QuicStreamType.BIDIRECTIONAL;
|
||||
|
||||
/**
|
||||
* Creates a new instance which uses the given {@link QuicChannel} to bootstrap {@link QuicStreamChannel}s.
|
||||
*
|
||||
* @param parent the {@link QuicChannel} that is used.
|
||||
|
||||
*/
|
||||
QuicStreamChannelBootstrap(QuicChannel parent) {
|
||||
this.parent = ObjectUtil.checkNotNull(parent, "parent");
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
*
|
||||
* @param option the {@link ChannelOption} to apply to the {@link QuicStreamChannel}.
|
||||
* @param value the value of the option.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicStreamChannelBootstrap option(ChannelOption<T> option, T value) {
|
||||
Quic.updateOptions(options, option, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||
*
|
||||
* @param key the {@link AttributeKey} to apply to the {@link QuicChannel}.
|
||||
* @param value the value of the attribute.
|
||||
* @param <T> the type of the value.
|
||||
* @return this instance.
|
||||
*/
|
||||
public <T> QuicStreamChannelBootstrap attr(AttributeKey<T> key, T value) {
|
||||
Quic.updateAttributes(attrs, key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||
* {@link QuicStreamChannel} once created.
|
||||
*
|
||||
* @param streamHandler the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
|
||||
* {@link io.netty.channel.ChannelPipeline}.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicStreamChannelBootstrap handler(ChannelHandler streamHandler) {
|
||||
this.handler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link QuicStreamType} to use for the {@link QuicStreamChannel}, default is
|
||||
* {@link QuicStreamType#BIDIRECTIONAL}.
|
||||
*
|
||||
* @param type the {@link QuicStreamType} of the {@link QuicStreamChannel}.
|
||||
* @return this instance.
|
||||
*/
|
||||
public QuicStreamChannelBootstrap type(QuicStreamType type) {
|
||||
this.type = ObjectUtil.checkNotNull(type, "type");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuicStreamChannel} and notifies the {@link Future}.
|
||||
*
|
||||
* @return the {@link Future} that is notified once the operation completes.
|
||||
*/
|
||||
public Future<QuicStreamChannel> create() {
|
||||
return create(parent.eventLoop().newPromise());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuicStreamChannel} and notifies the {@link Future}.
|
||||
*
|
||||
* @param promise the {@link Promise} that is notified once the operation completes.
|
||||
* @return the {@link Future} that is notified once the operation completes.
|
||||
*/
|
||||
public Future<QuicStreamChannel> create(Promise<QuicStreamChannel> promise) {
|
||||
if (handler == null) {
|
||||
throw new IllegalStateException("streamHandler not set");
|
||||
}
|
||||
|
||||
return parent.createStream(type, new QuicStreamChannelBootstrapHandler(handler,
|
||||
Quic.toOptionsArray(options), Quic.toAttributesArray(attrs)), promise);
|
||||
}
|
||||
|
||||
private static final class QuicStreamChannelBootstrapHandler extends ChannelInitializer<QuicStreamChannel> {
|
||||
private final ChannelHandler streamHandler;
|
||||
private final Map.Entry<ChannelOption<?>, Object>[] streamOptions;
|
||||
private final Map.Entry<AttributeKey<?>, Object>[] streamAttrs;
|
||||
|
||||
QuicStreamChannelBootstrapHandler(ChannelHandler streamHandler,
|
||||
Map.Entry<ChannelOption<?>, Object>[] streamOptions,
|
||||
Map.Entry<AttributeKey<?>, Object>[] streamAttrs) {
|
||||
this.streamHandler = streamHandler;
|
||||
this.streamOptions = streamOptions;
|
||||
this.streamAttrs = streamAttrs;
|
||||
}
|
||||
@Override
|
||||
protected void initChannel(QuicStreamChannel ch) {
|
||||
Quic.setupChannel(ch, streamOptions, streamAttrs, streamHandler, logger);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.MessageSizeEstimator;
|
||||
import io.netty.channel.RecvByteBufAllocator;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
import io.netty.channel.socket.DuplexChannelConfig;
|
||||
|
||||
/**
|
||||
* {@link DuplexChannelConfig} for QUIC streams.
|
||||
*/
|
||||
public interface QuicStreamChannelConfig extends DuplexChannelConfig {
|
||||
/**
|
||||
* Set this to {@code true} if the {@link QuicStreamChannel} should read {@link QuicStreamFrame}s and fire these
|
||||
* through the {@link io.netty.channel.ChannelPipeline}, {@code false} if it uses {@link io.netty.buffer.ByteBuf}.
|
||||
*
|
||||
* @param readFrames {@code true} if {@link QuicStreamFrame}s should be used, {@code false} if
|
||||
* {@link io.netty.buffer.ByteBuf} should be used.
|
||||
* @return this instance itself.
|
||||
*
|
||||
*/
|
||||
QuicStreamChannelConfig setReadFrames(boolean readFrames);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the {@link QuicStreamChannel} will read {@link QuicStreamFrame}s and fire these through
|
||||
* the {@link io.netty.channel.ChannelPipeline}, {@code false} if it uses {@link io.netty.buffer.ByteBuf}.
|
||||
*
|
||||
* @return {@code true} if {@link QuicStreamFrame}s should be used, {@code false} if
|
||||
* {@link io.netty.buffer.ByteBuf} should be used.
|
||||
*/
|
||||
boolean isReadFrames();
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setAllowHalfClosure(boolean allowHalfClosure);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setWriteSpinCount(int writeSpinCount);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setAllocator(ByteBufAllocator allocator);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setAutoRead(boolean autoRead);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setAutoClose(boolean autoClose);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
|
||||
|
||||
@Override
|
||||
QuicStreamChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufHolder;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
/**
|
||||
* A QUIC STREAM_FRAME.
|
||||
*/
|
||||
public interface QuicStreamFrame extends ByteBufHolder {
|
||||
|
||||
/**
|
||||
* An empty {@link QuicStreamFrame} that has the {@code FIN} flag set.
|
||||
*/
|
||||
QuicStreamFrame EMPTY_FIN = new QuicStreamFrame() {
|
||||
@Override
|
||||
public boolean hasFin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame duplicate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame retainedDuplicate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame replace(ByteBuf content) {
|
||||
return new DefaultQuicStreamFrame(content, hasFin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame retain() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame retain(int increment) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame touch() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamFrame touch(Object hint) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf content() {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int decrement) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the frame has the FIN set, which means it notifies the remote peer that
|
||||
* there will be no more writing happen. {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if the FIN flag should be set, {@code false} otherwise.
|
||||
*/
|
||||
boolean hasFin();
|
||||
|
||||
@Override
|
||||
QuicStreamFrame copy();
|
||||
|
||||
@Override
|
||||
QuicStreamFrame duplicate();
|
||||
|
||||
@Override
|
||||
QuicStreamFrame retainedDuplicate();
|
||||
|
||||
@Override
|
||||
QuicStreamFrame replace(ByteBuf content);
|
||||
|
||||
@Override
|
||||
QuicStreamFrame retain();
|
||||
|
||||
@Override
|
||||
QuicStreamFrame retain(int increment);
|
||||
|
||||
@Override
|
||||
QuicStreamFrame touch();
|
||||
|
||||
@Override
|
||||
QuicStreamFrame touch(Object hint);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Generates and hands over the next stream id to use for a QUIC stream.
|
||||
*/
|
||||
final class QuicStreamIdGenerator {
|
||||
private long nextBidirectionalStreamId;
|
||||
private long nextUnidirectionalStreamId;
|
||||
|
||||
QuicStreamIdGenerator(boolean server) {
|
||||
// See https://quicwg.org/base-drafts/rfc9000.html#name-stream-types-and-identifier
|
||||
nextBidirectionalStreamId = server ? 1 : 0;
|
||||
nextUnidirectionalStreamId = server ? 3 : 2;
|
||||
}
|
||||
|
||||
long nextStreamId(boolean bidirectional) {
|
||||
if (bidirectional) {
|
||||
long stream = nextBidirectionalStreamId;
|
||||
nextBidirectionalStreamId += 4;
|
||||
return stream;
|
||||
}
|
||||
long stream = nextUnidirectionalStreamId;
|
||||
nextUnidirectionalStreamId += 4;
|
||||
return stream;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Event fired once the stream limit of a {@link QuicChannel} changes.
|
||||
*/
|
||||
public final class QuicStreamLimitChangedEvent implements QuicEvent {
|
||||
|
||||
static final QuicStreamLimitChangedEvent INSTANCE = new QuicStreamLimitChangedEvent();
|
||||
|
||||
private QuicStreamLimitChangedEvent() { }
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The priority of a {@link QuicStreamChannel}.
|
||||
*/
|
||||
public final class QuicStreamPriority {
|
||||
|
||||
private final int urgency;
|
||||
private final boolean incremental;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param urgency the urgency of the stream.
|
||||
* @param incremental {@code true} if incremental.
|
||||
*/
|
||||
public QuicStreamPriority(int urgency, boolean incremental) {
|
||||
this.urgency = ObjectUtil.checkInRange(urgency, 0, Byte.MAX_VALUE, "urgency");
|
||||
this.incremental = incremental;
|
||||
}
|
||||
|
||||
/**
|
||||
* The urgency of the stream. Smaller number means more urgent and so data will be send earlier.
|
||||
*
|
||||
* @return the urgency.
|
||||
*/
|
||||
public int urgency() {
|
||||
return urgency;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code true} if incremental, {@code false} otherwise.
|
||||
*
|
||||
* @return if incremental.
|
||||
*/
|
||||
public boolean isIncremental() {
|
||||
return incremental;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
QuicStreamPriority that = (QuicStreamPriority) o;
|
||||
return urgency == that.urgency && incremental == that.incremental;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(urgency, incremental);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QuicStreamPriority{" +
|
||||
"urgency=" + urgency +
|
||||
", incremental=" + incremental +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* The type of a {@link QuicStreamChannel}.
|
||||
*/
|
||||
public enum QuicStreamType {
|
||||
|
||||
/**
|
||||
* An unidirectional stream.
|
||||
*/
|
||||
UNIDIRECTIONAL,
|
||||
/**
|
||||
* A bidirectional stream.
|
||||
*/
|
||||
BIDIRECTIONAL
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Handle token related operations.
|
||||
*/
|
||||
public interface QuicTokenHandler {
|
||||
|
||||
/**
|
||||
* Generate a new token for the given destination connection id and address. This token is written to {@code out}.
|
||||
* If no token should be generated and so no token validation should take place at all this method should return
|
||||
* {@code false}.
|
||||
*
|
||||
* @param out {@link ByteBuf} into which the token will be written.
|
||||
* @param dcid the destination connection id.
|
||||
* @param address the {@link InetSocketAddress} of the sender.
|
||||
* @return {@code true} if a token was written and so validation should happen, {@code false} otherwise.
|
||||
*/
|
||||
boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address);
|
||||
|
||||
/**
|
||||
* Validate the token and return the offset, {@code -1} is returned if the token is not valid.
|
||||
*
|
||||
* @param token the {@link ByteBuf} that contains the token. The ownership is not transferred.
|
||||
* @param address the {@link InetSocketAddress} of the sender.
|
||||
* @return the start index after the token or {@code -1} if the token was not valid.
|
||||
*/
|
||||
int validateToken(ByteBuf token, InetSocketAddress address);
|
||||
|
||||
/**
|
||||
* Return the maximal token length.
|
||||
*
|
||||
* @return the maximal supported token length.
|
||||
*/
|
||||
int maxTokenLength();
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
/**
|
||||
* Transport parameters for QUIC.
|
||||
*/
|
||||
public interface QuicTransportParameters {
|
||||
|
||||
/**
|
||||
* The maximum idle timeout.
|
||||
* @return timeout.
|
||||
*/
|
||||
long maxIdleTimeout();
|
||||
|
||||
/**
|
||||
* The maximum UDP payload size.
|
||||
*
|
||||
* @return maximum payload size.
|
||||
*/
|
||||
long maxUdpPayloadSize();
|
||||
|
||||
/**
|
||||
* The initial flow control maximum data for the connection.
|
||||
*
|
||||
* @return flowcontrol.
|
||||
*/
|
||||
long initialMaxData();
|
||||
|
||||
/**
|
||||
* The initial flow control maximum data for local bidirectional streams.
|
||||
*
|
||||
* @return flowcontrol.
|
||||
*/
|
||||
long initialMaxStreamDataBidiLocal();
|
||||
|
||||
/**
|
||||
* The initial flow control maximum data for remote bidirectional streams.
|
||||
*
|
||||
* @return flowcontrol.
|
||||
*/
|
||||
long initialMaxStreamDataBidiRemote();
|
||||
|
||||
/**
|
||||
* The initial flow control maximum data for unidirectional streams.
|
||||
*
|
||||
* @return flowcontrol.
|
||||
*/
|
||||
long initialMaxStreamDataUni();
|
||||
|
||||
|
||||
/**
|
||||
* The initial maximum bidirectional streams.
|
||||
*
|
||||
* @return streams.
|
||||
*/
|
||||
long initialMaxStreamsBidi();
|
||||
|
||||
/**
|
||||
* The initial maximum unidirectional streams.
|
||||
*
|
||||
* @return streams.
|
||||
*/
|
||||
long initialMaxStreamsUni();
|
||||
|
||||
/**
|
||||
* The ACK delay exponent
|
||||
*
|
||||
* @return exponent.
|
||||
*/
|
||||
long ackDelayExponent();
|
||||
|
||||
/**
|
||||
* The max ACK delay.
|
||||
*
|
||||
* @return delay.
|
||||
*/
|
||||
long maxAckDelay();
|
||||
|
||||
/**
|
||||
* Whether active migration is disabled.
|
||||
*
|
||||
* @return disabled.
|
||||
*/
|
||||
boolean disableActiveMigration();
|
||||
|
||||
/**
|
||||
* The active connection ID limit.
|
||||
*
|
||||
* @return limit.
|
||||
*/
|
||||
long activeConnIdLimit();
|
||||
|
||||
/**
|
||||
* DATAGRAM frame extension parameter, if any.
|
||||
*
|
||||
* @return param.
|
||||
*/
|
||||
long maxDatagramFrameSize();
|
||||
}
|
@ -0,0 +1,870 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.util.internal.ClassInitializerUtil;
|
||||
import io.netty.util.internal.NativeLibraryLoader;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
final class Quiche {
|
||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Quiche.class);
|
||||
private static final boolean DEBUG_LOGGING_ENABLED = logger.isDebugEnabled();
|
||||
|
||||
static {
|
||||
// Preload all classes that will be used in the OnLoad(...) function of JNI to eliminate the possiblity of a
|
||||
// class-loader deadlock. This is a workaround for https://github.com/netty/netty/issues/11209.
|
||||
|
||||
// This needs to match all the classes that are loaded via NETTY_JNI_UTIL_LOAD_CLASS or looked up via
|
||||
// NETTY_JNI_UTIL_FIND_CLASS.
|
||||
ClassInitializerUtil.tryLoadClasses(Quiche.class,
|
||||
// netty_quic_boringssl
|
||||
byte[].class, String.class, BoringSSLCertificateCallback.class,
|
||||
BoringSSLCertificateVerifyCallback.class, BoringSSLHandshakeCompleteCallback.class,
|
||||
|
||||
//netty_quic_quiche
|
||||
QuicheLogger.class
|
||||
);
|
||||
|
||||
try {
|
||||
// First, try calling a side-effect free JNI method to see if the library was already
|
||||
// loaded by the application.
|
||||
quiche_version();
|
||||
} catch (UnsatisfiedLinkError ignore) {
|
||||
// The library was not previously loaded, load it now.
|
||||
loadNativeLibrary();
|
||||
}
|
||||
|
||||
// Let's enable debug logging for quiche if its enabled in our logger.
|
||||
if (DEBUG_LOGGING_ENABLED) {
|
||||
quiche_enable_debug_logging(new QuicheLogger(logger));
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadNativeLibrary() {
|
||||
// This needs to be kept in sync with what is defined in netty_quic_quiche.c
|
||||
String libName = "netty_quiche";
|
||||
ClassLoader cl = PlatformDependent.getClassLoader(Quiche.class);
|
||||
|
||||
if (!PlatformDependent.isAndroid()) {
|
||||
libName += '_' + PlatformDependent.normalizedOs()
|
||||
+ '_' + PlatformDependent.normalizedArch();
|
||||
}
|
||||
|
||||
try {
|
||||
NativeLibraryLoader.load(libName, cl);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.debug("Failed to load {}", libName, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
static final short AF_INET = (short) QuicheNativeStaticallyReferencedJniMethods.afInet();
|
||||
static final short AF_INET6 = (short) QuicheNativeStaticallyReferencedJniMethods.afInet6();
|
||||
static final int SIZEOF_SOCKADDR_STORAGE = QuicheNativeStaticallyReferencedJniMethods.sizeofSockaddrStorage();
|
||||
static final int SIZEOF_SOCKADDR_IN = QuicheNativeStaticallyReferencedJniMethods.sizeofSockaddrIn();
|
||||
static final int SIZEOF_SOCKADDR_IN6 = QuicheNativeStaticallyReferencedJniMethods.sizeofSockaddrIn6();
|
||||
static final int SOCKADDR_IN_OFFSETOF_SIN_FAMILY =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrInOffsetofSinFamily();
|
||||
static final int SOCKADDR_IN_OFFSETOF_SIN_PORT =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrInOffsetofSinPort();
|
||||
static final int SOCKADDR_IN_OFFSETOF_SIN_ADDR =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrInOffsetofSinAddr();
|
||||
static final int IN_ADDRESS_OFFSETOF_S_ADDR = QuicheNativeStaticallyReferencedJniMethods.inAddressOffsetofSAddr();
|
||||
static final int SOCKADDR_IN6_OFFSETOF_SIN6_FAMILY =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Family();
|
||||
static final int SOCKADDR_IN6_OFFSETOF_SIN6_PORT =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Port();
|
||||
static final int SOCKADDR_IN6_OFFSETOF_SIN6_FLOWINFO =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Flowinfo();
|
||||
static final int SOCKADDR_IN6_OFFSETOF_SIN6_ADDR =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Addr();
|
||||
static final int SOCKADDR_IN6_OFFSETOF_SIN6_SCOPE_ID =
|
||||
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6ScopeId();
|
||||
static final int IN6_ADDRESS_OFFSETOF_S6_ADDR =
|
||||
QuicheNativeStaticallyReferencedJniMethods.in6AddressOffsetofS6Addr();
|
||||
static final int SIZEOF_SOCKLEN_T = QuicheNativeStaticallyReferencedJniMethods.sizeofSocklenT();
|
||||
static final int SIZEOF_SIZE_T = QuicheNativeStaticallyReferencedJniMethods.sizeofSizeT();
|
||||
|
||||
static final int SIZEOF_TIMESPEC = QuicheNativeStaticallyReferencedJniMethods.sizeofTimespec();
|
||||
|
||||
static final int SIZEOF_TIME_T = QuicheNativeStaticallyReferencedJniMethods.sizeofTimeT();
|
||||
static final int SIZEOF_LONG = QuicheNativeStaticallyReferencedJniMethods.sizeofLong();
|
||||
|
||||
static final int TIMESPEC_OFFSETOF_TV_SEC =
|
||||
QuicheNativeStaticallyReferencedJniMethods.timespecOffsetofTvSec();
|
||||
|
||||
static final int TIMESPEC_OFFSETOF_TV_NSEC =
|
||||
QuicheNativeStaticallyReferencedJniMethods.timespecOffsetofTvNsec();
|
||||
|
||||
static final int QUICHE_RECV_INFO_OFFSETOF_FROM =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofFrom();
|
||||
static final int QUICHE_RECV_INFO_OFFSETOF_FROM_LEN =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofFromLen();
|
||||
|
||||
static final int QUICHE_RECV_INFO_OFFSETOF_TO =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofTo();
|
||||
static final int QUICHE_RECV_INFO_OFFSETOF_TO_LEN =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofToLen();
|
||||
|
||||
static final int SIZEOF_QUICHE_RECV_INFO = QuicheNativeStaticallyReferencedJniMethods.sizeofQuicheRecvInfo();
|
||||
static final int QUICHE_SEND_INFO_OFFSETOF_TO =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofTo();
|
||||
static final int QUICHE_SEND_INFO_OFFSETOF_TO_LEN =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofToLen();
|
||||
|
||||
static final int QUICHE_SEND_INFO_OFFSETOF_FROM =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofFrom();
|
||||
static final int QUICHE_SEND_INFO_OFFSETOF_FROM_LEN =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofFromLen();
|
||||
|
||||
static final int QUICHE_SEND_INFO_OFFSETOF_AT =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofAt();
|
||||
static final int SIZEOF_QUICHE_SEND_INFO = QuicheNativeStaticallyReferencedJniMethods.sizeofQuicheSendInfo();
|
||||
|
||||
static final int QUICHE_PROTOCOL_VERSION = QuicheNativeStaticallyReferencedJniMethods.quiche_protocol_version();
|
||||
static final int QUICHE_MAX_CONN_ID_LEN = QuicheNativeStaticallyReferencedJniMethods.quiche_max_conn_id_len();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L266">QUICHE_SHUTDOWN_READ</a>.
|
||||
*/
|
||||
static final int QUICHE_SHUTDOWN_READ = QuicheNativeStaticallyReferencedJniMethods.quiche_shutdown_read();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L267">QUICHE_SHUTDOWN_WRITE</a>.
|
||||
*/
|
||||
static final int QUICHE_SHUTDOWN_WRITE = QuicheNativeStaticallyReferencedJniMethods.quiche_shutdown_write();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L59">QUICHE_ERR_DONE</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_DONE = QuicheNativeStaticallyReferencedJniMethods.quiche_err_done();
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L62">QUICHE_ERR_BUFFER_TOO_SHORT</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_BUFFER_TOO_SHORT =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_buffer_too_short();
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L65">QUICHE_ERR_UNKNOWN_VERSION</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_UNKNOWN_VERSION =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_unknown_version();
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L69">QUICHE_ERR_INVALID_FRAME</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_INVALID_FRAME = QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_frame();
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L72">QUICHE_ERR_INVALID_PACKET</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_INVALID_PACKET = QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_packet();
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L76">QUICHE_ERR_INVALID_STATE</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_INVALID_STATE = QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_state();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L80">
|
||||
* QUICHE_ERR_INVALID_STREAM_STATE</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_INVALID_STREAM_STATE =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_stream_state();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L83">
|
||||
* QUICHE_ERR_INVALID_TRANSPORT_PARAM</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_INVALID_TRANSPORT_PARAM =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_transport_param();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L86">
|
||||
* QUICHE_ERR_CRYPTO_FAIL</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_CRYPTO_FAIL = QuicheNativeStaticallyReferencedJniMethods.quiche_err_crypto_fail();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L89">
|
||||
* QUICHE_ERR_TLS_FAIL</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_TLS_FAIL = QuicheNativeStaticallyReferencedJniMethods.quiche_err_tls_fail();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L92">
|
||||
* QUICHE_ERR_FLOW_CONTROL</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_FLOW_CONTROL = QuicheNativeStaticallyReferencedJniMethods.quiche_err_flow_control();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#95">
|
||||
* QUICHE_ERR_STREAM_LIMIT</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_STREAM_LIMIT = QuicheNativeStaticallyReferencedJniMethods.quiche_err_stream_limit();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#98">
|
||||
* QUICHE_ERR_FINAL_SIZE</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_FINAL_SIZE = QuicheNativeStaticallyReferencedJniMethods.quiche_err_final_size();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#101">
|
||||
* QUICHE_ERR_CONGESTION_CONTROL</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_CONGESTION_CONTROL =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_congestion_control();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/e179f2aa52d475e037fe47d7b8466b3afde12d76/
|
||||
* include/quiche.h#L101">QUICHE_ERR_STREAM_STOPPED</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_STREAM_RESET =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_stream_reset();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L98">
|
||||
* QUICHE_ERR_STREAM_STOPPED</a>.
|
||||
*/
|
||||
static final int QUICHE_ERR_STREAM_STOPPED =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_stream_stopped();
|
||||
|
||||
|
||||
// Too many identifiers were provided.
|
||||
static final int QUICHE_ERR_ID_LIMIT =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_id_limit();
|
||||
|
||||
// Not enough available identifiers.
|
||||
static final int QUICHE_ERR_OUT_OF_IDENTIFIERS =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_out_of_identifiers();
|
||||
|
||||
// Error in key update.
|
||||
static final int QUICHE_ERR_KEY_UPDATE =
|
||||
QuicheNativeStaticallyReferencedJniMethods.quiche_err_key_update();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L176">
|
||||
* QUICHE_CC_RENO</a>.
|
||||
*/
|
||||
static final int QUICHE_CC_RENO = QuicheNativeStaticallyReferencedJniMethods.quiche_cc_reno();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L177">
|
||||
* QUICHE_CC_CUBIC</a>.
|
||||
*/
|
||||
static final int QUICHE_CC_CUBIC = QuicheNativeStaticallyReferencedJniMethods.quiche_cc_cubic();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.16.0/quiche/include/quiche.h#L206">
|
||||
* QUICHE_CC_BBR</a>.
|
||||
*/
|
||||
static final int QUICHE_CC_BBR = QuicheNativeStaticallyReferencedJniMethods.quiche_cc_bbr();
|
||||
|
||||
|
||||
static final int QUICHE_PATH_EVENT_NEW = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_new();
|
||||
static final int QUICHE_PATH_EVENT_VALIDATED = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_validated();
|
||||
static final int QUICHE_PATH_EVENT_FAILED_VALIDATION = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_failed_validation();
|
||||
static final int QUICHE_PATH_EVENT_CLOSED = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_closed();
|
||||
static final int QUICHE_PATH_EVENT_REUSED_SOURCE_CONNECTION_ID = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_reused_source_connection_id();
|
||||
static final int QUICHE_PATH_EVENT_PEER_MIGRATED = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_peer_migrated();
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L105">quiche_version</a>.
|
||||
*/
|
||||
static native String quiche_version();
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L227">quiche_version_is_supported</a>.
|
||||
*/
|
||||
static native boolean quiche_version_is_supported(int version);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L196">quiche_header_info</a>.
|
||||
*/
|
||||
static native int quiche_header_info(long bufAddr, int bufLength, int dcil, long versionAddr, long typeAddr,
|
||||
long scidAddr, long scidLenAddr, long dcidAddr, long dcidLenAddr,
|
||||
long tokenAddr, long tokenLenAddr);
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L215">quiche_negotiate_version</a>.
|
||||
*/
|
||||
static native int quiche_negotiate_version(
|
||||
long scidAddr, int scidLen, long dcidAddr, int dcidLen, long outAddr, int outLen);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L220">quiche_retry</a>.
|
||||
*/
|
||||
static native int quiche_retry(long scidAddr, int scidLen, long dcidAddr, int dcidLen, long newScidAddr,
|
||||
int newScidLen, long tokenAddr, int tokenLen, int version, long outAddr, int outLen);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L229">quiche_conn_new_with_tls</a>.
|
||||
*/
|
||||
static native long quiche_conn_new_with_tls(long scidAddr, int scidLen, long odcidAddr, int odcidLen,
|
||||
long localAddr, int localLen,
|
||||
long peerAddr, int peerLen,
|
||||
long configAddr, long ssl, boolean isServer);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/master/include/quiche.h#L248">
|
||||
* quiche_conn_set_qlog_path</a>.
|
||||
*/
|
||||
static native boolean quiche_conn_set_qlog_path(long connAddr, String path, String logTitle, String logDescription);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L249">quiche_conn_recv</a>.
|
||||
*/
|
||||
static native int quiche_conn_recv(long connAddr, long bufAddr, int bufLen, long infoAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L262">quiche_conn_send</a>.
|
||||
*/
|
||||
static native int quiche_conn_send(long connAddr, long outAddr, int outLen, long infoAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L373">quiche_conn_free</a>.
|
||||
*/
|
||||
static native void quiche_conn_free(long connAddr);
|
||||
|
||||
static QuicConnectionCloseEvent quiche_conn_peer_error(long connAddr) {
|
||||
Object[] error = quiche_conn_peer_error0(connAddr);
|
||||
if (error == null) {
|
||||
return null;
|
||||
}
|
||||
return new QuicConnectionCloseEvent((Boolean) error[0], (Integer) error[1], (byte[]) error[2]);
|
||||
}
|
||||
|
||||
private static native Object[] quiche_conn_peer_error0(long connAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L330">
|
||||
* quiche_conn_peer_streams_left_bidi</a>.
|
||||
*/
|
||||
static native long quiche_conn_peer_streams_left_bidi(long connAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L334">
|
||||
* quiche_conn_peer_streams_left_uni</a>.
|
||||
*/
|
||||
static native long quiche_conn_peer_streams_left_uni(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L275">quiche_conn_stream_priority</a>.
|
||||
*/
|
||||
static native int quiche_conn_stream_priority(
|
||||
long connAddr, long streamId, byte urgency, boolean incremental);
|
||||
|
||||
static native int quiche_conn_send_quantum(long connAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L309">quiche_conn_trace_id</a>.
|
||||
*/
|
||||
static native byte[] quiche_conn_trace_id(long connAddr);
|
||||
|
||||
static native byte[] quiche_conn_source_id(long connAddr);
|
||||
|
||||
static native byte[] quiche_conn_destination_id(long connAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L258">quiche_conn_stream_recv</a>.
|
||||
*/
|
||||
static native int quiche_conn_stream_recv(long connAddr, long streamId, long outAddr, int bufLen, long finAddr);
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L262">quiche_conn_stream_send</a>.
|
||||
*/
|
||||
static native int quiche_conn_stream_send(long connAddr, long streamId, long bufAddr, int bufLen, boolean fin);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L271">quiche_conn_stream_shutdown</a>.
|
||||
*/
|
||||
static native int quiche_conn_stream_shutdown(long connAddr, long streamId, int direction, long err);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L274">quiche_conn_stream_capacity</a>.
|
||||
*/
|
||||
static native int quiche_conn_stream_capacity(long connAddr, long streamId);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L277">quiche_conn_stream_finished</a>.
|
||||
*/
|
||||
static native boolean quiche_conn_stream_finished(long connAddr, long streamId);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L297">quiche_conn_close</a>.
|
||||
*/
|
||||
static native int quiche_conn_close(long connAddr, boolean app, long err, long reasonAddr, int reasonLen);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L305">quiche_conn_is_established</a>.
|
||||
*/
|
||||
static native boolean quiche_conn_is_established(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L309">quiche_conn_is_in_early_data</a>.
|
||||
*/
|
||||
static native boolean quiche_conn_is_in_early_data(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L312">quiche_conn_is_closed</a>.
|
||||
*/
|
||||
static native boolean quiche_conn_is_closed(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/
|
||||
* e9f59a55f5b56999d0e609a73aa8f1b450878a0c/include/quiche.h#L384">quiche_conn_is_timed_out</a>.
|
||||
*/
|
||||
static native boolean quiche_conn_is_timed_out(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L361">quiche_conn_stats</a>.
|
||||
* The implementation relies on all fields of
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L340">quiche_stats</a> being numerical.
|
||||
* The assumption made allows passing primitive array rather than dealing with objects.
|
||||
*/
|
||||
static native long[] quiche_conn_stats(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/master/quiche/include/quiche.h#L567C65-L567C88">
|
||||
* quiche_conn_stats</a>.
|
||||
*/
|
||||
static native long[] quiche_conn_peer_transport_params(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L288">quiche_conn_timeout_as_nanos</a>.
|
||||
*/
|
||||
static native long quiche_conn_timeout_as_nanos(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L294">quiche_conn_on_timeout</a>.
|
||||
*/
|
||||
static native void quiche_conn_on_timeout(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L282">quiche_conn_readable</a>.
|
||||
*/
|
||||
static native long quiche_conn_readable(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L285">quiche_conn_writable</a>.
|
||||
*/
|
||||
static native long quiche_conn_writable(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L329">quiche_stream_iter_next</a>.
|
||||
*
|
||||
* This method will fill the {@code streamIds} array and return the number of streams that were filled into
|
||||
* the array. If the number is the same as the length of the array you should call it again until it returns
|
||||
* less to ensure you process all the streams later on.
|
||||
*/
|
||||
static native int quiche_stream_iter_next(long iterAddr, long[] streamIds);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L332">quiche_stream_iter_free</a>.
|
||||
*
|
||||
*/
|
||||
static native void quiche_stream_iter_free(long iterAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L358">
|
||||
* quiche_conn_dgram_max_writable_len</a>.
|
||||
*/
|
||||
static native int quiche_conn_dgram_max_writable_len(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href=https://github.com/cloudflare/quiche/blob/
|
||||
* 9d0c677ef1411b24d720b5c8b73bcc94b5535c29/include/quiche.h#L381">
|
||||
* quiche_conn_dgram_recv_front_len</a>.
|
||||
*/
|
||||
static native int quiche_conn_dgram_recv_front_len(long connAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L361">
|
||||
* quiche_conn_dgram_recv</a>.
|
||||
*/
|
||||
static native int quiche_conn_dgram_recv(long connAddr, long buf, int size);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L3651">
|
||||
* quiche_conn_dgram_send</a>.
|
||||
*/
|
||||
static native int quiche_conn_dgram_send(long connAddr, long buf, int size);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.10.0/include/quiche.h#L267">
|
||||
* quiche_conn_set_session</a>.
|
||||
*/
|
||||
static native int quiche_conn_set_session(long connAddr, byte[] sessionBytes);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.10.0/include/quiche.h#L328">
|
||||
* quiche_conn_max_send_udp_payload_size</a>.
|
||||
*/
|
||||
static native int quiche_conn_max_send_udp_payload_size(long connAddr);
|
||||
|
||||
static native int quiche_conn_scids_left(long connAddr);
|
||||
|
||||
static native long quiche_conn_new_scid(long connAddr, long scidAddr, int scidLen, byte[] resetToken, boolean retire_if_needed, long seq);
|
||||
|
||||
static native byte[] quiche_conn_retired_scid_next(long connAddr);
|
||||
|
||||
static native long quiche_conn_path_event_next(long connAddr);
|
||||
static native int quiche_path_event_type(long pathEvent);
|
||||
static native void quiche_path_event_free(long pathEvent);
|
||||
static native Object[] quiche_path_event_new(long pathEvent);
|
||||
static native Object[] quiche_path_event_validated(long pathEvent);
|
||||
static native Object[] quiche_path_event_failed_validation(long pathEvent);
|
||||
static native Object[] quiche_path_event_closed(long pathEvent);
|
||||
static native Object[] quiche_path_event_reused_source_connection_id(long pathEvent);
|
||||
static native Object[] quiche_path_event_peer_migrated(long pathEvent);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L115">quiche_config_new</a>.
|
||||
*/
|
||||
static native long quiche_config_new(int version);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#129">
|
||||
* quiche_config_grease</a>.
|
||||
*/
|
||||
static native void quiche_config_grease(long configAddr, boolean value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#143">
|
||||
* quiche_config_set_max_idle_timeout</a>.
|
||||
*/
|
||||
static native void quiche_config_set_max_idle_timeout(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L150">
|
||||
* quiche_config_set_max_recv_udp_payload_size</a>.
|
||||
*/
|
||||
static native void quiche_config_set_max_recv_udp_payload_size(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L153">
|
||||
* quiche_config_set_max_recv_udp_payload_size</a>.
|
||||
*/
|
||||
static native void quiche_config_set_max_send_udp_payload_size(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#149">
|
||||
* quiche_config_set_initial_max_data</a>.
|
||||
*/
|
||||
static native void quiche_config_set_initial_max_data(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#152">
|
||||
* quiche_config_set_initial_max_stream_data_bidi_local</a>.
|
||||
*/
|
||||
static native void quiche_config_set_initial_max_stream_data_bidi_local(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#155">
|
||||
* quiche_config_set_initial_max_stream_data_bidi_remote</a>.
|
||||
*/
|
||||
static native void quiche_config_set_initial_max_stream_data_bidi_remote(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#158">
|
||||
* quiche_config_set_initial_max_stream_data_uni</a>.
|
||||
*/
|
||||
static native void quiche_config_set_initial_max_stream_data_uni(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#161">
|
||||
* quiche_config_set_initial_max_streams_bidi</a>.
|
||||
*/
|
||||
static native void quiche_config_set_initial_max_streams_bidi(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#164">
|
||||
* quiche_config_set_initial_max_streams_uni</a>.
|
||||
*/
|
||||
static native void quiche_config_set_initial_max_streams_uni(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#167">
|
||||
* quiche_config_set_ack_delay_exponent</a>.
|
||||
*/
|
||||
static native void quiche_config_set_ack_delay_exponent(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#170">
|
||||
* quiche_config_set_max_ack_delay</a>.
|
||||
*/
|
||||
static native void quiche_config_set_max_ack_delay(long configAddr, long value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#173">
|
||||
* quiche_config_set_disable_active_migration</a>.
|
||||
*/
|
||||
static native void quiche_config_set_disable_active_migration(long configAddr, boolean value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#181">
|
||||
* quiche_config_set_cc_algorithm</a>.
|
||||
*/
|
||||
static native void quiche_config_set_cc_algorithm(long configAddr, int algo);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#184">
|
||||
* quiche_config_enable_hystart</a>.
|
||||
*/
|
||||
static native void quiche_config_enable_hystart(long configAddr, boolean value);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L187">
|
||||
* quiche_config_enable_dgram</a>.
|
||||
*/
|
||||
static native void quiche_config_enable_dgram(long configAddr, boolean enable,
|
||||
int recv_queue_len, int send_queue_len);
|
||||
|
||||
// Sets the limit of active connection IDs.
|
||||
static native void quiche_config_set_active_connection_id_limit(long configAddr, long value);
|
||||
|
||||
// Sets the initial stateless reset token.
|
||||
static native void quiche_config_set_stateless_reset_token(long configAddr, byte[] token);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#192">
|
||||
* quiche_config_free</a>.
|
||||
*/
|
||||
static native void quiche_config_free(long configAddr);
|
||||
|
||||
/**
|
||||
* See
|
||||
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L108">quiche_config_new</a>.
|
||||
*/
|
||||
private static native void quiche_enable_debug_logging(QuicheLogger logger);
|
||||
|
||||
private static native long buffer_memory_address(ByteBuffer buffer);
|
||||
|
||||
static native int sockaddr_cmp(long addr, long addr2);
|
||||
|
||||
/**
|
||||
* Returns the memory address if the {@link ByteBuf} taking the readerIndex into account.
|
||||
*
|
||||
* @param buf the {@link ByteBuf} of which we want to obtain the memory address
|
||||
* (taking its {@link ByteBuf#readerIndex()} into account).
|
||||
* @return the memory address of this {@link ByteBuf}s readerIndex.
|
||||
*/
|
||||
static long readerMemoryAddress(ByteBuf buf) {
|
||||
return memoryAddress(buf, buf.readerIndex(), buf.readableBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory address if the {@link ByteBuf} taking the writerIndex into account.
|
||||
*
|
||||
* @param buf the {@link ByteBuf} of which we want to obtain the memory address
|
||||
* (taking its {@link ByteBuf#writerIndex()} into account).
|
||||
* @return the memory address of this {@link ByteBuf}s writerIndex.
|
||||
*/
|
||||
static long writerMemoryAddress(ByteBuf buf) {
|
||||
return memoryAddress(buf, buf.writerIndex(), buf.writableBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory address if the {@link ByteBuf} taking the offset into account.
|
||||
*
|
||||
* @param buf the {@link ByteBuf} of which we want to obtain the memory address
|
||||
* (taking the {@code offset} into account).
|
||||
* @param offset the offset of the memory address.
|
||||
* @param len the length of the {@link ByteBuf}.
|
||||
* @return the memory address of this {@link ByteBuf}s offset.
|
||||
*/
|
||||
static long memoryAddress(ByteBuf buf, int offset, int len) {
|
||||
assert buf.isDirect();
|
||||
if (buf.hasMemoryAddress()) {
|
||||
return buf.memoryAddress() + offset;
|
||||
}
|
||||
return memoryAddressWithPosition(buf.internalNioBuffer(offset, len));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory address of the given {@link ByteBuffer} taking its current {@link ByteBuffer#position()} into
|
||||
* account.
|
||||
*
|
||||
* @param buf the {@link ByteBuffer} of which we want to obtain the memory address
|
||||
* (taking its {@link ByteBuffer#position()} into account).
|
||||
* @return the memory address of this {@link ByteBuffer}s position.
|
||||
*/
|
||||
static long memoryAddressWithPosition(ByteBuffer buf) {
|
||||
assert buf.isDirect();
|
||||
return buffer_memory_address(buf) + buf.position();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
static ByteBuf allocateNativeOrder(int capacity) {
|
||||
// Just use Unpooled as the life-time of these buffers is long.
|
||||
ByteBuf buffer = Unpooled.directBuffer(capacity);
|
||||
|
||||
// As we use the buffers as pointers to int etc we need to ensure we use the right oder so we will
|
||||
// see the right value when we read primitive values.
|
||||
return PlatformDependent.BIG_ENDIAN_NATIVE_ORDER ? buffer : buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
static Exception newException(int err) {
|
||||
final QuicError error = QuicError.valueOf(err);
|
||||
final QuicException reason = new QuicException(error);
|
||||
if (err == QUICHE_ERR_TLS_FAIL) {
|
||||
String lastSslError = BoringSSL.ERR_last_error();
|
||||
final String message;
|
||||
if (lastSslError != null) {
|
||||
message = error.message() + ": " + lastSslError;
|
||||
} else {
|
||||
message = error.message();
|
||||
}
|
||||
final SSLHandshakeException sslExc = new SSLHandshakeException(message);
|
||||
sslExc.initCause(reason);
|
||||
return sslExc;
|
||||
}
|
||||
if (err == QUICHE_ERR_CRYPTO_FAIL) {
|
||||
return new SSLException(error.message(), reason);
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
static boolean shouldClose(int res) {
|
||||
return res == Quiche.QUICHE_ERR_CRYPTO_FAIL || res == Quiche.QUICHE_ERR_TLS_FAIL;
|
||||
}
|
||||
|
||||
static boolean throwIfError(int res) throws Exception {
|
||||
if (res < 0) {
|
||||
if (res == Quiche.QUICHE_ERR_DONE) {
|
||||
return true;
|
||||
}
|
||||
throw Quiche.newException(res);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void notifyPromise(int res, ChannelPromise promise) {
|
||||
if (res < 0 && res != Quiche.QUICHE_ERR_DONE) {
|
||||
promise.setFailure(Quiche.newException(res));
|
||||
} else {
|
||||
promise.setSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored.
|
||||
*
|
||||
* @param memory the first {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||
* @param memory2 the second {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||
* @return {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored, {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
static boolean isSameAddress(ByteBuffer memory, ByteBuffer memory2, int addressOffset) {
|
||||
long address1 = Quiche.memoryAddressWithPosition(memory) + addressOffset;
|
||||
long address2 = Quiche.memoryAddressWithPosition(memory2) + addressOffset;
|
||||
return SockaddrIn.cmp(address1, address2) == 0;
|
||||
}
|
||||
|
||||
static void setPrimitiveValue(ByteBuffer memory, int offset, int valueType, long value) {
|
||||
switch (valueType) {
|
||||
case 1:
|
||||
memory.put(offset, (byte) value);
|
||||
break;
|
||||
case 2:
|
||||
memory.putShort(offset, (short) value);
|
||||
break;
|
||||
case 4:
|
||||
memory.putInt(offset, (int) value);
|
||||
break;
|
||||
case 8:
|
||||
memory.putLong(offset, value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
static long getPrimitiveValue(ByteBuffer memory, int offset, int valueType) {
|
||||
switch (valueType) {
|
||||
case 1:
|
||||
return memory.get(offset);
|
||||
case 2:
|
||||
return memory.getShort(offset);
|
||||
case 4:
|
||||
return memory.getInt(offset);
|
||||
case 8:
|
||||
return memory.getLong(offset);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private Quiche() { }
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
final class QuicheConfig {
|
||||
private final boolean isDatagramSupported;
|
||||
private long config = -1;
|
||||
|
||||
QuicheConfig(int version, Boolean grease, Long maxIdleTimeout, Long maxSendUdpPayloadSize,
|
||||
Long maxRecvUdpPayloadSize, Long initialMaxData,
|
||||
Long initialMaxStreamDataBidiLocal, Long initialMaxStreamDataBidiRemote,
|
||||
Long initialMaxStreamDataUni, Long initialMaxStreamsBidi, Long initialMaxStreamsUni,
|
||||
Long ackDelayExponent, Long maxAckDelay, Boolean disableActiveMigration, Boolean enableHystart,
|
||||
QuicCongestionControlAlgorithm congestionControlAlgorithm,
|
||||
Integer recvQueueLen, Integer sendQueueLen,
|
||||
Long activeConnectionIdLimit, byte[] statelessResetToken) {
|
||||
long config = Quiche.quiche_config_new(version);
|
||||
try {
|
||||
if (grease != null) {
|
||||
Quiche.quiche_config_grease(config, grease);
|
||||
}
|
||||
if (maxIdleTimeout != null) {
|
||||
Quiche.quiche_config_set_max_idle_timeout(config, maxIdleTimeout);
|
||||
}
|
||||
if (maxSendUdpPayloadSize != null) {
|
||||
Quiche.quiche_config_set_max_send_udp_payload_size(config, maxSendUdpPayloadSize);
|
||||
}
|
||||
if (maxRecvUdpPayloadSize != null) {
|
||||
Quiche.quiche_config_set_max_recv_udp_payload_size(config, maxRecvUdpPayloadSize);
|
||||
}
|
||||
if (initialMaxData != null) {
|
||||
Quiche.quiche_config_set_initial_max_data(config, initialMaxData);
|
||||
}
|
||||
if (initialMaxStreamDataBidiLocal != null) {
|
||||
Quiche.quiche_config_set_initial_max_stream_data_bidi_local(config, initialMaxStreamDataBidiLocal);
|
||||
}
|
||||
if (initialMaxStreamDataBidiRemote != null) {
|
||||
Quiche.quiche_config_set_initial_max_stream_data_bidi_remote(config, initialMaxStreamDataBidiRemote);
|
||||
}
|
||||
if (initialMaxStreamDataUni != null) {
|
||||
Quiche.quiche_config_set_initial_max_stream_data_uni(config, initialMaxStreamDataUni);
|
||||
}
|
||||
if (initialMaxStreamsBidi != null) {
|
||||
Quiche.quiche_config_set_initial_max_streams_bidi(config, initialMaxStreamsBidi);
|
||||
}
|
||||
if (initialMaxStreamsUni != null) {
|
||||
Quiche.quiche_config_set_initial_max_streams_uni(config, initialMaxStreamsUni);
|
||||
}
|
||||
if (ackDelayExponent != null) {
|
||||
Quiche.quiche_config_set_ack_delay_exponent(config, ackDelayExponent);
|
||||
}
|
||||
if (maxAckDelay != null) {
|
||||
Quiche.quiche_config_set_max_ack_delay(config, maxAckDelay);
|
||||
}
|
||||
if (disableActiveMigration != null) {
|
||||
Quiche.quiche_config_set_disable_active_migration(config, disableActiveMigration);
|
||||
}
|
||||
if (enableHystart != null) {
|
||||
Quiche.quiche_config_enable_hystart(config, enableHystart);
|
||||
}
|
||||
if (congestionControlAlgorithm != null) {
|
||||
switch (congestionControlAlgorithm) {
|
||||
case RENO:
|
||||
Quiche.quiche_config_set_cc_algorithm(config, Quiche.QUICHE_CC_RENO);
|
||||
break;
|
||||
case CUBIC:
|
||||
Quiche.quiche_config_set_cc_algorithm(config, Quiche.QUICHE_CC_CUBIC);
|
||||
break;
|
||||
case BBR:
|
||||
Quiche.quiche_config_set_cc_algorithm(config, Quiche.QUICHE_CC_BBR);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown congestionControlAlgorithm: " + congestionControlAlgorithm);
|
||||
}
|
||||
}
|
||||
if (recvQueueLen != null && sendQueueLen != null) {
|
||||
isDatagramSupported = true;
|
||||
Quiche.quiche_config_enable_dgram(config, true, recvQueueLen, sendQueueLen);
|
||||
} else {
|
||||
isDatagramSupported = false;
|
||||
}
|
||||
if (activeConnectionIdLimit != null) {
|
||||
Quiche.quiche_config_set_active_connection_id_limit(config, activeConnectionIdLimit);
|
||||
}
|
||||
if (statelessResetToken != null) {
|
||||
Quiche.quiche_config_set_stateless_reset_token(config, statelessResetToken);
|
||||
}
|
||||
this.config = config;
|
||||
} catch (Throwable cause) {
|
||||
Quiche.quiche_config_free(config);
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isDatagramSupported() {
|
||||
return isDatagramSupported;
|
||||
}
|
||||
|
||||
long nativeAddress() {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Let's override finalize() as we want to ensure we never leak memory even if the user will miss to close
|
||||
// Channel that uses this handler that used the config and just let it get GC'ed.
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
free();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
void free() {
|
||||
if (config != -1) {
|
||||
try {
|
||||
Quiche.quiche_config_free(config);
|
||||
} finally {
|
||||
config = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
|
||||
/**
|
||||
* Delegates QUICHE logging to {@link InternalLogger}.
|
||||
*/
|
||||
final class QuicheLogger {
|
||||
private final InternalLogger logger;
|
||||
|
||||
QuicheLogger(InternalLogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
// Called from JNI.
|
||||
@SuppressWarnings("unused")
|
||||
void log(String msg) {
|
||||
logger.debug(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
final class QuicheNativeStaticallyReferencedJniMethods {
|
||||
|
||||
static native int quiche_protocol_version();
|
||||
static native int quiche_max_conn_id_len();
|
||||
static native int quiche_shutdown_read();
|
||||
static native int quiche_shutdown_write();
|
||||
|
||||
static native int quiche_err_done();
|
||||
static native int quiche_err_buffer_too_short();
|
||||
static native int quiche_err_unknown_version();
|
||||
static native int quiche_err_invalid_frame();
|
||||
static native int quiche_err_invalid_packet();
|
||||
static native int quiche_err_invalid_state();
|
||||
static native int quiche_err_invalid_stream_state();
|
||||
static native int quiche_err_invalid_transport_param();
|
||||
static native int quiche_err_crypto_fail();
|
||||
static native int quiche_err_tls_fail();
|
||||
static native int quiche_err_flow_control();
|
||||
static native int quiche_err_stream_limit();
|
||||
static native int quiche_err_final_size();
|
||||
static native int quiche_err_stream_stopped();
|
||||
static native int quiche_err_stream_reset();
|
||||
static native int quiche_err_congestion_control();
|
||||
static native int quiche_err_id_limit();
|
||||
static native int quiche_err_out_of_identifiers();
|
||||
static native int quiche_err_key_update();
|
||||
|
||||
static native int quiche_cc_reno();
|
||||
static native int quiche_cc_cubic();
|
||||
static native int quiche_cc_bbr();
|
||||
|
||||
static native int quicheRecvInfoOffsetofFrom();
|
||||
static native int quicheRecvInfoOffsetofFromLen();
|
||||
static native int quicheRecvInfoOffsetofTo();
|
||||
static native int quicheRecvInfoOffsetofToLen();
|
||||
|
||||
static native int sizeofQuicheRecvInfo();
|
||||
static native int quicheSendInfoOffsetofTo();
|
||||
static native int quicheSendInfoOffsetofToLen();
|
||||
static native int quicheSendInfoOffsetofFrom();
|
||||
static native int quicheSendInfoOffsetofFromLen();
|
||||
|
||||
static native int quicheSendInfoOffsetofAt();
|
||||
|
||||
static native int sizeofQuicheSendInfo();
|
||||
|
||||
static native int afInet();
|
||||
static native int afInet6();
|
||||
static native int sizeofSockaddrIn();
|
||||
static native int sizeofSockaddrIn6();
|
||||
static native int sockaddrInOffsetofSinFamily();
|
||||
static native int sockaddrInOffsetofSinPort();
|
||||
static native int sockaddrInOffsetofSinAddr();
|
||||
static native int inAddressOffsetofSAddr();
|
||||
static native int sockaddrIn6OffsetofSin6Family();
|
||||
static native int sockaddrIn6OffsetofSin6Port();
|
||||
static native int sockaddrIn6OffsetofSin6Flowinfo();
|
||||
static native int sockaddrIn6OffsetofSin6Addr();
|
||||
static native int sockaddrIn6OffsetofSin6ScopeId();
|
||||
static native int in6AddressOffsetofS6Addr();
|
||||
static native int sizeofSockaddrStorage();
|
||||
static native int sizeofSocklenT();
|
||||
static native int sizeofSizeT();
|
||||
|
||||
static native int sizeofTimespec();
|
||||
static native int timespecOffsetofTvSec();
|
||||
static native int timespecOffsetofTvNsec();
|
||||
static native int sizeofTimeT();
|
||||
static native int sizeofLong();
|
||||
|
||||
static native int quiche_path_event_new();
|
||||
static native int quiche_path_event_validated();
|
||||
static native int quiche_path_event_failed_validation();
|
||||
static native int quiche_path_event_closed();
|
||||
static native int quiche_path_event_reused_source_connection_id();
|
||||
static native int quiche_path_event_peer_migrated();
|
||||
|
||||
private QuicheNativeStaticallyReferencedJniMethods() { }
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.DefaultChannelConfig;
|
||||
import io.netty.channel.MessageSizeEstimator;
|
||||
import io.netty.channel.RecvByteBufAllocator;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default {@link QuicChannelConfig} implementation.
|
||||
*/
|
||||
final class QuicheQuicChannelConfig extends DefaultChannelConfig implements QuicChannelConfig {
|
||||
|
||||
private volatile QLogConfiguration qLogConfiguration;
|
||||
private volatile SegmentedDatagramPacketAllocator segmentedDatagramPacketAllocator =
|
||||
SegmentedDatagramPacketAllocator.NONE;
|
||||
|
||||
QuicheQuicChannelConfig(Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ChannelOption<?>, Object> getOptions() {
|
||||
return getOptions(super.getOptions(),
|
||||
QuicChannelOption.QLOG, QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getOption(ChannelOption<T> option) {
|
||||
if (option == QuicChannelOption.QLOG) {
|
||||
return (T) getQLogConfiguration();
|
||||
}
|
||||
if (option == QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR) {
|
||||
return (T) getSegmentedDatagramPacketAllocator();
|
||||
}
|
||||
return super.getOption(option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean setOption(ChannelOption<T> option, T value) {
|
||||
if (option == QuicChannelOption.QLOG) {
|
||||
setQLogConfiguration((QLogConfiguration) value);
|
||||
return true;
|
||||
}
|
||||
if (option == QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR) {
|
||||
setSegmentedDatagramPacketAllocator((SegmentedDatagramPacketAllocator) value);
|
||||
return true;
|
||||
}
|
||||
return super.setOption(option, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
super.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public QuicChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) {
|
||||
super.setMaxMessagesPerRead(maxMessagesPerRead);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setWriteSpinCount(int writeSpinCount) {
|
||||
super.setWriteSpinCount(writeSpinCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setAllocator(ByteBufAllocator allocator) {
|
||||
super.setAllocator(allocator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
|
||||
super.setRecvByteBufAllocator(allocator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setAutoRead(boolean autoRead) {
|
||||
super.setAutoRead(autoRead);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setAutoClose(boolean autoClose) {
|
||||
super.setAutoClose(autoClose);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
|
||||
super.setWriteBufferHighWaterMark(writeBufferHighWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
|
||||
super.setWriteBufferLowWaterMark(writeBufferLowWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||
super.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) {
|
||||
super.setMessageSizeEstimator(estimator);
|
||||
return this;
|
||||
}
|
||||
|
||||
QLogConfiguration getQLogConfiguration() {
|
||||
return qLogConfiguration;
|
||||
}
|
||||
|
||||
private void setQLogConfiguration(QLogConfiguration qLogConfiguration) {
|
||||
if (channel.isRegistered()) {
|
||||
throw new IllegalStateException("QLOG can only be enabled before the Channel was registered");
|
||||
}
|
||||
this.qLogConfiguration = qLogConfiguration;
|
||||
}
|
||||
|
||||
SegmentedDatagramPacketAllocator getSegmentedDatagramPacketAllocator() {
|
||||
return segmentedDatagramPacketAllocator;
|
||||
}
|
||||
|
||||
private void setSegmentedDatagramPacketAllocator(
|
||||
SegmentedDatagramPacketAllocator segmentedDatagramPacketAllocator) {
|
||||
this.segmentedDatagramPacketAllocator = segmentedDatagramPacketAllocator;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link QuicheQuicCodec} for QUIC clients.
|
||||
*/
|
||||
final class QuicheQuicClientCodec extends QuicheQuicCodec {
|
||||
|
||||
private final Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
|
||||
private final Executor sslTaskExecutor;
|
||||
|
||||
QuicheQuicClientCodec(QuicheConfig config, Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||
Executor sslTaskExecutor, int localConnIdLength, FlushStrategy flushStrategy) {
|
||||
// Let's just use Quic.MAX_DATAGRAM_SIZE as the maximum size for a token on the client side. This should be
|
||||
// safe enough and as we not have too many codecs at the same time this should be ok.
|
||||
super(config, localConnIdLength, Quic.MAX_DATAGRAM_SIZE, flushStrategy);
|
||||
this.sslEngineProvider = sslEngineProvider;
|
||||
this.sslTaskExecutor = sslTaskExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuicheQuicChannel quicPacketRead(
|
||||
ChannelHandlerContext ctx, InetSocketAddress sender, InetSocketAddress recipient,
|
||||
QuicPacketType type, int version, ByteBuf scid, ByteBuf dcid,
|
||||
ByteBuf token) {
|
||||
ByteBuffer key = dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes());
|
||||
return getChannel(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
||||
SocketAddress localAddress, ChannelPromise promise) {
|
||||
final QuicheQuicChannel channel;
|
||||
try {
|
||||
channel = QuicheQuicChannel.handleConnect(sslEngineProvider, sslTaskExecutor, remoteAddress, config.nativeAddress(),
|
||||
localConnIdLength, config.isDatagramSupported(),
|
||||
senderSockaddrMemory.internalNioBuffer(0, senderSockaddrMemory.capacity()),
|
||||
recipientSockaddrMemory.internalNioBuffer(0, recipientSockaddrMemory.capacity()));
|
||||
} catch (Exception e) {
|
||||
promise.setFailure(e);
|
||||
return;
|
||||
}
|
||||
if (channel != null) {
|
||||
addChannel(channel);
|
||||
|
||||
channel.finishConnect();
|
||||
promise.setSuccess();
|
||||
return;
|
||||
}
|
||||
ctx.connect(remoteAddress, localAddress, promise);
|
||||
}
|
||||
}
|
@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.MessageSizeEstimator;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.netty.handler.codec.quic.Quiche.allocateNativeOrder;
|
||||
|
||||
/**
|
||||
* Abstract base class for QUIC codecs.
|
||||
*/
|
||||
abstract class QuicheQuicCodec extends ChannelDuplexHandler {
|
||||
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicCodec.class);
|
||||
|
||||
private final Map<ByteBuffer, QuicheQuicChannel> connectionIdToChannel = new HashMap<>();
|
||||
private final Set<QuicheQuicChannel> channels = new HashSet<>();
|
||||
private final Queue<QuicheQuicChannel> needsFireChannelReadComplete = new ArrayDeque<>();
|
||||
private final int maxTokenLength;
|
||||
private final FlushStrategy flushStrategy;
|
||||
|
||||
private MessageSizeEstimator.Handle estimatorHandle;
|
||||
private QuicHeaderParser headerParser;
|
||||
private QuicHeaderParser.QuicHeaderProcessor parserCallback;
|
||||
private int pendingBytes;
|
||||
private int pendingPackets;
|
||||
private boolean inChannelReadComplete;
|
||||
|
||||
protected final QuicheConfig config;
|
||||
protected final int localConnIdLength;
|
||||
// This buffer is used to copy InetSocketAddress to sockaddr_storage and so pass it down the JNI layer.
|
||||
protected ByteBuf senderSockaddrMemory;
|
||||
protected ByteBuf recipientSockaddrMemory;
|
||||
|
||||
QuicheQuicCodec(QuicheConfig config, int localConnIdLength, int maxTokenLength, FlushStrategy flushStrategy) {
|
||||
this.config = config;
|
||||
this.localConnIdLength = localConnIdLength;
|
||||
this.maxTokenLength = maxTokenLength;
|
||||
this.flushStrategy = flushStrategy;
|
||||
}
|
||||
|
||||
protected final QuicheQuicChannel getChannel(ByteBuffer key) {
|
||||
return connectionIdToChannel.get(key);
|
||||
}
|
||||
|
||||
protected final void addMapping(ByteBuffer key, QuicheQuicChannel channel) {
|
||||
connectionIdToChannel.put(key, channel);
|
||||
}
|
||||
|
||||
protected final void removeMapping(ByteBuffer key) {
|
||||
connectionIdToChannel.remove(key);
|
||||
}
|
||||
|
||||
protected final void removeChannel(QuicheQuicChannel channel) {
|
||||
boolean removed = channels.remove(channel);
|
||||
assert removed;
|
||||
for (ByteBuffer id : channel.sourceConnectionIds()) {
|
||||
connectionIdToChannel.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void addChannel(QuicheQuicChannel channel) {
|
||||
boolean added = channels.add(channel);
|
||||
assert added;
|
||||
for (ByteBuffer id : channel.sourceConnectionIds()) {
|
||||
connectionIdToChannel.put(id, channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
senderSockaddrMemory = allocateNativeOrder(Quiche.SIZEOF_SOCKADDR_STORAGE);
|
||||
recipientSockaddrMemory = allocateNativeOrder(Quiche.SIZEOF_SOCKADDR_STORAGE);
|
||||
headerParser = new QuicHeaderParser(maxTokenLength, localConnIdLength);
|
||||
parserCallback = new QuicCodecHeaderProcessor(ctx);
|
||||
estimatorHandle = ctx.channel().config().getMessageSizeEstimator().newHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
try {
|
||||
// Use a copy of the array as closing the channel may cause an unwritable event that could also
|
||||
// remove channels.
|
||||
for (QuicheQuicChannel ch : channels.toArray(new QuicheQuicChannel[0])) {
|
||||
ch.forceClose();
|
||||
}
|
||||
channels.clear();
|
||||
connectionIdToChannel.clear();
|
||||
|
||||
needsFireChannelReadComplete.clear();
|
||||
if (pendingPackets > 0) {
|
||||
flushNow(ctx);
|
||||
}
|
||||
} finally {
|
||||
config.free();
|
||||
if (senderSockaddrMemory != null) {
|
||||
senderSockaddrMemory.release();
|
||||
}
|
||||
if (recipientSockaddrMemory != null) {
|
||||
recipientSockaddrMemory.release();
|
||||
}
|
||||
if (headerParser != null) {
|
||||
headerParser.close();
|
||||
headerParser = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
DatagramPacket packet = (DatagramPacket) msg;
|
||||
try {
|
||||
ByteBuf buffer = ((DatagramPacket) msg).content();
|
||||
if (!buffer.isDirect()) {
|
||||
// We need a direct buffer as otherwise we can not access the memoryAddress.
|
||||
// Let's do a copy to direct memory.
|
||||
ByteBuf direct = ctx.alloc().directBuffer(buffer.readableBytes());
|
||||
try {
|
||||
direct.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||
handleQuicPacket(packet.sender(), packet.recipient(), direct);
|
||||
} finally {
|
||||
direct.release();
|
||||
}
|
||||
} else {
|
||||
handleQuicPacket(packet.sender(), packet.recipient(), buffer);
|
||||
}
|
||||
} finally {
|
||||
packet.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleQuicPacket(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf buffer) {
|
||||
try {
|
||||
headerParser.parse(sender, recipient, buffer, parserCallback);
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Error while processing QUIC packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a QUIC packet and return {@code true} if we need to call {@link ChannelHandlerContext#flush()}.
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext}.
|
||||
* @param sender the {@link InetSocketAddress} of the sender of the QUIC packet
|
||||
* @param recipient the {@link InetSocketAddress} of the recipient of the QUIC packet
|
||||
* @param type the type of the packet.
|
||||
* @param version the QUIC version
|
||||
* @param scid the source connection id.
|
||||
* @param dcid the destination connection id
|
||||
* @param token the token
|
||||
* @return {@code true} if we need to call {@link ChannelHandlerContext#flush()} before there is no new events
|
||||
* for this handler in the current eventloop run.
|
||||
* @throws Exception thrown if there is an error during processing.
|
||||
*/
|
||||
protected abstract QuicheQuicChannel quicPacketRead(ChannelHandlerContext ctx, InetSocketAddress sender,
|
||||
InetSocketAddress recipient, QuicPacketType type, int version,
|
||||
ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception;
|
||||
|
||||
@Override
|
||||
public final void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
inChannelReadComplete = true;
|
||||
try {
|
||||
for (;;) {
|
||||
QuicheQuicChannel channel = needsFireChannelReadComplete.poll();
|
||||
if (channel == null) {
|
||||
break;
|
||||
}
|
||||
channel.recvComplete();
|
||||
if (channel.freeIfClosed()) {
|
||||
removeChannel(channel);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
inChannelReadComplete = false;
|
||||
if (pendingPackets > 0) {
|
||||
flushNow(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void channelWritabilityChanged(ChannelHandlerContext ctx) {
|
||||
if (ctx.channel().isWritable()) {
|
||||
List<QuicheQuicChannel> closed = null;
|
||||
for (QuicheQuicChannel channel : channels) {
|
||||
// TODO: Be a bit smarter about this.
|
||||
channel.writable();
|
||||
if (channel.freeIfClosed()) {
|
||||
if (closed == null) {
|
||||
closed = new ArrayList<>();
|
||||
}
|
||||
closed.add(channel);
|
||||
}
|
||||
}
|
||||
if (closed != null) {
|
||||
for (QuicheQuicChannel ch: closed) {
|
||||
removeChannel(ch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// As we batch flushes we need to ensure we at least try to flush a batch once the channel becomes
|
||||
// unwritable. Otherwise we may end up with buffering too much writes and so waste memory.
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
ctx.fireChannelWritabilityChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
||||
pendingPackets ++;
|
||||
int size = estimatorHandle.size(msg);
|
||||
if (size > 0) {
|
||||
pendingBytes += size;
|
||||
}
|
||||
try {
|
||||
ctx.write(msg, promise);
|
||||
} finally {
|
||||
flushIfNeeded(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void flush(ChannelHandlerContext ctx) {
|
||||
// If we are in the channelReadComplete(...) method we might be able to delay the flush(...) until we finish
|
||||
// processing all channels.
|
||||
if (inChannelReadComplete) {
|
||||
flushIfNeeded(ctx);
|
||||
} else if (pendingPackets > 0) {
|
||||
flushNow(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private void flushIfNeeded(ChannelHandlerContext ctx) {
|
||||
// Check if we should force a flush() and so ensure the packets are delivered in a timely
|
||||
// manner and also make room in the outboundbuffer again that belongs to the underlying channel.
|
||||
if (flushStrategy.shouldFlushNow(pendingPackets, pendingBytes)) {
|
||||
flushNow(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private void flushNow(ChannelHandlerContext ctx) {
|
||||
pendingBytes = 0;
|
||||
pendingPackets = 0;
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private final class QuicCodecHeaderProcessor implements QuicHeaderParser.QuicHeaderProcessor {
|
||||
|
||||
private final ChannelHandlerContext ctx;
|
||||
|
||||
QuicCodecHeaderProcessor(ChannelHandlerContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf buffer, QuicPacketType type,
|
||||
int version, ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception {
|
||||
QuicheQuicChannel channel = quicPacketRead(ctx, sender, recipient,
|
||||
type, version, scid,
|
||||
dcid, token);
|
||||
if (channel != null) {
|
||||
channelRecv(channel, sender, recipient, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once something was received for a {@link QuicheQuicChannel}.
|
||||
*
|
||||
* @param channel the channel for which the data was received
|
||||
* @param sender the sender
|
||||
* @param recipient the recipient
|
||||
* @param buffer the acutal data.
|
||||
*/
|
||||
protected void channelRecv(QuicheQuicChannel channel, InetSocketAddress sender,
|
||||
InetSocketAddress recipient, ByteBuf buffer) {
|
||||
// Add to queue first, we might be able to safe some flushes and consolidate them
|
||||
// in channelReadComplete(...) this way.
|
||||
if (channel.markInFireChannelReadCompleteQueue()) {
|
||||
needsFireChannelReadComplete.add(channel);
|
||||
}
|
||||
channel.recv(sender, recipient, buffer);
|
||||
}
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.ReferenceCounted;
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import io.netty.util.ResourceLeakDetectorFactory;
|
||||
import io.netty.util.ResourceLeakTracker;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class QuicheQuicConnection {
|
||||
private static final int TOTAL_RECV_INFO_SIZE = Quiche.SIZEOF_QUICHE_RECV_INFO +
|
||||
Quiche.SIZEOF_SOCKADDR_STORAGE + Quiche.SIZEOF_SOCKADDR_STORAGE;
|
||||
private static final ResourceLeakDetector<QuicheQuicConnection> leakDetector =
|
||||
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(QuicheQuicConnection.class);
|
||||
private final QuicheQuicSslEngine engine;
|
||||
|
||||
private final ResourceLeakTracker<QuicheQuicConnection> leakTracker;
|
||||
|
||||
final long ssl;
|
||||
private ReferenceCounted refCnt;
|
||||
|
||||
// This block of memory is used to store the following structs (in this order):
|
||||
// - quiche_recv_info
|
||||
// - sockaddr_storage
|
||||
// - quiche_recv_info
|
||||
// - sockaddr_storage
|
||||
// - quiche_send_info
|
||||
// - quiche_send_info
|
||||
//
|
||||
// We need to have every stored 2 times as we need to check if the last sockaddr has changed between
|
||||
// quiche_conn_recv and quiche_conn_send calls. If this happens we know a QUIC connection migration did happen.
|
||||
private final ByteBuf recvInfoBuffer;
|
||||
private final ByteBuf sendInfoBuffer;
|
||||
|
||||
private boolean recvInfoFirst = true;
|
||||
private boolean sendInfoFirst = true;
|
||||
private final ByteBuffer recvInfoBuffer1;
|
||||
private final ByteBuffer recvInfoBuffer2;
|
||||
private final ByteBuffer sendInfoBuffer1;
|
||||
private final ByteBuffer sendInfoBuffer2;
|
||||
|
||||
private long connection;
|
||||
|
||||
QuicheQuicConnection(long connection, long ssl, QuicheQuicSslEngine engine, ReferenceCounted refCnt) {
|
||||
this.connection = connection;
|
||||
this.ssl = ssl;
|
||||
this.engine = engine;
|
||||
this.refCnt = refCnt;
|
||||
// TODO: Maybe cache these per thread as we only use them temporary within a limited scope.
|
||||
recvInfoBuffer = Quiche.allocateNativeOrder(2 * TOTAL_RECV_INFO_SIZE);
|
||||
sendInfoBuffer = Quiche.allocateNativeOrder(2 * Quiche.SIZEOF_QUICHE_SEND_INFO);
|
||||
|
||||
// Let's memset the memory.
|
||||
recvInfoBuffer.setZero(0, recvInfoBuffer.capacity());
|
||||
sendInfoBuffer.setZero(0, sendInfoBuffer.capacity());
|
||||
|
||||
recvInfoBuffer1 = recvInfoBuffer.nioBuffer(0, TOTAL_RECV_INFO_SIZE);
|
||||
recvInfoBuffer2 = recvInfoBuffer.nioBuffer(TOTAL_RECV_INFO_SIZE, TOTAL_RECV_INFO_SIZE);
|
||||
|
||||
sendInfoBuffer1 = sendInfoBuffer.nioBuffer(0, Quiche.SIZEOF_QUICHE_SEND_INFO);
|
||||
sendInfoBuffer2 = sendInfoBuffer.nioBuffer(Quiche.SIZEOF_QUICHE_SEND_INFO, Quiche.SIZEOF_QUICHE_SEND_INFO);
|
||||
this.engine.connection = this;
|
||||
leakTracker = leakDetector.track(this);
|
||||
}
|
||||
|
||||
synchronized void reattach(ReferenceCounted refCnt) {
|
||||
this.refCnt.release();
|
||||
this.refCnt = refCnt;
|
||||
}
|
||||
|
||||
void free() {
|
||||
free(true);
|
||||
}
|
||||
|
||||
private void free(boolean closeLeakTracker) {
|
||||
boolean release = false;
|
||||
synchronized (this) {
|
||||
if (connection != -1) {
|
||||
try {
|
||||
BoringSSL.SSL_cleanup(ssl);
|
||||
Quiche.quiche_conn_free(connection);
|
||||
engine.ctx.remove(engine);
|
||||
release = true;
|
||||
refCnt.release();
|
||||
} finally {
|
||||
connection = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (release) {
|
||||
recvInfoBuffer.release();
|
||||
sendInfoBuffer.release();
|
||||
if (closeLeakTracker && leakTracker != null) {
|
||||
leakTracker.close(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Runnable sslTask() {
|
||||
final Runnable task;
|
||||
synchronized (this) {
|
||||
if (connection != -1) {
|
||||
task = BoringSSL.SSL_getTask(ssl);
|
||||
} else {
|
||||
task = null;
|
||||
}
|
||||
}
|
||||
if (task == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return () -> {
|
||||
if (connection == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
task.run();
|
||||
};
|
||||
}
|
||||
|
||||
QuicConnectionAddress sourceId() {
|
||||
return connectionId(() -> Quiche.quiche_conn_source_id(connection));
|
||||
}
|
||||
|
||||
QuicConnectionAddress destinationId() {
|
||||
return connectionId(() -> Quiche.quiche_conn_destination_id(connection));
|
||||
}
|
||||
|
||||
QuicConnectionAddress connectionId(Supplier<byte[]> idSupplier) {
|
||||
final byte[] id;
|
||||
synchronized (this) {
|
||||
if (connection == -1) {
|
||||
return null;
|
||||
}
|
||||
id = idSupplier.get();
|
||||
}
|
||||
return id == null ? null : new QuicConnectionAddress(id);
|
||||
}
|
||||
|
||||
QuicheQuicTransportParameters peerParameters() {
|
||||
final long[] ret;
|
||||
synchronized (this) {
|
||||
if (connection == -1) {
|
||||
return null;
|
||||
}
|
||||
ret = Quiche.quiche_conn_peer_transport_params(connection);
|
||||
}
|
||||
if (ret == null) {
|
||||
return null;
|
||||
}
|
||||
return new QuicheQuicTransportParameters(ret);
|
||||
}
|
||||
|
||||
QuicheQuicSslEngine engine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
long address() {
|
||||
assert connection != -1;
|
||||
return connection;
|
||||
}
|
||||
|
||||
void initInfo(InetSocketAddress local, InetSocketAddress remote) {
|
||||
assert connection != -1;
|
||||
assert recvInfoBuffer.refCnt() != 0;
|
||||
assert sendInfoBuffer.refCnt() != 0;
|
||||
|
||||
// Fill both quiche_recv_info structs with the same address.
|
||||
QuicheRecvInfo.setRecvInfo(recvInfoBuffer1, remote, local);
|
||||
QuicheRecvInfo.setRecvInfo(recvInfoBuffer2, remote, local);
|
||||
|
||||
// Fill both quiche_send_info structs with the same address.
|
||||
QuicheSendInfo.setSendInfo(sendInfoBuffer1, local, remote);
|
||||
QuicheSendInfo.setSendInfo(sendInfoBuffer2, local, remote);
|
||||
}
|
||||
|
||||
ByteBuffer nextRecvInfo() {
|
||||
assert recvInfoBuffer.refCnt() != 0;
|
||||
recvInfoFirst = !recvInfoFirst;
|
||||
return recvInfoFirst ? recvInfoBuffer1 : recvInfoBuffer2;
|
||||
}
|
||||
|
||||
ByteBuffer nextSendInfo() {
|
||||
assert sendInfoBuffer.refCnt() != 0;
|
||||
sendInfoFirst = !sendInfoFirst;
|
||||
return sendInfoFirst ? sendInfoBuffer1 : sendInfoBuffer2;
|
||||
}
|
||||
|
||||
boolean isSendInfoChanged() {
|
||||
assert sendInfoBuffer.refCnt() != 0;
|
||||
return !QuicheSendInfo.isSameAddress(sendInfoBuffer1, sendInfoBuffer2);
|
||||
}
|
||||
|
||||
boolean isRecvInfoChanged() {
|
||||
assert recvInfoBuffer.refCnt() != 0;
|
||||
return !QuicheRecvInfo.isSameAddress(recvInfoBuffer1, recvInfoBuffer2);
|
||||
}
|
||||
|
||||
boolean isClosed() {
|
||||
assert connection != -1;
|
||||
return Quiche.quiche_conn_is_closed(connection);
|
||||
}
|
||||
|
||||
// Let's override finalize() as we want to ensure we never leak memory even if the user will miss to close
|
||||
// Channel that uses this connection and just let it get GC'ed
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
free(false);
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
final class QuicheQuicConnectionStats implements QuicConnectionStats {
|
||||
|
||||
private final long[] values;
|
||||
|
||||
QuicheQuicConnectionStats(long[] values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long recv() {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sent() {
|
||||
return values[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lost() {
|
||||
return values[2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long retrans() {
|
||||
return values[3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sentBytes() {
|
||||
return values[4];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long recvBytes() {
|
||||
return values[5];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lostBytes() {
|
||||
return values[6];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long streamRetransBytes() {
|
||||
return values[7];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long pathsCount() {
|
||||
return values[8];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link String} representation of stats.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.simpleClassName(this) + "[" +
|
||||
"recv=" + recv() +
|
||||
", sent=" + sent() +
|
||||
", lost=" + lost() +
|
||||
", retrans=" + retrans() +
|
||||
", sentBytes=" + sentBytes() +
|
||||
", recvBytes=" + recvBytes() +
|
||||
", lostBytes=" + lostBytes() +
|
||||
", streamRetransBytes=" + streamRetransBytes() +
|
||||
", pathsCount=" + pathsCount() +
|
||||
"]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static io.netty.handler.codec.quic.Quiche.allocateNativeOrder;
|
||||
|
||||
/**
|
||||
* {@link QuicheQuicCodec} for QUIC servers.
|
||||
*/
|
||||
final class QuicheQuicServerCodec extends QuicheQuicCodec {
|
||||
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicServerCodec.class);
|
||||
|
||||
private final Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
|
||||
private final Executor sslTaskExecutor;
|
||||
private final QuicConnectionIdGenerator connectionIdAddressGenerator;
|
||||
private final QuicResetTokenGenerator resetTokenGenerator;
|
||||
private final QuicTokenHandler tokenHandler;
|
||||
private final ChannelHandler handler;
|
||||
private final Map.Entry<ChannelOption<?>, Object>[] optionsArray;
|
||||
private final Map.Entry<AttributeKey<?>, Object>[] attrsArray;
|
||||
private final ChannelHandler streamHandler;
|
||||
private final Map.Entry<ChannelOption<?>, Object>[] streamOptionsArray;
|
||||
private final Map.Entry<AttributeKey<?>, Object>[] streamAttrsArray;
|
||||
private ByteBuf mintTokenBuffer;
|
||||
private ByteBuf connIdBuffer;
|
||||
|
||||
QuicheQuicServerCodec(QuicheConfig config,
|
||||
int localConnIdLength,
|
||||
QuicTokenHandler tokenHandler,
|
||||
QuicConnectionIdGenerator connectionIdAddressGenerator,
|
||||
QuicResetTokenGenerator resetTokenGenerator,
|
||||
FlushStrategy flushStrategy,
|
||||
Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||
Executor sslTaskExecutor,
|
||||
ChannelHandler handler,
|
||||
Map.Entry<ChannelOption<?>, Object>[] optionsArray,
|
||||
Map.Entry<AttributeKey<?>, Object>[] attrsArray,
|
||||
ChannelHandler streamHandler,
|
||||
Map.Entry<ChannelOption<?>, Object>[] streamOptionsArray,
|
||||
Map.Entry<AttributeKey<?>, Object>[] streamAttrsArray) {
|
||||
super(config, localConnIdLength, tokenHandler.maxTokenLength(), flushStrategy);
|
||||
this.tokenHandler = tokenHandler;
|
||||
this.connectionIdAddressGenerator = connectionIdAddressGenerator;
|
||||
this.resetTokenGenerator = resetTokenGenerator;
|
||||
this.sslEngineProvider = sslEngineProvider;
|
||||
this.sslTaskExecutor = sslTaskExecutor;
|
||||
this.handler = handler;
|
||||
this.optionsArray = optionsArray;
|
||||
this.attrsArray = attrsArray;
|
||||
this.streamHandler = streamHandler;
|
||||
this.streamOptionsArray = streamOptionsArray;
|
||||
this.streamAttrsArray = streamAttrsArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||
super.handlerAdded(ctx);
|
||||
connIdBuffer = Quiche.allocateNativeOrder(localConnIdLength);
|
||||
mintTokenBuffer = allocateNativeOrder(tokenHandler.maxTokenLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
super.handlerRemoved(ctx);
|
||||
if (connIdBuffer != null) {
|
||||
connIdBuffer.release();
|
||||
}
|
||||
if (mintTokenBuffer != null) {
|
||||
mintTokenBuffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRecv(QuicheQuicChannel channel, InetSocketAddress sender,
|
||||
InetSocketAddress recipient, ByteBuf buffer) {
|
||||
super.channelRecv(channel, sender, recipient, buffer);
|
||||
for (ByteBuffer retiredSourceConnectionId : channel.retiredSourceConnectionId()) {
|
||||
removeMapping(retiredSourceConnectionId);
|
||||
}
|
||||
for (ByteBuffer newSourceConnectionId :
|
||||
channel.newSourceConnectionIds(connectionIdAddressGenerator, resetTokenGenerator)) {
|
||||
addMapping(newSourceConnectionId, channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuicheQuicChannel quicPacketRead(ChannelHandlerContext ctx, InetSocketAddress sender,
|
||||
InetSocketAddress recipient, QuicPacketType type, int version,
|
||||
ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception {
|
||||
ByteBuffer dcidByteBuffer = dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes());
|
||||
QuicheQuicChannel channel = getChannel(dcidByteBuffer);
|
||||
if (channel == null && type == QuicPacketType.ZERO_RTT && connectionIdAddressGenerator.isIdempotent()) {
|
||||
// 0 rtt packet should obtain the server generated dcid
|
||||
channel = getChannel(connectionIdAddressGenerator.newId(dcidByteBuffer, localConnIdLength));
|
||||
}
|
||||
if (channel == null) {
|
||||
return handleServer(ctx, sender, recipient, type, version, scid, dcid, token);
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
private QuicheQuicChannel handleServer(ChannelHandlerContext ctx, InetSocketAddress sender,
|
||||
InetSocketAddress recipient,
|
||||
@SuppressWarnings("unused") QuicPacketType type, int version,
|
||||
ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception {
|
||||
if (!Quiche.quiche_version_is_supported(version)) {
|
||||
// Version is not supported, try to negotiate it.
|
||||
ByteBuf out = ctx.alloc().directBuffer(Quic.MAX_DATAGRAM_SIZE);
|
||||
int outWriterIndex = out.writerIndex();
|
||||
|
||||
int res = Quiche.quiche_negotiate_version(
|
||||
Quiche.readerMemoryAddress(scid), scid.readableBytes(),
|
||||
Quiche.readerMemoryAddress(dcid), dcid.readableBytes(),
|
||||
Quiche.writerMemoryAddress(out), out.writableBytes());
|
||||
if (res < 0) {
|
||||
out.release();
|
||||
Quiche.throwIfError(res);
|
||||
} else {
|
||||
ctx.writeAndFlush(new DatagramPacket(out.writerIndex(outWriterIndex + res), sender));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final int offset;
|
||||
boolean noToken = false;
|
||||
if (!token.isReadable()) {
|
||||
// Clear buffers so we can reuse these.
|
||||
mintTokenBuffer.clear();
|
||||
connIdBuffer.clear();
|
||||
|
||||
// The remote peer did not send a token.
|
||||
if (tokenHandler.writeToken(mintTokenBuffer, dcid, sender)) {
|
||||
ByteBuffer connId = connectionIdAddressGenerator.newId(
|
||||
dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes()), localConnIdLength);
|
||||
connIdBuffer.writeBytes(connId);
|
||||
|
||||
ByteBuf out = ctx.alloc().directBuffer(Quic.MAX_DATAGRAM_SIZE);
|
||||
int outWriterIndex = out.writerIndex();
|
||||
int written = Quiche.quiche_retry(
|
||||
Quiche.readerMemoryAddress(scid), scid.readableBytes(),
|
||||
Quiche.readerMemoryAddress(dcid), dcid.readableBytes(),
|
||||
Quiche.readerMemoryAddress(connIdBuffer), connIdBuffer.readableBytes(),
|
||||
Quiche.readerMemoryAddress(mintTokenBuffer), mintTokenBuffer.readableBytes(),
|
||||
version,
|
||||
Quiche.writerMemoryAddress(out), out.writableBytes());
|
||||
|
||||
if (written < 0) {
|
||||
out.release();
|
||||
Quiche.throwIfError(written);
|
||||
} else {
|
||||
ctx.writeAndFlush(new DatagramPacket(out.writerIndex(outWriterIndex + written), sender));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
offset = 0;
|
||||
noToken = true;
|
||||
} else {
|
||||
offset = tokenHandler.validateToken(token, sender);
|
||||
if (offset == -1) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("invalid token: {}", token.toString(CharsetUtil.US_ASCII));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final ByteBuffer key;
|
||||
final long scidAddr;
|
||||
final int scidLen;
|
||||
final long ocidAddr;
|
||||
final int ocidLen;
|
||||
|
||||
if (noToken) {
|
||||
connIdBuffer.clear();
|
||||
key = connectionIdAddressGenerator.newId(
|
||||
dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes()), localConnIdLength);
|
||||
connIdBuffer.writeBytes(key.duplicate());
|
||||
scidAddr = Quiche.readerMemoryAddress(connIdBuffer);
|
||||
scidLen = localConnIdLength;
|
||||
ocidAddr = -1;
|
||||
ocidLen = -1;
|
||||
|
||||
QuicheQuicChannel existingChannel = getChannel(key);
|
||||
if (existingChannel != null) {
|
||||
return existingChannel;
|
||||
}
|
||||
} else {
|
||||
scidAddr = Quiche.readerMemoryAddress(dcid);
|
||||
scidLen = localConnIdLength;
|
||||
ocidAddr = Quiche.memoryAddress(token, offset, token.readableBytes());
|
||||
ocidLen = token.readableBytes() - offset;
|
||||
// Now create the key to store the channel in the map.
|
||||
byte[] bytes = new byte[localConnIdLength];
|
||||
dcid.getBytes(dcid.readerIndex(), bytes);
|
||||
key = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
QuicheQuicChannel channel = QuicheQuicChannel.forServer(
|
||||
ctx.channel(), key, recipient, sender, config.isDatagramSupported(),
|
||||
streamHandler, streamOptionsArray, streamAttrsArray, this::removeChannel, sslTaskExecutor);
|
||||
|
||||
Quic.setupChannel(channel, optionsArray, attrsArray, handler, LOGGER);
|
||||
QuicSslEngine engine = sslEngineProvider.apply(channel);
|
||||
if (!(engine instanceof QuicheQuicSslEngine)) {
|
||||
channel.unsafe().closeForcibly();
|
||||
throw new IllegalArgumentException("QuicSslEngine is not of type "
|
||||
+ QuicheQuicSslEngine.class.getSimpleName());
|
||||
}
|
||||
if (engine.getUseClientMode()) {
|
||||
channel.unsafe().closeForcibly();
|
||||
throw new IllegalArgumentException("QuicSslEngine is not created in server mode");
|
||||
}
|
||||
|
||||
QuicheQuicSslEngine quicSslEngine = (QuicheQuicSslEngine) engine;
|
||||
QuicheQuicConnection connection = quicSslEngine.createConnection(ssl -> {
|
||||
ByteBuffer localAddrMemory = recipientSockaddrMemory.internalNioBuffer(0, recipientSockaddrMemory.capacity());
|
||||
int localLen = SockaddrIn.setAddress(localAddrMemory, recipient);
|
||||
|
||||
ByteBuffer peerAddrMemory = senderSockaddrMemory.internalNioBuffer(0, senderSockaddrMemory.capacity());
|
||||
int peerLen = SockaddrIn.setAddress(peerAddrMemory, sender);
|
||||
return Quiche.quiche_conn_new_with_tls(scidAddr, scidLen, ocidAddr, ocidLen,
|
||||
Quiche.memoryAddressWithPosition(localAddrMemory), localLen,
|
||||
Quiche.memoryAddressWithPosition(peerAddrMemory), peerLen,
|
||||
config.nativeAddress(), ssl, true);
|
||||
});
|
||||
if (connection == null) {
|
||||
channel.unsafe().closeForcibly();
|
||||
LOGGER.debug("quiche_accept failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
channel.attachQuicheConnection(connection);
|
||||
|
||||
addChannel(channel);
|
||||
|
||||
ctx.channel().eventLoop().register(channel);
|
||||
return channel;
|
||||
}
|
||||
}
|
@ -0,0 +1,497 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.handler.ssl.ApplicationProtocolNegotiator;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.util.AbstractReferenceCounted;
|
||||
import io.netty.util.Mapping;
|
||||
import io.netty.util.ReferenceCounted;
|
||||
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
final class QuicheQuicSslContext extends QuicSslContext {
|
||||
final ClientAuth clientAuth;
|
||||
private final boolean server;
|
||||
@SuppressWarnings("deprecation")
|
||||
private final ApplicationProtocolNegotiator apn;
|
||||
private long sessionCacheSize;
|
||||
private long sessionTimeout;
|
||||
private final QuicheQuicSslSessionContext sessionCtx;
|
||||
private final QuicheQuicSslEngineMap engineMap = new QuicheQuicSslEngineMap();
|
||||
private final QuicClientSessionCache sessionCache;
|
||||
|
||||
private final BoringSSLSessionTicketCallback sessionTicketCallback = new BoringSSLSessionTicketCallback();
|
||||
|
||||
final NativeSslContext nativeSslContext;
|
||||
|
||||
QuicheQuicSslContext(boolean server, long sessionTimeout, long sessionCacheSize,
|
||||
ClientAuth clientAuth, TrustManagerFactory trustManagerFactory,
|
||||
KeyManagerFactory keyManagerFactory, String password,
|
||||
Mapping<? super String, ? extends QuicSslContext> mapping,
|
||||
Boolean earlyData, BoringSSLKeylog keylog,
|
||||
String... applicationProtocols) {
|
||||
Quic.ensureAvailability();
|
||||
this.server = server;
|
||||
this.clientAuth = server ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
|
||||
final X509TrustManager trustManager;
|
||||
if (trustManagerFactory == null) {
|
||||
try {
|
||||
trustManagerFactory =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
trustManager = chooseTrustManager(trustManagerFactory);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} else {
|
||||
trustManager = chooseTrustManager(trustManagerFactory);
|
||||
}
|
||||
final X509ExtendedKeyManager keyManager;
|
||||
if (keyManagerFactory == null) {
|
||||
if (server) {
|
||||
throw new IllegalArgumentException("No KeyManagerFactory");
|
||||
}
|
||||
keyManager = null;
|
||||
} else {
|
||||
keyManager = chooseKeyManager(keyManagerFactory);
|
||||
}
|
||||
final BoringSSLPrivateKeyMethod privateKeyMethod;
|
||||
if (keyManagerFactory instanceof BoringSSLKeylessManagerFactory) {
|
||||
privateKeyMethod = new BoringSSLAsyncPrivateKeyMethodAdapter(engineMap,
|
||||
((BoringSSLKeylessManagerFactory) keyManagerFactory).privateKeyMethod);
|
||||
} else {
|
||||
privateKeyMethod = null;
|
||||
}
|
||||
sessionCache = server ? null : new QuicClientSessionCache();
|
||||
int verifyMode = server ? boringSSLVerifyModeForServer(this.clientAuth) : BoringSSL.SSL_VERIFY_PEER;
|
||||
nativeSslContext = new NativeSslContext(BoringSSL.SSLContext_new(server, applicationProtocols,
|
||||
new BoringSSLHandshakeCompleteCallback(engineMap),
|
||||
new BoringSSLCertificateCallback(engineMap, keyManager, password),
|
||||
new BoringSSLCertificateVerifyCallback(engineMap, trustManager),
|
||||
mapping == null ? null : new BoringSSLTlsextServernameCallback(engineMap, mapping),
|
||||
keylog == null ? null : new BoringSSLKeylogCallback(engineMap, keylog),
|
||||
server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod,
|
||||
sessionTicketCallback, verifyMode,
|
||||
BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
|
||||
apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
|
||||
if (this.sessionCache != null) {
|
||||
// Cache is handled via our own implementation.
|
||||
this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
|
||||
this.sessionCache.setSessionTimeout((int) sessionTimeout);
|
||||
} else {
|
||||
// Cache is handled by BoringSSL internally
|
||||
BoringSSL.SSLContext_setSessionCacheSize(
|
||||
nativeSslContext.address(), sessionCacheSize);
|
||||
this.sessionCacheSize = sessionCacheSize;
|
||||
|
||||
BoringSSL.SSLContext_setSessionCacheTimeout(
|
||||
nativeSslContext.address(), sessionTimeout);
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
}
|
||||
if (earlyData != null) {
|
||||
BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), earlyData);
|
||||
}
|
||||
sessionCtx = new QuicheQuicSslSessionContext(this);
|
||||
}
|
||||
|
||||
private X509ExtendedKeyManager chooseKeyManager(KeyManagerFactory keyManagerFactory) {
|
||||
for (KeyManager manager: keyManagerFactory.getKeyManagers()) {
|
||||
if (manager instanceof X509ExtendedKeyManager) {
|
||||
return (X509ExtendedKeyManager) manager;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No X509ExtendedKeyManager included");
|
||||
}
|
||||
|
||||
private static X509TrustManager chooseTrustManager(TrustManagerFactory trustManagerFactory) {
|
||||
for (TrustManager manager: trustManagerFactory.getTrustManagers()) {
|
||||
if (manager instanceof X509TrustManager) {
|
||||
return (X509TrustManager) manager;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No X509TrustManager included");
|
||||
}
|
||||
|
||||
static X509Certificate[] toX509Certificates0(File file) throws CertificateException {
|
||||
return toX509Certificates(file);
|
||||
}
|
||||
|
||||
static PrivateKey toPrivateKey0(File keyFile, String keyPassword) throws NoSuchAlgorithmException,
|
||||
NoSuchPaddingException, InvalidKeySpecException,
|
||||
InvalidAlgorithmParameterException,
|
||||
KeyException, IOException {
|
||||
return toPrivateKey(keyFile, keyPassword);
|
||||
}
|
||||
|
||||
static TrustManagerFactory buildTrustManagerFactory0(
|
||||
X509Certificate[] certCollection)
|
||||
throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
|
||||
return buildTrustManagerFactory(certCollection, null, null);
|
||||
}
|
||||
|
||||
private static int boringSSLVerifyModeForServer(ClientAuth mode) {
|
||||
switch (mode) {
|
||||
case NONE:
|
||||
return BoringSSL.SSL_VERIFY_NONE;
|
||||
case REQUIRE:
|
||||
return BoringSSL.SSL_VERIFY_PEER | BoringSSL.SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
||||
case OPTIONAL:
|
||||
return BoringSSL.SSL_VERIFY_PEER;
|
||||
default:
|
||||
throw new Error(mode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator, QuicheQuicSslEngine engine) {
|
||||
nativeSslContext.retain();
|
||||
long ssl = BoringSSL.SSL_new(nativeSslContext.address(), isServer(), engine.tlsHostName);
|
||||
engineMap.put(ssl, engine);
|
||||
long connection = connectionCreator.apply(ssl);
|
||||
if (connection == -1) {
|
||||
engineMap.remove(ssl);
|
||||
// We retained before but as we don't create a QuicheQuicConnection and transfer ownership we need to
|
||||
// explict call release again here.
|
||||
nativeSslContext.release();
|
||||
return null;
|
||||
}
|
||||
// The connection will call nativeSslContext.release() once it is freed.
|
||||
return new QuicheQuicConnection(connection, ssl, engine, nativeSslContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given engine to this context
|
||||
*
|
||||
* @param engine the engine to add.
|
||||
* @return the pointer address of this context.
|
||||
*/
|
||||
long add(QuicheQuicSslEngine engine) {
|
||||
nativeSslContext.retain();
|
||||
engine.connection.reattach(nativeSslContext);
|
||||
engineMap.put(engine.connection.ssl, engine);
|
||||
return nativeSslContext.address();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given engine from this context.
|
||||
*
|
||||
* @param engine the engine to remove.
|
||||
*/
|
||||
void remove(QuicheQuicSslEngine engine) {
|
||||
QuicheQuicSslEngine removed = engineMap.remove(engine.connection.ssl);
|
||||
assert removed == null || removed == engine;
|
||||
engine.removeSessionFromCacheIfInvalid();
|
||||
}
|
||||
|
||||
QuicClientSessionCache getSessionCache() {
|
||||
return sessionCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClient() {
|
||||
return !server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> cipherSuites() {
|
||||
return Arrays.asList("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sessionCacheSize() {
|
||||
if (sessionCache != null) {
|
||||
return sessionCache.getSessionCacheSize();
|
||||
} else {
|
||||
synchronized (this) {
|
||||
return sessionCacheSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long sessionTimeout() {
|
||||
if (sessionCache != null) {
|
||||
return sessionCache.getSessionTimeout();
|
||||
} else {
|
||||
synchronized (this) {
|
||||
return sessionTimeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
|
||||
return apn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicSslEngine newEngine(ByteBufAllocator alloc) {
|
||||
return new QuicheQuicSslEngine(this, null, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
|
||||
return new QuicheQuicSslEngine(this, peerHost, peerPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicSslSessionContext sessionContext() {
|
||||
return sessionCtx;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslHandler newHandler(ByteBufAllocator alloc, Executor delegatedTaskExecutor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
|
||||
Executor delegatedTaskExecutor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
|
||||
boolean startTls, Executor delegatedTaskExecutor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
nativeSslContext.release();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
void setSessionTimeout(int seconds) throws IllegalArgumentException {
|
||||
if (sessionCache != null) {
|
||||
sessionCache.setSessionTimeout(seconds);
|
||||
} else {
|
||||
BoringSSL.SSLContext_setSessionCacheTimeout(nativeSslContext.address(), seconds);
|
||||
this.sessionTimeout = seconds;
|
||||
}
|
||||
}
|
||||
|
||||
void setSessionCacheSize(int size) throws IllegalArgumentException {
|
||||
if (sessionCache != null) {
|
||||
sessionCache.setSessionCacheSize(size);
|
||||
} else {
|
||||
BoringSSL.SSLContext_setSessionCacheSize(nativeSslContext.address(), size);
|
||||
sessionCacheSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
void setSessionTicketKeys(SslSessionTicketKey[] ticketKeys) {
|
||||
sessionTicketCallback.setSessionTicketKeys(ticketKeys);
|
||||
BoringSSL.SSLContext_setSessionTicketKeys(
|
||||
nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator {
|
||||
private final List<String> protocols;
|
||||
|
||||
QuicheQuicApplicationProtocolNegotiator(String... protocols) {
|
||||
if (protocols == null) {
|
||||
this.protocols = Collections.emptyList();
|
||||
} else {
|
||||
this.protocols = Collections.unmodifiableList(Arrays.asList(protocols));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> protocols() {
|
||||
return protocols;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext {
|
||||
private final QuicheQuicSslContext context;
|
||||
|
||||
QuicheQuicSslSessionContext(QuicheQuicSslContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSession(byte[] sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<byte[]> getIds() {
|
||||
return new Enumeration<byte[]>() {
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextElement() {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionTimeout(int seconds) throws IllegalArgumentException {
|
||||
context.setSessionTimeout(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionTimeout() {
|
||||
return (int) context.sessionTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionCacheSize(int size) throws IllegalArgumentException {
|
||||
context.setSessionCacheSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSessionCacheSize() {
|
||||
return (int) context.sessionCacheSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTicketKeys(SslSessionTicketKey... keys) {
|
||||
context.setSessionTicketKeys(keys);
|
||||
}
|
||||
}
|
||||
|
||||
static final class NativeSslContext extends AbstractReferenceCounted {
|
||||
private final long ctx;
|
||||
|
||||
NativeSslContext(long ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
long address() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deallocate() {
|
||||
BoringSSL.SSLContext_free(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReferenceCounted touch(Object hint) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NativeSslContext{" +
|
||||
"ctx=" + ctx +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BoringSSLAsyncPrivateKeyMethodAdapter implements BoringSSLPrivateKeyMethod {
|
||||
private final QuicheQuicSslEngineMap engineMap;
|
||||
private final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
|
||||
|
||||
BoringSSLAsyncPrivateKeyMethodAdapter(QuicheQuicSslEngineMap engineMap,
|
||||
BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
|
||||
this.engineMap = engineMap;
|
||||
this.privateKeyMethod = privateKeyMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sign(long ssl, int signatureAlgorithm, byte[] input, BiConsumer<byte[], Throwable> callback) {
|
||||
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
// May be null if it was destroyed in the meantime.
|
||||
callback.accept(null, null);
|
||||
} else {
|
||||
privateKeyMethod.sign(engine, signatureAlgorithm, input).addListener(f -> {
|
||||
Throwable cause = f.cause();
|
||||
if (cause != null) {
|
||||
callback.accept(null, cause);
|
||||
} else {
|
||||
callback.accept((byte[]) f.getNow(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrypt(long ssl, byte[] input, BiConsumer<byte[], Throwable> callback) {
|
||||
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||
if (engine == null) {
|
||||
// May be null if it was destroyed in the meantime.
|
||||
callback.accept(null, null);
|
||||
} else {
|
||||
privateKeyMethod.decrypt(engine, input).addListener(f -> {
|
||||
Throwable cause = f.cause();
|
||||
if (cause != null) {
|
||||
callback.accept(null, cause);
|
||||
} else {
|
||||
callback.accept((byte[]) f.getNow(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,565 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.util.LazyJavaxX509Certificate;
|
||||
import io.netty.handler.ssl.util.LazyX509Certificate;
|
||||
import io.netty.util.NetUtil;
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import javax.net.ssl.SNIHostName;
|
||||
import javax.net.ssl.SNIServerName;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSessionBindingEvent;
|
||||
import javax.net.ssl.SSLSessionBindingListener;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import javax.security.cert.X509Certificate;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Principal;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
final class QuicheQuicSslEngine extends QuicSslEngine {
|
||||
QuicheQuicSslContext ctx;
|
||||
private final String peerHost;
|
||||
private final int peerPort;
|
||||
private final QuicheQuicSslSession session = new QuicheQuicSslSession();
|
||||
private volatile Certificate[] localCertificateChain;
|
||||
private List<SNIServerName> sniHostNames;
|
||||
private boolean handshakeFinished;
|
||||
private String applicationProtocol;
|
||||
private boolean sessionReused;
|
||||
final String tlsHostName;
|
||||
volatile QuicheQuicConnection connection;
|
||||
|
||||
String sniHostname;
|
||||
|
||||
QuicheQuicSslEngine(QuicheQuicSslContext ctx, String peerHost, int peerPort) {
|
||||
this.ctx = ctx;
|
||||
this.peerHost = peerHost;
|
||||
this.peerPort = peerPort;
|
||||
// Use SNI if peerHost was specified and a valid hostname
|
||||
// See https://github.com/netty/netty/issues/4746
|
||||
if (ctx.isClient() && isValidHostNameForSNI(peerHost)) {
|
||||
tlsHostName = peerHost;
|
||||
sniHostNames = Collections.singletonList(new SNIHostName(tlsHostName));
|
||||
} else {
|
||||
tlsHostName = null;
|
||||
}
|
||||
}
|
||||
|
||||
long moveTo(String hostname, QuicheQuicSslContext ctx) {
|
||||
// First of remove the engine from its previous QuicheQuicSslContext.
|
||||
this.ctx.remove(this);
|
||||
this.ctx = ctx;
|
||||
long added = ctx.add(this);
|
||||
sniHostname = hostname;
|
||||
return added;
|
||||
}
|
||||
|
||||
QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator) {
|
||||
return ctx.createConnection(connectionCreator, this);
|
||||
}
|
||||
|
||||
void setLocalCertificateChain(Certificate[] localCertificateChain) {
|
||||
this.localCertificateChain = localCertificateChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given hostname can be used in SNI extension.
|
||||
*/
|
||||
static boolean isValidHostNameForSNI(String hostname) {
|
||||
return hostname != null &&
|
||||
hostname.indexOf('.') > 0 &&
|
||||
!hostname.endsWith(".") &&
|
||||
!NetUtil.isValidIpV4Address(hostname) &&
|
||||
!NetUtil.isValidIpV6Address(hostname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLParameters getSSLParameters() {
|
||||
SSLParameters parameters = super.getSSLParameters();
|
||||
parameters.setServerNames(sniHostNames);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// These method will override the method defined by Java 8u251 and later. As we may compile with an earlier
|
||||
// java8 version we don't use @Override annotations here.
|
||||
public synchronized String getApplicationProtocol() {
|
||||
return applicationProtocol;
|
||||
}
|
||||
|
||||
// These method will override the method defined by Java 8u251 and later. As we may compile with an earlier
|
||||
// java8 version we don't use @Override annotations here.
|
||||
public synchronized String getHandshakeApplicationProtocol() {
|
||||
return applicationProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getDelegatedTask() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInbound() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInboundDone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeOutbound() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOutboundDone() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return ctx.cipherSuites().toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEnabledCipherSuites() {
|
||||
return getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledCipherSuites(String[] suites) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedProtocols() {
|
||||
// QUIC only supports TLSv1.3
|
||||
return new String[] { "TLSv1.3" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getEnabledProtocols() {
|
||||
return getSupportedProtocols();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabledProtocols(String[] protocols) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSession getHandshakeSession() {
|
||||
if (handshakeFinished) {
|
||||
return null;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginHandshake() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
||||
if (handshakeFinished) {
|
||||
return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
|
||||
}
|
||||
return SSLEngineResult.HandshakeStatus.NEED_WRAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseClientMode(boolean clientMode) {
|
||||
if (clientMode != ctx.isClient()) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseClientMode() {
|
||||
return ctx.isClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNeedClientAuth(boolean b) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getNeedClientAuth() {
|
||||
return ctx.clientAuth == ClientAuth.REQUIRE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWantClientAuth(boolean b) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getWantClientAuth() {
|
||||
return ctx.clientAuth == ClientAuth.OPTIONAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnableSessionCreation(boolean flag) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getEnableSessionCreation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain,
|
||||
long creationTime, long timeout,
|
||||
byte[] applicationProtocol, boolean sessionReused) {
|
||||
if (applicationProtocol == null) {
|
||||
this.applicationProtocol = null;
|
||||
} else {
|
||||
this.applicationProtocol = new String(applicationProtocol);
|
||||
}
|
||||
session.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
|
||||
this.sessionReused = sessionReused;
|
||||
handshakeFinished = true;
|
||||
}
|
||||
|
||||
void removeSessionFromCacheIfInvalid() {
|
||||
session.removeFromCacheIfInvalid();
|
||||
}
|
||||
|
||||
synchronized boolean isSessionReused() {
|
||||
return sessionReused;
|
||||
}
|
||||
|
||||
private final class QuicheQuicSslSession implements SSLSession {
|
||||
private X509Certificate[] x509PeerCerts;
|
||||
private Certificate[] peerCerts;
|
||||
private String protocol;
|
||||
private String cipher;
|
||||
private byte[] id;
|
||||
private long creationTime = -1;
|
||||
private long timeout = -1;
|
||||
private boolean invalid;
|
||||
private long lastAccessedTime = -1;
|
||||
|
||||
// lazy init for memory reasons
|
||||
private Map<String, Object> values;
|
||||
|
||||
private boolean isEmpty(Object[] arr) {
|
||||
return arr == null || arr.length == 0;
|
||||
}
|
||||
private boolean isEmpty(byte[] arr) {
|
||||
return arr == null || arr.length == 0;
|
||||
}
|
||||
|
||||
void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||
byte[][] peerCertificateChain, long creationTime, long timeout) {
|
||||
synchronized (QuicheQuicSslEngine.this) {
|
||||
initPeerCerts(peerCertificateChain, peerCertificate);
|
||||
this.id = id;
|
||||
this.cipher = cipher;
|
||||
this.protocol = protocol;
|
||||
this.creationTime = creationTime * 1000L;
|
||||
this.timeout = timeout * 1000L;
|
||||
lastAccessedTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
void removeFromCacheIfInvalid() {
|
||||
if (!isValid()) {
|
||||
// Shouldn't be re-used again
|
||||
removeFromCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFromCache() {
|
||||
// Shouldn't be re-used again
|
||||
QuicClientSessionCache cache = ctx.getSessionCache();
|
||||
if (cache != null) {
|
||||
cache.removeSession(getPeerHost(), getPeerPort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init peer certificates that can be obtained via {@link #getPeerCertificateChain()}
|
||||
* and {@link #getPeerCertificates()}.
|
||||
*/
|
||||
private void initPeerCerts(byte[][] chain, byte[] clientCert) {
|
||||
// Return the full chain from the JNI layer.
|
||||
if (getUseClientMode()) {
|
||||
if (isEmpty(chain)) {
|
||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||
} else {
|
||||
peerCerts = new Certificate[chain.length];
|
||||
x509PeerCerts = new X509Certificate[chain.length];
|
||||
initCerts(chain, 0);
|
||||
}
|
||||
} else {
|
||||
// if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer
|
||||
// certificate. We use SSL_get_peer_certificate to get it in this case and add it to our
|
||||
// array later.
|
||||
//
|
||||
// See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html
|
||||
if (isEmpty(clientCert)) {
|
||||
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||
} else {
|
||||
if (isEmpty(chain)) {
|
||||
peerCerts = new Certificate[] {new LazyX509Certificate(clientCert)};
|
||||
x509PeerCerts = new X509Certificate[] {new LazyJavaxX509Certificate(clientCert)};
|
||||
} else {
|
||||
peerCerts = new Certificate[chain.length + 1];
|
||||
x509PeerCerts = new X509Certificate[chain.length + 1];
|
||||
peerCerts[0] = new LazyX509Certificate(clientCert);
|
||||
x509PeerCerts[0] = new LazyJavaxX509Certificate(clientCert);
|
||||
initCerts(chain, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initCerts(byte[][] chain, int startPos) {
|
||||
for (int i = 0; i < chain.length; i++) {
|
||||
int certPos = startPos + i;
|
||||
peerCerts[certPos] = new LazyX509Certificate(chain[i]);
|
||||
x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getId() {
|
||||
synchronized (QuicheQuicSslSession.this) {
|
||||
if (id == null) {
|
||||
return EmptyArrays.EMPTY_BYTES;
|
||||
}
|
||||
return id.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLSessionContext getSessionContext() {
|
||||
return ctx.sessionContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
synchronized (QuicheQuicSslEngine.this) {
|
||||
return creationTime;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
return lastAccessedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
boolean removeFromCache;
|
||||
synchronized (this) {
|
||||
removeFromCache = !invalid;
|
||||
invalid = true;
|
||||
}
|
||||
if (removeFromCache) {
|
||||
removeFromCache();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
synchronized (QuicheQuicSslEngine.this) {
|
||||
return !invalid && System.currentTimeMillis() - timeout < creationTime;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putValue(String name, Object value) {
|
||||
ObjectUtil.checkNotNull(name, "name");
|
||||
ObjectUtil.checkNotNull(value, "value");
|
||||
|
||||
final Object old;
|
||||
synchronized (this) {
|
||||
Map<String, Object> values = this.values;
|
||||
if (values == null) {
|
||||
// Use size of 2 to keep the memory overhead small
|
||||
values = this.values = new HashMap<>(2);
|
||||
}
|
||||
old = values.put(name, value);
|
||||
}
|
||||
|
||||
if (value instanceof SSLSessionBindingListener) {
|
||||
// Use newSSLSessionBindingEvent so we alway use the wrapper if needed.
|
||||
((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name));
|
||||
}
|
||||
notifyUnbound(old, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String name) {
|
||||
ObjectUtil.checkNotNull(name, "name");
|
||||
synchronized (this) {
|
||||
if (values == null) {
|
||||
return null;
|
||||
}
|
||||
return values.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(String name) {
|
||||
ObjectUtil.checkNotNull(name, "name");
|
||||
|
||||
final Object old;
|
||||
synchronized (this) {
|
||||
Map<String, Object> values = this.values;
|
||||
if (values == null) {
|
||||
return;
|
||||
}
|
||||
old = values.remove(name);
|
||||
}
|
||||
|
||||
notifyUnbound(old, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValueNames() {
|
||||
synchronized (this) {
|
||||
Map<String, Object> values = this.values;
|
||||
if (values == null || values.isEmpty()) {
|
||||
return EmptyArrays.EMPTY_STRINGS;
|
||||
}
|
||||
return values.keySet().toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) {
|
||||
return new SSLSessionBindingEvent(session, name);
|
||||
}
|
||||
|
||||
private void notifyUnbound(Object value, String name) {
|
||||
if (value instanceof SSLSessionBindingListener) {
|
||||
// Use newSSLSessionBindingEvent so we alway use the wrapper if needed.
|
||||
((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
|
||||
synchronized (QuicheQuicSslEngine.this) {
|
||||
if (isEmpty(peerCerts)) {
|
||||
throw new SSLPeerUnverifiedException("peer not verified");
|
||||
}
|
||||
return peerCerts.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getLocalCertificates() {
|
||||
Certificate[] localCerts = localCertificateChain;
|
||||
if (localCerts == null) {
|
||||
return null;
|
||||
}
|
||||
return localCerts.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
|
||||
synchronized (QuicheQuicSslEngine.this) {
|
||||
if (isEmpty(x509PeerCerts)) {
|
||||
throw new SSLPeerUnverifiedException("peer not verified");
|
||||
}
|
||||
return x509PeerCerts.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
|
||||
Certificate[] peer = getPeerCertificates();
|
||||
// No need for null or length > 0 is needed as this is done in getPeerCertificates()
|
||||
// already.
|
||||
return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getLocalPrincipal() {
|
||||
Certificate[] local = localCertificateChain;
|
||||
if (local == null || local.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherSuite() {
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPeerHost() {
|
||||
return peerHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeerPort() {
|
||||
return peerPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPacketBufferSize() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getApplicationBufferSize() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
final class QuicheQuicSslEngineMap {
|
||||
|
||||
private final ConcurrentMap<Long, QuicheQuicSslEngine> engines = new ConcurrentHashMap<>();
|
||||
|
||||
QuicheQuicSslEngine get(long ssl) {
|
||||
return engines.get(ssl);
|
||||
}
|
||||
|
||||
QuicheQuicSslEngine remove(long ssl) {
|
||||
return engines.remove(ssl);
|
||||
}
|
||||
|
||||
void put(long ssl, QuicheQuicSslEngine engine) {
|
||||
engines.put(ssl, engine);
|
||||
}
|
||||
}
|
@ -0,0 +1,956 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelId;
|
||||
import io.netty.channel.ChannelMetadata;
|
||||
import io.netty.channel.ChannelOutboundBuffer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.DefaultChannelId;
|
||||
import io.netty.channel.DefaultChannelPipeline;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.PendingWriteQueue;
|
||||
import io.netty.channel.RecvByteBufAllocator;
|
||||
import io.netty.channel.VoidChannelPromise;
|
||||
import io.netty.channel.socket.ChannelInputShutdownEvent;
|
||||
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
||||
import io.netty.channel.socket.ChannelOutputShutdownException;
|
||||
import io.netty.util.DefaultAttributeMap;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.PromiseNotifier;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* {@link QuicStreamChannel} implementation that uses <a href="https://github.com/cloudflare/quiche">quiche</a>.
|
||||
*/
|
||||
final class QuicheQuicStreamChannel extends DefaultAttributeMap implements QuicStreamChannel {
|
||||
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
|
||||
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicStreamChannel.class);
|
||||
private final QuicheQuicChannel parent;
|
||||
private final ChannelId id;
|
||||
private final ChannelPipeline pipeline;
|
||||
private final QuicStreamChannelUnsafe unsafe;
|
||||
private final ChannelPromise closePromise;
|
||||
private final PendingWriteQueue queue;
|
||||
|
||||
private final QuicStreamChannelConfig config;
|
||||
private final QuicStreamAddress address;
|
||||
|
||||
private boolean readable;
|
||||
private boolean readPending;
|
||||
private boolean inRecv;
|
||||
private boolean inWriteQueued;
|
||||
private boolean finReceived;
|
||||
private boolean finSent;
|
||||
|
||||
private volatile boolean registered;
|
||||
private volatile boolean writable = true;
|
||||
private volatile boolean active = true;
|
||||
private volatile boolean inputShutdown;
|
||||
private volatile boolean outputShutdown;
|
||||
private volatile QuicStreamPriority priority;
|
||||
private volatile int capacity;
|
||||
|
||||
QuicheQuicStreamChannel(QuicheQuicChannel parent, long streamId) {
|
||||
this.parent = parent;
|
||||
this.id = DefaultChannelId.newInstance();
|
||||
unsafe = new QuicStreamChannelUnsafe();
|
||||
this.pipeline = new DefaultChannelPipeline(this) {
|
||||
// TODO: add some overrides maybe ?
|
||||
};
|
||||
config = new QuicheQuicStreamChannelConfig(this);
|
||||
this.address = new QuicStreamAddress(streamId);
|
||||
this.closePromise = newPromise();
|
||||
queue = new PendingWriteQueue(this);
|
||||
// Local created unidirectional streams have the input shutdown by spec. There will never be any data for
|
||||
// these to be read.
|
||||
if (parent.streamType(streamId) == QuicStreamType.UNIDIRECTIONAL && parent.isStreamLocalCreated(streamId)) {
|
||||
inputShutdown = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamAddress localAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamAddress remoteAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocalCreated() {
|
||||
return parent().isStreamLocalCreated(streamId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamType type() {
|
||||
return parent().streamType(streamId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long streamId() {
|
||||
return address.streamId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamPriority priority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture updatePriority(QuicStreamPriority priority, ChannelPromise promise) {
|
||||
if (eventLoop().inEventLoop()) {
|
||||
updatePriority0(priority, promise);
|
||||
} else {
|
||||
eventLoop().execute(() -> updatePriority0(priority, promise));
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
private void updatePriority0(QuicStreamPriority priority, ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
try {
|
||||
parent().streamPriority(streamId(), (byte) priority.urgency(), priority.isIncremental());
|
||||
} catch (Throwable cause) {
|
||||
promise.setFailure(cause);
|
||||
return;
|
||||
}
|
||||
this.priority = priority;
|
||||
promise.setSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInputShutdown() {
|
||||
return inputShutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture shutdownOutput(ChannelPromise promise) {
|
||||
if (eventLoop().inEventLoop()) {
|
||||
shutdownOutput0(promise);
|
||||
} else {
|
||||
eventLoop().execute(() -> shutdownOutput0(promise));
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
private void shutdownOutput0(ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
outputShutdown = true;
|
||||
unsafe.writeWithoutCheckChannelState(QuicStreamFrame.EMPTY_FIN, promise);
|
||||
unsafe.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture shutdownInput(int error, ChannelPromise promise) {
|
||||
if (eventLoop().inEventLoop()) {
|
||||
shutdownInput0(error, promise);
|
||||
} else {
|
||||
eventLoop().execute(() -> shutdownInput0(error, promise));
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture shutdownOutput(int error, ChannelPromise promise) {
|
||||
if (eventLoop().inEventLoop()) {
|
||||
shutdownOutput0(error, promise);
|
||||
} else {
|
||||
eventLoop().execute(() -> shutdownOutput0(error, promise));
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicheQuicChannel parent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
private void shutdownInput0(int err, ChannelPromise channelPromise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
inputShutdown = true;
|
||||
parent().streamShutdown(streamId(), true, false, err, channelPromise);
|
||||
closeIfDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOutputShutdown() {
|
||||
return outputShutdown;
|
||||
}
|
||||
|
||||
private void shutdownOutput0(int error, ChannelPromise channelPromise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
parent().streamShutdown(streamId(), false, true, error, channelPromise);
|
||||
outputShutdown = true;
|
||||
closeIfDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return outputShutdown && inputShutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture shutdown(ChannelPromise channelPromise) {
|
||||
if (eventLoop().inEventLoop()) {
|
||||
shutdown0(channelPromise);
|
||||
} else {
|
||||
eventLoop().execute(() -> shutdown0(channelPromise));
|
||||
}
|
||||
return channelPromise;
|
||||
}
|
||||
|
||||
private void shutdown0(ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
inputShutdown = true;
|
||||
outputShutdown = true;
|
||||
unsafe.writeWithoutCheckChannelState(QuicStreamFrame.EMPTY_FIN, unsafe.voidPromise());
|
||||
unsafe.flush();
|
||||
parent().streamShutdown(streamId(), true, false, 0, promise);
|
||||
closeIfDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture shutdown(int error, ChannelPromise promise) {
|
||||
if (eventLoop().inEventLoop()) {
|
||||
shutdown0(error, promise);
|
||||
} else {
|
||||
eventLoop().execute(() -> shutdown0(error, promise));
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
private void shutdown0(int error, ChannelPromise channelPromise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
inputShutdown = true;
|
||||
outputShutdown = true;
|
||||
parent().streamShutdown(streamId(), true, true, error, channelPromise);
|
||||
closeIfDone();
|
||||
}
|
||||
|
||||
private void sendFinIfNeeded() throws Exception {
|
||||
if (!finSent) {
|
||||
finSent = true;
|
||||
parent().streamSendFin(streamId());
|
||||
}
|
||||
}
|
||||
|
||||
private void closeIfDone() {
|
||||
if (finSent && (finReceived || type() == QuicStreamType.UNIDIRECTIONAL && isLocalCreated())) {
|
||||
unsafe().close(unsafe().voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
private void removeStreamFromParent() {
|
||||
if (!active && finReceived) {
|
||||
parent().streamClosed(streamId());
|
||||
inputShutdown = true;
|
||||
outputShutdown = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannel flush() {
|
||||
pipeline.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannel read() {
|
||||
pipeline.read();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig config() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelMetadata metadata() {
|
||||
return METADATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelId id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
return parent.eventLoop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture closeFuture() {
|
||||
return closePromise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return writable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long bytesBeforeUnwritable() {
|
||||
// Capacity might be negative if the stream was closed.
|
||||
return Math.max(capacity, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long bytesBeforeWritable() {
|
||||
if (writable) {
|
||||
return 0;
|
||||
}
|
||||
// Just return something positive for now
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unsafe unsafe() {
|
||||
return unsafe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline pipeline() {
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return config.getAllocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Channel o) {
|
||||
return id.compareTo(o.id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of this channel.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if and only if the specified object is identical
|
||||
* with this channel (i.e: {@code this == o}).
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[id: 0x" + id.asShortText() + ", " + address + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream is writable.
|
||||
*/
|
||||
boolean writable(@SuppressWarnings("unused") int capacity) {
|
||||
assert eventLoop().inEventLoop();
|
||||
this.capacity = capacity;
|
||||
boolean mayNeedWrite = ((QuicStreamChannelUnsafe) unsafe()).writeQueued();
|
||||
// we need to re-read this.capacity as writeQueued() may update the capacity.
|
||||
updateWritabilityIfNeeded(this.capacity > 0);
|
||||
return mayNeedWrite;
|
||||
}
|
||||
|
||||
private void updateWritabilityIfNeeded(boolean newWritable) {
|
||||
if (writable != newWritable) {
|
||||
writable = newWritable;
|
||||
pipeline.fireChannelWritabilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream is readable.
|
||||
*/
|
||||
void readable() {
|
||||
assert eventLoop().inEventLoop();
|
||||
// Mark as readable and if a read is pending execute it.
|
||||
readable = true;
|
||||
if (readPending) {
|
||||
((QuicStreamChannelUnsafe) unsafe()).recv();
|
||||
}
|
||||
}
|
||||
|
||||
void forceClose() {
|
||||
assert eventLoop().inEventLoop();
|
||||
// Set received to true to ensure we will remove it from the internal map once we send the fin.
|
||||
finSent = true;
|
||||
unsafe().close(unsafe().voidPromise());
|
||||
}
|
||||
|
||||
private final class QuicStreamChannelUnsafe implements Unsafe {
|
||||
private RecvByteBufAllocator.Handle recvHandle;
|
||||
|
||||
private final ChannelPromise voidPromise = new VoidChannelPromise(
|
||||
QuicheQuicStreamChannel.this, false);
|
||||
@Override
|
||||
public void connect(SocketAddress remote, SocketAddress local, ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
promise.setFailure(new UnsupportedOperationException());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public RecvByteBufAllocator.Handle recvBufAllocHandle() {
|
||||
if (recvHandle == null) {
|
||||
recvHandle = config.getRecvByteBufAllocator().newHandle();
|
||||
}
|
||||
return recvHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress localAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress remoteAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(EventLoop eventLoop, ChannelPromise promise) {
|
||||
assert eventLoop.inEventLoop();
|
||||
if (registered) {
|
||||
promise.setFailure(new IllegalStateException());
|
||||
return;
|
||||
}
|
||||
if (eventLoop != parent.eventLoop()) {
|
||||
promise.setFailure(new IllegalArgumentException());
|
||||
return;
|
||||
}
|
||||
registered = true;
|
||||
promise.setSuccess();
|
||||
pipeline.fireChannelRegistered();
|
||||
pipeline.fireChannelActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
promise.setFailure(new UnsupportedOperationException());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
close(promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
if (!active || closePromise.isDone()) {
|
||||
if (promise.isVoid()) {
|
||||
return;
|
||||
}
|
||||
closePromise.addListener(new PromiseNotifier<>(promise));
|
||||
return;
|
||||
}
|
||||
active = false;
|
||||
try {
|
||||
// Close the channel and fail the queued messages in all cases.
|
||||
sendFinIfNeeded();
|
||||
} catch (Exception ignore) {
|
||||
// Just ignore
|
||||
} finally {
|
||||
if (!queue.isEmpty()) {
|
||||
// Only fail if the queue is non-empty.
|
||||
queue.removeAndFailAll(new ClosedChannelException());
|
||||
}
|
||||
|
||||
promise.trySuccess();
|
||||
closePromise.trySuccess();
|
||||
if (type() == QuicStreamType.UNIDIRECTIONAL && isLocalCreated()) {
|
||||
inputShutdown = true;
|
||||
outputShutdown = true;
|
||||
// If its an unidirectional stream and was created locally it is safe to close the stream now as
|
||||
// we will never receive data from the other side.
|
||||
parent().streamClosed(streamId());
|
||||
} else {
|
||||
removeStreamFromParent();
|
||||
}
|
||||
}
|
||||
if (inWriteQueued) {
|
||||
invokeLater(() -> deregister(voidPromise(), true));
|
||||
} else {
|
||||
deregister(voidPromise(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
|
||||
assert eventLoop().inEventLoop();
|
||||
if (!promise.setUncancellable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!registered) {
|
||||
promise.trySuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
// As a user may call deregister() from within any method while doing processing in the ChannelPipeline,
|
||||
// we need to ensure we do the actual deregister operation later. This is needed as for example,
|
||||
// we may be in the ByteToMessageDecoder.callDecode(...) method and so still try to do processing in
|
||||
// the old EventLoop while the user already registered the Channel to a new EventLoop. Without delay,
|
||||
// the deregister operation this could lead to have a handler invoked by different EventLoop and so
|
||||
// threads.
|
||||
//
|
||||
// See:
|
||||
// https://github.com/netty/netty/issues/4435
|
||||
invokeLater(() -> {
|
||||
if (fireChannelInactive) {
|
||||
pipeline.fireChannelInactive();
|
||||
}
|
||||
// Some transports like local and AIO does not allow the deregistration of
|
||||
// an open channel. Their doDeregister() calls close(). Consequently,
|
||||
// close() calls deregister() again - no need to fire channelUnregistered, so check
|
||||
// if it was registered.
|
||||
if (registered) {
|
||||
registered = false;
|
||||
pipeline.fireChannelUnregistered();
|
||||
}
|
||||
promise.setSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
private void invokeLater(Runnable task) {
|
||||
try {
|
||||
// This method is used by outbound operation implementations to trigger an inbound event later.
|
||||
// They do not trigger an inbound event immediately because an outbound operation might have been
|
||||
// triggered by another inbound event handler method. If fired immediately, the call stack
|
||||
// will look like this for example:
|
||||
//
|
||||
// handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
|
||||
// -> handlerA.ctx.close()
|
||||
// -> channel.unsafe.close()
|
||||
// -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet
|
||||
//
|
||||
// which means the execution of two inbound handler methods of the same handler overlap undesirably.
|
||||
eventLoop().execute(task);
|
||||
} catch (RejectedExecutionException e) {
|
||||
LOGGER.warn("Can't invoke task later as EventLoop rejected it", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeForcibly() {
|
||||
assert eventLoop().inEventLoop();
|
||||
close(unsafe().voidPromise());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deregister(ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
deregister(promise, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginRead() {
|
||||
assert eventLoop().inEventLoop();
|
||||
readPending = true;
|
||||
if (readable) {
|
||||
((QuicStreamChannelUnsafe) unsafe()).recv();
|
||||
|
||||
// As the stream was readable, and we called recv() ourselves we also need to call
|
||||
// connectionSendAndFlush(). This is needed as recv() might consume data and so a window update
|
||||
// frame might be produced. If we miss to call connectionSendAndFlush() we might never send the update
|
||||
// to the remote peer and so the remote peer might never attempt to send more data.
|
||||
// See also https://docs.rs/quiche/latest/quiche/struct.Connection.html#method.send.
|
||||
parent().connectionSendAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
private void closeIfNeeded(boolean wasFinSent) {
|
||||
// Let's check if we should close the channel now.
|
||||
// If it's a unidirectional channel we can close it as there will be no fin that we can read
|
||||
// from the remote peer. If its an bidirectional channel we should only close the channel if we
|
||||
// also received the fin from the remote peer.
|
||||
if (!wasFinSent && QuicheQuicStreamChannel.this.finSent
|
||||
&& (type() == QuicStreamType.UNIDIRECTIONAL || finReceived)) {
|
||||
// close the channel now
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
boolean writeQueued() {
|
||||
assert eventLoop().inEventLoop();
|
||||
boolean wasFinSent = QuicheQuicStreamChannel.this.finSent;
|
||||
inWriteQueued = true;
|
||||
try {
|
||||
if (queue.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean written = false;
|
||||
for (;;) {
|
||||
Object msg = queue.current();
|
||||
if (msg == null) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (!write0(msg)) {
|
||||
return written;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof QuicException && (
|
||||
(QuicException) e).error() == QuicError.STREAM_STOPPED) {
|
||||
// Once its signaled that the stream is stopped we can just fail everything.
|
||||
queue.removeAndFailAll(e);
|
||||
forceClose();
|
||||
break;
|
||||
}
|
||||
queue.remove().setFailure(e);
|
||||
continue;
|
||||
}
|
||||
queue.remove().setSuccess();
|
||||
written = true;
|
||||
}
|
||||
updateWritabilityIfNeeded(true);
|
||||
return written;
|
||||
} finally {
|
||||
closeIfNeeded(wasFinSent);
|
||||
inWriteQueued = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object msg, ChannelPromise promise) {
|
||||
assert eventLoop().inEventLoop();
|
||||
|
||||
// Check first if the Channel is in a state in which it will accept writes, if not fail everything
|
||||
// with the right exception
|
||||
if (!isOpen()) {
|
||||
queueAndFailAll(msg, promise, new ClosedChannelException());
|
||||
} else if (finSent) {
|
||||
queueAndFailAll(msg, promise, new ChannelOutputShutdownException("Fin was sent already"));
|
||||
} else {
|
||||
writeWithoutCheckChannelState(msg, promise);
|
||||
}
|
||||
}
|
||||
|
||||
private void queueAndFailAll(Object msg, ChannelPromise promise, Throwable cause) {
|
||||
queue.add(msg, promise);
|
||||
queue.removeAndFailAll(cause);
|
||||
}
|
||||
|
||||
void writeWithoutCheckChannelState(Object msg, ChannelPromise promise) {
|
||||
if (msg instanceof ByteBuf) {
|
||||
ByteBuf buffer = (ByteBuf) msg;
|
||||
if (!buffer.isDirect()) {
|
||||
ByteBuf tmpBuffer = alloc().directBuffer(buffer.readableBytes());
|
||||
tmpBuffer.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||
buffer.release();
|
||||
msg = tmpBuffer;
|
||||
}
|
||||
} else if (msg instanceof QuicStreamFrame) {
|
||||
QuicStreamFrame frame = (QuicStreamFrame) msg;
|
||||
ByteBuf buffer = frame.content();
|
||||
if (!buffer.isDirect()) {
|
||||
ByteBuf tmpBuffer = alloc().directBuffer(buffer.readableBytes());
|
||||
tmpBuffer.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||
buffer.release();
|
||||
msg = frame.replace(tmpBuffer);
|
||||
}
|
||||
} else {
|
||||
ReferenceCountUtil.release(msg);
|
||||
promise.setFailure(new UnsupportedOperationException(
|
||||
"unsupported message type: " + StringUtil.simpleClassName(msg)));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasFinSent = QuicheQuicStreamChannel.this.finSent;
|
||||
boolean mayNeedWritabilityUpdate = false;
|
||||
try {
|
||||
if (write0(msg)) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
promise.setSuccess();
|
||||
mayNeedWritabilityUpdate = capacity == 0;
|
||||
} else {
|
||||
queue.add(msg, promise);
|
||||
mayNeedWritabilityUpdate = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
promise.setFailure(e);
|
||||
mayNeedWritabilityUpdate = capacity == 0;
|
||||
} finally {
|
||||
if (mayNeedWritabilityUpdate) {
|
||||
updateWritabilityIfNeeded(false);
|
||||
}
|
||||
closeIfNeeded(wasFinSent);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean write0(Object msg) throws Exception {
|
||||
if (type() == QuicStreamType.UNIDIRECTIONAL && !isLocalCreated()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Writes on non-local created streams that are unidirectional are not supported");
|
||||
}
|
||||
if (finSent) {
|
||||
throw new ChannelOutputShutdownException("Fin was sent already");
|
||||
}
|
||||
|
||||
final boolean fin;
|
||||
ByteBuf buffer;
|
||||
if (msg instanceof ByteBuf) {
|
||||
fin = false;
|
||||
buffer = (ByteBuf) msg;
|
||||
} else {
|
||||
QuicStreamFrame frame = (QuicStreamFrame) msg;
|
||||
fin = frame.hasFin();
|
||||
buffer = frame.content();
|
||||
}
|
||||
|
||||
boolean readable = buffer.isReadable();
|
||||
if (!fin && !readable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean sendSomething = false;
|
||||
try {
|
||||
do {
|
||||
int res = parent().streamSend(streamId(), buffer, fin);
|
||||
|
||||
// Update the capacity as well.
|
||||
int cap = parent.streamCapacity(streamId());
|
||||
if (cap >= 0) {
|
||||
capacity = cap;
|
||||
}
|
||||
if (Quiche.throwIfError(res) || (readable && res == 0)) {
|
||||
return false;
|
||||
}
|
||||
sendSomething = true;
|
||||
buffer.skipBytes(res);
|
||||
} while (buffer.isReadable());
|
||||
|
||||
if (fin) {
|
||||
finSent = true;
|
||||
outputShutdown = true;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
// As we called quiche_conn_stream_send(...) we need to ensure we will call quiche_conn_send(...) either
|
||||
// now or we will do so once we see the channelReadComplete event.
|
||||
//
|
||||
// See https://docs.rs/quiche/0.6.0/quiche/struct.Connection.html#method.send
|
||||
if (sendSomething) {
|
||||
parent.connectionSendAndFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
assert eventLoop().inEventLoop();
|
||||
// NOOP.
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPromise voidPromise() {
|
||||
assert eventLoop().inEventLoop();
|
||||
return voidPromise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelOutboundBuffer outboundBuffer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void closeOnRead(ChannelPipeline pipeline, boolean readFrames) {
|
||||
if (readFrames && finReceived && finSent) {
|
||||
close(voidPromise());
|
||||
} else if (config.isAllowHalfClosure()) {
|
||||
if (finReceived) {
|
||||
// If we receive a fin there will be no more data to read so we need to fire both events
|
||||
// to be consistent with other transports.
|
||||
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
||||
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
|
||||
if (finSent) {
|
||||
// This was an unidirectional stream which means as soon as we received FIN and sent a FIN
|
||||
// we need close the connection.
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This was an unidirectional stream which means as soon as we received FIN we need
|
||||
// close the connection.
|
||||
close(voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause,
|
||||
@SuppressWarnings("deprecation") RecvByteBufAllocator.Handle allocHandle,
|
||||
boolean readFrames) {
|
||||
if (byteBuf != null) {
|
||||
if (byteBuf.isReadable()) {
|
||||
pipeline.fireChannelRead(byteBuf);
|
||||
} else {
|
||||
byteBuf.release();
|
||||
}
|
||||
}
|
||||
|
||||
readComplete(allocHandle, pipeline);
|
||||
pipeline.fireExceptionCaught(cause);
|
||||
if (finReceived) {
|
||||
closeOnRead(pipeline, readFrames);
|
||||
}
|
||||
}
|
||||
|
||||
void recv() {
|
||||
assert eventLoop().inEventLoop();
|
||||
if (inRecv) {
|
||||
// As the use may call read() we need to guard against re-entrancy here as otherwise it could
|
||||
// be possible that we re-enter this method while still processing it.
|
||||
return;
|
||||
}
|
||||
|
||||
inRecv = true;
|
||||
try {
|
||||
ChannelPipeline pipeline = pipeline();
|
||||
QuicheQuicStreamChannelConfig config = (QuicheQuicStreamChannelConfig) config();
|
||||
// Directly access the DirectIoByteBufAllocator as we need an direct buffer to read into in all cases
|
||||
// even if there is no Unsafe present and the direct buffer is not pooled.
|
||||
DirectIoByteBufAllocator allocator = config.allocator;
|
||||
@SuppressWarnings("deprecation")
|
||||
RecvByteBufAllocator.Handle allocHandle = this.recvBufAllocHandle();
|
||||
boolean readFrames = config.isReadFrames();
|
||||
|
||||
// We should loop as long as a read() was requested and there is anything left to read, which means the
|
||||
// stream was marked as readable before.
|
||||
while (active && readPending && readable) {
|
||||
allocHandle.reset(config);
|
||||
ByteBuf byteBuf = null;
|
||||
QuicheQuicChannel parent = parent();
|
||||
// It's possible that the stream was marked as finish while we iterated over the readable streams
|
||||
// or while we did have auto read disabled. If so we need to ensure we not try to read from it as it
|
||||
// would produce an error.
|
||||
boolean readCompleteNeeded = false;
|
||||
boolean continueReading = true;
|
||||
try {
|
||||
while (!finReceived && continueReading) {
|
||||
byteBuf = allocHandle.allocate(allocator);
|
||||
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
|
||||
switch (parent.streamRecv(streamId(), byteBuf)) {
|
||||
case DONE:
|
||||
// Nothing left to read;
|
||||
readable = false;
|
||||
break;
|
||||
case FIN:
|
||||
// If we received a FIN we also should mark the channel as non readable as
|
||||
// there is nothing left to read really.
|
||||
readable = false;
|
||||
finReceived = true;
|
||||
inputShutdown = true;
|
||||
break;
|
||||
case OK:
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
allocHandle.lastBytesRead(byteBuf.readableBytes());
|
||||
if (allocHandle.lastBytesRead() <= 0) {
|
||||
byteBuf.release();
|
||||
if (finReceived && readFrames) {
|
||||
// If we read QuicStreamFrames we should fire an frame through the pipeline
|
||||
// with an empty buffer but the fin flag set to true.
|
||||
byteBuf = Unpooled.EMPTY_BUFFER;
|
||||
} else {
|
||||
byteBuf = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We did read one message.
|
||||
allocHandle.incMessagesRead(1);
|
||||
readCompleteNeeded = true;
|
||||
|
||||
// It's important that we reset this to false before we call fireChannelRead(...)
|
||||
// as the user may request another read() from channelRead(...) callback.
|
||||
readPending = false;
|
||||
|
||||
if (readFrames) {
|
||||
pipeline.fireChannelRead(new DefaultQuicStreamFrame(byteBuf, finReceived));
|
||||
} else {
|
||||
pipeline.fireChannelRead(byteBuf);
|
||||
}
|
||||
byteBuf = null;
|
||||
continueReading = allocHandle.continueReading();
|
||||
}
|
||||
|
||||
if (readCompleteNeeded) {
|
||||
readComplete(allocHandle, pipeline);
|
||||
}
|
||||
if (finReceived) {
|
||||
readable = false;
|
||||
closeOnRead(pipeline, readFrames);
|
||||
}
|
||||
} catch (Throwable cause) {
|
||||
readable = false;
|
||||
handleReadException(pipeline, byteBuf, cause, allocHandle, readFrames);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// About to leave the method lets reset so we can enter it again.
|
||||
inRecv = false;
|
||||
removeStreamFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
// Read was complete and something was read, so we we need to reset the readPending flags, the allocHandle
|
||||
// and call fireChannelReadComplete(). The user may schedule another read now.
|
||||
private void readComplete(@SuppressWarnings("deprecation") RecvByteBufAllocator.Handle allocHandle,
|
||||
ChannelPipeline pipeline) {
|
||||
allocHandle.readComplete();
|
||||
pipeline.fireChannelReadComplete();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.DefaultChannelConfig;
|
||||
import io.netty.channel.MessageSizeEstimator;
|
||||
import io.netty.channel.RecvByteBufAllocator;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
final class QuicheQuicStreamChannelConfig extends DefaultChannelConfig implements QuicStreamChannelConfig {
|
||||
// We should use half-closure sementatics by default as this is what QUIC does by default.
|
||||
// If you receive a FIN you should still keep the stream open until you write a FIN as well.
|
||||
private volatile boolean allowHalfClosure = true;
|
||||
private volatile boolean readFrames;
|
||||
volatile DirectIoByteBufAllocator allocator;
|
||||
|
||||
QuicheQuicStreamChannelConfig(QuicStreamChannel channel) {
|
||||
super(channel);
|
||||
allocator = new DirectIoByteBufAllocator(super.getAllocator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ChannelOption<?>, Object> getOptions() {
|
||||
if (isHalfClosureSupported()) {
|
||||
return getOptions(super.getOptions(), ChannelOption.ALLOW_HALF_CLOSURE, QuicChannelOption.READ_FRAMES);
|
||||
}
|
||||
return super.getOptions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getOption(ChannelOption<T> option) {
|
||||
if (option == ChannelOption.ALLOW_HALF_CLOSURE) {
|
||||
return (T) Boolean.valueOf(isAllowHalfClosure());
|
||||
}
|
||||
if (option == QuicChannelOption.READ_FRAMES) {
|
||||
return (T) Boolean.valueOf(isReadFrames());
|
||||
}
|
||||
return super.getOption(option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean setOption(ChannelOption<T> option, T value) {
|
||||
validate(option, value);
|
||||
|
||||
if (option == ChannelOption.ALLOW_HALF_CLOSURE) {
|
||||
if (isHalfClosureSupported()) {
|
||||
setAllowHalfClosure((Boolean) value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (option == QuicChannelOption.READ_FRAMES) {
|
||||
setReadFrames((Boolean) value);
|
||||
}
|
||||
return super.setOption(option, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setReadFrames(boolean readFrames) {
|
||||
this.readFrames = readFrames;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadFrames() {
|
||||
return readFrames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
super.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) {
|
||||
super.setMaxMessagesPerRead(maxMessagesPerRead);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setWriteSpinCount(int writeSpinCount) {
|
||||
super.setWriteSpinCount(writeSpinCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setAllocator(ByteBufAllocator allocator) {
|
||||
this.allocator = new DirectIoByteBufAllocator(allocator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
|
||||
super.setRecvByteBufAllocator(allocator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setAutoRead(boolean autoRead) {
|
||||
super.setAutoRead(autoRead);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setAutoClose(boolean autoClose) {
|
||||
super.setAutoClose(autoClose);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
|
||||
super.setWriteBufferHighWaterMark(writeBufferHighWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
|
||||
super.setWriteBufferLowWaterMark(writeBufferLowWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||
super.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) {
|
||||
super.setMessageSizeEstimator(estimator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuicStreamChannelConfig setAllowHalfClosure(boolean allowHalfClosure) {
|
||||
if (!isHalfClosureSupported()) {
|
||||
throw new UnsupportedOperationException("Undirectional streams don't support half-closure");
|
||||
}
|
||||
this.allowHalfClosure = allowHalfClosure;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator getAllocator() {
|
||||
return allocator.wrapped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowHalfClosure() {
|
||||
return allowHalfClosure;
|
||||
}
|
||||
|
||||
private boolean isHalfClosureSupported() {
|
||||
return ((QuicStreamChannel) channel).type() == QuicStreamType.BIDIRECTIONAL;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2023 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
final class QuicheQuicTransportParameters implements QuicTransportParameters {
|
||||
private final long[] values;
|
||||
|
||||
QuicheQuicTransportParameters(long[] values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long maxIdleTimeout() {
|
||||
return values[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long maxUdpPayloadSize() {
|
||||
return values[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long initialMaxData() {
|
||||
return values[2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long initialMaxStreamDataBidiLocal() {
|
||||
return values[3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long initialMaxStreamDataBidiRemote() {
|
||||
return values[4];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long initialMaxStreamDataUni() {
|
||||
return values[5];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long initialMaxStreamsBidi() {
|
||||
return values[6];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long initialMaxStreamsUni() {
|
||||
return values[7];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long ackDelayExponent() {
|
||||
return values[8];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long maxAckDelay() {
|
||||
return values[9];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean disableActiveMigration() {
|
||||
return values[10] == 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long activeConnIdLimit() {
|
||||
return values[11];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long maxDatagramFrameSize() {
|
||||
return values[12];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.simpleClassName(this) + "[" +
|
||||
"maxIdleTimeout=" + maxIdleTimeout() +
|
||||
", maxUdpPayloadSize=" + maxUdpPayloadSize() +
|
||||
", initialMaxData=" + initialMaxData() +
|
||||
", initialMaxStreamDataBidiLocal=" + initialMaxStreamDataBidiLocal() +
|
||||
", initialMaxStreamDataBidiRemote=" + initialMaxStreamDataBidiRemote() +
|
||||
", initialMaxStreamDataUni=" + initialMaxStreamDataUni() +
|
||||
", initialMaxStreamsBidi=" + initialMaxStreamsBidi() +
|
||||
", initialMaxStreamsUni=" + initialMaxStreamsUni() +
|
||||
", ackDelayExponent=" + ackDelayExponent() +
|
||||
", maxAckDelay=" + maxAckDelay() +
|
||||
", disableActiveMigration=" + disableActiveMigration() +
|
||||
", activeConnIdLimit=" + activeConnIdLimit() +
|
||||
", maxDatagramFrameSize=" + maxDatagramFrameSize() +
|
||||
"]";
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class to handle access to {@code quiche_recv_info}.
|
||||
*/
|
||||
final class QuicheRecvInfo {
|
||||
|
||||
private QuicheRecvInfo() { }
|
||||
|
||||
/**
|
||||
* Set the {@link InetSocketAddress} into the {@code quiche_recv_info} struct.
|
||||
*
|
||||
* <pre>
|
||||
* typedef struct {
|
||||
* struct sockaddr *from;
|
||||
* socklen_t from_len;
|
||||
* struct sockaddr *to;
|
||||
* socklen_t to_len;
|
||||
* } quiche_recv_info;
|
||||
* </pre>
|
||||
*
|
||||
* @param memory the memory of {@code quiche_recv_info}.
|
||||
* @param from the {@link InetSocketAddress} to write into {@code quiche_recv_info}.
|
||||
* @param to the {@link InetSocketAddress} to write into {@code quiche_recv_info}.
|
||||
*/
|
||||
static void setRecvInfo(ByteBuffer memory, InetSocketAddress from, InetSocketAddress to) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
setAddress(memory, Quiche.SIZEOF_QUICHE_RECV_INFO, Quiche.QUICHE_RECV_INFO_OFFSETOF_FROM, Quiche.QUICHE_RECV_INFO_OFFSETOF_FROM_LEN, from);
|
||||
setAddress(memory, Quiche.SIZEOF_QUICHE_RECV_INFO + Quiche.SIZEOF_SOCKADDR_STORAGE,
|
||||
Quiche.QUICHE_RECV_INFO_OFFSETOF_TO, Quiche.QUICHE_RECV_INFO_OFFSETOF_TO_LEN, to);
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAddress(ByteBuffer memory, int socketAddressOffset, int addrOffset, int lenOffset, InetSocketAddress address) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
int sockaddrPosition = position +socketAddressOffset;
|
||||
memory.position(sockaddrPosition);
|
||||
long sockaddrMemoryAddress = Quiche.memoryAddressWithPosition(memory);
|
||||
int len = SockaddrIn.setAddress(memory, address);
|
||||
if (Quiche.SIZEOF_SIZE_T == 4) {
|
||||
memory.putInt(position + addrOffset, (int) sockaddrMemoryAddress);
|
||||
} else {
|
||||
memory.putLong(position + addrOffset, sockaddrMemoryAddress);
|
||||
}
|
||||
Quiche.setPrimitiveValue(memory, position + lenOffset, Quiche.SIZEOF_SOCKLEN_T, len);
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored.
|
||||
*
|
||||
* @param memory the first {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||
* @param memory2 the second {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||
* @return {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored, {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
static boolean isSameAddress(ByteBuffer memory, ByteBuffer memory2) {
|
||||
return Quiche.isSameAddress(memory, memory2, Quiche.SIZEOF_QUICHE_RECV_INFO);
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.concurrent.FastThreadLocal;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Utility class to handle access to {@code quiche_send_info}.
|
||||
*/
|
||||
final class QuicheSendInfo {
|
||||
|
||||
private static final FastThreadLocal<byte[]> IPV4_ARRAYS = new FastThreadLocal<byte[]>() {
|
||||
@Override
|
||||
protected byte[] initialValue() {
|
||||
return new byte[SockaddrIn.IPV4_ADDRESS_LENGTH];
|
||||
}
|
||||
};
|
||||
|
||||
private static final FastThreadLocal<byte[]> IPV6_ARRAYS = new FastThreadLocal<byte[]>() {
|
||||
@Override
|
||||
protected byte[] initialValue() {
|
||||
return new byte[SockaddrIn.IPV6_ADDRESS_LENGTH];
|
||||
}
|
||||
};
|
||||
|
||||
private static final byte[] TIMESPEC_ZEROOUT = new byte[Quiche.SIZEOF_TIMESPEC];
|
||||
|
||||
private QuicheSendInfo() { }
|
||||
|
||||
/**
|
||||
* Get the {@link InetSocketAddress} out of the {@code quiche_send_info} struct.
|
||||
*
|
||||
* @param memory the memory of {@code quiche_send_info}.
|
||||
* @return the address that was read.
|
||||
*/
|
||||
static InetSocketAddress getToAddress(ByteBuffer memory) {
|
||||
return getAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO_LEN, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO);
|
||||
}
|
||||
|
||||
static InetSocketAddress getFromAddress(ByteBuffer memory) {
|
||||
return getAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM_LEN, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM);
|
||||
}
|
||||
|
||||
private static InetSocketAddress getAddress(ByteBuffer memory, int lenOffset, int addressOffset) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
long len = getLen(memory, position + lenOffset);
|
||||
|
||||
memory.position(position + addressOffset);
|
||||
|
||||
if (len == Quiche.SIZEOF_SOCKADDR_IN) {
|
||||
return SockaddrIn.getIPv4(memory, IPV4_ARRAYS.get());
|
||||
}
|
||||
assert len == Quiche.SIZEOF_SOCKADDR_IN6;
|
||||
return SockaddrIn.getIPv6(memory, IPV6_ARRAYS.get(), IPV4_ARRAYS.get());
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
private static long getLen(ByteBuffer memory, int index) {
|
||||
return Quiche.getPrimitiveValue(memory, index, Quiche.SIZEOF_SOCKLEN_T);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link InetSocketAddress} into the {@code quiche_send_info} struct.
|
||||
* <pre>
|
||||
*
|
||||
* typedef struct {
|
||||
* // The local address the packet should be sent from.
|
||||
* struct sockaddr_storage from;
|
||||
* socklen_t from_len;
|
||||
*
|
||||
* // The address the packet should be sent to.
|
||||
* struct sockaddr_storage to;
|
||||
* socklen_t to_len;
|
||||
*
|
||||
* // The time to send the packet out.
|
||||
* struct timespec at;
|
||||
* } quiche_send_info;
|
||||
* </pre>
|
||||
*
|
||||
* @param memory the memory of {@code quiche_send_info}.
|
||||
* @param from the {@link InetSocketAddress} to write into {@code quiche_send_info}.
|
||||
* @param to the {@link InetSocketAddress} to write into {@code quiche_send_info}.
|
||||
*/
|
||||
static void setSendInfo(ByteBuffer memory, InetSocketAddress from, InetSocketAddress to) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
setAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM_LEN, from);
|
||||
setAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO_LEN, to);
|
||||
// Zero out the timespec.
|
||||
memory.position(position + Quiche.QUICHE_SEND_INFO_OFFSETOF_AT);
|
||||
memory.put(TIMESPEC_ZEROOUT);
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAddress(ByteBuffer memory, int addrOffset, int lenOffset, InetSocketAddress addr) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
memory.position(position + addrOffset);
|
||||
int len = SockaddrIn.setAddress(memory, addr);
|
||||
Quiche.setPrimitiveValue(memory, position + lenOffset, Quiche.SIZEOF_SOCKLEN_T, len);
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@code timespec} from the {@code quiche_send_info} struct in nanos.
|
||||
* <pre>
|
||||
*
|
||||
* typedef struct {
|
||||
* // The local address the packet should be sent from.
|
||||
* struct sockaddr_storage from;
|
||||
* socklen_t from_len;
|
||||
*
|
||||
* // The address the packet should be sent to.
|
||||
* struct sockaddr_storage to;
|
||||
* socklen_t to_len;
|
||||
*
|
||||
* // The time to send the packet out.
|
||||
* struct timespec at;
|
||||
* } quiche_send_info;
|
||||
* </pre>
|
||||
*
|
||||
* @param memory the memory of {@code quiche_send_info}.
|
||||
*/
|
||||
static long getAtNanos(ByteBuffer memory) {
|
||||
long sec = Quiche.getPrimitiveValue(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_AT +
|
||||
Quiche.TIMESPEC_OFFSETOF_TV_SEC, Quiche.SIZEOF_TIME_T);
|
||||
long nsec = Quiche.getPrimitiveValue(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_AT +
|
||||
Quiche.TIMESPEC_OFFSETOF_TV_SEC, Quiche.SIZEOF_LONG);
|
||||
return TimeUnit.SECONDS.toNanos(sec) + nsec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if both {@link ByteBuffer}s have the same {@code sockaddr_storage} stored.
|
||||
*
|
||||
* @param memory the first {@link ByteBuffer} which holds a {@code quiche_send_info}.
|
||||
* @param memory2 the second {@link ByteBuffer} which holds a {@code quiche_send_info}.
|
||||
* @return {@code true} if both {@link ByteBuffer}s have the same {@code sockaddr_storage} stored,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
static boolean isSameAddress(ByteBuffer memory, ByteBuffer memory2) {
|
||||
return Quiche.isSameAddress(memory, memory2, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
final class SecureRandomQuicConnectionIdGenerator implements QuicConnectionIdGenerator {
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
static final QuicConnectionIdGenerator INSTANCE = new SecureRandomQuicConnectionIdGenerator();
|
||||
|
||||
private SecureRandomQuicConnectionIdGenerator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer newId(int length) {
|
||||
ObjectUtil.checkInRange(length, 0, maxConnectionIdLength(), "length");
|
||||
byte[] bytes = new byte[length];
|
||||
RANDOM.nextBytes(bytes);
|
||||
return ByteBuffer.wrap(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer newId(ByteBuffer buffer, int length) {
|
||||
return newId(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxConnectionIdLength() {
|
||||
return Quiche.QUICHE_MAX_CONN_ID_LEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdempotent() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2021 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Used to allocate datagram packets that use UDP_SEGMENT (GSO).
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SegmentedDatagramPacketAllocator {
|
||||
|
||||
/**
|
||||
* {@link SegmentedDatagramPacketAllocator} which should be used if no UDP_SEGMENT is supported and used.
|
||||
*/
|
||||
SegmentedDatagramPacketAllocator NONE = new SegmentedDatagramPacketAllocator() {
|
||||
@Override
|
||||
public int maxNumSegments() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatagramPacket newPacket(ByteBuf buffer, int segmentSize, InetSocketAddress remoteAddress) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The maximum number of segments to use per packet. By default this is {@code 10} but this may be overridden by
|
||||
* the implementation of the interface.
|
||||
*
|
||||
* @return the segments.
|
||||
*/
|
||||
default int maxNumSegments() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new segmented {@link DatagramPacket}.
|
||||
*
|
||||
* @param buffer the {@link ByteBuf} that is used as content.
|
||||
* @param segmentSize the size of each segment.
|
||||
* @param remoteAddress the remote address to send to.
|
||||
* @return the packet.
|
||||
*/
|
||||
DatagramPacket newPacket(ByteBuf buffer, int segmentSize, InetSocketAddress remoteAddress);
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2020 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.quic;
|
||||
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
final class SockaddrIn {
|
||||
static final byte[] IPV4_MAPPED_IPV6_PREFIX = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff };
|
||||
static final int IPV4_ADDRESS_LENGTH = 4;
|
||||
static final int IPV6_ADDRESS_LENGTH = 16;
|
||||
static final byte[] SOCKADDR_IN6_EMPTY_ARRAY = new byte[Quiche.SIZEOF_SOCKADDR_IN6];
|
||||
static final byte[] SOCKADDR_IN_EMPTY_ARRAY = new byte[Quiche.SIZEOF_SOCKADDR_IN];
|
||||
|
||||
private SockaddrIn() { }
|
||||
|
||||
static int cmp(long memory, long memory2) {
|
||||
return Quiche.sockaddr_cmp(memory, memory2);
|
||||
}
|
||||
|
||||
static int setAddress(ByteBuffer memory, InetSocketAddress address) {
|
||||
InetAddress addr = address.getAddress();
|
||||
return setAddress(addr instanceof Inet6Address, memory, address);
|
||||
}
|
||||
|
||||
static int setAddress(boolean ipv6, ByteBuffer memory, InetSocketAddress address) {
|
||||
if (ipv6) {
|
||||
return SockaddrIn.setIPv6(memory, address.getAddress(), address.getPort());
|
||||
} else {
|
||||
return SockaddrIn.setIPv4(memory, address.getAddress(), address.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* struct sockaddr_in {
|
||||
* sa_family_t sin_family; // address family: AF_INET
|
||||
* in_port_t sin_port; // port in network byte order
|
||||
* struct in_addr sin_addr; // internet address
|
||||
* };
|
||||
*
|
||||
* // Internet address.
|
||||
* struct in_addr {
|
||||
* uint32_t s_addr; // address in network byte order
|
||||
* };
|
||||
*
|
||||
*/
|
||||
static int setIPv4(ByteBuffer memory, InetAddress address, int port) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
// memset
|
||||
memory.put(SOCKADDR_IN_EMPTY_ARRAY);
|
||||
|
||||
memory.putShort(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_FAMILY, Quiche.AF_INET);
|
||||
memory.putShort(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_PORT, (short) port);
|
||||
|
||||
byte[] bytes = address.getAddress();
|
||||
int offset = 0;
|
||||
if (bytes.length == IPV6_ADDRESS_LENGTH) {
|
||||
// IPV6 mapped IPV4 address, we only need the last 4 bytes.
|
||||
offset = IPV4_MAPPED_IPV6_PREFIX.length;
|
||||
}
|
||||
assert bytes.length == offset + IPV4_ADDRESS_LENGTH;
|
||||
memory.position(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_ADDR + Quiche.IN_ADDRESS_OFFSETOF_S_ADDR);
|
||||
memory.put(bytes, offset, IPV4_ADDRESS_LENGTH);
|
||||
return Quiche.SIZEOF_SOCKADDR_IN;
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* struct sockaddr_in6 {
|
||||
* sa_family_t sin6_family; // AF_INET6
|
||||
* in_port_t sin6_port; // port number
|
||||
* uint32_t sin6_flowinfo; // IPv6 flow information
|
||||
* struct in6_addr sin6_addr; // IPv6 address
|
||||
* uint32_t sin6_scope_id; /* Scope ID (new in 2.4)
|
||||
* };
|
||||
*
|
||||
* struct in6_addr {
|
||||
* unsigned char s6_addr[16]; // IPv6 address
|
||||
* };
|
||||
*/
|
||||
static int setIPv6(ByteBuffer memory, InetAddress address, int port) {
|
||||
int position = memory.position();
|
||||
try {
|
||||
// memset
|
||||
memory.put(SOCKADDR_IN6_EMPTY_ARRAY);
|
||||
|
||||
memory.putShort(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_FAMILY, Quiche.AF_INET6);
|
||||
memory.putShort(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_PORT, (short) port);
|
||||
|
||||
// Skip sin6_flowinfo as we did memset before
|
||||
byte[] bytes = address.getAddress();
|
||||
int offset = Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_ADDR + Quiche.IN6_ADDRESS_OFFSETOF_S6_ADDR;
|
||||
|
||||
if (bytes.length == IPV4_ADDRESS_LENGTH) {
|
||||
memory.position(position + offset);
|
||||
memory.put(IPV4_MAPPED_IPV6_PREFIX);
|
||||
|
||||
memory.position(position + offset + IPV4_MAPPED_IPV6_PREFIX.length);
|
||||
memory.put(bytes, 0, IPV4_ADDRESS_LENGTH);
|
||||
|
||||
// Skip sin6_scope_id as we did memset before
|
||||
} else {
|
||||
memory.position(position + offset);
|
||||
memory.put(bytes, 0, IPV6_ADDRESS_LENGTH);
|
||||
|
||||
memory.putInt(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_SCOPE_ID,
|
||||
((Inet6Address) address).getScopeId());
|
||||
}
|
||||
return Quiche.SIZEOF_SOCKADDR_IN6;
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
static InetSocketAddress getIPv4(ByteBuffer memory, byte[] tmpArray) {
|
||||
assert tmpArray.length == IPV4_ADDRESS_LENGTH;
|
||||
int position = memory.position();
|
||||
|
||||
try {
|
||||
int port = memory.getShort(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_PORT) & 0xFFFF;
|
||||
memory.position(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_ADDR + Quiche.IN_ADDRESS_OFFSETOF_S_ADDR);
|
||||
memory.get(tmpArray);
|
||||
try {
|
||||
return new InetSocketAddress(InetAddress.getByAddress(tmpArray), port);
|
||||
} catch (UnknownHostException ignore) {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
|
||||
static InetSocketAddress getIPv6(ByteBuffer memory, byte[] ipv6Array, byte[] ipv4Array) {
|
||||
assert ipv6Array.length == IPV6_ADDRESS_LENGTH;
|
||||
assert ipv4Array.length == IPV4_ADDRESS_LENGTH;
|
||||
int position = memory.position();
|
||||
|
||||
try {
|
||||
int port = memory.getShort(
|
||||
position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_PORT) & 0xFFFF;
|
||||
memory.position(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_ADDR + Quiche.IN6_ADDRESS_OFFSETOF_S6_ADDR);
|
||||
memory.get(ipv6Array);
|
||||
if (PlatformDependent.equals(
|
||||
ipv6Array, 0, IPV4_MAPPED_IPV6_PREFIX, 0, IPV4_MAPPED_IPV6_PREFIX.length)) {
|
||||
System.arraycopy(ipv6Array, IPV4_MAPPED_IPV6_PREFIX.length, ipv4Array, 0, IPV4_ADDRESS_LENGTH);
|
||||
try {
|
||||
return new InetSocketAddress(Inet4Address.getByAddress(ipv4Array), port);
|
||||
} catch (UnknownHostException ignore) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
int scopeId = memory.getInt(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_SCOPE_ID);
|
||||
try {
|
||||
return new InetSocketAddress(Inet6Address.getByAddress(null, ipv6Array, scopeId), port);
|
||||
} catch (UnknownHostException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
memory.position(position);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue