├── .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 | *

Operations on Streams

9 | * This document does not define an API for QUIC, but instead defines a 10 | * set of functions on streams that application protocols can rely upon. 11 | * An application protocol can assume that a QUIC implementation 12 | * provides an interface that includes the operations described in this 13 | * section. An implementation designed for use with a specific 14 | * application protocol might provide only those operations that are 15 | * used by that protocol. 16 | * 17 | * On the sending part of a stream, an application protocol can: 18 | * 19 | * 35 | * On the receiving part of a stream, an application protocol can: 36 | * 37 | * 46 | * An application protocol can also request to be informed of state 47 | * changes on streams, including when the peer has opened or reset a 48 | * stream, when a peer aborts reading on a stream, when new data is 49 | * available, and when data can or cannot be written to the stream due 50 | * to flow control. 51 | * 52 | * @author Tim Trense 53 | * @see QUIC Spec/Section 2 54 | */ 55 | public interface Stream { 56 | 57 | /** 58 | * @return the identifier of this stream, unique within a connection, not null 59 | */ 60 | StreamId getId(); 61 | 62 | /** 63 | * @return the current priority of the stream, not null 64 | */ 65 | StreamPriority getPriority(); 66 | 67 | /** 68 | * @param priority the new priority of the stream, not null 69 | */ 70 | void setPriority( StreamPriority priority ); 71 | 72 | /** 73 | * @return the state that this stream is currently in for sending data, not null 74 | */ 75 | StreamState getCurrentSendingState(); 76 | 77 | /** 78 | * @return the state that this stream is currently in for receiving data, not null 79 | */ 80 | StreamState getCurrentReceivingState(); 81 | 82 | /** 83 | * The current version of QUIC uses a credit based flow control. 84 | * 85 | * @return the flow control limits set by the peer 86 | */ 87 | CreditBasedFlowControl getSendingFlowControl(); 88 | 89 | /** 90 | * The current version of QUIC uses a credit based flow control. 91 | * 92 | * @return the flow control limits set by this endpoint 93 | */ 94 | CreditBasedFlowControl getReceivingFlowControl(); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/StreamId.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * A 62-bit unsigned integer, unique to all streams within a connection 5 | * 6 | * @author Tim Trense 7 | * @see QUIC Spec/Section 2.1 8 | */ 9 | public interface StreamId { 10 | 11 | long MASK_INITIATOR = 0b0001; 12 | long MASK_DIRECTIONALITY = 0b0010; 13 | 14 | /** 15 | * @return the value of this streams id 16 | */ 17 | VariableLengthInteger getValue(); 18 | 19 | /** 20 | * @return the value of this streams id 21 | */ 22 | default long getLongValue() { 23 | return getValue().longValue(); 24 | } 25 | 26 | /** 27 | * unmasks the streams id from the least 2 significant bits which indicate initiator and uni/bi-directionality. 28 | * 29 | * @return the always incrementing part of the id 30 | */ 31 | default long getCountingLongValue() {return getLongValue() >> 2;} 32 | 33 | /** 34 | * masks the streams id on the least 2 significant bits which indicate initiator and uni/bi-directionality. 35 | * 36 | * @return the part of the id which indicates initiator and directionality 37 | */ 38 | default long getMask() {return getLongValue() & ( MASK_INITIATOR | MASK_DIRECTIONALITY );} 39 | 40 | /** 41 | * the uni/bi-directionality is indicated by the send-lest-significant bit meaning 1=uni, 0=bi -directional. 42 | * 43 | * @return whether this stream can only either send or receive data 44 | * @see #isBidirectional() 45 | */ 46 | default boolean isUnidirectional() { 47 | return ( getLongValue() & MASK_DIRECTIONALITY ) == MASK_DIRECTIONALITY; 48 | } 49 | 50 | /** 51 | * returns the negated value of {@link #isUnidirectional()} 52 | * 53 | * @return whether this stream can both send and receive data 54 | * @see #isUnidirectional() 55 | */ 56 | default boolean isBidirectional() { 57 | return ( getLongValue() & MASK_DIRECTIONALITY ) == 0; 58 | } 59 | 60 | /** 61 | * the initiator of this stream is indicated by the least-significant bit meaning 1=server, 0=client 62 | * 63 | * @return whether the stream was initiated by the server 64 | * @see #isClientInitiated() 65 | */ 66 | default boolean isServerInitiated() { 67 | return ( getLongValue() & MASK_INITIATOR ) == MASK_INITIATOR; 68 | } 69 | 70 | /** 71 | * returns the negated value of {@link #isServerInitiated()} 72 | * 73 | * @return whether the stream was initiated by the client 74 | * @see #isServerInitiated() 75 | */ 76 | default boolean isClientInitiated() { 77 | return ( getLongValue() & MASK_INITIATOR ) == 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/StreamPriority.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * A Stream Priority is an indicator for the RELATIVE priority compared to the other streams of a connection. 5 | * All streams within a connection MUST use the same (or a comparable one) implementation for their priority. 6 | * All streams start with the default value for their priority implementation, making all equally relevant. 7 | *

8 | * Comparing instances of non-comparable implementations SHOULD result in EQUAL PRIORITY (thus 9 | * {@link Comparable#compareTo(Object)} returns 0. 10 | *

11 | *

12 | *

Stream Prioritization

13 | * Stream multiplexing can have a significant effect on application 14 | * performance if resources allocated to streams are correctly 15 | * prioritized. 16 | *

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 { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/TransportParameter.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic; 2 | 3 | /** 4 | * @param the data type of the hold value 5 | * @see QUIC Spec/Section 18.2 6 | */ 7 | public interface TransportParameter { 8 | 9 | /** 10 | * @return the id of the parameter 11 | */ 12 | TransportParameterType getType(); 13 | 14 | /** 15 | * @return the length in bytes required to encode the data 16 | */ 17 | int getLength(); 18 | 19 | /** 20 | * @return the actual hold value 21 | */ 22 | T getValue(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/Connection.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import java.util.Set; 4 | 5 | import com.timtrense.quic.ConnectionId; 6 | import com.timtrense.quic.EncryptionLevel; 7 | 8 | /** 9 | * Basic abstraction of a QUIC connection 10 | * 11 | * @author Tim Trense 12 | */ 13 | public interface Connection { 14 | 15 | /** 16 | * Each QUIC connection is identified by a set of both local and remote {@link ConnectionId connection IDs}. 17 | * The local IDs are stored by the owner of this instance while the remote IDs can be controlled by the peer, 18 | * thus making them part of this connection. 19 | * 20 | * @return all known connection IDs from the remote peer 21 | */ 22 | Set getRemoteConnectionIds(); 23 | 24 | /** 25 | * QUIC packets use different levels of protection. 26 | * If already known, the user may request the protection at a specific level. 27 | * Note that by progressing in the crypto handshake (and even before any communication in this session), 28 | * the known protections may vary depending on the information and state already present on this endpoint. 29 | * 30 | * @param encryptionLevel the protection level, not null 31 | * @return the protection, if already known 32 | */ 33 | PacketProtection getPacketProtection( EncryptionLevel encryptionLevel ); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/DatagramParserState.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | /** 4 | * The state that a {@link DatagramParser} may have. 5 | * It always has one. 6 | * 7 | * @author Tim Trense 8 | */ 9 | public enum DatagramParserState { 10 | 11 | /** 12 | * INITIAL STATE. 13 | * The parser was instantiated but is not yet running 14 | */ 15 | NEW, 16 | /** 17 | * The parsers thread started 18 | */ 19 | ACTIVE, 20 | /** 21 | * TERMINAL STATE. 22 | * The parser threw an unrecoverable error 23 | */ 24 | ERROR, 25 | /** 26 | * TERMINAL STATE. 27 | * The parser stopped gracefully and the parsing thread is about to die 28 | */ 29 | STOP 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/DatagramParserStateListener.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | /** 4 | * Listener for changes of the {@link DatagramParserState} of a {@link DatagramParser} 5 | * 6 | * @author Tim Trense 7 | */ 8 | public interface DatagramParserStateListener { 9 | 10 | /** 11 | * Called with the parser still being in the old state. 12 | * 13 | * @param parser the parser that's state is transitioning 14 | * @param newState the state the parser will be in, any time after this call 15 | */ 16 | void beforeStateChange( DatagramParser parser, DatagramParserState newState ); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/DatagramRecycler.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import java.net.DatagramPacket; 4 | 5 | /** 6 | * A disposer to re-use now-unneeded instances of {@link java.net.DatagramPacket DatagramPackets} to reduce the 7 | * need to re-allocate their byte-buffers 8 | * 9 | * @author Tim Trense 10 | */ 11 | public interface DatagramRecycler { 12 | 13 | /** 14 | * indicates the datagram as not being used anymore 15 | * 16 | * @param datagramPacket the datagram to give back for re-usage 17 | * @return whether the datagram was accepted (which may not be the case if the allowed 18 | * datagrams are constraint eg. by size of their buffers) 19 | */ 20 | boolean giveBack( DatagramPacket datagramPacket ); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/Endpoint.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import lombok.Data; 6 | import lombok.NonNull; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | import com.timtrense.quic.ConnectionId; 10 | import com.timtrense.quic.EncryptionLevel; 11 | import com.timtrense.quic.EndpointRole; 12 | 13 | /** 14 | * An endpoint is the most high level access for an application to use QUIC. 15 | * 16 | * @author Tim Trense 17 | */ 18 | @Data 19 | @RequiredArgsConstructor 20 | public class Endpoint implements ParsingContext { 21 | 22 | private @NonNull EndpointRole role; 23 | /** 24 | * all configuration parameters of this endpoint 25 | */ 26 | private @NonNull EndpointConfiguration configuration = new EndpointConfiguration(); 27 | /** 28 | * Maps local {@link ConnectionId connection IDs} to the connection 29 | */ 30 | private @NonNull Map connections = new HashMap<>(); 31 | 32 | /** 33 | * Searches the connection from one of the given local connection ids 34 | * 35 | * @param connectionId a local connection id 36 | * @return the connection if found 37 | */ 38 | Connection findConnectionByLocalId( @NonNull ConnectionId connectionId ) { 39 | for ( Map.Entry entries : connections.entrySet() ) { 40 | if ( entries.getKey().equals( connectionId ) ) { 41 | return entries.getValue(); 42 | } 43 | } 44 | return null; 45 | } 46 | 47 | /** 48 | * Searches the connection from one of the given local connection ids 49 | * 50 | * @param connectionId the serialized form of a local connection id 51 | * @return the connection if found 52 | */ 53 | Connection findConnectionByLocalId( @NonNull byte[] connectionId ) { 54 | for ( Map.Entry entries : connections.entrySet() ) { 55 | if ( entries.getKey().equalsValue( connectionId ) ) { 56 | return entries.getValue(); 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | @Override 63 | public PacketProtection getPacketProtection( ConnectionId connectionId, EncryptionLevel encryptionLevel ) { 64 | Connection connection = findConnectionByLocalId( connectionId ); 65 | if ( connection == null ) { 66 | return null; 67 | } 68 | return connection.getPacketProtection( encryptionLevel ); 69 | } 70 | 71 | /** 72 | * @return a yet-unused 8 byte length connection id in serialized form 73 | */ 74 | public byte[] createRandomUnusedConnectionId() { 75 | byte[] cid = new byte[8]; 76 | do { 77 | configuration.getRandom().nextBytes( cid ); 78 | // this loop will not repeat in real life, because chances of 79 | // having a colliding 256-pow-8 random value are near to zero 80 | } while ( findConnectionByLocalId( cid ) != null ); 81 | return cid; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/EndpointConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Random; 5 | import lombok.Data; 6 | import lombok.NonNull; 7 | 8 | import com.timtrense.quic.TransportParameter; 9 | import com.timtrense.quic.impl.base.TransportParameterCollection; 10 | import com.timtrense.quic.impl.base.TransportParameterCollectionImpl; 11 | 12 | /** 13 | * All configuration parameters for an {@link Endpoint} 14 | * 15 | * @author Tim Trense 16 | */ 17 | @Data 18 | public class EndpointConfiguration { 19 | 20 | /** 21 | * The source of randomness to apply globally to the endpoint 22 | */ 23 | private @NonNull Random random = new SecureRandom(); 24 | 25 | /** 26 | * All {@link TransportParameter transport parameters} in use 27 | */ 28 | private @NonNull TransportParameterCollection transportParameters = new TransportParameterCollectionImpl(); 29 | 30 | /** 31 | * The maximum amount of bytes a datagram may carry. 32 | * Default = 1600, because most networks MTU is 1500 + a little buffer 33 | */ 34 | private int maxDatagramSize = 1600; 35 | 36 | /** 37 | * @see Receiver#getDatagramPool() 38 | * @see DatagramPool#getPoolSizeLimit() 39 | */ 40 | private int receiveDatagramQueueSizeLimit = 3; 41 | 42 | /** 43 | * @see DatagramAssembler#getDatagramPool() 44 | * @see DatagramPool#getPoolSizeLimit() 45 | */ 46 | private int sendDatagramQueueSizeLimit = 3; 47 | 48 | /** 49 | * @see DatagramParser#getParseDatagramQueueSizeLimit() 50 | */ 51 | private int parseDatagramQueueSizeLimit = 10; 52 | 53 | /** 54 | * @see Receiver#getReceivedQueueBlockTimeout() 55 | */ 56 | private int receiverReceivedQueueBlockTimeout = 1000; 57 | 58 | /** 59 | * @see DatagramAssembler#getSendQueueBlockTimeout() 60 | */ 61 | private int assemblerSendQueueBlockTimeout = 1000; 62 | 63 | /** 64 | * @see DatagramParser#getParsedQueueBlockTimeout() 65 | */ 66 | private int parsedTargetBlockingTimeout = 1000; 67 | 68 | /** 69 | * A name of the endpoint that may be used to identify it within: thread names, log messages etc. 70 | */ 71 | private String endpointName = Endpoint.class.getName(); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/FrameParser.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import lombok.NonNull; 7 | 8 | import com.timtrense.quic.Frame; 9 | import com.timtrense.quic.Packet; 10 | import com.timtrense.quic.impl.exception.QuicParsingException; 11 | 12 | /** 13 | * Parsing algorithm for frames within a packet 14 | * 15 | * @author Tim Trense 16 | */ 17 | public interface FrameParser { 18 | 19 | /** 20 | * Parses one frame in the given packet. 21 | * 22 | * @param containingPacket the packet containing those frames 23 | * @param data the data of the datagram, positioned at the start of this next frame 24 | * @param maxLength the remaining length of the packet, that this next frame could take at most 25 | * @return a parsed, valid frame 26 | * @throws QuicParsingException if any parsing error occurs 27 | */ 28 | Frame parseFrame( 29 | @NonNull Packet containingPacket, 30 | @NonNull ByteBuffer data, 31 | int frameIndex, 32 | int maxLength ) 33 | throws QuicParsingException; 34 | 35 | /** 36 | * Parses all frames in the given packet. 37 | * 38 | * @param containingPacket the packet that will contain those frames 39 | * @param data the data of the datagram, positioned at the start of the containing packet 40 | * @param packetLength the length of the packet, or -1 if the packet takes up all 41 | * data till the end of the datagram 42 | * @return all frames contained in that packet, never an incomplete or invalid list 43 | * @throws QuicParsingException if any parsing error occurs 44 | */ 45 | default List parseFrames( 46 | @NonNull Packet containingPacket, 47 | @NonNull ByteBuffer data, 48 | int packetLength ) 49 | throws QuicParsingException { 50 | if ( packetLength < 0 ) { 51 | packetLength = data.remaining(); 52 | } 53 | List payload = new LinkedList<>(); 54 | for ( int packetIndex = 0; packetLength > 0; packetIndex++ ) { 55 | Frame f = parseFrame( containingPacket, data, packetIndex, packetLength ); 56 | if ( f == null ) { 57 | payload = null; 58 | break; 59 | } 60 | payload.add( f ); 61 | packetLength -= f.getFrameLength(); 62 | } 63 | return payload; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/PacketParser.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | import com.timtrense.quic.Packet; 6 | import com.timtrense.quic.impl.exception.QuicParsingException; 7 | 8 | /** 9 | * Parsing algorithm for coalesced packets within a datagram 10 | * 11 | * @author Tim Trense 12 | */ 13 | public interface PacketParser { 14 | 15 | /** 16 | * Parses the data of the received datagram using the remainingData starting from the buffers current position. 17 | * Implementations do not need to leave the buffer untouched if a parsing error occurs. 18 | * Implementations MUST position the buffer after the last byte of the parsed packet on successful exit. 19 | * 20 | * @param receivedDatagram the received datagram 21 | * @param remainingData a buffer on the received datagrams data, positioned 22 | * at the start byte of the packet with No.=packetIndex to be parsed 23 | * @param packetIndex the index of the packet to be parsed within the datagram 24 | * @return the parsed packet. never an invalid one nor null 25 | * @throws QuicParsingException if any parsing error occurs 26 | */ 27 | Packet parsePacket( 28 | ReceivedDatagram receivedDatagram, 29 | ByteBuffer remainingData, 30 | int packetIndex 31 | ) throws QuicParsingException; 32 | 33 | /** 34 | * @return the frame parser in charge 35 | */ 36 | FrameParser getFrameParser(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/PacketProtection.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import lombok.NonNull; 4 | 5 | /** 6 | * This interface implements the most high level encryption API for protecting packets with TLS 1.3 . 7 | * 8 | * @author Tim Trense 9 | */ 10 | public interface PacketProtection { 11 | 12 | /** 13 | * Computes the header protection mask 14 | * 15 | * @param sample the initial keying material, sampled bytes from the ciphertext 16 | * @param offset offset within the given byte-array to start reading the ikm from 17 | * @param length length of the ikm 18 | * @return the header protection mask or null if it cannot be computed from the current state and sample 19 | */ 20 | byte[] deriveHeaderProtectionMask( @NonNull byte[] sample, int offset, int length ); 21 | 22 | /** 23 | * forwards {@link #deriveHeaderProtectionMask(byte[], int, int)} with offset=0 and length=sample.length. 24 | * 25 | * @param sample the initial keying material, sampled bytes from the ciphertext 26 | * @return the header protection mask or null if it cannot be computed from the current state and sample 27 | */ 28 | default byte[] deriveHeaderProtectionMask( @NonNull byte[] sample ) { 29 | return deriveHeaderProtectionMask( sample, 0, sample.length ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/ParsingContext.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl; 2 | 3 | import com.timtrense.quic.ConnectionId; 4 | import com.timtrense.quic.EncryptionLevel; 5 | import com.timtrense.quic.EndpointRole; 6 | 7 | /** 8 | * The context in which parsing happens. 9 | * The context holds all values that are required for parsing, yet not contained in 10 | * the {@link java.net.DatagramPacket datagrams} nor {@link com.timtrense.quic.Packet packets} to be parsed 11 | * 12 | * @author Tim Trense 13 | */ 14 | public interface ParsingContext { 15 | 16 | /** 17 | * indicates whether parsing is done on server or client side 18 | * 19 | * @return the role of the endpoint on which parsing is done 20 | */ 21 | EndpointRole getRole(); 22 | 23 | /** 24 | * Searches the relevant keys for encrypting or decrypting messages for that connection at 25 | * the given encryption level. 26 | *

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 { 17 | 18 | private TransportParameterType type; 19 | private byte[] value; 20 | 21 | @Override 22 | public int getLength() { 23 | return value.length; 24 | } 25 | 26 | @Override 27 | public byte[] getValue() { 28 | return value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/ConnectionIdImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import java.util.Arrays; 4 | import lombok.Data; 5 | import lombok.NonNull; 6 | 7 | import com.timtrense.quic.ConnectionId; 8 | import com.timtrense.quic.VariableLengthInteger; 9 | 10 | @Data 11 | public class ConnectionIdImpl implements ConnectionId { 12 | 13 | private final @NonNull byte[] value; 14 | private final @NonNull VariableLengthInteger sequenceNumber; 15 | 16 | @Override 17 | public byte[] getValue() { 18 | return value; 19 | } 20 | 21 | @Override 22 | public VariableLengthInteger getSequenceNumber() { 23 | return sequenceNumber; 24 | } 25 | 26 | @Override 27 | public boolean equals( Object o ) { 28 | if ( this == o ) { 29 | return true; 30 | } 31 | if ( !( o instanceof ConnectionId ) ) { 32 | return false; 33 | } 34 | ConnectionId that = (ConnectionId)o; 35 | return Arrays.equals( value, that.getValue() ); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Arrays.hashCode( value ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/DefaultStreamPriority.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.NonNull; 7 | 8 | import com.timtrense.quic.StreamPriority; 9 | 10 | /** 11 | * The default implementation of {@link com.timtrense.quic.StreamPriority} 12 | * 13 | * @author Tim Trense 14 | */ 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class DefaultStreamPriority implements StreamPriority { 19 | 20 | public static final DefaultStreamPriority BASE_PRIORITY = new DefaultStreamPriority( 0 ); 21 | public static final DefaultStreamPriority MAX_PRIORITY = new DefaultStreamPriority( Integer.MAX_VALUE ); 22 | public static final DefaultStreamPriority MIN_PRIORITY = new DefaultStreamPriority( Integer.MIN_VALUE ); 23 | 24 | private int value = 0; 25 | 26 | @Override 27 | public int compareTo( @NonNull StreamPriority o ) { 28 | if ( o instanceof DefaultStreamPriority ) { 29 | return Integer.compare( value, ( (DefaultStreamPriority)o ).value ); 30 | } 31 | else { 32 | return 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/FlagTransportParameterImpl.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 are flags, are omitted if the value is false, otherwise included, but always 11 | * have a length of zero. 12 | * 13 | * existing known flag frames are : {@link TransportParameterType#DISABLE_ACTIVE_MIGRATION} 14 | * 15 | * @author Tim Trense 16 | */ 17 | @Data 18 | @AllArgsConstructor 19 | public class FlagTransportParameterImpl implements TransportParameter { 20 | 21 | private TransportParameterType type; 22 | private boolean value; 23 | 24 | @Override 25 | public int getLength() { 26 | return 0; 27 | } 28 | 29 | @Override 30 | public Boolean getValue() { 31 | return value; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/FlowControlImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.Data; 4 | 5 | import com.timtrense.quic.CreditBasedFlowControl; 6 | import com.timtrense.quic.VariableLengthInteger; 7 | 8 | @Data 9 | public class FlowControlImpl implements CreditBasedFlowControl { 10 | 11 | private VariableLengthInteger limit; 12 | private VariableLengthInteger transferred; 13 | 14 | public FlowControlImpl( VariableLengthInteger limit ) { 15 | this.limit = limit; 16 | this.transferred = VariableLengthInteger.ZERO; 17 | } 18 | 19 | @Override 20 | public long getLimit() { 21 | return limit.getValue(); 22 | } 23 | 24 | @Override 25 | public long getTransferred() { 26 | return transferred.getValue(); 27 | } 28 | 29 | public VariableLengthInteger getVariableLengthIntegerLimit() { 30 | return limit; 31 | } 32 | 33 | public VariableLengthInteger getVariableLengthIntegerTransferred() { 34 | return transferred; 35 | } 36 | 37 | @Override 38 | public long incrementTransferred( int numberOfBytes ) { 39 | if ( numberOfBytes < 0 ) { 40 | throw new IllegalArgumentException( "Cannot increment the transferred number of bytes count by a negative" + 41 | " amount of: " + numberOfBytes ); 42 | } 43 | transferred = transferred.increment( numberOfBytes ); 44 | return transferred.longValue(); 45 | } 46 | 47 | @Override 48 | public long incrementLimit( int numberOfBytes ) { 49 | if ( numberOfBytes < 0 ) { 50 | throw new IllegalArgumentException( "Cannot increment the limit number of bytes count by a negative " + 51 | "amount of: " + numberOfBytes ); 52 | } 53 | limit = limit.increment( numberOfBytes ); 54 | return limit.longValue(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/IntegerTransportParameterImpl.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 integer values are encoded using {@link VariableLengthIntegerEncoder} 11 | * 12 | * @author Tim Trense 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | public class IntegerTransportParameterImpl implements TransportParameter { 17 | 18 | private TransportParameterType type; 19 | private long value; 20 | 21 | @Override 22 | public int getLength() { 23 | return VariableLengthIntegerEncoder.getLengthInBytes( value ); 24 | } 25 | 26 | @Override 27 | public Long getValue() { 28 | return value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/PacketNumberEncoder.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | /** 4 | * Encoder and Decoder for packet numbers with removed header protection as described 5 | * in Appendix A, QUIC Spec/Transport. 6 | * 7 | * @author Tim Trense 8 | */ 9 | public class PacketNumberEncoder { 10 | 11 | /** 12 | * Private constructor enforcing this class to not be instantiable 13 | */ 14 | private PacketNumberEncoder() {} 15 | 16 | /** 17 | * decodes a serialized packet number 18 | * 19 | * @param truncatedPacketNumber the serialized packet number 20 | * @param largestPacketNumber the largest packet number received so far 21 | * @param bitLengthOfTruncatedPacketNumber the number of bits that were used to binary-encode that packet number 22 | * @return the decoded packet number 23 | */ 24 | public static long decodePacketNumber( 25 | long truncatedPacketNumber, 26 | long largestPacketNumber, 27 | int bitLengthOfTruncatedPacketNumber 28 | ) { 29 | long expectedPacketNumber = largestPacketNumber + 1; 30 | long pnWindow = 1L << bitLengthOfTruncatedPacketNumber; 31 | long pnHalfWindow = pnWindow >> 1; // effectively dividing by 2 32 | long pnMask = -pnWindow; // effectively doing ~( pnWindow - 1 ) that is binary-inverted(window minus 1) 33 | 34 | long candidatePn = ( expectedPacketNumber & pnMask ) | truncatedPacketNumber; 35 | if ( candidatePn <= expectedPacketNumber - pnHalfWindow 36 | && candidatePn < ( 0x4000000000000000L /* 1L << 62 */ ) - pnWindow ) { 37 | candidatePn += pnWindow; 38 | } 39 | else if ( candidatePn > expectedPacketNumber + pnHalfWindow 40 | && candidatePn >= pnWindow ) { 41 | candidatePn -= pnWindow; 42 | } 43 | 44 | return candidatePn; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/PacketNumberImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import com.timtrense.quic.PacketNumber; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | public class PacketNumberImpl implements PacketNumber { 11 | 12 | private long value; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/PreferredAddressImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.Data; 4 | 5 | import com.timtrense.quic.ConnectionId; 6 | import com.timtrense.quic.PreferredAddress; 7 | import com.timtrense.quic.StatelessResetToken; 8 | 9 | /** 10 | * Default implementation of {@link PreferredAddress} 11 | * 12 | * @author Tim Trense 13 | */ 14 | @Data 15 | public class PreferredAddressImpl implements PreferredAddress { 16 | 17 | private byte[] ipv4Address; 18 | private int ipv4Port; 19 | 20 | private byte[] ipv6Address; 21 | private int ipv6Port; 22 | 23 | private ConnectionId connectionId; 24 | 25 | private StatelessResetToken statelessResetToken; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/PreferredAddressTransportParameterImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import com.timtrense.quic.PreferredAddress; 7 | import com.timtrense.quic.TransportParameter; 8 | import com.timtrense.quic.TransportParameterType; 9 | 10 | /** 11 | * Transport parameters which holds an {@link PreferredAddress} 12 | * 13 | * existing known flag frames are : {@link TransportParameterType#PREFERRED_ADDRESS} 14 | * 15 | * @author Tim Trense 16 | */ 17 | @Data 18 | @AllArgsConstructor 19 | public class PreferredAddressTransportParameterImpl implements TransportParameter { 20 | 21 | private TransportParameterType type; 22 | private PreferredAddress value; 23 | 24 | @Override 25 | public int getLength() { 26 | return 0; 27 | } 28 | 29 | @Override 30 | public PreferredAddress getValue() { 31 | return value; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/StatelessResetTokenImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | 6 | import com.timtrense.quic.StatelessResetToken; 7 | 8 | /** 9 | * Default implementation of {@link StatelessResetToken} 10 | * 11 | * @author Tim Trense 12 | */ 13 | @Data 14 | public class StatelessResetTokenImpl implements StatelessResetToken { 15 | 16 | private @NonNull byte[] value; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/StreamIdContext.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import java.util.List; 4 | 5 | import com.timtrense.quic.EndpointRole; 6 | import com.timtrense.quic.StreamId; 7 | 8 | /** 9 | * The context within which {@link StreamId StreamIds} are generated and used. 10 | * 11 | * @author Tim Trense 12 | * @see QUIC Spec/Section 2.1 13 | */ 14 | public interface StreamIdContext { 15 | 16 | /** 17 | * @return whether this context belongs to the servers side, thus creating only {@link StreamId StreamIds} 18 | * which are server-initiated 19 | */ 20 | EndpointRole getRole(); 21 | 22 | /** 23 | * creates a new {@link StreamId} that is not yet used. By specification this gives strictly monotonic 24 | * incrementing values. 25 | * 26 | * this method is not thread-safe unless synchronized on this instance. 27 | * 28 | * @param forUnidirectional whether the id should indicate a unidirectional stream (true) or a 29 | * bidirectional one (false) 30 | * @return a new, unique, not yet used {@link StreamId} 31 | */ 32 | StreamId createNewId( boolean forUnidirectional ); 33 | 34 | /** 35 | * notifies the context that a new stream id was introduced to the connection (presumably by the remote). 36 | * all remotely created stream ids must indicated the inverted value for server-initiation with respect to 37 | * this context. 38 | * when invoked, the method creates all ids of the given type (initiator and directionality) in between 39 | * the current counting step and the given id. 40 | * 41 | * this method is not thread-safe unless synchronized on this instance. 42 | * 43 | * @param streamIdValue the remotely introduced new id 44 | * @return a new {@link StreamId} instance for that value or null if that value is either invalid or already used 45 | */ 46 | StreamId notifyAboutNewId( long streamIdValue ); 47 | 48 | /** 49 | * @return an immutable view to all ids sorted by their values which naturally contains each known id only once 50 | */ 51 | List getAllStreamIds(); 52 | 53 | /** 54 | * Wrapper for {@code createNewId(true)} 55 | * 56 | * @return a new, unique, not yet used {@link StreamId}, which will always 57 | * give {@code true} for {@link StreamId#isUnidirectional()} and {@code false} for {@link StreamId#isBidirectional()} 58 | */ 59 | default StreamId createNewUnidirectionalId() { 60 | return createNewId( true ); 61 | } 62 | 63 | /** 64 | * Wrapper for {@code createNewId(false)} 65 | * 66 | * @return a new, unique, not yet used {@link StreamId}, which will always 67 | * give {@code true} for {@link StreamId#isBidirectional()} and {@code false} for {@link StreamId#isUnidirectional()} 68 | */ 69 | default StreamId createNewBidirectionalId() { 70 | return createNewId( false ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/StreamIdContextImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import java.util.Collections; 4 | import java.util.Comparator; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import lombok.Data; 8 | 9 | import com.timtrense.quic.EndpointRole; 10 | import com.timtrense.quic.StreamId; 11 | import com.timtrense.quic.VariableLengthInteger; 12 | 13 | @Data 14 | public class StreamIdContextImpl implements StreamIdContext { 15 | 16 | private final EndpointRole role; 17 | private long nextCountingValue = 0L; 18 | private List knownStreamIds = new LinkedList<>(); 19 | 20 | @Override 21 | public StreamId createNewId( boolean forUnidirectional ) { 22 | long value = nextCountingValue++; // post-increment 23 | StreamId created = new StreamIdImpl( 24 | new VariableLengthInteger( 25 | ( value << 2 ) 26 | | ( role == EndpointRole.SERVER ? StreamId.MASK_INITIATOR : 0 ) 27 | | ( forUnidirectional ? StreamId.MASK_DIRECTIONALITY : 0 ) 28 | ) 29 | ); 30 | knownStreamIds.add( created ); 31 | return created; 32 | } 33 | 34 | @Override 35 | public StreamId notifyAboutNewId( long streamIdValue ) { 36 | if ( streamIdValue < 0 ) { 37 | return null; 38 | } 39 | 40 | StreamId testId = new StreamIdImpl( new VariableLengthInteger( streamIdValue ) ); 41 | if ( testId.isServerInitiated() == ( role == EndpointRole.SERVER ) ) { 42 | // remotely created ids must indicate the inverted value for server initiated 43 | return null; 44 | } 45 | createIdsInBetween( nextCountingValue, testId.getCountingLongValue(), testId.getMask() ); 46 | knownStreamIds.add( testId ); 47 | knownStreamIds.sort( Comparator.comparing( StreamId::getLongValue ) ); 48 | if ( nextCountingValue <= streamIdValue ) { 49 | nextCountingValue = ( streamIdValue >> 2 ) + 1; 50 | } 51 | return testId; 52 | } 53 | 54 | @Override 55 | public List getAllStreamIds() { 56 | return Collections.unmodifiableList( knownStreamIds ); 57 | } 58 | 59 | private void createIdsInBetween( long currentCountingValue, long endCountingValue, long mask ) { 60 | for ( ; currentCountingValue < endCountingValue; currentCountingValue++ ) { 61 | StreamId inBetween = new StreamIdImpl( new VariableLengthInteger( ( currentCountingValue << 2 ) | mask ) ); 62 | knownStreamIds.add( inBetween ); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/StreamIdImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.Data; 4 | 5 | import com.timtrense.quic.VariableLengthInteger; 6 | 7 | @Data 8 | public class StreamIdImpl implements com.timtrense.quic.StreamId { 9 | 10 | private final VariableLengthInteger value; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/StreamImpl.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NonNull; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import com.timtrense.quic.CreditBasedFlowControl; 9 | import com.timtrense.quic.Stream; 10 | import com.timtrense.quic.StreamId; 11 | import com.timtrense.quic.StreamPriority; 12 | 13 | /** 14 | * @author Tim Trense 15 | */ 16 | @Data 17 | @RequiredArgsConstructor 18 | @AllArgsConstructor 19 | public class StreamImpl implements Stream { 20 | 21 | private final @NonNull StreamId id; 22 | private final @NonNull CreditBasedFlowControl sendingFlowControl; 23 | private final @NonNull CreditBasedFlowControl receivingFlowControl; 24 | private @NonNull StreamPriority priority = DefaultStreamPriority.BASE_PRIORITY; 25 | private @NonNull SendingStreamStateImpl currentSendingState = SendingStreamStateImpl.NEW; 26 | private @NonNull ReceivingStreamStateImpl currentReceivingState = ReceivingStreamStateImpl.NEW; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/base/TransportParameterCollection.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.base; 2 | 3 | import java.util.Collection; 4 | 5 | import com.timtrense.quic.TransportParameter; 6 | import com.timtrense.quic.TransportParameterType; 7 | 8 | /** 9 | * defines the complete transport configuration consisting of all applied parameters or their respective defaults 10 | * 11 | * @author Tim Trense 12 | */ 13 | public interface TransportParameterCollection { 14 | 15 | /** 16 | * queries the parameter or its respective default 17 | * 18 | * @param type the parameter type 19 | * @return the explicitly specified value for that parameter type or its respective default 20 | */ 21 | TransportParameter getParameter( TransportParameterType type ); 22 | 23 | /** 24 | * returns the parameters default 25 | * 26 | * @param type the parameter type 27 | * @return the explicitly specified value for that parameter type or its respective default 28 | */ 29 | TransportParameter getParameterDefault( TransportParameterType type ); 30 | 31 | /** 32 | * explicitly specifies the parameter, thus overriding the default 33 | * 34 | * @param parameter the value to specify, including the type 35 | * @return true on success, false if the given value cannot be applied to the parameter type (for instance 36 | * because the datatype mismatches or the value is out-of-bounds) 37 | */ 38 | boolean setParameter( TransportParameter parameter ); 39 | 40 | /** 41 | * resets the parameters value to its default 42 | * 43 | * @param type the parameter type 44 | */ 45 | void resetParameterValue( TransportParameterType type ); 46 | 47 | /** 48 | * @return an unmodifiable view of all explicitly applied parameters 49 | */ 50 | Collection> getAllExplicitParameters(); 51 | 52 | /** 53 | * @param type the type to know whether it was explicitly set 54 | * @return true if the respective default was overridden by setting the parameter explicitly, false if 55 | * the default is used 56 | */ 57 | boolean isExplicitlySet( TransportParameterType type ); 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/exception/MalformedDatagramException.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.exception; 2 | 3 | import java.nio.ByteBuffer; 4 | import lombok.Getter; 5 | import lombok.NonNull; 6 | 7 | import com.timtrense.quic.impl.ReceivedDatagram; 8 | 9 | /** 10 | * The datagrams content is no valid QUIC datagram payload 11 | * 12 | * @author Tim Trense 13 | */ 14 | public class MalformedDatagramException extends QuicParsingException { 15 | 16 | @Getter 17 | private final transient @NonNull ReceivedDatagram datagram; 18 | @Getter 19 | private final transient @NonNull ByteBuffer payload; 20 | 21 | public MalformedDatagramException( 22 | @NonNull ReceivedDatagram datagram, 23 | @NonNull ByteBuffer payload 24 | ) { 25 | super( "The datagrams content is not valid for a QUIC datagram" ); 26 | this.datagram = datagram; 27 | this.payload = payload; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/exception/MalformedFrameException.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.exception; 2 | 3 | import java.nio.ByteBuffer; 4 | import lombok.Getter; 5 | import lombok.NonNull; 6 | 7 | import com.timtrense.quic.Packet; 8 | 9 | /** 10 | * The frames header or content is no valid QUIC frame 11 | * 12 | * @author Tim Trense 13 | */ 14 | public class MalformedFrameException extends QuicParsingException { 15 | 16 | @Getter 17 | private final transient Packet containingPacket; 18 | @Getter 19 | private final transient @NonNull ByteBuffer payload; 20 | /** 21 | * the index of the packet within the datagram 22 | */ 23 | @Getter 24 | private final int frameIndex; 25 | 26 | public MalformedFrameException( 27 | Packet containingPacket, 28 | @NonNull ByteBuffer payload, 29 | int frameIndex 30 | ) { 31 | super( "The frames content is not valid: Frame number " + frameIndex ); 32 | this.containingPacket = containingPacket; 33 | this.payload = payload; 34 | this.frameIndex = frameIndex; 35 | } 36 | 37 | public MalformedFrameException( 38 | String message, 39 | Packet containingPacket, 40 | @NonNull ByteBuffer payload, 41 | int frameIndex 42 | ) { 43 | super( message ); 44 | this.containingPacket = containingPacket; 45 | this.payload = payload; 46 | this.frameIndex = frameIndex; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/exception/MalformedPacketException.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.exception; 2 | 3 | import java.nio.ByteBuffer; 4 | import lombok.Getter; 5 | import lombok.NonNull; 6 | 7 | import com.timtrense.quic.impl.ReceivedDatagram; 8 | 9 | /** 10 | * The datagrams content is no valid QUIC datagram payload 11 | * 12 | * @author Tim Trense 13 | */ 14 | public class MalformedPacketException extends QuicParsingException { 15 | 16 | @Getter 17 | private final transient ReceivedDatagram datagram; 18 | @Getter 19 | private final transient @NonNull ByteBuffer payload; 20 | /** 21 | * the index of the packet within the datagram 22 | */ 23 | @Getter 24 | private final int packetIndex; 25 | 26 | public MalformedPacketException( 27 | ReceivedDatagram datagram, 28 | @NonNull ByteBuffer payload, 29 | int packetIndex 30 | ) { 31 | super( "The packets content is not valid: Packet number " + packetIndex ); 32 | this.datagram = datagram; 33 | this.payload = payload; 34 | this.packetIndex = packetIndex; 35 | } 36 | 37 | public MalformedPacketException( 38 | String message, 39 | ReceivedDatagram datagram, 40 | @NonNull ByteBuffer payload, 41 | int packetIndex 42 | ) { 43 | super( message ); 44 | this.datagram = datagram; 45 | this.payload = payload; 46 | this.packetIndex = packetIndex; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/exception/MalformedTlsException.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.exception; 2 | 3 | /** 4 | * The crypto content is no valid TLS message 5 | * 6 | * @author Tim Trense 7 | */ 8 | public class MalformedTlsException extends QuicParsingException { 9 | 10 | public MalformedTlsException( 11 | String message 12 | ) { 13 | super( message ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/impl/exception/OutOfOrderProtectedPacketException.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.impl.exception; 2 | 3 | import java.nio.ByteBuffer; 4 | import lombok.Getter; 5 | import lombok.NonNull; 6 | 7 | import com.timtrense.quic.impl.ReceivedDatagram; 8 | 9 | /** 10 | * At least one packet of the datagrams content could not be parsed, 11 | * because the required decryption material was missing. 12 | *

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 | *

38 | * Hint:
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 extensions = 30 | new ArrayList<>( 6 /*server hello min size for extensions*/ ); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/tls/Extension.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.tls; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | *

 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 getExtensions(); 17 | 18 | /** 19 | * sets the internal list of carried extensions 20 | * 21 | * @param extensions the new list of carried {@link Extension extension} 22 | */ 23 | void setExtensions( @NonNull List extensions ); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/timtrense/quic/tls/Handshake.java: -------------------------------------------------------------------------------- 1 | package com.timtrense.quic.tls; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * A message within the TLS-1.3-Protocol 7 | * 8 | *
 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 | * 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 | * 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 | --------------------------------------------------------------------------------