Jörg Prante 7 months ago
commit b85679f001

57
.gitignore vendored

@ -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() { }
}

@ -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…
Cancel
Save