├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── doc
└── IntelliJ_IDEA_Codestyle.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lombok.config
├── settings.gradle
└── src
├── main
└── java
│ └── com
│ └── timtrense
│ └── quic
│ ├── AckRange.java
│ ├── ConnectionId.java
│ ├── CreditBasedFlowControl.java
│ ├── EcnCount.java
│ ├── EncryptionLevel.java
│ ├── EndpointRole.java
│ ├── ExtensionFrame.java
│ ├── FlowControl.java
│ ├── Frame.java
│ ├── FrameContainingPacket.java
│ ├── FrameGeneralType.java
│ ├── FrameType.java
│ ├── LongHeaderPacket.java
│ ├── LongHeaderPacketType.java
│ ├── NumberedPacket.java
│ ├── Packet.java
│ ├── PacketNumber.java
│ ├── PacketNumberSpace.java
│ ├── PreferredAddress.java
│ ├── ProtocolVersion.java
│ ├── ShortHeaderPacket.java
│ ├── StatelessResetToken.java
│ ├── Stream.java
│ ├── StreamId.java
│ ├── StreamPriority.java
│ ├── StreamState.java
│ ├── TransportParameter.java
│ ├── TransportParameterType.java
│ ├── VariableLengthInteger.java
│ ├── VersionNegotiationPacket.java
│ ├── impl
│ ├── Connection.java
│ ├── DatagramParser.java
│ ├── DatagramParserState.java
│ ├── DatagramParserStateListener.java
│ ├── DatagramPool.java
│ ├── DatagramRecycler.java
│ ├── Endpoint.java
│ ├── EndpointConfiguration.java
│ ├── FrameParser.java
│ ├── FrameParserImpl.java
│ ├── HkdfUtil.java
│ ├── PacketParser.java
│ ├── PacketParserImpl.java
│ ├── PacketProtection.java
│ ├── ParsingContext.java
│ ├── ReceivedDatagram.java
│ ├── Receiver.java
│ ├── ReceiverState.java
│ ├── ReceiverStateListener.java
│ ├── base
│ │ ├── ByteArrayTransportParameterImpl.java
│ │ ├── ConnectionIdImpl.java
│ │ ├── DefaultStreamPriority.java
│ │ ├── FlagTransportParameterImpl.java
│ │ ├── FlowControlImpl.java
│ │ ├── InitialPacketProtectionImpl.java
│ │ ├── IntegerTransportParameterImpl.java
│ │ ├── PacketNumberEncoder.java
│ │ ├── PacketNumberImpl.java
│ │ ├── PreferredAddressImpl.java
│ │ ├── PreferredAddressTransportParameterImpl.java
│ │ ├── ReceivingStreamStateImpl.java
│ │ ├── SendingStreamStateImpl.java
│ │ ├── StatelessResetTokenImpl.java
│ │ ├── StreamIdContext.java
│ │ ├── StreamIdContextImpl.java
│ │ ├── StreamIdImpl.java
│ │ ├── StreamImpl.java
│ │ ├── TransportParameterCollection.java
│ │ ├── TransportParameterCollectionImpl.java
│ │ └── VariableLengthIntegerEncoder.java
│ ├── exception
│ │ ├── MalformedDatagramException.java
│ │ ├── MalformedFrameException.java
│ │ ├── MalformedPacketException.java
│ │ ├── MalformedTlsException.java
│ │ ├── OutOfOrderProtectedPacketException.java
│ │ ├── QuicException.java
│ │ ├── QuicParsingException.java
│ │ └── UnsupportedProtocolVersionException.java
│ ├── frames
│ │ ├── AckFrameImpl.java
│ │ ├── ConnectionCloseFrameImpl.java
│ │ ├── CryptoFrameImpl.java
│ │ ├── DataBlockedFrameImpl.java
│ │ ├── HandshakeDoneFrameImpl.java
│ │ ├── MaxDataFrameImpl.java
│ │ ├── MaxStreamDataFrameImpl.java
│ │ ├── MaxStreamsFrameImpl.java
│ │ ├── MultiPaddingFrameImpl.java
│ │ ├── NewConnectionIdFrameImpl.java
│ │ ├── NewTokenFrameImpl.java
│ │ ├── PaddingFrameImpl.java
│ │ ├── PathChallangeFrameImpl.java
│ │ ├── PathResponseFrameImpl.java
│ │ ├── ResetStreamFrameImpl.java
│ │ ├── RetireConnectionIdFrameImpl.java
│ │ ├── StopSendingFrameImpl.java
│ │ ├── StreamDataBlockedFrameImpl.java
│ │ ├── StreamFrameImpl.java
│ │ └── StreamsBlockedFrameImpl.java
│ ├── package-info.java
│ └── packets
│ │ ├── BaseLongHeaderPacket.java
│ │ ├── HandshakePacketImpl.java
│ │ ├── InitialPacketImpl.java
│ │ ├── RetryPacketImpl.java
│ │ ├── ShortHeaderPacketImpl.java
│ │ ├── VersionNegotiationPacketImpl.java
│ │ └── ZeroRttPacketImpl.java
│ ├── package-info.java
│ └── tls
│ ├── CertificateEntry.java
│ ├── CertificateStatusType.java
│ ├── CertificateType.java
│ ├── CipherSuite.java
│ ├── ExtendedHandshake.java
│ ├── Extension.java
│ ├── ExtensionCarryingHandshake.java
│ ├── ExtensionType.java
│ ├── Handshake.java
│ ├── HandshakeType.java
│ ├── HostName.java
│ ├── KeyShareEntry.java
│ ├── KeyUpdateRequest.java
│ ├── NameType.java
│ ├── NamedGroup.java
│ ├── OcspExtensions.java
│ ├── OcspResponderId.java
│ ├── OidFilter.java
│ ├── ProtocolName.java
│ ├── ProtocolVersion.java
│ ├── PskBinderEntry.java
│ ├── PskIdentity.java
│ ├── PskKeyExchangeMode.java
│ ├── ServerName.java
│ ├── SignatureScheme.java
│ ├── UncompressedPointRepresentation.java
│ ├── extensions
│ ├── ApplicationLayerProtocolNegotiationExtension.java
│ ├── CertificateAuthoritiesExtension.java
│ ├── ClientSupportedVersionsExtension.java
│ ├── CookieExtension.java
│ ├── EarlyDataIndicationExtension.java
│ ├── KeyShareClientHelloExtension.java
│ ├── KeyShareExtensionBase.java
│ ├── KeyShareHelloRetryRequestExtension.java
│ ├── KeyShareServerHelloExtension.java
│ ├── OidFilterExtension.java
│ ├── PostHandshakeClientAuthExtension.java
│ ├── PreSharedKeyClientHelloExtension.java
│ ├── PreSharedKeyExtensionBase.java
│ ├── PreSharedKeyServerHelloExtension.java
│ ├── PskKeyExchangeModeExtension.java
│ ├── QuicTransportParametersExtension.java
│ ├── RecordSizeLimitExtension.java
│ ├── RenegotiationInfoExtension.java
│ ├── ServerNameIndicationExtension.java
│ ├── ServerSupportedVersionsExtension.java
│ ├── SignatureAlgorithmsExtension.java
│ ├── StatusRequestExtensionBase.java
│ ├── StatusRequestOcspExtension.java
│ ├── SupportedGroupsExtension.java
│ ├── SupportedVersionsExtensionBase.java
│ └── package-info.java
│ ├── handshake
│ ├── AuthenticationMessage.java
│ ├── Certificate.java
│ ├── CertificateRequest.java
│ ├── CertificateVerify.java
│ ├── ClientHello.java
│ ├── EncryptedExtensions.java
│ ├── EndOfEarlyData.java
│ ├── Finished.java
│ ├── HelloRetryRequest.java
│ ├── KeyExchangeMessage.java
│ ├── KeyUpdate.java
│ ├── NewSessionTicket.java
│ ├── PostHandshakeMessage.java
│ ├── ServerHello.java
│ ├── ServerParametersMessage.java
│ └── package-info.java
│ ├── impl
│ ├── ExtensionParser.java
│ ├── ExtensionParserImpl.java
│ ├── MessageParser.java
│ ├── MessageParserImpl.java
│ └── package-info.java
│ └── package-info.java
└── test
└── java
└── com
└── timtrense
└── quic
├── HexByteStringConvertHelper.java
├── StreamIdContextTest.java
├── StreamIdTest.java
├── VariableLengthIntegerEncoderTest.java
├── VariableLengthIntegerTest.java
├── impl
├── HkdfUtilTest.java
├── PacketParserImplTest.java
├── base
│ └── InitialPacketProtectionImplTest.java
└── packets
│ └── InitialPacketImplTest.java
└── tls
└── MessageParserImplTest.java
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.java text diff=java
2 | *.gradle text diff=java
3 | *.gradle.kts text diff=java
4 | *.bat text diff=batch
5 | *.md text diff=markdown
6 | *.properties text
7 |
8 | *.class binary
9 | *.jar binary
10 | *.jks binary
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # build directories
8 | /.gradle/
9 | /.idea
10 | /build
11 | /workspace.xml
12 |
13 | /src/test/java/com/timtrense/quic/Adhoc.java
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Tim Trense
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java Quic
2 | Pure Java implementation of IETF [QUIC](https://quicwg.github.io/).
3 | The primary goal of this project is to eventually evolve to Java's standard implementation of QUIC Protocol.
4 |
5 | ## Project Structure
6 | This project combines a clean QUIC interface in com.timtrense.quic as well as a default implementation for it in com.timtrense.quic.impl.base and ~.frames and ~.packets.
7 | Because QUIC needs to have a specific variant of TLS 1.3 implemented to work, the com.timtrense.quic.tls package
8 | addresses an implementation of TLS which is specific for QUIC.
9 |
10 | ## Project Status
11 | The implementation of the QUIC protocol was standardised in RFC 9000 and implemented the IETF specification [here](https://tools.ietf.org/html/draft-ietf-quic-transport-32) and referenced documents.
12 | I will set up a Dockerfile for integration testing [here](https://quicwg.org/) as soon as this implementation reaches usability.
13 |
14 | ## QUIC Protocol Features
15 |
16 | QUIC has many benefits when compared with existing "TLS over TCP" scenarios:
17 |
18 | - All packets are encrypted and handshake is authenticated with TLS 1.3.
19 | - Parallel streams of both reliable and unreliable application data.
20 | - Exchange application data in the first round trip (0-RTT).
21 | - Improved congestion control and loss recovery.
22 | - Survives a change in the clients IP address or port.
23 | - Stateless load balancing.
24 | - Easily extendable for new features and extensions.
25 | - QUIC conforming with [RFC9000](https://datatracker.ietf.org/doc/html/rfc9000)
26 | - HTTP/3 conforming with [RFC9114](https://datatracker.ietf.org/doc/html/rfc9114)
27 | - Minimal TLS 1.3 implementation conforming with [RFC8446](https://datatracker.ietf.org/doc/html/rfc8446)
28 |
29 | ## Contributing
30 | Contributions welcome. Please feel free to contact me or write a pull request.
31 | Because the main focus currently is implementing the protocol itself, there are many test cases yet to write.
32 | Or sonarqube issues to fix. You may have a look on the sonarqube or test cases as a starting point.
33 | I would really appreciate any help I could possibly get with this project.
34 |
35 | ## License
36 | This project is open source and licensed under the [MIT License](https://github.com/trensetim/quic/blob/main/LICENSE) and freely available even for commercial use and in undisclosed commercial projects.
37 |
38 | ## Acknowledgements
39 | Thanks to ptrd/kwik for doing the heavy lifting on most parts of implementing the QUIC protocol
40 | and related TLS implementation (I would really like him to open source it).
41 | I decided to do my own implementation of QUIC because I felt too much of a pain in trying to understand kwiks source
42 | code and doubting that that code base can be long-term maintained.
43 |
44 | This implementation uses [HKDF by Patrick Favre-Bulle](https://github.com/patrickfav/hkdf)
45 | because it is nicely split into extract and expand, which is necessary for how TLS works in QUIC.
46 | And it also uses [Bytes by Patrick Favre-Bulle](https://github.com/patrickfav/bytes-java)
47 | because with it, by-quic-defined byte arrays are made immutable.
48 |
49 | And huge thanks to [QuicWG](https://github.com/quicwg) for making that promising protocol in the first place.
50 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'org.sonarqube' version '2.7'
4 | id 'jacoco'
5 | }
6 |
7 | group 'com.timtrense'
8 | version '0.1-SNAPSHOT'
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
14 | dependencies {
15 | // https://mvnrepository.com/artifact/at.favre.lib/hkdf
16 | compile group: 'at.favre.lib', name: 'hkdf', version: '1.1.0'
17 | // https://mvnrepository.com/artifact/at.favre.lib/bytes
18 | compile group: 'at.favre.lib', name: 'bytes', version: '1.4.0'
19 | // https://mvnrepository.com/artifact/org.projectlombok/lombok
20 | compile group: 'org.projectlombok', name: 'lombok', version: '1.18.16'
21 | annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.16'
22 | testCompile group: 'junit', name: 'junit', version: '4.12'
23 | }
24 |
25 | test {
26 | exclude 'com/timtrense/quic/Adhoc.class'
27 | finalizedBy jacocoTestReport
28 | ignoreFailures = Boolean.getBoolean("test.ignoreFailures")
29 | }
30 |
31 | jacocoTestReport {
32 | dependsOn test
33 | reports {
34 | xml.enabled true
35 | }
36 | }
37 |
38 | task copyDependencies(type: Copy) {
39 | from configurations.compile
40 | into 'build/dependencies'
41 | }
42 |
43 | assemble {
44 | dependsOn copyDependencies
45 | }
46 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trensetim/quic/6729effd47b0a63ac4f6a8548de5e03b13d043a5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 17 14:51:51 CET 2020
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 |
88 | @rem Execute Gradle
89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
90 |
91 | :end
92 | @rem End local scope for the variables with windows NT shell
93 | if "%ERRORLEVEL%"=="0" goto mainEnd
94 |
95 | :fail
96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
97 | rem the _cmd.exe /c_ return code!
98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
99 | exit /b 1
100 |
101 | :mainEnd
102 | if "%OS%"=="Windows_NT" endlocal
103 |
104 | :omega
105 |
--------------------------------------------------------------------------------
/lombok.config:
--------------------------------------------------------------------------------
1 | config.stopBubbling = true
2 | lombok.addLombokGeneratedAnnotation = true
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'quic'
2 |
3 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/AckRange.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * Each ACK Range consists of alternating Gap and ACK Range values in
5 | * descending packet number order. ACK Ranges can be repeated. The
6 | * number of Gap and ACK Range values is determined by the ACK Range
7 | * Count field; one of each value is present for each value in the ACK
8 | * Range Count field.
9 | *
10 | * Gap and ACK Range value use a relative integer encoding for
11 | * efficiency. Though each encoded value is positive, the values are
12 | * subtracted, so that each ACK Range describes progressively lower-
13 | * numbered packets.
14 | *
15 | * Each ACK Range acknowledges a contiguous range of packets by
16 | * indicating the number of acknowledged packets that precede the
17 | * largest packet number in that range. A value of zero indicates that
18 | * only the largest packet number is acknowledged. Larger ACK Range
19 | * values indicate a larger range, with corresponding lower values for
20 | * the smallest packet number in the range. Thus, given a largest
21 | * packet number for the range, the smallest value is determined by the
22 | * formula:
23 | *
24 | * smallest = largest - ack_range
25 | *
26 | * An ACK Range acknowledges all packets between the smallest packet
27 | * number and the largest, inclusive.
28 | *
29 | * The largest value for an ACK Range is determined by cumulatively
30 | * subtracting the size of all preceding ACK Ranges and Gaps.
31 | *
32 | * Each Gap indicates a range of packets that are not being
33 | * acknowledged. The number of packets in the gap is one higher than
34 | * the encoded value of the Gap field.
35 | *
36 | * The value of the Gap field establishes the largest packet number
37 | * value for the subsequent ACK Range using the following formula:
38 | *
39 | * largest = previous_smallest - gap - 2
40 | *
41 | * If any computed packet number is negative, an endpoint MUST generate
42 | * a connection error of type FRAME_ENCODING_ERROR.
43 | *
44 | * @author Tim Trense
45 | * @see QUIC Spec/Section 19.3.1
46 | */
47 | public interface AckRange {
48 |
49 | /**
50 | * A variable-length integer indicating the number of contiguous
51 | * unacknowledged packets preceding the packet number one lower than
52 | * the smallest in the preceding ACK Range.
53 | *
54 | * @return number of contiguous unacknowledged packets preceding the packet number one lower than the smallest in
55 | * the preceding ACK Range
56 | */
57 | VariableLengthInteger getGap();
58 |
59 | /**
60 | * A variable-length integer indicating the number of
61 | * contiguous acknowledged packets preceding the largest packet
62 | * number, as determined by the preceding Gap.
63 | *
64 | * @return number of contiguous acknowledged packets preceding the largest packet number, as determined by the
65 | * preceding Gap
66 | */
67 | VariableLengthInteger getLength();
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/EcnCount.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * The ACK frame uses the least significant bit (that is, type 0x03) to
5 | * indicate ECN feedback and report receipt of QUIC packets with
6 | * associated ECN codepoints of ECT(0), ECT(1), or CE in the packet's IP
7 | * header. ECN Counts are only present when the ACK frame type is 0x03.
8 | *
9 | * @author Tim Trense
10 | * @see QUIC Spec/Section 19.3.2
11 | */
12 | public interface EcnCount {
13 |
14 | /**
15 | * A variable-length integer representing the total number
16 | * of packets received with the ECT(0) codepoint in the packet number
17 | * space of the ACK frame
18 | *
19 | * @return total number of packets received with the ECT(0) codepoint in the packet number space of the ACK frame
20 | */
21 | VariableLengthInteger getEct0Count();
22 |
23 | /**
24 | * A variable-length integer representing the total number
25 | * of packets received with the ECT(1) codepoint in the packet number
26 | * space of the ACK frame
27 | *
28 | * @return total number of packets received with the ECT(1) codepoint in the packet number space of the ACK frame
29 | */
30 | VariableLengthInteger getEct1Count();
31 |
32 | /**
33 | * A variable-length integer representing the total number of
34 | * packets received with the CE codepoint in the packet number space
35 | * of the ACK frame.
36 | *
37 | * @return total number of packets received with the CE codepoint in the packet number space of the ACK frame
38 | */
39 | VariableLengthInteger getEcnCeCount();
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/EncryptionLevel.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * Data is protected using a number of encryption levels.
5 | *
6 | * Application Data may appear only in the Early Data and Application
7 | * Data levels. Handshake and Alert messages may appear in any level.
8 | *
9 | * The 0-RTT handshake is only possible if the client and server have
10 | * previously communicated. In the 1-RTT handshake, the client is
11 | * unable to send protected Application Data until it has received all
12 | * of the Handshake messages sent by the server.
13 | *
14 | * @author Tim Trense
15 | * @see QUIC Spec-TLS/Section 2.1
16 | */
17 | public enum EncryptionLevel {
18 |
19 | /**
20 | * Initial Keys
21 | */
22 | INITIAL,
23 | /**
24 | * Early Data (0-RTT) Keys
25 | */
26 | EARLY_DATA,
27 | /**
28 | * Handshake Keys
29 | */
30 | HANDSHAKE,
31 | /**
32 | * Application Data (1-RTT) Keys
33 | */
34 | APPLICATION_DATA
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/EndpointRole.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * QUIC is a client/server-based protocol. So any endpoint EITHER is a {@link #CLIENT} or a {@link #SERVER}
5 | *
6 | * @author Tim Trense
7 | */
8 | public enum EndpointRole {
9 |
10 | CLIENT,
11 |
12 | SERVER
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/ExtensionFrame.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * All QUIC extensions that provide frames MUST let them implement this interface
5 | *
6 | * QUIC frames do not use a self-describing encoding. An endpoint
7 | * therefore needs to understand the syntax of all frames before it can
8 | * successfully process a packet. This allows for efficient encoding of
9 | * frames, but it means that an endpoint cannot send a frame of a type
10 | * that is unknown to its peer.
11 | *
12 | * An extension to QUIC that wishes to use a new type of frame MUST
13 | * first ensure that a peer is able to understand the frame. An
14 | * endpoint can use a transport parameter to signal its willingness to
15 | * receive extension frame types. One transport parameter can indicate
16 | * support for one or more extension frame types.
17 | *
18 | * Extensions that modify or replace core protocol functionality
19 | * (including frame types) will be difficult to combine with other
20 | * extensions that modify or replace the same functionality unless the
21 | * behavior of the combination is explicitly defined. Such extensions
22 | * SHOULD define their interaction with previously-defined extensions
23 | * modifying the same protocol components.
24 | *
25 | * Extension frames MUST be congestion controlled and MUST cause an ACK
26 | * frame to be sent. The exception is extension frames that replace or
27 | * supplement the ACK frame. Extension frames are not included in flow
28 | * control unless specified in the extension.
29 | *
30 | * An IANA registry is used to manage the assignment of frame types; see
31 | * Section 22.3.
32 | *
33 | * @author Tim Trense
34 | * @see QUIC Spec/Section 19.21
35 | */
36 | public interface ExtensionFrame extends Frame {
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/FlowControl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * It is necessary to limit the amount of data that a receiver could
5 | * buffer, to prevent a fast sender from overwhelming a slow receiver,
6 | * or to prevent a malicious sender from consuming a large amount of
7 | * memory at a receiver. To enable a receiver to limit memory
8 | * commitment to a connection and to apply back pressure on the sender,
9 | * streams are flow controlled both individually and as an aggregate. A
10 | * QUIC receiver controls the maximum amount of data the sender can send
11 | * on a stream at any time, as described in Section 4.1 and Section 4.2.
12 | *
13 | * Similarly, to limit concurrency within a connection, a QUIC endpoint
14 | * controls the maximum cumulative number of streams that its peer can
15 | * initiate, as described in Section 4.6.
16 | *
17 | * Data sent in CRYPTO frames is not flow controlled in the same way as
18 | * stream data. QUIC relies on the cryptographic protocol
19 | * implementation to avoid excessive buffering of data; see [QUIC-TLS].
20 | * To avoid excessive buffering at multiple layers, QUIC implementations
21 | * SHOULD provide an interface for the cryptographic protocol
22 | * implementation to communicate its buffering limits.
23 | *
24 | * See {@link CreditBasedFlowControl} for details on flow control in QUIC.
25 | *
26 | * @author Tim Trense
27 | * @see QUIC Spec/Section 4
28 | */
29 | public interface FlowControl {
30 |
31 | /**
32 | * Checks the implementation whether the caller would be allowed to send AT LEAST the queried amount of
33 | * data to the peer.
34 | *
35 | * The call is inherently NOT thread-safe unless nobody else is sending on the referred connection,
36 | * because the connection implementation MUST guarantee that the transferable amount of data does NEVER
37 | * decrease while no data is being sent. Only sending some data MAY decrease the
38 | * currently transferable amount of data.
39 | *
40 | * As time passes, the peer MAY allow this endpoint to send more data. This implies that the transferable
41 | * amount of data MAY increase out of control of the caller and MAY even be increased right after a call
42 | * to this function.
43 | *
44 | * @param numberOfBytes the amount the caller tries to send
45 | * @return true if the flow control allows sending at least that amount of data NOW in its current state,
46 | * false otherwise
47 | */
48 | boolean canSend( int numberOfBytes );
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/Frame.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * payload component of a packet.
5 | *
6 | * As described in Section 12.4, packets contain one or more frames.
7 | * This section describes the format and semantics of the core QUIC
8 | * frame types.
9 | *
10 | * @author Tim Trense
11 | * @see QUIC Spec/Section 12.4
12 | */
13 | public interface Frame {
14 |
15 | /**
16 | * @return the type of this frame
17 | */
18 | FrameType getType();
19 |
20 | /**
21 | * @return true if all necessary data for that frame is present
22 | */
23 | boolean isValid();
24 |
25 | /**
26 | * Attention: this function may return a valid number that is not actually accurate
27 | * IF AND ONLY IF {@link #isValid()} == false
28 | *
29 | * @return the length of this frame in bytes or -1 if the frame is either invalid or of unknown length
30 | * @throws NullPointerException if the frame contains required fields with null value
31 | */
32 | long getFrameLength();
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/FrameGeneralType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * general fully distinct types of frames
5 | *
6 | * @author Tim Trense
7 | * @see QUIC Spec/Section 19
8 | * @see FrameType
9 | */
10 | public enum FrameGeneralType {
11 |
12 | PADDING,
13 | PING,
14 | ACK,
15 | RESET_STREAM,
16 | STOP_SENDING,
17 | CRYPTO,
18 | NEW_TOKEN,
19 | STREAM,
20 | MAX_DATA,
21 | MAX_STREAM_DATA,
22 | MAX_STREAMS,
23 | DATA_BLOCKED,
24 | STREAM_DATA_BLOCKED,
25 | STREAMS_BLOCKED,
26 | NEW_CONNECTION_ID,
27 | RETIRE_CONNECTION_ID,
28 | PATH_CHALLENGE,
29 | PATH_RESPONSE,
30 | CONNECTION_CLOSE,
31 | HANDSHAKE_DONE
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/FrameType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * a frame type can have some bits set to indicate the very layout of the frame.
7 | *
8 | * @author Tim Trense
9 | * @see QUIC Spec/Section 19
10 | * @see FrameGeneralType
11 | */
12 | public enum FrameType {
13 |
14 | // TODO: find better names for _1, _2, ...
15 |
16 | PADDING( 0x00, FrameGeneralType.PADDING ),
17 | PING( 0x01, FrameGeneralType.PING ),
18 | ACK( 0x02, FrameGeneralType.ACK ),
19 | ACK_WITH_ECN( 0x03, FrameGeneralType.ACK ),
20 | RESET_STREAM( 0x04, FrameGeneralType.RESET_STREAM ),
21 | STOP_SENDING( 0x05, FrameGeneralType.STOP_SENDING ),
22 | CRYPTO( 0x06, FrameGeneralType.CRYPTO ),
23 | NEW_TOKEN( 0x07, FrameGeneralType.NEW_TOKEN ),
24 | STREAM( 0x08, FrameGeneralType.STREAM ),
25 | STREAM_FIN( 0x09, FrameGeneralType.STREAM ),
26 | STREAM_LEN( 0x0a, FrameGeneralType.STREAM ),
27 | STREAM_LEN_FIN( 0x0b, FrameGeneralType.STREAM ),
28 | STREAM_OFF( 0x0c, FrameGeneralType.STREAM ),
29 | STREAM_OFF_FIN( 0x0d, FrameGeneralType.STREAM ),
30 | STREAM_OFF_LEN( 0x0e, FrameGeneralType.STREAM ),
31 | STREAM_OFF_LEN_FIN( 0x0f, FrameGeneralType.STREAM ),
32 | MAX_DATA( 0x10, FrameGeneralType.MAX_DATA ),
33 | MAX_STREAM_DATA( 0x11, FrameGeneralType.MAX_STREAM_DATA ),
34 | MAX_STREAMS_1( 0x12, FrameGeneralType.MAX_STREAMS ),
35 | MAX_STREAMS_2( 0x13, FrameGeneralType.MAX_STREAMS ),
36 | DATA_BLOCKED( 0x14, FrameGeneralType.DATA_BLOCKED ),
37 | STREAM_DATA_BLOCKED( 0x15, FrameGeneralType.STREAM_DATA_BLOCKED ),
38 | STREAMS_BLOCKED_1( 0x16, FrameGeneralType.STREAMS_BLOCKED ),
39 | STREAMS_BLOCKED_2( 0x17, FrameGeneralType.STREAMS_BLOCKED ),
40 | NEW_CONNECTION_ID( 0x18, FrameGeneralType.NEW_CONNECTION_ID ),
41 | RETIRE_CONNECTION_ID( 0x19, FrameGeneralType.RETIRE_CONNECTION_ID ),
42 | PATH_CHALLENGE( 0x1a, FrameGeneralType.PATH_CHALLENGE ),
43 | PATH_RESPONSE( 0x1b, FrameGeneralType.PATH_RESPONSE ),
44 | CONNECTION_CLOSE( 0x1c, FrameGeneralType.CONNECTION_CLOSE ),
45 | CONNECTION_CLOSE_ON_FRAME_TYPE( 0x1d, FrameGeneralType.CONNECTION_CLOSE ),
46 | HANDSHAKE_DONE( 0x1e, FrameGeneralType.HANDSHAKE_DONE );
47 |
48 | @Getter
49 | private final VariableLengthInteger value;
50 | @Getter
51 | private final FrameGeneralType generalType;
52 |
53 | FrameType( int value, FrameGeneralType generalType ) {
54 | this.value = new VariableLengthInteger( value );
55 | this.generalType = generalType;
56 | }
57 |
58 | /**
59 | * @return the value of {@link #getValue()} as a long
60 | */
61 | public long getLongValue() {
62 | return value.longValue();
63 | }
64 |
65 | public static FrameType findByValue( int value ) {
66 | for ( FrameType f : values() ) {
67 | if ( f.value.longValue() == value ) {
68 | return f;
69 | }
70 | }
71 | return null;
72 | }
73 |
74 | public static FrameType findByValue( VariableLengthInteger value ) {
75 | for ( FrameType f : values() ) {
76 | if ( f.value.equals( value ) ) {
77 | return f;
78 | }
79 | }
80 | return null;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/LongHeaderPacketType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | *
7 | * +======+===========+================+ 8 | * | Type | Name | Section | 9 | * +======+===========+================+ 10 | * | 0x0 | Initial | Section 17.2.2 | 11 | * +------+-----------+----------------+ 12 | * | 0x1 | 0-RTT | Section 17.2.3 | 13 | * +------+-----------+----------------+ 14 | * | 0x2 | Handshake | Section 17.2.4 | 15 | * +------+-----------+----------------+ 16 | * | 0x3 | Retry | Section 17.2.5 | 17 | * +------+-----------+----------------+ 18 | *19 | * 20 | * @author Tim Trense 21 | * @see QUIC Spec/Section 17.2 22 | */ 23 | public enum LongHeaderPacketType { 24 | 25 | INITIAL( 0x00 ), 26 | ZERO_RTT( 0x01 ), 27 | HANDSHAKE( 0x02 ), 28 | RETRY( 0x03 ); 29 | 30 | @Getter 31 | private final int id; 32 | 33 | LongHeaderPacketType( int id ) { 34 | this.id = id; 35 | } 36 | 37 | public static LongHeaderPacketType findById( int value ) { 38 | for ( LongHeaderPacketType f : values() ) { 39 | if ( f.id == value ) { 40 | return f; 41 | } 42 | } 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/NumberedPacket.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * This interface combines all types of {@link Packet Packets} that hold a packet number 5 | * 6 | * @author Tim Trense 7 | * @see QUIC Spec/Section 17.1 8 | * @see Packet 9 | */ 10 | public interface NumberedPacket extends Packet { 11 | 12 | /** 13 | * The packet number field is 1 to 4 bytes long. 14 | * 15 | * @return the encoded length of the packet number in bytes 16 | */ 17 | int getPacketNumberLength(); 18 | 19 | /** 20 | * The packet number field is 1 to 4 bytes long. The 21 | * packet number has confidentiality protection separate from packet 22 | * protection, as described in Section 5.4 of [QUIC-TLS]. The length 23 | * of the packet number field is encoded in Packet Number Length 24 | * field. See Section 17.1 for details. 25 | * 26 | * @return the actual packet number 27 | */ 28 | PacketNumber getPacketNumber(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/PacketNumberSpace.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * For details on packet numbers, see {@link PacketNumber}. 5 | * 6 | * Conceptually, a packet number space is the context in which a packet 7 | * can be processed and acknowledged. Initial packets can only be sent 8 | * with Initial packet protection keys and acknowledged in packets that 9 | * are also Initial packets. Similarly, Handshake packets are sent at 10 | * the Handshake encryption level and can only be acknowledged in 11 | * Handshake packets. 12 | * 13 | * This enforces cryptographic separation between the data sent in the 14 | * different packet number spaces. Packet numbers in each space start 15 | * at packet number 0. Subsequent packets sent in the same packet 16 | * number space MUST increase the packet number by at least one. 17 | * 18 | * 0-RTT and 1-RTT data exist in the same packet number space to make 19 | * loss recovery algorithms easier to implement between the two packet 20 | * types. 21 | * 22 | * A QUIC endpoint MUST NOT reuse a packet number within the same packet 23 | * number space in one connection. If the packet number for sending 24 | * reaches 2^62 - 1, the sender MUST close the connection without 25 | * sending a CONNECTION_CLOSE frame or any further packets; an endpoint 26 | * MAY send a Stateless Reset (Section 10.3) in response to further 27 | * packets that it receives. 28 | * 29 | * A receiver MUST discard a newly unprotected packet unless it is 30 | * certain that it has not processed another packet with the same packet 31 | * number from the same packet number space. Duplicate suppression MUST 32 | * happen after removing packet protection for the reasons described in 33 | * Section 9.3 of [QUIC-TLS]. 34 | * 35 | * Version Negotiation (Section 17.2.1) and Retry (Section 17.2.5) 36 | * packets do not include a packet number. 37 | * 38 | * @author Tim Trense 39 | * @see QUIC Spec/Section 12.3 40 | */ 41 | public enum PacketNumberSpace { 42 | 43 | /** 44 | * All Initial packets (Section 17.2.2) are in this space 45 | */ 46 | INITIAL, 47 | /** 48 | * All Handshake packets (Section 17.2.4) are in this space. 49 | */ 50 | HANDSHAKE, 51 | /** 52 | * All 0-RTT (Section 17.2.3) and 1-RTT (Section 17.3) encrypted packets are in this space. 53 | */ 54 | APPLICATION_DATA 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/PreferredAddress.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * The server's preferred address is used to 5 | * effect a change in server address at the end of the handshake, as 6 | * described in Section 9.6. This transport parameter is only sent 7 | * by a server. Servers MAY choose to only send a preferred address 8 | * of one address family by sending an all-zero address and port 9 | * (0.0.0.0:0 or ::.0) for the other family. IP addresses are 10 | * encoded in network byte order. 11 | * 12 | * The preferred_address transport parameter contains an address and 13 | * port for both IP version 4 and 6. The four-byte IPv4 Address 14 | * field is followed by the associated two-byte IPv4 Port field. 15 | * This is followed by a 16-byte IPv6 Address field and two-byte IPv6 16 | * Port field. After address and port pairs, a Connection ID Length 17 | * field describes the length of the following Connection ID field. 18 | * Finally, a 16-byte Stateless Reset Token field includes the 19 | * stateless reset token associated with the connection ID. The 20 | * format of this transport parameter is shown in Figure 22. 21 | * 22 | * The Connection ID field and the Stateless Reset Token field 23 | * contain an alternative connection ID that has a sequence number of 24 | * 1; see Section 5.1.1. Having these values sent alongside the 25 | * preferred address ensures that there will be at least one unused 26 | * active connection ID when the client initiates migration to the 27 | * preferred address. 28 | * 29 | * The Connection ID and Stateless Reset Token fields of a preferred 30 | * address are identical in syntax and semantics to the corresponding 31 | * fields of a NEW_CONNECTION_ID frame (Section 19.15). A server 32 | * that chooses a zero-length connection ID MUST NOT provide a 33 | * preferred address. Similarly, a server MUST NOT include a zero- 34 | * length connection ID in this transport parameter. A client MUST 35 | * treat violation of these requirements as a connection error of 36 | * type TRANSPORT_PARAMETER_ERROR. 37 | *
38 | * Preferred Address { 39 | * IPv4 Address (32), 40 | * IPv4 Port (16), 41 | * IPv6 Address (128), 42 | * IPv6 Port (16), 43 | * Connection ID Length (8), 44 | * Connection ID (..), 45 | * Stateless Reset Token (128) 46 | * } 47 | * Figure 22: Preferred Address format 48 | *49 | * 50 | * @author Tim Trense 51 | * @see QUIC Spec/Page 125 52 | */ 53 | public interface PreferredAddress { 54 | 55 | byte[] getIpv4Address(); 56 | 57 | int getIpv4Port(); 58 | 59 | byte[] getIpv6Address(); 60 | 61 | int getIpv6Port(); 62 | 63 | ConnectionId getConnectionId(); 64 | 65 | StatelessResetToken getStatelessResetToken(); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/StatelessResetToken.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * A 128-bit value that will be used for a 5 | * stateless reset when the associated connection ID is used; see 6 | * Section 10.3. 7 | * 8 | * A stateless reset is provided as an option of last resort for an 9 | * endpoint that does not have access to the state of a connection. A 10 | * crash or outage might result in peers continuing to send data to an 11 | * endpoint that is unable to properly continue the connection. An 12 | * endpoint MAY send a stateless reset in response to receiving a packet 13 | * that it cannot associate with an active connection. 14 | * 15 | * A stateless reset is not appropriate for indicating errors in active 16 | * connections. An endpoint that wishes to communicate a fatal 17 | * connection error MUST use a CONNECTION_CLOSE frame if it is able. 18 | * 19 | * To support this process, a token is sent by endpoints. The token is 20 | * carried in the Stateless Reset Token field of a NEW_CONNECTION_ID 21 | * frame. Servers can also specify a stateless_reset_token transport 22 | * parameter during the handshake that applies to the connection ID that 23 | * it selected during the handshake; clients cannot use this transport 24 | * parameter because their transport parameters do not have 25 | * confidentiality protection. These tokens are protected by 26 | * encryption, so only client and server know their value. Tokens are 27 | * invalidated when their associated connection ID is retired via a 28 | * RETIRE_CONNECTION_ID frame (Section 19.16). 29 | * 30 | * @author Tim Trense 31 | * @see QUIC Spec/Section 10.3 32 | */ 33 | public interface StatelessResetToken { 34 | 35 | byte[] getValue(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/Stream.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * Ordered Byte-Stream to send and/or receive data. 5 | * 6 | * 7 | * 8 | *
8 | * Comparing instances of non-comparable implementations SHOULD result in EQUAL PRIORITY (thus 9 | * {@link Comparable#compareTo(Object)} returns 0. 10 | *
11 | *
12 | *17 | * QUIC does not provide a mechanism for exchanging prioritization 18 | * information. Instead, it relies on receiving priority information 19 | * from the application. 20 | *
21 | * A QUIC implementation SHOULD provide ways in which an application can
22 | * indicate the relative priority of streams. An implementation uses
23 | * information provided by the application to determine how to allocate
24 | * resources to active streams.
25 | *
26 | * @author Tim Trense
27 | * @see QUIC Spec/Section 2.3
28 | */
29 | public interface StreamPriority extends Comparable
27 | * Note: The implementation may return null if the requested keying material is not yet present
28 | * or exchanged with the peer.
29 | *
30 | * Note: Implementations must provide an initialized instance.
31 | *
32 | * @param connectionId the resolved connection id
33 | * @param encryptionLevel the level to retrieve the keys from
34 | * @return the associated protection
35 | */
36 | PacketProtection getPacketProtection( ConnectionId connectionId, EncryptionLevel encryptionLevel );
37 |
38 | //TODO: getPeerSecret(byte[] connectionId, EncryptionLevel)
39 | //TODO: getLocalSecret(byte[] connectionId, EncryptionLevel)
40 | //TODO: getConnectionIdLength(byte[] connectionId)
41 | //TODO: getConnectionProtocolInUse(byte[] connectionId)
42 | //TODO: setConnectionProtocolInUse(byte[] connectionId, ProtocolVersion isUse)
43 | //TODO: setConnectionIdLength(byte[] connectionId, int connectionIdLength)
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/ReceivedDatagram.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl;
2 |
3 | import java.net.DatagramPacket;
4 | import java.time.Instant;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NonNull;
8 | import lombok.RequiredArgsConstructor;
9 |
10 | /**
11 | * A {@link DatagramPacket} that was received by a {@link Receiver}
12 | *
13 | * @author Tim Trense
14 | */
15 | @Data
16 | @RequiredArgsConstructor
17 | @AllArgsConstructor
18 | public class ReceivedDatagram {
19 |
20 | /**
21 | * the actual received datagram
22 | */
23 | private @NonNull DatagramPacket datagram;
24 | /**
25 | * the timestamp of receiving
26 | */
27 | private @NonNull Instant receiveTime;
28 | /**
29 | * a counter given by the {@link Receiver}
30 | */
31 | private long number;
32 | /**
33 | * how many times the datagram could not be parsed,
34 | * possibly due to the lack of decryption material because of reordering on the network
35 | */
36 | private short parseRetryCount = 0;
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/ReceiverState.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl;
2 |
3 | /**
4 | * The state that a {@link Receiver} may have.
5 | * It always has one.
6 | *
7 | * @author Tim Trense
8 | */
9 | public enum ReceiverState {
10 |
11 | /**
12 | * INITIAL STATE.
13 | * The receiver was instantiated but is not yet running
14 | */
15 | NEW,
16 | /**
17 | * The receivers thread started
18 | */
19 | ACTIVE,
20 | /**
21 | * TERMINAL STATE.
22 | * The receiving threw an unrecoverable error
23 | */
24 | ERROR,
25 | /**
26 | * TERMINAL STATE.
27 | * The receiving stopped gracefully and the receiving thread is about to die
28 | */
29 | STOP
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/ReceiverStateListener.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl;
2 |
3 | /**
4 | * Listener for changes of the {@link ReceiverState} of an {@link Receiver}
5 | *
6 | * @author Tim Trense
7 | */
8 | public interface ReceiverStateListener {
9 |
10 | /**
11 | * Called with the receiver still being in the old state.
12 | *
13 | * @param receiver the receiver that's state is transitioning
14 | * @param newState the state the receiver will be in, any time after this call
15 | */
16 | void beforeStateChange( Receiver receiver, ReceiverState newState );
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/base/ByteArrayTransportParameterImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.base;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 |
6 | import com.timtrense.quic.TransportParameter;
7 | import com.timtrense.quic.TransportParameterType;
8 |
9 | /**
10 | * Transport parameters which have byte-Arrays as values
11 | *
12 | * @author Tim Trense
13 | */
14 | @Data
15 | @AllArgsConstructor
16 | public class ByteArrayTransportParameterImpl implements TransportParameter
13 | * Could happen when, due to packet reordering, the first short header packet arrives before handshake is finished.
14 | * https://tools.ietf.org/html/draft-ietf-quic-tls-32#section-5.7
15 | * "Due to reordering and loss, protected packets might be received by an
16 | * endpoint before the final TLS handshake messages are received."
17 | *
18 | * @author Tim Trense
19 | */
20 | public class OutOfOrderProtectedPacketException extends QuicParsingException {
21 |
22 | @Getter
23 | private final transient ReceivedDatagram datagram;
24 | @Getter
25 | private final transient @NonNull ByteBuffer payload;
26 | /**
27 | * the index of the packet within the datagram
28 | */
29 | @Getter
30 | private final int packetIndex;
31 |
32 | public OutOfOrderProtectedPacketException(
33 | ReceivedDatagram datagram,
34 | @NonNull ByteBuffer payload,
35 | int packetIndex
36 | ) {
37 | super( "The packet was received out-of-order and may be processed later: Packet number " + packetIndex );
38 | this.datagram = datagram;
39 | this.payload = payload;
40 | this.packetIndex = packetIndex;
41 | }
42 |
43 | public OutOfOrderProtectedPacketException(
44 | String message,
45 | ReceivedDatagram datagram,
46 | @NonNull ByteBuffer payload,
47 | int packetIndex
48 | ) {
49 | super( message );
50 | this.datagram = datagram;
51 | this.payload = payload;
52 | this.packetIndex = packetIndex;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/exception/QuicException.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.exception;
2 |
3 | /**
4 | * Base class for all exceptions throws by this implementation, related to the protocol itself
5 | *
6 | * @author Tim Trense
7 | */
8 | public abstract class QuicException extends Exception {
9 |
10 | public QuicException( String message ) {
11 | super( message );
12 | }
13 |
14 | public QuicException( String message, Throwable cause, boolean writableStackTrace ) {
15 | super( message, cause, true, writableStackTrace );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/exception/QuicParsingException.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.exception;
2 |
3 | /**
4 | * Base class for all exceptions thrown by this implementation, related to parsing data sent to this protocol
5 | *
6 | * @author Tim Trense
7 | */
8 | public abstract class QuicParsingException extends Exception {
9 |
10 | public QuicParsingException( String message ) {
11 | super( message, null, false, false );
12 | }
13 |
14 | public QuicParsingException( String message, Throwable cause, boolean writableStackTrace ) {
15 | super( message, cause, false, writableStackTrace );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/exception/UnsupportedProtocolVersionException.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.exception;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * This implementation does not support the indicated {@link com.timtrense.quic.ProtocolVersion} of QUIC
7 | *
8 | * @author Tim Trense
9 | */
10 | public class UnsupportedProtocolVersionException extends QuicParsingException {
11 |
12 | @Getter
13 | private final int version;
14 |
15 | public UnsupportedProtocolVersionException( int version ) {
16 | super( "The ProtocolVersion is not supported: " + version );
17 | this.version = version;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/DataBlockedFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * data blocked frame.
13 | * existing known frames are : {@link FrameType#DATA_BLOCKED}.
14 | *
15 | * A sender SHOULD send a DATA_BLOCKED frame (type=0x14) when it wishes
16 | * to send data, but is unable to do so due to connection-level flow
17 | * control; see Section 4. DATA_BLOCKED frames can be used as input to
18 | * tuning of flow control algorithms; see Section 4.2.
19 | *
20 | * @author Tim Trense
21 | * @see QUIC Spec/Section 19.3
22 | */
23 | @Data
24 | public class DataBlockedFrameImpl implements Frame {
25 |
26 | private final FrameType type;
27 |
28 | public DataBlockedFrameImpl( @NonNull FrameType frameType ) {
29 | this.type = frameType;
30 | if ( type.getGeneralType() != FrameGeneralType.DATA_BLOCKED ) {
31 | throw new IllegalArgumentException(
32 | "Cannot build an AckFrame with FrameGeneralType other than "
33 | + FrameGeneralType.DATA_BLOCKED.name()
34 | );
35 | }
36 | }
37 |
38 | /**
39 | * A variable-length integer indicating the connection-
40 | * level limit at which blocking occurred.
41 | */
42 | private VariableLengthInteger maximumData;
43 |
44 | @Override
45 | public boolean isValid() {
46 | return maximumData != null;
47 | }
48 |
49 | @Override
50 | public long getFrameLength() {
51 | long sum = type.getValue().getEncodedLengthInBytes();
52 | sum += maximumData.getEncodedLengthInBytes();
53 | return sum;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/HandshakeDoneFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 |
10 | /**
11 | * handshake done frame.
12 | * existing known frames are : {@link FrameType#HANDSHAKE_DONE}.
13 | *
14 | * The server uses a HANDSHAKE_DONE frame (type=0x1e) to signal
15 | * confirmation of the handshake to the client.
16 | *
17 | * HANDSHAKE_DONE frames are formatted as shown in Figure 44, which
18 | * shows that HANDSHAKE_DONE frames have no content.
19 | *
20 | * A HANDSHAKE_DONE frame can only be sent by the server. Servers MUST
21 | * NOT send a HANDSHAKE_DONE frame before completing the handshake. A
22 | * server MUST treat receipt of a HANDSHAKE_DONE frame as a connection
23 | * error of type PROTOCOL_VIOLATION.
24 | *
25 | * @author Tim Trense
26 | * @see QUIC Spec/Section 19.3
27 | */
28 | @Data
29 | public class HandshakeDoneFrameImpl implements Frame {
30 |
31 | private final FrameType type;
32 |
33 | public HandshakeDoneFrameImpl( @NonNull FrameType frameType ) {
34 | this.type = frameType;
35 | if ( type.getGeneralType() != FrameGeneralType.HANDSHAKE_DONE ) {
36 | throw new IllegalArgumentException(
37 | "Cannot build an AckFrame with FrameGeneralType other than "
38 | + FrameGeneralType.HANDSHAKE_DONE.name()
39 | );
40 | }
41 | }
42 |
43 | @Override
44 | public boolean isValid() {
45 | return true;
46 | }
47 |
48 | @Override
49 | public long getFrameLength() {
50 | return type.getValue().getEncodedLengthInBytes();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/MaxDataFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * max data frame.
13 | * existing known frames are : {@link FrameType#MAX_DATA}.
14 | *
15 | * A MAX_DATA frame (type=0x10) is used in flow control to inform the
16 | * peer of the maximum amount of data that can be sent on the connection
17 | * as a whole.
18 | *
19 | * All data sent in STREAM frames counts toward this limit. The sum of
20 | * the final sizes on all streams - including streams in terminal states
21 | * - MUST NOT exceed the value advertised by a receiver. An endpoint
22 | * MUST terminate a connection with a FLOW_CONTROL_ERROR error if it
23 | * receives more data than the maximum data value that it has sent.
24 | * This includes violations of remembered limits in Early Data; see
25 | * Section 7.4.1.
26 | *
27 | * @author Tim Trense
28 | * @see QUIC Spec/Section 19.3
29 | */
30 | @Data
31 | public class MaxDataFrameImpl implements Frame {
32 |
33 | private final FrameType type;
34 |
35 | public MaxDataFrameImpl( @NonNull FrameType frameType ) {
36 | this.type = frameType;
37 | if ( type.getGeneralType() != FrameGeneralType.MAX_DATA ) {
38 | throw new IllegalArgumentException(
39 | "Cannot build an AckFrame with FrameGeneralType other than "
40 | + FrameGeneralType.MAX_DATA.name()
41 | );
42 | }
43 | }
44 |
45 | /**
46 | * A variable-length integer indicating the maximum
47 | * amount of data that can be sent on the entire connection, in units
48 | * of bytes.
49 | */
50 | private VariableLengthInteger maximumData;
51 |
52 | @Override
53 | public boolean isValid() {
54 | return maximumData != null;
55 | }
56 |
57 | @Override
58 | public long getFrameLength() {
59 | long sum = type.getValue().getEncodedLengthInBytes();
60 | sum += maximumData.getEncodedLengthInBytes();
61 | return sum;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/MaxStreamDataFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.StreamId;
10 | import com.timtrense.quic.VariableLengthInteger;
11 |
12 | /**
13 | * max stream data frame.
14 | * existing known frames are : {@link FrameType#MAX_STREAM_DATA}.
15 | *
16 | * A MAX_STREAM_DATA frame (type=0x11) is used in flow control to inform
17 | * a peer of the maximum amount of data that can be sent on a stream.
18 | *
19 | * A MAX_STREAM_DATA frame can be sent for streams in the Recv state;
20 | * see Section 3.1. Receiving a MAX_STREAM_DATA frame for a locally-
21 | * initiated stream that has not yet been created MUST be treated as a
22 | * connection error of type STREAM_STATE_ERROR. An endpoint that
23 | * receives a MAX_STREAM_DATA frame for a receive-only stream MUST
24 | * terminate the connection with error STREAM_STATE_ERROR.
25 | *
26 | * When counting data toward this limit, an endpoint accounts for the
27 | * largest received offset of data that is sent or received on the
28 | * stream. Loss or reordering can mean that the largest received offset
29 | * on a stream can be greater than the total size of data received on
30 | * that stream. Receiving STREAM frames might not increase the largest
31 | * received offset.
32 | *
33 | * The data sent on a stream MUST NOT exceed the largest maximum stream
34 | * data value advertised by the receiver. An endpoint MUST terminate a
35 | * connection with a FLOW_CONTROL_ERROR error if it receives more data
36 | * than the largest maximum stream data that it has sent for the
37 | * affected stream. This includes violations of remembered limits in
38 | * Early Data; see Section 7.4.1.
39 | *
40 | * @author Tim Trense
41 | * @see QUIC Spec/Section 19.3
42 | */
43 | @Data
44 | public class MaxStreamDataFrameImpl implements Frame {
45 |
46 | private final FrameType type;
47 |
48 | public MaxStreamDataFrameImpl( @NonNull FrameType frameType ) {
49 | this.type = frameType;
50 | if ( type.getGeneralType() != FrameGeneralType.MAX_STREAM_DATA ) {
51 | throw new IllegalArgumentException(
52 | "Cannot build an AckFrame with FrameGeneralType other than "
53 | + FrameGeneralType.MAX_STREAM_DATA.name()
54 | );
55 | }
56 | }
57 |
58 | /**
59 | * The stream ID of the stream that is affected encoded as a
60 | * variable-length integer.
61 | */
62 | private StreamId streamId;
63 |
64 | /**
65 | * A variable-length integer indicating the maximum
66 | * amount of data that can be sent on the entire connection, in units
67 | * of bytes.
68 | */
69 | private VariableLengthInteger maximumStreamData;
70 |
71 | @Override
72 | public boolean isValid() {
73 | return streamId != null
74 | && maximumStreamData != null;
75 | }
76 |
77 | @Override
78 | public long getFrameLength() {
79 | long sum = type.getValue().getEncodedLengthInBytes();
80 | sum += streamId.getValue().getEncodedLengthInBytes();
81 | sum += maximumStreamData.getEncodedLengthInBytes();
82 | return sum;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/MaxStreamsFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * max streams frame.
13 | * existing known frames are : {@link FrameGeneralType#MAX_STREAMS}.
14 | *
15 | * A MAX_STREAMS frame (type=0x12 or 0x13) inform the peer of the
16 | * cumulative number of streams of a given type it is permitted to open.
17 | * A MAX_STREAMS frame with a type of 0x12 applies to bidirectional
18 | * streams, and a MAX_STREAMS frame with a type of 0x13 applies to
19 | * unidirectional streams.
20 | *
21 | * Loss or reordering can cause a MAX_STREAMS frame to be received that
22 | * state a lower stream limit than an endpoint has previously received.
23 | * MAX_STREAMS frames that do not increase the stream limit MUST be
24 | * ignored.
25 | *
26 | * An endpoint MUST NOT open more streams than permitted by the current
27 | * stream limit set by its peer. For instance, a server that receives a
28 | * unidirectional stream limit of 3 is permitted to open stream 3, 7,
29 | * and 11, but not stream 15. An endpoint MUST terminate a connection
30 | * with a STREAM_LIMIT_ERROR error if a peer opens more streams than was
31 | * permitted. This includes violations of remembered limits in Early
32 | * Data; see Section 7.4.1.
33 | *
34 | * Note that these frames (and the corresponding transport parameters)
35 | * do not describe the number of streams that can be opened
36 | * concurrently. The limit includes streams that have been closed as
37 | * well as those that are open.
38 | *
39 | * @author Tim Trense
40 | * @see QUIC Spec/Section 19.3
41 | */
42 | @Data
43 | public class MaxStreamsFrameImpl implements Frame {
44 |
45 | private final FrameType type;
46 |
47 | public MaxStreamsFrameImpl( @NonNull FrameType frameType ) {
48 | this.type = frameType;
49 | if ( type.getGeneralType() != FrameGeneralType.MAX_STREAMS ) {
50 | throw new IllegalArgumentException(
51 | "Cannot build an AckFrame with FrameGeneralType other than "
52 | + FrameGeneralType.MAX_STREAMS.name()
53 | );
54 | }
55 | }
56 |
57 | /**
58 | * A count of the cumulative number of streams of the
59 | * corresponding type that can be opened over the lifetime of the
60 | * connection. This value cannot exceed 2^60, as it is not possible
61 | * to encode stream IDs larger than 2^62-1. Receipt of a frame that
62 | * permits opening of a stream larger than this limit MUST be treated
63 | * as a FRAME_ENCODING_ERROR.
64 | */
65 | private VariableLengthInteger maximumStreams;
66 |
67 | @Override
68 | public boolean isValid() {
69 | return maximumStreams != null;
70 | }
71 |
72 | @Override
73 | public long getFrameLength() {
74 | long sum = type.getValue().getEncodedLengthInBytes();
75 | sum += maximumStreams.getEncodedLengthInBytes();
76 | return sum;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/MultiPaddingFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.Getter;
5 | import lombok.ToString;
6 |
7 | import com.timtrense.quic.Frame;
8 | import com.timtrense.quic.FrameType;
9 |
10 | /**
11 | * A MultiPaddingFrame is entirely made-up. There is no reason for it in the specification.
12 | * Because Padding Frames are completely empty apart from their serialized {@link FrameType},
13 | * this class solely prevents the implementation from the necessity of creating absurd numbers
14 | * of {@link PaddingFrameImpl} instances
15 | *
16 | * @author Tim Trense
17 | * @see PaddingFrameImpl
18 | */
19 | @ToString
20 | @EqualsAndHashCode
21 | public class MultiPaddingFrameImpl implements Frame {
22 |
23 | @Getter
24 | private int length;
25 |
26 | /**
27 | * Creates a new combination of length
{@link PaddingFrameImpl}
28 | *
29 | * @param length how many padding frames to represent by this instance, must be positive
30 | */
31 | public MultiPaddingFrameImpl( int length ) {
32 | setLength( length );
33 | }
34 |
35 | /**
36 | * Creates a new instance that represents exactly ONE {@link PaddingFrameImpl}.
37 | *
39 | * Users should use new PaddingFrameImpl(FrameType.PADDING)
directly
40 | * instead of creating a useless MULTI-PaddingFrameImpl if they wish to represent
41 | * exactly one padding frame. Instantiation of this class is fine if the true
42 | * length of consecutive padding frames will only after instantiation happens be
43 | * known. Nevertheless it is perfectly valid to have a multi padding frame of
44 | * length 1.
45 | */
46 | public MultiPaddingFrameImpl() {
47 | this( 1 );
48 | }
49 |
50 | @Override
51 | public FrameType getType() {
52 | return FrameType.PADDING;
53 | }
54 |
55 | public void setLength( int length ) {
56 | if ( length <= 0 ) {
57 | throw new IllegalArgumentException( "Cannot have a multi padding frame with non-positive length" );
58 | }
59 | this.length = length;
60 | }
61 |
62 | @Override
63 | public boolean isValid() {
64 | return true;
65 | }
66 |
67 | /**
68 | * The returned length does NOT specify ONE frame, but instead is the combined
69 | * length of all (one-byte-length) {@link PaddingFrameImpl} combined within this instance
70 | *
71 | * @return the number of bytes padded, always positive
72 | */
73 | @Override
74 | public long getFrameLength() {
75 | return length;
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/NewTokenFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * new token frame.
13 | * existing known frames are : {@link FrameType#NEW_TOKEN}.
14 | *
15 | * A server sends a NEW_TOKEN frame (type=0x07) to provide the client
16 | * with a token to send in the header of an Initial packet for a future
17 | * connection.
18 | *
19 | * An endpoint might receive multiple NEW_TOKEN frames that contain the
20 | * same token value if packets containing the frame are incorrectly
21 | * determined to be lost. Endpoints are responsible for discarding
22 | * duplicate values, which might be used to link connection attempts;
23 | * see Section 8.1.3.
24 | *
25 | * Clients MUST NOT send NEW_TOKEN frames. Servers MUST treat receipt
26 | * of a NEW_TOKEN frame as a connection error of type
27 | * PROTOCOL_VIOLATION.
28 | *
29 | * @author Tim Trense
30 | * @see QUIC Spec/Section 19.3
31 | */
32 | @Data
33 | public class NewTokenFrameImpl implements Frame {
34 |
35 | private final FrameType type;
36 |
37 | public NewTokenFrameImpl( @NonNull FrameType frameType ) {
38 | this.type = frameType;
39 | if ( type.getGeneralType() != FrameGeneralType.NEW_TOKEN ) {
40 | throw new IllegalArgumentException(
41 | "Cannot build an AckFrame with FrameGeneralType other than "
42 | + FrameGeneralType.NEW_TOKEN.name()
43 | );
44 | }
45 | }
46 |
47 | /**
48 | * A variable-length integer specifying the length of the
49 | * token in bytes.
50 | */
51 | private VariableLengthInteger tokenLength;
52 | /**
53 | * An opaque blob that the client may use with a future Initial
54 | * packet. The token MUST NOT be empty. An endpoint MUST treat
55 | * receipt of a NEW_TOKEN frame with an empty Token field as a
56 | * connection error of type FRAME_ENCODING_ERROR.
57 | */
58 | private byte[] token;
59 |
60 | @Override
61 | public boolean isValid() {
62 | return tokenLength != null
63 | && token != null
64 | && token.length > 0
65 | && tokenLength.longValue() == token.length
66 | ;
67 | }
68 |
69 | @Override
70 | public long getFrameLength() {
71 | long sum = type.getValue().getEncodedLengthInBytes();
72 | sum += tokenLength.getEncodedLengthInBytes();
73 | sum += tokenLength.getValue();
74 | return sum;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/PaddingFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 |
5 | import com.timtrense.quic.Frame;
6 | import com.timtrense.quic.FrameGeneralType;
7 | import com.timtrense.quic.FrameType;
8 |
9 | /**
10 | * padding frame.
11 | * existing known frames are : {@link FrameType#PADDING}
12 | *
13 | * A PADDING frame (type=0x00) has no semantic value. PADDING frames
14 | * can be used to increase the size of a packet. Padding can be used to
15 | * increase an initial client packet to the minimum required size, or to
16 | * provide protection against traffic analysis for protected packets.
17 | *
18 | * PADDING frames are formatted as shown in Figure 23, which shows that
19 | * PADDING frames have no content. That is, a PADDING frame consists of
20 | * the single byte that identifies the frame as a PADDING frame.
21 | *
22 | * Note: Implementations should use {@link MultiPaddingFrameImpl}
23 | * in order to not need to instantiate absurd amounts of {@link PaddingFrameImpl}
24 | *
25 | * @author Tim Trense
26 | * @see QUIC Spec/Section 19.3
27 | */
28 | @Data
29 | public class PaddingFrameImpl implements Frame {
30 |
31 | private final FrameType type;
32 |
33 | public PaddingFrameImpl( FrameType frameType ) {
34 | this.type = frameType;
35 | if ( type.getGeneralType() != FrameGeneralType.PADDING ) {
36 | throw new IllegalArgumentException(
37 | "Cannot build a PaddingFrame with FrameGeneralType other than "
38 | + FrameGeneralType.PADDING.name()
39 | );
40 | }
41 | }
42 |
43 | @Override
44 | public boolean isValid() {
45 | return true;
46 | }
47 |
48 | @Override
49 | public long getFrameLength() {
50 | // this will always be 1, because type.getValue() == 0
51 | // return type.getValue().getEncodedLengthInBytes();
52 | return 1;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/PathChallangeFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 |
10 | /**
11 | * path challange frame.
12 | * existing known frames are : {@link FrameType#PATH_CHALLENGE}.
13 | *
14 | * Endpoints can use PATH_CHALLENGE frames (type=0x1a) to check
15 | * reachability to the peer and for path validation during connection
16 | * migration.
17 | *
18 | * Including 64 bits of entropy in a PATH_CHALLENGE frame ensures that
19 | * it is easier to receive the packet than it is to guess the value
20 | * correctly.
21 | *
22 | * The recipient of this frame MUST generate a PATH_RESPONSE frame
23 | * (Section 19.18) containing the same Data.
24 | *
25 | * @author Tim Trense
26 | * @see QUIC Spec/Section 19.3
27 | */
28 | @Data
29 | public class PathChallangeFrameImpl implements Frame {
30 |
31 | private final FrameType type;
32 |
33 | public PathChallangeFrameImpl( @NonNull FrameType frameType ) {
34 | this.type = frameType;
35 | if ( type.getGeneralType() != FrameGeneralType.PATH_CHALLENGE ) {
36 | throw new IllegalArgumentException(
37 | "Cannot build an AckFrame with FrameGeneralType other than "
38 | + FrameGeneralType.PATH_CHALLENGE.name()
39 | );
40 | }
41 | }
42 |
43 | /**
44 | * This 8-byte field contains arbitrary data.
45 | */
46 | private byte[] data;
47 |
48 | @Override
49 | public boolean isValid() {
50 | return data != null
51 | && data.length == 8
52 | ;
53 | }
54 |
55 | @Override
56 | public long getFrameLength() {
57 | /*
58 | long sum = type.getValue().getEncodedLengthInBytes();
59 | sum += 8; // data.length == 8 by protocol specification
60 | return sum;
61 | */
62 | return 9; // 8 (data.length) + 1 (encoded length of type PATH_CHALLENGE which is 0x1a)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/PathResponseFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 |
10 | /**
11 | * path response frame.
12 | * existing known frames are : {@link FrameType#PATH_RESPONSE}.
13 | *
14 | * A PATH_RESPONSE frame (type=0x1b) is sent in response to a
15 | * PATH_CHALLENGE frame.
16 | *
17 | * PATH_RESPONSE frames are formatted as shown in Figure 42, which is
18 | * identical to the PATH_CHALLENGE frame (Section 19.17).
19 | *
20 | * If the content of a PATH_RESPONSE frame does not match the content of
21 | * a PATH_CHALLENGE frame previously sent by the endpoint, the endpoint
22 | * MAY generate a connection error of type PROTOCOL_VIOLATION.
23 | *
24 | * @author Tim Trense
25 | * @see QUIC Spec/Section 19.3
26 | */
27 | @Data
28 | public class PathResponseFrameImpl implements Frame {
29 |
30 | private final FrameType type;
31 |
32 | public PathResponseFrameImpl( @NonNull FrameType frameType ) {
33 | this.type = frameType;
34 | if ( type.getGeneralType() != FrameGeneralType.PATH_RESPONSE ) {
35 | throw new IllegalArgumentException(
36 | "Cannot build an AckFrame with FrameGeneralType other than "
37 | + FrameGeneralType.PATH_RESPONSE.name()
38 | );
39 | }
40 | }
41 |
42 | /**
43 | * This 8-byte field contains arbitrary data.
44 | */
45 | private byte[] data;
46 |
47 | @Override
48 | public boolean isValid() {
49 | return data != null
50 | && data.length == 8
51 | ;
52 | }
53 |
54 | @Override
55 | public long getFrameLength() {
56 | /*
57 | long sum = type.getValue().getEncodedLengthInBytes();
58 | sum += 8; // data.length == 8 by protocol specification
59 | return sum;
60 | */
61 | return 9; // 8 (data.length) + 1 (encoded length of type PATH_RESPONSE which is 0x1b)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/ResetStreamFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.StreamId;
10 | import com.timtrense.quic.VariableLengthInteger;
11 |
12 | /**
13 | * reset stream frame.
14 | * existing known frames are : {@link FrameType#RESET_STREAM}
15 | *
16 | * An endpoint uses a RESET_STREAM frame (type=0x04) to abruptly
17 | * terminate the sending part of a stream.
18 | *
19 | * After sending a RESET_STREAM, an endpoint ceases transmission and
20 | * retransmission of STREAM frames on the identified stream. A receiver
21 | * of RESET_STREAM can discard any data that it already received on that
22 | * stream.
23 | *
24 | * An endpoint that receives a RESET_STREAM frame for a send-only stream
25 | * MUST terminate the connection with error STREAM_STATE_ERROR.
26 | *
27 | * @author Tim Trense
28 | * @see QUIC Spec/Section 19.4
29 | */
30 | @Data
31 | public class ResetStreamFrameImpl implements Frame {
32 |
33 | private final FrameType type;
34 |
35 | public ResetStreamFrameImpl( @NonNull FrameType type ) {
36 | this.type = type;
37 | if ( type.getGeneralType() != FrameGeneralType.RESET_STREAM ) {
38 | throw new IllegalArgumentException(
39 | "Cannot build an ResetStreamFrame with FrameGeneralType other than "
40 | + FrameGeneralType.RESET_STREAM.name()
41 | );
42 | }
43 | }
44 |
45 | /**
46 | * A variable-length integer encoding of the Stream ID of
47 | * the stream being terminated
48 | */
49 | private StreamId streamId;
50 |
51 | /**
52 | * A variable-length integer
53 | * containing the application protocol error code (see Section 20.2)
54 | * that indicates why the stream is being closed
55 | */
56 | private VariableLengthInteger applicationProtocolErrorCode;
57 |
58 | /**
59 | * A variable-length integer indicating the final size of
60 | * the stream by the RESET_STREAM sender, in unit of bytes; see
61 | * Section 4.5
62 | */
63 | private VariableLengthInteger finalSize;
64 |
65 | @Override
66 | public boolean isValid() {
67 | return streamId != null
68 | && finalSize != null
69 | && applicationProtocolErrorCode != null;
70 | }
71 |
72 | @Override
73 | public long getFrameLength() {
74 | long sum = type.getValue().getEncodedLengthInBytes();
75 | sum += streamId.getValue().getEncodedLengthInBytes();
76 | sum += applicationProtocolErrorCode.getEncodedLengthInBytes();
77 | sum += finalSize.getEncodedLengthInBytes();
78 | return sum;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/RetireConnectionIdFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * retire connection id frame.
13 | * existing known frames are : {@link FrameType#RETIRE_CONNECTION_ID}.
14 | *
15 | * An endpoint sends a RETIRE_CONNECTION_ID frame (type=0x19) to
16 | * indicate that it will no longer use a connection ID that was issued
17 | * by its peer. This may include the connection ID provided during the
18 | * handshake. Sending a RETIRE_CONNECTION_ID frame also serves as a
19 | * request to the peer to send additional connection IDs for future use;
20 | * see Section 5.1. New connection IDs can be delivered to a peer using
21 | * the NEW_CONNECTION_ID frame (Section 19.15).
22 | *
23 | * Retiring a connection ID invalidates the stateless reset token
24 | * associated with that connection ID.
25 | *
26 | * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number
27 | * greater than any previously sent to the peer MUST be treated as a
28 | * connection error of type PROTOCOL_VIOLATION.
29 | *
30 | * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST
31 | * NOT refer to the Destination Connection ID field of the packet in
32 | * which the frame is contained. The peer MAY treat this as a
33 | * connection error of type PROTOCOL_VIOLATION.
34 | *
35 | * An endpoint cannot send this frame if it was provided with a zero-
36 | * length connection ID by its peer. An endpoint that provides a zero-
37 | * length connection ID MUST treat receipt of a RETIRE_CONNECTION_ID
38 | * frame as a connection error of type PROTOCOL_VIOLATION.
39 | *
40 | * @author Tim Trense
41 | * @see QUIC Spec/Section 19.3
42 | */
43 | @Data
44 | public class RetireConnectionIdFrameImpl implements Frame {
45 |
46 | private final FrameType type;
47 |
48 | public RetireConnectionIdFrameImpl( @NonNull FrameType frameType ) {
49 | this.type = frameType;
50 | if ( type.getGeneralType() != FrameGeneralType.RETIRE_CONNECTION_ID ) {
51 | throw new IllegalArgumentException(
52 | "Cannot build an AckFrame with FrameGeneralType other than "
53 | + FrameGeneralType.RETIRE_CONNECTION_ID.name()
54 | );
55 | }
56 | }
57 |
58 | /**
59 | * The sequence number of the connection ID being
60 | * retired; see Section 5.1.2.
61 | */
62 | private VariableLengthInteger sequenceNumber;
63 |
64 | @Override
65 | public boolean isValid() {
66 | return sequenceNumber != null;
67 | }
68 |
69 | @Override
70 | public long getFrameLength() {
71 | long sum = type.getValue().getEncodedLengthInBytes();
72 | sum += sequenceNumber.getEncodedLengthInBytes();
73 | return sum;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/StopSendingFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 |
5 | import com.timtrense.quic.Frame;
6 | import com.timtrense.quic.FrameGeneralType;
7 | import com.timtrense.quic.FrameType;
8 | import com.timtrense.quic.StreamId;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * stop sending frame.
13 | * existing known frames are : {@link FrameType#STOP_SENDING}
14 | *
15 | * An endpoint uses a STOP_SENDING frame (type=0x05) to communicate that
16 | * incoming data is being discarded on receipt at application request.
17 | * STOP_SENDING requests that a peer cease transmission on a stream.
18 | *
19 | * A STOP_SENDING frame can be sent for streams in the Recv or Size
20 | * Known states; see Section 3.1. Receiving a STOP_SENDING frame for a
21 | * locally-initiated stream that has not yet been created MUST be
22 | * treated as a connection error of type STREAM_STATE_ERROR. An
23 | * endpoint that receives a STOP_SENDING frame for a receive-only stream
24 | * MUST terminate the connection with error STREAM_STATE_ERROR.
25 | *
26 | * @author Tim Trense
27 | * @see QUIC Spec/Section 19.5
28 | */
29 | @Data
30 | public class StopSendingFrameImpl implements Frame {
31 |
32 | private final FrameType type;
33 |
34 | public StopSendingFrameImpl( FrameType type ) {
35 | this.type = type;
36 | if ( type.getGeneralType() != FrameGeneralType.STOP_SENDING ) {
37 | throw new IllegalArgumentException(
38 | "Cannot build an ResetStreamFrame with FrameGeneralType other than "
39 | + FrameGeneralType.STOP_SENDING.name()
40 | );
41 | }
42 | }
43 |
44 | /**
45 | * A variable-length integer carrying the Stream ID of the stream being ignored
46 | */
47 | private StreamId streamId;
48 |
49 | /**
50 | * A variable-length integer
51 | * containing the application-specified reason the sender is ignoring
52 | * the stream; see Section 20.2
53 | */
54 | private VariableLengthInteger applicationProtocolErrorCode;
55 |
56 | @Override
57 | public boolean isValid() {
58 | return streamId != null
59 | && applicationProtocolErrorCode != null;
60 | }
61 |
62 | @Override
63 | public long getFrameLength() {
64 | long sum = type.getValue().getEncodedLengthInBytes();
65 | sum += streamId.getValue().getEncodedLengthInBytes();
66 | sum += applicationProtocolErrorCode.getEncodedLengthInBytes();
67 | return sum;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/StreamDataBlockedFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.StreamId;
10 | import com.timtrense.quic.VariableLengthInteger;
11 |
12 | /**
13 | * stream data blocked frame.
14 | * existing known frames are : {@link FrameGeneralType#STREAM_DATA_BLOCKED}.
15 | *
16 | * A sender SHOULD send a STREAM_DATA_BLOCKED frame (type=0x15) when it
17 | * wishes to send data, but is unable to do so due to stream-level flow
18 | * control. This frame is analogous to DATA_BLOCKED (Section 19.12).
19 | *
20 | * An endpoint that receives a STREAM_DATA_BLOCKED frame for a send-only
21 | * stream MUST terminate the connection with error STREAM_STATE_ERROR.
22 | *
23 | * @author Tim Trense
24 | * @see QUIC Spec/Section 19.3
25 | */
26 | @Data
27 | public class StreamDataBlockedFrameImpl implements Frame {
28 |
29 | private final FrameType type;
30 |
31 | public StreamDataBlockedFrameImpl( @NonNull FrameType frameType ) {
32 | this.type = frameType;
33 | if ( type.getGeneralType() != FrameGeneralType.STREAM_DATA_BLOCKED ) {
34 | throw new IllegalArgumentException(
35 | "Cannot build an AckFrame with FrameGeneralType other than "
36 | + FrameGeneralType.STREAM_DATA_BLOCKED.name()
37 | );
38 | }
39 | }
40 |
41 | /**
42 | * A variable-length integer indicating the stream that is
43 | * blocked due to flow control.
44 | */
45 | private StreamId streamId;
46 |
47 | /**
48 | * A variable-length integer indicating the offset
49 | * of the stream at which the blocking occurred.
50 | */
51 | private VariableLengthInteger maximumStreamData;
52 |
53 | @Override
54 | public boolean isValid() {
55 | return streamId != null
56 | && maximumStreamData != null;
57 | }
58 |
59 | @Override
60 | public long getFrameLength() {
61 | long sum = type.getValue().getEncodedLengthInBytes();
62 | sum += streamId.getValue().getEncodedLengthInBytes();
63 | sum += maximumStreamData.getEncodedLengthInBytes();
64 | return sum;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/frames/StreamsBlockedFrameImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.frames;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.Frame;
7 | import com.timtrense.quic.FrameGeneralType;
8 | import com.timtrense.quic.FrameType;
9 | import com.timtrense.quic.VariableLengthInteger;
10 |
11 | /**
12 | * streams blocked frame.
13 | * existing known frames are : {@link FrameGeneralType#STREAMS_BLOCKED}.
14 | *
15 | * A sender SHOULD send a STREAMS_BLOCKED frame (type=0x16 or 0x17) when
16 | * it wishes to open a stream, but is unable to due to the maximum
17 | * stream limit set by its peer; see Section 19.11. A STREAMS_BLOCKED
18 | * frame of type 0x16 is used to indicate reaching the bidirectional
19 | * stream limit, and a STREAMS_BLOCKED frame of type 0x17 is used to
20 | * indicate reaching the unidirectional stream limit.
21 | *
22 | * A STREAMS_BLOCKED frame does not open the stream, but informs the
23 | * peer that a new stream was needed and the stream limit prevented the
24 | * creation of the stream.
25 | *
26 | * @author Tim Trense
27 | * @see QUIC Spec/Section 19.3
28 | */
29 | @Data
30 | public class StreamsBlockedFrameImpl implements Frame {
31 |
32 | private final FrameType type;
33 |
34 | public StreamsBlockedFrameImpl( @NonNull FrameType frameType ) {
35 | this.type = frameType;
36 | if ( type.getGeneralType() != FrameGeneralType.STREAMS_BLOCKED ) {
37 | throw new IllegalArgumentException(
38 | "Cannot build an AckFrame with FrameGeneralType other than "
39 | + FrameGeneralType.STREAMS_BLOCKED.name()
40 | );
41 | }
42 | }
43 |
44 | /**
45 | * A variable-length integer indicating the maximum
46 | * number of streams allowed at the time the frame was sent. This
47 | * value cannot exceed 2^60, as it is not possible to encode stream
48 | * IDs larger than 2^62-1. Receipt of a frame that encodes a larger
49 | * stream ID MUST be treated as a STREAM_LIMIT_ERROR or a
50 | * FRAME_ENCODING_ERROR.
51 | */
52 | private VariableLengthInteger maximumStreams;
53 |
54 | @Override
55 | public boolean isValid() {
56 | return maximumStreams != null;
57 | }
58 |
59 | @Override
60 | public long getFrameLength() {
61 | long sum = type.getValue().getEncodedLengthInBytes();
62 | sum += maximumStreams.getEncodedLengthInBytes();
63 | return sum;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * QUIC implementation in pure Java.
3 | *
4 | * This package contains the implementation of {@link com.timtrense.quic}.
5 | *
6 | * @author Tim Trense
7 | */
8 | package com.timtrense.quic.impl;
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/packets/BaseLongHeaderPacket.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.packets;
2 |
3 | import lombok.Data;
4 | import lombok.ToString;
5 |
6 | import com.timtrense.quic.ConnectionId;
7 | import com.timtrense.quic.LongHeaderPacket;
8 | import com.timtrense.quic.ProtocolVersion;
9 |
10 | /**
11 | * Common abstract base class for all {@link LongHeaderPacket long header packets},
12 | * containing all properties that they all share.
13 | *
14 | * @author Tim Trense
15 | */
16 | @Data
17 | @ToString( callSuper = true )
18 | public abstract class BaseLongHeaderPacket implements LongHeaderPacket {
19 |
20 | protected byte flags;
21 | protected ProtocolVersion version;
22 | protected long destinationConnectionIdLength;
23 | protected ConnectionId destinationConnectionId;
24 | protected long sourceConnectionIdLength;
25 | protected ConnectionId sourceConnectionId;
26 |
27 | /**
28 | * Subclasses still need to override this. This implementation may not give the complete length
29 | *
30 | * @return the length of the base part of the long header packet
31 | */
32 | @Override
33 | public long getPacketLength() {
34 | return getHeaderLength();
35 | }
36 |
37 | /**
38 | * Subclasses still need to override this. This implementation may not give the complete length
39 | *
40 | * @return the length of the base part of the long header packet
41 | */
42 | @Override
43 | public long getHeaderLength() {
44 | // this sum will be precomputed by the compiler
45 | long sum = 1L // flags
46 | + 4L // version-length
47 | + 1L // destination connection id length field
48 | + 1L // source connection id length field
49 | ;
50 | sum += destinationConnectionIdLength;
51 | sum += sourceConnectionIdLength;
52 | return sum;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/impl/packets/ShortHeaderPacketImpl.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.packets;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 | import lombok.Data;
6 | import lombok.NonNull;
7 | import lombok.RequiredArgsConstructor;
8 | import lombok.ToString;
9 |
10 | import com.timtrense.quic.ConnectionId;
11 | import com.timtrense.quic.Frame;
12 | import com.timtrense.quic.PacketNumber;
13 | import com.timtrense.quic.ShortHeaderPacket;
14 |
15 | /**
16 | * For all details on this class, see {@link ShortHeaderPacket}
17 | *
18 | * @author Tim Trense
19 | */
20 | @Data
21 | @ToString( callSuper = true )
22 | @RequiredArgsConstructor
23 | public class ShortHeaderPacketImpl implements ShortHeaderPacket {
24 |
25 | private final @NonNull List payload = new LinkedList<>();
26 | private byte flags;
27 | private ConnectionId destinationConnectionId;
28 | private PacketNumber packetNumber;
29 |
30 | @Override
31 | public boolean isPacketValid() {
32 | return ( ( flags & 0b10000000 ) == 0b00000000 ) // header form = short (0)
33 | && ( ( flags & 0b01000000 ) == 0b01000000 ) // fixed bit
34 | // spin bit may have arbitrary value
35 | // key phase bit may have arbitrary value
36 | && ( ( flags & 0b00000011 ) != 0b00000000 ) // packet number length may not be zero
37 | && packetNumber != null
38 | && destinationConnectionId != null;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * QUIC interface in pure java.
3 | *
4 | * This package contains a general purpose interface to the IETF QUIC transport protocol.
5 | *
6 | * @author Tim Trense
7 | * @see QUIC Specification
8 | */
9 | package com.timtrense.quic;
10 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/CertificateEntry.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author Tim Trense
7 | * @see TLS 1.3 Spec/Section 4.4.2
8 | */
9 | @Data
10 | public class CertificateEntry {
11 |
12 | /**
13 | * the type of the certificate
14 | */
15 | private CertificateType certificateType;
16 | /**
17 | * depending on the type one of the following:
18 | *
19 | * select (certificate_type) {
20 | * case RawPublicKey:
21 | * // From RFC 7250 ASN.1_subjectPublicKeyInfo
22 | * opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
23 | * case X509:
24 | * opaque cert_data<1..2^24-1>;
25 | * };
26 | *
27 | */
28 | private byte[] certificateData = new byte[0];
29 | /**
30 | * A set of extension values for the CertificateEntry. The
31 | * "Extension" format is defined in Section 4.2. Valid extensions
32 | * for server certificates at present include the OCSP Status
33 | * extension [RFC6066] and the SignedCertificateTimestamp extension
34 | * [RFC6962]; future extensions may be defined for this message as
35 | * well. Extensions in the Certificate message from the server MUST
36 | * correspond to ones from the ClientHello message. Extensions in
37 | * the Certificate message from the client MUST correspond to
38 | * extensions in the CertificateRequest message from the server. If
39 | * an extension applies to the entire chain, it SHOULD be included in
40 | * the first CertificateEntry.
41 | */
42 | private Extension[] extensions = new Extension[0];
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/CertificateStatusType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | *
7 | * enum { ocsp(1), (255) } CertificateStatusType;
8 | *
9 | *
10 | * @author Tim Trense
11 | * @see TLS 1.3 Extensions Spec/Section 8
12 | */
13 | public enum CertificateStatusType {
14 |
15 | /**
16 | * @see Online Certificate Status Protocol
17 | */
18 | OCSP( 1 )
19 |
20 | // HIGHEST_VALUE( 255 )
21 | ;
22 |
23 | @Getter
24 | private final long value;
25 |
26 | CertificateStatusType( long value ) {this.value = value;}
27 |
28 | public static CertificateStatusType findByValue( int value ) {
29 | for ( CertificateStatusType f : values() ) {
30 | if ( f.value == value ) {
31 | return f;
32 | }
33 | }
34 | return null;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/CertificateType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | *
7 | * enum {
8 | * X509(0),
9 | * RawPublicKey(2),
10 | * (255)
11 | * } CertificateType;
12 | *
13 | *
14 | * @author Tim Trense
15 | * @see TLS 1.3 Spec/Section 4.4.2
16 | */
17 | public enum CertificateType {
18 |
19 | X509( 0 ),
20 | RAW_PUBLIC_KEY( 2 )
21 |
22 | // HIGHEST_VALUE( 255 )
23 | ;
24 |
25 | @Getter
26 | private final long value;
27 |
28 | CertificateType( long value ) {this.value = value;}
29 |
30 | public static CertificateType findByValue( int value ) {
31 | for ( CertificateType f : values() ) {
32 | if ( f.value == value ) {
33 | return f;
34 | }
35 | }
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/CipherSuite.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * A symmetric cipher suite defines the pair of the AEAD algorithm and
7 | * hash algorithm to be used with HKDF. Cipher suite names follow the
8 | * naming convention:
9 | *
10 | * CipherSuite TLS_AEAD_HASH = VALUE;
11 | *
12 | *
13 | * +-----------+------------------------------------------------+
14 | * | Component | Contents |
15 | * +-----------+------------------------------------------------+
16 | * | TLS | The string "TLS" |
17 | * | | |
18 | * | AEAD | The AEAD algorithm used for record protection |
19 | * | | |
20 | * | HASH | The hash algorithm used with HKDF |
21 | * | | |
22 | * | VALUE | The two-byte ID assigned for this cipher suite |
23 | * +-----------+------------------------------------------------+
24 | *
25 | *
26 | * @author Tim Trense
27 | * @see TLS 1.3 Spec/Appendix B.4
28 | */
29 | public enum CipherSuite {
30 |
31 | TLS_AES_128_GCM_SHA256( (short)0x1301 ),
32 | TLS_AES_256_GCM_SHA384( (short)0x1302 ),
33 | TLS_CHACHA20_POLY1305_SHA256( (short)0x1303 ),
34 | TLS_AES_128_CCM_SHA256( (short)0x1304 ),
35 | TLS_AES_128_CCM_8_SHA256( (short)0x1305 );
36 |
37 | @Getter
38 | private final short value;
39 |
40 | CipherSuite( short value ) {this.value = value;}
41 |
42 | public static CipherSuite findByValue( short value ) {
43 | for ( CipherSuite f : values() ) {
44 | if ( f.value == value ) {
45 | return f;
46 | }
47 | }
48 | return null;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/ExtendedHandshake.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import lombok.Data;
6 | import lombok.EqualsAndHashCode;
7 | import lombok.NonNull;
8 |
9 | /**
10 | * This class gives {@link Handshake}-Messages which contain {@link Extension extensions} a common base.
11 | * This class is the default implementation for {@link ExtensionCarryingHandshake}.
12 | * Code that operates on carried extensions should NOT work on this class but instead use access to the
13 | * implemented interface, because not all messages that contain extensions have this class as their base,
14 | * but all do implement the interface
15 | *
16 | * @author Tim Trense
17 | * @see TLS 1.3 Spec/Section 4
18 | */
19 | @Data
20 | @EqualsAndHashCode( callSuper = true )
21 | public abstract class ExtendedHandshake extends Handshake implements ExtensionCarryingHandshake {
22 |
23 | /**
24 | * A mutable list of {@link Extension extensions} registered with this message.
25 | *
26 | * Implementation Note: the field will be initialized with an
27 | * {@link ArrayList} of initial size 6 upon instantiation
28 | */
29 | private @NonNull List
7 | * struct {
8 | * ExtensionType extension_type;
9 | * opaque extension_data<0..2^16-1>;
10 | * } Extension;
11 | *
12 | *
13 | * @author Tim Trense
14 | * @see TLS 1.3 Spec/Section 4.2
15 | */
16 | @Data
17 | public abstract class Extension {
18 |
19 | /**
20 | * @return What type of extension this is
21 | */
22 | public abstract ExtensionType getExtensionType();
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/ExtensionCarryingHandshake.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.util.List;
4 | import lombok.NonNull;
5 |
6 | /**
7 | * Interface to be implemented by {@link Handshake handshake messages} that contain {@link Extension extensions}
8 | *
9 | * @author Tim Trense
10 | */
11 | public interface ExtensionCarryingHandshake {
12 |
13 | /**
14 | * @return a mutable list of all carried {@link Extension extension}, never null
15 | */
16 | List
9 | * struct {
10 | * HandshakeType msg_type;
11 | * uint24 length; // remaining bytes in message
12 | * select(Handshake.msg_type){
13 | * case client_hello:ClientHello;
14 | * case server_hello:ServerHello;
15 | * case end_of_early_data:EndOfEarlyData;
16 | * case encrypted_extensions:EncryptedExtensions;
17 | * case certificate_request:CertificateRequest;
18 | * case certificate:Certificate;
19 | * case certificate_verify:CertificateVerify;
20 | * case finished:Finished;
21 | * case new_session_ticket:NewSessionTicket;
22 | * case key_update:KeyUpdate;
23 | * };
24 | * } Handshake;
25 | *
26 | *
27 | * @author Tim Trense
28 | * @see TLS 1.3 Spec/Section 4
29 | */
30 | @Data
31 | public abstract class Handshake {
32 |
33 | /**
34 | * @return What type of handshake message this is
35 | */
36 | public abstract HandshakeType getMessageType();
37 |
38 | /**
39 | * Remaining bytes in message as a 3-byte unsigned integer
40 | *
41 | * Implementation Note: the field will be set to zero upon instantiation
42 | */
43 | private int length;
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/HandshakeType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | import com.timtrense.quic.tls.handshake.Certificate;
6 | import com.timtrense.quic.tls.handshake.CertificateRequest;
7 | import com.timtrense.quic.tls.handshake.CertificateVerify;
8 | import com.timtrense.quic.tls.handshake.ClientHello;
9 | import com.timtrense.quic.tls.handshake.EncryptedExtensions;
10 | import com.timtrense.quic.tls.handshake.EndOfEarlyData;
11 | import com.timtrense.quic.tls.handshake.Finished;
12 | import com.timtrense.quic.tls.handshake.HelloRetryRequest;
13 | import com.timtrense.quic.tls.handshake.KeyUpdate;
14 | import com.timtrense.quic.tls.handshake.NewSessionTicket;
15 | import com.timtrense.quic.tls.handshake.ServerHello;
16 |
17 | /**
18 | * The handshake protocol is used to negotiate the security parameters
19 | * of a connection. Handshake messages are supplied to the TLS record
20 | * layer, where they are encapsulated within one or more TLSPlaintext or
21 | * TLSCiphertext structures which are processed and transmitted as
22 | * specified by the current active connection state.
23 | *
24 | * @author Tim Trense
25 | * @see TLS 1.3 Spec/Section 4
26 | */
27 | public enum HandshakeType {
28 |
29 | /**
30 | * @see ClientHello
31 | */
32 | CLIENT_HELLO( 1 ),
33 | /**
34 | * The value will be used for {@link HelloRetryRequest} too, as it
35 | * is defined as being structurally equivalent to {@link ServerHello}
36 | *
37 | * @see ServerHello
38 | */
39 | SERVER_HELLO( 2 ),
40 | /**
41 | * @see NewSessionTicket
42 | */
43 | NEW_SESSION_TICKET( 4 ),
44 | /**
45 | * @see EndOfEarlyData
46 | */
47 | END_OF_EARLY_DATA( 5 ),
48 | /**
49 | * @see EncryptedExtensions
50 | */
51 | ENCRYPTED_EXTENSIONS( 8 ),
52 | /**
53 | * @see Certificate
54 | */
55 | CERTIFICATE( 11 ),
56 | /**
57 | * @see CertificateRequest
58 | */
59 | CERTIFICATE_REQUEST( 13 ),
60 | /**
61 | * @see CertificateVerify
62 | */
63 | CERTIFICATE_VERIFY( 15 ),
64 | /**
65 | * @see Finished
66 | */
67 | FINISHED( 20 ),
68 | /**
69 | * @see KeyUpdate
70 | */
71 | KEY_UPDATE( 24 ),
72 | MESSAGE_HASH( 254 ),
73 |
74 | // HIGHEST_VALUE( 255 )
75 | ;
76 |
77 | @Getter
78 | private final long value;
79 |
80 | HandshakeType( long value ) {this.value = value;}
81 |
82 | public static HandshakeType findByValue( int value ) {
83 | for ( HandshakeType f : values() ) {
84 | if ( f.value == value ) {
85 | return f;
86 | }
87 | }
88 | return null;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/HostName.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import lombok.Data;
5 | import lombok.EqualsAndHashCode;
6 | import lombok.NonNull;
7 |
8 | /**
9 | * A {@link ServerName} that identifies a server via its fully qualified domain name.
10 | *
11 | * "HostName" contains the fully qualified DNS hostname of the server,
12 | * as understood by the client. The hostname is represented as a byte
13 | * string using ASCII encoding without a trailing dot. This allows the
14 | * support of internationalized domain names through the use of A-labels
15 | * defined in [RFC5890]. DNS hostnames are case-insensitive. The
16 | * algorithm to compare hostnames is described in [RFC5890], Section
17 | * 2.3.2.4.
18 | *
19 | * Literal IPv4 and IPv6 addresses are not permitted in "HostName".
20 | *
21 | * @author Tim Trense
22 | * @see TLS 1.3 Extensions Spec/Section 3
23 | */
24 | @Data
25 | @EqualsAndHashCode( callSuper = true )
26 | public class HostName extends ServerName {
27 |
28 | private @NonNull byte[] value;
29 |
30 | /**
31 | * Converts the given host name to a byte-array via {@link StandardCharsets#US_ASCII}
32 | *
33 | * @param value the hostname to apply. must be ASCII-encodable, must not be null
34 | */
35 | public HostName( @NonNull String value ) {
36 | this( value.getBytes( StandardCharsets.US_ASCII ) );
37 | }
38 |
39 | public HostName( @NonNull byte[] value ) {
40 | this.value = value;
41 | }
42 |
43 | /**
44 | * Converts the given host name to a byte-array via {@link StandardCharsets#US_ASCII}
45 | *
46 | * @param hostname the hostname to apply. must be ASCII-encodable, must not be null
47 | */
48 | public void setHostnameByString( @NonNull String hostname ) {
49 | this.value = hostname.getBytes( StandardCharsets.US_ASCII );
50 | }
51 |
52 | @Override
53 | public String toString() {
54 | return new String( value, StandardCharsets.US_ASCII );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/KeyShareEntry.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | *
7 | * struct {
8 | * NamedGroup group;
9 | * opaque key_exchange<1..2^16-1>;
10 | * } KeyShareEntry;
11 | *
12 | *
13 | * @author Tim Trense
14 | * @see TLS 1.3 Spec/Section 4.2.8
15 | */
16 | @Data
17 | public class KeyShareEntry {
18 |
19 | /**
20 | * The named group for the key being exchanged.
21 | */
22 | private NamedGroup group;
23 | /**
24 | * Key exchange information. The contents of this field
25 | * are determined by the specified group and its corresponding
26 | * definition. Finite Field Diffie-Hellman [DH76] parameters are
27 | * described in Section 4.2.8.1; Elliptic Curve Diffie-Hellman
28 | * parameters are described in Section 4.2.8.2.
29 | */
30 | private byte[] keyExchange;
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/KeyUpdateRequest.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | *
7 | * enum {
8 | * update_not_requested(0), update_requested(1), (255)
9 | * } KeyUpdateRequest;
10 | *
11 | *
12 | * @author Tim Trense
13 | * @see TLS 1.3 Spec/Section 4.6.3
14 | */
15 | public enum KeyUpdateRequest {
16 |
17 | UPDATE_NOT_REQUESTED( 0 ),
18 | UPDATE_REQUESTED( 1 )
19 |
20 | // HIGHEST_VALUE( 255 )
21 | ;
22 |
23 | @Getter
24 | private final long value;
25 |
26 | KeyUpdateRequest( long value ) {this.value = value;}
27 |
28 | public static KeyUpdateRequest findByValue( int value ) {
29 | for ( KeyUpdateRequest f : values() ) {
30 | if ( f.value == value ) {
31 | return f;
32 | }
33 | }
34 | return null;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/NameType.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * Currently, the only server names supported are DNS hostnames;
7 | * however, this does not imply any dependency of TLS on DNS, and other
8 | * name types may be added in the future (by an RFC that updates this
9 | * document). The data structure associated with the host_name NameType
10 | * is a variable-length vector that begins with a 16-bit length. For
11 | * backward compatibility, all future data structures associated with
12 | * new NameTypes MUST begin with a 16-bit length field. TLS MAY treat
13 | * provided server names as opaque data and pass the names and types to
14 | * the application.
15 | *
16 | * @author Tim Trense
17 | * @see TLS 1.3 Extensions Spec/Section 3
18 | */
19 | public enum NameType {
20 |
21 | HOST_NAME( 0 )
22 |
23 | // HIGHEST_VALUE( 255 )
24 | ;
25 |
26 | @Getter
27 | private final long value;
28 |
29 | NameType( long value ) {this.value = value;}
30 |
31 | public static NameType findByValue( int value ) {
32 | for ( NameType f : values() ) {
33 | if ( f.value == value ) {
34 | return f;
35 | }
36 | }
37 | return null;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/OcspExtensions.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import lombok.Data;
5 | import lombok.NonNull;
6 |
7 | /**
8 | * OCSP extensions
9 | *
10 | * @author Tim Trense
11 | * @see OCS Protocol
12 | */
13 | @Data
14 | public class OcspExtensions {
15 |
16 | private @NonNull byte[] value;
17 |
18 | public OcspExtensions( @NonNull byte[] value ) {
19 | this.value = value;
20 | }
21 |
22 | @Override
23 | public String toString() {
24 | return new String( value, StandardCharsets.US_ASCII );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/OcspResponderId.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import lombok.Data;
5 | import lombok.NonNull;
6 |
7 | /**
8 | * An OCSP responder
9 | *
10 | * @author Tim Trense
11 | * @see OCS Protocol
12 | */
13 | @Data
14 | public class OcspResponderId {
15 |
16 | private @NonNull byte[] value;
17 |
18 | /**
19 | * Converts the given responder id to a byte-array via {@link StandardCharsets#US_ASCII}
20 | *
21 | * @param value the hostname to apply. must be ASCII-encodable, must not be null
22 | */
23 | public OcspResponderId( @NonNull String value ) {
24 | this( value.getBytes( StandardCharsets.US_ASCII ) );
25 | }
26 |
27 | public OcspResponderId( @NonNull byte[] value ) {
28 | this.value = value;
29 | }
30 |
31 | /**
32 | * Converts the given responder id to a byte-array via {@link StandardCharsets#US_ASCII}
33 | *
34 | * @param id the id to apply. must be ASCII-encodable, must not be null
35 | */
36 | public void setIdByString( @NonNull String id ) {
37 | this.value = id.getBytes( StandardCharsets.US_ASCII );
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return new String( value, StandardCharsets.US_ASCII );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/OidFilter.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | *
7 | * struct {
8 | * opaque certificate_extension_oid<1..2^8-1>;
9 | * opaque certificate_extension_values<0..2^16-1>;
10 | * } OIDFilter;
11 | *
12 | *
13 | * @author Tim Trense
14 | * @see TLS 1.3 Spec/Section 4.2.5
15 | */
16 | @Data
17 | public class OidFilter {
18 |
19 | private byte[] certificateExtensionOld = new byte[0];
20 | private byte[] certificateExtensionValues = new byte[0];
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/ProtocolName.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import lombok.Data;
5 | import lombok.NonNull;
6 |
7 | /**
8 | * An application layer protocol name
9 | *
10 | * @author Tim Trense
11 | * @see ALPN TLS 1.3 Extension/Section 3.1
12 | */
13 | @Data
14 | public class ProtocolName {
15 |
16 | private byte[] value;
17 |
18 | /**
19 | * Converts the given host name to a byte-array via {@link StandardCharsets#US_ASCII}
20 | *
21 | * @param value the protocolName to apply. must be ASCII-encodable, must not be null
22 | */
23 | public ProtocolName( @NonNull String value ) {
24 | this( value.getBytes( StandardCharsets.US_ASCII ) );
25 | }
26 |
27 | public ProtocolName( @NonNull byte[] value ) {
28 | this.value = value;
29 | }
30 |
31 | /**
32 | * Converts the given host name to a byte-array via {@link StandardCharsets#US_ASCII}
33 | *
34 | * @param protocolName the protocolName to apply. must be ASCII-encodable, must not be null
35 | */
36 | public void setHostnameByString( @NonNull String protocolName ) {
37 | this.value = protocolName.getBytes( StandardCharsets.US_ASCII );
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return new String( value, StandardCharsets.US_ASCII );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/ProtocolVersion.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * TLS Protocol Version. Values will be encoded as uint16.
7 | *
8 | * @author Tim Trense
9 | */
10 | public enum ProtocolVersion {
11 |
12 | // lower values are not supported
13 | /**
14 | * TLS 1.2 is not supported by this implementation.
15 | * This constant solely exists, because in rare situations
16 | * the specification of the protocol needs an implementation
17 | * to specify values referring to compatibility.
18 | */
19 | TLS_1_2( 0x0303 ),
20 | /**
21 | * The TLS 1.3 version that this implementation addresses.
22 | */
23 | TLS_1_3( 0x0304 );
24 |
25 | @Getter
26 | private final long value;
27 |
28 | ProtocolVersion( long value ) {this.value = value;}
29 |
30 | public static ProtocolVersion findByValue( int value ) {
31 | for ( ProtocolVersion f : values() ) {
32 | if ( f.value == value ) {
33 | return f;
34 | }
35 | }
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/PskBinderEntry.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * The PSK binder value forms a binding between a PSK and the current
7 | * handshake, as well as a binding between the handshake in which the
8 | * PSK was generated (if via a NewSessionTicket message) and the current
9 | * handshake. Each entry in the binders list is computed as an HMAC
10 | * over a transcript hash (see Section 4.4.1) containing a partial
11 | * ClientHello up to and including the PreSharedKeyExtension.identities
12 | * field. That is, it includes all of the ClientHello but not the
13 | * binders list itself. The length fields for the message (including
14 | * the overall length, the length of the extensions block, and the
15 | * length of the "pre_shared_key" extension) are all set as if binders
16 | * of the correct lengths were present.
17 | *
18 | * The PskBinderEntry is computed in the same way as the Finished
19 | * message (Section 4.4.4) but with the BaseKey being the binder_key
20 | * derived via the key schedule from the corresponding PSK which is
21 | * being offered (see Section 7.1).
22 | *
23 | * If the handshake includes a HelloRetryRequest, the initial
24 | * ClientHello and HelloRetryRequest are included in the transcript
25 | * along with the new ClientHello. For instance, if the client sends
26 | * ClientHello1, its binder will be computed over:
27 | *
28 | * Transcript-Hash(Truncate(ClientHello1))
29 | *
30 | * Where Truncate() removes the binders list from the ClientHello.
31 | *
32 | * If the server responds with a HelloRetryRequest and the client then
33 | * sends ClientHello2, its binder will be computed over:
34 | *
35 | * Transcript-Hash(
36 | * ClientHello1,
37 | * HelloRetryRequest,
38 | * Truncate(ClientHello2)
39 | * )
40 | *
41 | * The full ClientHello1/ClientHello2 is included in all other handshake
42 | * hash computations. Note that in the first flight,
43 | * Truncate(ClientHello1) is hashed directly, but in the second flight,
44 | * ClientHello1 is hashed and then reinjected as a "message_hash"
45 | * message, as described in Section 4.4.1.
46 | */
47 | @Data
48 | public class PskBinderEntry {
49 |
50 | /**
51 | * Implementation Note: the field will be initialized to an empty array upon instantiation
52 | */
53 | private byte[] entry = new byte[0];
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/PskIdentity.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | *
7 | * struct {
8 | * opaque identity<1..2^16-1>;
9 | * uint32 obfuscated_ticket_age;
10 | * } PskIdentity;
11 | *
12 | *
13 | * @author Tim Trense
14 | * @see TLS 1.3 Spec/Section 4.2.11
15 | */
16 | @Data
17 | public class PskIdentity {
18 |
19 | /**
20 | * A label for a key. For instance, a ticket (as defined in
21 | * Appendix B.3.4) or a label for a pre-shared key established
22 | * externally.
23 | *
24 | * Implementation Note: the field will be set to an empty array upon instantiation
25 | */
26 | private byte[] identity = new byte[0];
27 |
28 | /**
29 | * uint32
30 | *
31 | * An obfuscated version of the age of the key.
32 | * Section 4.2.11.1 describes how to form this value for identities
33 | * established via the NewSessionTicket message. For identities
34 | * established externally, an obfuscated_ticket_age of 0 SHOULD be
35 | * used, and servers MUST ignore the value.
36 | */
37 | private long obfuscatedTicketAge;
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/PskKeyExchangeMode.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * @author Tim Trense
7 | * @see TLS 1.3 Spec/Section 4.2.9
8 | */
9 | public enum PskKeyExchangeMode {
10 |
11 | /**
12 | * PSK-only key establishment. In this mode, the server
13 | * MUST NOT supply a "key_share" value.
14 | */
15 | PSK_KEY_ESTABLISHMENT( 0 ),
16 | /**
17 | * PSK with (EC)DHE key establishment. In this mode, the
18 | * client and server MUST supply "key_share" values as described in
19 | * Section 4.2.8.
20 | */
21 | PSK_DHE_KEY_ESTABLISHMENT( 1 ),
22 |
23 | // HIGHEST_VALUE( 255 )
24 | ;
25 |
26 | @Getter
27 | private final long value;
28 |
29 | PskKeyExchangeMode( long value ) {this.value = value;}
30 |
31 | public static PskKeyExchangeMode findByValue( int value ) {
32 | for ( PskKeyExchangeMode f : values() ) {
33 | if ( f.value == value ) {
34 | return f;
35 | }
36 | }
37 | return null;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/ServerName.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 |
6 | /**
7 | * @author Tim Trense
8 | * @see TLS 1.3 Extensions Spec/Section 3
9 | */
10 | @Data
11 | public abstract class ServerName {
12 |
13 | /**
14 | * Implementation Note: the field is initialized to {@link NameType#HOST_NAME} which is implemented in
15 | * {@link HostName}, because it is the only known {@link NameType} currently
16 | */
17 | private @NonNull NameType nameType = NameType.HOST_NAME;
18 |
19 | // subclasses should add their specific actual name implementation
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/CertificateAuthoritiesExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 | import lombok.NonNull;
6 |
7 | import com.timtrense.quic.tls.Extension;
8 | import com.timtrense.quic.tls.ExtensionType;
9 |
10 | /**
11 | *
12 | * struct {
13 | * DistinguishedName authorities<3..2^16-1>;
14 | * } CertificateAuthoritiesExtension;
15 | *
16 | *
17 | * The "certificate_authorities" extension is used to indicate the
18 | * certificate authorities (CAs) which an endpoint supports and which
19 | * SHOULD be used by the receiving endpoint to guide certificate
20 | * selection.
21 | *
22 | * The client MAY send the "certificate_authorities" extension in the
23 | * ClientHello message. The server MAY send it in the
24 | * CertificateRequest message.
25 | *
26 | * The "trusted_ca_keys" extension [RFC6066], which serves a similar
27 | * purpose but is more complicated, is not used in TLS 1.3 (although it
28 | * may appear in ClientHello messages from clients which are offering
29 | * prior versions of TLS).
30 | *
31 | * @author Tim Trense
32 | * @see TLS 1.3 Spec/Section 4.2.4
33 | */
34 | @Data
35 | @EqualsAndHashCode( callSuper = true )
36 | public class CertificateAuthoritiesExtension extends Extension {
37 |
38 | /**
39 | *
40 | * // opaque == 1 single uninterpreted byte
41 | * opaque DistinguishedName<1..2^16-1>;
42 | *
43 | * struct {
44 | * DistinguishedName authorities<3..2^16-1>;
45 | * } CertificateAuthoritiesExtension;
46 | *
47 | *
48 | * A list of the distinguished names [X501] of acceptable
49 | * certificate authorities, represented in DER-encoded [X690] format.
50 | * These distinguished names specify a desired distinguished name for
51 | * a trust anchor or subordinate CA; thus, this message can be used
52 | * to describe known trust anchors as well as a desired authorization
53 | * space.
54 | */
55 | private @NonNull byte[][] authorities = new byte[0][];
56 |
57 | @Override
58 | public ExtensionType getExtensionType() {
59 | return ExtensionType.SIGNATURE_ALGORITHMS;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/ClientSupportedVersionsExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.ProtocolVersion;
7 |
8 | /**
9 | * @author Tim Trense
10 | * @see SupportedVersionsExtensionBase
11 | */
12 | @Data
13 | @EqualsAndHashCode( callSuper = true )
14 | public class ClientSupportedVersionsExtension extends SupportedVersionsExtensionBase {
15 |
16 | private ProtocolVersion[] versions;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/CookieExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Extension;
7 | import com.timtrense.quic.tls.ExtensionType;
8 |
9 | /**
10 | *
11 | * struct {
12 | * opaque cookie<1..2^16-1>;
13 | * } Cookie;
14 | *
15 | *
16 | * Cookies serve two primary purposes:
17 | *
18 | *
33 | *
34 | * When sending a HelloRetryRequest, the server MAY provide a "cookie"
35 | * extension to the client (this is an exception to the usual rule that
36 | * the only extensions that may be sent are those that appear in the
37 | * ClientHello). When sending the new ClientHello, the client MUST copy
38 | * the contents of the extension received in the HelloRetryRequest into
39 | * a "cookie" extension in the new ClientHello. Clients MUST NOT use
40 | * cookies in their initial ClientHello in subsequent connections.
41 | *
42 | * When a server is operating statelessly, it may receive an unprotected
43 | * record of type change_cipher_spec between the first and second
44 | * ClientHello (see Section 5). Since the server is not storing any
45 | * state, this will appear as if it were the first message to be
46 | * received. Servers operating statelessly MUST ignore these records.
47 | *
48 | * @author Tim Trense
49 | * @see TLS 1.3 Spec/Section 4.2.2
50 | */
51 | @Data
52 | @EqualsAndHashCode( callSuper = true )
53 | public class CookieExtension extends Extension {
54 |
55 | /**
56 | * 1 up to (2 pow 16 -1) bytes of cookie data
57 | */
58 | private byte[] cookie;
59 |
60 | @Override
61 | public ExtensionType getExtensionType() {
62 | return ExtensionType.COOKIE;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/KeyShareClientHelloExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.KeyShareEntry;
7 |
8 | /**
9 | * In the ClientHello message, the "extension_data" field of this
10 | * extension contains a "KeyShareClientHello" value:
11 | *
12 | *
13 | * struct {
14 | * KeyShareEntry client_shares<0..2^16-1>;
15 | * } KeyShareClientHello;
16 | *
17 | *
18 | * This vector MAY be empty if the client is requesting a
19 | * HelloRetryRequest. Each KeyShareEntry value MUST correspond to a
20 | * group offered in the "supported_groups" extension and MUST appear in
21 | * the same order. However, the values MAY be a non-contiguous subset
22 | * of the "supported_groups" extension and MAY omit the most preferred
23 | * groups. Such a situation could arise if the most preferred groups
24 | * are new and unlikely to be supported in enough places to make
25 | * pregenerating key shares for them efficient.
26 | *
27 | * Clients can offer as many KeyShareEntry values as the number of
28 | * supported groups it is offering, each representing a single set of
29 | * key exchange parameters. For instance, a client might offer shares
30 | * for several elliptic curves or multiple FFDHE groups. The
31 | * key_exchange values for each KeyShareEntry MUST be generated
32 | * independently. Clients MUST NOT offer multiple KeyShareEntry values
33 | * for the same group. Clients MUST NOT offer any KeyShareEntry values
34 | * for groups not listed in the client's "supported_groups" extension.
35 | * Servers MAY check for violations of these rules and abort the
36 | * handshake with an "illegal_parameter" alert if one is violated.
37 | *
38 | * @author Tim Trense
39 | * @see TLS 1.3 Spec/Section 4.2.8
40 | */
41 | @Data
42 | @EqualsAndHashCode( callSuper = true )
43 | public class KeyShareClientHelloExtension extends KeyShareExtensionBase {
44 |
45 | /**
46 | * A list of offered KeyShareEntry values in descending
47 | * order of client preference.
48 | *
49 | * Implementation Note: the field will be set to an empty array upon instantiation
50 | */
51 | private KeyShareEntry[] clientShares = new KeyShareEntry[0];
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/KeyShareExtensionBase.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Extension;
7 | import com.timtrense.quic.tls.ExtensionType;
8 |
9 | /**
10 | * The "key_share" extension contains the endpoint's cryptographic
11 | * parameters.
12 | *
13 | * Clients MAY send an empty client_shares vector in order to request
14 | * group selection from the server, at the cost of an additional round
15 | * trip (see Section 4.1.4).
16 | *
17 | * @author Tim Trense
18 | * @see TLS 1.3 Spec/Section 4.2.8
19 | */
20 | @Data
21 | @EqualsAndHashCode( callSuper = true )
22 | public abstract class KeyShareExtensionBase extends Extension {
23 |
24 | @Override
25 | public final ExtensionType getExtensionType() {
26 | return ExtensionType.KEY_SHARE;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/KeyShareHelloRetryRequestExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.NamedGroup;
7 |
8 | /**
9 | * In a HelloRetryRequest message, the "extension_data" field of this
10 | * extension contains a KeyShareHelloRetryRequest value:
11 | *
12 | *
13 | * struct {
14 | * NamedGroup selected_group;
15 | * } KeyShareHelloRetryRequest;
16 | *
17 | *
18 | * Upon receipt of this extension in a HelloRetryRequest, the client
19 | * MUST verify that (1) the selected_group field corresponds to a group
20 | * which was provided in the "supported_groups" extension in the
21 | * original ClientHello and (2) the selected_group field does not
22 | * correspond to a group which was provided in the "key_share" extension
23 | * in the original ClientHello. If either of these checks fails, then
24 | * the client MUST abort the handshake with an "illegal_parameter"
25 | * alert. Otherwise, when sending the new ClientHello, the client MUST
26 | * replace the original "key_share" extension with one containing only a
27 | * new KeyShareEntry for the group indicated in the selected_group field
28 | * of the triggering HelloRetryRequest.
29 | *
30 | * @author Tim Trense
31 | * @see TLS 1.3 Spec/Section 4.2.8
32 | */
33 | @Data
34 | @EqualsAndHashCode( callSuper = true )
35 | public class KeyShareHelloRetryRequestExtension extends KeyShareExtensionBase {
36 |
37 | /**
38 | * The mutually supported group the server intends to
39 | * negotiate and is requesting a retried ClientHello/KeyShare for.
40 | */
41 | private NamedGroup selectedGroup;
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/KeyShareServerHelloExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.KeyShareEntry;
7 |
8 | /**
9 | * In a ServerHello message, the "extension_data" field of this
10 | * extension contains a KeyShareServerHello value:
11 | *
12 | *
13 | * struct {
14 | * KeyShareEntry server_share;
15 | * } KeyShareServerHello;
16 | *
17 | *
18 | * If using (EC)DHE key establishment, servers offer exactly one
19 | * KeyShareEntry in the ServerHello. This value MUST be in the same
20 | * group as the KeyShareEntry value offered by the client that the
21 | * server has selected for the negotiated key exchange. Servers
22 | * MUST NOT send a KeyShareEntry for any group not indicated in the
23 | * client's "supported_groups" extension and MUST NOT send a
24 | * KeyShareEntry when using the "psk_ke" PskKeyExchangeMode. If using
25 | * (EC)DHE key establishment and a HelloRetryRequest containing a
26 | * "key_share" extension was received by the client, the client MUST
27 | * verify that the selected NamedGroup in the ServerHello is the same as
28 | * that in the HelloRetryRequest. If this check fails, the client MUST
29 | * abort the handshake with an "illegal_parameter" alert.
30 | *
31 | * @author Tim Trense
32 | * @see TLS 1.3 Spec/Section 4.2.8
33 | */
34 | @Data
35 | @EqualsAndHashCode( callSuper = true )
36 | public class KeyShareServerHelloExtension extends KeyShareExtensionBase {
37 |
38 | /**
39 | * A single KeyShareEntry value that is in the same group
40 | * as one of the client's shares.
41 | */
42 | private KeyShareEntry serverShare;
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/PostHandshakeClientAuthExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Extension;
7 | import com.timtrense.quic.tls.ExtensionType;
8 |
9 | /**
10 | *
11 | * struct {} PostHandshakeAuth;
12 | *
13 | *
14 | * The "post_handshake_auth" extension is used to indicate that a client
15 | * is willing to perform post-handshake authentication (Section 4.6.2).
16 | * Servers MUST NOT send a post-handshake CertificateRequest to clients
17 | * which do not offer this extension. Servers MUST NOT send this
18 | * extension.
19 | *
20 | * The "extension_data" field of the "post_handshake_auth" extension is
21 | * zero length.
22 | *
23 | * @author Tim Trense
24 | * @see TLS 1.3 Spec/Section 4.2.6
25 | */
26 | @Data
27 | @EqualsAndHashCode( callSuper = true )
28 | public class PostHandshakeClientAuthExtension extends Extension {
29 |
30 | @Override
31 | public ExtensionType getExtensionType() {
32 | return ExtensionType.POST_HANDSHAKE_AUTH;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/PreSharedKeyClientHelloExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.PskBinderEntry;
7 | import com.timtrense.quic.tls.PskIdentity;
8 |
9 | /**
10 | * Client implementation of {@link PreSharedKeyExtensionBase}
11 | *
12 | * @author Tim Trense
13 | * @see TLS 1.3 Spec/Section 4.2.11
14 | */
15 | @Data
16 | @EqualsAndHashCode( callSuper = true )
17 | public class PreSharedKeyClientHelloExtension extends PreSharedKeyExtensionBase {
18 |
19 | /**
20 | * A list of the identities that the client is willing to
21 | * negotiate with the server. If sent alongside the "early_data"
22 | * extension (see Section 4.2.10), the first identity is the one used
23 | * for 0-RTT data.
24 | *
25 | * Implementation Note: the field will be set to an empty array upon instantiation
26 | */
27 | private PskIdentity[] identities = new PskIdentity[0];
28 |
29 | /**
30 | * A series of HMAC values, one for each value in the
31 | * identities list and in the same order, computed as described
32 | * below.
33 | */
34 | private PskBinderEntry[] binders = new PskBinderEntry[0];
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/PreSharedKeyServerHelloExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | /**
7 | * Server implementation of {@link PreSharedKeyExtensionBase}
8 | *
9 | * @author Tim Trense
10 | * @see TLS 1.3 Spec/Section 4.2.11
11 | */
12 | @Data
13 | @EqualsAndHashCode( callSuper = true )
14 | public class PreSharedKeyServerHelloExtension extends PreSharedKeyExtensionBase {
15 |
16 | /**
17 | * uint16
18 | *
19 | * The server's chosen identity expressed as a
20 | * (0-based) index into the identities in the client's list.
21 | *
22 | * @see PreSharedKeyClientHelloExtension#getIdentities()
23 | */
24 | private int selectedIdentity;
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/PskKeyExchangeModeExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Extension;
7 | import com.timtrense.quic.tls.ExtensionType;
8 | import com.timtrense.quic.tls.PskKeyExchangeMode;
9 |
10 | /**
11 | * // PSK = Pre-Shared-Key.
12 | *
13 | *
14 | * struct {
15 | * PskKeyExchangeMode ke_modes<1..255>;
16 | * } PskKeyExchangeModes;
17 | *
18 | *
19 | * In order to use PSKs, clients MUST also send a
20 | * "psk_key_exchange_modes" extension. The semantics of this extension
21 | * are that the client only supports the use of PSKs with these modes,
22 | * which restricts both the use of PSKs offered in this ClientHello and
23 | * those which the server might supply via NewSessionTicket.
24 | *
25 | * A client MUST provide a "psk_key_exchange_modes" extension if it
26 | * offers a "pre_shared_key" extension. If clients offer
27 | * "pre_shared_key" without a "psk_key_exchange_modes" extension,
28 | * servers MUST abort the handshake. Servers MUST NOT select a key
29 | * exchange mode that is not listed by the client. This extension also
30 | * restricts the modes for use with PSK resumption. Servers SHOULD NOT
31 | * send NewSessionTicket with tickets that are not compatible with the
32 | * advertised modes; however, if a server does so, the impact will just
33 | * be that the client's attempts at resumption fail.
34 | *
35 | * The server MUST NOT send a "psk_key_exchange_modes" extension.
36 | *
37 | * Any future values that are allocated must ensure that the transmitted
38 | * protocol messages unambiguously identify which mode was selected by
39 | * the server; at present, this is indicated by the presence of the
40 | * "key_share" in the ServerHello.
41 | *
42 | * @author Tim Trense
43 | * @see TLS 1.3 Spec/Section 4.2.9
44 | */
45 | @Data
46 | @EqualsAndHashCode( callSuper = true )
47 | public class PskKeyExchangeModeExtension extends Extension {
48 |
49 | /**
50 | * Implementation Note: this field will be set to an empty array upon instantiation
51 | */
52 | private PskKeyExchangeMode[] keyExchangeModes = new PskKeyExchangeMode[0];
53 |
54 | @Override
55 | public ExtensionType getExtensionType() {
56 | return ExtensionType.PSK_KEY_EXCHANGE_MODES;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/RenegotiationInfoExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Extension;
7 | import com.timtrense.quic.tls.ExtensionType;
8 |
9 | /**
10 | * This document defines a new TLS extension, "renegotiation_info" (with
11 | * extension type 0xff01), which contains a cryptographic binding to the
12 | * enclosing TLS connection (if any) for which the renegotiation is
13 | * being performed. The "extension data" field of this extension
14 | * contains a "RenegotiationInfo" structure:
15 | *
16 | * struct {
17 | * opaque renegotiated_connection<0..255>;
18 | * } RenegotiationInfo;
19 | *
20 | * The contents of this extension are specified as follows.
21 | *
22 | *
42 | * This extension also can be used with Datagram TLS (DTLS) [RFC4347].
43 | * Although, for editorial simplicity, this document refers to TLS, all
44 | * requirements in this document apply equally to DTLS.
45 | *
46 | * @author Tim Trense
47 | * @see
48 | * TLS 1.3 Extension Renegotiation Info Spec/Section 3.2
49 | */
50 | @Data
51 | @EqualsAndHashCode( callSuper = true )
52 | public class RenegotiationInfoExtension extends Extension {
53 |
54 | /**
55 | * Implementation Note: the field will be set to null upon instantiation
56 | */
57 | private byte[] renegotiatedConnection=new byte[0];
58 |
59 | @Override
60 | public ExtensionType getExtensionType() {
61 | return ExtensionType.RENEGOTIATION_INFO;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/ServerSupportedVersionsExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.ProtocolVersion;
7 |
8 | /**
9 | * @author Tim Trense
10 | * @see SupportedVersionsExtensionBase
11 | */
12 | @Data
13 | @EqualsAndHashCode( callSuper = true )
14 | public class ServerSupportedVersionsExtension extends SupportedVersionsExtensionBase {
15 |
16 | private ProtocolVersion selectedVersion;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/StatusRequestOcspExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.CertificateStatusType;
7 | import com.timtrense.quic.tls.OcspExtensions;
8 | import com.timtrense.quic.tls.OcspResponderId;
9 |
10 | /**
11 | * @author Tim Trense
12 | * @see StatusRequestExtensionBase
13 | */
14 | @Data
15 | @EqualsAndHashCode( callSuper = true )
16 | public class StatusRequestOcspExtension extends StatusRequestExtensionBase {
17 |
18 | /**
19 | * In the OCSPStatusRequest, the "ResponderIDs" provides a list of OCSP
20 | * responders that the client trusts. A zero-length "responder_id_list"
21 | * sequence has the special meaning that the responders are implicitly
22 | * known to the server, e.g., by prior arrangement.
23 | */
24 | private OcspResponderId[] responderIdList;
25 | /**
26 | * "Extensions" is a DER encoding of OCSP request extensions.
27 | *
28 | * A zero-length "request_extensions" value means that there are no
29 | * extensions (as opposed to a zero-length ASN.1 SEQUENCE, which is not
30 | * valid for the "Extensions" type).
31 | */
32 | private OcspExtensions requestExtensions;
33 |
34 | public StatusRequestOcspExtension() {
35 | setStatusType( CertificateStatusType.OCSP );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/SupportedGroupsExtension.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.extensions;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Extension;
7 | import com.timtrense.quic.tls.ExtensionType;
8 | import com.timtrense.quic.tls.NamedGroup;
9 |
10 | /**
11 | * When sent by the client, the "supported_groups" extension indicates
12 | * the named groups which the client supports for key exchange, ordered
13 | * from most preferred to least preferred.
14 | * configured TLS implementations.
15 | *
16 | * Note: In versions of TLS prior to TLS 1.3, this extension was named
17 | * "elliptic_curves" and only contained elliptic curve groups. See
18 | * [RFC8422] and [RFC7919]. This extension was also used to negotiate
19 | * ECDSA curves. Signature algorithms are now negotiated independently
20 | * (see Section 4.2.3).
21 | *
22 | * Items in named_group_list are ordered according to the sender's
23 | * preferences (most preferred choice first).
24 | *
25 | * As of TLS 1.3, servers are permitted to send the "supported_groups"
26 | * extension to the client. Clients MUST NOT act upon any information
27 | * found in "supported_groups" prior to successful completion of the
28 | * handshake but MAY use the information learned from a successfully
29 | * completed handshake to change what groups they use in their
30 | * "key_share" extension in subsequent connections. If the server has a
31 | * group it prefers to the ones in the "key_share" extension but is
32 | * still willing to accept the ClientHello, it SHOULD send
33 | * "supported_groups" to update the client's view of its preferences;
34 | * this extension SHOULD contain all groups the server supports,
35 | * regardless of whether they are currently supported by the client.
36 | *
37 | * @author Tim Trense
38 | * @see TLS 1.3 Spec/Section 4.2.7
39 | */
40 | @Data
41 | @EqualsAndHashCode( callSuper = true )
42 | public class SupportedGroupsExtension extends Extension {
43 |
44 | /**
45 | * Implementation Note: the field will be set to an empty array upon instantiation
46 | */
47 | private NamedGroup[] namedGroupList = new NamedGroup[0];
48 |
49 | @Override
50 | public ExtensionType getExtensionType() {
51 | return ExtensionType.SUPPORTED_GROUPS;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/extensions/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains the implementation of the extensions for messages of TLS 1.3
3 | */
4 | package com.timtrense.quic.tls.extensions;
5 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/CertificateRequest.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.HandshakeType;
7 |
8 | /**
9 | * A server which is authenticating with a certificate MAY optionally
10 | * request a certificate from the client. This message, if sent, MUST
11 | * follow EncryptedExtensions.
12 | *
13 | * Structure of this message:
14 | *
15 | * struct {
16 | * opaque certificate_request_context<0..2^8-1>;
17 | * Extension extensions<2..2^16-1>;
18 | * } CertificateRequest;
19 | *
20 | * In prior versions of TLS, the CertificateRequest message carried a
21 | * list of signature algorithms and certificate authorities which the
22 | * server would accept. In TLS 1.3, the former is expressed by sending
23 | * the "signature_algorithms" and optionally "signature_algorithms_cert"
24 | * extensions. The latter is expressed by sending the
25 | * "certificate_authorities" extension (see Section 4.2.4).
26 | *
27 | * Servers which are authenticating with a PSK MUST NOT send the
28 | * CertificateRequest message in the main handshake, though they MAY
29 | * send it in post-handshake authentication (see Section 4.6.2) provided
30 | * that the client has sent the "post_handshake_auth" extension (see
31 | * Section 4.2.6).
32 | *
33 | * contained extensions:
34 | * A set of extensions describing the parameters of the
35 | * certificate being requested. The "signature_algorithms" extension
36 | * MUST be specified, and other extensions may optionally be included
37 | * if defined for this message. Clients MUST ignore unrecognized
38 | * extensions.
39 | *
40 | * @author Tim Trense
41 | * @see TLS 1.3 Spec/Section 4.3.2
42 | */
43 | @Data
44 | @EqualsAndHashCode( callSuper = true )
45 | public class CertificateRequest extends ServerParametersMessage {
46 |
47 | /**
48 | * An opaque string which identifies the
49 | * certificate request and which will be echoed in the client's
50 | * Certificate message. The certificate_request_context MUST be
51 | * unique within the scope of this connection (thus preventing replay
52 | * of client CertificateVerify messages). This field SHALL be zero
53 | * length unless used for the post-handshake authentication exchanges
54 | * described in Section 4.6.2. When requesting post-handshake
55 | * authentication, the server SHOULD make the context unpredictable
56 | * to the client (e.g., by randomly generating it) in order to
57 | * prevent an attacker who has temporary access to the client's
58 | * private key from pre-computing valid CertificateVerify messages.
59 | */
60 | private byte[] certificateRequestContext = new byte[0];
61 |
62 | /*
63 | extensions: see super, see class javadoc
64 | */
65 |
66 | @Override
67 | public HandshakeType getMessageType() {
68 | return HandshakeType.CERTIFICATE_REQUEST;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/EncryptedExtensions.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import com.timtrense.quic.tls.HandshakeType;
4 |
5 | /**
6 | * In all handshakes, the server MUST send the EncryptedExtensions
7 | * message immediately after the ServerHello message. This is the first
8 | * message that is encrypted under keys derived from the
9 | * server_handshake_traffic_secret.
10 | *
11 | * The EncryptedExtensions message contains extensions that can be
12 | * protected, i.e., any which are not needed to establish the
13 | * cryptographic context but which are not associated with individual
14 | * certificates. The client MUST check EncryptedExtensions for the
15 | * presence of any forbidden extensions and if any are found MUST abort
16 | * the handshake with an "illegal_parameter" alert.
17 | *
18 | * Note: This message contains information that determines the rest
19 | * of the handshake, but is encrypted with keys derived from the
20 | * server_handshake_traffic_secret. See
21 | * TLS 1.3 Spec/Section 4.3
22 | *
23 | * @author Tim Trense
24 | * @see TLS 1.3 Spec/Section 4.3.1
25 | */
26 | public class EncryptedExtensions extends ServerParametersMessage {
27 | @Override
28 | public HandshakeType getMessageType() {
29 | return HandshakeType.ENCRYPTED_EXTENSIONS;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/EndOfEarlyData.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.Handshake;
7 | import com.timtrense.quic.tls.HandshakeType;
8 |
9 | /**
10 | *
11 | * struct {} EndOfEarlyData;
12 | *
13 | * If the server sent an "early_data" extension in EncryptedExtensions,
14 | * the client MUST send an EndOfEarlyData message after receiving the
15 | * server Finished. If the server does not send an "early_data"
16 | * extension in EncryptedExtensions, then the client MUST NOT send an
17 | * EndOfEarlyData message. This message indicates that all 0-RTT
18 | * application_data messages, if any, have been transmitted and that the
19 | * following records are protected under handshake traffic keys.
20 | * Servers MUST NOT send this message, and clients receiving it MUST
21 | * terminate the connection with an "unexpected_message" alert. This
22 | * message is encrypted under keys derived from the
23 | * client_early_traffic_secret.
24 | *
25 | * @author Tim Trense
26 | * @see TLS 1.3 Spec/Section 4.4.4
27 | */
28 | @Data
29 | @EqualsAndHashCode( callSuper = true )
30 | public class EndOfEarlyData extends Handshake {
31 |
32 | @Override
33 | public HandshakeType getMessageType() {
34 | return HandshakeType.END_OF_EARLY_DATA;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/KeyExchangeMessage.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import com.timtrense.quic.tls.ExtendedHandshake;
4 |
5 | /**
6 | * Common base class for messages from
7 | * Section 4.1 "Key Exchange Messages"
8 | * of the TLS 1.3 Specification.
9 | *
10 | *
11 | * The key exchange messages are used to determine the security
12 | * capabilities of the client and the server and to establish shared
13 | * secrets, including the traffic keys used to protect the rest of the
14 | * handshake and the data.
15 | *
16 | * @author Tim Trense
17 | */
18 | public abstract class KeyExchangeMessage extends ExtendedHandshake {
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/KeyUpdate.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import com.timtrense.quic.tls.HandshakeType;
7 | import com.timtrense.quic.tls.KeyUpdateRequest;
8 |
9 | /**
10 | * Key-Update-Messages are not used by QUIC (only implemented for completeness)
11 | *
12 | *
13 | * struct {
14 | * KeyUpdateRequest request_update;
15 | * } KeyUpdate;
16 | *
17 | *
18 | * The KeyUpdate handshake message is used to indicate that the sender
19 | * is updating its sending cryptographic keys. This message can be sent
20 | * by either peer after it has sent a Finished message. Implementations
21 | * that receive a KeyUpdate message prior to receiving a Finished
22 | * message MUST terminate the connection with an "unexpected_message"
23 | * alert. After sending a KeyUpdate message, the sender SHALL send all
24 | * its traffic using the next generation of keys, computed as described
25 | * in Section 7.2. Upon receiving a KeyUpdate, the receiver MUST update
26 | * its receiving keys.
27 | *
28 | * If the request_update field is set to "update_requested", then the
29 | * receiver MUST send a KeyUpdate of its own with request_update set to
30 | * "update_not_requested" prior to sending its next Application Data
31 | * record. This mechanism allows either side to force an update to the
32 | * entire connection, but causes an implementation which receives
33 | * multiple KeyUpdates while it is silent to respond with a single
34 | * update. Note that implementations may receive an arbitrary number of
35 | * messages between sending a KeyUpdate with request_update set to
36 | * "update_requested" and receiving the peer's KeyUpdate, because those
37 | * messages may already be in flight. However, because send and receive
38 | * keys are derived from independent traffic secrets, retaining the
39 | * receive traffic secret does not threaten the forward secrecy of data
40 | * sent before the sender changed keys.
41 | *
42 | * If implementations independently send their own KeyUpdates with
43 | * request_update set to "update_requested" and they cross in flight,
44 | * then each side will also send a response, with the result that each
45 | * side increments by two generations.
46 | *
47 | * Both sender and receiver MUST encrypt their KeyUpdate messages with
48 | * the old keys. Additionally, both sides MUST enforce that a KeyUpdate
49 | * with the old key is received before accepting any messages encrypted
50 | * with the new key. Failure to do so may allow message truncation
51 | * attacks.
52 | *
53 | * @author Tim Trense
54 | * @see TLS 1.3 Spec/Section 4.6.3
55 | */
56 | @Data
57 | @EqualsAndHashCode( callSuper = true )
58 | public class KeyUpdate extends PostHandshakeMessage {
59 |
60 | /**
61 | * Indicates whether the recipient of the KeyUpdate
62 | * should respond with its own KeyUpdate. If an implementation
63 | * receives any other value, it MUST terminate the connection with an
64 | * "illegal_parameter" alert.
65 | */
66 | private KeyUpdateRequest keyUpdateRequest;
67 |
68 | @Override
69 | public HandshakeType getMessageType() {
70 | return HandshakeType.KEY_UPDATE;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/PostHandshakeMessage.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import com.timtrense.quic.tls.Handshake;
4 |
5 | /**
6 | * Common base class for messages from
7 | * Section 4.6 "Post-Handshake Messages"
8 | * of the TLS 1.3 Specification.
9 | *
10 | *
11 | * TLS also allows other messages to be sent after the main handshake.
12 | * These messages use a handshake content type and are encrypted under
13 | * the appropriate application traffic key.
14 | *
15 | *
16 | * Implementation Note: Because the term "Handshake" describes a message in the protocol,
17 | * this class extends {@link Handshake}. Confusingly Handshake means "message" here.
18 | *
19 | * @author Tim Trense
20 | */
21 | public abstract class PostHandshakeMessage extends Handshake {
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/ServerParametersMessage.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.handshake;
2 |
3 | import com.timtrense.quic.tls.ExtendedHandshake;
4 |
5 | /**
6 | * Common base class for messages from
7 | * Section 4.3 "Server Parameters"
8 | * of the TLS 1.3 Specification.
9 | *
10 | *
11 | * The next two messages from the server, EncryptedExtensions and
12 | * CertificateRequest, contain information from the server that
13 | * determines the rest of the handshake. These messages are encrypted
14 | * with keys derived from the server_handshake_traffic_secret.
15 | *
16 | * @author Tim Trense
17 | */
18 | public abstract class ServerParametersMessage extends ExtendedHandshake {
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/handshake/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains the implementation of the messages of TLS 1.3
3 | */
4 | package com.timtrense.quic.tls.handshake;
5 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/impl/ExtensionParser.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.impl;
2 |
3 | import java.nio.ByteBuffer;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.impl.exception.QuicParsingException;
7 | import com.timtrense.quic.tls.Extension;
8 | import com.timtrense.quic.tls.ExtensionCarryingHandshake;
9 |
10 | /**
11 | * Interface to parsing TLS message extensions
12 | *
13 | * @author Tim Trense
14 | */
15 | public interface ExtensionParser {
16 |
17 | /**
18 | * Parses one TLS 1.3 message from the given data on the wire.
19 | *
20 | * @param handshake the containing message of the extension to parse
21 | * @param data the raw data from the wire, positioned at the start of this extension
22 | * @param maxLength the remaining length of the buffer, that this next message could take at most
23 | * @return a parsed, valid message
24 | * @throws QuicParsingException if any parsing error occurs
25 | */
26 | Extension parseExtension(
27 | @NonNull ExtensionCarryingHandshake handshake,
28 | @NonNull ByteBuffer data,
29 | int maxLength )
30 | throws QuicParsingException;
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/impl/MessageParser.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls.impl;
2 |
3 | import java.nio.ByteBuffer;
4 | import lombok.NonNull;
5 |
6 | import com.timtrense.quic.impl.exception.QuicParsingException;
7 | import com.timtrense.quic.tls.Handshake;
8 |
9 | /**
10 | * Interface to parsing TLS messages
11 | *
12 | * @author Tim Trense
13 | */
14 | public interface MessageParser {
15 |
16 | /**
17 | * Parses one TLS 1.3 message from the given data on the wire.
18 | *
19 | * @param data the raw data from the wire, positioned at the start of this message
20 | * @param maxLength the remaining length of the buffer, that this next message could take at most
21 | * @return a parsed, valid message
22 | * @throws QuicParsingException if any parsing error occurs
23 | */
24 | Handshake parseMessage(
25 | @NonNull ByteBuffer data,
26 | int maxLength )
27 | throws QuicParsingException;
28 |
29 | void setExtensionParser( @NonNull ExtensionParser extensionParser );
30 |
31 | ExtensionParser getExtensionParser();
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/impl/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains the implementation of the logic of TLS 1.3
3 | */
4 | package com.timtrense.quic.tls.impl;
5 |
--------------------------------------------------------------------------------
/src/main/java/com/timtrense/quic/tls/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains the implementation of Transport Layer Security (TLS) Version 1.3
3 | * as described in https://tools.ietf.org/html/rfc8446 .
4 | */
5 | package com.timtrense.quic.tls;
6 |
--------------------------------------------------------------------------------
/src/test/java/com/timtrense/quic/HexByteStringConvertHelper.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | /**
4 | * Convenience helper for converting hex-strings in test cases to byte-arrays
5 | *
6 | * @author Tim Trense, Nice people from StackOverflow
7 | * @see Stack Overflow Accepted Answer
8 | */
9 | public class HexByteStringConvertHelper {
10 |
11 | public static byte[] hexStringToByteArray( String s ) {
12 | int len = s.length();
13 | byte[] data = new byte[len / 2];
14 | for ( int i = 0; i < len; i += 2 ) {
15 | data[i / 2] = (byte)( ( Character.digit( s.charAt( i ), 16 ) << 4 )
16 | + Character.digit( s.charAt( i + 1 ), 16 ) );
17 | }
18 | return data;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/java/com/timtrense/quic/StreamIdTest.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | import org.junit.Test;
4 |
5 | import com.timtrense.quic.impl.base.StreamIdImpl;
6 |
7 | import static org.junit.Assert.assertEquals;
8 | import static org.junit.Assert.assertFalse;
9 | import static org.junit.Assert.assertTrue;
10 |
11 | public class StreamIdTest {
12 |
13 | @Test
14 | public void getId_givenId_returnsSameId() {
15 | long streamIdValue = 17;
16 | StreamId streamId = new StreamIdImpl( new VariableLengthInteger( streamIdValue ) );
17 | assertEquals( streamIdValue, streamId.getLongValue() );
18 | }
19 |
20 | @Test
21 | public void isUnidirectional_id2_true() {
22 | long streamIdValue = 2;
23 | StreamId streamId = new StreamIdImpl( new VariableLengthInteger( streamIdValue ) );
24 | assertTrue( streamId.isUnidirectional() );
25 | assertFalse( streamId.isBidirectional() );
26 | }
27 |
28 | @Test
29 | public void isUnidirectional_id1_false() {
30 | long streamIdValue = 1;
31 | StreamId streamId = new StreamIdImpl( new VariableLengthInteger( streamIdValue ) );
32 | assertFalse( streamId.isUnidirectional() );
33 | assertTrue( streamId.isBidirectional() );
34 | }
35 |
36 | @Test
37 | public void isClientInitiated_id2_true() {
38 | long streamIdValue = 2;
39 | StreamId streamId = new StreamIdImpl( new VariableLengthInteger( streamIdValue ) );
40 | assertTrue( streamId.isClientInitiated() );
41 | assertFalse( streamId.isServerInitiated() );
42 | }
43 |
44 | @Test
45 | public void isClientInitiated_id1_false() {
46 | long streamIdValue = 1;
47 | StreamId streamId = new StreamIdImpl( new VariableLengthInteger( streamIdValue ) );
48 | assertFalse( streamId.isClientInitiated() );
49 | assertTrue( streamId.isServerInitiated() );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/com/timtrense/quic/VariableLengthIntegerTest.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | import org.junit.Test;
6 |
7 | import static org.junit.Assert.assertEquals;
8 |
9 | public class VariableLengthIntegerTest {
10 |
11 | @Test
12 | public void getEncodedLengthInBytes_ofAllByteLengthConstants_isAsClaimedByName() {
13 | assertEquals( 1, VariableLengthInteger.MIN_VALUE_1BYTE.getEncodedLengthInBytes() );
14 | assertEquals( 1, VariableLengthInteger.MAX_VALUE_1BYTE.getEncodedLengthInBytes() );
15 |
16 | assertEquals( 2, VariableLengthInteger.MIN_VALUE_2BYTE.getEncodedLengthInBytes() );
17 | assertEquals( 2, VariableLengthInteger.MAX_VALUE_2BYTE.getEncodedLengthInBytes() );
18 |
19 | assertEquals( 4, VariableLengthInteger.MIN_VALUE_4BYTE.getEncodedLengthInBytes() );
20 | assertEquals( 4, VariableLengthInteger.MAX_VALUE_4BYTE.getEncodedLengthInBytes() );
21 |
22 | assertEquals( 8, VariableLengthInteger.MAX_VALUE_8BYTE.getEncodedLengthInBytes() );
23 | assertEquals( 8, VariableLengthInteger.MAX_VALUE_8BYTE.getEncodedLengthInBytes() );
24 | }
25 |
26 | @Test
27 | public void getEncodedLengthInBytes_ofMinConstants_is1Byte() {
28 | assertEquals( 1, VariableLengthInteger.MIN_VALUE_1BYTE.getEncodedLengthInBytes() );
29 | assertEquals( 1, VariableLengthInteger.MIN_VALUE.getEncodedLengthInBytes() );
30 | assertEquals( 1, VariableLengthInteger.ZERO.getEncodedLengthInBytes() );
31 | }
32 |
33 | @Test
34 | public void getEncodedLengthInBytes_ofMaxConstants_is8Byte() {
35 | assertEquals( 8, VariableLengthInteger.MAX_VALUE_8BYTE.getEncodedLengthInBytes() );
36 | assertEquals( 8, VariableLengthInteger.MAX_VALUE.getEncodedLengthInBytes() );
37 | }
38 |
39 | @Test
40 | public void encodingAndDecoding_ofAllConstants_remainsValue() {
41 | VariableLengthInteger[] values = new VariableLengthInteger[]{
42 | VariableLengthInteger.ZERO,
43 | VariableLengthInteger.MIN_VALUE,
44 | VariableLengthInteger.MAX_VALUE,
45 | VariableLengthInteger.MIN_VALUE_1BYTE,
46 | VariableLengthInteger.MAX_VALUE_1BYTE,
47 | VariableLengthInteger.MIN_VALUE_2BYTE,
48 | VariableLengthInteger.MAX_VALUE_2BYTE,
49 | VariableLengthInteger.MIN_VALUE_4BYTE,
50 | VariableLengthInteger.MAX_VALUE_4BYTE,
51 | VariableLengthInteger.MIN_VALUE_8BYTE,
52 | VariableLengthInteger.MAX_VALUE_8BYTE
53 | };
54 |
55 | for ( VariableLengthInteger i : values ) {
56 | ByteBuffer byteBuffer = ByteBuffer.allocate( 8 /*maximum required number of bytes*/ );
57 | i.encode( byteBuffer );
58 | byteBuffer.rewind();
59 | VariableLengthInteger iTest = VariableLengthInteger.decode( byteBuffer );
60 | assertEquals( i, iTest );
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/com/timtrense/quic/impl/packets/InitialPacketImplTest.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.impl.packets;
2 |
3 | import org.junit.Test;
4 |
5 | import com.timtrense.quic.ProtocolVersion;
6 | import com.timtrense.quic.VariableLengthInteger;
7 | import com.timtrense.quic.impl.base.ConnectionIdImpl;
8 | import com.timtrense.quic.impl.base.PacketNumberImpl;
9 |
10 | import static org.junit.Assert.assertEquals;
11 | import static org.junit.Assert.assertFalse;
12 | import static org.junit.Assert.assertTrue;
13 |
14 | public class InitialPacketImplTest {
15 |
16 | private static InitialPacketImpl createValid() {
17 | InitialPacketImpl validPacket = new InitialPacketImpl();
18 | validPacket.setFlags( (byte)0b11000001 );
19 | validPacket.setVersion( ProtocolVersion.ONE );
20 | validPacket.setPacketNumber( new PacketNumberImpl( 0L ) );
21 | validPacket.setDestinationConnectionIdLength( 1 );
22 | validPacket.setDestinationConnectionId( new ConnectionIdImpl(
23 | new byte[]{1}, com.timtrense.quic.VariableLengthInteger.ZERO ) );
24 | validPacket.setSourceConnectionIdLength( 1 );
25 | validPacket.setSourceConnectionId( new ConnectionIdImpl(
26 | new byte[]{2}, com.timtrense.quic.VariableLengthInteger.ZERO ) );
27 | validPacket.setTokenLength( VariableLengthInteger.ZERO );
28 | validPacket.setToken( null );
29 | validPacket.setDeclaredPayloadLength( VariableLengthInteger.ZERO );
30 | return validPacket;
31 | }
32 |
33 | @Test
34 | public void isPacketValid_defaultValidValues_true() {
35 | InitialPacketImpl validPacket = createValid();
36 | assertTrue( validPacket.isPacketValid() );
37 | }
38 |
39 | @Test
40 | public void isPacketValid_mismatchingHeaderTypeBits_false() {
41 | InitialPacketImpl validPacket = createValid();
42 | validPacket.setFlags( (byte)0b11010001 );
43 | assertFalse( validPacket.isPacketValid() );
44 | }
45 |
46 | @Test
47 | public void getPacketLength_defaultValidValues_13() {
48 | InitialPacketImpl validPacket = createValid();
49 | assertEquals( 13, validPacket.getPacketLength() );
50 | }
51 |
52 | @Test
53 | public void getPacketNumberLength_defaultValidValues_2() {
54 | InitialPacketImpl validPacket = createValid();
55 | assertEquals( 2, validPacket.getPacketNumberLength() );
56 | }
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/com/timtrense/quic/tls/MessageParserImplTest.java:
--------------------------------------------------------------------------------
1 | package com.timtrense.quic.tls;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | import org.junit.BeforeClass;
6 | import org.junit.Test;
7 |
8 | import com.timtrense.quic.HexByteStringConvertHelper;
9 | import com.timtrense.quic.impl.exception.QuicParsingException;
10 | import com.timtrense.quic.tls.impl.ExtensionParserImpl;
11 | import com.timtrense.quic.tls.impl.MessageParserImpl;
12 |
13 | import static org.junit.Assert.assertNotNull;
14 |
15 | public class MessageParserImplTest {
16 |
17 | private static byte[] cryptoPayloadAppendixA;
18 |
19 | @BeforeClass
20 | public static void prepareProtectedIntialPacket() {
21 |
22 | String hexdumpFromAppendixA =
23 | // skip 060040f1 as these are the encoded preceding crypto frame fields
24 | " 010000ed0303ebf8fa56f129 39b9584a3896472ec40bb863cfd3e868" +
25 | " 04fe3a47f06a2b69484c00 00 04 1301 1302 01 00 00 c0 00 0000 10 000e00000b6578" +
26 | " 616d706c652e636f6d ff01 00 01 00 000a 0008 0006 001d 0017 0018" +
27 | " 0010 0007 0005 04 616c706e" + // ALPN
28 | " 0005 0005 01 0000 0000" + // Certificate Status Request
29 | " 0033 0026 0024 001d 0020" + // Key Share
30 | " 9370b2c9caa47fba" +
31 | " baf4559fedba753d" +
32 | " e171fa71f50f1ce1" +
33 | " 5d43e994ec74d748" +
34 | " 002b 0003 02 0304 " + // Supported Versions
35 | " 000d 0010 000e 0403 0503 0603 0203 0804 0805 0806 " + // Signature Algorithms
36 | " 002d 0002 01 01 " + // Key Exchange Modes
37 | " 001c 0002 4001 " + // Record Size Limit
38 | " ffa5 0032" + // QUIC Transport Parameters
39 | " 04 08 ffffffffffffffff" + // .. INITIAL_MAX_DATA
40 | " 05 04 8000ffff" + // .. INITIAL_MAX_STREAM_DATA_BIDI_LOCAL
41 | " 07 04 8000ffff" + // .. INITIAL_MAX_STREAM_DATA_UNI
42 | " 08 01 10" + // .. INITIAL_MAX_STREAMS_BIDI
43 | " 01 04 80007530" + // .. MAX_IDLE_TIMEOUT
44 | " 09 01 10 " + // .. INITIAL_MAX_STREAMS_UNI
45 | " 0f 08 8394c8f03e515708" + // .. INITIAL_SOURCE_CONNECTION_ID
46 | " 06 04 8000ffff"; // .. INITIAL_MAX_STREAM_DATA_BIDI_REMOTE
47 |
48 | hexdumpFromAppendixA = hexdumpFromAppendixA.replaceAll( " ", "" );
49 | cryptoPayloadAppendixA = HexByteStringConvertHelper.hexStringToByteArray( hexdumpFromAppendixA );
50 | }
51 |
52 | @Test
53 | public void parseMessage_givenAppendixAContent_givesClientHello() throws QuicParsingException {
54 | MessageParserImpl messageParser = new MessageParserImpl();
55 | messageParser.setExtensionParser( new ExtensionParserImpl() );
56 | ByteBuffer data = ByteBuffer.wrap( cryptoPayloadAppendixA );
57 |
58 | Handshake handshake = messageParser.parseMessage( data, cryptoPayloadAppendixA.length );
59 |
60 | assertNotNull( handshake );
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------