main
commit
b85679f001
@ -0,0 +1,57 @@
|
|||||||
|
# Eclipse project files
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings
|
||||||
|
|
||||||
|
# IntelliJ IDEA project files and directories
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
.shelf/
|
||||||
|
|
||||||
|
# Geany project file
|
||||||
|
.geany
|
||||||
|
|
||||||
|
# KDevelop project file and directory
|
||||||
|
.kdev4/
|
||||||
|
*.kdev4
|
||||||
|
|
||||||
|
# Build targets
|
||||||
|
/target
|
||||||
|
*/target
|
||||||
|
|
||||||
|
# Report directories
|
||||||
|
/reports
|
||||||
|
*/reports
|
||||||
|
|
||||||
|
# Mac-specific directory that no other operating system needs.
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# JVM crash logs
|
||||||
|
hs_err_pid*.log
|
||||||
|
replay_pid*.log
|
||||||
|
|
||||||
|
# JVM dumps
|
||||||
|
*.hprof.xz
|
||||||
|
*.threads
|
||||||
|
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
*/.unison.*
|
||||||
|
|
||||||
|
# exclude mainframer files
|
||||||
|
mainframer
|
||||||
|
.mainframer
|
||||||
|
|
||||||
|
# exclude docker-sync stuff
|
||||||
|
.docker-sync
|
||||||
|
*/.docker-sync
|
||||||
|
|
||||||
|
# exclude vscode files
|
||||||
|
.vscode/
|
||||||
|
*.factorypath
|
||||||
|
|
||||||
|
# exclude file created by the flatten plugin
|
||||||
|
.flattened-pom.xml
|
||||||
|
.java-version
|
Binary file not shown.
@ -0,0 +1,18 @@
|
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
distributionUrl=https://maven-central.storage-download.googleapis.com/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
|
||||||
|
distributionSha256Sum=7822eb593d29558d8edf87845a2c47e36e2a89d17a84cd2390824633214ed423
|
@ -0,0 +1,10 @@
|
|||||||
|
brew 'autoconf'
|
||||||
|
brew 'automake'
|
||||||
|
brew 'libtool'
|
||||||
|
brew 'openssl'
|
||||||
|
brew 'perl'
|
||||||
|
brew 'ninja'
|
||||||
|
brew 'golang'
|
||||||
|
brew 'cmake'
|
||||||
|
brew 'apr'
|
||||||
|
brew 'rust'
|
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
The Netty Project
|
||||||
|
=================
|
||||||
|
|
||||||
|
Please visit the Netty web site for more information:
|
||||||
|
|
||||||
|
* https://netty.io/
|
||||||
|
|
||||||
|
Copyright 2020 The Netty Project
|
||||||
|
|
||||||
|
The Netty Project licenses this file to you under the Apache License,
|
||||||
|
version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at:
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
Also, please refer to each LICENSE.<component>.txt file, which is located in
|
||||||
|
the 'license' directory of the distribution file, for the license terms of the
|
||||||
|
components that this product depends on.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
This product contains the Maven wrapper scripts from 'Maven Wrapper', that provides an easy way to ensure a user has everything necessary to run the Maven build.
|
||||||
|
|
||||||
|
* LICENSE:
|
||||||
|
* license/LICENSE.mvn-wrapper.txt (Apache License 2.0)
|
||||||
|
* HOMEPAGE:
|
||||||
|
* https://github.com/takari/maven-wrapper
|
||||||
|
|
||||||
|
This product is statically linked against Quiche.
|
||||||
|
|
||||||
|
* LICENSE:
|
||||||
|
* license/LICENSE.quiche.txt (BSD2)
|
||||||
|
* HOMEPAGE:
|
||||||
|
* https://github.com/cloudflare/quiche
|
||||||
|
|
||||||
|
|
||||||
|
This product is statically linked against boringssl.
|
||||||
|
|
||||||
|
* LICENSE (Combination ISC and OpenSSL license)
|
||||||
|
* license/LICENSE.boringssl.txt (Combination ISC and OpenSSL license)
|
||||||
|
* HOMEPAGE:
|
||||||
|
* https://boringssl.googlesource.com/boringssl/
|
@ -0,0 +1,52 @@
|
|||||||
|
![Build project](https://github.com/netty/netty-incubator-codec-quic/workflows/Build%20project/badge.svg)
|
||||||
|
|
||||||
|
# Netty QUIC codec
|
||||||
|
|
||||||
|
This is a new experimental QUIC codec for netty which makes use of [quiche](https://github.com/cloudflare/quiche).
|
||||||
|
|
||||||
|
## How to include the dependency
|
||||||
|
|
||||||
|
To include the dependency you need to ensure you also specify the right classifier. At the moment we only support Linux
|
||||||
|
x86_64 / aarch_64, macOS / OSX x86_64 / aarch_64 and Windows x86_64 but this may change.
|
||||||
|
|
||||||
|
As an example this is how you would include the dependency in maven:
|
||||||
|
For Linux x86_64:
|
||||||
|
```
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty.incubator</groupId>
|
||||||
|
<artifactId>netty-incubator-codec-native-quic</artifactId>
|
||||||
|
<version>0.0.21.Final</version>
|
||||||
|
<classifier>linux-x86_64</classifier>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
For macOS / OSX:
|
||||||
|
|
||||||
|
```
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty.incubator</groupId>
|
||||||
|
<artifactId>netty-incubator-codec-native-quic</artifactId>
|
||||||
|
<version>0.0.21.Final</version>
|
||||||
|
<classifier>osx-x86_64</classifier>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
For Windows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty.incubator</groupId>
|
||||||
|
<artifactId>netty-incubator-codec-native-quic</artifactId>
|
||||||
|
<version>0.0.21.Final</version>
|
||||||
|
<classifier>windows-x86_64</classifier>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use this codec ?
|
||||||
|
|
||||||
|
For some examples please check our
|
||||||
|
[example package](https://github.com/netty/netty-incubator-codec-quic/tree/main/codec-native-quic/src/test/java/io/netty/incubator/codec/quic).
|
||||||
|
This contains a server and a client that can speak some limited HTTP/0.9 with each other.
|
||||||
|
|
||||||
|
For more "advanced" use cases, consider checking our
|
||||||
|
[netty-incubator-codec-http3](https://github.com/netty/netty-incubator-codec-http3) project.
|
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2021 The Netty Project
|
||||||
|
~
|
||||||
|
~ The Netty Project licenses this file to you under the Apache License,
|
||||||
|
~ version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
~ with the License. You may obtain a copy of the License at:
|
||||||
|
~
|
||||||
|
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
~ License for the specific language governing permissions and limitations
|
||||||
|
~ under the License.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.xbib.netty</groupId>
|
||||||
|
<artifactId>netty-handler-codec-parent-quic</artifactId>
|
||||||
|
<version>0.0.56.Final-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>netty-handler-codec-classes-quic</artifactId>
|
||||||
|
<version>0.0.56.Final-SNAPSHOT</version>
|
||||||
|
<name>Netty/Handler/Codec/Classes/Quic</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<javaModuleName>io.netty.handler.codec.classes.quic</javaModuleName>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>default-jar</id>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||||
|
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
|
||||||
|
</manifest>
|
||||||
|
<manifestEntries>
|
||||||
|
<Automatic-Module-Name>${javaModuleName}</Automatic-Module-Name>
|
||||||
|
</manifestEntries>
|
||||||
|
<index>true</index>
|
||||||
|
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-buffer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-codec</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-handler</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-transport</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-transport-classes-epoll</artifactId>
|
||||||
|
<!-- Let's mark as optional as it is not strictly needed -->
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.handler.ssl.util.LazyX509Certificate;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
final class BoringSSL {
|
||||||
|
private BoringSSL() { }
|
||||||
|
|
||||||
|
static final int SSL_VERIFY_NONE = BoringSSLNativeStaticallyReferencedJniMethods.ssl_verify_none();
|
||||||
|
static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = BoringSSLNativeStaticallyReferencedJniMethods
|
||||||
|
.ssl_verify_fail_if_no_peer_cert();
|
||||||
|
static final int SSL_VERIFY_PEER = BoringSSLNativeStaticallyReferencedJniMethods.ssl_verify_peer();
|
||||||
|
static final int X509_V_OK = BoringSSLNativeStaticallyReferencedJniMethods.x509_v_ok();
|
||||||
|
static final int X509_V_ERR_CERT_HAS_EXPIRED =
|
||||||
|
BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_cert_has_expired();
|
||||||
|
static final int X509_V_ERR_CERT_NOT_YET_VALID =
|
||||||
|
BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_cert_not_yet_valid();
|
||||||
|
static final int X509_V_ERR_CERT_REVOKED = BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_cert_revoked();
|
||||||
|
static final int X509_V_ERR_UNSPECIFIED = BoringSSLNativeStaticallyReferencedJniMethods.x509_v_err_unspecified();
|
||||||
|
|
||||||
|
static long SSLContext_new(boolean server, String[] applicationProtocols,
|
||||||
|
BoringSSLHandshakeCompleteCallback handshakeCompleteCallback,
|
||||||
|
BoringSSLCertificateCallback certificateCallback,
|
||||||
|
BoringSSLCertificateVerifyCallback verifyCallback,
|
||||||
|
BoringSSLTlsextServernameCallback servernameCallback,
|
||||||
|
BoringSSLKeylogCallback keylogCallback,
|
||||||
|
BoringSSLSessionCallback sessionCallback,
|
||||||
|
BoringSSLPrivateKeyMethod privateKeyMethod,
|
||||||
|
BoringSSLSessionTicketCallback sessionTicketCallback,
|
||||||
|
int verifyMode,
|
||||||
|
byte[][] subjectNames) {
|
||||||
|
return SSLContext_new0(server, toWireFormat(applicationProtocols),
|
||||||
|
handshakeCompleteCallback, certificateCallback, verifyCallback, servernameCallback,
|
||||||
|
keylogCallback, sessionCallback, privateKeyMethod, sessionTicketCallback, verifyMode, subjectNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] toWireFormat(String[] applicationProtocols) {
|
||||||
|
if (applicationProtocols == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
|
for (String p : applicationProtocols) {
|
||||||
|
byte[] bytes = p.getBytes(StandardCharsets.US_ASCII);
|
||||||
|
out.write(bytes.length);
|
||||||
|
out.write(bytes);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native long SSLContext_new0(boolean server,
|
||||||
|
byte[] applicationProtocols, Object handshakeCompleteCallback,
|
||||||
|
Object certificateCallback, Object verifyCallback,
|
||||||
|
Object servernameCallback, Object keylogCallback,
|
||||||
|
Object sessionCallback,
|
||||||
|
Object privateKeyMethod,
|
||||||
|
Object sessionTicketCallback,
|
||||||
|
int verifyDepth, byte[][] subjectNames);
|
||||||
|
static native void SSLContext_set_early_data_enabled(long context, boolean enabled);
|
||||||
|
static native long SSLContext_setSessionCacheSize(long context, long size);
|
||||||
|
static native long SSLContext_setSessionCacheTimeout(long context, long size);
|
||||||
|
|
||||||
|
static native void SSLContext_setSessionTicketKeys(long context, boolean enableCallback);
|
||||||
|
|
||||||
|
static native void SSLContext_free(long context);
|
||||||
|
static long SSL_new(long context, boolean server, String hostname) {
|
||||||
|
return SSL_new0(context, server, tlsExtHostName(hostname));
|
||||||
|
}
|
||||||
|
static native long SSL_new0(long context, boolean server, String hostname);
|
||||||
|
static native void SSL_free(long ssl);
|
||||||
|
|
||||||
|
static native Runnable SSL_getTask(long ssl);
|
||||||
|
|
||||||
|
static native void SSL_cleanup(long ssl);
|
||||||
|
|
||||||
|
static native long EVP_PKEY_parse(byte[] bytes, String pass);
|
||||||
|
static native void EVP_PKEY_free(long key);
|
||||||
|
|
||||||
|
static native long CRYPTO_BUFFER_stack_new(long ssl, byte[][] bytes);
|
||||||
|
static native void CRYPTO_BUFFER_stack_free(long chain);
|
||||||
|
|
||||||
|
static native String ERR_last_error();
|
||||||
|
|
||||||
|
private static String tlsExtHostName(String hostname) {
|
||||||
|
if (hostname != null && hostname.endsWith(".")) {
|
||||||
|
// Strip trailing dot if included.
|
||||||
|
// See https://github.com/netty/netty-tcnative/issues/400
|
||||||
|
hostname = hostname.substring(0, hostname.length() - 1);
|
||||||
|
}
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate[] certificates(byte[][] chain) {
|
||||||
|
X509Certificate[] peerCerts = new X509Certificate[chain.length];
|
||||||
|
for (int i = 0; i < peerCerts.length; i++) {
|
||||||
|
peerCerts[i] = new LazyX509Certificate(chain[i]);
|
||||||
|
}
|
||||||
|
return peerCerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[][] subjectNames(X509Certificate[] certificates) {
|
||||||
|
byte[][] subjectNames = new byte[certificates.length][];
|
||||||
|
for (int i = 0; i < certificates.length; i++) {
|
||||||
|
subjectNames[i] = certificates[i].getSubjectX500Principal().getEncoded();
|
||||||
|
}
|
||||||
|
return subjectNames;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
public interface BoringSSLAsyncPrivateKeyMethod {
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA1 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA1;
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA256 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA256;
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA384 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA384;
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA512 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_SHA512;
|
||||||
|
int SSL_SIGN_ECDSA_SHA1 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SHA1;
|
||||||
|
int SSL_SIGN_ECDSA_SECP256R1_SHA256 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP256R1_SHA256;
|
||||||
|
int SSL_SIGN_ECDSA_SECP384R1_SHA384 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP384R1_SHA384;
|
||||||
|
int SSL_SIGN_ECDSA_SECP521R1_SHA512 = BoringSSLPrivateKeyMethod.SSL_SIGN_ECDSA_SECP521R1_SHA512;
|
||||||
|
int SSL_SIGN_RSA_PSS_RSAE_SHA256 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA256;
|
||||||
|
int SSL_SIGN_RSA_PSS_RSAE_SHA384 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA384;
|
||||||
|
int SSL_SIGN_RSA_PSS_RSAE_SHA512 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PSS_RSAE_SHA512;
|
||||||
|
int SSL_SIGN_ED25519 = BoringSSLPrivateKeyMethod.SSL_SIGN_ED25519;
|
||||||
|
int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = BoringSSLPrivateKeyMethod.SSL_SIGN_RSA_PKCS1_MD5_SHA1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the input with the given key and notifies the returned {@link Future} with the signed bytes.
|
||||||
|
*
|
||||||
|
* @param engine the {@link SSLEngine}
|
||||||
|
* @param signatureAlgorithm the algorithm to use for signing
|
||||||
|
* @param input the digest itself
|
||||||
|
* @return the {@link Future} that will be notified with the signed data
|
||||||
|
* (must not be {@code null}) when the operation completes.
|
||||||
|
*/
|
||||||
|
Future<byte[]> sign(SSLEngine engine, int signatureAlgorithm, byte[] input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the input with the given key and notifies the returned {@link Future} with the decrypted bytes.
|
||||||
|
*
|
||||||
|
* @param engine the {@link SSLEngine}
|
||||||
|
* @param input the input which should be decrypted
|
||||||
|
* @return the {@link Future} that will be notified with the decrypted data
|
||||||
|
* (must not be {@code null}) when the operation completes.
|
||||||
|
*/
|
||||||
|
Future<byte[]> decrypt(SSLEngine engine, byte[] input);
|
||||||
|
}
|
@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
final class BoringSSLCertificateCallback {
|
||||||
|
private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||||
|
private static final byte[] END_PRIVATE_KEY = "\n-----END PRIVATE KEY-----\n".getBytes(CharsetUtil.US_ASCII);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The types contained in the {@code keyTypeBytes} array.
|
||||||
|
*/
|
||||||
|
// Extracted from https://github.com/openssl/openssl/blob/master/include/openssl/tls1.h
|
||||||
|
private static final byte TLS_CT_RSA_SIGN = 1;
|
||||||
|
private static final byte TLS_CT_DSS_SIGN = 2;
|
||||||
|
private static final byte TLS_CT_RSA_FIXED_DH = 3;
|
||||||
|
private static final byte TLS_CT_DSS_FIXED_DH = 4;
|
||||||
|
private static final byte TLS_CT_ECDSA_SIGN = 64;
|
||||||
|
private static final byte TLS_CT_RSA_FIXED_ECDH = 65;
|
||||||
|
private static final byte TLS_CT_ECDSA_FIXED_ECDH = 66;
|
||||||
|
|
||||||
|
// Code in this class is inspired by code of conscrypts:
|
||||||
|
// - https://android.googlesource.com/platform/external/
|
||||||
|
// conscrypt/+/master/src/main/java/org/conscrypt/OpenSSLEngineImpl.java
|
||||||
|
// - https://android.googlesource.com/platform/external/
|
||||||
|
// conscrypt/+/master/src/main/java/org/conscrypt/SSLParametersImpl.java
|
||||||
|
//
|
||||||
|
static final String KEY_TYPE_RSA = "RSA";
|
||||||
|
static final String KEY_TYPE_DH_RSA = "DH_RSA";
|
||||||
|
static final String KEY_TYPE_EC = "EC";
|
||||||
|
static final String KEY_TYPE_EC_EC = "EC_EC";
|
||||||
|
static final String KEY_TYPE_EC_RSA = "EC_RSA";
|
||||||
|
|
||||||
|
// key type mappings for types.
|
||||||
|
private static final Map<String, String> KEY_TYPES = new HashMap<String, String>();
|
||||||
|
static {
|
||||||
|
KEY_TYPES.put("RSA", KEY_TYPE_RSA);
|
||||||
|
KEY_TYPES.put("DHE_RSA", KEY_TYPE_RSA);
|
||||||
|
KEY_TYPES.put("ECDHE_RSA", KEY_TYPE_RSA);
|
||||||
|
KEY_TYPES.put("ECDHE_ECDSA", KEY_TYPE_EC);
|
||||||
|
KEY_TYPES.put("ECDH_RSA", KEY_TYPE_EC_RSA);
|
||||||
|
KEY_TYPES.put("ECDH_ECDSA", KEY_TYPE_EC_EC);
|
||||||
|
KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Set<String> SUPPORTED_KEY_TYPES = Collections.unmodifiableSet(new LinkedHashSet<>(
|
||||||
|
Arrays.asList(KEY_TYPE_RSA,
|
||||||
|
KEY_TYPE_DH_RSA,
|
||||||
|
KEY_TYPE_EC,
|
||||||
|
KEY_TYPE_EC_RSA,
|
||||||
|
KEY_TYPE_EC_EC)));
|
||||||
|
|
||||||
|
// Directly returning this is safe as we never modify it within our JNI code.
|
||||||
|
private static final long[] NO_KEY_MATERIAL_CLIENT_SIDE = new long[] { 0, 0 };
|
||||||
|
|
||||||
|
private final QuicheQuicSslEngineMap engineMap;
|
||||||
|
private final X509ExtendedKeyManager keyManager;
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
BoringSSLCertificateCallback(QuicheQuicSslEngineMap engineMap, X509ExtendedKeyManager keyManager, String password) {
|
||||||
|
this.engineMap = engineMap;
|
||||||
|
this.keyManager = keyManager;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
long[] handle(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals, String[] authMethods) {
|
||||||
|
QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (keyManager == null) {
|
||||||
|
if (engine.getUseClientMode()) {
|
||||||
|
return NO_KEY_MATERIAL_CLIENT_SIDE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (engine.getUseClientMode()) {
|
||||||
|
final Set<String> keyTypesSet = supportedClientKeyTypes(keyTypeBytes);
|
||||||
|
final String[] keyTypes = keyTypesSet.toArray(new String[0]);
|
||||||
|
final X500Principal[] issuers;
|
||||||
|
if (asn1DerEncodedPrincipals == null) {
|
||||||
|
issuers = null;
|
||||||
|
} else {
|
||||||
|
issuers = new X500Principal[asn1DerEncodedPrincipals.length];
|
||||||
|
for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
|
||||||
|
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removeMappingIfNeeded(ssl, selectKeyMaterialClientSide(ssl, engine, keyTypes, issuers));
|
||||||
|
} else {
|
||||||
|
// For now we just ignore the asn1DerEncodedPrincipals as this is kind of inline with what the
|
||||||
|
// OpenJDK SSLEngineImpl does.
|
||||||
|
return removeMappingIfNeeded(ssl, selectKeyMaterialServerSide(ssl, engine, authMethods));
|
||||||
|
}
|
||||||
|
} catch (SSLException e) {
|
||||||
|
engineMap.remove(ssl);
|
||||||
|
return null;
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
engineMap.remove(ssl);
|
||||||
|
throw cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] removeMappingIfNeeded(long ssl, long[] result) {
|
||||||
|
if (result == null) {
|
||||||
|
engineMap.remove(ssl);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] selectKeyMaterialServerSide(long ssl, QuicheQuicSslEngine engine, String[] authMethods)
|
||||||
|
throws SSLException {
|
||||||
|
if (authMethods.length == 0) {
|
||||||
|
throw new SSLHandshakeException("Unable to find key material");
|
||||||
|
}
|
||||||
|
|
||||||
|
// authMethods may contain duplicates or may result in the same type
|
||||||
|
// but call chooseServerAlias(...) may be expensive. So let's ensure
|
||||||
|
// we filter out duplicates.
|
||||||
|
Set<String> typeSet = new HashSet<String>(KEY_TYPES.size());
|
||||||
|
for (String authMethod : authMethods) {
|
||||||
|
String type = KEY_TYPES.get(authMethod);
|
||||||
|
if (type != null && typeSet.add(type)) {
|
||||||
|
String alias = chooseServerAlias(engine, type);
|
||||||
|
if (alias != null) {
|
||||||
|
return selectMaterial(ssl, engine, alias) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new SSLHandshakeException("Unable to find key material for auth method(s): "
|
||||||
|
+ Arrays.toString(authMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] selectKeyMaterialClientSide(long ssl, QuicheQuicSslEngine engine, String[] keyTypes,
|
||||||
|
X500Principal[] issuer) {
|
||||||
|
String alias = chooseClientAlias(engine, keyTypes, issuer);
|
||||||
|
// Only try to set the keymaterial if we have a match. This is also consistent with what OpenJDK does:
|
||||||
|
// https://hg.openjdk.java.net/jdk/jdk11/file/76072a077ee1/
|
||||||
|
// src/java.base/share/classes/sun/security/ssl/CertificateRequest.java#l362
|
||||||
|
if (alias != null) {
|
||||||
|
return selectMaterial(ssl, engine, alias) ;
|
||||||
|
}
|
||||||
|
return NO_KEY_MATERIAL_CLIENT_SIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] selectMaterial(long ssl, QuicheQuicSslEngine engine, String alias) {
|
||||||
|
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
|
||||||
|
if (certificates == null || certificates.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[][] certs = new byte[certificates.length][];
|
||||||
|
|
||||||
|
for (int i = 0; i < certificates.length; i++) {
|
||||||
|
try {
|
||||||
|
certs[i] = certificates[i].getEncoded();
|
||||||
|
} catch (CertificateEncodingException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final long key;
|
||||||
|
PrivateKey privateKey = keyManager.getPrivateKey(alias);
|
||||||
|
if (privateKey == BoringSSLKeylessPrivateKey.INSTANCE) {
|
||||||
|
key = 0;
|
||||||
|
} else {
|
||||||
|
byte[] pemKey = toPemEncoded(privateKey);
|
||||||
|
if (pemKey == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
key = BoringSSL.EVP_PKEY_parse(pemKey, password);
|
||||||
|
}
|
||||||
|
long chain = BoringSSL.CRYPTO_BUFFER_stack_new(ssl, certs);
|
||||||
|
engine.setLocalCertificateChain(certificates);
|
||||||
|
|
||||||
|
// Return and signal that the key and chain should be released as well.
|
||||||
|
return new long[] { key, chain };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] toPemEncoded(PrivateKey key) {
|
||||||
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
|
out.write(BEGIN_PRIVATE_KEY);
|
||||||
|
out.write(Base64.getEncoder().encode(key.getEncoded()));
|
||||||
|
out.write(END_PRIVATE_KEY);
|
||||||
|
return out.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private String chooseClientAlias(QuicheQuicSslEngine engine,
|
||||||
|
String[] keyTypes, X500Principal[] issuer) {
|
||||||
|
return keyManager.chooseEngineClientAlias(keyTypes, issuer, engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String chooseServerAlias(QuicheQuicSslEngine engine, String type) {
|
||||||
|
return keyManager.chooseEngineServerAlias(type, null, engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the supported key types for client certificates.
|
||||||
|
*
|
||||||
|
* @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
|
||||||
|
* See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
|
||||||
|
* @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
|
||||||
|
* {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
|
||||||
|
*/
|
||||||
|
private static Set<String> supportedClientKeyTypes(byte[] clientCertificateTypes) {
|
||||||
|
if (clientCertificateTypes == null) {
|
||||||
|
// Try all of the supported key types.
|
||||||
|
return SUPPORTED_KEY_TYPES;
|
||||||
|
}
|
||||||
|
Set<String> result = new HashSet<>(clientCertificateTypes.length);
|
||||||
|
for (byte keyTypeCode : clientCertificateTypes) {
|
||||||
|
String keyType = clientKeyType(keyTypeCode);
|
||||||
|
if (keyType == null) {
|
||||||
|
// Unsupported client key type -- ignore
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.add(keyType);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String clientKeyType(byte clientCertificateType) {
|
||||||
|
// See also https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
|
||||||
|
switch (clientCertificateType) {
|
||||||
|
case TLS_CT_RSA_SIGN:
|
||||||
|
return KEY_TYPE_RSA; // RFC rsa_sign
|
||||||
|
case TLS_CT_RSA_FIXED_DH:
|
||||||
|
return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
|
||||||
|
case TLS_CT_ECDSA_SIGN:
|
||||||
|
return KEY_TYPE_EC; // RFC ecdsa_sign
|
||||||
|
case TLS_CT_RSA_FIXED_ECDH:
|
||||||
|
return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
|
||||||
|
case TLS_CT_ECDSA_FIXED_ECDH:
|
||||||
|
return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute {@link BoringSSLCertificateCallback#handle(long, byte[], byte[][], String[])}.
|
||||||
|
*/
|
||||||
|
final class BoringSSLCertificateCallbackTask extends BoringSSLTask {
|
||||||
|
private final byte[] keyTypeBytes;
|
||||||
|
private final byte[][] asn1DerEncodedPrincipals;
|
||||||
|
private final String[] authMethods;
|
||||||
|
private final BoringSSLCertificateCallback callback;
|
||||||
|
|
||||||
|
// Accessed via JNI.
|
||||||
|
private long key;
|
||||||
|
private long chain;
|
||||||
|
|
||||||
|
BoringSSLCertificateCallbackTask(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals, String[] authMethods,
|
||||||
|
BoringSSLCertificateCallback callback) {
|
||||||
|
// It is important that this constructor never throws. Be sure to not change this!
|
||||||
|
super(ssl);
|
||||||
|
// It's ok to not clone the arrays as we create these in JNI and not-reuse.
|
||||||
|
this.keyTypeBytes = keyTypeBytes;
|
||||||
|
this.asn1DerEncodedPrincipals = asn1DerEncodedPrincipals;
|
||||||
|
this.authMethods = authMethods;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://www.openssl.org/docs/man1.0.2/man3/SSL_set_cert_cb.html.
|
||||||
|
@Override
|
||||||
|
protected void runTask(long ssl, TaskCallback taskCallback) {
|
||||||
|
try {
|
||||||
|
long[] result = callback.handle(ssl, keyTypeBytes, asn1DerEncodedPrincipals, authMethods);
|
||||||
|
if (result == null) {
|
||||||
|
taskCallback.onResult(ssl, 0);
|
||||||
|
} else {
|
||||||
|
this.key = result[0];
|
||||||
|
this.chain = result[1];
|
||||||
|
taskCallback.onResult(ssl, 1);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Just catch the exception and return 0 to fail the handshake.
|
||||||
|
// The problem is that rethrowing here is really "useless" as we will process it as part of an openssl
|
||||||
|
// c callback which needs to return 0 for an error to abort the handshake.
|
||||||
|
taskCallback.onResult(ssl, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void destroy() {
|
||||||
|
if (key != 0) {
|
||||||
|
BoringSSL.EVP_PKEY_free(key);
|
||||||
|
key = 0;
|
||||||
|
}
|
||||||
|
if (chain != 0) {
|
||||||
|
BoringSSL.CRYPTO_BUFFER_stack_free(chain);
|
||||||
|
chain = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.handler.ssl.OpenSslCertificateException;
|
||||||
|
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.security.cert.CertPathValidatorException;
|
||||||
|
import java.security.cert.CertificateExpiredException;
|
||||||
|
import java.security.cert.CertificateNotYetValidException;
|
||||||
|
import java.security.cert.CertificateRevokedException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
final class BoringSSLCertificateVerifyCallback {
|
||||||
|
|
||||||
|
private static final boolean TRY_USING_EXTENDED_TRUST_MANAGER;
|
||||||
|
static {
|
||||||
|
boolean tryUsingExtendedTrustManager;
|
||||||
|
try {
|
||||||
|
Class.forName(X509ExtendedTrustManager.class.getName());
|
||||||
|
tryUsingExtendedTrustManager = true;
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
tryUsingExtendedTrustManager = false;
|
||||||
|
}
|
||||||
|
TRY_USING_EXTENDED_TRUST_MANAGER = tryUsingExtendedTrustManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final QuicheQuicSslEngineMap engineMap;
|
||||||
|
private final X509TrustManager manager;
|
||||||
|
|
||||||
|
BoringSSLCertificateVerifyCallback(QuicheQuicSslEngineMap engineMap, X509TrustManager manager) {
|
||||||
|
this.engineMap = engineMap;
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
int verify(long ssl, byte[][] x509, String authAlgorithm) {
|
||||||
|
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine == null) {
|
||||||
|
// May be null if it was destroyed in the meantime.
|
||||||
|
return BoringSSL.X509_V_ERR_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manager == null) {
|
||||||
|
engineMap.remove(ssl);
|
||||||
|
return BoringSSL.X509_V_ERR_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
X509Certificate[] peerCerts = BoringSSL.certificates(x509);
|
||||||
|
try {
|
||||||
|
if (engine.getUseClientMode()) {
|
||||||
|
if (TRY_USING_EXTENDED_TRUST_MANAGER && manager instanceof X509ExtendedTrustManager) {
|
||||||
|
((X509ExtendedTrustManager) manager).checkServerTrusted(peerCerts, authAlgorithm, engine);
|
||||||
|
} else {
|
||||||
|
manager.checkServerTrusted(peerCerts, authAlgorithm);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (TRY_USING_EXTENDED_TRUST_MANAGER && manager instanceof X509ExtendedTrustManager) {
|
||||||
|
((X509ExtendedTrustManager) manager).checkClientTrusted(peerCerts, authAlgorithm, engine);
|
||||||
|
} else {
|
||||||
|
manager.checkClientTrusted(peerCerts, authAlgorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BoringSSL.X509_V_OK;
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
engineMap.remove(ssl);
|
||||||
|
// Try to extract the correct error code that should be used.
|
||||||
|
if (cause instanceof OpenSslCertificateException) {
|
||||||
|
// This will never return a negative error code as its validated when constructing the
|
||||||
|
// OpenSslCertificateException.
|
||||||
|
return ((OpenSslCertificateException) cause).errorCode();
|
||||||
|
}
|
||||||
|
if (cause instanceof CertificateExpiredException) {
|
||||||
|
return BoringSSL.X509_V_ERR_CERT_HAS_EXPIRED;
|
||||||
|
}
|
||||||
|
if (cause instanceof CertificateNotYetValidException) {
|
||||||
|
return BoringSSL.X509_V_ERR_CERT_NOT_YET_VALID;
|
||||||
|
}
|
||||||
|
return translateToError(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int translateToError(Throwable cause) {
|
||||||
|
if (cause instanceof CertificateRevokedException) {
|
||||||
|
return BoringSSL.X509_V_ERR_CERT_REVOKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The X509TrustManagerImpl uses a Validator which wraps a CertPathValidatorException into
|
||||||
|
// an CertificateException. So we need to handle the wrapped CertPathValidatorException to be
|
||||||
|
// able to send the correct alert.
|
||||||
|
Throwable wrapped = cause.getCause();
|
||||||
|
while (wrapped != null) {
|
||||||
|
if (wrapped instanceof CertPathValidatorException) {
|
||||||
|
CertPathValidatorException ex = (CertPathValidatorException) wrapped;
|
||||||
|
CertPathValidatorException.Reason reason = ex.getReason();
|
||||||
|
if (reason == CertPathValidatorException.BasicReason.EXPIRED) {
|
||||||
|
return BoringSSL.X509_V_ERR_CERT_HAS_EXPIRED;
|
||||||
|
}
|
||||||
|
if (reason == CertPathValidatorException.BasicReason.NOT_YET_VALID) {
|
||||||
|
return BoringSSL.X509_V_ERR_CERT_NOT_YET_VALID;
|
||||||
|
}
|
||||||
|
if (reason == CertPathValidatorException.BasicReason.REVOKED) {
|
||||||
|
return BoringSSL.X509_V_ERR_CERT_REVOKED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrapped = wrapped.getCause();
|
||||||
|
}
|
||||||
|
return BoringSSL.X509_V_ERR_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute {@link BoringSSLCertificateVerifyCallback#verify(long, byte[][], String)}.
|
||||||
|
*/
|
||||||
|
final class BoringSSLCertificateVerifyCallbackTask extends BoringSSLTask {
|
||||||
|
private final byte[][] x509;
|
||||||
|
private final String authAlgorithm;
|
||||||
|
private final BoringSSLCertificateVerifyCallback verifier;
|
||||||
|
|
||||||
|
BoringSSLCertificateVerifyCallbackTask(long ssl, byte[][] x509, String authAlgorithm,
|
||||||
|
BoringSSLCertificateVerifyCallback verifier) {
|
||||||
|
super(ssl);
|
||||||
|
this.x509 = x509;
|
||||||
|
this.authAlgorithm = authAlgorithm;
|
||||||
|
this.verifier = verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runTask(long ssl, TaskCallback callback) {
|
||||||
|
int result = verifier.verify(ssl, x509, authAlgorithm);
|
||||||
|
callback.onResult(ssl, result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
final class BoringSSLHandshakeCompleteCallback {
|
||||||
|
|
||||||
|
private final QuicheQuicSslEngineMap map;
|
||||||
|
|
||||||
|
BoringSSLHandshakeCompleteCallback(QuicheQuicSslEngineMap map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
void handshakeComplete(long ssl, byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||||
|
byte[][] peerCertificateChain, long creationTime, long timeout, byte[] applicationProtocol,
|
||||||
|
boolean sessionReused) {
|
||||||
|
QuicheQuicSslEngine engine = map.get(ssl);
|
||||||
|
if (engine != null) {
|
||||||
|
engine.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime,
|
||||||
|
timeout, applicationProtocol, sessionReused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.KeyManagerFactorySpi;
|
||||||
|
import javax.net.ssl.ManagerFactoryParameters;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.KeyStoreSpi;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link KeyManagerFactory} that can be used to support custom key signing via {@link BoringSSLAsyncPrivateKeyMethod}.
|
||||||
|
*/
|
||||||
|
public final class BoringSSLKeylessManagerFactory extends KeyManagerFactory {
|
||||||
|
|
||||||
|
final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
|
||||||
|
|
||||||
|
private BoringSSLKeylessManagerFactory(KeyManagerFactory keyManagerFactory,
|
||||||
|
BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
|
||||||
|
super(new KeylessManagerFactorySpi(keyManagerFactory),
|
||||||
|
keyManagerFactory.getProvider(), keyManagerFactory.getAlgorithm());
|
||||||
|
this.privateKeyMethod = requireNonNull(privateKeyMethod, "privateKeyMethod");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new factory instance.
|
||||||
|
*
|
||||||
|
* @param privateKeyMethod the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
|
||||||
|
* @param chain the {@link File} that contains the {@link X509Certificate} chain.
|
||||||
|
* @return a new factory instance.
|
||||||
|
* @throws CertificateException on error.
|
||||||
|
* @throws IOException on error.
|
||||||
|
* @throws KeyStoreException on error.
|
||||||
|
* @throws NoSuchAlgorithmException on error.
|
||||||
|
* @throws UnrecoverableKeyException on error.
|
||||||
|
*/
|
||||||
|
public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod, File chain)
|
||||||
|
throws CertificateException, IOException,
|
||||||
|
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
return newKeyless(privateKeyMethod, Files.newInputStream(chain.toPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new factory instance.
|
||||||
|
*
|
||||||
|
* @param privateKeyMethod the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
|
||||||
|
* @param chain the {@link InputStream} that contains the {@link X509Certificate} chain.
|
||||||
|
* @return a new factory instance.
|
||||||
|
* @throws CertificateException on error.
|
||||||
|
* @throws IOException on error.
|
||||||
|
* @throws KeyStoreException on error.
|
||||||
|
* @throws NoSuchAlgorithmException on error.
|
||||||
|
* @throws UnrecoverableKeyException on error.
|
||||||
|
*/
|
||||||
|
public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
|
||||||
|
InputStream chain)
|
||||||
|
throws CertificateException, IOException,
|
||||||
|
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
return newKeyless(privateKeyMethod, QuicSslContext.toX509Certificates0(chain));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new factory instance.
|
||||||
|
*
|
||||||
|
* @param privateKeyMethod the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
|
||||||
|
* @param certificateChain the {@link X509Certificate} chain.
|
||||||
|
* @return a new factory instance.
|
||||||
|
* @throws CertificateException on error.
|
||||||
|
* @throws IOException on error.
|
||||||
|
* @throws KeyStoreException on error.
|
||||||
|
* @throws NoSuchAlgorithmException on error.
|
||||||
|
* @throws UnrecoverableKeyException on error.
|
||||||
|
*/
|
||||||
|
public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
|
||||||
|
X509Certificate... certificateChain)
|
||||||
|
throws CertificateException, IOException,
|
||||||
|
KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
checkNotNull(certificateChain, "certificateChain");
|
||||||
|
KeyStore store = new KeylessKeyStore(certificateChain.clone());
|
||||||
|
store.load(null, null);
|
||||||
|
BoringSSLKeylessManagerFactory factory = new BoringSSLKeylessManagerFactory(
|
||||||
|
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()), privateKeyMethod);
|
||||||
|
factory.init(store, null);
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class KeylessManagerFactorySpi extends KeyManagerFactorySpi {
|
||||||
|
|
||||||
|
private final KeyManagerFactory keyManagerFactory;
|
||||||
|
|
||||||
|
KeylessManagerFactorySpi(KeyManagerFactory keyManagerFactory) {
|
||||||
|
this.keyManagerFactory = requireNonNull(keyManagerFactory, "keyManagerFactory");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(KeyStore ks, char[] password)
|
||||||
|
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||||
|
keyManagerFactory.init(ks, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void engineInit(ManagerFactoryParameters spec) {
|
||||||
|
throw new UnsupportedOperationException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected KeyManager[] engineGetKeyManagers() {
|
||||||
|
return keyManagerFactory.getKeyManagers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static final class KeylessKeyStore extends KeyStore {
|
||||||
|
private static final String ALIAS = "key";
|
||||||
|
private KeylessKeyStore(final X509Certificate[] certificateChain) {
|
||||||
|
super(new KeyStoreSpi() {
|
||||||
|
|
||||||
|
private final Date creationDate = new Date();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key engineGetKey(String alias, char[] password) {
|
||||||
|
if (engineContainsAlias(alias)) {
|
||||||
|
return BoringSSLKeylessPrivateKey.INSTANCE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate[] engineGetCertificateChain(String alias) {
|
||||||
|
return engineContainsAlias(alias)? certificateChain.clone() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate engineGetCertificate(String alias) {
|
||||||
|
return engineContainsAlias(alias)? certificateChain[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date engineGetCreationDate(String alias) {
|
||||||
|
return engineContainsAlias(alias)? creationDate : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
|
||||||
|
throws KeyStoreException {
|
||||||
|
throw new KeyStoreException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
|
||||||
|
throw new KeyStoreException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
|
||||||
|
throw new KeyStoreException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||||
|
throw new KeyStoreException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<String> engineAliases() {
|
||||||
|
return Collections.enumeration(Collections.singleton(ALIAS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean engineContainsAlias(String alias) {
|
||||||
|
return ALIAS.equals(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int engineSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean engineIsKeyEntry(String alias) {
|
||||||
|
return engineContainsAlias(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean engineIsCertificateEntry(String alias) {
|
||||||
|
return engineContainsAlias(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String engineGetCertificateAlias(Certificate cert) {
|
||||||
|
if (cert instanceof X509Certificate) {
|
||||||
|
for (X509Certificate x509Certificate : certificateChain) {
|
||||||
|
if (x509Certificate.equals(cert)) {
|
||||||
|
return ALIAS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void engineStore(OutputStream stream, char[] password) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void engineLoad(InputStream stream, char[] password) {
|
||||||
|
if (stream != null && password != null) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, "keyless");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
|
final class BoringSSLKeylessPrivateKey implements PrivateKey {
|
||||||
|
|
||||||
|
static final BoringSSLKeylessPrivateKey INSTANCE = new BoringSSLKeylessPrivateKey();
|
||||||
|
|
||||||
|
private BoringSSLKeylessPrivateKey() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return "keyless";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFormat() {
|
||||||
|
return "keyless";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
return EmptyArrays.EMPTY_BYTES;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to log keys, logging keys are following
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||||
|
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||||
|
*/
|
||||||
|
public interface BoringSSLKeylog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a key should be logged.
|
||||||
|
*
|
||||||
|
* @param engine the engine.
|
||||||
|
* @param key the key.
|
||||||
|
*/
|
||||||
|
void logKey(SSLEngine engine, String key);
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
final class BoringSSLKeylogCallback {
|
||||||
|
|
||||||
|
private final QuicheQuicSslEngineMap engineMap;
|
||||||
|
private final BoringSSLKeylog keylog;
|
||||||
|
|
||||||
|
BoringSSLKeylogCallback(QuicheQuicSslEngineMap engineMap, BoringSSLKeylog keylog) {
|
||||||
|
this.engineMap = engineMap;
|
||||||
|
this.keylog = keylog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
void logKey(long ssl, String key) {
|
||||||
|
SSLEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine != null) {
|
||||||
|
keylog.logKey(engine, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
final class BoringSSLLoggingKeylog implements BoringSSLKeylog {
|
||||||
|
static final BoringSSLLoggingKeylog INSTANCE = new BoringSSLLoggingKeylog();
|
||||||
|
|
||||||
|
private BoringSSLLoggingKeylog() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(BoringSSLLoggingKeylog.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logKey(SSLEngine engine, String key) {
|
||||||
|
logger.debug(key);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
final class BoringSSLNativeStaticallyReferencedJniMethods {
|
||||||
|
static native int ssl_verify_none();
|
||||||
|
static native int ssl_verify_peer();
|
||||||
|
static native int ssl_verify_fail_if_no_peer_cert();
|
||||||
|
|
||||||
|
static native int x509_v_ok();
|
||||||
|
static native int x509_v_err_cert_has_expired();
|
||||||
|
static native int x509_v_err_cert_not_yet_valid();
|
||||||
|
static native int x509_v_err_cert_revoked();
|
||||||
|
static native int x509_v_err_unspecified();
|
||||||
|
static native int ssl_sign_rsa_pkcs_sha1();
|
||||||
|
static native int ssl_sign_rsa_pkcs_sha256();
|
||||||
|
static native int ssl_sign_rsa_pkcs_sha384();
|
||||||
|
static native int ssl_sign_rsa_pkcs_sha512();
|
||||||
|
static native int ssl_sign_ecdsa_pkcs_sha1();
|
||||||
|
static native int ssl_sign_ecdsa_secp256r1_sha256();
|
||||||
|
static native int ssl_sign_ecdsa_secp384r1_sha384();
|
||||||
|
static native int ssl_sign_ecdsa_secp521r1_sha512();
|
||||||
|
static native int ssl_sign_rsa_pss_rsae_sha256();
|
||||||
|
static native int ssl_sign_rsa_pss_rsae_sha384();
|
||||||
|
static native int ssl_sign_rsa_pss_rsae_sha512();
|
||||||
|
static native int ssl_sign_ed25519();
|
||||||
|
static native int ssl_sign_rsa_pkcs1_md5_sha1();
|
||||||
|
|
||||||
|
private BoringSSLNativeStaticallyReferencedJniMethods() { }
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to customize private key signing / decrypt (when using RSA).
|
||||||
|
*/
|
||||||
|
interface BoringSSLPrivateKeyMethod {
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA1 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha1();
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA256 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha256();
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA384 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha384();
|
||||||
|
int SSL_SIGN_RSA_PKCS1_SHA512 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs_sha512();
|
||||||
|
int SSL_SIGN_ECDSA_SHA1 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_pkcs_sha1();
|
||||||
|
int SSL_SIGN_ECDSA_SECP256R1_SHA256 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_secp256r1_sha256();
|
||||||
|
int SSL_SIGN_ECDSA_SECP384R1_SHA384 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_secp384r1_sha384();
|
||||||
|
int SSL_SIGN_ECDSA_SECP521R1_SHA512 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ecdsa_secp521r1_sha512();
|
||||||
|
int SSL_SIGN_RSA_PSS_RSAE_SHA256 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pss_rsae_sha256();
|
||||||
|
int SSL_SIGN_RSA_PSS_RSAE_SHA384 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pss_rsae_sha384();
|
||||||
|
int SSL_SIGN_RSA_PSS_RSAE_SHA512 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pss_rsae_sha512();
|
||||||
|
int SSL_SIGN_ED25519 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_ed25519();
|
||||||
|
int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = BoringSSLNativeStaticallyReferencedJniMethods.ssl_sign_rsa_pkcs1_md5_sha1();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the input with given EC key and returns the signed bytes.
|
||||||
|
*
|
||||||
|
* @param ssl the SSL instance
|
||||||
|
* @param signatureAlgorithm the algorithm to use for signing
|
||||||
|
* @param input the input itself
|
||||||
|
* @return the sign
|
||||||
|
* @throws Exception thrown if an error accours while signing.
|
||||||
|
*/
|
||||||
|
void sign(long ssl, int signatureAlgorithm, byte[] input, BiConsumer<byte[], Throwable> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the input with the given RSA key and returns the decrypted bytes.
|
||||||
|
*
|
||||||
|
* @param ssl the SSL instance
|
||||||
|
* @param input the input which should be decrypted
|
||||||
|
* @return the decrypted data
|
||||||
|
* @throws Exception thrown if an error accours while decrypting.
|
||||||
|
*/
|
||||||
|
void decrypt(long ssl, byte[] input, BiConsumer<byte[], Throwable> callback);
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
final class BoringSSLPrivateKeyMethodDecryptTask extends BoringSSLPrivateKeyMethodTask {
|
||||||
|
private final byte[] input;
|
||||||
|
|
||||||
|
BoringSSLPrivateKeyMethodDecryptTask(long ssl, byte[] input, BoringSSLPrivateKeyMethod method) {
|
||||||
|
super(ssl, method);
|
||||||
|
// It's OK to not clone the arrays as we create these in JNI and not reuse.
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runMethod(long ssl, BoringSSLPrivateKeyMethod method, BiConsumer<byte[], Throwable> consumer) {
|
||||||
|
method.decrypt(ssl, input, consumer);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
final class BoringSSLPrivateKeyMethodSignTask extends BoringSSLPrivateKeyMethodTask {
|
||||||
|
private final int signatureAlgorithm;
|
||||||
|
private final byte[] digest;
|
||||||
|
|
||||||
|
BoringSSLPrivateKeyMethodSignTask(long ssl, int signatureAlgorithm, byte[] digest, BoringSSLPrivateKeyMethod method) {
|
||||||
|
super(ssl, method);
|
||||||
|
this.signatureAlgorithm = signatureAlgorithm;
|
||||||
|
// It's OK to not clone the arrays as we create these in JNI and not reuse.
|
||||||
|
this.digest = digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runMethod(long ssl, BoringSSLPrivateKeyMethod method, BiConsumer<byte[], Throwable> callback) {
|
||||||
|
method.sign(ssl, signatureAlgorithm, digest, callback);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
abstract class BoringSSLPrivateKeyMethodTask extends BoringSSLTask {
|
||||||
|
|
||||||
|
private final BoringSSLPrivateKeyMethod method;
|
||||||
|
|
||||||
|
// Will be accessed via JNI.
|
||||||
|
private byte[] resultBytes;
|
||||||
|
|
||||||
|
BoringSSLPrivateKeyMethodTask(long ssl, BoringSSLPrivateKeyMethod method) {
|
||||||
|
super(ssl);
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final void runTask(long ssl, TaskCallback callback) {
|
||||||
|
runMethod(ssl, method, (result, error) -> {
|
||||||
|
if (result == null || error != null) {
|
||||||
|
callback.onResult(ssl, -1);
|
||||||
|
} else {
|
||||||
|
resultBytes = result;
|
||||||
|
callback.onResult(ssl, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void runMethod(long ssl, BoringSSLPrivateKeyMethod method,
|
||||||
|
BiConsumer<byte[], Throwable> callback);
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
final class BoringSSLSessionCallback {
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(BoringSSLSessionCallback.class);
|
||||||
|
private final QuicClientSessionCache sessionCache;
|
||||||
|
private final QuicheQuicSslEngineMap engineMap;
|
||||||
|
|
||||||
|
BoringSSLSessionCallback(QuicheQuicSslEngineMap engineMap, QuicClientSessionCache sessionCache) {
|
||||||
|
this.engineMap = engineMap;
|
||||||
|
this.sessionCache = sessionCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
void newSession(long ssl, long creationTime, long timeout, byte[] session, boolean isSingleUse, byte[] peerParams) {
|
||||||
|
if (sessionCache == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine == null) {
|
||||||
|
logger.warn("engine is null ssl: {}", ssl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerParams == null) {
|
||||||
|
peerParams = EmptyArrays.EMPTY_BYTES;
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("ssl: {}, session: {}, peerParams: {}", ssl, Arrays.toString(session),
|
||||||
|
Arrays.toString(peerParams));
|
||||||
|
}
|
||||||
|
byte[] quicSession = toQuicheQuicSession(session, peerParams);
|
||||||
|
if (quicSession != null) {
|
||||||
|
logger.debug("save session host={}, port={}",
|
||||||
|
engine.getSession().getPeerHost(), engine.getSession().getPeerPort());
|
||||||
|
sessionCache.saveSession(engine.getSession().getPeerHost(), engine.getSession().getPeerPort(),
|
||||||
|
TimeUnit.SECONDS.toMillis(creationTime), TimeUnit.SECONDS.toMillis(timeout),
|
||||||
|
quicSession, isSingleUse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mimic the encoding of quiche: https://github.com/cloudflare/quiche/blob/0.10.0/src/lib.rs#L1668
|
||||||
|
private static byte[] toQuicheQuicSession(byte[] sslSession, byte[] peerParams) {
|
||||||
|
if (sslSession != null && peerParams != null) {
|
||||||
|
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos)) {
|
||||||
|
dos.writeLong(sslSession.length);
|
||||||
|
dos.write(sslSession);
|
||||||
|
dos.writeLong(peerParams.length);
|
||||||
|
dos.write(peerParams);
|
||||||
|
return bos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
|
final class BoringSSLSessionTicketCallback {
|
||||||
|
|
||||||
|
// As we dont assume to have a lot of keys configured we will just use an array for now as a data store.
|
||||||
|
private volatile byte[][] sessionKeys;
|
||||||
|
|
||||||
|
// Accessed via JNI.
|
||||||
|
byte[] findSessionTicket(byte[] keyname) {
|
||||||
|
byte[][] keys = this.sessionKeys;
|
||||||
|
if (keys == null || keys.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (keyname == null) {
|
||||||
|
return keys[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
byte[] key = keys[i];
|
||||||
|
if (PlatformDependent.equals(keyname, 0, key, 1, keyname.length)) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSessionTicketKeys(SslSessionTicketKey[] keys) {
|
||||||
|
if (keys != null && keys.length != 0) {
|
||||||
|
byte[][] sessionKeys = new byte[keys.length][];
|
||||||
|
for(int i = 0; i < keys.length; ++i) {
|
||||||
|
SslSessionTicketKey key = keys[i];
|
||||||
|
byte[] binaryKey = new byte[49];
|
||||||
|
// We mark the first key as preferred by using 1 as byte marker
|
||||||
|
binaryKey[0] = i == 0 ? (byte) 1 : (byte) 0;
|
||||||
|
int dstCurPos = 1;
|
||||||
|
System.arraycopy(key.name, 0, binaryKey, dstCurPos, 16);
|
||||||
|
dstCurPos += 16;
|
||||||
|
System.arraycopy(key.hmacKey, 0, binaryKey, dstCurPos, 16);
|
||||||
|
dstCurPos += 16;
|
||||||
|
System.arraycopy(key.aesKey, 0, binaryKey, dstCurPos, 16);
|
||||||
|
sessionKeys[i] = binaryKey;
|
||||||
|
}
|
||||||
|
this.sessionKeys = sessionKeys;
|
||||||
|
} else {
|
||||||
|
sessionKeys = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SSL related task that will be returned by {@link BoringSSL#SSL_getTask(long)}.
|
||||||
|
*/
|
||||||
|
abstract class BoringSSLTask implements Runnable {
|
||||||
|
private final long ssl;
|
||||||
|
protected boolean didRun;
|
||||||
|
|
||||||
|
// These fields are accessed via JNI.
|
||||||
|
private int returnValue;
|
||||||
|
private volatile boolean complete;
|
||||||
|
|
||||||
|
protected BoringSSLTask(long ssl) {
|
||||||
|
// It is important that this constructor never throws. Be sure to not change this!
|
||||||
|
this.ssl = ssl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void run() {
|
||||||
|
if (!didRun) {
|
||||||
|
didRun = true;
|
||||||
|
runTask(ssl, (long ssl, int result) -> {
|
||||||
|
returnValue = result;
|
||||||
|
complete = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once the task should be destroyed.
|
||||||
|
*/
|
||||||
|
protected void destroy() {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the task and return the return value that should be passed back to OpenSSL.
|
||||||
|
*/
|
||||||
|
protected abstract void runTask(long ssl, TaskCallback callback);
|
||||||
|
|
||||||
|
interface TaskCallback {
|
||||||
|
void onResult(long ssl, int result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.Mapping;
|
||||||
|
|
||||||
|
final class BoringSSLTlsextServernameCallback {
|
||||||
|
|
||||||
|
private final QuicheQuicSslEngineMap engineMap;
|
||||||
|
private final Mapping<? super String, ? extends QuicSslContext> mapping;
|
||||||
|
|
||||||
|
BoringSSLTlsextServernameCallback(QuicheQuicSslEngineMap engineMap,
|
||||||
|
Mapping<? super String, ? extends QuicSslContext> mapping) {
|
||||||
|
this.engineMap = engineMap;
|
||||||
|
this.mapping = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
long selectCtx(long ssl, String serverName) {
|
||||||
|
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine == null) {
|
||||||
|
// May be null if it was destroyed in the meantime.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicSslContext context = mapping.map(serverName);
|
||||||
|
if (context == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return engine.moveTo(serverName, (QuicheQuicSslContext) context);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.DefaultByteBufHolder;
|
||||||
|
|
||||||
|
public final class DefaultQuicStreamFrame extends DefaultByteBufHolder implements QuicStreamFrame {
|
||||||
|
|
||||||
|
private final boolean fin;
|
||||||
|
|
||||||
|
public DefaultQuicStreamFrame(ByteBuf data, boolean fin) {
|
||||||
|
super(data);
|
||||||
|
this.fin = fin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFin() {
|
||||||
|
return fin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame copy() {
|
||||||
|
return new DefaultQuicStreamFrame(content().copy(), fin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame duplicate() {
|
||||||
|
return new DefaultQuicStreamFrame(content().duplicate(), fin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame retainedDuplicate() {
|
||||||
|
return new DefaultQuicStreamFrame(content().retainedDuplicate(), fin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame replace(ByteBuf content) {
|
||||||
|
return new DefaultQuicStreamFrame(content, fin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame retain() {
|
||||||
|
super.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame retain(int increment) {
|
||||||
|
super.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame touch() {
|
||||||
|
super.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame touch(Object hint) {
|
||||||
|
super.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DefaultQuicStreamFrame{" +
|
||||||
|
"fin=" + fin +
|
||||||
|
", content=" + contentToString() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DefaultQuicStreamFrame that = (DefaultQuicStreamFrame) o;
|
||||||
|
|
||||||
|
if (fin != that.fin) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = 31 * result + (fin ? 1 : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
|
|
||||||
|
final class DirectIoByteBufAllocator implements ByteBufAllocator {
|
||||||
|
|
||||||
|
private final ByteBufAllocator wrapped;
|
||||||
|
|
||||||
|
DirectIoByteBufAllocator(ByteBufAllocator wrapped) {
|
||||||
|
if (wrapped instanceof DirectIoByteBufAllocator) {
|
||||||
|
wrapped = ((DirectIoByteBufAllocator) wrapped).wrapped();
|
||||||
|
}
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBufAllocator wrapped() {
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf buffer() {
|
||||||
|
return wrapped.buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf buffer(int initialCapacity) {
|
||||||
|
return wrapped.buffer(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf buffer(int initialCapacity, int maxCapacity) {
|
||||||
|
return wrapped.buffer(initialCapacity, maxCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf ioBuffer() {
|
||||||
|
return directBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf ioBuffer(int initialCapacity) {
|
||||||
|
return directBuffer(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) {
|
||||||
|
return directBuffer(initialCapacity, maxCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf heapBuffer() {
|
||||||
|
return wrapped.heapBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf heapBuffer(int initialCapacity) {
|
||||||
|
return wrapped.heapBuffer(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
|
||||||
|
return wrapped.heapBuffer(initialCapacity, maxCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf directBuffer() {
|
||||||
|
return wrapped.directBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf directBuffer(int initialCapacity) {
|
||||||
|
return wrapped.directBuffer(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
|
||||||
|
return wrapped.directBuffer(initialCapacity, maxCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeByteBuf compositeBuffer() {
|
||||||
|
return wrapped.compositeBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeByteBuf compositeBuffer(int maxNumComponents) {
|
||||||
|
return wrapped.compositeBuffer(maxNumComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeByteBuf compositeHeapBuffer() {
|
||||||
|
return wrapped.compositeHeapBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeByteBuf compositeHeapBuffer(int maxNumComponents) {
|
||||||
|
return wrapped.compositeHeapBuffer(maxNumComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeByteBuf compositeDirectBuffer() {
|
||||||
|
return wrapped.compositeDirectBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompositeByteBuf compositeDirectBuffer(int maxNumComponents) {
|
||||||
|
return wrapped.compositeDirectBuffer(maxNumComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirectBufferPooled() {
|
||||||
|
return wrapped.isDirectBufferPooled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
|
||||||
|
return wrapped.calculateNewCapacity(minNewCapacity, maxCapacity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.epoll.SegmentedDatagramPacket;
|
||||||
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that provides utility methods to setup {@code QUIC} when using the {@code EPOLL} transport.
|
||||||
|
*/
|
||||||
|
public final class EpollQuicUtils {
|
||||||
|
|
||||||
|
private EpollQuicUtils() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link SegmentedDatagramPacketAllocator} that can be used while using
|
||||||
|
* {@link io.netty.channel.epoll.EpollDatagramChannel}.
|
||||||
|
*
|
||||||
|
* @param maxNumSegments the maximum number of segments that we try to send in one packet.
|
||||||
|
* @return a allocator.
|
||||||
|
*/
|
||||||
|
public static SegmentedDatagramPacketAllocator newSegmentedAllocator(int maxNumSegments) {
|
||||||
|
ObjectUtil.checkInRange(maxNumSegments, 1, 64, "maxNumSegments");
|
||||||
|
if (SegmentedDatagramPacket.isSupported()) {
|
||||||
|
return new EpollSegmentedDatagramPacketAllocator(maxNumSegments);
|
||||||
|
}
|
||||||
|
return SegmentedDatagramPacketAllocator.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class EpollSegmentedDatagramPacketAllocator implements SegmentedDatagramPacketAllocator {
|
||||||
|
|
||||||
|
private final int maxNumSegments;
|
||||||
|
|
||||||
|
EpollSegmentedDatagramPacketAllocator(int maxNumSegments) {
|
||||||
|
this.maxNumSegments = maxNumSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxNumSegments() {
|
||||||
|
return maxNumSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatagramPacket newPacket(ByteBuf buffer, int segmentSize, InetSocketAddress remoteAddress) {
|
||||||
|
return new io.netty.channel.unix.SegmentedDatagramPacket(buffer, segmentSize, remoteAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to configure a strategy for when flushes should be happening.
|
||||||
|
*/
|
||||||
|
public interface FlushStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default {@link FlushStrategy} implementation.
|
||||||
|
*/
|
||||||
|
FlushStrategy DEFAULT = afterNumBytes(20 * Quic.MAX_DATAGRAM_SIZE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if a flush should happen now, {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @param numPackets the number of packets that were written since the last flush.
|
||||||
|
* @param numBytes the number of bytes that were written since the last flush.
|
||||||
|
* @return {@code true} if a flush should be done now, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean shouldFlushNow(int numPackets, int numBytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation that flushes after a number of bytes.
|
||||||
|
*
|
||||||
|
* @param bytes the number of bytes after which we should issue a flush.
|
||||||
|
* @return the {@link FlushStrategy}.
|
||||||
|
*/
|
||||||
|
static FlushStrategy afterNumBytes(int bytes) {
|
||||||
|
ObjectUtil.checkPositive(bytes, "bytes");
|
||||||
|
return (numPackets, numBytes) -> numBytes > bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation that flushes after a number of packets.
|
||||||
|
*
|
||||||
|
* @param packets the number of packets after which we should issue a flush.
|
||||||
|
* @return the {@link FlushStrategy}.
|
||||||
|
*/
|
||||||
|
static FlushStrategy afterNumPackets(int packets) {
|
||||||
|
ObjectUtil.checkPositive(packets, "packets");
|
||||||
|
return (numPackets, numBytes) -> numPackets > packets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.FastThreadLocal;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
final class Hmac {
|
||||||
|
|
||||||
|
private static final FastThreadLocal<Mac> MACS = new FastThreadLocal<Mac>() {
|
||||||
|
@Override
|
||||||
|
protected Mac initialValue() {
|
||||||
|
return newMac();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String ALGORITM = "HmacSHA256";
|
||||||
|
private static final byte[] randomKey = new byte[16];
|
||||||
|
|
||||||
|
static {
|
||||||
|
new SecureRandom().nextBytes(randomKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mac newMac() {
|
||||||
|
try {
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(randomKey, ALGORITM);
|
||||||
|
Mac mac = Mac.getInstance(ALGORITM);
|
||||||
|
mac.init(keySpec);
|
||||||
|
return mac;
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeyException exception) {
|
||||||
|
throw new IllegalStateException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ByteBuffer sign(ByteBuffer input, int outLength) {
|
||||||
|
Mac mac = MACS.get();
|
||||||
|
mac.reset();
|
||||||
|
mac.update(input);
|
||||||
|
byte[] signBytes = mac.doFinal();
|
||||||
|
if (signBytes.length != outLength) {
|
||||||
|
signBytes = Arrays.copyOf(signBytes, outLength);
|
||||||
|
}
|
||||||
|
return ByteBuffer.wrap(signBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Hmac() { }
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link QuicConnectionIdGenerator} which creates new connection id by signing the given input
|
||||||
|
* using hmac algorithms.
|
||||||
|
*/
|
||||||
|
final class HmacSignQuicConnectionIdGenerator implements QuicConnectionIdGenerator {
|
||||||
|
static final QuicConnectionIdGenerator INSTANCE = new HmacSignQuicConnectionIdGenerator();
|
||||||
|
|
||||||
|
private HmacSignQuicConnectionIdGenerator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer newId(int length) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"HmacSignQuicConnectionIdGenerator should always have an input to sign with");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer newId(ByteBuffer buffer, int length) {
|
||||||
|
ObjectUtil.checkNotNull(buffer, "buffer");
|
||||||
|
ObjectUtil.checkPositive(buffer.remaining(), "buffer");
|
||||||
|
ObjectUtil.checkInRange(length, 0, maxConnectionIdLength(), "length");
|
||||||
|
|
||||||
|
return Hmac.sign(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxConnectionIdLength() {
|
||||||
|
return Quiche.QUICHE_MAX_CONN_ID_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIdempotent() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link QuicResetTokenGenerator} which creates new reset token by using the connection id by signing the given input
|
||||||
|
* using <a href="https://www.ietf.org/archive/id/draft-ietf-quic-transport-29.html#section-10.4.2">HMAC algorithms</a>.
|
||||||
|
*/
|
||||||
|
final class HmacSignQuicResetTokenGenerator implements QuicResetTokenGenerator {
|
||||||
|
static final QuicResetTokenGenerator INSTANCE = new HmacSignQuicResetTokenGenerator();
|
||||||
|
|
||||||
|
private HmacSignQuicResetTokenGenerator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer newResetToken(ByteBuffer cid) {
|
||||||
|
ObjectUtil.checkNotNull(cid, "cid");
|
||||||
|
ObjectUtil.checkPositive(cid.remaining(), "cid");
|
||||||
|
return Hmac.sign(cid, Quic.RESET_TOKEN_LEN);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import io.netty.util.NetUtil;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insecure {@link QuicTokenHandler} which only does basic token generation / validation without any
|
||||||
|
* crypto.
|
||||||
|
*
|
||||||
|
* <strong>This shouldn't be used in production.</strong>
|
||||||
|
*/
|
||||||
|
public final class InsecureQuicTokenHandler implements QuicTokenHandler {
|
||||||
|
|
||||||
|
private static final String SERVER_NAME = "netty";
|
||||||
|
private static final byte[] SERVER_NAME_BYTES = SERVER_NAME.getBytes(CharsetUtil.US_ASCII);
|
||||||
|
private static final ByteBuf SERVER_NAME_BUFFER = Unpooled.unreleasableBuffer(
|
||||||
|
Unpooled.wrappedBuffer(SERVER_NAME_BYTES)).asReadOnly();
|
||||||
|
|
||||||
|
// Just package-private for unit tests
|
||||||
|
static final int MAX_TOKEN_LEN = Quiche.QUICHE_MAX_CONN_ID_LEN +
|
||||||
|
NetUtil.LOCALHOST6.getAddress().length + SERVER_NAME_BYTES.length;
|
||||||
|
|
||||||
|
private InsecureQuicTokenHandler() {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final InsecureQuicTokenHandler INSTANCE = new InsecureQuicTokenHandler();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address) {
|
||||||
|
byte[] addr = address.getAddress().getAddress();
|
||||||
|
out.writeBytes(SERVER_NAME_BYTES)
|
||||||
|
.writeBytes(addr)
|
||||||
|
.writeBytes(dcid, dcid.readerIndex(), dcid.readableBytes());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int validateToken(ByteBuf token, InetSocketAddress address) {
|
||||||
|
final byte[] addr = address.getAddress().getAddress();
|
||||||
|
|
||||||
|
int minLength = SERVER_NAME_BYTES.length + address.getAddress().getAddress().length;
|
||||||
|
if (token.readableBytes() <= SERVER_NAME_BYTES.length + addr.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SERVER_NAME_BUFFER.equals(token.slice(0, SERVER_NAME_BYTES.length))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ByteBuf addressBuffer = Unpooled.wrappedBuffer(addr);
|
||||||
|
try {
|
||||||
|
if (!addressBuffer.equals(token.slice(SERVER_NAME_BYTES.length, addr.length))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
addressBuffer.release();
|
||||||
|
}
|
||||||
|
return minLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxTokenLength() {
|
||||||
|
return MAX_TOKEN_LEN;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link QuicTokenHandler} which will disable token generation / validation completely.
|
||||||
|
* This will reduce the round-trip for QUIC connection migration, but will also weaking the
|
||||||
|
* security during connection establishment.
|
||||||
|
*/
|
||||||
|
final class NoQuicTokenHandler implements QuicTokenHandler {
|
||||||
|
|
||||||
|
public final static QuicTokenHandler INSTANCE = new NoQuicTokenHandler();
|
||||||
|
|
||||||
|
private NoQuicTokenHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int validateToken(ByteBuf token, InetSocketAddress address) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxTokenLength() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration used for setup
|
||||||
|
* <a href="https://quiclog.github.io/internet-drafts/draft-marx-qlog-main-schema.html">qlog</a>.
|
||||||
|
*/
|
||||||
|
public final class QLogConfiguration {
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
private final String logTitle;
|
||||||
|
private final String logDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new configuration.
|
||||||
|
*
|
||||||
|
* @param path the path to the log file to use. This file must not exist yet. If the path is a
|
||||||
|
* directory the filename will be generated
|
||||||
|
* @param logTitle the title to use when logging.
|
||||||
|
* @param logDescription the description to use when logging.
|
||||||
|
*/
|
||||||
|
public QLogConfiguration(String path, String logTitle, String logDescription) {
|
||||||
|
this.path = Objects.requireNonNull(path, "path");
|
||||||
|
this.logTitle = Objects.requireNonNull(logTitle, "logTitle");
|
||||||
|
this.logDescription = Objects.requireNonNull(logDescription, "logDescription");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path to the log file.
|
||||||
|
*
|
||||||
|
* @return the path.
|
||||||
|
*/
|
||||||
|
public String path() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the title.
|
||||||
|
*
|
||||||
|
* @return the title.
|
||||||
|
*/
|
||||||
|
public String logTitle() {
|
||||||
|
return logTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the description.
|
||||||
|
*
|
||||||
|
* @return the description.
|
||||||
|
*/
|
||||||
|
public String logDescription() {
|
||||||
|
return logDescription;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class Quic {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static final Map.Entry<ChannelOption<?>, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0];
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static final Map.Entry<AttributeKey<?>, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0];
|
||||||
|
|
||||||
|
static final int MAX_DATAGRAM_SIZE = 1350;
|
||||||
|
|
||||||
|
static final int RESET_TOKEN_LEN = 16;
|
||||||
|
|
||||||
|
private static final Throwable UNAVAILABILITY_CAUSE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Throwable cause = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String version = Quiche.quiche_version();
|
||||||
|
assert version != null;
|
||||||
|
} catch (Throwable error) {
|
||||||
|
cause = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
UNAVAILABILITY_CAUSE = cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the given QUIC version is supported.
|
||||||
|
*
|
||||||
|
* @param version the version.
|
||||||
|
* @return {@code true} if supported, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isVersionSupported(int version) {
|
||||||
|
return isAvailable() && Quiche.quiche_version_is_supported(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the QUIC implementation is usable on the running platform is available.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this QUIC implementation can be used on the current platform, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isAvailable() {
|
||||||
|
return UNAVAILABILITY_CAUSE == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that QUIC implementation is usable on the running platform is available.
|
||||||
|
*
|
||||||
|
* @throws UnsatisfiedLinkError if unavailable
|
||||||
|
*/
|
||||||
|
public static void ensureAvailability() {
|
||||||
|
if (UNAVAILABILITY_CAUSE != null) {
|
||||||
|
throw (Error) new UnsatisfiedLinkError(
|
||||||
|
"failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cause of unavailability.
|
||||||
|
*
|
||||||
|
* @return the cause if unavailable. {@code null} if available.
|
||||||
|
*/
|
||||||
|
public static Throwable unavailabilityCause() {
|
||||||
|
return UNAVAILABILITY_CAUSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map.Entry<ChannelOption<?>, Object>[] toOptionsArray(Map<ChannelOption<?>, Object> opts) {
|
||||||
|
return new HashMap<>(opts).entrySet().toArray(EMPTY_OPTION_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map.Entry<AttributeKey<?>, Object>[] toAttributesArray(Map<AttributeKey<?>, Object> attributes) {
|
||||||
|
return new LinkedHashMap<>(attributes).entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setAttributes(Channel channel, Map.Entry<AttributeKey<?>, Object>[] attrs) {
|
||||||
|
for (Map.Entry<AttributeKey<?>, Object> e: attrs) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
|
||||||
|
channel.attr(key).set(e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setChannelOptions(
|
||||||
|
Channel channel, Map.Entry<ChannelOption<?>, Object>[] options, InternalLogger logger) {
|
||||||
|
for (Map.Entry<ChannelOption<?>, Object> e: options) {
|
||||||
|
setChannelOption(channel, e.getKey(), e.getValue(), logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static void setChannelOption(
|
||||||
|
Channel channel, ChannelOption<?> option, Object value, InternalLogger logger) {
|
||||||
|
try {
|
||||||
|
if (!channel.config().setOption((ChannelOption<Object>) option, value)) {
|
||||||
|
logger.warn("Unknown channel option '{}' for channel '{}'", option, channel);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to set channel option '{}' with value '{}' for channel '{}'", option, value, channel, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||||
|
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||||
|
*/
|
||||||
|
static <T> void updateOptions(Map<ChannelOption<?>, Object> options, ChannelOption<T> option, T value) {
|
||||||
|
ObjectUtil.checkNotNull(option, "option");
|
||||||
|
if (value == null) {
|
||||||
|
options.remove(option);
|
||||||
|
} else {
|
||||||
|
options.put(option, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||||
|
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||||
|
*/
|
||||||
|
static <T> void updateAttributes(Map<AttributeKey<?>, Object> attributes, AttributeKey<T> key, T value) {
|
||||||
|
ObjectUtil.checkNotNull(key, "key");
|
||||||
|
if (value == null) {
|
||||||
|
attributes.remove(key);
|
||||||
|
} else {
|
||||||
|
attributes.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setupChannel(Channel ch, Map.Entry<ChannelOption<?>, Object>[] options,
|
||||||
|
Map.Entry<AttributeKey<?>, Object>[] attrs, ChannelHandler handler,
|
||||||
|
InternalLogger logger) {
|
||||||
|
Quic.setChannelOptions(ch, options, logger);
|
||||||
|
Quic.setAttributes(ch, attrs);
|
||||||
|
if (handler != null) {
|
||||||
|
ch.pipeline().addLast(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Quic() { }
|
||||||
|
}
|
@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelProgressivePromise;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A QUIC {@link Channel}.
|
||||||
|
*/
|
||||||
|
public interface QuicChannel extends Channel {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture bind(SocketAddress localAddress) {
|
||||||
|
return pipeline().bind(localAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress) {
|
||||||
|
return pipeline().connect(remoteAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||||
|
return pipeline().connect(remoteAddress, localAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture disconnect() {
|
||||||
|
return pipeline().disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture close() {
|
||||||
|
return pipeline().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture deregister() {
|
||||||
|
return pipeline().deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
return pipeline().bind(localAddress, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
|
||||||
|
return pipeline().connect(remoteAddress, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
return pipeline().connect(remoteAddress, localAddress, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture disconnect(ChannelPromise promise) {
|
||||||
|
return pipeline().disconnect(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture close(ChannelPromise promise) {
|
||||||
|
return pipeline().close(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture deregister(ChannelPromise promise) {
|
||||||
|
return pipeline().deregister(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture write(Object msg) {
|
||||||
|
return pipeline().write(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture write(Object msg, ChannelPromise promise) {
|
||||||
|
return pipeline().write(msg, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
||||||
|
return pipeline().writeAndFlush(msg, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture writeAndFlush(Object msg) {
|
||||||
|
return pipeline().writeAndFlush(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelPromise newPromise() {
|
||||||
|
return pipeline().newPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelProgressivePromise newProgressivePromise() {
|
||||||
|
return pipeline().newProgressivePromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture newSucceededFuture() {
|
||||||
|
return pipeline().newSucceededFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture newFailedFuture(Throwable cause) {
|
||||||
|
return pipeline().newFailedFuture(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelPromise voidPromise() {
|
||||||
|
return pipeline().voidPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannel read();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannel flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configuration of this channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the used {@link SSLEngine} or {@code null} if none is used (yet).
|
||||||
|
*
|
||||||
|
* @return the engine.
|
||||||
|
*/
|
||||||
|
SSLEngine sslEngine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of streams that can be created before stream creation will fail
|
||||||
|
* with {@link QuicError#STREAM_LIMIT} error.
|
||||||
|
*
|
||||||
|
* @param type the stream type.
|
||||||
|
* @return the number of streams left.
|
||||||
|
*/
|
||||||
|
long peerAllowedStreams(QuicStreamType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the connection was closed because of idle timeout.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the connection was closed because of idle timeout, {@code false}.
|
||||||
|
*/
|
||||||
|
boolean isTimedOut();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link QuicTransportParameters} of the peer once received, or {@code null} if not known yet.
|
||||||
|
*
|
||||||
|
* @return peerTransportParams.
|
||||||
|
*/
|
||||||
|
QuicTransportParameters peerTransportParameters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stream that is using this {@link QuicChannel} and notifies the {@link Future} once done.
|
||||||
|
* The {@link ChannelHandler} (if not {@code null}) is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicStreamChannel} automatically.
|
||||||
|
*
|
||||||
|
* @param type the {@link QuicStreamType} of the {@link QuicStreamChannel}.
|
||||||
|
* @param handler the {@link ChannelHandler} that will be added to the {@link QuicStreamChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline} during the stream creation.
|
||||||
|
* @return the {@link Future} that will be notified once the operation completes.
|
||||||
|
*/
|
||||||
|
default Future<QuicStreamChannel> createStream(QuicStreamType type, ChannelHandler handler) {
|
||||||
|
return createStream(type, handler, eventLoop().newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stream that is using this {@link QuicChannel} and notifies the {@link Promise} once done.
|
||||||
|
* The {@link ChannelHandler} (if not {@code null}) is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicStreamChannel} automatically.
|
||||||
|
*
|
||||||
|
* @param type the {@link QuicStreamType} of the {@link QuicStreamChannel}.
|
||||||
|
* @param handler the {@link ChannelHandler} that will be added to the {@link QuicStreamChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline} during the stream creation.
|
||||||
|
* @param promise the {@link ChannelPromise} that will be notified once the operation completes.
|
||||||
|
* @return the {@link Future} that will be notified once the operation completes.
|
||||||
|
*/
|
||||||
|
Future<QuicStreamChannel> createStream(QuicStreamType type, ChannelHandler handler,
|
||||||
|
Promise<QuicStreamChannel> promise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link QuicStreamChannelBootstrap} which makes it easy to bootstrap new {@link QuicStreamChannel}s
|
||||||
|
* with custom options and attributes. For simpler use-cases you may want to consider using
|
||||||
|
* {@link #createStream(QuicStreamType, ChannelHandler)} or
|
||||||
|
* {@link #createStream(QuicStreamType, ChannelHandler, Promise)} directly.
|
||||||
|
*
|
||||||
|
* @return {@link QuicStreamChannelBootstrap} that can be used to bootstrap a {@link QuicStreamChannel}.
|
||||||
|
*/
|
||||||
|
default QuicStreamChannelBootstrap newStreamBootstrap() {
|
||||||
|
return new QuicStreamChannelBootstrap(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the {@link QuicChannel}
|
||||||
|
*
|
||||||
|
* @param applicationClose {@code true} if an application close should be used,
|
||||||
|
* {@code false} if a normal close should be used.
|
||||||
|
* @param error the application error number, or {@code 0} if no special error should be signaled.
|
||||||
|
* @param reason the reason for the closure (which may be an empty {@link ByteBuf}.
|
||||||
|
* @return the future that is notified.
|
||||||
|
*/
|
||||||
|
default ChannelFuture close(boolean applicationClose, int error, ByteBuf reason) {
|
||||||
|
return close(applicationClose, error, reason, newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the {@link QuicChannel}
|
||||||
|
*
|
||||||
|
* @param applicationClose {@code true} if an application close should be used,
|
||||||
|
* {@code false} if a normal close should be used.
|
||||||
|
* @param error the application error number, or {@code 0} if no special error should be signaled.
|
||||||
|
* @param reason the reason for the closure (which may be an empty {@link ByteBuf}.
|
||||||
|
* @param promise the {@link ChannelPromise} that will be notified.
|
||||||
|
* @return the future that is notified.
|
||||||
|
*/
|
||||||
|
ChannelFuture close(boolean applicationClose, int error, ByteBuf reason, ChannelPromise promise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects statistics about the connection and notifies the {@link Future} once done.
|
||||||
|
*
|
||||||
|
* @return the {@link Future} that is notified once the stats were collected.
|
||||||
|
*/
|
||||||
|
default Future<QuicConnectionStats> collectStats() {
|
||||||
|
return collectStats(eventLoop().newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects statistics about the connection and notifies the {@link Promise} once done.
|
||||||
|
*
|
||||||
|
* @param promise the {@link ChannelPromise} that is notified once the stats were collected.
|
||||||
|
* @return the {@link Future} that is notified once the stats were collected.
|
||||||
|
*/
|
||||||
|
Future<QuicConnectionStats> collectStats(Promise<QuicConnectionStats> promise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link QuicChannelBootstrap} that can be used to create and connect new {@link QuicChannel}s to
|
||||||
|
* endpoints using the given {@link Channel} as transport layer.
|
||||||
|
*
|
||||||
|
* @param channel the {@link Channel} that is used as transport layer.
|
||||||
|
* @return {@link QuicChannelBootstrap} that can be used to bootstrap a client side {@link QuicChannel}.
|
||||||
|
*/
|
||||||
|
static QuicChannelBootstrap newBootstrap(Channel channel) {
|
||||||
|
return new QuicChannelBootstrap(channel);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap that helps to bootstrap {@link QuicChannel}s and connecting these to remote peers.
|
||||||
|
*/
|
||||||
|
public final class QuicChannelBootstrap {
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(QuicChannelBootstrap.class);
|
||||||
|
|
||||||
|
private final Channel parent;
|
||||||
|
// The order in which ChannelOptions are applied is important they may depend on each other for validation
|
||||||
|
// purposes.
|
||||||
|
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
|
||||||
|
private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
|
||||||
|
private final Map<ChannelOption<?>, Object> streamOptions = new LinkedHashMap<>();
|
||||||
|
private final Map<AttributeKey<?>, Object> streamAttrs = new HashMap<>();
|
||||||
|
|
||||||
|
private SocketAddress local;
|
||||||
|
private SocketAddress remote;
|
||||||
|
private QuicConnectionAddress connectionAddress = QuicConnectionAddress.EPHEMERAL;
|
||||||
|
private ChannelHandler handler;
|
||||||
|
private ChannelHandler streamHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance which uses the given {@link Channel} to bootstrap the {@link QuicChannel}.
|
||||||
|
* This {@link io.netty.channel.ChannelPipeline} of the {@link Channel} needs to have the quic codec in the
|
||||||
|
* pipeline.
|
||||||
|
*
|
||||||
|
* @param parent the {@link Channel} that is used as the transport layer.
|
||||||
|
*/
|
||||||
|
QuicChannelBootstrap(Channel parent) {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
this.parent = ObjectUtil.checkNotNull(parent, "parent");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicChannel} instances once they got
|
||||||
|
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||||
|
*
|
||||||
|
* @param option the {@link ChannelOption} to apply to the {@link QuicChannel}.
|
||||||
|
* @param value the value of the option.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicChannelBootstrap option(ChannelOption<T> option, T value) {
|
||||||
|
Quic.updateOptions(options, option, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify an initial attribute of the newly created {@link QuicChannel}. If the {@code value} is
|
||||||
|
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||||
|
*
|
||||||
|
* @param key the {@link AttributeKey} to apply to the {@link QuicChannel}.
|
||||||
|
* @param value the value of the attribute.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicChannelBootstrap attr(AttributeKey<T> key, T value) {
|
||||||
|
Quic.updateAttributes(attrs, key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicChannel} once created.
|
||||||
|
*
|
||||||
|
* @param handler the {@link ChannelHandler} that is added to the {@link QuicChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline}.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicChannelBootstrap handler(ChannelHandler handler) {
|
||||||
|
this.handler = ObjectUtil.checkNotNull(handler, "handler");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||||
|
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||||
|
*
|
||||||
|
* @param option the {@link ChannelOption} to apply to the {@link QuicStreamChannel}s.
|
||||||
|
* @param value the value of the option.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicChannelBootstrap streamOption(ChannelOption<T> option, T value) {
|
||||||
|
Quic.updateOptions(streamOptions, option, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||||
|
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||||
|
*
|
||||||
|
* @param key the {@link AttributeKey} to apply to the {@link QuicStreamChannel}s.
|
||||||
|
* @param value the value of the attribute.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicChannelBootstrap streamAttr(AttributeKey<T> key, T value) {
|
||||||
|
Quic.updateAttributes(streamAttrs, key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicStreamChannel} once created.
|
||||||
|
*
|
||||||
|
* @param streamHandler the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline}.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicChannelBootstrap streamHandler(ChannelHandler streamHandler) {
|
||||||
|
this.streamHandler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the local address.
|
||||||
|
*
|
||||||
|
* @param local the {@link SocketAddress} of the local peer.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicChannelBootstrap localAddress(SocketAddress local) {
|
||||||
|
this.local = ObjectUtil.checkNotNull(local, "local");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the remote address of the host to talk to.
|
||||||
|
*
|
||||||
|
* @param remote the {@link SocketAddress} of the remote peer.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicChannelBootstrap remoteAddress(SocketAddress remote) {
|
||||||
|
this.remote = ObjectUtil.checkNotNull(remote, "remote");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link QuicConnectionAddress} to use. If none is specified a random address is generated on your
|
||||||
|
* behalf.
|
||||||
|
*
|
||||||
|
* @param connectionAddress the {@link QuicConnectionAddress} to use.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicChannelBootstrap connectionAddress(QuicConnectionAddress connectionAddress) {
|
||||||
|
this.connectionAddress = ObjectUtil.checkNotNull(connectionAddress, "connectionAddress");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects a {@link QuicChannel} to the remote peer and notifies the future once done.
|
||||||
|
*
|
||||||
|
* @return {@link Future} which is notified once the operation completes.
|
||||||
|
*/
|
||||||
|
public Future<QuicChannel> connect() {
|
||||||
|
return connect(parent.eventLoop().newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects a {@link QuicChannel} to the remote peer and notifies the promise once done.
|
||||||
|
*
|
||||||
|
* @param promise the {@link Promise} which is notified once the operations completes.
|
||||||
|
* @return {@link Future} which is notified once the operation completes.
|
||||||
|
|
||||||
|
*/
|
||||||
|
public Future<QuicChannel> connect(Promise<QuicChannel> promise) {
|
||||||
|
if (handler == null && streamHandler == null) {
|
||||||
|
throw new IllegalStateException("handler and streamHandler not set");
|
||||||
|
}
|
||||||
|
SocketAddress local = this.local;
|
||||||
|
if (local == null) {
|
||||||
|
local = parent.localAddress();
|
||||||
|
}
|
||||||
|
if (local == null) {
|
||||||
|
local = new InetSocketAddress(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddress remote = this.remote;
|
||||||
|
if (remote == null) {
|
||||||
|
remote = parent.remoteAddress();
|
||||||
|
}
|
||||||
|
if (remote == null) {
|
||||||
|
throw new IllegalStateException("remote not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
final QuicConnectionAddress address = connectionAddress;
|
||||||
|
QuicChannel channel = QuicheQuicChannel.forClient(parent, (InetSocketAddress) local,
|
||||||
|
(InetSocketAddress) remote,
|
||||||
|
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
|
||||||
|
|
||||||
|
Quic.setupChannel(channel, Quic.toOptionsArray(options), Quic.toAttributesArray(attrs), handler, logger);
|
||||||
|
EventLoop eventLoop = parent.eventLoop();
|
||||||
|
eventLoop.register(channel).addListener((ChannelFuture future) -> {
|
||||||
|
Throwable cause = future.cause();
|
||||||
|
if (cause != null) {
|
||||||
|
promise.setFailure(cause);
|
||||||
|
} else {
|
||||||
|
channel.connect(address).addListener(f -> {
|
||||||
|
Throwable error = f.cause();
|
||||||
|
if (error != null) {
|
||||||
|
promise.setFailure(error);
|
||||||
|
} else {
|
||||||
|
promise.setSuccess(channel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.channel.ChannelConfig;
|
||||||
|
import io.netty.channel.MessageSizeEstimator;
|
||||||
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A QUIC {@link ChannelConfig}.
|
||||||
|
*/
|
||||||
|
public interface QuicChannelConfig extends ChannelConfig {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
QuicChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setWriteSpinCount(int writeSpinCount);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setAllocator(ByteBufAllocator allocator);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setAutoRead(boolean autoRead);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setAutoClose(boolean autoClose);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator);
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ChannelOption}s specific to QUIC.
|
||||||
|
*/
|
||||||
|
public final class QuicChannelOption<T> extends ChannelOption<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to {@code true} the {@link QuicStreamChannel} will read {@link QuicStreamFrame}s and fire it through
|
||||||
|
* the pipeline, if {@code false} it will read {@link io.netty.buffer.ByteBuf} and translate the FIN flag to
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
|
public static final ChannelOption<Boolean> READ_FRAMES =
|
||||||
|
valueOf(QuicChannelOption.class, "READ_FRAMES");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable <a href="https://quiclog.github.io/internet-drafts/draft-marx-qlog-main-schema.html">qlog</a>
|
||||||
|
* for a {@link QuicChannel}.
|
||||||
|
*/
|
||||||
|
public static final ChannelOption<QLogConfiguration> QLOG = valueOf(QuicChannelOption.class, "QLOG");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use <a href="https://blog.cloudflare.com/accelerating-udp-packet-transmission-for-quic/">GSO</a>
|
||||||
|
* for QUIC packets if possible.
|
||||||
|
*/
|
||||||
|
public static final ChannelOption<SegmentedDatagramPacketAllocator> SEGMENTED_DATAGRAM_PACKET_ALLOCATOR =
|
||||||
|
valueOf(QuicChannelOption.class, "SEGMENTED_DATAGRAM_PACKET_ALLOCATOR");
|
||||||
|
|
||||||
|
@SuppressWarnings({ "deprecation" })
|
||||||
|
private QuicChannelOption() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link QuicCodecBuilder} that configures and builds a {@link ChannelHandler} that should be added to the
|
||||||
|
* {@link io.netty.channel.ChannelPipeline} of a {@code QUIC} client.
|
||||||
|
*/
|
||||||
|
public final class QuicClientCodecBuilder extends QuicCodecBuilder<QuicClientCodecBuilder> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*/
|
||||||
|
public QuicClientCodecBuilder() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuicClientCodecBuilder(QuicCodecBuilder<QuicClientCodecBuilder> builder) {
|
||||||
|
super(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicClientCodecBuilder clone() {
|
||||||
|
return new QuicClientCodecBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ChannelHandler build(QuicheConfig config,
|
||||||
|
Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||||
|
Executor sslTaskExecutor,
|
||||||
|
int localConnIdLength, FlushStrategy flushStrategy) {
|
||||||
|
return new QuicheQuicClientCodec(config, sslEngineProvider, sslTaskExecutor, localConnIdLength, flushStrategy);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
final class QuicClientSessionCache {
|
||||||
|
|
||||||
|
private static final int DEFAULT_CACHE_SIZE;
|
||||||
|
static {
|
||||||
|
// Respect the same system property as the JDK implementation to make it easy to switch between implementations.
|
||||||
|
int cacheSize = SystemPropertyUtil.getInt("javax.net.ssl.sessionCacheSize", 20480);
|
||||||
|
if (cacheSize >= 0) {
|
||||||
|
DEFAULT_CACHE_SIZE = cacheSize;
|
||||||
|
} else {
|
||||||
|
DEFAULT_CACHE_SIZE = 20480;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AtomicInteger maximumCacheSize = new AtomicInteger(DEFAULT_CACHE_SIZE);
|
||||||
|
|
||||||
|
// Let's use the same default value as OpenSSL does.
|
||||||
|
// See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_default_timeout.html
|
||||||
|
private final AtomicInteger sessionTimeout = new AtomicInteger(300);
|
||||||
|
private int sessionCounter;
|
||||||
|
|
||||||
|
private final Map<HostPort, SessionHolder> sessions =
|
||||||
|
new LinkedHashMap<HostPort, SessionHolder>() {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -7773696788135734448L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<HostPort, SessionHolder> eldest) {
|
||||||
|
int maxSize = maximumCacheSize.get();
|
||||||
|
return maxSize >= 0 && size() > maxSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void saveSession(String host, int port, long creationTime, long timeout, byte[] session, boolean isSingleUse) {
|
||||||
|
HostPort hostPort = keyFor(host, port);
|
||||||
|
if (hostPort != null) {
|
||||||
|
synchronized (sessions) {
|
||||||
|
// Mimic what OpenSSL is doing and expunge every 255 new sessions
|
||||||
|
// See https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_flush_sessions.html
|
||||||
|
if (++sessionCounter == 255) {
|
||||||
|
sessionCounter = 0;
|
||||||
|
expungeInvalidSessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions.put(hostPort, new SessionHolder(creationTime, timeout, session, isSingleUse));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for testing.
|
||||||
|
boolean hasSession(String host, int port) {
|
||||||
|
HostPort hostPort = keyFor(host, port);
|
||||||
|
if (hostPort != null) {
|
||||||
|
synchronized (sessions) {
|
||||||
|
return sessions.containsKey(hostPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getSession(String host, int port) {
|
||||||
|
HostPort hostPort = keyFor(host, port);
|
||||||
|
if (hostPort != null) {
|
||||||
|
SessionHolder sessionHolder;
|
||||||
|
synchronized (sessions) {
|
||||||
|
sessionHolder = sessions.get(hostPort);
|
||||||
|
if (sessionHolder == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (sessionHolder.isSingleUse()) {
|
||||||
|
// Remove session as it should only be re-used once.
|
||||||
|
sessions.remove(hostPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sessionHolder.isValid()) {
|
||||||
|
return sessionHolder.sessionBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSession(String host, int port) {
|
||||||
|
HostPort hostPort = keyFor(host, port);
|
||||||
|
if (hostPort != null) {
|
||||||
|
synchronized (sessions) {
|
||||||
|
sessions.remove(hostPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSessionTimeout(int seconds) {
|
||||||
|
int oldTimeout = sessionTimeout.getAndSet(seconds);
|
||||||
|
if (oldTimeout > seconds) {
|
||||||
|
// Drain the whole cache as this way we can use the ordering of the LinkedHashMap to detect early
|
||||||
|
// if there are any other sessions left that are invalid.
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSessionTimeout() {
|
||||||
|
return sessionTimeout.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSessionCacheSize(int size) {
|
||||||
|
long oldSize = maximumCacheSize.getAndSet(size);
|
||||||
|
if (oldSize > size || size == 0) {
|
||||||
|
// Just keep it simple for now and drain the whole cache.
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSessionCacheSize() {
|
||||||
|
return maximumCacheSize.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache and free all cached SSL_SESSION*.
|
||||||
|
*/
|
||||||
|
void clear() {
|
||||||
|
synchronized (sessions) {
|
||||||
|
sessions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void expungeInvalidSessions() {
|
||||||
|
assert Thread.holdsLock(sessions);
|
||||||
|
|
||||||
|
if (sessions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Iterator<Map.Entry<HostPort, SessionHolder>> iterator = sessions.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
SessionHolder sessionHolder = iterator.next().getValue();
|
||||||
|
// As we use a LinkedHashMap we can break the while loop as soon as we find a valid session.
|
||||||
|
// This is true as we always drain the cache as soon as we change the timeout to a smaller value as
|
||||||
|
// it was set before. This way its true that the insertion order matches the timeout order.
|
||||||
|
if (sessionHolder.isValid(now)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HostPort keyFor(String host, int port) {
|
||||||
|
if (host == null && port < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new HostPort(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SessionHolder {
|
||||||
|
private final long creationTime;
|
||||||
|
private final long timeout;
|
||||||
|
private final byte[] sessionBytes;
|
||||||
|
private final boolean isSingleUse;
|
||||||
|
|
||||||
|
SessionHolder(long creationTime, long timeout, byte[] session, boolean isSingleUse) {
|
||||||
|
this.creationTime = creationTime;
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.sessionBytes = session;
|
||||||
|
this.isSingleUse = isSingleUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isValid() {
|
||||||
|
return isValid(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isValid(long current) {
|
||||||
|
return current <= creationTime + timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSingleUse() {
|
||||||
|
return isSingleUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] sessionBytes() {
|
||||||
|
return sessionBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host / Port tuple used to find a session in the cache.
|
||||||
|
*/
|
||||||
|
private static final class HostPort {
|
||||||
|
private final int hash;
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
HostPort(String host, int port) {
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
// Calculate a hashCode that does ignore case.
|
||||||
|
this.hash = 31 * AsciiString.hashCode(host) + port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof HostPort)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HostPort other = (HostPort) obj;
|
||||||
|
return port == other.port && host.equalsIgnoreCase(other.host);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "HostPort{" +
|
||||||
|
"host='" + host + '\'' +
|
||||||
|
", port=" + port +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special {@link QuicClosedChannelException} which also provides extra info if the close was a result of a
|
||||||
|
* {@link QuicConnectionCloseEvent} that was triggered by the remote peer.
|
||||||
|
*/
|
||||||
|
public final class QuicClosedChannelException extends ClosedChannelException {
|
||||||
|
|
||||||
|
private final QuicConnectionCloseEvent event;
|
||||||
|
|
||||||
|
QuicClosedChannelException(QuicConnectionCloseEvent event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link QuicConnectionCloseEvent} that caused the closure or {@code null} if none was received.
|
||||||
|
*
|
||||||
|
* @return the event.
|
||||||
|
*/
|
||||||
|
public QuicConnectionCloseEvent event() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,508 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkInRange;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkPositive;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for {@code QUIC} codec builders.
|
||||||
|
*
|
||||||
|
* @param <B> the type of the {@link QuicCodecBuilder}.
|
||||||
|
*/
|
||||||
|
public abstract class QuicCodecBuilder<B extends QuicCodecBuilder<B>> {
|
||||||
|
private final boolean server;
|
||||||
|
private Boolean grease;
|
||||||
|
private Long maxIdleTimeout;
|
||||||
|
private Long maxRecvUdpPayloadSize;
|
||||||
|
private Long maxSendUdpPayloadSize;
|
||||||
|
private Long initialMaxData;
|
||||||
|
private Long initialMaxStreamDataBidiLocal;
|
||||||
|
private Long initialMaxStreamDataBidiRemote;
|
||||||
|
private Long initialMaxStreamDataUni;
|
||||||
|
private Long initialMaxStreamsBidi;
|
||||||
|
private Long initialMaxStreamsUni;
|
||||||
|
private Long ackDelayExponent;
|
||||||
|
private Long maxAckDelay;
|
||||||
|
private Boolean disableActiveMigration;
|
||||||
|
private Boolean enableHystart;
|
||||||
|
private QuicCongestionControlAlgorithm congestionControlAlgorithm;
|
||||||
|
private int localConnIdLength;
|
||||||
|
private Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
|
||||||
|
private FlushStrategy flushStrategy = FlushStrategy.DEFAULT;
|
||||||
|
private Integer recvQueueLen;
|
||||||
|
private Integer sendQueueLen;
|
||||||
|
private Long activeConnectionIdLimit;
|
||||||
|
private byte[] statelessResetToken;
|
||||||
|
|
||||||
|
private Executor sslTaskExecutor;
|
||||||
|
|
||||||
|
// package-private for testing only
|
||||||
|
int version;
|
||||||
|
|
||||||
|
QuicCodecBuilder(boolean server) {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
this.version = Quiche.QUICHE_PROTOCOL_VERSION;
|
||||||
|
this.localConnIdLength = Quiche.QUICHE_MAX_CONN_ID_LEN;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicCodecBuilder(QuicCodecBuilder<B> builder) {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
this.server = builder.server;
|
||||||
|
this.grease = builder.grease;
|
||||||
|
this.maxIdleTimeout = builder.maxIdleTimeout;
|
||||||
|
this.maxRecvUdpPayloadSize = builder.maxRecvUdpPayloadSize;
|
||||||
|
this.maxSendUdpPayloadSize = builder.maxSendUdpPayloadSize;
|
||||||
|
this.initialMaxData = builder.initialMaxData;
|
||||||
|
this.initialMaxStreamDataBidiLocal = builder.initialMaxStreamDataBidiLocal;
|
||||||
|
this.initialMaxStreamDataBidiRemote = builder.initialMaxStreamDataBidiRemote;
|
||||||
|
this.initialMaxStreamDataUni = builder.initialMaxStreamDataUni;
|
||||||
|
this.initialMaxStreamsBidi = builder.initialMaxStreamsBidi;
|
||||||
|
this.initialMaxStreamsUni = builder.initialMaxStreamsUni;
|
||||||
|
this.ackDelayExponent = builder.ackDelayExponent;
|
||||||
|
this.maxAckDelay = builder.maxAckDelay;
|
||||||
|
this.disableActiveMigration = builder.disableActiveMigration;
|
||||||
|
this.enableHystart = builder.enableHystart;
|
||||||
|
this.congestionControlAlgorithm = builder.congestionControlAlgorithm;
|
||||||
|
this.localConnIdLength = builder.localConnIdLength;
|
||||||
|
this.sslEngineProvider = builder.sslEngineProvider;
|
||||||
|
this.flushStrategy = builder.flushStrategy;
|
||||||
|
this.recvQueueLen = builder.recvQueueLen;
|
||||||
|
this.sendQueueLen = builder.sendQueueLen;
|
||||||
|
this.activeConnectionIdLimit = builder.activeConnectionIdLimit;
|
||||||
|
this.statelessResetToken = builder.statelessResetToken;
|
||||||
|
this.sslTaskExecutor = builder.sslTaskExecutor;
|
||||||
|
this.version = builder.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns itself.
|
||||||
|
*
|
||||||
|
* @return itself.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected final B self() {
|
||||||
|
return (B) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link FlushStrategy} that will be used to detect when an automatic flush
|
||||||
|
* should happen.
|
||||||
|
*
|
||||||
|
* @param flushStrategy the strategy to use.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B flushStrategy(FlushStrategy flushStrategy) {
|
||||||
|
this.flushStrategy = Objects.requireNonNull(flushStrategy, "flushStrategy");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the congestion control algorithm to use.
|
||||||
|
*
|
||||||
|
* The default is {@link QuicCongestionControlAlgorithm#CUBIC}.
|
||||||
|
*
|
||||||
|
* @param congestionControlAlgorithm the {@link QuicCongestionControlAlgorithm} to use.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B congestionControlAlgorithm(QuicCongestionControlAlgorithm congestionControlAlgorithm) {
|
||||||
|
this.congestionControlAlgorithm = congestionControlAlgorithm;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if <a href="https://tools.ietf.org/html/draft-thomson-quic-bit-grease-00">greasing</a> should be enabled
|
||||||
|
* or not.
|
||||||
|
*
|
||||||
|
* The default value is {@code true}.
|
||||||
|
*
|
||||||
|
* @param enable {@code true} if enabled, {@code false} otherwise.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B grease(boolean enable) {
|
||||||
|
grease = enable;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_idle_timeout">
|
||||||
|
* set_max_idle_timeout</a>.
|
||||||
|
*
|
||||||
|
* The default value is infinite, that is, no timeout is used.
|
||||||
|
*
|
||||||
|
* @param amount the maximum idle timeout.
|
||||||
|
* @param unit the {@link TimeUnit}.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B maxIdleTimeout(long amount, TimeUnit unit) {
|
||||||
|
this.maxIdleTimeout = unit.toMillis(checkPositiveOrZero(amount, "amount"));
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/35e38d987c1e53ef2bd5f23b754c50162b5adac8/src/lib.rs#L669">
|
||||||
|
* set_max_send_udp_payload_size</a>.
|
||||||
|
*
|
||||||
|
* The default and minimum value is 1200.
|
||||||
|
*
|
||||||
|
* @param size the maximum payload size that is advertised to the remote peer.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B maxSendUdpPayloadSize(long size) {
|
||||||
|
this.maxSendUdpPayloadSize = checkPositiveOrZero(size, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/35e38d987c1e53ef2bd5f23b754c50162b5adac8/src/lib.rs#L662">
|
||||||
|
* set_max_recv_udp_payload_size</a>.
|
||||||
|
*
|
||||||
|
* The default value is 65527.
|
||||||
|
*
|
||||||
|
* @param size the maximum payload size that is advertised to the remote peer.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B maxRecvUdpPayloadSize(long size) {
|
||||||
|
this.maxRecvUdpPayloadSize = checkPositiveOrZero(size, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_data">
|
||||||
|
* set_initial_max_data</a>.
|
||||||
|
*
|
||||||
|
* The default value is 0.
|
||||||
|
*
|
||||||
|
* @param value the initial maximum data limit.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B initialMaxData(long value) {
|
||||||
|
this.initialMaxData = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local">
|
||||||
|
* set_initial_max_stream_data_bidi_local</a>.
|
||||||
|
*
|
||||||
|
* The default value is 0.
|
||||||
|
*
|
||||||
|
* @param value the initial maximum data limit for local bidirectional streams.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B initialMaxStreamDataBidirectionalLocal(long value) {
|
||||||
|
this.initialMaxStreamDataBidiLocal = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote">
|
||||||
|
* set_initial_max_stream_data_bidi_remote</a>.
|
||||||
|
*
|
||||||
|
* The default value is 0.
|
||||||
|
*
|
||||||
|
* @param value the initial maximum data limit for remote bidirectional streams.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B initialMaxStreamDataBidirectionalRemote(long value) {
|
||||||
|
this.initialMaxStreamDataBidiRemote = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_uni">
|
||||||
|
* set_initial_max_stream_data_uni</a>.
|
||||||
|
*
|
||||||
|
* The default value is 0.
|
||||||
|
*
|
||||||
|
* @param value the initial maximum data limit for unidirectional streams.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B initialMaxStreamDataUnidirectional(long value) {
|
||||||
|
this.initialMaxStreamDataUni = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_bidi">
|
||||||
|
* set_initial_max_streams_bidi</a>.
|
||||||
|
*
|
||||||
|
* The default value is 0.
|
||||||
|
*
|
||||||
|
* @param value the initial maximum stream limit for bidirectional streams.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B initialMaxStreamsBidirectional(long value) {
|
||||||
|
this.initialMaxStreamsBidi = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_uni">
|
||||||
|
* set_initial_max_streams_uni</a>.
|
||||||
|
*
|
||||||
|
* The default value is 0.
|
||||||
|
*
|
||||||
|
* @param value the initial maximum stream limit for unidirectional streams.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B initialMaxStreamsUnidirectional(long value) {
|
||||||
|
this.initialMaxStreamsUni = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_ack_delay_exponent">
|
||||||
|
* set_ack_delay_exponent</a>.
|
||||||
|
*
|
||||||
|
* The default value is 3.
|
||||||
|
*
|
||||||
|
* @param value the delay exponent used for ACKs.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B ackDelayExponent(long value) {
|
||||||
|
this.ackDelayExponent = checkPositiveOrZero(value, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_ack_delay">
|
||||||
|
* set_max_ack_delay</a>.
|
||||||
|
*
|
||||||
|
* The default value is 25 milliseconds.
|
||||||
|
*
|
||||||
|
* @param amount the max ack delay.
|
||||||
|
* @param unit the {@link TimeUnit}.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B maxAckDelay(long amount, TimeUnit unit) {
|
||||||
|
this.maxAckDelay = unit.toMillis(checkPositiveOrZero(amount, "amount"));
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_disable_active_migration">
|
||||||
|
* set_disable_active_migration</a>.
|
||||||
|
*
|
||||||
|
* The default value is {@code true}.
|
||||||
|
*
|
||||||
|
* @param enable {@code true} if migration should be enabled, {@code false} otherwise.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B activeMigration(boolean enable) {
|
||||||
|
this.disableActiveMigration = !enable;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.enable_hystart">
|
||||||
|
* enable_hystart</a>.
|
||||||
|
*
|
||||||
|
* The default value is {@code true}.
|
||||||
|
*
|
||||||
|
* @param enable {@code true} if Hystart should be enabled.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B hystart(boolean enable) {
|
||||||
|
this.enableHystart = enable;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the local connection id length that is used.
|
||||||
|
*
|
||||||
|
* The default is 20, which is also the maximum that is supported.
|
||||||
|
*
|
||||||
|
* @param value the length of local generated connections ids.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B localConnectionIdLength(int value) {
|
||||||
|
this.localConnIdLength = checkInRange(value, 0, Quiche.QUICHE_MAX_CONN_ID_LEN, "value");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to configure the {@code QUIC version} that should be used.
|
||||||
|
*
|
||||||
|
* The default value is the latest supported version by the underlying library.
|
||||||
|
*
|
||||||
|
* @param version the {@code QUIC version} to use.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B version(int version) {
|
||||||
|
this.version = version;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">
|
||||||
|
* Datagram support.</a>
|
||||||
|
* @param recvQueueLen the RECV queue length.
|
||||||
|
* @param sendQueueLen the SEND queue length.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B datagram(int recvQueueLen, int sendQueueLen) {
|
||||||
|
checkPositive(recvQueueLen, "recvQueueLen");
|
||||||
|
checkPositive(sendQueueLen, "sendQueueLen");
|
||||||
|
|
||||||
|
this.recvQueueLen = recvQueueLen;
|
||||||
|
this.sendQueueLen = sendQueueLen;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link QuicSslContext} that will be used to create {@link QuicSslEngine}s for {@link QuicChannel}s.
|
||||||
|
*
|
||||||
|
* If you need a more flexible way to provide {@link QuicSslEngine}s use {@link #sslEngineProvider(Function)}.
|
||||||
|
*
|
||||||
|
* @param sslContext the context.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B sslContext(QuicSslContext sslContext) {
|
||||||
|
if (server != sslContext.isServer()) {
|
||||||
|
throw new IllegalArgumentException("QuicSslContext.isServer() " + sslContext.isServer()
|
||||||
|
+ " isn't supported by this builder");
|
||||||
|
}
|
||||||
|
return sslEngineProvider(q -> sslContext.newEngine(q.alloc()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Function} that will return the {@link QuicSslEngine} that should be used for the
|
||||||
|
* {@link QuicChannel}.
|
||||||
|
*
|
||||||
|
* @param sslEngineProvider the provider.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B sslEngineProvider(Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider) {
|
||||||
|
this.sslEngineProvider = sslEngineProvider;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to configure a {@link Executor} that will be used to run expensive SSL operations.
|
||||||
|
*
|
||||||
|
* @param sslTaskExecutor the {@link Executor} that will be used to offload expensive SSL operations.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B sslTaskExecutor(Executor sslTaskExecutor) {
|
||||||
|
this.sslTaskExecutor = sslTaskExecutor;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to configure the {@code active connect id limit} that should be used.
|
||||||
|
*
|
||||||
|
* @param limit the limit to use.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B activeConnectionIdLimit(long limit) {
|
||||||
|
checkPositive(limit, "limit");
|
||||||
|
|
||||||
|
this.activeConnectionIdLimit = limit;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to configure the {@code active connect id limit} that should be used.
|
||||||
|
*
|
||||||
|
* @param token the token to use.
|
||||||
|
* @return the instance itself.
|
||||||
|
*/
|
||||||
|
public final B statelessResetToken(byte[] token) {
|
||||||
|
if (token.length != 16) {
|
||||||
|
throw new IllegalArgumentException("token must be 16 bytes but was " + token.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.statelessResetToken = token.clone();
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuicheConfig createConfig() {
|
||||||
|
return new QuicheConfig(version, grease,
|
||||||
|
maxIdleTimeout, maxSendUdpPayloadSize, maxRecvUdpPayloadSize, initialMaxData,
|
||||||
|
initialMaxStreamDataBidiLocal, initialMaxStreamDataBidiRemote,
|
||||||
|
initialMaxStreamDataUni, initialMaxStreamsBidi, initialMaxStreamsUni,
|
||||||
|
ackDelayExponent, maxAckDelay, disableActiveMigration, enableHystart,
|
||||||
|
congestionControlAlgorithm, recvQueueLen, sendQueueLen, activeConnectionIdLimit, statelessResetToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the configuration before building the codec.
|
||||||
|
*/
|
||||||
|
protected void validate() {
|
||||||
|
if (sslEngineProvider == null) {
|
||||||
|
throw new IllegalStateException("sslEngineProvider can't be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the QUIC codec that should be added to the {@link io.netty.channel.ChannelPipeline} of the underlying
|
||||||
|
* {@link io.netty.channel.Channel} which is used as transport for QUIC.
|
||||||
|
*
|
||||||
|
* @return the {@link ChannelHandler} which acts as QUIC codec.
|
||||||
|
*/
|
||||||
|
public final ChannelHandler build() {
|
||||||
|
validate();
|
||||||
|
QuicheConfig config = createConfig();
|
||||||
|
try {
|
||||||
|
return build(config, sslEngineProvider, sslTaskExecutor, localConnIdLength, flushStrategy);
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
config.free();
|
||||||
|
throw cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone the builder
|
||||||
|
*
|
||||||
|
* @return the new instance that is a clone if this instance.
|
||||||
|
*/
|
||||||
|
public abstract B clone();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the QUIC codec.
|
||||||
|
*
|
||||||
|
* @param config the {@link QuicheConfig} that should be used.
|
||||||
|
* @param sslContextProvider the context provider
|
||||||
|
* @param sslTaskExecutor the {@link Executor} to use.
|
||||||
|
* @param localConnIdLength the local connection id length.
|
||||||
|
* @param flushStrategy the {@link FlushStrategy} that should be used.
|
||||||
|
* @return the {@link ChannelHandler} which acts as codec.
|
||||||
|
*/
|
||||||
|
protected abstract ChannelHandler build(QuicheConfig config,
|
||||||
|
Function<QuicChannel, ? extends QuicSslEngine> sslContextProvider,
|
||||||
|
Executor sslTaskExecutor,
|
||||||
|
int localConnIdLength, FlushStrategy flushStrategy);
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available congestion control algorithms to use.
|
||||||
|
*/
|
||||||
|
public enum QuicCongestionControlAlgorithm {
|
||||||
|
RENO,
|
||||||
|
CUBIC,
|
||||||
|
BBR
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link QuicConnectionAddress} that can be used to connect too.
|
||||||
|
*/
|
||||||
|
public final class QuicConnectionAddress extends SocketAddress {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special {@link QuicConnectionAddress} that should be used when the connection address should be generated
|
||||||
|
* and chosen on the fly.
|
||||||
|
*/
|
||||||
|
public static final QuicConnectionAddress EPHEMERAL = new QuicConnectionAddress(null, false);
|
||||||
|
|
||||||
|
private final String toStr;
|
||||||
|
|
||||||
|
// Accessed by QuicheQuicheChannel
|
||||||
|
final ByteBuffer connId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance
|
||||||
|
*
|
||||||
|
* @param connId the connection id to use.
|
||||||
|
*/
|
||||||
|
public QuicConnectionAddress(byte[] connId) {
|
||||||
|
this(ByteBuffer.wrap(connId.clone()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance
|
||||||
|
*
|
||||||
|
* @param connId the connection id to use.
|
||||||
|
*/
|
||||||
|
public QuicConnectionAddress(ByteBuffer connId) {
|
||||||
|
this(connId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuicConnectionAddress(ByteBuffer connId, boolean validate) {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
if (validate && connId.remaining() > Quiche.QUICHE_MAX_CONN_ID_LEN) {
|
||||||
|
throw new IllegalArgumentException("Connection ID can only be of max length "
|
||||||
|
+ Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||||
|
}
|
||||||
|
this.connId = connId;
|
||||||
|
if (connId == null) {
|
||||||
|
toStr = "QuicConnectionAddress{EPHEMERAL}";
|
||||||
|
} else {
|
||||||
|
ByteBuf buffer = Unpooled.wrappedBuffer(connId);
|
||||||
|
try {
|
||||||
|
toStr = "QuicConnectionAddress{" +
|
||||||
|
"connId=" + ByteBufUtil.hexDump(buffer) + '}';
|
||||||
|
} finally {
|
||||||
|
buffer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
if (this == EPHEMERAL) {
|
||||||
|
return System.identityHashCode(EPHEMERAL);
|
||||||
|
}
|
||||||
|
return Objects.hash(connId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof QuicConnectionAddress)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QuicConnectionAddress address = (QuicConnectionAddress) obj;
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (connId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return connId.equals(address.connId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a random generated {@link QuicConnectionAddress} of a given length
|
||||||
|
* that can be used to connect a {@link QuicChannel}
|
||||||
|
*
|
||||||
|
* @param length the length of the {@link QuicConnectionAddress} to generate.
|
||||||
|
* @return the generated address.
|
||||||
|
*/
|
||||||
|
public static QuicConnectionAddress random(int length) {
|
||||||
|
return new QuicConnectionAddress(QuicConnectionIdGenerator.randomGenerator().newId(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a random generated {@link QuicConnectionAddress} of maximum size
|
||||||
|
* that can be used to connect a {@link QuicChannel}
|
||||||
|
*
|
||||||
|
* @return the generated address.
|
||||||
|
*/
|
||||||
|
public static QuicConnectionAddress random() {
|
||||||
|
return random(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event that is generated if the remote peer sends a
|
||||||
|
* <a href="https://www.rfc-editor.org/rfc/rfc9000#name-connection_close-frames">CLOSE_CONNECTION frame</a>.
|
||||||
|
* This allows to inspect the various details of the cause of the close.
|
||||||
|
*/
|
||||||
|
public final class QuicConnectionCloseEvent implements QuicEvent {
|
||||||
|
|
||||||
|
final boolean applicationClose;
|
||||||
|
final int error;
|
||||||
|
final byte[] reason;
|
||||||
|
|
||||||
|
QuicConnectionCloseEvent(boolean applicationClose, int error, byte[] reason) {
|
||||||
|
this.applicationClose = applicationClose;
|
||||||
|
this.error = error;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return {@code true} if this was an application close, {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @return if this is an application close.
|
||||||
|
*/
|
||||||
|
public boolean isApplicationClose() {
|
||||||
|
return applicationClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the error that was provided for the close.
|
||||||
|
*
|
||||||
|
* @return the error.
|
||||||
|
*/
|
||||||
|
public int error() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if a <a href="https://www.rfc-editor.org/rfc/rfc9001#section-4.8">TLS error</a>
|
||||||
|
* is contained.
|
||||||
|
* @return {@code true} if this is an {@code TLS error}, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isTlsError() {
|
||||||
|
return !applicationClose && error >= 0x0100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reason for the close, which may be empty if no reason was given as part of the close.
|
||||||
|
*
|
||||||
|
* @return the reason.
|
||||||
|
*/
|
||||||
|
public byte[] reason() {
|
||||||
|
return reason.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicConnectionCloseEvent{" +
|
||||||
|
"applicationClose=" + applicationClose +
|
||||||
|
", error=" + error +
|
||||||
|
", reason=" + Arrays.toString(reason) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the contained {@code TLS error} from the {@code QUIC error}. If the given {@code QUIC error} does not
|
||||||
|
* contain a {@code TLS error} it will return {@code -1}.
|
||||||
|
*
|
||||||
|
* @param error the {@code QUIC error}
|
||||||
|
* @return the {@code TLS error} or {@code -1} if there was no {@code TLS error} contained.
|
||||||
|
*/
|
||||||
|
public static int extractTlsError(int error) {
|
||||||
|
int tlsError = error - 0x0100;
|
||||||
|
if (tlsError < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return tlsError;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new connection id instances.
|
||||||
|
*/
|
||||||
|
public interface QuicConnectionIdGenerator {
|
||||||
|
/**
|
||||||
|
* Creates a new connection id with the given length. This method may not be supported by
|
||||||
|
* a sign id generator implementation as a sign id generator should always have an input
|
||||||
|
* to sign with, otherwise this method may generate the same id which may cause some
|
||||||
|
* unpredictable issues when we use it.
|
||||||
|
*
|
||||||
|
* @param length the length of the id.
|
||||||
|
* @return the id.
|
||||||
|
*/
|
||||||
|
ByteBuffer newId(int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new connection id with the given length. The given input may be used to sign or
|
||||||
|
* seed the id, or may be ignored (depending on the implementation).
|
||||||
|
*
|
||||||
|
* @param input the input which may be used to generate the id.
|
||||||
|
* @param length the length of the id.
|
||||||
|
* @return the id.
|
||||||
|
*/
|
||||||
|
ByteBuffer newId(ByteBuffer input, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum length of a connection id.
|
||||||
|
*
|
||||||
|
* @return the maximum length of a connection id that is supported.
|
||||||
|
*/
|
||||||
|
int maxConnectionIdLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the implementation is idempotent, which means we will get the same id
|
||||||
|
* with the same input ByteBuffer. Otherwise, returns false.
|
||||||
|
*
|
||||||
|
* @return whether the implementation is idempotent.
|
||||||
|
*/
|
||||||
|
boolean isIdempotent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link QuicConnectionIdGenerator} which randomly generates new connection ids.
|
||||||
|
*
|
||||||
|
* @return a {@link QuicConnectionIdGenerator} which randomly generated ids.
|
||||||
|
*/
|
||||||
|
static QuicConnectionIdGenerator randomGenerator() {
|
||||||
|
return SecureRandomQuicConnectionIdGenerator.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link QuicConnectionIdGenerator} which generates new connection ids by signing the given input.
|
||||||
|
*
|
||||||
|
* @return a {@link QuicConnectionIdGenerator} which generates ids by signing the given input.
|
||||||
|
*/
|
||||||
|
static QuicConnectionIdGenerator signGenerator() {
|
||||||
|
return HmacSignQuicConnectionIdGenerator.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics about the {@code QUIC} connection. If unknown by the implementation it might return {@code -1} values
|
||||||
|
* for the various methods.
|
||||||
|
*/
|
||||||
|
public interface QuicConnectionStats {
|
||||||
|
/**
|
||||||
|
* @return The number of QUIC packets received on the connection.
|
||||||
|
*/
|
||||||
|
long recv();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of QUIC packets sent on this connection.
|
||||||
|
*/
|
||||||
|
long sent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of QUIC packets that were lost.
|
||||||
|
*/
|
||||||
|
long lost();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of sent QUIC packets with retransmitted data.
|
||||||
|
*/
|
||||||
|
long retrans();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of sent bytes.
|
||||||
|
*/
|
||||||
|
long sentBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of received bytes.
|
||||||
|
*/
|
||||||
|
long recvBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of bytes lost.
|
||||||
|
*/
|
||||||
|
long lostBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of stream bytes retransmitted.
|
||||||
|
*/
|
||||||
|
long streamRetransBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of known paths for the connection.
|
||||||
|
*/
|
||||||
|
long pathsCount();
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when the remote peer supports the
|
||||||
|
* <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">QUIC DATAGRAM extension.</a>
|
||||||
|
*/
|
||||||
|
public final class QuicDatagramExtensionEvent implements QuicExtensionEvent {
|
||||||
|
|
||||||
|
private final int maxLength;
|
||||||
|
|
||||||
|
QuicDatagramExtensionEvent(int maxLength) {
|
||||||
|
this.maxLength = ObjectUtil.checkPositiveOrZero(maxLength, "maxLength");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum datagram payload length the peer will accept. If you try to write bigger datagrams the write will
|
||||||
|
* fail.
|
||||||
|
*
|
||||||
|
* @return the max length.
|
||||||
|
*/
|
||||||
|
public int maxLength() {
|
||||||
|
return maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicDatagramExtensionEvent{" +
|
||||||
|
"maxLength=" + maxLength +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.collection.IntObjectHashMap;
|
||||||
|
import io.netty.util.collection.IntObjectMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All QUIC error codes identified by Quiche.
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/src/lib.rs#L335-L380">Error</a>
|
||||||
|
*/
|
||||||
|
public enum QuicError {
|
||||||
|
BUFFER_TOO_SHORT(Quiche.QUICHE_ERR_BUFFER_TOO_SHORT, "QUICHE_ERR_BUFFER_TOO_SHORT"),
|
||||||
|
UNKNOWN_VERSION(Quiche.QUICHE_ERR_UNKNOWN_VERSION, "QUICHE_ERR_UNKNOWN_VERSION"),
|
||||||
|
INVALID_FRAME(Quiche.QUICHE_ERR_INVALID_FRAME, "QUICHE_ERR_INVALID_FRAME"),
|
||||||
|
INVALID_PACKET(Quiche.QUICHE_ERR_INVALID_PACKET, "QUICHE_ERR_INVALID_PACKET"),
|
||||||
|
INVALID_STATE(Quiche.QUICHE_ERR_INVALID_STATE, "QUICHE_ERR_INVALID_STATE"),
|
||||||
|
INVALID_STREAM_STATE(Quiche.QUICHE_ERR_INVALID_STREAM_STATE, "QUICHE_ERR_INVALID_STREAM_STATE"),
|
||||||
|
INVALID_TRANSPORT_PARAM(Quiche.QUICHE_ERR_INVALID_TRANSPORT_PARAM, "QUICHE_ERR_INVALID_TRANSPORT_PARAM"),
|
||||||
|
CRYPTO_FAIL(Quiche.QUICHE_ERR_CRYPTO_FAIL, "QUICHE_ERR_CRYPTO_FAIL"),
|
||||||
|
TLS_FAIL(Quiche.QUICHE_ERR_TLS_FAIL, "QUICHE_ERR_TLS_FAIL"),
|
||||||
|
FLOW_CONTROL(Quiche.QUICHE_ERR_FLOW_CONTROL, "QUICHE_ERR_FLOW_CONTROL"),
|
||||||
|
STREAM_LIMIT(Quiche.QUICHE_ERR_STREAM_LIMIT, "QUICHE_ERR_STREAM_LIMIT"),
|
||||||
|
FINAL_SIZE(Quiche.QUICHE_ERR_FINAL_SIZE, "QUICHE_ERR_FINAL_SIZE"),
|
||||||
|
CONGESTION_CONTROL(Quiche.QUICHE_ERR_CONGESTION_CONTROL, "QUICHE_ERR_CONGESTION_CONTROL"),
|
||||||
|
STREAM_RESET(Quiche.QUICHE_ERR_STREAM_RESET, "STREAM_RESET"),
|
||||||
|
STREAM_STOPPED(Quiche.QUICHE_ERR_STREAM_STOPPED, "STREAM_STOPPED"),
|
||||||
|
ID_LIMIT(Quiche.QUICHE_ERR_ID_LIMIT, "ID_LIMIT"),
|
||||||
|
QUT_OF_IDENTIFIERS(Quiche.QUICHE_ERR_OUT_OF_IDENTIFIERS, "OUT_OF_IDENTIFIERS"),
|
||||||
|
KEY_UPDATE(Quiche.QUICHE_ERR_KEY_UPDATE, "KEY_UPDATE");
|
||||||
|
|
||||||
|
private static final IntObjectMap<QuicError> ERROR_MAP = new IntObjectHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (QuicError errorCode : QuicError.values()) {
|
||||||
|
ERROR_MAP.put(errorCode.code(), errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
QuicError(int code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String message() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return String.format("QuicError{code=%d, message=%s}", code, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QuicError valueOf(int code) {
|
||||||
|
final QuicError errorCode = ERROR_MAP.get(code);
|
||||||
|
if (errorCode == null) {
|
||||||
|
throw new IllegalArgumentException("unknown " + QuicError.class.getSimpleName() + " code: " + code);
|
||||||
|
}
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for events that will be passed through the {@link io.netty.channel.ChannelPipeline} via
|
||||||
|
* {@link io.netty.channel.ChannelPipeline#fireUserEventTriggered(Object)} to notify the user about {@code QUIC}
|
||||||
|
* specific events.
|
||||||
|
*/
|
||||||
|
public interface QuicEvent { }
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception produced while processing {@code QUIC}.
|
||||||
|
*/
|
||||||
|
public final class QuicException extends IOException {
|
||||||
|
|
||||||
|
private final QuicError error;
|
||||||
|
|
||||||
|
QuicException(QuicError error) {
|
||||||
|
super(error.message());
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link QuicError} which was the cause of the {@link QuicException}.
|
||||||
|
*
|
||||||
|
* @return the {@link QuicError} that caused this {@link QuicException}.
|
||||||
|
*/
|
||||||
|
public QuicError error() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for events that will be passed through the {@link io.netty.channel.ChannelPipeline} via
|
||||||
|
* {@link io.netty.channel.ChannelPipeline#fireUserEventTriggered(Object)} to notify the user about supported
|
||||||
|
* QUIC extensions by the remote peer.
|
||||||
|
*/
|
||||||
|
public interface QuicExtensionEvent extends QuicEvent { }
|
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.quic.Quiche.allocateNativeOrder;
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the QUIC packet header and notifies a callback once parsing was successful.
|
||||||
|
*
|
||||||
|
* Once the parser is not needed anymore the user needs to call {@link #close()} to ensure all resources are
|
||||||
|
* released. Failed to do so may lead to memory leaks.
|
||||||
|
*
|
||||||
|
* This class can be used for advanced use-cases. Usually you want to just use {@link QuicClientCodecBuilder} or
|
||||||
|
* {@link QuicServerCodecBuilder}.
|
||||||
|
*/
|
||||||
|
public final class QuicHeaderParser implements AutoCloseable {
|
||||||
|
private final int maxTokenLength;
|
||||||
|
private final int localConnectionIdLength;
|
||||||
|
private final ByteBuf versionBuffer;
|
||||||
|
private final ByteBuf typeBuffer;
|
||||||
|
private final ByteBuf scidLenBuffer;
|
||||||
|
private final ByteBuf scidBuffer;
|
||||||
|
private final ByteBuf dcidLenBuffer;
|
||||||
|
private final ByteBuf dcidBuffer;
|
||||||
|
private final ByteBuf tokenBuffer;
|
||||||
|
private final ByteBuf tokenLenBuffer;
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
public QuicHeaderParser(int maxTokenLength, int localConnectionIdLength) {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
this.maxTokenLength = checkPositiveOrZero(maxTokenLength, "maxTokenLength");
|
||||||
|
this.localConnectionIdLength = checkPositiveOrZero(localConnectionIdLength, "localConnectionIdLength");
|
||||||
|
// Allocate the buffer from which we read primative values like integer/long with native order to ensure
|
||||||
|
// we read the right value.
|
||||||
|
versionBuffer = allocateNativeOrder(Integer.BYTES);
|
||||||
|
typeBuffer = allocateNativeOrder(Byte.BYTES);
|
||||||
|
scidLenBuffer = allocateNativeOrder(Integer.BYTES);
|
||||||
|
dcidLenBuffer = allocateNativeOrder(Integer.BYTES);
|
||||||
|
tokenLenBuffer = allocateNativeOrder(Integer.BYTES);
|
||||||
|
|
||||||
|
// Now allocate the buffers that dont need native ordering and so will be cheaper to access when we slice into
|
||||||
|
// these or obtain a view into these via internalNioBuffer(...).
|
||||||
|
scidBuffer = Unpooled.directBuffer(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||||
|
dcidBuffer = Unpooled.directBuffer(Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||||
|
tokenBuffer = Unpooled.directBuffer(maxTokenLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (!closed) {
|
||||||
|
closed = true;
|
||||||
|
versionBuffer.release();
|
||||||
|
typeBuffer.release();
|
||||||
|
scidBuffer.release();
|
||||||
|
scidLenBuffer.release();
|
||||||
|
dcidBuffer.release();
|
||||||
|
dcidLenBuffer.release();
|
||||||
|
tokenLenBuffer.release();
|
||||||
|
tokenBuffer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a QUIC packet and extract the header values out of it. This method takes no ownership of the packet itself
|
||||||
|
* which means the caller of this method is expected to call {@link ByteBuf#release()} once the packet is not needed
|
||||||
|
* anymore.
|
||||||
|
*
|
||||||
|
* @param sender the sender of the packet. This is directly passed to the {@link QuicHeaderProcessor} once
|
||||||
|
* parsing was successful.
|
||||||
|
* @param recipient the recipient of the packet.This is directly passed to the {@link QuicHeaderProcessor} once
|
||||||
|
* parsing was successful.
|
||||||
|
* @param packet raw QUIC packet itself. The ownership of the packet is not transferred. This is directly
|
||||||
|
* passed to the {@link QuicHeaderProcessor} once parsing was successful.
|
||||||
|
* @param callback the {@link QuicHeaderProcessor} that is called once a QUIC packet could be parsed and all
|
||||||
|
* the header values be extracted.
|
||||||
|
* @throws Exception thrown if we couldn't parse the header or if the {@link QuicHeaderProcessor} throws an
|
||||||
|
* exception.
|
||||||
|
*/
|
||||||
|
public void parse(InetSocketAddress sender,
|
||||||
|
InetSocketAddress recipient, ByteBuf packet, QuicHeaderProcessor callback) throws Exception {
|
||||||
|
if (closed) {
|
||||||
|
throw new IllegalStateException(QuicHeaderParser.class.getSimpleName() + " is already closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set various len values so quiche_header_info can make use of these.
|
||||||
|
scidLenBuffer.setInt(0, Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||||
|
dcidLenBuffer.setInt(0, Quiche.QUICHE_MAX_CONN_ID_LEN);
|
||||||
|
tokenLenBuffer.setInt(0, maxTokenLength);
|
||||||
|
|
||||||
|
int res = Quiche.quiche_header_info(
|
||||||
|
Quiche.readerMemoryAddress(packet), packet.readableBytes(),
|
||||||
|
localConnectionIdLength,
|
||||||
|
Quiche.memoryAddress(versionBuffer, 0, versionBuffer.capacity()),
|
||||||
|
Quiche.memoryAddress(typeBuffer, 0, typeBuffer.capacity()),
|
||||||
|
Quiche.memoryAddress(scidBuffer, 0, scidBuffer.capacity()),
|
||||||
|
Quiche.memoryAddress(scidLenBuffer, 0, scidLenBuffer.capacity()),
|
||||||
|
Quiche.memoryAddress(dcidBuffer, 0, dcidBuffer.capacity()),
|
||||||
|
Quiche.memoryAddress(dcidLenBuffer, 0, dcidLenBuffer.capacity()),
|
||||||
|
Quiche.memoryAddress(tokenBuffer, 0, tokenBuffer.capacity()),
|
||||||
|
Quiche.writerMemoryAddress(tokenLenBuffer));
|
||||||
|
if (res >= 0) {
|
||||||
|
int version = versionBuffer.getInt(0);
|
||||||
|
byte type = typeBuffer.getByte(0);
|
||||||
|
int scidLen = scidLenBuffer.getInt(0);
|
||||||
|
int dcidLen = dcidLenBuffer.getInt(0);
|
||||||
|
int tokenLen = tokenLenBuffer.getInt(0);
|
||||||
|
|
||||||
|
callback.process(sender, recipient, packet, QuicPacketType.of(type), version,
|
||||||
|
scidBuffer.setIndex(0, scidLen),
|
||||||
|
dcidBuffer.setIndex(0, dcidLen),
|
||||||
|
tokenBuffer.setIndex(0, tokenLen));
|
||||||
|
} else {
|
||||||
|
throw Quiche.newException(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a QUIC packet and its header could be parsed.
|
||||||
|
*/
|
||||||
|
public interface QuicHeaderProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a QUIC packet header was parsed.
|
||||||
|
*
|
||||||
|
* @param sender the sender of the QUIC packet.
|
||||||
|
* @param recipient the recipient of the QUIC packet.
|
||||||
|
* @param packet the raw QUIC packet. The ownership is not transferred, which means you will need to call
|
||||||
|
* {@link ByteBuf#retain()} on it if you want to keep a reference after this method
|
||||||
|
* returns.
|
||||||
|
* @param type the type of the packet.
|
||||||
|
* @param version the version of the packet.
|
||||||
|
* @param scid the source connection id. The ownership is not transferred and its generally not allowed
|
||||||
|
* to hold any references to this buffer outside of the method as it will be re-used.
|
||||||
|
* @param dcid the destination connection id. The ownership is not transferred and its generally not
|
||||||
|
* allowed to hold any references to this buffer outside of the method as it will be
|
||||||
|
* re-used.
|
||||||
|
* @param token the token.The ownership is not transferred and its generally not allowed
|
||||||
|
* to hold any references to this buffer outside of the method as it will be re-used.
|
||||||
|
* @throws Exception throws if an error happens during processing.
|
||||||
|
*/
|
||||||
|
void process(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf packet,
|
||||||
|
QuicPacketType type, int version, ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the
|
||||||
|
* <a href="https://quicwg.org/base-drafts/rfc9000.html#name-packets-and-frames">QUIC packet</a>.
|
||||||
|
*/
|
||||||
|
public enum QuicPacketType {
|
||||||
|
/**
|
||||||
|
* Initial packet.
|
||||||
|
*/
|
||||||
|
INITIAL((byte) 1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry packet.
|
||||||
|
*/
|
||||||
|
RETRY((byte) 2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handshake packet.
|
||||||
|
*/
|
||||||
|
HANDSHAKE((byte) 3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 0-RTT packet.
|
||||||
|
*/
|
||||||
|
ZERO_RTT((byte) 4),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1-RTT short header packet.
|
||||||
|
*/
|
||||||
|
SHORT((byte) 5),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version negotiation packet.
|
||||||
|
*/
|
||||||
|
VERSION_NEGOTIATION((byte) 6);
|
||||||
|
|
||||||
|
final byte type;
|
||||||
|
|
||||||
|
QuicPacketType(byte type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link QuicPacketType} for the given byte.
|
||||||
|
*
|
||||||
|
* @param type the byte that represent the type.
|
||||||
|
* @return the {@link QuicPacketType}.
|
||||||
|
*/
|
||||||
|
static QuicPacketType of(byte type) {
|
||||||
|
switch(type) {
|
||||||
|
case 1:
|
||||||
|
return INITIAL;
|
||||||
|
case 2:
|
||||||
|
return RETRY;
|
||||||
|
case 3:
|
||||||
|
return HANDSHAKE;
|
||||||
|
case 4:
|
||||||
|
return ZERO_RTT;
|
||||||
|
case 5:
|
||||||
|
return SHORT;
|
||||||
|
case 6:
|
||||||
|
return VERSION_NEGOTIATION;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown QUIC packet type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A network path specific {@link QuicEvent}.
|
||||||
|
*/
|
||||||
|
public abstract class QuicPathEvent implements QuicEvent {
|
||||||
|
|
||||||
|
private final InetSocketAddress local;
|
||||||
|
private final InetSocketAddress remote;
|
||||||
|
|
||||||
|
QuicPathEvent(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
this.local = requireNonNull(local, "local");
|
||||||
|
this.remote = requireNonNull(remote, "remote");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local address of the network path.
|
||||||
|
*
|
||||||
|
* @return local
|
||||||
|
*/
|
||||||
|
public InetSocketAddress local() {
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote address of the network path.
|
||||||
|
*
|
||||||
|
* @return local
|
||||||
|
*/
|
||||||
|
public InetSocketAddress remote() {
|
||||||
|
return remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent{" +
|
||||||
|
"local=" + local +
|
||||||
|
", remote=" + remote +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicPathEvent that = (QuicPathEvent) o;
|
||||||
|
if (!Objects.equals(local, that.local)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Objects.equals(remote, that.remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = local != null ? local.hashCode() : 0;
|
||||||
|
result = 31 * result + (remote != null ? remote.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class New extends QuicPathEvent {
|
||||||
|
/**
|
||||||
|
* A new network path (local address, remote address) has been seen on a received packet.
|
||||||
|
* Note that this event is only triggered for servers, as the client is responsible from initiating new paths.
|
||||||
|
* The application may then probe this new path, if desired.
|
||||||
|
*
|
||||||
|
* @param local local address.
|
||||||
|
* @param remote remote address.
|
||||||
|
*/
|
||||||
|
public New(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
super(local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent.New{" +
|
||||||
|
"local=" + local() +
|
||||||
|
", remote=" + remote() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Validated extends QuicPathEvent {
|
||||||
|
/**
|
||||||
|
* The related network path between local and remote has been validated.
|
||||||
|
*
|
||||||
|
* @param local local address.
|
||||||
|
* @param remote remote address.
|
||||||
|
*/
|
||||||
|
public Validated(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
super(local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent.Validated{" +
|
||||||
|
"local=" + local() +
|
||||||
|
", remote=" + remote() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class FailedValidation extends QuicPathEvent {
|
||||||
|
/**
|
||||||
|
* The related network path between local and remote failed to be validated.
|
||||||
|
* This network path will not be used anymore, unless the application requests probing this path again.
|
||||||
|
*
|
||||||
|
* @param local local address.
|
||||||
|
* @param remote remote address.
|
||||||
|
*/
|
||||||
|
public FailedValidation(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
super(local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent.FailedValidation{" +
|
||||||
|
"local=" + local() +
|
||||||
|
", remote=" + remote() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Closed extends QuicPathEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The related network path between local and remote has been closed and is now unusable on this connection.
|
||||||
|
*
|
||||||
|
* @param local local address.
|
||||||
|
* @param remote remote address.
|
||||||
|
*/
|
||||||
|
public Closed(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
super(local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent.Closed{" +
|
||||||
|
"local=" + local() +
|
||||||
|
", remote=" + remote() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ReusedSourceConnectionId extends QuicPathEvent {
|
||||||
|
private final long seq;
|
||||||
|
private final InetSocketAddress oldLocal;
|
||||||
|
private final InetSocketAddress oldRemote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stack observes that the Source Connection ID with the given sequence number,
|
||||||
|
* initially used by the peer over the first pair of addresses, is now reused over
|
||||||
|
* the second pair of addresses.
|
||||||
|
*
|
||||||
|
* @param seq sequence number
|
||||||
|
* @param oldLocal old local address.
|
||||||
|
* @param oldRemote old remote address.
|
||||||
|
* @param local local address.
|
||||||
|
* @param remote remote address.
|
||||||
|
*/
|
||||||
|
public ReusedSourceConnectionId(long seq, InetSocketAddress oldLocal, InetSocketAddress oldRemote,
|
||||||
|
InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
super(local, remote);
|
||||||
|
this.seq = seq;
|
||||||
|
this.oldLocal = requireNonNull(oldLocal, "oldLocal");
|
||||||
|
this.oldRemote = requireNonNull(oldRemote, "oldRemote");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source connection id sequence number.
|
||||||
|
*
|
||||||
|
* @return sequence number
|
||||||
|
*/
|
||||||
|
public long seq() {
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The old local address of the network path.
|
||||||
|
*
|
||||||
|
* @return local
|
||||||
|
*/
|
||||||
|
public InetSocketAddress oldLocal() {
|
||||||
|
return oldLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The old remote address of the network path.
|
||||||
|
*
|
||||||
|
* @return local
|
||||||
|
*/
|
||||||
|
public InetSocketAddress oldRemote() {
|
||||||
|
return oldRemote;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!super.equals(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReusedSourceConnectionId that = (ReusedSourceConnectionId) o;
|
||||||
|
|
||||||
|
if (seq != that.seq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(oldLocal, that.oldLocal)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Objects.equals(oldRemote, that.oldRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = 31 * result + (int) (seq ^ (seq >>> 32));
|
||||||
|
result = 31 * result + (oldLocal != null ? oldLocal.hashCode() : 0);
|
||||||
|
result = 31 * result + (oldRemote != null ? oldRemote.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent.ReusedSourceConnectionId{" +
|
||||||
|
"seq=" + seq +
|
||||||
|
", oldLocal=" + oldLocal +
|
||||||
|
", oldRemote=" + oldRemote +
|
||||||
|
", local=" + local() +
|
||||||
|
", remote=" + remote() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class PeerMigrated extends QuicPathEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection observed that the remote migrated over the network path denoted by the pair of addresses,
|
||||||
|
* i.e., non-probing packets have been received on this network path. This is a server side only event.
|
||||||
|
* Note that this event is only raised if the path has been validated.
|
||||||
|
*
|
||||||
|
* @param local local address.
|
||||||
|
* @param remote remote address.
|
||||||
|
*/
|
||||||
|
public PeerMigrated(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
super(local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicPathEvent.PeerMigrated{" +
|
||||||
|
"local=" + local() +
|
||||||
|
", remote=" + remote() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate
|
||||||
|
* <a href="https://www.ietf.org/archive/id/draft-ietf-quic-transport-29.html#name-calculating-a-stateless-res">
|
||||||
|
* stateless reset tokens</a> to use.
|
||||||
|
*/
|
||||||
|
public interface QuicResetTokenGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a reset token to use for the given connection id. The returned token MUST be of length 16.
|
||||||
|
* @param cid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ByteBuffer newResetToken(ByteBuffer cid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a {@link QuicResetTokenGenerator} which generates new reset tokens by signing the given input.
|
||||||
|
*
|
||||||
|
* @return a {@link QuicResetTokenGenerator} which generates new reset tokens by signing the given input.
|
||||||
|
*/
|
||||||
|
static QuicResetTokenGenerator signGenerator() {
|
||||||
|
return HmacSignQuicResetTokenGenerator.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link QuicCodecBuilder} that configures and builds a {@link ChannelHandler} that should be added to the
|
||||||
|
* {@link io.netty.channel.ChannelPipeline} of a {@code QUIC} server.
|
||||||
|
*/
|
||||||
|
public final class QuicServerCodecBuilder extends QuicCodecBuilder<QuicServerCodecBuilder> {
|
||||||
|
// The order in which ChannelOptions are applied is important they may depend on each other for validation
|
||||||
|
// purposes.
|
||||||
|
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
|
||||||
|
private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
|
||||||
|
private final Map<ChannelOption<?>, Object> streamOptions = new LinkedHashMap<>();
|
||||||
|
private final Map<AttributeKey<?>, Object> streamAttrs = new HashMap<>();
|
||||||
|
private ChannelHandler handler;
|
||||||
|
private ChannelHandler streamHandler;
|
||||||
|
private QuicConnectionIdGenerator connectionIdAddressGenerator;
|
||||||
|
private QuicTokenHandler tokenHandler;
|
||||||
|
private QuicResetTokenGenerator resetTokenGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*/
|
||||||
|
public QuicServerCodecBuilder() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuicServerCodecBuilder(QuicServerCodecBuilder builder) {
|
||||||
|
super(builder);
|
||||||
|
options.putAll(builder.options);
|
||||||
|
attrs.putAll(builder.attrs);
|
||||||
|
streamOptions.putAll(builder.streamOptions);
|
||||||
|
streamAttrs.putAll(builder.streamAttrs);
|
||||||
|
handler = builder.handler;
|
||||||
|
streamHandler = builder.streamHandler;
|
||||||
|
connectionIdAddressGenerator = builder.connectionIdAddressGenerator;
|
||||||
|
tokenHandler = builder.tokenHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicServerCodecBuilder clone() {
|
||||||
|
return new QuicServerCodecBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicChannel} instances once they got
|
||||||
|
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||||
|
*
|
||||||
|
* @param option the {@link ChannelOption} to apply to the {@link QuicChannel}.
|
||||||
|
* @param value the value of the option.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicServerCodecBuilder option(ChannelOption<T> option, T value) {
|
||||||
|
Quic.updateOptions(options, option, value);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify an initial attribute of the newly created {@link QuicChannel}. If the {@code value} is
|
||||||
|
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||||
|
*
|
||||||
|
* @param key the {@link AttributeKey} to apply to the {@link QuicChannel}.
|
||||||
|
* @param value the value of the attribute.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicServerCodecBuilder attr(AttributeKey<T> key, T value) {
|
||||||
|
Quic.updateAttributes(attrs, key, value);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicChannel} once created.
|
||||||
|
*
|
||||||
|
* @param handler the {@link ChannelHandler} that is added to the {@link QuicChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline}.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicServerCodecBuilder handler(ChannelHandler handler) {
|
||||||
|
this.handler = ObjectUtil.checkNotNull(handler, "handler");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||||
|
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||||
|
*
|
||||||
|
* @param option the {@link ChannelOption} to apply to the {@link QuicStreamChannel}s.
|
||||||
|
* @param value the value of the option.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicServerCodecBuilder streamOption(ChannelOption<T> option, T value) {
|
||||||
|
Quic.updateOptions(streamOptions, option, value);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||||
|
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||||
|
*
|
||||||
|
* @param key the {@link AttributeKey} to apply to the {@link QuicStreamChannel}s.
|
||||||
|
* @param value the value of the attribute.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicServerCodecBuilder streamAttr(AttributeKey<T> key, T value) {
|
||||||
|
Quic.updateAttributes(streamAttrs, key, value);
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicStreamChannel} once created.
|
||||||
|
*
|
||||||
|
* @param streamHandler the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline}.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicServerCodecBuilder streamHandler(ChannelHandler streamHandler) {
|
||||||
|
this.streamHandler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link QuicConnectionIdGenerator} to use.
|
||||||
|
*
|
||||||
|
* @param connectionIdAddressGenerator the {@link QuicConnectionIdGenerator} to use.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicServerCodecBuilder connectionIdAddressGenerator(
|
||||||
|
QuicConnectionIdGenerator connectionIdAddressGenerator) {
|
||||||
|
this.connectionIdAddressGenerator = connectionIdAddressGenerator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link QuicTokenHandler} that is used to generate and validate tokens or
|
||||||
|
* {@code null} if no tokens should be used at all.
|
||||||
|
*
|
||||||
|
* @param tokenHandler the {@link QuicTokenHandler} to use.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicServerCodecBuilder tokenHandler(QuicTokenHandler tokenHandler) {
|
||||||
|
this.tokenHandler = tokenHandler;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link QuicResetTokenGenerator} that is used to generate stateless reset tokens or
|
||||||
|
* {@code null} if the default should be used.
|
||||||
|
*
|
||||||
|
* @param resetTokenGenerator the {@link QuicResetTokenGenerator} to use.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicServerCodecBuilder resetTokenGenerator(QuicResetTokenGenerator resetTokenGenerator) {
|
||||||
|
this.resetTokenGenerator = resetTokenGenerator;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void validate() {
|
||||||
|
super.validate();
|
||||||
|
if (handler == null && streamHandler == null) {
|
||||||
|
throw new IllegalStateException("handler and streamHandler not set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ChannelHandler build(QuicheConfig config,
|
||||||
|
Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||||
|
Executor sslTaskExecutor,
|
||||||
|
int localConnIdLength, FlushStrategy flushStrategy) {
|
||||||
|
validate();
|
||||||
|
QuicTokenHandler tokenHandler = this.tokenHandler;
|
||||||
|
if (tokenHandler == null) {
|
||||||
|
tokenHandler = NoQuicTokenHandler.INSTANCE;
|
||||||
|
}
|
||||||
|
QuicConnectionIdGenerator generator = connectionIdAddressGenerator;
|
||||||
|
if (generator == null) {
|
||||||
|
generator = QuicConnectionIdGenerator.signGenerator();
|
||||||
|
}
|
||||||
|
QuicResetTokenGenerator resetTokenGenerator = this.resetTokenGenerator;
|
||||||
|
if (resetTokenGenerator == null) {
|
||||||
|
resetTokenGenerator = QuicResetTokenGenerator.signGenerator();
|
||||||
|
}
|
||||||
|
ChannelHandler handler = this.handler;
|
||||||
|
ChannelHandler streamHandler = this.streamHandler;
|
||||||
|
return new QuicheQuicServerCodec(config, localConnIdLength, tokenHandler, generator, resetTokenGenerator,
|
||||||
|
flushStrategy, sslEngineProvider, sslTaskExecutor, handler,
|
||||||
|
Quic.toOptionsArray(options), Quic.toAttributesArray(attrs),
|
||||||
|
streamHandler, Quic.toOptionsArray(streamOptions), Quic.toAttributesArray(streamAttrs));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special {@link SslContext} that can be used for {@code QUIC}.
|
||||||
|
*/
|
||||||
|
public abstract class QuicSslContext extends SslContext {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract QuicSslEngine newEngine(ByteBufAllocator alloc);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract QuicSslSessionContext sessionContext();
|
||||||
|
|
||||||
|
static X509Certificate[] toX509Certificates0(InputStream stream)
|
||||||
|
throws CertificateException {
|
||||||
|
return SslContext.toX509Certificates(stream);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,381 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
|
import io.netty.handler.ssl.util.KeyManagerFactoryWrapper;
|
||||||
|
import io.netty.handler.ssl.util.TrustManagerFactoryWrapper;
|
||||||
|
import io.netty.util.Mapping;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for configuring a new SslContext for creation.
|
||||||
|
*/
|
||||||
|
public final class QuicSslContextBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special {@link X509ExtendedKeyManager} implementation which will just fail the certificate selection.
|
||||||
|
* This is used as a "dummy" implementation when SNI is used as we should always select an other
|
||||||
|
* {@link QuicSslContext} based on the provided hostname.
|
||||||
|
*/
|
||||||
|
private static final X509ExtendedKeyManager SNI_KEYMANAGER = new X509ExtendedKeyManager() {
|
||||||
|
private final X509Certificate[] emptyCerts = new X509Certificate[0];
|
||||||
|
private final String[] emptyStrings = new String[0];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||||
|
return emptyStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||||
|
return emptyStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getCertificateChain(String alias) {
|
||||||
|
return emptyCerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivateKey(String alias) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder for new client-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||||
|
*/
|
||||||
|
public static QuicSslContextBuilder forClient() {
|
||||||
|
return new QuicSslContextBuilder(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||||
|
*
|
||||||
|
* @param keyFile a PKCS#8 private key file in PEM format
|
||||||
|
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||||
|
* password-protected
|
||||||
|
* @param certChainFile an X.509 certificate chain file in PEM format
|
||||||
|
* @see #keyManager(File, String, File)
|
||||||
|
*/
|
||||||
|
public static QuicSslContextBuilder forServer(
|
||||||
|
File keyFile, String keyPassword, File certChainFile) {
|
||||||
|
return new QuicSslContextBuilder(true).keyManager(keyFile, keyPassword, certChainFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||||
|
*
|
||||||
|
* @param key a PKCS#8 private key
|
||||||
|
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||||
|
* password-protected
|
||||||
|
* @param certChain the X.509 certificate chain
|
||||||
|
* @see #keyManager(File, String, File)
|
||||||
|
*/
|
||||||
|
public static QuicSslContextBuilder forServer(
|
||||||
|
PrivateKey key, String keyPassword, X509Certificate... certChain) {
|
||||||
|
return new QuicSslContextBuilder(true).keyManager(key, keyPassword, certChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder for new server-side {@link QuicSslContext} that can be used for {@code QUIC}.
|
||||||
|
*
|
||||||
|
* @param keyManagerFactory non-{@code null} factory for server's private key
|
||||||
|
* @see #keyManager(KeyManagerFactory, String)
|
||||||
|
*/
|
||||||
|
public static QuicSslContextBuilder forServer(KeyManagerFactory keyManagerFactory, String password) {
|
||||||
|
return new QuicSslContextBuilder(true).keyManager(keyManagerFactory, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder for new server-side {@link QuicSslContext} with {@link KeyManager} that can be used for
|
||||||
|
* {@code QUIC}.
|
||||||
|
*
|
||||||
|
* @param keyManager non-{@code null} KeyManager for server's private key
|
||||||
|
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||||
|
* password-protected
|
||||||
|
*/
|
||||||
|
public static QuicSslContextBuilder forServer(KeyManager keyManager, String keyPassword) {
|
||||||
|
return new QuicSslContextBuilder(true).keyManager(keyManager, keyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables support for
|
||||||
|
* <a href="https://quicwg.org/ops-drafts/draft-ietf-quic-manageability.html#name-server-name-indication-sni">
|
||||||
|
* SNI</a> on the server side.
|
||||||
|
*
|
||||||
|
* @param mapping the {@link Mapping} that is used to map names to the {@link QuicSslContext} to use.
|
||||||
|
* Usually using {@link io.netty.util.DomainWildcardMappingBuilder} should be used
|
||||||
|
* to create the {@link Mapping}.
|
||||||
|
*/
|
||||||
|
public static QuicSslContext buildForServerWithSni(Mapping<? super String, ? extends QuicSslContext> mapping) {
|
||||||
|
return forServer(SNI_KEYMANAGER, null).sni(mapping).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean forServer;
|
||||||
|
private TrustManagerFactory trustManagerFactory;
|
||||||
|
private String keyPassword;
|
||||||
|
private KeyManagerFactory keyManagerFactory;
|
||||||
|
private long sessionCacheSize = 20480;
|
||||||
|
private long sessionTimeout = 300;
|
||||||
|
private ClientAuth clientAuth = ClientAuth.NONE;
|
||||||
|
private String[] applicationProtocols;
|
||||||
|
private Boolean earlyData;
|
||||||
|
private BoringSSLKeylog keylog;
|
||||||
|
private Mapping<? super String, ? extends QuicSslContext> mapping;
|
||||||
|
|
||||||
|
private QuicSslContextBuilder(boolean forServer) {
|
||||||
|
this.forServer = forServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuicSslContextBuilder sni(Mapping<? super String, ? extends QuicSslContext> mapping) {
|
||||||
|
this.mapping = checkNotNull(mapping, "mapping");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable / disable the usage of early data.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder earlyData(boolean enabled) {
|
||||||
|
this.earlyData = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable / disable keylog. When enabled, TLS keys are logged to an internal logger named
|
||||||
|
* "io.netty.handler.codec.quic.BoringSSLLogginKeylog" with DEBUG level, see
|
||||||
|
* {@link BoringSSLKeylog} for detail, logging keys are following
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||||
|
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder keylog(boolean enabled) {
|
||||||
|
keylog(enabled ? BoringSSLLoggingKeylog.INSTANCE : null);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable / disable keylog. When enabled, TLS keys are logged to {@link BoringSSLKeylog#logKey(SSLEngine, String)}
|
||||||
|
* logging keys are following
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">
|
||||||
|
* NSS Key Log Format</a>. This is intended for debugging use with tools like Wireshark.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder keylog(BoringSSLKeylog keylog) {
|
||||||
|
this.keylog = keylog;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trusted certificates for verifying the remote endpoint's certificate. The file should
|
||||||
|
* contain an X.509 certificate collection in PEM format. {@code null} uses the system default
|
||||||
|
* which only works with Java 8u261 and later as these versions support TLS1.3,
|
||||||
|
* see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
|
||||||
|
* JDK 8u261 Update Release Notes</a>
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder trustManager(File trustCertCollectionFile) {
|
||||||
|
try {
|
||||||
|
return trustManager(QuicheQuicSslContext.toX509Certificates0(trustCertCollectionFile));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("File does not contain valid certificates: "
|
||||||
|
+ trustCertCollectionFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trusted certificates for verifying the remote endpoint's certificate. {@code null} uses the system default
|
||||||
|
* which only works with Java 8u261 and later as these versions support TLS1.3,
|
||||||
|
* see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
|
||||||
|
* JDK 8u261 Update Release Notes</a>
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder trustManager(X509Certificate... trustCertCollection) {
|
||||||
|
try {
|
||||||
|
return trustManager(QuicheQuicSslContext.buildTrustManagerFactory0(trustCertCollection));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trusted manager for verifying the remote endpoint's certificate. {@code null} uses the system default
|
||||||
|
* which only works with Java 8u261 and later as these versions support TLS1.3,
|
||||||
|
* see <a href="https://www.oracle.com/java/technologies/javase/8u261-relnotes.html">
|
||||||
|
* JDK 8u261 Update Release Notes</a>
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder trustManager(TrustManagerFactory trustManagerFactory) {
|
||||||
|
this.trustManagerFactory = trustManagerFactory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single trusted manager for verifying the remote endpoint's certificate.
|
||||||
|
* This is helpful when custom implementation of {@link TrustManager} is needed.
|
||||||
|
* Internally, a simple wrapper of {@link TrustManagerFactory} that only produces this
|
||||||
|
* specified {@link TrustManager} will be created, thus all the requirements specified in
|
||||||
|
* {@link #trustManager(TrustManagerFactory trustManagerFactory)} also apply here.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder trustManager(TrustManager trustManager) {
|
||||||
|
return trustManager(new TrustManagerFactoryWrapper(trustManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifying certificate for this host. {@code keyCertChainFile} and {@code keyFile} may
|
||||||
|
* be {@code null} for client contexts, which disables mutual authentication.
|
||||||
|
*
|
||||||
|
* @param keyFile a PKCS#8 private key file in PEM format
|
||||||
|
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
|
||||||
|
* password-protected
|
||||||
|
* @param keyCertChainFile an X.509 certificate chain file in PEM format
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder keyManager(File keyFile, String keyPassword, File keyCertChainFile) {
|
||||||
|
X509Certificate[] keyCertChain;
|
||||||
|
PrivateKey key;
|
||||||
|
try {
|
||||||
|
keyCertChain = QuicheQuicSslContext.toX509Certificates0(keyCertChainFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("File does not contain valid certificates: " + keyCertChainFile, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
key = QuicheQuicSslContext.toPrivateKey0(keyFile, keyPassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("File does not contain valid private key: " + keyFile, e);
|
||||||
|
}
|
||||||
|
return keyManager(key, keyPassword, keyCertChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifying certificate for this host. {@code keyCertChain} and {@code key} may
|
||||||
|
* be {@code null} for client contexts, which disables mutual authentication.
|
||||||
|
*
|
||||||
|
* @param key a PKCS#8 private key file
|
||||||
|
* @param keyPassword the password of the {@code key}, or {@code null} if it's not
|
||||||
|
* password-protected
|
||||||
|
* @param certChain an X.509 certificate chain
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder keyManager(PrivateKey key, String keyPassword, X509Certificate... certChain) {
|
||||||
|
try {
|
||||||
|
java.security.KeyStore ks = java.security.KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
ks.load(null);
|
||||||
|
char[] pass = keyPassword == null ? new char[0]: keyPassword.toCharArray();
|
||||||
|
ks.setKeyEntry("alias", key, pass, certChain);
|
||||||
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
|
||||||
|
KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
keyManagerFactory.init(ks, pass);
|
||||||
|
return keyManager(keyManagerFactory, keyPassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifying manager for this host. {@code keyManagerFactory} may be {@code null} for
|
||||||
|
* client contexts, which disables mutual authentication.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder keyManager(KeyManagerFactory keyManagerFactory, String keyPassword) {
|
||||||
|
this.keyPassword = keyPassword;
|
||||||
|
this.keyManagerFactory = keyManagerFactory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single key manager managing the identity information of this host.
|
||||||
|
* This is helpful when custom implementation of {@link KeyManager} is needed.
|
||||||
|
* Internally, a wrapper of {@link KeyManagerFactory} that only produces this specified
|
||||||
|
* {@link KeyManager} will be created, thus all the requirements specified in
|
||||||
|
* {@link #keyManager(KeyManagerFactory, String)} also apply here.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder keyManager(KeyManager keyManager, String password) {
|
||||||
|
return keyManager(new KeyManagerFactoryWrapper(keyManager), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application protocol negotiation configuration. {@code null} disables support.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder applicationProtocols(String... applicationProtocols) {
|
||||||
|
this.applicationProtocols = applicationProtocols;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the size of the cache used for storing SSL session objects. {@code 0} to use the
|
||||||
|
* default value.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder sessionCacheSize(long sessionCacheSize) {
|
||||||
|
this.sessionCacheSize = sessionCacheSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the timeout for the cached SSL session objects, in seconds. {@code 0} to use the
|
||||||
|
* default value.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder sessionTimeout(long sessionTimeout) {
|
||||||
|
this.sessionTimeout = sessionTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the client authentication mode.
|
||||||
|
*/
|
||||||
|
public QuicSslContextBuilder clientAuth(ClientAuth clientAuth) {
|
||||||
|
if (!forServer) {
|
||||||
|
throw new UnsupportedOperationException("Only supported for server");
|
||||||
|
}
|
||||||
|
this.clientAuth = checkNotNull(clientAuth, "clientAuth");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new {@link QuicSslContext} instance with configured settings that can be used for {@code QUIC}.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public QuicSslContext build() {
|
||||||
|
if (forServer) {
|
||||||
|
return new QuicheQuicSslContext(true, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
|
||||||
|
keyManagerFactory, keyPassword, mapping, earlyData, keylog, applicationProtocols);
|
||||||
|
} else {
|
||||||
|
return new QuicheQuicSslContext(false, sessionTimeout, sessionCacheSize, clientAuth, trustManagerFactory,
|
||||||
|
keyManagerFactory, keyPassword, mapping, earlyData, keylog,
|
||||||
|
applicationProtocols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link SSLEngine} that can be used for QUIC.
|
||||||
|
*/
|
||||||
|
public abstract class QuicSslEngine extends SSLEngine { }
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSessionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SSLSessionContext} which also supports advanced operations.
|
||||||
|
*/
|
||||||
|
public interface QuicSslSessionContext extends SSLSessionContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link SslSessionTicketKey}s that should be used. The first key of the array is used for encryption
|
||||||
|
* and decryption while the rest of the array is only used for decryption. This allows you to better handling
|
||||||
|
* rotating of the keys. The rotating is the responsibility of the user.
|
||||||
|
* If {@code null} is used for {@code keys} a key will automatically generated by the library and also rotated.
|
||||||
|
*
|
||||||
|
* @param keys the tickets to use.
|
||||||
|
*/
|
||||||
|
void setTicketKeys(SslSessionTicketKey... keys);
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link SocketAddress} for QUIC stream.
|
||||||
|
*/
|
||||||
|
public final class QuicStreamAddress extends SocketAddress {
|
||||||
|
|
||||||
|
private final long streamId;
|
||||||
|
|
||||||
|
public QuicStreamAddress(long streamId) {
|
||||||
|
this.streamId = streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the id of the stream.
|
||||||
|
*
|
||||||
|
* @return the id.
|
||||||
|
*/
|
||||||
|
public long streamId() {
|
||||||
|
return streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof QuicStreamAddress)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QuicStreamAddress that = (QuicStreamAddress) o;
|
||||||
|
return streamId == that.streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(streamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicStreamAddress{" +
|
||||||
|
"streamId=" + streamId +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelFutureListener;
|
||||||
|
import io.netty.channel.ChannelProgressivePromise;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.socket.DuplexChannel;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A QUIC stream.
|
||||||
|
*/
|
||||||
|
public interface QuicStreamChannel extends DuplexChannel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be added to a {@link ChannelFuture} when the output should be cleanly shutdown via a {@code FIN}. No more
|
||||||
|
* writes will be allowed after this point.
|
||||||
|
*/
|
||||||
|
ChannelFutureListener SHUTDOWN_OUTPUT = f -> ((QuicStreamChannel) f.channel()).shutdownOutput();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture bind(SocketAddress socketAddress) {
|
||||||
|
return pipeline().bind(socketAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress) {
|
||||||
|
return pipeline().connect(remoteAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
|
||||||
|
return pipeline().connect(remoteAddress, localAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture disconnect() {
|
||||||
|
return pipeline().disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture close() {
|
||||||
|
return pipeline().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture deregister() {
|
||||||
|
return pipeline().deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture bind(SocketAddress localAddress, ChannelPromise channelPromise) {
|
||||||
|
return pipeline().bind(localAddress, channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise channelPromise) {
|
||||||
|
return pipeline().connect(remoteAddress, channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture connect(
|
||||||
|
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise channelPromise) {
|
||||||
|
return pipeline().connect(remoteAddress, localAddress, channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture disconnect(ChannelPromise channelPromise) {
|
||||||
|
return pipeline().disconnect(channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture close(ChannelPromise channelPromise) {
|
||||||
|
return pipeline().close(channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture deregister(ChannelPromise channelPromise) {
|
||||||
|
return pipeline().deregister(channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture write(Object msg) {
|
||||||
|
return pipeline().write(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture write(Object msg, ChannelPromise channelPromise) {
|
||||||
|
return pipeline().write(msg, channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture writeAndFlush(Object msg, ChannelPromise channelPromise) {
|
||||||
|
return pipeline().writeAndFlush(msg, channelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture writeAndFlush(Object msg) {
|
||||||
|
return pipeline().writeAndFlush(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelPromise newPromise() {
|
||||||
|
return pipeline().newPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelProgressivePromise newProgressivePromise() {
|
||||||
|
return pipeline().newProgressivePromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture newSucceededFuture() {
|
||||||
|
return pipeline().newSucceededFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture newFailedFuture(Throwable cause) {
|
||||||
|
return pipeline().newFailedFuture(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelPromise voidPromise() {
|
||||||
|
return pipeline().voidPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture shutdownInput() {
|
||||||
|
return shutdownInput(newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture shutdownInput(ChannelPromise promise) {
|
||||||
|
return shutdownInput(0, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture shutdownOutput() {
|
||||||
|
return shutdownOutput(newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ChannelFuture shutdown() {
|
||||||
|
return shutdown(newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for calling {@link #shutdownInput(int)} and {@link #shutdownInput(int)}.
|
||||||
|
*
|
||||||
|
* @param error the error to send.
|
||||||
|
* @return the future that is notified on completion.
|
||||||
|
*/
|
||||||
|
default ChannelFuture shutdown(int error) {
|
||||||
|
return shutdown(error, newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for calling {@link #shutdownInput(int, ChannelPromise)} and {@link #shutdownInput(int, ChannelPromise)}.
|
||||||
|
*
|
||||||
|
* @param error the error to send.
|
||||||
|
* @param promise will be notified on completion.
|
||||||
|
* @return the future that is notified on completion.
|
||||||
|
*/
|
||||||
|
ChannelFuture shutdown(int error, ChannelPromise promise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the input of the stream with the given error code. This means a {@code STOP_SENDING} frame will
|
||||||
|
* be send to the remote peer and all data received will be discarded.
|
||||||
|
*
|
||||||
|
* @param error the error to send as part of the {@code STOP_SENDING} frame.
|
||||||
|
* @return the future that is notified on completion.
|
||||||
|
*/
|
||||||
|
default ChannelFuture shutdownInput(int error) {
|
||||||
|
return shutdownInput(error, newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the input of the stream with the given error code. This means a {@code STOP_SENDING} frame will
|
||||||
|
* be send to the remote peer and all data received will be discarded.
|
||||||
|
*
|
||||||
|
* @param error the error to send as part of the {@code STOP_SENDING} frame.
|
||||||
|
* @param promise will be notified on completion.
|
||||||
|
* @return the future that is notified on completion.
|
||||||
|
*/
|
||||||
|
ChannelFuture shutdownInput(int error, ChannelPromise promise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the output of the stream with the given error code. This means a {@code RESET_STREAM} frame will
|
||||||
|
* be send to the remote peer and all data that is not sent yet will be discarded.
|
||||||
|
*
|
||||||
|
* <strong>Important:</strong>If you want to shutdown the output without sending a {@code RESET_STREAM} frame you
|
||||||
|
* should use {@link #shutdownOutput()} which will shutdown the output by sending a {@code FIN} and so signal
|
||||||
|
* a clean shutdown.
|
||||||
|
*
|
||||||
|
* @param error the error to send as part of the {@code RESET_STREAM} frame.
|
||||||
|
* @return the future that is notified on completion.
|
||||||
|
*/
|
||||||
|
default ChannelFuture shutdownOutput(int error) {
|
||||||
|
return shutdownOutput(error, newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the output of the stream with the given error code. This means a {@code RESET_STREAM} frame will
|
||||||
|
* be send to the remote peer and all data that is not sent yet will be discarded.
|
||||||
|
*
|
||||||
|
* <strong>Important:</strong>If you want to shutdown the output without sending a {@code RESET_STREAM} frame you
|
||||||
|
* should use {@link #shutdownOutput(ChannelPromise)} which will shutdown the output by sending a {@code FIN}
|
||||||
|
* and so signal a clean shutdown.
|
||||||
|
*
|
||||||
|
* @param error the error to send as part of the {@code RESET_STREAM} frame.
|
||||||
|
* @param promise will be notified on completion.
|
||||||
|
* @return the future that is notified on completion.
|
||||||
|
*/
|
||||||
|
ChannelFuture shutdownOutput(int error, ChannelPromise promise);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamAddress localAddress();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamAddress remoteAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the stream was created locally.
|
||||||
|
*
|
||||||
|
* @return {@code true} if created locally, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean isLocalCreated();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link QuicStreamType} of the stream.
|
||||||
|
*
|
||||||
|
* @return {@link QuicStreamType} of this stream.
|
||||||
|
*/
|
||||||
|
QuicStreamType type();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the stream.
|
||||||
|
*
|
||||||
|
* @return the stream id of this {@link QuicStreamChannel}.
|
||||||
|
*/
|
||||||
|
long streamId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link QuicStreamPriority} if explicit set for the stream via {@link #updatePriority(QuicStreamPriority)} or
|
||||||
|
* {@link #updatePriority(QuicStreamPriority, ChannelPromise)}. Otherwise {@code null}.
|
||||||
|
*
|
||||||
|
* @return the priority if any was set.
|
||||||
|
*/
|
||||||
|
QuicStreamPriority priority();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the priority of the stream. A stream's priority determines the order in which stream data is sent
|
||||||
|
* on the wire (streams with lower priority are sent first).
|
||||||
|
*
|
||||||
|
* @param priority the priority.
|
||||||
|
* @return future that is notified once the operation completes.
|
||||||
|
*/
|
||||||
|
default ChannelFuture updatePriority(QuicStreamPriority priority) {
|
||||||
|
return updatePriority(priority, newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the priority of the stream. A stream's priority determines the order in which stream data is sent
|
||||||
|
* on the wire (streams with lower priority are sent first).
|
||||||
|
*
|
||||||
|
* @param priority the priority.
|
||||||
|
* @param promise notified once operations completes.
|
||||||
|
* @return future that is notified once the operation completes.
|
||||||
|
*/
|
||||||
|
ChannelFuture updatePriority(QuicStreamPriority priority, ChannelPromise promise);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicChannel parent();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannel read();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannel flush();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig config();
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to bootstrap outgoing {@link QuicStreamChannel}s.
|
||||||
|
*/
|
||||||
|
public final class QuicStreamChannelBootstrap {
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(QuicStreamChannelBootstrap.class);
|
||||||
|
|
||||||
|
private final QuicChannel parent;
|
||||||
|
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<>();
|
||||||
|
private final Map<AttributeKey<?>, Object> attrs = new HashMap<>();
|
||||||
|
private ChannelHandler handler;
|
||||||
|
private QuicStreamType type = QuicStreamType.BIDIRECTIONAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance which uses the given {@link QuicChannel} to bootstrap {@link QuicStreamChannel}s.
|
||||||
|
*
|
||||||
|
* @param parent the {@link QuicChannel} that is used.
|
||||||
|
|
||||||
|
*/
|
||||||
|
QuicStreamChannelBootstrap(QuicChannel parent) {
|
||||||
|
this.parent = ObjectUtil.checkNotNull(parent, "parent");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify a {@link ChannelOption} which is used for the {@link QuicStreamChannel} instances once they got
|
||||||
|
* created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||||
|
*
|
||||||
|
* @param option the {@link ChannelOption} to apply to the {@link QuicStreamChannel}.
|
||||||
|
* @param value the value of the option.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicStreamChannelBootstrap option(ChannelOption<T> option, T value) {
|
||||||
|
Quic.updateOptions(options, option, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow to specify an initial attribute of the newly created {@link QuicStreamChannel}. If the {@code value} is
|
||||||
|
* {@code null}, the attribute of the specified {@code key} is removed.
|
||||||
|
*
|
||||||
|
* @param key the {@link AttributeKey} to apply to the {@link QuicChannel}.
|
||||||
|
* @param value the value of the attribute.
|
||||||
|
* @param <T> the type of the value.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public <T> QuicStreamChannelBootstrap attr(AttributeKey<T> key, T value) {
|
||||||
|
Quic.updateAttributes(attrs, key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link ChannelHandler} that is added to the {@link io.netty.channel.ChannelPipeline} of the
|
||||||
|
* {@link QuicStreamChannel} once created.
|
||||||
|
*
|
||||||
|
* @param streamHandler the {@link ChannelHandler} that is added to the {@link QuicStreamChannel}s
|
||||||
|
* {@link io.netty.channel.ChannelPipeline}.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicStreamChannelBootstrap handler(ChannelHandler streamHandler) {
|
||||||
|
this.handler = ObjectUtil.checkNotNull(streamHandler, "streamHandler");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link QuicStreamType} to use for the {@link QuicStreamChannel}, default is
|
||||||
|
* {@link QuicStreamType#BIDIRECTIONAL}.
|
||||||
|
*
|
||||||
|
* @param type the {@link QuicStreamType} of the {@link QuicStreamChannel}.
|
||||||
|
* @return this instance.
|
||||||
|
*/
|
||||||
|
public QuicStreamChannelBootstrap type(QuicStreamType type) {
|
||||||
|
this.type = ObjectUtil.checkNotNull(type, "type");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link QuicStreamChannel} and notifies the {@link Future}.
|
||||||
|
*
|
||||||
|
* @return the {@link Future} that is notified once the operation completes.
|
||||||
|
*/
|
||||||
|
public Future<QuicStreamChannel> create() {
|
||||||
|
return create(parent.eventLoop().newPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link QuicStreamChannel} and notifies the {@link Future}.
|
||||||
|
*
|
||||||
|
* @param promise the {@link Promise} that is notified once the operation completes.
|
||||||
|
* @return the {@link Future} that is notified once the operation completes.
|
||||||
|
*/
|
||||||
|
public Future<QuicStreamChannel> create(Promise<QuicStreamChannel> promise) {
|
||||||
|
if (handler == null) {
|
||||||
|
throw new IllegalStateException("streamHandler not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent.createStream(type, new QuicStreamChannelBootstrapHandler(handler,
|
||||||
|
Quic.toOptionsArray(options), Quic.toAttributesArray(attrs)), promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class QuicStreamChannelBootstrapHandler extends ChannelInitializer<QuicStreamChannel> {
|
||||||
|
private final ChannelHandler streamHandler;
|
||||||
|
private final Map.Entry<ChannelOption<?>, Object>[] streamOptions;
|
||||||
|
private final Map.Entry<AttributeKey<?>, Object>[] streamAttrs;
|
||||||
|
|
||||||
|
QuicStreamChannelBootstrapHandler(ChannelHandler streamHandler,
|
||||||
|
Map.Entry<ChannelOption<?>, Object>[] streamOptions,
|
||||||
|
Map.Entry<AttributeKey<?>, Object>[] streamAttrs) {
|
||||||
|
this.streamHandler = streamHandler;
|
||||||
|
this.streamOptions = streamOptions;
|
||||||
|
this.streamAttrs = streamAttrs;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void initChannel(QuicStreamChannel ch) {
|
||||||
|
Quic.setupChannel(ch, streamOptions, streamAttrs, streamHandler, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.channel.MessageSizeEstimator;
|
||||||
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
|
import io.netty.channel.socket.DuplexChannelConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link DuplexChannelConfig} for QUIC streams.
|
||||||
|
*/
|
||||||
|
public interface QuicStreamChannelConfig extends DuplexChannelConfig {
|
||||||
|
/**
|
||||||
|
* Set this to {@code true} if the {@link QuicStreamChannel} should read {@link QuicStreamFrame}s and fire these
|
||||||
|
* through the {@link io.netty.channel.ChannelPipeline}, {@code false} if it uses {@link io.netty.buffer.ByteBuf}.
|
||||||
|
*
|
||||||
|
* @param readFrames {@code true} if {@link QuicStreamFrame}s should be used, {@code false} if
|
||||||
|
* {@link io.netty.buffer.ByteBuf} should be used.
|
||||||
|
* @return this instance itself.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
QuicStreamChannelConfig setReadFrames(boolean readFrames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the {@link QuicStreamChannel} will read {@link QuicStreamFrame}s and fire these through
|
||||||
|
* the {@link io.netty.channel.ChannelPipeline}, {@code false} if it uses {@link io.netty.buffer.ByteBuf}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if {@link QuicStreamFrame}s should be used, {@code false} if
|
||||||
|
* {@link io.netty.buffer.ByteBuf} should be used.
|
||||||
|
*/
|
||||||
|
boolean isReadFrames();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setAllowHalfClosure(boolean allowHalfClosure);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setWriteSpinCount(int writeSpinCount);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setAllocator(ByteBufAllocator allocator);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setAutoRead(boolean autoRead);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setAutoClose(boolean autoClose);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufHolder;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A QUIC STREAM_FRAME.
|
||||||
|
*/
|
||||||
|
public interface QuicStreamFrame extends ByteBufHolder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An empty {@link QuicStreamFrame} that has the {@code FIN} flag set.
|
||||||
|
*/
|
||||||
|
QuicStreamFrame EMPTY_FIN = new QuicStreamFrame() {
|
||||||
|
@Override
|
||||||
|
public boolean hasFin() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame copy() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame duplicate() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame retainedDuplicate() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame replace(ByteBuf content) {
|
||||||
|
return new DefaultQuicStreamFrame(content, hasFin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame retain() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame retain(int increment) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame touch() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamFrame touch(Object hint) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf content() {
|
||||||
|
return Unpooled.EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refCnt() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release(int decrement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the frame has the FIN set, which means it notifies the remote peer that
|
||||||
|
* there will be no more writing happen. {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the FIN flag should be set, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean hasFin();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame copy();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame duplicate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame retainedDuplicate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame replace(ByteBuf content);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame retain();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame retain(int increment);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame touch();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
QuicStreamFrame touch(Object hint);
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and hands over the next stream id to use for a QUIC stream.
|
||||||
|
*/
|
||||||
|
final class QuicStreamIdGenerator {
|
||||||
|
private long nextBidirectionalStreamId;
|
||||||
|
private long nextUnidirectionalStreamId;
|
||||||
|
|
||||||
|
QuicStreamIdGenerator(boolean server) {
|
||||||
|
// See https://quicwg.org/base-drafts/rfc9000.html#name-stream-types-and-identifier
|
||||||
|
nextBidirectionalStreamId = server ? 1 : 0;
|
||||||
|
nextUnidirectionalStreamId = server ? 3 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
long nextStreamId(boolean bidirectional) {
|
||||||
|
if (bidirectional) {
|
||||||
|
long stream = nextBidirectionalStreamId;
|
||||||
|
nextBidirectionalStreamId += 4;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
long stream = nextUnidirectionalStreamId;
|
||||||
|
nextUnidirectionalStreamId += 4;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired once the stream limit of a {@link QuicChannel} changes.
|
||||||
|
*/
|
||||||
|
public final class QuicStreamLimitChangedEvent implements QuicEvent {
|
||||||
|
|
||||||
|
static final QuicStreamLimitChangedEvent INSTANCE = new QuicStreamLimitChangedEvent();
|
||||||
|
|
||||||
|
private QuicStreamLimitChangedEvent() { }
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The priority of a {@link QuicStreamChannel}.
|
||||||
|
*/
|
||||||
|
public final class QuicStreamPriority {
|
||||||
|
|
||||||
|
private final int urgency;
|
||||||
|
private final boolean incremental;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance
|
||||||
|
*
|
||||||
|
* @param urgency the urgency of the stream.
|
||||||
|
* @param incremental {@code true} if incremental.
|
||||||
|
*/
|
||||||
|
public QuicStreamPriority(int urgency, boolean incremental) {
|
||||||
|
this.urgency = ObjectUtil.checkInRange(urgency, 0, Byte.MAX_VALUE, "urgency");
|
||||||
|
this.incremental = incremental;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The urgency of the stream. Smaller number means more urgent and so data will be send earlier.
|
||||||
|
*
|
||||||
|
* @return the urgency.
|
||||||
|
*/
|
||||||
|
public int urgency() {
|
||||||
|
return urgency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code true} if incremental, {@code false} otherwise.
|
||||||
|
*
|
||||||
|
* @return if incremental.
|
||||||
|
*/
|
||||||
|
public boolean isIncremental() {
|
||||||
|
return incremental;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QuicStreamPriority that = (QuicStreamPriority) o;
|
||||||
|
return urgency == that.urgency && incremental == that.incremental;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(urgency, incremental);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QuicStreamPriority{" +
|
||||||
|
"urgency=" + urgency +
|
||||||
|
", incremental=" + incremental +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of a {@link QuicStreamChannel}.
|
||||||
|
*/
|
||||||
|
public enum QuicStreamType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An unidirectional stream.
|
||||||
|
*/
|
||||||
|
UNIDIRECTIONAL,
|
||||||
|
/**
|
||||||
|
* A bidirectional stream.
|
||||||
|
*/
|
||||||
|
BIDIRECTIONAL
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle token related operations.
|
||||||
|
*/
|
||||||
|
public interface QuicTokenHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a new token for the given destination connection id and address. This token is written to {@code out}.
|
||||||
|
* If no token should be generated and so no token validation should take place at all this method should return
|
||||||
|
* {@code false}.
|
||||||
|
*
|
||||||
|
* @param out {@link ByteBuf} into which the token will be written.
|
||||||
|
* @param dcid the destination connection id.
|
||||||
|
* @param address the {@link InetSocketAddress} of the sender.
|
||||||
|
* @return {@code true} if a token was written and so validation should happen, {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the token and return the offset, {@code -1} is returned if the token is not valid.
|
||||||
|
*
|
||||||
|
* @param token the {@link ByteBuf} that contains the token. The ownership is not transferred.
|
||||||
|
* @param address the {@link InetSocketAddress} of the sender.
|
||||||
|
* @return the start index after the token or {@code -1} if the token was not valid.
|
||||||
|
*/
|
||||||
|
int validateToken(ByteBuf token, InetSocketAddress address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the maximal token length.
|
||||||
|
*
|
||||||
|
* @return the maximal supported token length.
|
||||||
|
*/
|
||||||
|
int maxTokenLength();
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transport parameters for QUIC.
|
||||||
|
*/
|
||||||
|
public interface QuicTransportParameters {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum idle timeout.
|
||||||
|
* @return timeout.
|
||||||
|
*/
|
||||||
|
long maxIdleTimeout();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum UDP payload size.
|
||||||
|
*
|
||||||
|
* @return maximum payload size.
|
||||||
|
*/
|
||||||
|
long maxUdpPayloadSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial flow control maximum data for the connection.
|
||||||
|
*
|
||||||
|
* @return flowcontrol.
|
||||||
|
*/
|
||||||
|
long initialMaxData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial flow control maximum data for local bidirectional streams.
|
||||||
|
*
|
||||||
|
* @return flowcontrol.
|
||||||
|
*/
|
||||||
|
long initialMaxStreamDataBidiLocal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial flow control maximum data for remote bidirectional streams.
|
||||||
|
*
|
||||||
|
* @return flowcontrol.
|
||||||
|
*/
|
||||||
|
long initialMaxStreamDataBidiRemote();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial flow control maximum data for unidirectional streams.
|
||||||
|
*
|
||||||
|
* @return flowcontrol.
|
||||||
|
*/
|
||||||
|
long initialMaxStreamDataUni();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial maximum bidirectional streams.
|
||||||
|
*
|
||||||
|
* @return streams.
|
||||||
|
*/
|
||||||
|
long initialMaxStreamsBidi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial maximum unidirectional streams.
|
||||||
|
*
|
||||||
|
* @return streams.
|
||||||
|
*/
|
||||||
|
long initialMaxStreamsUni();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ACK delay exponent
|
||||||
|
*
|
||||||
|
* @return exponent.
|
||||||
|
*/
|
||||||
|
long ackDelayExponent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The max ACK delay.
|
||||||
|
*
|
||||||
|
* @return delay.
|
||||||
|
*/
|
||||||
|
long maxAckDelay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether active migration is disabled.
|
||||||
|
*
|
||||||
|
* @return disabled.
|
||||||
|
*/
|
||||||
|
boolean disableActiveMigration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active connection ID limit.
|
||||||
|
*
|
||||||
|
* @return limit.
|
||||||
|
*/
|
||||||
|
long activeConnIdLimit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DATAGRAM frame extension parameter, if any.
|
||||||
|
*
|
||||||
|
* @return param.
|
||||||
|
*/
|
||||||
|
long maxDatagramFrameSize();
|
||||||
|
}
|
@ -0,0 +1,870 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.util.internal.ClassInitializerUtil;
|
||||||
|
import io.netty.util.internal.NativeLibraryLoader;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
final class Quiche {
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Quiche.class);
|
||||||
|
private static final boolean DEBUG_LOGGING_ENABLED = logger.isDebugEnabled();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Preload all classes that will be used in the OnLoad(...) function of JNI to eliminate the possiblity of a
|
||||||
|
// class-loader deadlock. This is a workaround for https://github.com/netty/netty/issues/11209.
|
||||||
|
|
||||||
|
// This needs to match all the classes that are loaded via NETTY_JNI_UTIL_LOAD_CLASS or looked up via
|
||||||
|
// NETTY_JNI_UTIL_FIND_CLASS.
|
||||||
|
ClassInitializerUtil.tryLoadClasses(Quiche.class,
|
||||||
|
// netty_quic_boringssl
|
||||||
|
byte[].class, String.class, BoringSSLCertificateCallback.class,
|
||||||
|
BoringSSLCertificateVerifyCallback.class, BoringSSLHandshakeCompleteCallback.class,
|
||||||
|
|
||||||
|
//netty_quic_quiche
|
||||||
|
QuicheLogger.class
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// First, try calling a side-effect free JNI method to see if the library was already
|
||||||
|
// loaded by the application.
|
||||||
|
quiche_version();
|
||||||
|
} catch (UnsatisfiedLinkError ignore) {
|
||||||
|
// The library was not previously loaded, load it now.
|
||||||
|
loadNativeLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's enable debug logging for quiche if its enabled in our logger.
|
||||||
|
if (DEBUG_LOGGING_ENABLED) {
|
||||||
|
quiche_enable_debug_logging(new QuicheLogger(logger));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadNativeLibrary() {
|
||||||
|
// This needs to be kept in sync with what is defined in netty_quic_quiche.c
|
||||||
|
String libName = "netty_quiche";
|
||||||
|
ClassLoader cl = PlatformDependent.getClassLoader(Quiche.class);
|
||||||
|
|
||||||
|
if (!PlatformDependent.isAndroid()) {
|
||||||
|
libName += '_' + PlatformDependent.normalizedOs()
|
||||||
|
+ '_' + PlatformDependent.normalizedArch();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
NativeLibraryLoader.load(libName, cl);
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
logger.debug("Failed to load {}", libName, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final short AF_INET = (short) QuicheNativeStaticallyReferencedJniMethods.afInet();
|
||||||
|
static final short AF_INET6 = (short) QuicheNativeStaticallyReferencedJniMethods.afInet6();
|
||||||
|
static final int SIZEOF_SOCKADDR_STORAGE = QuicheNativeStaticallyReferencedJniMethods.sizeofSockaddrStorage();
|
||||||
|
static final int SIZEOF_SOCKADDR_IN = QuicheNativeStaticallyReferencedJniMethods.sizeofSockaddrIn();
|
||||||
|
static final int SIZEOF_SOCKADDR_IN6 = QuicheNativeStaticallyReferencedJniMethods.sizeofSockaddrIn6();
|
||||||
|
static final int SOCKADDR_IN_OFFSETOF_SIN_FAMILY =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrInOffsetofSinFamily();
|
||||||
|
static final int SOCKADDR_IN_OFFSETOF_SIN_PORT =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrInOffsetofSinPort();
|
||||||
|
static final int SOCKADDR_IN_OFFSETOF_SIN_ADDR =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrInOffsetofSinAddr();
|
||||||
|
static final int IN_ADDRESS_OFFSETOF_S_ADDR = QuicheNativeStaticallyReferencedJniMethods.inAddressOffsetofSAddr();
|
||||||
|
static final int SOCKADDR_IN6_OFFSETOF_SIN6_FAMILY =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Family();
|
||||||
|
static final int SOCKADDR_IN6_OFFSETOF_SIN6_PORT =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Port();
|
||||||
|
static final int SOCKADDR_IN6_OFFSETOF_SIN6_FLOWINFO =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Flowinfo();
|
||||||
|
static final int SOCKADDR_IN6_OFFSETOF_SIN6_ADDR =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6Addr();
|
||||||
|
static final int SOCKADDR_IN6_OFFSETOF_SIN6_SCOPE_ID =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.sockaddrIn6OffsetofSin6ScopeId();
|
||||||
|
static final int IN6_ADDRESS_OFFSETOF_S6_ADDR =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.in6AddressOffsetofS6Addr();
|
||||||
|
static final int SIZEOF_SOCKLEN_T = QuicheNativeStaticallyReferencedJniMethods.sizeofSocklenT();
|
||||||
|
static final int SIZEOF_SIZE_T = QuicheNativeStaticallyReferencedJniMethods.sizeofSizeT();
|
||||||
|
|
||||||
|
static final int SIZEOF_TIMESPEC = QuicheNativeStaticallyReferencedJniMethods.sizeofTimespec();
|
||||||
|
|
||||||
|
static final int SIZEOF_TIME_T = QuicheNativeStaticallyReferencedJniMethods.sizeofTimeT();
|
||||||
|
static final int SIZEOF_LONG = QuicheNativeStaticallyReferencedJniMethods.sizeofLong();
|
||||||
|
|
||||||
|
static final int TIMESPEC_OFFSETOF_TV_SEC =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.timespecOffsetofTvSec();
|
||||||
|
|
||||||
|
static final int TIMESPEC_OFFSETOF_TV_NSEC =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.timespecOffsetofTvNsec();
|
||||||
|
|
||||||
|
static final int QUICHE_RECV_INFO_OFFSETOF_FROM =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofFrom();
|
||||||
|
static final int QUICHE_RECV_INFO_OFFSETOF_FROM_LEN =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofFromLen();
|
||||||
|
|
||||||
|
static final int QUICHE_RECV_INFO_OFFSETOF_TO =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofTo();
|
||||||
|
static final int QUICHE_RECV_INFO_OFFSETOF_TO_LEN =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheRecvInfoOffsetofToLen();
|
||||||
|
|
||||||
|
static final int SIZEOF_QUICHE_RECV_INFO = QuicheNativeStaticallyReferencedJniMethods.sizeofQuicheRecvInfo();
|
||||||
|
static final int QUICHE_SEND_INFO_OFFSETOF_TO =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofTo();
|
||||||
|
static final int QUICHE_SEND_INFO_OFFSETOF_TO_LEN =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofToLen();
|
||||||
|
|
||||||
|
static final int QUICHE_SEND_INFO_OFFSETOF_FROM =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofFrom();
|
||||||
|
static final int QUICHE_SEND_INFO_OFFSETOF_FROM_LEN =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofFromLen();
|
||||||
|
|
||||||
|
static final int QUICHE_SEND_INFO_OFFSETOF_AT =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quicheSendInfoOffsetofAt();
|
||||||
|
static final int SIZEOF_QUICHE_SEND_INFO = QuicheNativeStaticallyReferencedJniMethods.sizeofQuicheSendInfo();
|
||||||
|
|
||||||
|
static final int QUICHE_PROTOCOL_VERSION = QuicheNativeStaticallyReferencedJniMethods.quiche_protocol_version();
|
||||||
|
static final int QUICHE_MAX_CONN_ID_LEN = QuicheNativeStaticallyReferencedJniMethods.quiche_max_conn_id_len();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L266">QUICHE_SHUTDOWN_READ</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_SHUTDOWN_READ = QuicheNativeStaticallyReferencedJniMethods.quiche_shutdown_read();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L267">QUICHE_SHUTDOWN_WRITE</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_SHUTDOWN_WRITE = QuicheNativeStaticallyReferencedJniMethods.quiche_shutdown_write();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L59">QUICHE_ERR_DONE</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_DONE = QuicheNativeStaticallyReferencedJniMethods.quiche_err_done();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L62">QUICHE_ERR_BUFFER_TOO_SHORT</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_BUFFER_TOO_SHORT =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_buffer_too_short();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L65">QUICHE_ERR_UNKNOWN_VERSION</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_UNKNOWN_VERSION =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_unknown_version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L69">QUICHE_ERR_INVALID_FRAME</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_INVALID_FRAME = QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_frame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L72">QUICHE_ERR_INVALID_PACKET</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_INVALID_PACKET = QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_packet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L76">QUICHE_ERR_INVALID_STATE</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_INVALID_STATE = QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_state();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L80">
|
||||||
|
* QUICHE_ERR_INVALID_STREAM_STATE</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_INVALID_STREAM_STATE =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_stream_state();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L83">
|
||||||
|
* QUICHE_ERR_INVALID_TRANSPORT_PARAM</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_INVALID_TRANSPORT_PARAM =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_invalid_transport_param();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L86">
|
||||||
|
* QUICHE_ERR_CRYPTO_FAIL</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_CRYPTO_FAIL = QuicheNativeStaticallyReferencedJniMethods.quiche_err_crypto_fail();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L89">
|
||||||
|
* QUICHE_ERR_TLS_FAIL</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_TLS_FAIL = QuicheNativeStaticallyReferencedJniMethods.quiche_err_tls_fail();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L92">
|
||||||
|
* QUICHE_ERR_FLOW_CONTROL</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_FLOW_CONTROL = QuicheNativeStaticallyReferencedJniMethods.quiche_err_flow_control();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#95">
|
||||||
|
* QUICHE_ERR_STREAM_LIMIT</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_STREAM_LIMIT = QuicheNativeStaticallyReferencedJniMethods.quiche_err_stream_limit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#98">
|
||||||
|
* QUICHE_ERR_FINAL_SIZE</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_FINAL_SIZE = QuicheNativeStaticallyReferencedJniMethods.quiche_err_final_size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#101">
|
||||||
|
* QUICHE_ERR_CONGESTION_CONTROL</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_CONGESTION_CONTROL =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_congestion_control();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/e179f2aa52d475e037fe47d7b8466b3afde12d76/
|
||||||
|
* include/quiche.h#L101">QUICHE_ERR_STREAM_STOPPED</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_STREAM_RESET =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_stream_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L98">
|
||||||
|
* QUICHE_ERR_STREAM_STOPPED</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_ERR_STREAM_STOPPED =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_stream_stopped();
|
||||||
|
|
||||||
|
|
||||||
|
// Too many identifiers were provided.
|
||||||
|
static final int QUICHE_ERR_ID_LIMIT =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_id_limit();
|
||||||
|
|
||||||
|
// Not enough available identifiers.
|
||||||
|
static final int QUICHE_ERR_OUT_OF_IDENTIFIERS =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_out_of_identifiers();
|
||||||
|
|
||||||
|
// Error in key update.
|
||||||
|
static final int QUICHE_ERR_KEY_UPDATE =
|
||||||
|
QuicheNativeStaticallyReferencedJniMethods.quiche_err_key_update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L176">
|
||||||
|
* QUICHE_CC_RENO</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_CC_RENO = QuicheNativeStaticallyReferencedJniMethods.quiche_cc_reno();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L177">
|
||||||
|
* QUICHE_CC_CUBIC</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_CC_CUBIC = QuicheNativeStaticallyReferencedJniMethods.quiche_cc_cubic();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.16.0/quiche/include/quiche.h#L206">
|
||||||
|
* QUICHE_CC_BBR</a>.
|
||||||
|
*/
|
||||||
|
static final int QUICHE_CC_BBR = QuicheNativeStaticallyReferencedJniMethods.quiche_cc_bbr();
|
||||||
|
|
||||||
|
|
||||||
|
static final int QUICHE_PATH_EVENT_NEW = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_new();
|
||||||
|
static final int QUICHE_PATH_EVENT_VALIDATED = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_validated();
|
||||||
|
static final int QUICHE_PATH_EVENT_FAILED_VALIDATION = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_failed_validation();
|
||||||
|
static final int QUICHE_PATH_EVENT_CLOSED = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_closed();
|
||||||
|
static final int QUICHE_PATH_EVENT_REUSED_SOURCE_CONNECTION_ID = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_reused_source_connection_id();
|
||||||
|
static final int QUICHE_PATH_EVENT_PEER_MIGRATED = QuicheNativeStaticallyReferencedJniMethods.quiche_path_event_peer_migrated();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L105">quiche_version</a>.
|
||||||
|
*/
|
||||||
|
static native String quiche_version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L227">quiche_version_is_supported</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_version_is_supported(int version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L196">quiche_header_info</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_header_info(long bufAddr, int bufLength, int dcil, long versionAddr, long typeAddr,
|
||||||
|
long scidAddr, long scidLenAddr, long dcidAddr, long dcidLenAddr,
|
||||||
|
long tokenAddr, long tokenLenAddr);
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L215">quiche_negotiate_version</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_negotiate_version(
|
||||||
|
long scidAddr, int scidLen, long dcidAddr, int dcidLen, long outAddr, int outLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L220">quiche_retry</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_retry(long scidAddr, int scidLen, long dcidAddr, int dcidLen, long newScidAddr,
|
||||||
|
int newScidLen, long tokenAddr, int tokenLen, int version, long outAddr, int outLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L229">quiche_conn_new_with_tls</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_conn_new_with_tls(long scidAddr, int scidLen, long odcidAddr, int odcidLen,
|
||||||
|
long localAddr, int localLen,
|
||||||
|
long peerAddr, int peerLen,
|
||||||
|
long configAddr, long ssl, boolean isServer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/master/include/quiche.h#L248">
|
||||||
|
* quiche_conn_set_qlog_path</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_conn_set_qlog_path(long connAddr, String path, String logTitle, String logDescription);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L249">quiche_conn_recv</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_recv(long connAddr, long bufAddr, int bufLen, long infoAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L262">quiche_conn_send</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_send(long connAddr, long outAddr, int outLen, long infoAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L373">quiche_conn_free</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_conn_free(long connAddr);
|
||||||
|
|
||||||
|
static QuicConnectionCloseEvent quiche_conn_peer_error(long connAddr) {
|
||||||
|
Object[] error = quiche_conn_peer_error0(connAddr);
|
||||||
|
if (error == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new QuicConnectionCloseEvent((Boolean) error[0], (Integer) error[1], (byte[]) error[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native Object[] quiche_conn_peer_error0(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L330">
|
||||||
|
* quiche_conn_peer_streams_left_bidi</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_conn_peer_streams_left_bidi(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L334">
|
||||||
|
* quiche_conn_peer_streams_left_uni</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_conn_peer_streams_left_uni(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L275">quiche_conn_stream_priority</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_stream_priority(
|
||||||
|
long connAddr, long streamId, byte urgency, boolean incremental);
|
||||||
|
|
||||||
|
static native int quiche_conn_send_quantum(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L309">quiche_conn_trace_id</a>.
|
||||||
|
*/
|
||||||
|
static native byte[] quiche_conn_trace_id(long connAddr);
|
||||||
|
|
||||||
|
static native byte[] quiche_conn_source_id(long connAddr);
|
||||||
|
|
||||||
|
static native byte[] quiche_conn_destination_id(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L258">quiche_conn_stream_recv</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_stream_recv(long connAddr, long streamId, long outAddr, int bufLen, long finAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L262">quiche_conn_stream_send</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_stream_send(long connAddr, long streamId, long bufAddr, int bufLen, boolean fin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L271">quiche_conn_stream_shutdown</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_stream_shutdown(long connAddr, long streamId, int direction, long err);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L274">quiche_conn_stream_capacity</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_stream_capacity(long connAddr, long streamId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L277">quiche_conn_stream_finished</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_conn_stream_finished(long connAddr, long streamId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L297">quiche_conn_close</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_close(long connAddr, boolean app, long err, long reasonAddr, int reasonLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L305">quiche_conn_is_established</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_conn_is_established(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L309">quiche_conn_is_in_early_data</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_conn_is_in_early_data(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L312">quiche_conn_is_closed</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_conn_is_closed(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/
|
||||||
|
* e9f59a55f5b56999d0e609a73aa8f1b450878a0c/include/quiche.h#L384">quiche_conn_is_timed_out</a>.
|
||||||
|
*/
|
||||||
|
static native boolean quiche_conn_is_timed_out(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L361">quiche_conn_stats</a>.
|
||||||
|
* The implementation relies on all fields of
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L340">quiche_stats</a> being numerical.
|
||||||
|
* The assumption made allows passing primitive array rather than dealing with objects.
|
||||||
|
*/
|
||||||
|
static native long[] quiche_conn_stats(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/master/quiche/include/quiche.h#L567C65-L567C88">
|
||||||
|
* quiche_conn_stats</a>.
|
||||||
|
*/
|
||||||
|
static native long[] quiche_conn_peer_transport_params(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L288">quiche_conn_timeout_as_nanos</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_conn_timeout_as_nanos(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L294">quiche_conn_on_timeout</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_conn_on_timeout(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L282">quiche_conn_readable</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_conn_readable(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L285">quiche_conn_writable</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_conn_writable(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L329">quiche_stream_iter_next</a>.
|
||||||
|
*
|
||||||
|
* This method will fill the {@code streamIds} array and return the number of streams that were filled into
|
||||||
|
* the array. If the number is the same as the length of the array you should call it again until it returns
|
||||||
|
* less to ensure you process all the streams later on.
|
||||||
|
*/
|
||||||
|
static native int quiche_stream_iter_next(long iterAddr, long[] streamIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L332">quiche_stream_iter_free</a>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static native void quiche_stream_iter_free(long iterAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L358">
|
||||||
|
* quiche_conn_dgram_max_writable_len</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_dgram_max_writable_len(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href=https://github.com/cloudflare/quiche/blob/
|
||||||
|
* 9d0c677ef1411b24d720b5c8b73bcc94b5535c29/include/quiche.h#L381">
|
||||||
|
* quiche_conn_dgram_recv_front_len</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_dgram_recv_front_len(long connAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L361">
|
||||||
|
* quiche_conn_dgram_recv</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_dgram_recv(long connAddr, long buf, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L3651">
|
||||||
|
* quiche_conn_dgram_send</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_dgram_send(long connAddr, long buf, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.10.0/include/quiche.h#L267">
|
||||||
|
* quiche_conn_set_session</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_set_session(long connAddr, byte[] sessionBytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.10.0/include/quiche.h#L328">
|
||||||
|
* quiche_conn_max_send_udp_payload_size</a>.
|
||||||
|
*/
|
||||||
|
static native int quiche_conn_max_send_udp_payload_size(long connAddr);
|
||||||
|
|
||||||
|
static native int quiche_conn_scids_left(long connAddr);
|
||||||
|
|
||||||
|
static native long quiche_conn_new_scid(long connAddr, long scidAddr, int scidLen, byte[] resetToken, boolean retire_if_needed, long seq);
|
||||||
|
|
||||||
|
static native byte[] quiche_conn_retired_scid_next(long connAddr);
|
||||||
|
|
||||||
|
static native long quiche_conn_path_event_next(long connAddr);
|
||||||
|
static native int quiche_path_event_type(long pathEvent);
|
||||||
|
static native void quiche_path_event_free(long pathEvent);
|
||||||
|
static native Object[] quiche_path_event_new(long pathEvent);
|
||||||
|
static native Object[] quiche_path_event_validated(long pathEvent);
|
||||||
|
static native Object[] quiche_path_event_failed_validation(long pathEvent);
|
||||||
|
static native Object[] quiche_path_event_closed(long pathEvent);
|
||||||
|
static native Object[] quiche_path_event_reused_source_connection_id(long pathEvent);
|
||||||
|
static native Object[] quiche_path_event_peer_migrated(long pathEvent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L115">quiche_config_new</a>.
|
||||||
|
*/
|
||||||
|
static native long quiche_config_new(int version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#129">
|
||||||
|
* quiche_config_grease</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_grease(long configAddr, boolean value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#143">
|
||||||
|
* quiche_config_set_max_idle_timeout</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_max_idle_timeout(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L150">
|
||||||
|
* quiche_config_set_max_recv_udp_payload_size</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_max_recv_udp_payload_size(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.7.0/include/quiche.h#L153">
|
||||||
|
* quiche_config_set_max_recv_udp_payload_size</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_max_send_udp_payload_size(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#149">
|
||||||
|
* quiche_config_set_initial_max_data</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_initial_max_data(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#152">
|
||||||
|
* quiche_config_set_initial_max_stream_data_bidi_local</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_initial_max_stream_data_bidi_local(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#155">
|
||||||
|
* quiche_config_set_initial_max_stream_data_bidi_remote</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_initial_max_stream_data_bidi_remote(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#158">
|
||||||
|
* quiche_config_set_initial_max_stream_data_uni</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_initial_max_stream_data_uni(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#161">
|
||||||
|
* quiche_config_set_initial_max_streams_bidi</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_initial_max_streams_bidi(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#164">
|
||||||
|
* quiche_config_set_initial_max_streams_uni</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_initial_max_streams_uni(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#167">
|
||||||
|
* quiche_config_set_ack_delay_exponent</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_ack_delay_exponent(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#170">
|
||||||
|
* quiche_config_set_max_ack_delay</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_max_ack_delay(long configAddr, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#173">
|
||||||
|
* quiche_config_set_disable_active_migration</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_disable_active_migration(long configAddr, boolean value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#181">
|
||||||
|
* quiche_config_set_cc_algorithm</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_set_cc_algorithm(long configAddr, int algo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#184">
|
||||||
|
* quiche_config_enable_hystart</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_enable_hystart(long configAddr, boolean value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L187">
|
||||||
|
* quiche_config_enable_dgram</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_enable_dgram(long configAddr, boolean enable,
|
||||||
|
int recv_queue_len, int send_queue_len);
|
||||||
|
|
||||||
|
// Sets the limit of active connection IDs.
|
||||||
|
static native void quiche_config_set_active_connection_id_limit(long configAddr, long value);
|
||||||
|
|
||||||
|
// Sets the initial stateless reset token.
|
||||||
|
static native void quiche_config_set_stateless_reset_token(long configAddr, byte[] token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#192">
|
||||||
|
* quiche_config_free</a>.
|
||||||
|
*/
|
||||||
|
static native void quiche_config_free(long configAddr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See
|
||||||
|
* <a href="https://github.com/cloudflare/quiche/blob/0.6.0/include/quiche.h#L108">quiche_config_new</a>.
|
||||||
|
*/
|
||||||
|
private static native void quiche_enable_debug_logging(QuicheLogger logger);
|
||||||
|
|
||||||
|
private static native long buffer_memory_address(ByteBuffer buffer);
|
||||||
|
|
||||||
|
static native int sockaddr_cmp(long addr, long addr2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the memory address if the {@link ByteBuf} taking the readerIndex into account.
|
||||||
|
*
|
||||||
|
* @param buf the {@link ByteBuf} of which we want to obtain the memory address
|
||||||
|
* (taking its {@link ByteBuf#readerIndex()} into account).
|
||||||
|
* @return the memory address of this {@link ByteBuf}s readerIndex.
|
||||||
|
*/
|
||||||
|
static long readerMemoryAddress(ByteBuf buf) {
|
||||||
|
return memoryAddress(buf, buf.readerIndex(), buf.readableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the memory address if the {@link ByteBuf} taking the writerIndex into account.
|
||||||
|
*
|
||||||
|
* @param buf the {@link ByteBuf} of which we want to obtain the memory address
|
||||||
|
* (taking its {@link ByteBuf#writerIndex()} into account).
|
||||||
|
* @return the memory address of this {@link ByteBuf}s writerIndex.
|
||||||
|
*/
|
||||||
|
static long writerMemoryAddress(ByteBuf buf) {
|
||||||
|
return memoryAddress(buf, buf.writerIndex(), buf.writableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the memory address if the {@link ByteBuf} taking the offset into account.
|
||||||
|
*
|
||||||
|
* @param buf the {@link ByteBuf} of which we want to obtain the memory address
|
||||||
|
* (taking the {@code offset} into account).
|
||||||
|
* @param offset the offset of the memory address.
|
||||||
|
* @param len the length of the {@link ByteBuf}.
|
||||||
|
* @return the memory address of this {@link ByteBuf}s offset.
|
||||||
|
*/
|
||||||
|
static long memoryAddress(ByteBuf buf, int offset, int len) {
|
||||||
|
assert buf.isDirect();
|
||||||
|
if (buf.hasMemoryAddress()) {
|
||||||
|
return buf.memoryAddress() + offset;
|
||||||
|
}
|
||||||
|
return memoryAddressWithPosition(buf.internalNioBuffer(offset, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the memory address of the given {@link ByteBuffer} taking its current {@link ByteBuffer#position()} into
|
||||||
|
* account.
|
||||||
|
*
|
||||||
|
* @param buf the {@link ByteBuffer} of which we want to obtain the memory address
|
||||||
|
* (taking its {@link ByteBuffer#position()} into account).
|
||||||
|
* @return the memory address of this {@link ByteBuffer}s position.
|
||||||
|
*/
|
||||||
|
static long memoryAddressWithPosition(ByteBuffer buf) {
|
||||||
|
assert buf.isDirect();
|
||||||
|
return buffer_memory_address(buf) + buf.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
static ByteBuf allocateNativeOrder(int capacity) {
|
||||||
|
// Just use Unpooled as the life-time of these buffers is long.
|
||||||
|
ByteBuf buffer = Unpooled.directBuffer(capacity);
|
||||||
|
|
||||||
|
// As we use the buffers as pointers to int etc we need to ensure we use the right oder so we will
|
||||||
|
// see the right value when we read primitive values.
|
||||||
|
return PlatformDependent.BIG_ENDIAN_NATIVE_ORDER ? buffer : buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Exception newException(int err) {
|
||||||
|
final QuicError error = QuicError.valueOf(err);
|
||||||
|
final QuicException reason = new QuicException(error);
|
||||||
|
if (err == QUICHE_ERR_TLS_FAIL) {
|
||||||
|
String lastSslError = BoringSSL.ERR_last_error();
|
||||||
|
final String message;
|
||||||
|
if (lastSslError != null) {
|
||||||
|
message = error.message() + ": " + lastSslError;
|
||||||
|
} else {
|
||||||
|
message = error.message();
|
||||||
|
}
|
||||||
|
final SSLHandshakeException sslExc = new SSLHandshakeException(message);
|
||||||
|
sslExc.initCause(reason);
|
||||||
|
return sslExc;
|
||||||
|
}
|
||||||
|
if (err == QUICHE_ERR_CRYPTO_FAIL) {
|
||||||
|
return new SSLException(error.message(), reason);
|
||||||
|
}
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean shouldClose(int res) {
|
||||||
|
return res == Quiche.QUICHE_ERR_CRYPTO_FAIL || res == Quiche.QUICHE_ERR_TLS_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean throwIfError(int res) throws Exception {
|
||||||
|
if (res < 0) {
|
||||||
|
if (res == Quiche.QUICHE_ERR_DONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw Quiche.newException(res);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notifyPromise(int res, ChannelPromise promise) {
|
||||||
|
if (res < 0 && res != Quiche.QUICHE_ERR_DONE) {
|
||||||
|
promise.setFailure(Quiche.newException(res));
|
||||||
|
} else {
|
||||||
|
promise.setSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored.
|
||||||
|
*
|
||||||
|
* @param memory the first {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||||
|
* @param memory2 the second {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||||
|
* @return {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored, {@code false}
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
static boolean isSameAddress(ByteBuffer memory, ByteBuffer memory2, int addressOffset) {
|
||||||
|
long address1 = Quiche.memoryAddressWithPosition(memory) + addressOffset;
|
||||||
|
long address2 = Quiche.memoryAddressWithPosition(memory2) + addressOffset;
|
||||||
|
return SockaddrIn.cmp(address1, address2) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setPrimitiveValue(ByteBuffer memory, int offset, int valueType, long value) {
|
||||||
|
switch (valueType) {
|
||||||
|
case 1:
|
||||||
|
memory.put(offset, (byte) value);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
memory.putShort(offset, (short) value);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
memory.putInt(offset, (int) value);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
memory.putLong(offset, value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static long getPrimitiveValue(ByteBuffer memory, int offset, int valueType) {
|
||||||
|
switch (valueType) {
|
||||||
|
case 1:
|
||||||
|
return memory.get(offset);
|
||||||
|
case 2:
|
||||||
|
return memory.getShort(offset);
|
||||||
|
case 4:
|
||||||
|
return memory.getInt(offset);
|
||||||
|
case 8:
|
||||||
|
return memory.getLong(offset);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Quiche() { }
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
final class QuicheConfig {
|
||||||
|
private final boolean isDatagramSupported;
|
||||||
|
private long config = -1;
|
||||||
|
|
||||||
|
QuicheConfig(int version, Boolean grease, Long maxIdleTimeout, Long maxSendUdpPayloadSize,
|
||||||
|
Long maxRecvUdpPayloadSize, Long initialMaxData,
|
||||||
|
Long initialMaxStreamDataBidiLocal, Long initialMaxStreamDataBidiRemote,
|
||||||
|
Long initialMaxStreamDataUni, Long initialMaxStreamsBidi, Long initialMaxStreamsUni,
|
||||||
|
Long ackDelayExponent, Long maxAckDelay, Boolean disableActiveMigration, Boolean enableHystart,
|
||||||
|
QuicCongestionControlAlgorithm congestionControlAlgorithm,
|
||||||
|
Integer recvQueueLen, Integer sendQueueLen,
|
||||||
|
Long activeConnectionIdLimit, byte[] statelessResetToken) {
|
||||||
|
long config = Quiche.quiche_config_new(version);
|
||||||
|
try {
|
||||||
|
if (grease != null) {
|
||||||
|
Quiche.quiche_config_grease(config, grease);
|
||||||
|
}
|
||||||
|
if (maxIdleTimeout != null) {
|
||||||
|
Quiche.quiche_config_set_max_idle_timeout(config, maxIdleTimeout);
|
||||||
|
}
|
||||||
|
if (maxSendUdpPayloadSize != null) {
|
||||||
|
Quiche.quiche_config_set_max_send_udp_payload_size(config, maxSendUdpPayloadSize);
|
||||||
|
}
|
||||||
|
if (maxRecvUdpPayloadSize != null) {
|
||||||
|
Quiche.quiche_config_set_max_recv_udp_payload_size(config, maxRecvUdpPayloadSize);
|
||||||
|
}
|
||||||
|
if (initialMaxData != null) {
|
||||||
|
Quiche.quiche_config_set_initial_max_data(config, initialMaxData);
|
||||||
|
}
|
||||||
|
if (initialMaxStreamDataBidiLocal != null) {
|
||||||
|
Quiche.quiche_config_set_initial_max_stream_data_bidi_local(config, initialMaxStreamDataBidiLocal);
|
||||||
|
}
|
||||||
|
if (initialMaxStreamDataBidiRemote != null) {
|
||||||
|
Quiche.quiche_config_set_initial_max_stream_data_bidi_remote(config, initialMaxStreamDataBidiRemote);
|
||||||
|
}
|
||||||
|
if (initialMaxStreamDataUni != null) {
|
||||||
|
Quiche.quiche_config_set_initial_max_stream_data_uni(config, initialMaxStreamDataUni);
|
||||||
|
}
|
||||||
|
if (initialMaxStreamsBidi != null) {
|
||||||
|
Quiche.quiche_config_set_initial_max_streams_bidi(config, initialMaxStreamsBidi);
|
||||||
|
}
|
||||||
|
if (initialMaxStreamsUni != null) {
|
||||||
|
Quiche.quiche_config_set_initial_max_streams_uni(config, initialMaxStreamsUni);
|
||||||
|
}
|
||||||
|
if (ackDelayExponent != null) {
|
||||||
|
Quiche.quiche_config_set_ack_delay_exponent(config, ackDelayExponent);
|
||||||
|
}
|
||||||
|
if (maxAckDelay != null) {
|
||||||
|
Quiche.quiche_config_set_max_ack_delay(config, maxAckDelay);
|
||||||
|
}
|
||||||
|
if (disableActiveMigration != null) {
|
||||||
|
Quiche.quiche_config_set_disable_active_migration(config, disableActiveMigration);
|
||||||
|
}
|
||||||
|
if (enableHystart != null) {
|
||||||
|
Quiche.quiche_config_enable_hystart(config, enableHystart);
|
||||||
|
}
|
||||||
|
if (congestionControlAlgorithm != null) {
|
||||||
|
switch (congestionControlAlgorithm) {
|
||||||
|
case RENO:
|
||||||
|
Quiche.quiche_config_set_cc_algorithm(config, Quiche.QUICHE_CC_RENO);
|
||||||
|
break;
|
||||||
|
case CUBIC:
|
||||||
|
Quiche.quiche_config_set_cc_algorithm(config, Quiche.QUICHE_CC_CUBIC);
|
||||||
|
break;
|
||||||
|
case BBR:
|
||||||
|
Quiche.quiche_config_set_cc_algorithm(config, Quiche.QUICHE_CC_BBR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unknown congestionControlAlgorithm: " + congestionControlAlgorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recvQueueLen != null && sendQueueLen != null) {
|
||||||
|
isDatagramSupported = true;
|
||||||
|
Quiche.quiche_config_enable_dgram(config, true, recvQueueLen, sendQueueLen);
|
||||||
|
} else {
|
||||||
|
isDatagramSupported = false;
|
||||||
|
}
|
||||||
|
if (activeConnectionIdLimit != null) {
|
||||||
|
Quiche.quiche_config_set_active_connection_id_limit(config, activeConnectionIdLimit);
|
||||||
|
}
|
||||||
|
if (statelessResetToken != null) {
|
||||||
|
Quiche.quiche_config_set_stateless_reset_token(config, statelessResetToken);
|
||||||
|
}
|
||||||
|
this.config = config;
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
Quiche.quiche_config_free(config);
|
||||||
|
throw cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDatagramSupported() {
|
||||||
|
return isDatagramSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
long nativeAddress() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's override finalize() as we want to ensure we never leak memory even if the user will miss to close
|
||||||
|
// Channel that uses this handler that used the config and just let it get GC'ed.
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
try {
|
||||||
|
free();
|
||||||
|
} finally {
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void free() {
|
||||||
|
if (config != -1) {
|
||||||
|
try {
|
||||||
|
Quiche.quiche_config_free(config);
|
||||||
|
} finally {
|
||||||
|
config = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates QUICHE logging to {@link InternalLogger}.
|
||||||
|
*/
|
||||||
|
final class QuicheLogger {
|
||||||
|
private final InternalLogger logger;
|
||||||
|
|
||||||
|
QuicheLogger(InternalLogger logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from JNI.
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
void log(String msg) {
|
||||||
|
logger.debug(msg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
final class QuicheNativeStaticallyReferencedJniMethods {
|
||||||
|
|
||||||
|
static native int quiche_protocol_version();
|
||||||
|
static native int quiche_max_conn_id_len();
|
||||||
|
static native int quiche_shutdown_read();
|
||||||
|
static native int quiche_shutdown_write();
|
||||||
|
|
||||||
|
static native int quiche_err_done();
|
||||||
|
static native int quiche_err_buffer_too_short();
|
||||||
|
static native int quiche_err_unknown_version();
|
||||||
|
static native int quiche_err_invalid_frame();
|
||||||
|
static native int quiche_err_invalid_packet();
|
||||||
|
static native int quiche_err_invalid_state();
|
||||||
|
static native int quiche_err_invalid_stream_state();
|
||||||
|
static native int quiche_err_invalid_transport_param();
|
||||||
|
static native int quiche_err_crypto_fail();
|
||||||
|
static native int quiche_err_tls_fail();
|
||||||
|
static native int quiche_err_flow_control();
|
||||||
|
static native int quiche_err_stream_limit();
|
||||||
|
static native int quiche_err_final_size();
|
||||||
|
static native int quiche_err_stream_stopped();
|
||||||
|
static native int quiche_err_stream_reset();
|
||||||
|
static native int quiche_err_congestion_control();
|
||||||
|
static native int quiche_err_id_limit();
|
||||||
|
static native int quiche_err_out_of_identifiers();
|
||||||
|
static native int quiche_err_key_update();
|
||||||
|
|
||||||
|
static native int quiche_cc_reno();
|
||||||
|
static native int quiche_cc_cubic();
|
||||||
|
static native int quiche_cc_bbr();
|
||||||
|
|
||||||
|
static native int quicheRecvInfoOffsetofFrom();
|
||||||
|
static native int quicheRecvInfoOffsetofFromLen();
|
||||||
|
static native int quicheRecvInfoOffsetofTo();
|
||||||
|
static native int quicheRecvInfoOffsetofToLen();
|
||||||
|
|
||||||
|
static native int sizeofQuicheRecvInfo();
|
||||||
|
static native int quicheSendInfoOffsetofTo();
|
||||||
|
static native int quicheSendInfoOffsetofToLen();
|
||||||
|
static native int quicheSendInfoOffsetofFrom();
|
||||||
|
static native int quicheSendInfoOffsetofFromLen();
|
||||||
|
|
||||||
|
static native int quicheSendInfoOffsetofAt();
|
||||||
|
|
||||||
|
static native int sizeofQuicheSendInfo();
|
||||||
|
|
||||||
|
static native int afInet();
|
||||||
|
static native int afInet6();
|
||||||
|
static native int sizeofSockaddrIn();
|
||||||
|
static native int sizeofSockaddrIn6();
|
||||||
|
static native int sockaddrInOffsetofSinFamily();
|
||||||
|
static native int sockaddrInOffsetofSinPort();
|
||||||
|
static native int sockaddrInOffsetofSinAddr();
|
||||||
|
static native int inAddressOffsetofSAddr();
|
||||||
|
static native int sockaddrIn6OffsetofSin6Family();
|
||||||
|
static native int sockaddrIn6OffsetofSin6Port();
|
||||||
|
static native int sockaddrIn6OffsetofSin6Flowinfo();
|
||||||
|
static native int sockaddrIn6OffsetofSin6Addr();
|
||||||
|
static native int sockaddrIn6OffsetofSin6ScopeId();
|
||||||
|
static native int in6AddressOffsetofS6Addr();
|
||||||
|
static native int sizeofSockaddrStorage();
|
||||||
|
static native int sizeofSocklenT();
|
||||||
|
static native int sizeofSizeT();
|
||||||
|
|
||||||
|
static native int sizeofTimespec();
|
||||||
|
static native int timespecOffsetofTvSec();
|
||||||
|
static native int timespecOffsetofTvNsec();
|
||||||
|
static native int sizeofTimeT();
|
||||||
|
static native int sizeofLong();
|
||||||
|
|
||||||
|
static native int quiche_path_event_new();
|
||||||
|
static native int quiche_path_event_validated();
|
||||||
|
static native int quiche_path_event_failed_validation();
|
||||||
|
static native int quiche_path_event_closed();
|
||||||
|
static native int quiche_path_event_reused_source_connection_id();
|
||||||
|
static native int quiche_path_event_peer_migrated();
|
||||||
|
|
||||||
|
private QuicheNativeStaticallyReferencedJniMethods() { }
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.DefaultChannelConfig;
|
||||||
|
import io.netty.channel.MessageSizeEstimator;
|
||||||
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default {@link QuicChannelConfig} implementation.
|
||||||
|
*/
|
||||||
|
final class QuicheQuicChannelConfig extends DefaultChannelConfig implements QuicChannelConfig {
|
||||||
|
|
||||||
|
private volatile QLogConfiguration qLogConfiguration;
|
||||||
|
private volatile SegmentedDatagramPacketAllocator segmentedDatagramPacketAllocator =
|
||||||
|
SegmentedDatagramPacketAllocator.NONE;
|
||||||
|
|
||||||
|
QuicheQuicChannelConfig(Channel channel) {
|
||||||
|
super(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<ChannelOption<?>, Object> getOptions() {
|
||||||
|
return getOptions(super.getOptions(),
|
||||||
|
QuicChannelOption.QLOG, QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T getOption(ChannelOption<T> option) {
|
||||||
|
if (option == QuicChannelOption.QLOG) {
|
||||||
|
return (T) getQLogConfiguration();
|
||||||
|
}
|
||||||
|
if (option == QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR) {
|
||||||
|
return (T) getSegmentedDatagramPacketAllocator();
|
||||||
|
}
|
||||||
|
return super.getOption(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> boolean setOption(ChannelOption<T> option, T value) {
|
||||||
|
if (option == QuicChannelOption.QLOG) {
|
||||||
|
setQLogConfiguration((QLogConfiguration) value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (option == QuicChannelOption.SEGMENTED_DATAGRAM_PACKET_ALLOCATOR) {
|
||||||
|
setSegmentedDatagramPacketAllocator((SegmentedDatagramPacketAllocator) value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.setOption(option, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||||
|
super.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public QuicChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) {
|
||||||
|
super.setMaxMessagesPerRead(maxMessagesPerRead);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setWriteSpinCount(int writeSpinCount) {
|
||||||
|
super.setWriteSpinCount(writeSpinCount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setAllocator(ByteBufAllocator allocator) {
|
||||||
|
super.setAllocator(allocator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
|
||||||
|
super.setRecvByteBufAllocator(allocator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setAutoRead(boolean autoRead) {
|
||||||
|
super.setAutoRead(autoRead);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setAutoClose(boolean autoClose) {
|
||||||
|
super.setAutoClose(autoClose);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
|
||||||
|
super.setWriteBufferHighWaterMark(writeBufferHighWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
|
||||||
|
super.setWriteBufferLowWaterMark(writeBufferLowWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||||
|
super.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) {
|
||||||
|
super.setMessageSizeEstimator(estimator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLogConfiguration getQLogConfiguration() {
|
||||||
|
return qLogConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setQLogConfiguration(QLogConfiguration qLogConfiguration) {
|
||||||
|
if (channel.isRegistered()) {
|
||||||
|
throw new IllegalStateException("QLOG can only be enabled before the Channel was registered");
|
||||||
|
}
|
||||||
|
this.qLogConfiguration = qLogConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
SegmentedDatagramPacketAllocator getSegmentedDatagramPacketAllocator() {
|
||||||
|
return segmentedDatagramPacketAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSegmentedDatagramPacketAllocator(
|
||||||
|
SegmentedDatagramPacketAllocator segmentedDatagramPacketAllocator) {
|
||||||
|
this.segmentedDatagramPacketAllocator = segmentedDatagramPacketAllocator;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link QuicheQuicCodec} for QUIC clients.
|
||||||
|
*/
|
||||||
|
final class QuicheQuicClientCodec extends QuicheQuicCodec {
|
||||||
|
|
||||||
|
private final Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
|
||||||
|
private final Executor sslTaskExecutor;
|
||||||
|
|
||||||
|
QuicheQuicClientCodec(QuicheConfig config, Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||||
|
Executor sslTaskExecutor, int localConnIdLength, FlushStrategy flushStrategy) {
|
||||||
|
// Let's just use Quic.MAX_DATAGRAM_SIZE as the maximum size for a token on the client side. This should be
|
||||||
|
// safe enough and as we not have too many codecs at the same time this should be ok.
|
||||||
|
super(config, localConnIdLength, Quic.MAX_DATAGRAM_SIZE, flushStrategy);
|
||||||
|
this.sslEngineProvider = sslEngineProvider;
|
||||||
|
this.sslTaskExecutor = sslTaskExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected QuicheQuicChannel quicPacketRead(
|
||||||
|
ChannelHandlerContext ctx, InetSocketAddress sender, InetSocketAddress recipient,
|
||||||
|
QuicPacketType type, int version, ByteBuf scid, ByteBuf dcid,
|
||||||
|
ByteBuf token) {
|
||||||
|
ByteBuffer key = dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes());
|
||||||
|
return getChannel(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
|
||||||
|
SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
final QuicheQuicChannel channel;
|
||||||
|
try {
|
||||||
|
channel = QuicheQuicChannel.handleConnect(sslEngineProvider, sslTaskExecutor, remoteAddress, config.nativeAddress(),
|
||||||
|
localConnIdLength, config.isDatagramSupported(),
|
||||||
|
senderSockaddrMemory.internalNioBuffer(0, senderSockaddrMemory.capacity()),
|
||||||
|
recipientSockaddrMemory.internalNioBuffer(0, recipientSockaddrMemory.capacity()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
promise.setFailure(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (channel != null) {
|
||||||
|
addChannel(channel);
|
||||||
|
|
||||||
|
channel.finishConnect();
|
||||||
|
promise.setSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.connect(remoteAddress, localAddress, promise);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,315 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelDuplexHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.MessageSizeEstimator;
|
||||||
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.quic.Quiche.allocateNativeOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for QUIC codecs.
|
||||||
|
*/
|
||||||
|
abstract class QuicheQuicCodec extends ChannelDuplexHandler {
|
||||||
|
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicCodec.class);
|
||||||
|
|
||||||
|
private final Map<ByteBuffer, QuicheQuicChannel> connectionIdToChannel = new HashMap<>();
|
||||||
|
private final Set<QuicheQuicChannel> channels = new HashSet<>();
|
||||||
|
private final Queue<QuicheQuicChannel> needsFireChannelReadComplete = new ArrayDeque<>();
|
||||||
|
private final int maxTokenLength;
|
||||||
|
private final FlushStrategy flushStrategy;
|
||||||
|
|
||||||
|
private MessageSizeEstimator.Handle estimatorHandle;
|
||||||
|
private QuicHeaderParser headerParser;
|
||||||
|
private QuicHeaderParser.QuicHeaderProcessor parserCallback;
|
||||||
|
private int pendingBytes;
|
||||||
|
private int pendingPackets;
|
||||||
|
private boolean inChannelReadComplete;
|
||||||
|
|
||||||
|
protected final QuicheConfig config;
|
||||||
|
protected final int localConnIdLength;
|
||||||
|
// This buffer is used to copy InetSocketAddress to sockaddr_storage and so pass it down the JNI layer.
|
||||||
|
protected ByteBuf senderSockaddrMemory;
|
||||||
|
protected ByteBuf recipientSockaddrMemory;
|
||||||
|
|
||||||
|
QuicheQuicCodec(QuicheConfig config, int localConnIdLength, int maxTokenLength, FlushStrategy flushStrategy) {
|
||||||
|
this.config = config;
|
||||||
|
this.localConnIdLength = localConnIdLength;
|
||||||
|
this.maxTokenLength = maxTokenLength;
|
||||||
|
this.flushStrategy = flushStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final QuicheQuicChannel getChannel(ByteBuffer key) {
|
||||||
|
return connectionIdToChannel.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void addMapping(ByteBuffer key, QuicheQuicChannel channel) {
|
||||||
|
connectionIdToChannel.put(key, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void removeMapping(ByteBuffer key) {
|
||||||
|
connectionIdToChannel.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void removeChannel(QuicheQuicChannel channel) {
|
||||||
|
boolean removed = channels.remove(channel);
|
||||||
|
assert removed;
|
||||||
|
for (ByteBuffer id : channel.sourceConnectionIds()) {
|
||||||
|
connectionIdToChannel.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void addChannel(QuicheQuicChannel channel) {
|
||||||
|
boolean added = channels.add(channel);
|
||||||
|
assert added;
|
||||||
|
for (ByteBuffer id : channel.sourceConnectionIds()) {
|
||||||
|
connectionIdToChannel.put(id, channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||||
|
senderSockaddrMemory = allocateNativeOrder(Quiche.SIZEOF_SOCKADDR_STORAGE);
|
||||||
|
recipientSockaddrMemory = allocateNativeOrder(Quiche.SIZEOF_SOCKADDR_STORAGE);
|
||||||
|
headerParser = new QuicHeaderParser(maxTokenLength, localConnIdLength);
|
||||||
|
parserCallback = new QuicCodecHeaderProcessor(ctx);
|
||||||
|
estimatorHandle = ctx.channel().config().getMessageSizeEstimator().newHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||||
|
try {
|
||||||
|
// Use a copy of the array as closing the channel may cause an unwritable event that could also
|
||||||
|
// remove channels.
|
||||||
|
for (QuicheQuicChannel ch : channels.toArray(new QuicheQuicChannel[0])) {
|
||||||
|
ch.forceClose();
|
||||||
|
}
|
||||||
|
channels.clear();
|
||||||
|
connectionIdToChannel.clear();
|
||||||
|
|
||||||
|
needsFireChannelReadComplete.clear();
|
||||||
|
if (pendingPackets > 0) {
|
||||||
|
flushNow(ctx);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
config.free();
|
||||||
|
if (senderSockaddrMemory != null) {
|
||||||
|
senderSockaddrMemory.release();
|
||||||
|
}
|
||||||
|
if (recipientSockaddrMemory != null) {
|
||||||
|
recipientSockaddrMemory.release();
|
||||||
|
}
|
||||||
|
if (headerParser != null) {
|
||||||
|
headerParser.close();
|
||||||
|
headerParser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
DatagramPacket packet = (DatagramPacket) msg;
|
||||||
|
try {
|
||||||
|
ByteBuf buffer = ((DatagramPacket) msg).content();
|
||||||
|
if (!buffer.isDirect()) {
|
||||||
|
// We need a direct buffer as otherwise we can not access the memoryAddress.
|
||||||
|
// Let's do a copy to direct memory.
|
||||||
|
ByteBuf direct = ctx.alloc().directBuffer(buffer.readableBytes());
|
||||||
|
try {
|
||||||
|
direct.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||||
|
handleQuicPacket(packet.sender(), packet.recipient(), direct);
|
||||||
|
} finally {
|
||||||
|
direct.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleQuicPacket(packet.sender(), packet.recipient(), buffer);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
packet.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleQuicPacket(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf buffer) {
|
||||||
|
try {
|
||||||
|
headerParser.parse(sender, recipient, buffer, parserCallback);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.debug("Error while processing QUIC packet", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a QUIC packet and return {@code true} if we need to call {@link ChannelHandlerContext#flush()}.
|
||||||
|
*
|
||||||
|
* @param ctx the {@link ChannelHandlerContext}.
|
||||||
|
* @param sender the {@link InetSocketAddress} of the sender of the QUIC packet
|
||||||
|
* @param recipient the {@link InetSocketAddress} of the recipient of the QUIC packet
|
||||||
|
* @param type the type of the packet.
|
||||||
|
* @param version the QUIC version
|
||||||
|
* @param scid the source connection id.
|
||||||
|
* @param dcid the destination connection id
|
||||||
|
* @param token the token
|
||||||
|
* @return {@code true} if we need to call {@link ChannelHandlerContext#flush()} before there is no new events
|
||||||
|
* for this handler in the current eventloop run.
|
||||||
|
* @throws Exception thrown if there is an error during processing.
|
||||||
|
*/
|
||||||
|
protected abstract QuicheQuicChannel quicPacketRead(ChannelHandlerContext ctx, InetSocketAddress sender,
|
||||||
|
InetSocketAddress recipient, QuicPacketType type, int version,
|
||||||
|
ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void channelReadComplete(ChannelHandlerContext ctx) {
|
||||||
|
inChannelReadComplete = true;
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
QuicheQuicChannel channel = needsFireChannelReadComplete.poll();
|
||||||
|
if (channel == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
channel.recvComplete();
|
||||||
|
if (channel.freeIfClosed()) {
|
||||||
|
removeChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
inChannelReadComplete = false;
|
||||||
|
if (pendingPackets > 0) {
|
||||||
|
flushNow(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void channelWritabilityChanged(ChannelHandlerContext ctx) {
|
||||||
|
if (ctx.channel().isWritable()) {
|
||||||
|
List<QuicheQuicChannel> closed = null;
|
||||||
|
for (QuicheQuicChannel channel : channels) {
|
||||||
|
// TODO: Be a bit smarter about this.
|
||||||
|
channel.writable();
|
||||||
|
if (channel.freeIfClosed()) {
|
||||||
|
if (closed == null) {
|
||||||
|
closed = new ArrayList<>();
|
||||||
|
}
|
||||||
|
closed.add(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (closed != null) {
|
||||||
|
for (QuicheQuicChannel ch: closed) {
|
||||||
|
removeChannel(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// As we batch flushes we need to ensure we at least try to flush a batch once the channel becomes
|
||||||
|
// unwritable. Otherwise we may end up with buffering too much writes and so waste memory.
|
||||||
|
ctx.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fireChannelWritabilityChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
|
||||||
|
pendingPackets ++;
|
||||||
|
int size = estimatorHandle.size(msg);
|
||||||
|
if (size > 0) {
|
||||||
|
pendingBytes += size;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ctx.write(msg, promise);
|
||||||
|
} finally {
|
||||||
|
flushIfNeeded(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void flush(ChannelHandlerContext ctx) {
|
||||||
|
// If we are in the channelReadComplete(...) method we might be able to delay the flush(...) until we finish
|
||||||
|
// processing all channels.
|
||||||
|
if (inChannelReadComplete) {
|
||||||
|
flushIfNeeded(ctx);
|
||||||
|
} else if (pendingPackets > 0) {
|
||||||
|
flushNow(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushIfNeeded(ChannelHandlerContext ctx) {
|
||||||
|
// Check if we should force a flush() and so ensure the packets are delivered in a timely
|
||||||
|
// manner and also make room in the outboundbuffer again that belongs to the underlying channel.
|
||||||
|
if (flushStrategy.shouldFlushNow(pendingPackets, pendingBytes)) {
|
||||||
|
flushNow(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushNow(ChannelHandlerContext ctx) {
|
||||||
|
pendingBytes = 0;
|
||||||
|
pendingPackets = 0;
|
||||||
|
ctx.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class QuicCodecHeaderProcessor implements QuicHeaderParser.QuicHeaderProcessor {
|
||||||
|
|
||||||
|
private final ChannelHandlerContext ctx;
|
||||||
|
|
||||||
|
QuicCodecHeaderProcessor(ChannelHandlerContext ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf buffer, QuicPacketType type,
|
||||||
|
int version, ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception {
|
||||||
|
QuicheQuicChannel channel = quicPacketRead(ctx, sender, recipient,
|
||||||
|
type, version, scid,
|
||||||
|
dcid, token);
|
||||||
|
if (channel != null) {
|
||||||
|
channelRecv(channel, sender, recipient, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once something was received for a {@link QuicheQuicChannel}.
|
||||||
|
*
|
||||||
|
* @param channel the channel for which the data was received
|
||||||
|
* @param sender the sender
|
||||||
|
* @param recipient the recipient
|
||||||
|
* @param buffer the acutal data.
|
||||||
|
*/
|
||||||
|
protected void channelRecv(QuicheQuicChannel channel, InetSocketAddress sender,
|
||||||
|
InetSocketAddress recipient, ByteBuf buffer) {
|
||||||
|
// Add to queue first, we might be able to safe some flushes and consolidate them
|
||||||
|
// in channelReadComplete(...) this way.
|
||||||
|
if (channel.markInFireChannelReadCompleteQueue()) {
|
||||||
|
needsFireChannelReadComplete.add(channel);
|
||||||
|
}
|
||||||
|
channel.recv(sender, recipient, buffer);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.util.ReferenceCounted;
|
||||||
|
import io.netty.util.ResourceLeakDetector;
|
||||||
|
import io.netty.util.ResourceLeakDetectorFactory;
|
||||||
|
import io.netty.util.ResourceLeakTracker;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
final class QuicheQuicConnection {
|
||||||
|
private static final int TOTAL_RECV_INFO_SIZE = Quiche.SIZEOF_QUICHE_RECV_INFO +
|
||||||
|
Quiche.SIZEOF_SOCKADDR_STORAGE + Quiche.SIZEOF_SOCKADDR_STORAGE;
|
||||||
|
private static final ResourceLeakDetector<QuicheQuicConnection> leakDetector =
|
||||||
|
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(QuicheQuicConnection.class);
|
||||||
|
private final QuicheQuicSslEngine engine;
|
||||||
|
|
||||||
|
private final ResourceLeakTracker<QuicheQuicConnection> leakTracker;
|
||||||
|
|
||||||
|
final long ssl;
|
||||||
|
private ReferenceCounted refCnt;
|
||||||
|
|
||||||
|
// This block of memory is used to store the following structs (in this order):
|
||||||
|
// - quiche_recv_info
|
||||||
|
// - sockaddr_storage
|
||||||
|
// - quiche_recv_info
|
||||||
|
// - sockaddr_storage
|
||||||
|
// - quiche_send_info
|
||||||
|
// - quiche_send_info
|
||||||
|
//
|
||||||
|
// We need to have every stored 2 times as we need to check if the last sockaddr has changed between
|
||||||
|
// quiche_conn_recv and quiche_conn_send calls. If this happens we know a QUIC connection migration did happen.
|
||||||
|
private final ByteBuf recvInfoBuffer;
|
||||||
|
private final ByteBuf sendInfoBuffer;
|
||||||
|
|
||||||
|
private boolean recvInfoFirst = true;
|
||||||
|
private boolean sendInfoFirst = true;
|
||||||
|
private final ByteBuffer recvInfoBuffer1;
|
||||||
|
private final ByteBuffer recvInfoBuffer2;
|
||||||
|
private final ByteBuffer sendInfoBuffer1;
|
||||||
|
private final ByteBuffer sendInfoBuffer2;
|
||||||
|
|
||||||
|
private long connection;
|
||||||
|
|
||||||
|
QuicheQuicConnection(long connection, long ssl, QuicheQuicSslEngine engine, ReferenceCounted refCnt) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.ssl = ssl;
|
||||||
|
this.engine = engine;
|
||||||
|
this.refCnt = refCnt;
|
||||||
|
// TODO: Maybe cache these per thread as we only use them temporary within a limited scope.
|
||||||
|
recvInfoBuffer = Quiche.allocateNativeOrder(2 * TOTAL_RECV_INFO_SIZE);
|
||||||
|
sendInfoBuffer = Quiche.allocateNativeOrder(2 * Quiche.SIZEOF_QUICHE_SEND_INFO);
|
||||||
|
|
||||||
|
// Let's memset the memory.
|
||||||
|
recvInfoBuffer.setZero(0, recvInfoBuffer.capacity());
|
||||||
|
sendInfoBuffer.setZero(0, sendInfoBuffer.capacity());
|
||||||
|
|
||||||
|
recvInfoBuffer1 = recvInfoBuffer.nioBuffer(0, TOTAL_RECV_INFO_SIZE);
|
||||||
|
recvInfoBuffer2 = recvInfoBuffer.nioBuffer(TOTAL_RECV_INFO_SIZE, TOTAL_RECV_INFO_SIZE);
|
||||||
|
|
||||||
|
sendInfoBuffer1 = sendInfoBuffer.nioBuffer(0, Quiche.SIZEOF_QUICHE_SEND_INFO);
|
||||||
|
sendInfoBuffer2 = sendInfoBuffer.nioBuffer(Quiche.SIZEOF_QUICHE_SEND_INFO, Quiche.SIZEOF_QUICHE_SEND_INFO);
|
||||||
|
this.engine.connection = this;
|
||||||
|
leakTracker = leakDetector.track(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void reattach(ReferenceCounted refCnt) {
|
||||||
|
this.refCnt.release();
|
||||||
|
this.refCnt = refCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free() {
|
||||||
|
free(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void free(boolean closeLeakTracker) {
|
||||||
|
boolean release = false;
|
||||||
|
synchronized (this) {
|
||||||
|
if (connection != -1) {
|
||||||
|
try {
|
||||||
|
BoringSSL.SSL_cleanup(ssl);
|
||||||
|
Quiche.quiche_conn_free(connection);
|
||||||
|
engine.ctx.remove(engine);
|
||||||
|
release = true;
|
||||||
|
refCnt.release();
|
||||||
|
} finally {
|
||||||
|
connection = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (release) {
|
||||||
|
recvInfoBuffer.release();
|
||||||
|
sendInfoBuffer.release();
|
||||||
|
if (closeLeakTracker && leakTracker != null) {
|
||||||
|
leakTracker.close(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable sslTask() {
|
||||||
|
final Runnable task;
|
||||||
|
synchronized (this) {
|
||||||
|
if (connection != -1) {
|
||||||
|
task = BoringSSL.SSL_getTask(ssl);
|
||||||
|
} else {
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (task == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return () -> {
|
||||||
|
if (connection == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.run();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicConnectionAddress sourceId() {
|
||||||
|
return connectionId(() -> Quiche.quiche_conn_source_id(connection));
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicConnectionAddress destinationId() {
|
||||||
|
return connectionId(() -> Quiche.quiche_conn_destination_id(connection));
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicConnectionAddress connectionId(Supplier<byte[]> idSupplier) {
|
||||||
|
final byte[] id;
|
||||||
|
synchronized (this) {
|
||||||
|
if (connection == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
id = idSupplier.get();
|
||||||
|
}
|
||||||
|
return id == null ? null : new QuicConnectionAddress(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicTransportParameters peerParameters() {
|
||||||
|
final long[] ret;
|
||||||
|
synchronized (this) {
|
||||||
|
if (connection == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ret = Quiche.quiche_conn_peer_transport_params(connection);
|
||||||
|
}
|
||||||
|
if (ret == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new QuicheQuicTransportParameters(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicSslEngine engine() {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
long address() {
|
||||||
|
assert connection != -1;
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initInfo(InetSocketAddress local, InetSocketAddress remote) {
|
||||||
|
assert connection != -1;
|
||||||
|
assert recvInfoBuffer.refCnt() != 0;
|
||||||
|
assert sendInfoBuffer.refCnt() != 0;
|
||||||
|
|
||||||
|
// Fill both quiche_recv_info structs with the same address.
|
||||||
|
QuicheRecvInfo.setRecvInfo(recvInfoBuffer1, remote, local);
|
||||||
|
QuicheRecvInfo.setRecvInfo(recvInfoBuffer2, remote, local);
|
||||||
|
|
||||||
|
// Fill both quiche_send_info structs with the same address.
|
||||||
|
QuicheSendInfo.setSendInfo(sendInfoBuffer1, local, remote);
|
||||||
|
QuicheSendInfo.setSendInfo(sendInfoBuffer2, local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer nextRecvInfo() {
|
||||||
|
assert recvInfoBuffer.refCnt() != 0;
|
||||||
|
recvInfoFirst = !recvInfoFirst;
|
||||||
|
return recvInfoFirst ? recvInfoBuffer1 : recvInfoBuffer2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer nextSendInfo() {
|
||||||
|
assert sendInfoBuffer.refCnt() != 0;
|
||||||
|
sendInfoFirst = !sendInfoFirst;
|
||||||
|
return sendInfoFirst ? sendInfoBuffer1 : sendInfoBuffer2;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSendInfoChanged() {
|
||||||
|
assert sendInfoBuffer.refCnt() != 0;
|
||||||
|
return !QuicheSendInfo.isSameAddress(sendInfoBuffer1, sendInfoBuffer2);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isRecvInfoChanged() {
|
||||||
|
assert recvInfoBuffer.refCnt() != 0;
|
||||||
|
return !QuicheRecvInfo.isSameAddress(recvInfoBuffer1, recvInfoBuffer2);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isClosed() {
|
||||||
|
assert connection != -1;
|
||||||
|
return Quiche.quiche_conn_is_closed(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's override finalize() as we want to ensure we never leak memory even if the user will miss to close
|
||||||
|
// Channel that uses this connection and just let it get GC'ed
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
try {
|
||||||
|
free(false);
|
||||||
|
} finally {
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
|
final class QuicheQuicConnectionStats implements QuicConnectionStats {
|
||||||
|
|
||||||
|
private final long[] values;
|
||||||
|
|
||||||
|
QuicheQuicConnectionStats(long[] values) {
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long recv() {
|
||||||
|
return values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long sent() {
|
||||||
|
return values[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long lost() {
|
||||||
|
return values[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long retrans() {
|
||||||
|
return values[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long sentBytes() {
|
||||||
|
return values[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long recvBytes() {
|
||||||
|
return values[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long lostBytes() {
|
||||||
|
return values[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long streamRetransBytes() {
|
||||||
|
return values[7];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long pathsCount() {
|
||||||
|
return values[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link String} representation of stats.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StringUtil.simpleClassName(this) + "[" +
|
||||||
|
"recv=" + recv() +
|
||||||
|
", sent=" + sent() +
|
||||||
|
", lost=" + lost() +
|
||||||
|
", retrans=" + retrans() +
|
||||||
|
", sentBytes=" + sentBytes() +
|
||||||
|
", recvBytes=" + recvBytes() +
|
||||||
|
", lostBytes=" + lostBytes() +
|
||||||
|
", streamRetransBytes=" + streamRetransBytes() +
|
||||||
|
", pathsCount=" + pathsCount() +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.quic.Quiche.allocateNativeOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link QuicheQuicCodec} for QUIC servers.
|
||||||
|
*/
|
||||||
|
final class QuicheQuicServerCodec extends QuicheQuicCodec {
|
||||||
|
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicServerCodec.class);
|
||||||
|
|
||||||
|
private final Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
|
||||||
|
private final Executor sslTaskExecutor;
|
||||||
|
private final QuicConnectionIdGenerator connectionIdAddressGenerator;
|
||||||
|
private final QuicResetTokenGenerator resetTokenGenerator;
|
||||||
|
private final QuicTokenHandler tokenHandler;
|
||||||
|
private final ChannelHandler handler;
|
||||||
|
private final Map.Entry<ChannelOption<?>, Object>[] optionsArray;
|
||||||
|
private final Map.Entry<AttributeKey<?>, Object>[] attrsArray;
|
||||||
|
private final ChannelHandler streamHandler;
|
||||||
|
private final Map.Entry<ChannelOption<?>, Object>[] streamOptionsArray;
|
||||||
|
private final Map.Entry<AttributeKey<?>, Object>[] streamAttrsArray;
|
||||||
|
private ByteBuf mintTokenBuffer;
|
||||||
|
private ByteBuf connIdBuffer;
|
||||||
|
|
||||||
|
QuicheQuicServerCodec(QuicheConfig config,
|
||||||
|
int localConnIdLength,
|
||||||
|
QuicTokenHandler tokenHandler,
|
||||||
|
QuicConnectionIdGenerator connectionIdAddressGenerator,
|
||||||
|
QuicResetTokenGenerator resetTokenGenerator,
|
||||||
|
FlushStrategy flushStrategy,
|
||||||
|
Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider,
|
||||||
|
Executor sslTaskExecutor,
|
||||||
|
ChannelHandler handler,
|
||||||
|
Map.Entry<ChannelOption<?>, Object>[] optionsArray,
|
||||||
|
Map.Entry<AttributeKey<?>, Object>[] attrsArray,
|
||||||
|
ChannelHandler streamHandler,
|
||||||
|
Map.Entry<ChannelOption<?>, Object>[] streamOptionsArray,
|
||||||
|
Map.Entry<AttributeKey<?>, Object>[] streamAttrsArray) {
|
||||||
|
super(config, localConnIdLength, tokenHandler.maxTokenLength(), flushStrategy);
|
||||||
|
this.tokenHandler = tokenHandler;
|
||||||
|
this.connectionIdAddressGenerator = connectionIdAddressGenerator;
|
||||||
|
this.resetTokenGenerator = resetTokenGenerator;
|
||||||
|
this.sslEngineProvider = sslEngineProvider;
|
||||||
|
this.sslTaskExecutor = sslTaskExecutor;
|
||||||
|
this.handler = handler;
|
||||||
|
this.optionsArray = optionsArray;
|
||||||
|
this.attrsArray = attrsArray;
|
||||||
|
this.streamHandler = streamHandler;
|
||||||
|
this.streamOptionsArray = streamOptionsArray;
|
||||||
|
this.streamAttrsArray = streamAttrsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||||
|
super.handlerAdded(ctx);
|
||||||
|
connIdBuffer = Quiche.allocateNativeOrder(localConnIdLength);
|
||||||
|
mintTokenBuffer = allocateNativeOrder(tokenHandler.maxTokenLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||||
|
super.handlerRemoved(ctx);
|
||||||
|
if (connIdBuffer != null) {
|
||||||
|
connIdBuffer.release();
|
||||||
|
}
|
||||||
|
if (mintTokenBuffer != null) {
|
||||||
|
mintTokenBuffer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRecv(QuicheQuicChannel channel, InetSocketAddress sender,
|
||||||
|
InetSocketAddress recipient, ByteBuf buffer) {
|
||||||
|
super.channelRecv(channel, sender, recipient, buffer);
|
||||||
|
for (ByteBuffer retiredSourceConnectionId : channel.retiredSourceConnectionId()) {
|
||||||
|
removeMapping(retiredSourceConnectionId);
|
||||||
|
}
|
||||||
|
for (ByteBuffer newSourceConnectionId :
|
||||||
|
channel.newSourceConnectionIds(connectionIdAddressGenerator, resetTokenGenerator)) {
|
||||||
|
addMapping(newSourceConnectionId, channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected QuicheQuicChannel quicPacketRead(ChannelHandlerContext ctx, InetSocketAddress sender,
|
||||||
|
InetSocketAddress recipient, QuicPacketType type, int version,
|
||||||
|
ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception {
|
||||||
|
ByteBuffer dcidByteBuffer = dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes());
|
||||||
|
QuicheQuicChannel channel = getChannel(dcidByteBuffer);
|
||||||
|
if (channel == null && type == QuicPacketType.ZERO_RTT && connectionIdAddressGenerator.isIdempotent()) {
|
||||||
|
// 0 rtt packet should obtain the server generated dcid
|
||||||
|
channel = getChannel(connectionIdAddressGenerator.newId(dcidByteBuffer, localConnIdLength));
|
||||||
|
}
|
||||||
|
if (channel == null) {
|
||||||
|
return handleServer(ctx, sender, recipient, type, version, scid, dcid, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuicheQuicChannel handleServer(ChannelHandlerContext ctx, InetSocketAddress sender,
|
||||||
|
InetSocketAddress recipient,
|
||||||
|
@SuppressWarnings("unused") QuicPacketType type, int version,
|
||||||
|
ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception {
|
||||||
|
if (!Quiche.quiche_version_is_supported(version)) {
|
||||||
|
// Version is not supported, try to negotiate it.
|
||||||
|
ByteBuf out = ctx.alloc().directBuffer(Quic.MAX_DATAGRAM_SIZE);
|
||||||
|
int outWriterIndex = out.writerIndex();
|
||||||
|
|
||||||
|
int res = Quiche.quiche_negotiate_version(
|
||||||
|
Quiche.readerMemoryAddress(scid), scid.readableBytes(),
|
||||||
|
Quiche.readerMemoryAddress(dcid), dcid.readableBytes(),
|
||||||
|
Quiche.writerMemoryAddress(out), out.writableBytes());
|
||||||
|
if (res < 0) {
|
||||||
|
out.release();
|
||||||
|
Quiche.throwIfError(res);
|
||||||
|
} else {
|
||||||
|
ctx.writeAndFlush(new DatagramPacket(out.writerIndex(outWriterIndex + res), sender));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int offset;
|
||||||
|
boolean noToken = false;
|
||||||
|
if (!token.isReadable()) {
|
||||||
|
// Clear buffers so we can reuse these.
|
||||||
|
mintTokenBuffer.clear();
|
||||||
|
connIdBuffer.clear();
|
||||||
|
|
||||||
|
// The remote peer did not send a token.
|
||||||
|
if (tokenHandler.writeToken(mintTokenBuffer, dcid, sender)) {
|
||||||
|
ByteBuffer connId = connectionIdAddressGenerator.newId(
|
||||||
|
dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes()), localConnIdLength);
|
||||||
|
connIdBuffer.writeBytes(connId);
|
||||||
|
|
||||||
|
ByteBuf out = ctx.alloc().directBuffer(Quic.MAX_DATAGRAM_SIZE);
|
||||||
|
int outWriterIndex = out.writerIndex();
|
||||||
|
int written = Quiche.quiche_retry(
|
||||||
|
Quiche.readerMemoryAddress(scid), scid.readableBytes(),
|
||||||
|
Quiche.readerMemoryAddress(dcid), dcid.readableBytes(),
|
||||||
|
Quiche.readerMemoryAddress(connIdBuffer), connIdBuffer.readableBytes(),
|
||||||
|
Quiche.readerMemoryAddress(mintTokenBuffer), mintTokenBuffer.readableBytes(),
|
||||||
|
version,
|
||||||
|
Quiche.writerMemoryAddress(out), out.writableBytes());
|
||||||
|
|
||||||
|
if (written < 0) {
|
||||||
|
out.release();
|
||||||
|
Quiche.throwIfError(written);
|
||||||
|
} else {
|
||||||
|
ctx.writeAndFlush(new DatagramPacket(out.writerIndex(outWriterIndex + written), sender));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
offset = 0;
|
||||||
|
noToken = true;
|
||||||
|
} else {
|
||||||
|
offset = tokenHandler.validateToken(token, sender);
|
||||||
|
if (offset == -1) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("invalid token: {}", token.toString(CharsetUtil.US_ASCII));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ByteBuffer key;
|
||||||
|
final long scidAddr;
|
||||||
|
final int scidLen;
|
||||||
|
final long ocidAddr;
|
||||||
|
final int ocidLen;
|
||||||
|
|
||||||
|
if (noToken) {
|
||||||
|
connIdBuffer.clear();
|
||||||
|
key = connectionIdAddressGenerator.newId(
|
||||||
|
dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes()), localConnIdLength);
|
||||||
|
connIdBuffer.writeBytes(key.duplicate());
|
||||||
|
scidAddr = Quiche.readerMemoryAddress(connIdBuffer);
|
||||||
|
scidLen = localConnIdLength;
|
||||||
|
ocidAddr = -1;
|
||||||
|
ocidLen = -1;
|
||||||
|
|
||||||
|
QuicheQuicChannel existingChannel = getChannel(key);
|
||||||
|
if (existingChannel != null) {
|
||||||
|
return existingChannel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scidAddr = Quiche.readerMemoryAddress(dcid);
|
||||||
|
scidLen = localConnIdLength;
|
||||||
|
ocidAddr = Quiche.memoryAddress(token, offset, token.readableBytes());
|
||||||
|
ocidLen = token.readableBytes() - offset;
|
||||||
|
// Now create the key to store the channel in the map.
|
||||||
|
byte[] bytes = new byte[localConnIdLength];
|
||||||
|
dcid.getBytes(dcid.readerIndex(), bytes);
|
||||||
|
key = ByteBuffer.wrap(bytes);
|
||||||
|
}
|
||||||
|
QuicheQuicChannel channel = QuicheQuicChannel.forServer(
|
||||||
|
ctx.channel(), key, recipient, sender, config.isDatagramSupported(),
|
||||||
|
streamHandler, streamOptionsArray, streamAttrsArray, this::removeChannel, sslTaskExecutor);
|
||||||
|
|
||||||
|
Quic.setupChannel(channel, optionsArray, attrsArray, handler, LOGGER);
|
||||||
|
QuicSslEngine engine = sslEngineProvider.apply(channel);
|
||||||
|
if (!(engine instanceof QuicheQuicSslEngine)) {
|
||||||
|
channel.unsafe().closeForcibly();
|
||||||
|
throw new IllegalArgumentException("QuicSslEngine is not of type "
|
||||||
|
+ QuicheQuicSslEngine.class.getSimpleName());
|
||||||
|
}
|
||||||
|
if (engine.getUseClientMode()) {
|
||||||
|
channel.unsafe().closeForcibly();
|
||||||
|
throw new IllegalArgumentException("QuicSslEngine is not created in server mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicSslEngine quicSslEngine = (QuicheQuicSslEngine) engine;
|
||||||
|
QuicheQuicConnection connection = quicSslEngine.createConnection(ssl -> {
|
||||||
|
ByteBuffer localAddrMemory = recipientSockaddrMemory.internalNioBuffer(0, recipientSockaddrMemory.capacity());
|
||||||
|
int localLen = SockaddrIn.setAddress(localAddrMemory, recipient);
|
||||||
|
|
||||||
|
ByteBuffer peerAddrMemory = senderSockaddrMemory.internalNioBuffer(0, senderSockaddrMemory.capacity());
|
||||||
|
int peerLen = SockaddrIn.setAddress(peerAddrMemory, sender);
|
||||||
|
return Quiche.quiche_conn_new_with_tls(scidAddr, scidLen, ocidAddr, ocidLen,
|
||||||
|
Quiche.memoryAddressWithPosition(localAddrMemory), localLen,
|
||||||
|
Quiche.memoryAddressWithPosition(peerAddrMemory), peerLen,
|
||||||
|
config.nativeAddress(), ssl, true);
|
||||||
|
});
|
||||||
|
if (connection == null) {
|
||||||
|
channel.unsafe().closeForcibly();
|
||||||
|
LOGGER.debug("quiche_accept failed");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.attachQuicheConnection(connection);
|
||||||
|
|
||||||
|
addChannel(channel);
|
||||||
|
|
||||||
|
ctx.channel().eventLoop().register(channel);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,497 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.handler.ssl.ApplicationProtocolNegotiator;
|
||||||
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
|
import io.netty.handler.ssl.SslHandler;
|
||||||
|
import io.netty.util.AbstractReferenceCounted;
|
||||||
|
import io.netty.util.Mapping;
|
||||||
|
import io.netty.util.ReferenceCounted;
|
||||||
|
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.LongFunction;
|
||||||
|
|
||||||
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
final class QuicheQuicSslContext extends QuicSslContext {
|
||||||
|
final ClientAuth clientAuth;
|
||||||
|
private final boolean server;
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private final ApplicationProtocolNegotiator apn;
|
||||||
|
private long sessionCacheSize;
|
||||||
|
private long sessionTimeout;
|
||||||
|
private final QuicheQuicSslSessionContext sessionCtx;
|
||||||
|
private final QuicheQuicSslEngineMap engineMap = new QuicheQuicSslEngineMap();
|
||||||
|
private final QuicClientSessionCache sessionCache;
|
||||||
|
|
||||||
|
private final BoringSSLSessionTicketCallback sessionTicketCallback = new BoringSSLSessionTicketCallback();
|
||||||
|
|
||||||
|
final NativeSslContext nativeSslContext;
|
||||||
|
|
||||||
|
QuicheQuicSslContext(boolean server, long sessionTimeout, long sessionCacheSize,
|
||||||
|
ClientAuth clientAuth, TrustManagerFactory trustManagerFactory,
|
||||||
|
KeyManagerFactory keyManagerFactory, String password,
|
||||||
|
Mapping<? super String, ? extends QuicSslContext> mapping,
|
||||||
|
Boolean earlyData, BoringSSLKeylog keylog,
|
||||||
|
String... applicationProtocols) {
|
||||||
|
Quic.ensureAvailability();
|
||||||
|
this.server = server;
|
||||||
|
this.clientAuth = server ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
|
||||||
|
final X509TrustManager trustManager;
|
||||||
|
if (trustManagerFactory == null) {
|
||||||
|
try {
|
||||||
|
trustManagerFactory =
|
||||||
|
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init((KeyStore) null);
|
||||||
|
trustManager = chooseTrustManager(trustManagerFactory);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trustManager = chooseTrustManager(trustManagerFactory);
|
||||||
|
}
|
||||||
|
final X509ExtendedKeyManager keyManager;
|
||||||
|
if (keyManagerFactory == null) {
|
||||||
|
if (server) {
|
||||||
|
throw new IllegalArgumentException("No KeyManagerFactory");
|
||||||
|
}
|
||||||
|
keyManager = null;
|
||||||
|
} else {
|
||||||
|
keyManager = chooseKeyManager(keyManagerFactory);
|
||||||
|
}
|
||||||
|
final BoringSSLPrivateKeyMethod privateKeyMethod;
|
||||||
|
if (keyManagerFactory instanceof BoringSSLKeylessManagerFactory) {
|
||||||
|
privateKeyMethod = new BoringSSLAsyncPrivateKeyMethodAdapter(engineMap,
|
||||||
|
((BoringSSLKeylessManagerFactory) keyManagerFactory).privateKeyMethod);
|
||||||
|
} else {
|
||||||
|
privateKeyMethod = null;
|
||||||
|
}
|
||||||
|
sessionCache = server ? null : new QuicClientSessionCache();
|
||||||
|
int verifyMode = server ? boringSSLVerifyModeForServer(this.clientAuth) : BoringSSL.SSL_VERIFY_PEER;
|
||||||
|
nativeSslContext = new NativeSslContext(BoringSSL.SSLContext_new(server, applicationProtocols,
|
||||||
|
new BoringSSLHandshakeCompleteCallback(engineMap),
|
||||||
|
new BoringSSLCertificateCallback(engineMap, keyManager, password),
|
||||||
|
new BoringSSLCertificateVerifyCallback(engineMap, trustManager),
|
||||||
|
mapping == null ? null : new BoringSSLTlsextServernameCallback(engineMap, mapping),
|
||||||
|
keylog == null ? null : new BoringSSLKeylogCallback(engineMap, keylog),
|
||||||
|
server ? null : new BoringSSLSessionCallback(engineMap, sessionCache), privateKeyMethod,
|
||||||
|
sessionTicketCallback, verifyMode,
|
||||||
|
BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
|
||||||
|
apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
|
||||||
|
if (this.sessionCache != null) {
|
||||||
|
// Cache is handled via our own implementation.
|
||||||
|
this.sessionCache.setSessionCacheSize((int) sessionCacheSize);
|
||||||
|
this.sessionCache.setSessionTimeout((int) sessionTimeout);
|
||||||
|
} else {
|
||||||
|
// Cache is handled by BoringSSL internally
|
||||||
|
BoringSSL.SSLContext_setSessionCacheSize(
|
||||||
|
nativeSslContext.address(), sessionCacheSize);
|
||||||
|
this.sessionCacheSize = sessionCacheSize;
|
||||||
|
|
||||||
|
BoringSSL.SSLContext_setSessionCacheTimeout(
|
||||||
|
nativeSslContext.address(), sessionTimeout);
|
||||||
|
this.sessionTimeout = sessionTimeout;
|
||||||
|
}
|
||||||
|
if (earlyData != null) {
|
||||||
|
BoringSSL.SSLContext_set_early_data_enabled(nativeSslContext.address(), earlyData);
|
||||||
|
}
|
||||||
|
sessionCtx = new QuicheQuicSslSessionContext(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private X509ExtendedKeyManager chooseKeyManager(KeyManagerFactory keyManagerFactory) {
|
||||||
|
for (KeyManager manager: keyManagerFactory.getKeyManagers()) {
|
||||||
|
if (manager instanceof X509ExtendedKeyManager) {
|
||||||
|
return (X509ExtendedKeyManager) manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No X509ExtendedKeyManager included");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static X509TrustManager chooseTrustManager(TrustManagerFactory trustManagerFactory) {
|
||||||
|
for (TrustManager manager: trustManagerFactory.getTrustManagers()) {
|
||||||
|
if (manager instanceof X509TrustManager) {
|
||||||
|
return (X509TrustManager) manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No X509TrustManager included");
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate[] toX509Certificates0(File file) throws CertificateException {
|
||||||
|
return toX509Certificates(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PrivateKey toPrivateKey0(File keyFile, String keyPassword) throws NoSuchAlgorithmException,
|
||||||
|
NoSuchPaddingException, InvalidKeySpecException,
|
||||||
|
InvalidAlgorithmParameterException,
|
||||||
|
KeyException, IOException {
|
||||||
|
return toPrivateKey(keyFile, keyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TrustManagerFactory buildTrustManagerFactory0(
|
||||||
|
X509Certificate[] certCollection)
|
||||||
|
throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
|
||||||
|
return buildTrustManagerFactory(certCollection, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int boringSSLVerifyModeForServer(ClientAuth mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case NONE:
|
||||||
|
return BoringSSL.SSL_VERIFY_NONE;
|
||||||
|
case REQUIRE:
|
||||||
|
return BoringSSL.SSL_VERIFY_PEER | BoringSSL.SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
||||||
|
case OPTIONAL:
|
||||||
|
return BoringSSL.SSL_VERIFY_PEER;
|
||||||
|
default:
|
||||||
|
throw new Error(mode.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator, QuicheQuicSslEngine engine) {
|
||||||
|
nativeSslContext.retain();
|
||||||
|
long ssl = BoringSSL.SSL_new(nativeSslContext.address(), isServer(), engine.tlsHostName);
|
||||||
|
engineMap.put(ssl, engine);
|
||||||
|
long connection = connectionCreator.apply(ssl);
|
||||||
|
if (connection == -1) {
|
||||||
|
engineMap.remove(ssl);
|
||||||
|
// We retained before but as we don't create a QuicheQuicConnection and transfer ownership we need to
|
||||||
|
// explict call release again here.
|
||||||
|
nativeSslContext.release();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// The connection will call nativeSslContext.release() once it is freed.
|
||||||
|
return new QuicheQuicConnection(connection, ssl, engine, nativeSslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the given engine to this context
|
||||||
|
*
|
||||||
|
* @param engine the engine to add.
|
||||||
|
* @return the pointer address of this context.
|
||||||
|
*/
|
||||||
|
long add(QuicheQuicSslEngine engine) {
|
||||||
|
nativeSslContext.retain();
|
||||||
|
engine.connection.reattach(nativeSslContext);
|
||||||
|
engineMap.put(engine.connection.ssl, engine);
|
||||||
|
return nativeSslContext.address();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given engine from this context.
|
||||||
|
*
|
||||||
|
* @param engine the engine to remove.
|
||||||
|
*/
|
||||||
|
void remove(QuicheQuicSslEngine engine) {
|
||||||
|
QuicheQuicSslEngine removed = engineMap.remove(engine.connection.ssl);
|
||||||
|
assert removed == null || removed == engine;
|
||||||
|
engine.removeSessionFromCacheIfInvalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicClientSessionCache getSessionCache() {
|
||||||
|
return sessionCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClient() {
|
||||||
|
return !server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> cipherSuites() {
|
||||||
|
return Arrays.asList("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long sessionCacheSize() {
|
||||||
|
if (sessionCache != null) {
|
||||||
|
return sessionCache.getSessionCacheSize();
|
||||||
|
} else {
|
||||||
|
synchronized (this) {
|
||||||
|
return sessionCacheSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long sessionTimeout() {
|
||||||
|
if (sessionCache != null) {
|
||||||
|
return sessionCache.getSessionTimeout();
|
||||||
|
} else {
|
||||||
|
synchronized (this) {
|
||||||
|
return sessionTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
|
||||||
|
return apn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicSslEngine newEngine(ByteBufAllocator alloc) {
|
||||||
|
return new QuicheQuicSslEngine(this, null, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicSslEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
|
||||||
|
return new QuicheQuicSslEngine(this, peerHost, peerPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicSslSessionContext sessionContext() {
|
||||||
|
return sessionCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SslHandler newHandler(ByteBufAllocator alloc, Executor delegatedTaskExecutor) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SslHandler newHandler(ByteBufAllocator alloc, boolean startTls, Executor executor) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
|
||||||
|
Executor delegatedTaskExecutor) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort,
|
||||||
|
boolean startTls, Executor delegatedTaskExecutor) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
try {
|
||||||
|
nativeSslContext.release();
|
||||||
|
} finally {
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSessionTimeout(int seconds) throws IllegalArgumentException {
|
||||||
|
if (sessionCache != null) {
|
||||||
|
sessionCache.setSessionTimeout(seconds);
|
||||||
|
} else {
|
||||||
|
BoringSSL.SSLContext_setSessionCacheTimeout(nativeSslContext.address(), seconds);
|
||||||
|
this.sessionTimeout = seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSessionCacheSize(int size) throws IllegalArgumentException {
|
||||||
|
if (sessionCache != null) {
|
||||||
|
sessionCache.setSessionCacheSize(size);
|
||||||
|
} else {
|
||||||
|
BoringSSL.SSLContext_setSessionCacheSize(nativeSslContext.address(), size);
|
||||||
|
sessionCacheSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSessionTicketKeys(SslSessionTicketKey[] ticketKeys) {
|
||||||
|
sessionTicketCallback.setSessionTicketKeys(ticketKeys);
|
||||||
|
BoringSSL.SSLContext_setSessionTicketKeys(
|
||||||
|
nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator {
|
||||||
|
private final List<String> protocols;
|
||||||
|
|
||||||
|
QuicheQuicApplicationProtocolNegotiator(String... protocols) {
|
||||||
|
if (protocols == null) {
|
||||||
|
this.protocols = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
this.protocols = Collections.unmodifiableList(Arrays.asList(protocols));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> protocols() {
|
||||||
|
return protocols;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext {
|
||||||
|
private final QuicheQuicSslContext context;
|
||||||
|
|
||||||
|
QuicheQuicSslSessionContext(QuicheQuicSslContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getSession(byte[] sessionId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration<byte[]> getIds() {
|
||||||
|
return new Enumeration<byte[]>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasMoreElements() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] nextElement() {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSessionTimeout(int seconds) throws IllegalArgumentException {
|
||||||
|
context.setSessionTimeout(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSessionTimeout() {
|
||||||
|
return (int) context.sessionTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSessionCacheSize(int size) throws IllegalArgumentException {
|
||||||
|
context.setSessionCacheSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSessionCacheSize() {
|
||||||
|
return (int) context.sessionCacheSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTicketKeys(SslSessionTicketKey... keys) {
|
||||||
|
context.setSessionTicketKeys(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class NativeSslContext extends AbstractReferenceCounted {
|
||||||
|
private final long ctx;
|
||||||
|
|
||||||
|
NativeSslContext(long ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
long address() {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deallocate() {
|
||||||
|
BoringSSL.SSLContext_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted touch(Object hint) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NativeSslContext{" +
|
||||||
|
"ctx=" + ctx +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BoringSSLAsyncPrivateKeyMethodAdapter implements BoringSSLPrivateKeyMethod {
|
||||||
|
private final QuicheQuicSslEngineMap engineMap;
|
||||||
|
private final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
|
||||||
|
|
||||||
|
BoringSSLAsyncPrivateKeyMethodAdapter(QuicheQuicSslEngineMap engineMap,
|
||||||
|
BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
|
||||||
|
this.engineMap = engineMap;
|
||||||
|
this.privateKeyMethod = privateKeyMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sign(long ssl, int signatureAlgorithm, byte[] input, BiConsumer<byte[], Throwable> callback) {
|
||||||
|
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine == null) {
|
||||||
|
// May be null if it was destroyed in the meantime.
|
||||||
|
callback.accept(null, null);
|
||||||
|
} else {
|
||||||
|
privateKeyMethod.sign(engine, signatureAlgorithm, input).addListener(f -> {
|
||||||
|
Throwable cause = f.cause();
|
||||||
|
if (cause != null) {
|
||||||
|
callback.accept(null, cause);
|
||||||
|
} else {
|
||||||
|
callback.accept((byte[]) f.getNow(), null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decrypt(long ssl, byte[] input, BiConsumer<byte[], Throwable> callback) {
|
||||||
|
final QuicheQuicSslEngine engine = engineMap.get(ssl);
|
||||||
|
if (engine == null) {
|
||||||
|
// May be null if it was destroyed in the meantime.
|
||||||
|
callback.accept(null, null);
|
||||||
|
} else {
|
||||||
|
privateKeyMethod.decrypt(engine, input).addListener(f -> {
|
||||||
|
Throwable cause = f.cause();
|
||||||
|
if (cause != null) {
|
||||||
|
callback.accept(null, cause);
|
||||||
|
} else {
|
||||||
|
callback.accept((byte[]) f.getNow(), null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,565 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
|
import io.netty.handler.ssl.util.LazyJavaxX509Certificate;
|
||||||
|
import io.netty.handler.ssl.util.LazyX509Certificate;
|
||||||
|
import io.netty.util.NetUtil;
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import javax.net.ssl.SNIHostName;
|
||||||
|
import javax.net.ssl.SNIServerName;
|
||||||
|
import javax.net.ssl.SSLEngineResult;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSessionBindingEvent;
|
||||||
|
import javax.net.ssl.SSLSessionBindingListener;
|
||||||
|
import javax.net.ssl.SSLSessionContext;
|
||||||
|
import javax.security.cert.X509Certificate;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.LongFunction;
|
||||||
|
|
||||||
|
final class QuicheQuicSslEngine extends QuicSslEngine {
|
||||||
|
QuicheQuicSslContext ctx;
|
||||||
|
private final String peerHost;
|
||||||
|
private final int peerPort;
|
||||||
|
private final QuicheQuicSslSession session = new QuicheQuicSslSession();
|
||||||
|
private volatile Certificate[] localCertificateChain;
|
||||||
|
private List<SNIServerName> sniHostNames;
|
||||||
|
private boolean handshakeFinished;
|
||||||
|
private String applicationProtocol;
|
||||||
|
private boolean sessionReused;
|
||||||
|
final String tlsHostName;
|
||||||
|
volatile QuicheQuicConnection connection;
|
||||||
|
|
||||||
|
String sniHostname;
|
||||||
|
|
||||||
|
QuicheQuicSslEngine(QuicheQuicSslContext ctx, String peerHost, int peerPort) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.peerHost = peerHost;
|
||||||
|
this.peerPort = peerPort;
|
||||||
|
// Use SNI if peerHost was specified and a valid hostname
|
||||||
|
// See https://github.com/netty/netty/issues/4746
|
||||||
|
if (ctx.isClient() && isValidHostNameForSNI(peerHost)) {
|
||||||
|
tlsHostName = peerHost;
|
||||||
|
sniHostNames = Collections.singletonList(new SNIHostName(tlsHostName));
|
||||||
|
} else {
|
||||||
|
tlsHostName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long moveTo(String hostname, QuicheQuicSslContext ctx) {
|
||||||
|
// First of remove the engine from its previous QuicheQuicSslContext.
|
||||||
|
this.ctx.remove(this);
|
||||||
|
this.ctx = ctx;
|
||||||
|
long added = ctx.add(this);
|
||||||
|
sniHostname = hostname;
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicConnection createConnection(LongFunction<Long> connectionCreator) {
|
||||||
|
return ctx.createConnection(connectionCreator, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLocalCertificateChain(Certificate[] localCertificateChain) {
|
||||||
|
this.localCertificateChain = localCertificateChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the given hostname can be used in SNI extension.
|
||||||
|
*/
|
||||||
|
static boolean isValidHostNameForSNI(String hostname) {
|
||||||
|
return hostname != null &&
|
||||||
|
hostname.indexOf('.') > 0 &&
|
||||||
|
!hostname.endsWith(".") &&
|
||||||
|
!NetUtil.isValidIpV4Address(hostname) &&
|
||||||
|
!NetUtil.isValidIpV6Address(hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLParameters getSSLParameters() {
|
||||||
|
SSLParameters parameters = super.getSSLParameters();
|
||||||
|
parameters.setServerNames(sniHostNames);
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These method will override the method defined by Java 8u251 and later. As we may compile with an earlier
|
||||||
|
// java8 version we don't use @Override annotations here.
|
||||||
|
public synchronized String getApplicationProtocol() {
|
||||||
|
return applicationProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These method will override the method defined by Java 8u251 and later. As we may compile with an earlier
|
||||||
|
// java8 version we don't use @Override annotations here.
|
||||||
|
public synchronized String getHandshakeApplicationProtocol() {
|
||||||
|
return applicationProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Runnable getDelegatedTask() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeInbound() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInboundDone() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeOutbound() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutboundDone() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedCipherSuites() {
|
||||||
|
return ctx.cipherSuites().toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getEnabledCipherSuites() {
|
||||||
|
return getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabledCipherSuites(String[] suites) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getSupportedProtocols() {
|
||||||
|
// QUIC only supports TLSv1.3
|
||||||
|
return new String[] { "TLSv1.3" };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getEnabledProtocols() {
|
||||||
|
return getSupportedProtocols();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabledProtocols(String[] protocols) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSession getHandshakeSession() {
|
||||||
|
if (handshakeFinished) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginHandshake() {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
||||||
|
if (handshakeFinished) {
|
||||||
|
return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
|
||||||
|
}
|
||||||
|
return SSLEngineResult.HandshakeStatus.NEED_WRAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseClientMode(boolean clientMode) {
|
||||||
|
if (clientMode != ctx.isClient()) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getUseClientMode() {
|
||||||
|
return ctx.isClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNeedClientAuth(boolean b) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getNeedClientAuth() {
|
||||||
|
return ctx.clientAuth == ClientAuth.REQUIRE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWantClientAuth(boolean b) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getWantClientAuth() {
|
||||||
|
return ctx.clientAuth == ClientAuth.OPTIONAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnableSessionCreation(boolean flag) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getEnableSessionCreation() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||||
|
byte[][] peerCertificateChain,
|
||||||
|
long creationTime, long timeout,
|
||||||
|
byte[] applicationProtocol, boolean sessionReused) {
|
||||||
|
if (applicationProtocol == null) {
|
||||||
|
this.applicationProtocol = null;
|
||||||
|
} else {
|
||||||
|
this.applicationProtocol = new String(applicationProtocol);
|
||||||
|
}
|
||||||
|
session.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
|
||||||
|
this.sessionReused = sessionReused;
|
||||||
|
handshakeFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSessionFromCacheIfInvalid() {
|
||||||
|
session.removeFromCacheIfInvalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isSessionReused() {
|
||||||
|
return sessionReused;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class QuicheQuicSslSession implements SSLSession {
|
||||||
|
private X509Certificate[] x509PeerCerts;
|
||||||
|
private Certificate[] peerCerts;
|
||||||
|
private String protocol;
|
||||||
|
private String cipher;
|
||||||
|
private byte[] id;
|
||||||
|
private long creationTime = -1;
|
||||||
|
private long timeout = -1;
|
||||||
|
private boolean invalid;
|
||||||
|
private long lastAccessedTime = -1;
|
||||||
|
|
||||||
|
// lazy init for memory reasons
|
||||||
|
private Map<String, Object> values;
|
||||||
|
|
||||||
|
private boolean isEmpty(Object[] arr) {
|
||||||
|
return arr == null || arr.length == 0;
|
||||||
|
}
|
||||||
|
private boolean isEmpty(byte[] arr) {
|
||||||
|
return arr == null || arr.length == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate,
|
||||||
|
byte[][] peerCertificateChain, long creationTime, long timeout) {
|
||||||
|
synchronized (QuicheQuicSslEngine.this) {
|
||||||
|
initPeerCerts(peerCertificateChain, peerCertificate);
|
||||||
|
this.id = id;
|
||||||
|
this.cipher = cipher;
|
||||||
|
this.protocol = protocol;
|
||||||
|
this.creationTime = creationTime * 1000L;
|
||||||
|
this.timeout = timeout * 1000L;
|
||||||
|
lastAccessedTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFromCacheIfInvalid() {
|
||||||
|
if (!isValid()) {
|
||||||
|
// Shouldn't be re-used again
|
||||||
|
removeFromCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFromCache() {
|
||||||
|
// Shouldn't be re-used again
|
||||||
|
QuicClientSessionCache cache = ctx.getSessionCache();
|
||||||
|
if (cache != null) {
|
||||||
|
cache.removeSession(getPeerHost(), getPeerPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init peer certificates that can be obtained via {@link #getPeerCertificateChain()}
|
||||||
|
* and {@link #getPeerCertificates()}.
|
||||||
|
*/
|
||||||
|
private void initPeerCerts(byte[][] chain, byte[] clientCert) {
|
||||||
|
// Return the full chain from the JNI layer.
|
||||||
|
if (getUseClientMode()) {
|
||||||
|
if (isEmpty(chain)) {
|
||||||
|
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||||
|
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||||
|
} else {
|
||||||
|
peerCerts = new Certificate[chain.length];
|
||||||
|
x509PeerCerts = new X509Certificate[chain.length];
|
||||||
|
initCerts(chain, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer
|
||||||
|
// certificate. We use SSL_get_peer_certificate to get it in this case and add it to our
|
||||||
|
// array later.
|
||||||
|
//
|
||||||
|
// See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html
|
||||||
|
if (isEmpty(clientCert)) {
|
||||||
|
peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
|
||||||
|
x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
|
||||||
|
} else {
|
||||||
|
if (isEmpty(chain)) {
|
||||||
|
peerCerts = new Certificate[] {new LazyX509Certificate(clientCert)};
|
||||||
|
x509PeerCerts = new X509Certificate[] {new LazyJavaxX509Certificate(clientCert)};
|
||||||
|
} else {
|
||||||
|
peerCerts = new Certificate[chain.length + 1];
|
||||||
|
x509PeerCerts = new X509Certificate[chain.length + 1];
|
||||||
|
peerCerts[0] = new LazyX509Certificate(clientCert);
|
||||||
|
x509PeerCerts[0] = new LazyJavaxX509Certificate(clientCert);
|
||||||
|
initCerts(chain, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCerts(byte[][] chain, int startPos) {
|
||||||
|
for (int i = 0; i < chain.length; i++) {
|
||||||
|
int certPos = startPos + i;
|
||||||
|
peerCerts[certPos] = new LazyX509Certificate(chain[i]);
|
||||||
|
x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getId() {
|
||||||
|
synchronized (QuicheQuicSslSession.this) {
|
||||||
|
if (id == null) {
|
||||||
|
return EmptyArrays.EMPTY_BYTES;
|
||||||
|
}
|
||||||
|
return id.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSessionContext getSessionContext() {
|
||||||
|
return ctx.sessionContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCreationTime() {
|
||||||
|
synchronized (QuicheQuicSslEngine.this) {
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLastAccessedTime() {
|
||||||
|
return lastAccessedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidate() {
|
||||||
|
boolean removeFromCache;
|
||||||
|
synchronized (this) {
|
||||||
|
removeFromCache = !invalid;
|
||||||
|
invalid = true;
|
||||||
|
}
|
||||||
|
if (removeFromCache) {
|
||||||
|
removeFromCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
synchronized (QuicheQuicSslEngine.this) {
|
||||||
|
return !invalid && System.currentTimeMillis() - timeout < creationTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putValue(String name, Object value) {
|
||||||
|
ObjectUtil.checkNotNull(name, "name");
|
||||||
|
ObjectUtil.checkNotNull(value, "value");
|
||||||
|
|
||||||
|
final Object old;
|
||||||
|
synchronized (this) {
|
||||||
|
Map<String, Object> values = this.values;
|
||||||
|
if (values == null) {
|
||||||
|
// Use size of 2 to keep the memory overhead small
|
||||||
|
values = this.values = new HashMap<>(2);
|
||||||
|
}
|
||||||
|
old = values.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof SSLSessionBindingListener) {
|
||||||
|
// Use newSSLSessionBindingEvent so we alway use the wrapper if needed.
|
||||||
|
((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name));
|
||||||
|
}
|
||||||
|
notifyUnbound(old, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValue(String name) {
|
||||||
|
ObjectUtil.checkNotNull(name, "name");
|
||||||
|
synchronized (this) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return values.get(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeValue(String name) {
|
||||||
|
ObjectUtil.checkNotNull(name, "name");
|
||||||
|
|
||||||
|
final Object old;
|
||||||
|
synchronized (this) {
|
||||||
|
Map<String, Object> values = this.values;
|
||||||
|
if (values == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
old = values.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUnbound(old, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getValueNames() {
|
||||||
|
synchronized (this) {
|
||||||
|
Map<String, Object> values = this.values;
|
||||||
|
if (values == null || values.isEmpty()) {
|
||||||
|
return EmptyArrays.EMPTY_STRINGS;
|
||||||
|
}
|
||||||
|
return values.keySet().toArray(new String[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) {
|
||||||
|
return new SSLSessionBindingEvent(session, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyUnbound(Object value, String name) {
|
||||||
|
if (value instanceof SSLSessionBindingListener) {
|
||||||
|
// Use newSSLSessionBindingEvent so we alway use the wrapper if needed.
|
||||||
|
((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
|
||||||
|
synchronized (QuicheQuicSslEngine.this) {
|
||||||
|
if (isEmpty(peerCerts)) {
|
||||||
|
throw new SSLPeerUnverifiedException("peer not verified");
|
||||||
|
}
|
||||||
|
return peerCerts.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Certificate[] getLocalCertificates() {
|
||||||
|
Certificate[] localCerts = localCertificateChain;
|
||||||
|
if (localCerts == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return localCerts.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
|
||||||
|
synchronized (QuicheQuicSslEngine.this) {
|
||||||
|
if (isEmpty(x509PeerCerts)) {
|
||||||
|
throw new SSLPeerUnverifiedException("peer not verified");
|
||||||
|
}
|
||||||
|
return x509PeerCerts.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
|
||||||
|
Certificate[] peer = getPeerCertificates();
|
||||||
|
// No need for null or length > 0 is needed as this is done in getPeerCertificates()
|
||||||
|
// already.
|
||||||
|
return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getLocalPrincipal() {
|
||||||
|
Certificate[] local = localCertificateChain;
|
||||||
|
if (local == null || local.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCipherSuite() {
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPeerHost() {
|
||||||
|
return peerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeerPort() {
|
||||||
|
return peerPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPacketBufferSize() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getApplicationBufferSize() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
final class QuicheQuicSslEngineMap {
|
||||||
|
|
||||||
|
private final ConcurrentMap<Long, QuicheQuicSslEngine> engines = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
QuicheQuicSslEngine get(long ssl) {
|
||||||
|
return engines.get(ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuicheQuicSslEngine remove(long ssl) {
|
||||||
|
return engines.remove(ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(long ssl, QuicheQuicSslEngine engine) {
|
||||||
|
engines.put(ssl, engine);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,956 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelId;
|
||||||
|
import io.netty.channel.ChannelMetadata;
|
||||||
|
import io.netty.channel.ChannelOutboundBuffer;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.DefaultChannelId;
|
||||||
|
import io.netty.channel.DefaultChannelPipeline;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.channel.PendingWriteQueue;
|
||||||
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
|
import io.netty.channel.VoidChannelPromise;
|
||||||
|
import io.netty.channel.socket.ChannelInputShutdownEvent;
|
||||||
|
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
|
||||||
|
import io.netty.channel.socket.ChannelOutputShutdownException;
|
||||||
|
import io.netty.util.DefaultAttributeMap;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
import io.netty.util.concurrent.PromiseNotifier;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link QuicStreamChannel} implementation that uses <a href="https://github.com/cloudflare/quiche">quiche</a>.
|
||||||
|
*/
|
||||||
|
final class QuicheQuicStreamChannel extends DefaultAttributeMap implements QuicStreamChannel {
|
||||||
|
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
|
||||||
|
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(QuicheQuicStreamChannel.class);
|
||||||
|
private final QuicheQuicChannel parent;
|
||||||
|
private final ChannelId id;
|
||||||
|
private final ChannelPipeline pipeline;
|
||||||
|
private final QuicStreamChannelUnsafe unsafe;
|
||||||
|
private final ChannelPromise closePromise;
|
||||||
|
private final PendingWriteQueue queue;
|
||||||
|
|
||||||
|
private final QuicStreamChannelConfig config;
|
||||||
|
private final QuicStreamAddress address;
|
||||||
|
|
||||||
|
private boolean readable;
|
||||||
|
private boolean readPending;
|
||||||
|
private boolean inRecv;
|
||||||
|
private boolean inWriteQueued;
|
||||||
|
private boolean finReceived;
|
||||||
|
private boolean finSent;
|
||||||
|
|
||||||
|
private volatile boolean registered;
|
||||||
|
private volatile boolean writable = true;
|
||||||
|
private volatile boolean active = true;
|
||||||
|
private volatile boolean inputShutdown;
|
||||||
|
private volatile boolean outputShutdown;
|
||||||
|
private volatile QuicStreamPriority priority;
|
||||||
|
private volatile int capacity;
|
||||||
|
|
||||||
|
QuicheQuicStreamChannel(QuicheQuicChannel parent, long streamId) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.id = DefaultChannelId.newInstance();
|
||||||
|
unsafe = new QuicStreamChannelUnsafe();
|
||||||
|
this.pipeline = new DefaultChannelPipeline(this) {
|
||||||
|
// TODO: add some overrides maybe ?
|
||||||
|
};
|
||||||
|
config = new QuicheQuicStreamChannelConfig(this);
|
||||||
|
this.address = new QuicStreamAddress(streamId);
|
||||||
|
this.closePromise = newPromise();
|
||||||
|
queue = new PendingWriteQueue(this);
|
||||||
|
// Local created unidirectional streams have the input shutdown by spec. There will never be any data for
|
||||||
|
// these to be read.
|
||||||
|
if (parent.streamType(streamId) == QuicStreamType.UNIDIRECTIONAL && parent.isStreamLocalCreated(streamId)) {
|
||||||
|
inputShutdown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamAddress localAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamAddress remoteAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocalCreated() {
|
||||||
|
return parent().isStreamLocalCreated(streamId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamType type() {
|
||||||
|
return parent().streamType(streamId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long streamId() {
|
||||||
|
return address.streamId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamPriority priority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture updatePriority(QuicStreamPriority priority, ChannelPromise promise) {
|
||||||
|
if (eventLoop().inEventLoop()) {
|
||||||
|
updatePriority0(priority, promise);
|
||||||
|
} else {
|
||||||
|
eventLoop().execute(() -> updatePriority0(priority, promise));
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePriority0(QuicStreamPriority priority, ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
try {
|
||||||
|
parent().streamPriority(streamId(), (byte) priority.urgency(), priority.isIncremental());
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
promise.setFailure(cause);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.priority = priority;
|
||||||
|
promise.setSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return inputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdownOutput(ChannelPromise promise) {
|
||||||
|
if (eventLoop().inEventLoop()) {
|
||||||
|
shutdownOutput0(promise);
|
||||||
|
} else {
|
||||||
|
eventLoop().execute(() -> shutdownOutput0(promise));
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownOutput0(ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
outputShutdown = true;
|
||||||
|
unsafe.writeWithoutCheckChannelState(QuicStreamFrame.EMPTY_FIN, promise);
|
||||||
|
unsafe.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdownInput(int error, ChannelPromise promise) {
|
||||||
|
if (eventLoop().inEventLoop()) {
|
||||||
|
shutdownInput0(error, promise);
|
||||||
|
} else {
|
||||||
|
eventLoop().execute(() -> shutdownInput0(error, promise));
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdownOutput(int error, ChannelPromise promise) {
|
||||||
|
if (eventLoop().inEventLoop()) {
|
||||||
|
shutdownOutput0(error, promise);
|
||||||
|
} else {
|
||||||
|
eventLoop().execute(() -> shutdownOutput0(error, promise));
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicheQuicChannel parent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownInput0(int err, ChannelPromise channelPromise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
inputShutdown = true;
|
||||||
|
parent().streamShutdown(streamId(), true, false, err, channelPromise);
|
||||||
|
closeIfDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return outputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownOutput0(int error, ChannelPromise channelPromise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
parent().streamShutdown(streamId(), false, true, error, channelPromise);
|
||||||
|
outputShutdown = true;
|
||||||
|
closeIfDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return outputShutdown && inputShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdown(ChannelPromise channelPromise) {
|
||||||
|
if (eventLoop().inEventLoop()) {
|
||||||
|
shutdown0(channelPromise);
|
||||||
|
} else {
|
||||||
|
eventLoop().execute(() -> shutdown0(channelPromise));
|
||||||
|
}
|
||||||
|
return channelPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown0(ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
inputShutdown = true;
|
||||||
|
outputShutdown = true;
|
||||||
|
unsafe.writeWithoutCheckChannelState(QuicStreamFrame.EMPTY_FIN, unsafe.voidPromise());
|
||||||
|
unsafe.flush();
|
||||||
|
parent().streamShutdown(streamId(), true, false, 0, promise);
|
||||||
|
closeIfDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture shutdown(int error, ChannelPromise promise) {
|
||||||
|
if (eventLoop().inEventLoop()) {
|
||||||
|
shutdown0(error, promise);
|
||||||
|
} else {
|
||||||
|
eventLoop().execute(() -> shutdown0(error, promise));
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown0(int error, ChannelPromise channelPromise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
inputShutdown = true;
|
||||||
|
outputShutdown = true;
|
||||||
|
parent().streamShutdown(streamId(), true, true, error, channelPromise);
|
||||||
|
closeIfDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendFinIfNeeded() throws Exception {
|
||||||
|
if (!finSent) {
|
||||||
|
finSent = true;
|
||||||
|
parent().streamSendFin(streamId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeIfDone() {
|
||||||
|
if (finSent && (finReceived || type() == QuicStreamType.UNIDIRECTIONAL && isLocalCreated())) {
|
||||||
|
unsafe().close(unsafe().voidPromise());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeStreamFromParent() {
|
||||||
|
if (!active && finReceived) {
|
||||||
|
parent().streamClosed(streamId());
|
||||||
|
inputShutdown = true;
|
||||||
|
outputShutdown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannel flush() {
|
||||||
|
pipeline.flush();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannel read() {
|
||||||
|
pipeline.read();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig config() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelMetadata metadata() {
|
||||||
|
return METADATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelId id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventLoop eventLoop() {
|
||||||
|
return parent.eventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRegistered() {
|
||||||
|
return registered;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture closeFuture() {
|
||||||
|
return closePromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWritable() {
|
||||||
|
return writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long bytesBeforeUnwritable() {
|
||||||
|
// Capacity might be negative if the stream was closed.
|
||||||
|
return Math.max(capacity, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long bytesBeforeWritable() {
|
||||||
|
if (writable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Just return something positive for now
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Unsafe unsafe() {
|
||||||
|
return unsafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline pipeline() {
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBufAllocator alloc() {
|
||||||
|
return config.getAllocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Channel o) {
|
||||||
|
return id.compareTo(o.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of this channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the specified object is identical
|
||||||
|
* with this channel (i.e: {@code this == o}).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return this == o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[id: 0x" + id.asShortText() + ", " + address + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream is writable.
|
||||||
|
*/
|
||||||
|
boolean writable(@SuppressWarnings("unused") int capacity) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
this.capacity = capacity;
|
||||||
|
boolean mayNeedWrite = ((QuicStreamChannelUnsafe) unsafe()).writeQueued();
|
||||||
|
// we need to re-read this.capacity as writeQueued() may update the capacity.
|
||||||
|
updateWritabilityIfNeeded(this.capacity > 0);
|
||||||
|
return mayNeedWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWritabilityIfNeeded(boolean newWritable) {
|
||||||
|
if (writable != newWritable) {
|
||||||
|
writable = newWritable;
|
||||||
|
pipeline.fireChannelWritabilityChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream is readable.
|
||||||
|
*/
|
||||||
|
void readable() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
// Mark as readable and if a read is pending execute it.
|
||||||
|
readable = true;
|
||||||
|
if (readPending) {
|
||||||
|
((QuicStreamChannelUnsafe) unsafe()).recv();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void forceClose() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
// Set received to true to ensure we will remove it from the internal map once we send the fin.
|
||||||
|
finSent = true;
|
||||||
|
unsafe().close(unsafe().voidPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class QuicStreamChannelUnsafe implements Unsafe {
|
||||||
|
private RecvByteBufAllocator.Handle recvHandle;
|
||||||
|
|
||||||
|
private final ChannelPromise voidPromise = new VoidChannelPromise(
|
||||||
|
QuicheQuicStreamChannel.this, false);
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress remote, SocketAddress local, ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
promise.setFailure(new UnsupportedOperationException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public RecvByteBufAllocator.Handle recvBufAllocHandle() {
|
||||||
|
if (recvHandle == null) {
|
||||||
|
recvHandle = config.getRecvByteBufAllocator().newHandle();
|
||||||
|
}
|
||||||
|
return recvHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress localAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress remoteAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(EventLoop eventLoop, ChannelPromise promise) {
|
||||||
|
assert eventLoop.inEventLoop();
|
||||||
|
if (registered) {
|
||||||
|
promise.setFailure(new IllegalStateException());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eventLoop != parent.eventLoop()) {
|
||||||
|
promise.setFailure(new IllegalArgumentException());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registered = true;
|
||||||
|
promise.setSuccess();
|
||||||
|
pipeline.fireChannelRegistered();
|
||||||
|
pipeline.fireChannelActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(SocketAddress localAddress, ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
promise.setFailure(new UnsupportedOperationException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect(ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
close(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
if (!active || closePromise.isDone()) {
|
||||||
|
if (promise.isVoid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closePromise.addListener(new PromiseNotifier<>(promise));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
active = false;
|
||||||
|
try {
|
||||||
|
// Close the channel and fail the queued messages in all cases.
|
||||||
|
sendFinIfNeeded();
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// Just ignore
|
||||||
|
} finally {
|
||||||
|
if (!queue.isEmpty()) {
|
||||||
|
// Only fail if the queue is non-empty.
|
||||||
|
queue.removeAndFailAll(new ClosedChannelException());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.trySuccess();
|
||||||
|
closePromise.trySuccess();
|
||||||
|
if (type() == QuicStreamType.UNIDIRECTIONAL && isLocalCreated()) {
|
||||||
|
inputShutdown = true;
|
||||||
|
outputShutdown = true;
|
||||||
|
// If its an unidirectional stream and was created locally it is safe to close the stream now as
|
||||||
|
// we will never receive data from the other side.
|
||||||
|
parent().streamClosed(streamId());
|
||||||
|
} else {
|
||||||
|
removeStreamFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inWriteQueued) {
|
||||||
|
invokeLater(() -> deregister(voidPromise(), true));
|
||||||
|
} else {
|
||||||
|
deregister(voidPromise(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
if (!promise.setUncancellable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registered) {
|
||||||
|
promise.trySuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As a user may call deregister() from within any method while doing processing in the ChannelPipeline,
|
||||||
|
// we need to ensure we do the actual deregister operation later. This is needed as for example,
|
||||||
|
// we may be in the ByteToMessageDecoder.callDecode(...) method and so still try to do processing in
|
||||||
|
// the old EventLoop while the user already registered the Channel to a new EventLoop. Without delay,
|
||||||
|
// the deregister operation this could lead to have a handler invoked by different EventLoop and so
|
||||||
|
// threads.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// https://github.com/netty/netty/issues/4435
|
||||||
|
invokeLater(() -> {
|
||||||
|
if (fireChannelInactive) {
|
||||||
|
pipeline.fireChannelInactive();
|
||||||
|
}
|
||||||
|
// Some transports like local and AIO does not allow the deregistration of
|
||||||
|
// an open channel. Their doDeregister() calls close(). Consequently,
|
||||||
|
// close() calls deregister() again - no need to fire channelUnregistered, so check
|
||||||
|
// if it was registered.
|
||||||
|
if (registered) {
|
||||||
|
registered = false;
|
||||||
|
pipeline.fireChannelUnregistered();
|
||||||
|
}
|
||||||
|
promise.setSuccess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeLater(Runnable task) {
|
||||||
|
try {
|
||||||
|
// This method is used by outbound operation implementations to trigger an inbound event later.
|
||||||
|
// They do not trigger an inbound event immediately because an outbound operation might have been
|
||||||
|
// triggered by another inbound event handler method. If fired immediately, the call stack
|
||||||
|
// will look like this for example:
|
||||||
|
//
|
||||||
|
// handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
|
||||||
|
// -> handlerA.ctx.close()
|
||||||
|
// -> channel.unsafe.close()
|
||||||
|
// -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet
|
||||||
|
//
|
||||||
|
// which means the execution of two inbound handler methods of the same handler overlap undesirably.
|
||||||
|
eventLoop().execute(task);
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
LOGGER.warn("Can't invoke task later as EventLoop rejected it", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeForcibly() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
close(unsafe().voidPromise());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deregister(ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
deregister(promise, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginRead() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
readPending = true;
|
||||||
|
if (readable) {
|
||||||
|
((QuicStreamChannelUnsafe) unsafe()).recv();
|
||||||
|
|
||||||
|
// As the stream was readable, and we called recv() ourselves we also need to call
|
||||||
|
// connectionSendAndFlush(). This is needed as recv() might consume data and so a window update
|
||||||
|
// frame might be produced. If we miss to call connectionSendAndFlush() we might never send the update
|
||||||
|
// to the remote peer and so the remote peer might never attempt to send more data.
|
||||||
|
// See also https://docs.rs/quiche/latest/quiche/struct.Connection.html#method.send.
|
||||||
|
parent().connectionSendAndFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeIfNeeded(boolean wasFinSent) {
|
||||||
|
// Let's check if we should close the channel now.
|
||||||
|
// If it's a unidirectional channel we can close it as there will be no fin that we can read
|
||||||
|
// from the remote peer. If its an bidirectional channel we should only close the channel if we
|
||||||
|
// also received the fin from the remote peer.
|
||||||
|
if (!wasFinSent && QuicheQuicStreamChannel.this.finSent
|
||||||
|
&& (type() == QuicStreamType.UNIDIRECTIONAL || finReceived)) {
|
||||||
|
// close the channel now
|
||||||
|
close(voidPromise());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean writeQueued() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
boolean wasFinSent = QuicheQuicStreamChannel.this.finSent;
|
||||||
|
inWriteQueued = true;
|
||||||
|
try {
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean written = false;
|
||||||
|
for (;;) {
|
||||||
|
Object msg = queue.current();
|
||||||
|
if (msg == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!write0(msg)) {
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e instanceof QuicException && (
|
||||||
|
(QuicException) e).error() == QuicError.STREAM_STOPPED) {
|
||||||
|
// Once its signaled that the stream is stopped we can just fail everything.
|
||||||
|
queue.removeAndFailAll(e);
|
||||||
|
forceClose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
queue.remove().setFailure(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
queue.remove().setSuccess();
|
||||||
|
written = true;
|
||||||
|
}
|
||||||
|
updateWritabilityIfNeeded(true);
|
||||||
|
return written;
|
||||||
|
} finally {
|
||||||
|
closeIfNeeded(wasFinSent);
|
||||||
|
inWriteQueued = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(Object msg, ChannelPromise promise) {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
|
||||||
|
// Check first if the Channel is in a state in which it will accept writes, if not fail everything
|
||||||
|
// with the right exception
|
||||||
|
if (!isOpen()) {
|
||||||
|
queueAndFailAll(msg, promise, new ClosedChannelException());
|
||||||
|
} else if (finSent) {
|
||||||
|
queueAndFailAll(msg, promise, new ChannelOutputShutdownException("Fin was sent already"));
|
||||||
|
} else {
|
||||||
|
writeWithoutCheckChannelState(msg, promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queueAndFailAll(Object msg, ChannelPromise promise, Throwable cause) {
|
||||||
|
queue.add(msg, promise);
|
||||||
|
queue.removeAndFailAll(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeWithoutCheckChannelState(Object msg, ChannelPromise promise) {
|
||||||
|
if (msg instanceof ByteBuf) {
|
||||||
|
ByteBuf buffer = (ByteBuf) msg;
|
||||||
|
if (!buffer.isDirect()) {
|
||||||
|
ByteBuf tmpBuffer = alloc().directBuffer(buffer.readableBytes());
|
||||||
|
tmpBuffer.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||||
|
buffer.release();
|
||||||
|
msg = tmpBuffer;
|
||||||
|
}
|
||||||
|
} else if (msg instanceof QuicStreamFrame) {
|
||||||
|
QuicStreamFrame frame = (QuicStreamFrame) msg;
|
||||||
|
ByteBuf buffer = frame.content();
|
||||||
|
if (!buffer.isDirect()) {
|
||||||
|
ByteBuf tmpBuffer = alloc().directBuffer(buffer.readableBytes());
|
||||||
|
tmpBuffer.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
|
||||||
|
buffer.release();
|
||||||
|
msg = frame.replace(tmpBuffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ReferenceCountUtil.release(msg);
|
||||||
|
promise.setFailure(new UnsupportedOperationException(
|
||||||
|
"unsupported message type: " + StringUtil.simpleClassName(msg)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wasFinSent = QuicheQuicStreamChannel.this.finSent;
|
||||||
|
boolean mayNeedWritabilityUpdate = false;
|
||||||
|
try {
|
||||||
|
if (write0(msg)) {
|
||||||
|
ReferenceCountUtil.release(msg);
|
||||||
|
promise.setSuccess();
|
||||||
|
mayNeedWritabilityUpdate = capacity == 0;
|
||||||
|
} else {
|
||||||
|
queue.add(msg, promise);
|
||||||
|
mayNeedWritabilityUpdate = true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
ReferenceCountUtil.release(msg);
|
||||||
|
promise.setFailure(e);
|
||||||
|
mayNeedWritabilityUpdate = capacity == 0;
|
||||||
|
} finally {
|
||||||
|
if (mayNeedWritabilityUpdate) {
|
||||||
|
updateWritabilityIfNeeded(false);
|
||||||
|
}
|
||||||
|
closeIfNeeded(wasFinSent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean write0(Object msg) throws Exception {
|
||||||
|
if (type() == QuicStreamType.UNIDIRECTIONAL && !isLocalCreated()) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Writes on non-local created streams that are unidirectional are not supported");
|
||||||
|
}
|
||||||
|
if (finSent) {
|
||||||
|
throw new ChannelOutputShutdownException("Fin was sent already");
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean fin;
|
||||||
|
ByteBuf buffer;
|
||||||
|
if (msg instanceof ByteBuf) {
|
||||||
|
fin = false;
|
||||||
|
buffer = (ByteBuf) msg;
|
||||||
|
} else {
|
||||||
|
QuicStreamFrame frame = (QuicStreamFrame) msg;
|
||||||
|
fin = frame.hasFin();
|
||||||
|
buffer = frame.content();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean readable = buffer.isReadable();
|
||||||
|
if (!fin && !readable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean sendSomething = false;
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
int res = parent().streamSend(streamId(), buffer, fin);
|
||||||
|
|
||||||
|
// Update the capacity as well.
|
||||||
|
int cap = parent.streamCapacity(streamId());
|
||||||
|
if (cap >= 0) {
|
||||||
|
capacity = cap;
|
||||||
|
}
|
||||||
|
if (Quiche.throwIfError(res) || (readable && res == 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sendSomething = true;
|
||||||
|
buffer.skipBytes(res);
|
||||||
|
} while (buffer.isReadable());
|
||||||
|
|
||||||
|
if (fin) {
|
||||||
|
finSent = true;
|
||||||
|
outputShutdown = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
// As we called quiche_conn_stream_send(...) we need to ensure we will call quiche_conn_send(...) either
|
||||||
|
// now or we will do so once we see the channelReadComplete event.
|
||||||
|
//
|
||||||
|
// See https://docs.rs/quiche/0.6.0/quiche/struct.Connection.html#method.send
|
||||||
|
if (sendSomething) {
|
||||||
|
parent.connectionSendAndFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
// NOOP.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPromise voidPromise() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
return voidPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelOutboundBuffer outboundBuffer() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeOnRead(ChannelPipeline pipeline, boolean readFrames) {
|
||||||
|
if (readFrames && finReceived && finSent) {
|
||||||
|
close(voidPromise());
|
||||||
|
} else if (config.isAllowHalfClosure()) {
|
||||||
|
if (finReceived) {
|
||||||
|
// If we receive a fin there will be no more data to read so we need to fire both events
|
||||||
|
// to be consistent with other transports.
|
||||||
|
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
|
||||||
|
pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
|
||||||
|
if (finSent) {
|
||||||
|
// This was an unidirectional stream which means as soon as we received FIN and sent a FIN
|
||||||
|
// we need close the connection.
|
||||||
|
close(voidPromise());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This was an unidirectional stream which means as soon as we received FIN we need
|
||||||
|
// close the connection.
|
||||||
|
close(voidPromise());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReadException(ChannelPipeline pipeline, ByteBuf byteBuf, Throwable cause,
|
||||||
|
@SuppressWarnings("deprecation") RecvByteBufAllocator.Handle allocHandle,
|
||||||
|
boolean readFrames) {
|
||||||
|
if (byteBuf != null) {
|
||||||
|
if (byteBuf.isReadable()) {
|
||||||
|
pipeline.fireChannelRead(byteBuf);
|
||||||
|
} else {
|
||||||
|
byteBuf.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readComplete(allocHandle, pipeline);
|
||||||
|
pipeline.fireExceptionCaught(cause);
|
||||||
|
if (finReceived) {
|
||||||
|
closeOnRead(pipeline, readFrames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recv() {
|
||||||
|
assert eventLoop().inEventLoop();
|
||||||
|
if (inRecv) {
|
||||||
|
// As the use may call read() we need to guard against re-entrancy here as otherwise it could
|
||||||
|
// be possible that we re-enter this method while still processing it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inRecv = true;
|
||||||
|
try {
|
||||||
|
ChannelPipeline pipeline = pipeline();
|
||||||
|
QuicheQuicStreamChannelConfig config = (QuicheQuicStreamChannelConfig) config();
|
||||||
|
// Directly access the DirectIoByteBufAllocator as we need an direct buffer to read into in all cases
|
||||||
|
// even if there is no Unsafe present and the direct buffer is not pooled.
|
||||||
|
DirectIoByteBufAllocator allocator = config.allocator;
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
RecvByteBufAllocator.Handle allocHandle = this.recvBufAllocHandle();
|
||||||
|
boolean readFrames = config.isReadFrames();
|
||||||
|
|
||||||
|
// We should loop as long as a read() was requested and there is anything left to read, which means the
|
||||||
|
// stream was marked as readable before.
|
||||||
|
while (active && readPending && readable) {
|
||||||
|
allocHandle.reset(config);
|
||||||
|
ByteBuf byteBuf = null;
|
||||||
|
QuicheQuicChannel parent = parent();
|
||||||
|
// It's possible that the stream was marked as finish while we iterated over the readable streams
|
||||||
|
// or while we did have auto read disabled. If so we need to ensure we not try to read from it as it
|
||||||
|
// would produce an error.
|
||||||
|
boolean readCompleteNeeded = false;
|
||||||
|
boolean continueReading = true;
|
||||||
|
try {
|
||||||
|
while (!finReceived && continueReading) {
|
||||||
|
byteBuf = allocHandle.allocate(allocator);
|
||||||
|
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
|
||||||
|
switch (parent.streamRecv(streamId(), byteBuf)) {
|
||||||
|
case DONE:
|
||||||
|
// Nothing left to read;
|
||||||
|
readable = false;
|
||||||
|
break;
|
||||||
|
case FIN:
|
||||||
|
// If we received a FIN we also should mark the channel as non readable as
|
||||||
|
// there is nothing left to read really.
|
||||||
|
readable = false;
|
||||||
|
finReceived = true;
|
||||||
|
inputShutdown = true;
|
||||||
|
break;
|
||||||
|
case OK:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
allocHandle.lastBytesRead(byteBuf.readableBytes());
|
||||||
|
if (allocHandle.lastBytesRead() <= 0) {
|
||||||
|
byteBuf.release();
|
||||||
|
if (finReceived && readFrames) {
|
||||||
|
// If we read QuicStreamFrames we should fire an frame through the pipeline
|
||||||
|
// with an empty buffer but the fin flag set to true.
|
||||||
|
byteBuf = Unpooled.EMPTY_BUFFER;
|
||||||
|
} else {
|
||||||
|
byteBuf = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We did read one message.
|
||||||
|
allocHandle.incMessagesRead(1);
|
||||||
|
readCompleteNeeded = true;
|
||||||
|
|
||||||
|
// It's important that we reset this to false before we call fireChannelRead(...)
|
||||||
|
// as the user may request another read() from channelRead(...) callback.
|
||||||
|
readPending = false;
|
||||||
|
|
||||||
|
if (readFrames) {
|
||||||
|
pipeline.fireChannelRead(new DefaultQuicStreamFrame(byteBuf, finReceived));
|
||||||
|
} else {
|
||||||
|
pipeline.fireChannelRead(byteBuf);
|
||||||
|
}
|
||||||
|
byteBuf = null;
|
||||||
|
continueReading = allocHandle.continueReading();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readCompleteNeeded) {
|
||||||
|
readComplete(allocHandle, pipeline);
|
||||||
|
}
|
||||||
|
if (finReceived) {
|
||||||
|
readable = false;
|
||||||
|
closeOnRead(pipeline, readFrames);
|
||||||
|
}
|
||||||
|
} catch (Throwable cause) {
|
||||||
|
readable = false;
|
||||||
|
handleReadException(pipeline, byteBuf, cause, allocHandle, readFrames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// About to leave the method lets reset so we can enter it again.
|
||||||
|
inRecv = false;
|
||||||
|
removeStreamFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read was complete and something was read, so we we need to reset the readPending flags, the allocHandle
|
||||||
|
// and call fireChannelReadComplete(). The user may schedule another read now.
|
||||||
|
private void readComplete(@SuppressWarnings("deprecation") RecvByteBufAllocator.Handle allocHandle,
|
||||||
|
ChannelPipeline pipeline) {
|
||||||
|
allocHandle.readComplete();
|
||||||
|
pipeline.fireChannelReadComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.DefaultChannelConfig;
|
||||||
|
import io.netty.channel.MessageSizeEstimator;
|
||||||
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
|
import io.netty.channel.WriteBufferWaterMark;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
final class QuicheQuicStreamChannelConfig extends DefaultChannelConfig implements QuicStreamChannelConfig {
|
||||||
|
// We should use half-closure sementatics by default as this is what QUIC does by default.
|
||||||
|
// If you receive a FIN you should still keep the stream open until you write a FIN as well.
|
||||||
|
private volatile boolean allowHalfClosure = true;
|
||||||
|
private volatile boolean readFrames;
|
||||||
|
volatile DirectIoByteBufAllocator allocator;
|
||||||
|
|
||||||
|
QuicheQuicStreamChannelConfig(QuicStreamChannel channel) {
|
||||||
|
super(channel);
|
||||||
|
allocator = new DirectIoByteBufAllocator(super.getAllocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<ChannelOption<?>, Object> getOptions() {
|
||||||
|
if (isHalfClosureSupported()) {
|
||||||
|
return getOptions(super.getOptions(), ChannelOption.ALLOW_HALF_CLOSURE, QuicChannelOption.READ_FRAMES);
|
||||||
|
}
|
||||||
|
return super.getOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T getOption(ChannelOption<T> option) {
|
||||||
|
if (option == ChannelOption.ALLOW_HALF_CLOSURE) {
|
||||||
|
return (T) Boolean.valueOf(isAllowHalfClosure());
|
||||||
|
}
|
||||||
|
if (option == QuicChannelOption.READ_FRAMES) {
|
||||||
|
return (T) Boolean.valueOf(isReadFrames());
|
||||||
|
}
|
||||||
|
return super.getOption(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> boolean setOption(ChannelOption<T> option, T value) {
|
||||||
|
validate(option, value);
|
||||||
|
|
||||||
|
if (option == ChannelOption.ALLOW_HALF_CLOSURE) {
|
||||||
|
if (isHalfClosureSupported()) {
|
||||||
|
setAllowHalfClosure((Boolean) value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (option == QuicChannelOption.READ_FRAMES) {
|
||||||
|
setReadFrames((Boolean) value);
|
||||||
|
}
|
||||||
|
return super.setOption(option, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setReadFrames(boolean readFrames) {
|
||||||
|
this.readFrames = readFrames;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadFrames() {
|
||||||
|
return readFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||||
|
super.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) {
|
||||||
|
super.setMaxMessagesPerRead(maxMessagesPerRead);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setWriteSpinCount(int writeSpinCount) {
|
||||||
|
super.setWriteSpinCount(writeSpinCount);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setAllocator(ByteBufAllocator allocator) {
|
||||||
|
this.allocator = new DirectIoByteBufAllocator(allocator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) {
|
||||||
|
super.setRecvByteBufAllocator(allocator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setAutoRead(boolean autoRead) {
|
||||||
|
super.setAutoRead(autoRead);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setAutoClose(boolean autoClose) {
|
||||||
|
super.setAutoClose(autoClose);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
|
||||||
|
super.setWriteBufferHighWaterMark(writeBufferHighWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
|
||||||
|
super.setWriteBufferLowWaterMark(writeBufferLowWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||||
|
super.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) {
|
||||||
|
super.setMessageSizeEstimator(estimator);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QuicStreamChannelConfig setAllowHalfClosure(boolean allowHalfClosure) {
|
||||||
|
if (!isHalfClosureSupported()) {
|
||||||
|
throw new UnsupportedOperationException("Undirectional streams don't support half-closure");
|
||||||
|
}
|
||||||
|
this.allowHalfClosure = allowHalfClosure;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBufAllocator getAllocator() {
|
||||||
|
return allocator.wrapped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowHalfClosure() {
|
||||||
|
return allowHalfClosure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHalfClosureSupported() {
|
||||||
|
return ((QuicStreamChannel) channel).type() == QuicStreamType.BIDIRECTIONAL;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
|
final class QuicheQuicTransportParameters implements QuicTransportParameters {
|
||||||
|
private final long[] values;
|
||||||
|
|
||||||
|
QuicheQuicTransportParameters(long[] values) {
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long maxIdleTimeout() {
|
||||||
|
return values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long maxUdpPayloadSize() {
|
||||||
|
return values[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long initialMaxData() {
|
||||||
|
return values[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long initialMaxStreamDataBidiLocal() {
|
||||||
|
return values[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long initialMaxStreamDataBidiRemote() {
|
||||||
|
return values[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long initialMaxStreamDataUni() {
|
||||||
|
return values[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long initialMaxStreamsBidi() {
|
||||||
|
return values[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long initialMaxStreamsUni() {
|
||||||
|
return values[7];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long ackDelayExponent() {
|
||||||
|
return values[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long maxAckDelay() {
|
||||||
|
return values[9];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean disableActiveMigration() {
|
||||||
|
return values[10] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long activeConnIdLimit() {
|
||||||
|
return values[11];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long maxDatagramFrameSize() {
|
||||||
|
return values[12];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StringUtil.simpleClassName(this) + "[" +
|
||||||
|
"maxIdleTimeout=" + maxIdleTimeout() +
|
||||||
|
", maxUdpPayloadSize=" + maxUdpPayloadSize() +
|
||||||
|
", initialMaxData=" + initialMaxData() +
|
||||||
|
", initialMaxStreamDataBidiLocal=" + initialMaxStreamDataBidiLocal() +
|
||||||
|
", initialMaxStreamDataBidiRemote=" + initialMaxStreamDataBidiRemote() +
|
||||||
|
", initialMaxStreamDataUni=" + initialMaxStreamDataUni() +
|
||||||
|
", initialMaxStreamsBidi=" + initialMaxStreamsBidi() +
|
||||||
|
", initialMaxStreamsUni=" + initialMaxStreamsUni() +
|
||||||
|
", ackDelayExponent=" + ackDelayExponent() +
|
||||||
|
", maxAckDelay=" + maxAckDelay() +
|
||||||
|
", disableActiveMigration=" + disableActiveMigration() +
|
||||||
|
", activeConnIdLimit=" + activeConnIdLimit() +
|
||||||
|
", maxDatagramFrameSize=" + maxDatagramFrameSize() +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to handle access to {@code quiche_recv_info}.
|
||||||
|
*/
|
||||||
|
final class QuicheRecvInfo {
|
||||||
|
|
||||||
|
private QuicheRecvInfo() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link InetSocketAddress} into the {@code quiche_recv_info} struct.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* typedef struct {
|
||||||
|
* struct sockaddr *from;
|
||||||
|
* socklen_t from_len;
|
||||||
|
* struct sockaddr *to;
|
||||||
|
* socklen_t to_len;
|
||||||
|
* } quiche_recv_info;
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param memory the memory of {@code quiche_recv_info}.
|
||||||
|
* @param from the {@link InetSocketAddress} to write into {@code quiche_recv_info}.
|
||||||
|
* @param to the {@link InetSocketAddress} to write into {@code quiche_recv_info}.
|
||||||
|
*/
|
||||||
|
static void setRecvInfo(ByteBuffer memory, InetSocketAddress from, InetSocketAddress to) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
setAddress(memory, Quiche.SIZEOF_QUICHE_RECV_INFO, Quiche.QUICHE_RECV_INFO_OFFSETOF_FROM, Quiche.QUICHE_RECV_INFO_OFFSETOF_FROM_LEN, from);
|
||||||
|
setAddress(memory, Quiche.SIZEOF_QUICHE_RECV_INFO + Quiche.SIZEOF_SOCKADDR_STORAGE,
|
||||||
|
Quiche.QUICHE_RECV_INFO_OFFSETOF_TO, Quiche.QUICHE_RECV_INFO_OFFSETOF_TO_LEN, to);
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setAddress(ByteBuffer memory, int socketAddressOffset, int addrOffset, int lenOffset, InetSocketAddress address) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
int sockaddrPosition = position +socketAddressOffset;
|
||||||
|
memory.position(sockaddrPosition);
|
||||||
|
long sockaddrMemoryAddress = Quiche.memoryAddressWithPosition(memory);
|
||||||
|
int len = SockaddrIn.setAddress(memory, address);
|
||||||
|
if (Quiche.SIZEOF_SIZE_T == 4) {
|
||||||
|
memory.putInt(position + addrOffset, (int) sockaddrMemoryAddress);
|
||||||
|
} else {
|
||||||
|
memory.putLong(position + addrOffset, sockaddrMemoryAddress);
|
||||||
|
}
|
||||||
|
Quiche.setPrimitiveValue(memory, position + lenOffset, Quiche.SIZEOF_SOCKLEN_T, len);
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored.
|
||||||
|
*
|
||||||
|
* @param memory the first {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||||
|
* @param memory2 the second {@link ByteBuffer} which holds a {@code quiche_recv_info}.
|
||||||
|
* @return {@code true} if both {@link ByteBuffer}s have the same {@code sock_addr} stored, {@code false}
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
static boolean isSameAddress(ByteBuffer memory, ByteBuffer memory2) {
|
||||||
|
return Quiche.isSameAddress(memory, memory2, Quiche.SIZEOF_QUICHE_RECV_INFO);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.concurrent.FastThreadLocal;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to handle access to {@code quiche_send_info}.
|
||||||
|
*/
|
||||||
|
final class QuicheSendInfo {
|
||||||
|
|
||||||
|
private static final FastThreadLocal<byte[]> IPV4_ARRAYS = new FastThreadLocal<byte[]>() {
|
||||||
|
@Override
|
||||||
|
protected byte[] initialValue() {
|
||||||
|
return new byte[SockaddrIn.IPV4_ADDRESS_LENGTH];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final FastThreadLocal<byte[]> IPV6_ARRAYS = new FastThreadLocal<byte[]>() {
|
||||||
|
@Override
|
||||||
|
protected byte[] initialValue() {
|
||||||
|
return new byte[SockaddrIn.IPV6_ADDRESS_LENGTH];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final byte[] TIMESPEC_ZEROOUT = new byte[Quiche.SIZEOF_TIMESPEC];
|
||||||
|
|
||||||
|
private QuicheSendInfo() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link InetSocketAddress} out of the {@code quiche_send_info} struct.
|
||||||
|
*
|
||||||
|
* @param memory the memory of {@code quiche_send_info}.
|
||||||
|
* @return the address that was read.
|
||||||
|
*/
|
||||||
|
static InetSocketAddress getToAddress(ByteBuffer memory) {
|
||||||
|
return getAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO_LEN, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static InetSocketAddress getFromAddress(ByteBuffer memory) {
|
||||||
|
return getAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM_LEN, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InetSocketAddress getAddress(ByteBuffer memory, int lenOffset, int addressOffset) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
long len = getLen(memory, position + lenOffset);
|
||||||
|
|
||||||
|
memory.position(position + addressOffset);
|
||||||
|
|
||||||
|
if (len == Quiche.SIZEOF_SOCKADDR_IN) {
|
||||||
|
return SockaddrIn.getIPv4(memory, IPV4_ARRAYS.get());
|
||||||
|
}
|
||||||
|
assert len == Quiche.SIZEOF_SOCKADDR_IN6;
|
||||||
|
return SockaddrIn.getIPv6(memory, IPV6_ARRAYS.get(), IPV4_ARRAYS.get());
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getLen(ByteBuffer memory, int index) {
|
||||||
|
return Quiche.getPrimitiveValue(memory, index, Quiche.SIZEOF_SOCKLEN_T);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link InetSocketAddress} into the {@code quiche_send_info} struct.
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* typedef struct {
|
||||||
|
* // The local address the packet should be sent from.
|
||||||
|
* struct sockaddr_storage from;
|
||||||
|
* socklen_t from_len;
|
||||||
|
*
|
||||||
|
* // The address the packet should be sent to.
|
||||||
|
* struct sockaddr_storage to;
|
||||||
|
* socklen_t to_len;
|
||||||
|
*
|
||||||
|
* // The time to send the packet out.
|
||||||
|
* struct timespec at;
|
||||||
|
* } quiche_send_info;
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param memory the memory of {@code quiche_send_info}.
|
||||||
|
* @param from the {@link InetSocketAddress} to write into {@code quiche_send_info}.
|
||||||
|
* @param to the {@link InetSocketAddress} to write into {@code quiche_send_info}.
|
||||||
|
*/
|
||||||
|
static void setSendInfo(ByteBuffer memory, InetSocketAddress from, InetSocketAddress to) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
setAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM, Quiche.QUICHE_SEND_INFO_OFFSETOF_FROM_LEN, from);
|
||||||
|
setAddress(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO_LEN, to);
|
||||||
|
// Zero out the timespec.
|
||||||
|
memory.position(position + Quiche.QUICHE_SEND_INFO_OFFSETOF_AT);
|
||||||
|
memory.put(TIMESPEC_ZEROOUT);
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setAddress(ByteBuffer memory, int addrOffset, int lenOffset, InetSocketAddress addr) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
memory.position(position + addrOffset);
|
||||||
|
int len = SockaddrIn.setAddress(memory, addr);
|
||||||
|
Quiche.setPrimitiveValue(memory, position + lenOffset, Quiche.SIZEOF_SOCKLEN_T, len);
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@code timespec} from the {@code quiche_send_info} struct in nanos.
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* typedef struct {
|
||||||
|
* // The local address the packet should be sent from.
|
||||||
|
* struct sockaddr_storage from;
|
||||||
|
* socklen_t from_len;
|
||||||
|
*
|
||||||
|
* // The address the packet should be sent to.
|
||||||
|
* struct sockaddr_storage to;
|
||||||
|
* socklen_t to_len;
|
||||||
|
*
|
||||||
|
* // The time to send the packet out.
|
||||||
|
* struct timespec at;
|
||||||
|
* } quiche_send_info;
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param memory the memory of {@code quiche_send_info}.
|
||||||
|
*/
|
||||||
|
static long getAtNanos(ByteBuffer memory) {
|
||||||
|
long sec = Quiche.getPrimitiveValue(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_AT +
|
||||||
|
Quiche.TIMESPEC_OFFSETOF_TV_SEC, Quiche.SIZEOF_TIME_T);
|
||||||
|
long nsec = Quiche.getPrimitiveValue(memory, Quiche.QUICHE_SEND_INFO_OFFSETOF_AT +
|
||||||
|
Quiche.TIMESPEC_OFFSETOF_TV_SEC, Quiche.SIZEOF_LONG);
|
||||||
|
return TimeUnit.SECONDS.toNanos(sec) + nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if both {@link ByteBuffer}s have the same {@code sockaddr_storage} stored.
|
||||||
|
*
|
||||||
|
* @param memory the first {@link ByteBuffer} which holds a {@code quiche_send_info}.
|
||||||
|
* @param memory2 the second {@link ByteBuffer} which holds a {@code quiche_send_info}.
|
||||||
|
* @return {@code true} if both {@link ByteBuffer}s have the same {@code sockaddr_storage} stored,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
static boolean isSameAddress(ByteBuffer memory, ByteBuffer memory2) {
|
||||||
|
return Quiche.isSameAddress(memory, memory2, Quiche.QUICHE_SEND_INFO_OFFSETOF_TO);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
final class SecureRandomQuicConnectionIdGenerator implements QuicConnectionIdGenerator {
|
||||||
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
|
|
||||||
|
static final QuicConnectionIdGenerator INSTANCE = new SecureRandomQuicConnectionIdGenerator();
|
||||||
|
|
||||||
|
private SecureRandomQuicConnectionIdGenerator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer newId(int length) {
|
||||||
|
ObjectUtil.checkInRange(length, 0, maxConnectionIdLength(), "length");
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
RANDOM.nextBytes(bytes);
|
||||||
|
return ByteBuffer.wrap(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer newId(ByteBuffer buffer, int length) {
|
||||||
|
return newId(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxConnectionIdLength() {
|
||||||
|
return Quiche.QUICHE_MAX_CONN_ID_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isIdempotent() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.socket.DatagramPacket;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to allocate datagram packets that use UDP_SEGMENT (GSO).
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface SegmentedDatagramPacketAllocator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SegmentedDatagramPacketAllocator} which should be used if no UDP_SEGMENT is supported and used.
|
||||||
|
*/
|
||||||
|
SegmentedDatagramPacketAllocator NONE = new SegmentedDatagramPacketAllocator() {
|
||||||
|
@Override
|
||||||
|
public int maxNumSegments() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatagramPacket newPacket(ByteBuf buffer, int segmentSize, InetSocketAddress remoteAddress) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of segments to use per packet. By default this is {@code 10} but this may be overridden by
|
||||||
|
* the implementation of the interface.
|
||||||
|
*
|
||||||
|
* @return the segments.
|
||||||
|
*/
|
||||||
|
default int maxNumSegments() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new segmented {@link DatagramPacket}.
|
||||||
|
*
|
||||||
|
* @param buffer the {@link ByteBuf} that is used as content.
|
||||||
|
* @param segmentSize the size of each segment.
|
||||||
|
* @param remoteAddress the remote address to send to.
|
||||||
|
* @return the packet.
|
||||||
|
*/
|
||||||
|
DatagramPacket newPacket(ByteBuf buffer, int segmentSize, InetSocketAddress remoteAddress);
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.quic;
|
||||||
|
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
final class SockaddrIn {
|
||||||
|
static final byte[] IPV4_MAPPED_IPV6_PREFIX = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff };
|
||||||
|
static final int IPV4_ADDRESS_LENGTH = 4;
|
||||||
|
static final int IPV6_ADDRESS_LENGTH = 16;
|
||||||
|
static final byte[] SOCKADDR_IN6_EMPTY_ARRAY = new byte[Quiche.SIZEOF_SOCKADDR_IN6];
|
||||||
|
static final byte[] SOCKADDR_IN_EMPTY_ARRAY = new byte[Quiche.SIZEOF_SOCKADDR_IN];
|
||||||
|
|
||||||
|
private SockaddrIn() { }
|
||||||
|
|
||||||
|
static int cmp(long memory, long memory2) {
|
||||||
|
return Quiche.sockaddr_cmp(memory, memory2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int setAddress(ByteBuffer memory, InetSocketAddress address) {
|
||||||
|
InetAddress addr = address.getAddress();
|
||||||
|
return setAddress(addr instanceof Inet6Address, memory, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int setAddress(boolean ipv6, ByteBuffer memory, InetSocketAddress address) {
|
||||||
|
if (ipv6) {
|
||||||
|
return SockaddrIn.setIPv6(memory, address.getAddress(), address.getPort());
|
||||||
|
} else {
|
||||||
|
return SockaddrIn.setIPv4(memory, address.getAddress(), address.getPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* struct sockaddr_in {
|
||||||
|
* sa_family_t sin_family; // address family: AF_INET
|
||||||
|
* in_port_t sin_port; // port in network byte order
|
||||||
|
* struct in_addr sin_addr; // internet address
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* // Internet address.
|
||||||
|
* struct in_addr {
|
||||||
|
* uint32_t s_addr; // address in network byte order
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static int setIPv4(ByteBuffer memory, InetAddress address, int port) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
// memset
|
||||||
|
memory.put(SOCKADDR_IN_EMPTY_ARRAY);
|
||||||
|
|
||||||
|
memory.putShort(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_FAMILY, Quiche.AF_INET);
|
||||||
|
memory.putShort(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_PORT, (short) port);
|
||||||
|
|
||||||
|
byte[] bytes = address.getAddress();
|
||||||
|
int offset = 0;
|
||||||
|
if (bytes.length == IPV6_ADDRESS_LENGTH) {
|
||||||
|
// IPV6 mapped IPV4 address, we only need the last 4 bytes.
|
||||||
|
offset = IPV4_MAPPED_IPV6_PREFIX.length;
|
||||||
|
}
|
||||||
|
assert bytes.length == offset + IPV4_ADDRESS_LENGTH;
|
||||||
|
memory.position(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_ADDR + Quiche.IN_ADDRESS_OFFSETOF_S_ADDR);
|
||||||
|
memory.put(bytes, offset, IPV4_ADDRESS_LENGTH);
|
||||||
|
return Quiche.SIZEOF_SOCKADDR_IN;
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct sockaddr_in6 {
|
||||||
|
* sa_family_t sin6_family; // AF_INET6
|
||||||
|
* in_port_t sin6_port; // port number
|
||||||
|
* uint32_t sin6_flowinfo; // IPv6 flow information
|
||||||
|
* struct in6_addr sin6_addr; // IPv6 address
|
||||||
|
* uint32_t sin6_scope_id; /* Scope ID (new in 2.4)
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* struct in6_addr {
|
||||||
|
* unsigned char s6_addr[16]; // IPv6 address
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
static int setIPv6(ByteBuffer memory, InetAddress address, int port) {
|
||||||
|
int position = memory.position();
|
||||||
|
try {
|
||||||
|
// memset
|
||||||
|
memory.put(SOCKADDR_IN6_EMPTY_ARRAY);
|
||||||
|
|
||||||
|
memory.putShort(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_FAMILY, Quiche.AF_INET6);
|
||||||
|
memory.putShort(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_PORT, (short) port);
|
||||||
|
|
||||||
|
// Skip sin6_flowinfo as we did memset before
|
||||||
|
byte[] bytes = address.getAddress();
|
||||||
|
int offset = Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_ADDR + Quiche.IN6_ADDRESS_OFFSETOF_S6_ADDR;
|
||||||
|
|
||||||
|
if (bytes.length == IPV4_ADDRESS_LENGTH) {
|
||||||
|
memory.position(position + offset);
|
||||||
|
memory.put(IPV4_MAPPED_IPV6_PREFIX);
|
||||||
|
|
||||||
|
memory.position(position + offset + IPV4_MAPPED_IPV6_PREFIX.length);
|
||||||
|
memory.put(bytes, 0, IPV4_ADDRESS_LENGTH);
|
||||||
|
|
||||||
|
// Skip sin6_scope_id as we did memset before
|
||||||
|
} else {
|
||||||
|
memory.position(position + offset);
|
||||||
|
memory.put(bytes, 0, IPV6_ADDRESS_LENGTH);
|
||||||
|
|
||||||
|
memory.putInt(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_SCOPE_ID,
|
||||||
|
((Inet6Address) address).getScopeId());
|
||||||
|
}
|
||||||
|
return Quiche.SIZEOF_SOCKADDR_IN6;
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static InetSocketAddress getIPv4(ByteBuffer memory, byte[] tmpArray) {
|
||||||
|
assert tmpArray.length == IPV4_ADDRESS_LENGTH;
|
||||||
|
int position = memory.position();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int port = memory.getShort(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_PORT) & 0xFFFF;
|
||||||
|
memory.position(position + Quiche.SOCKADDR_IN_OFFSETOF_SIN_ADDR + Quiche.IN_ADDRESS_OFFSETOF_S_ADDR);
|
||||||
|
memory.get(tmpArray);
|
||||||
|
try {
|
||||||
|
return new InetSocketAddress(InetAddress.getByAddress(tmpArray), port);
|
||||||
|
} catch (UnknownHostException ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static InetSocketAddress getIPv6(ByteBuffer memory, byte[] ipv6Array, byte[] ipv4Array) {
|
||||||
|
assert ipv6Array.length == IPV6_ADDRESS_LENGTH;
|
||||||
|
assert ipv4Array.length == IPV4_ADDRESS_LENGTH;
|
||||||
|
int position = memory.position();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int port = memory.getShort(
|
||||||
|
position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_PORT) & 0xFFFF;
|
||||||
|
memory.position(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_ADDR + Quiche.IN6_ADDRESS_OFFSETOF_S6_ADDR);
|
||||||
|
memory.get(ipv6Array);
|
||||||
|
if (PlatformDependent.equals(
|
||||||
|
ipv6Array, 0, IPV4_MAPPED_IPV6_PREFIX, 0, IPV4_MAPPED_IPV6_PREFIX.length)) {
|
||||||
|
System.arraycopy(ipv6Array, IPV4_MAPPED_IPV6_PREFIX.length, ipv4Array, 0, IPV4_ADDRESS_LENGTH);
|
||||||
|
try {
|
||||||
|
return new InetSocketAddress(Inet4Address.getByAddress(ipv4Array), port);
|
||||||
|
} catch (UnknownHostException ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int scopeId = memory.getInt(position + Quiche.SOCKADDR_IN6_OFFSETOF_SIN6_SCOPE_ID);
|
||||||
|
try {
|
||||||
|
return new InetSocketAddress(Inet6Address.getByAddress(null, ipv6Array, scopeId), port);
|
||||||
|
} catch (UnknownHostException ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
memory.position(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue